- Implemented StkRigAdapter class to adapt a master rig to a loaded .stk dictionary. - Added methods for fitting bone lengths, recalibrating IK targets, and mounting vector shapes. - Defined constants for default proportions and bone paths. - Included error handling for missing nodes and invalid data structures.
410 lines
21 KiB
Markdown
410 lines
21 KiB
Markdown
# Phase 7 — Architectural Specification
|
||
|
||
## Overview
|
||
|
||
Phase 7 adds a **silhouette guide** to the **Whole Stickman** preview window. The guide is a semi-transparent stick figure whose joint anchors match the **rest-pose dimensions of `master_rig.tscn`**, so the user can align their body-part shapes to the same pivots the future `Skeleton2D` rig will use. The guide is a **view aid only** — it is never part of the figure, never hit-testable, and never saved to `.stk`.
|
||
|
||
Key behaviors:
|
||
|
||
- Drawn **in world space** inside `WholeStickmanPreview`, so it **moves with pan/zoom** exactly like the grid and user parts.
|
||
- Drawn **above the grid, below user parts** (so it is visible but never obscures the user's shapes).
|
||
- **13 circular joint anchors** (radius 6.0 px) at: Head, Neck, LeftShoulder, LeftElbow, LeftWrist, RightShoulder, RightElbow, RightWrist, Hips, LeftKnee, LeftAnkle, RightKnee, RightAnkle.
|
||
- **Color-coded limb sides** to prevent misassigned limb axes:
|
||
- Left Side (Left Arm / Left Leg): **Cyan/Blue**
|
||
- Right Side (Right Arm / Right Leg): **Orange/Red**
|
||
- Central Axis (Spine / Head): **Neutral White**
|
||
- Toggleable via a new **View → Show Pose Guide** menu item (checkable), default **on**, persisted to `settings.json`.
|
||
- **User decisions (approved):** guide rendered at **1:1 master-rig scale** (dwarfs the default starter parts — intentional), **on by default**, Head joint at the **head-circle center**, state **persisted** to `settings.json`, joint dots **constant 6 px screen size**.
|
||
|
||
The joint positions are **hardcoded constants** derived once from `master_rig.tscn` (not read at runtime). Rationale in §2.
|
||
|
||
---
|
||
|
||
## 1. Data Model Changes
|
||
|
||
### 1a. `.stk` file — NO changes
|
||
|
||
The silhouette guide is a view aid. It is **not** written to, and **not** read from, `.stk` files. `FILE_VERSION` stays `"1.3"`. No migration is required.
|
||
|
||
### 1b. `settings.json` — one new key
|
||
|
||
Add `show_pose_guide` (bool) to the existing settings file. `SETTINGS_VERSION` stays `"1.0"` (the pattern established in Phase 6, where `recent_colors` was added without a version bump).
|
||
|
||
```json
|
||
{
|
||
"version": "1.0",
|
||
"grid_size": 15,
|
||
"snap_to_grid": false,
|
||
"recent_colors": ["#000000"],
|
||
"show_pose_guide": true
|
||
}
|
||
```
|
||
|
||
| Key | Type | Default | Description |
|
||
|---|---|---|---|
|
||
| `show_pose_guide` | `bool` | `true` | Whether the pose silhouette guide is visible in the Whole Stickman preview. |
|
||
|
||
Load/save follows the existing `_load_settings()` / `_save_settings()` pattern (missing key → default `false`, silent; corrupt file → defaults).
|
||
|
||
### 1c. Joint positions — hardcoded (recommended)
|
||
|
||
**Recommendation: hardcode** the 13 joint positions + the head circle radius as `const` data, derived once from `master_rig.tscn`. **Do not** instantiate `master_rig.tscn` at runtime or evaluate its `Skeleton2D`/IK stack. Justification:
|
||
|
||
1. **The guide needs the *rest* pose, not the *posed* figure.** `master_rig.tscn`'s `SkeletonModificationStack2D` is enabled and contains 4× `TwoBoneIK` + 1× `LookAt` modifications. Instantiating the scene would apply IK, moving bones *away* from their rest transforms (hands toward `IK_Targets`, head toward the look-at target). The requirement explicitly asks for **rest-pose** dimensions.
|
||
2. **Zero runtime cost & no scene dependency.** Hardcoded constants are deterministic, editor-safe, and add no node/scene instantiation or potential side effects (no `RemoteTransform2D` writes, no `@tool` script instantiation).
|
||
3. **Traceable.** The source node + transform for every constant is documented in §3, so the values can be regenerated if `master_rig.tscn` changes.
|
||
|
||
The rest transforms are static authored data; reading them at runtime buys nothing and only risks IK side effects.
|
||
|
||
---
|
||
|
||
## 2. WholeStickmanPreview — Rendering
|
||
|
||
All changes live in `scripts/whole_stickman_preview.gd`. No new scene/node is required (the guide is a `_draw()` overlay, mirroring how `_draw_grid()` is already inlined).
|
||
|
||
### 2a. New constants
|
||
|
||
```gdscript
|
||
# Phase 7: pose silhouette guide
|
||
const GUIDE_JOINT_RADIUS: float = 6.0 # screen-space joint dot radius (px)
|
||
const GUIDE_SCALE: float = 1.0 # master_rig -> preview world scale (1:1, per user decision)
|
||
const GUIDE_OFFSET: Vector2 = Vector2(170.0, 580.0) # master_rig (0,0) [hips] -> preview world
|
||
const GUIDE_HEAD_RADIUS: float = 100.0 # master-space head circle radius
|
||
|
||
const GUIDE_COLOR_LEFT: Color = Color(0.35, 0.70, 1.00, 0.45) # cyan-blue, semi
|
||
const GUIDE_COLOR_RIGHT: Color = Color(1.00, 0.50, 0.20, 0.45) # orange-red, semi
|
||
const GUIDE_COLOR_CENTRAL: Color = Color(1.00, 1.00, 1.00, 0.45) # white, semi
|
||
|
||
const GUIDE_JOINT_COLOR_LEFT: Color = Color(0.35, 0.70, 1.00, 0.65)
|
||
const GUIDE_JOINT_COLOR_RIGHT: Color = Color(1.00, 0.50, 0.20, 0.65)
|
||
const GUIDE_JOINT_COLOR_CENTRAL: Color = Color(1.00, 1.00, 1.00, 0.65)
|
||
|
||
const GUIDE_JOINTS: Dictionary = {
|
||
"Hips": Vector2(0.0, 0.0),
|
||
"Neck": Vector2(0.0, -391.5),
|
||
"Head": Vector2(0.0, -463.5),
|
||
"LeftShoulder": Vector2(0.0, -248.0),
|
||
"RightShoulder": Vector2(0.0, -248.0),
|
||
"LeftElbow": Vector2(-168.0, -256.0),
|
||
"RightElbow": Vector2(168.0, -256.0),
|
||
"LeftWrist": Vector2(-168.0, -456.0),
|
||
"RightWrist": Vector2(168.0, -456.0),
|
||
"LeftKnee": Vector2(-95.0, 176.0),
|
||
"RightKnee": Vector2(95.0, 176.0),
|
||
"LeftAnkle": Vector2(-96.0, 376.0),
|
||
"RightAnkle": Vector2(96.0, 376.0),
|
||
}
|
||
|
||
const GUIDE_LEFT_JOINTS: PackedStringArray = [
|
||
"LeftShoulder", "LeftElbow", "LeftWrist", "LeftKnee", "LeftAnkle"
|
||
]
|
||
const GUIDE_RIGHT_JOINTS: PackedStringArray = [
|
||
"RightShoulder", "RightElbow", "RightWrist", "RightKnee", "RightAnkle"
|
||
]
|
||
# any joint not in LEFT/RIGHT is central (Hips, Neck, Head)
|
||
|
||
const GUIDE_BONES: Array = [
|
||
["Hips", "Neck"], # spine (central)
|
||
["LeftShoulder", "LeftElbow"], # left upper arm
|
||
["LeftElbow", "LeftWrist"], # left forearm
|
||
["RightShoulder", "RightElbow"], # right upper arm
|
||
["RightElbow", "RightWrist"], # right forearm
|
||
["Hips", "LeftKnee"], # left thigh
|
||
["LeftKnee", "LeftAnkle"], # left shin
|
||
["Hips", "RightKnee"], # right thigh
|
||
["RightKnee", "RightAnkle"], # right shin
|
||
]
|
||
```
|
||
|
||
> The 13 joint names in `GUIDE_JOINTS` are exactly the key pivot locations required by PROJECT.md. `GUIDE_SCALE` and `GUIDE_OFFSET` are the only tunables mapping master-rig coordinates into preview world space (see §3 for the chosen values).
|
||
|
||
### 2b. New state + public API
|
||
|
||
```gdscript
|
||
var _show_guide: bool = true # Phase 7 (default ON per user decision)
|
||
|
||
func set_show_guide(enabled: bool) -> void:
|
||
_show_guide = enabled
|
||
preview_area.queue_redraw()
|
||
```
|
||
|
||
### 2c. Draw order (updated)
|
||
|
||
In `_on_preview_draw()` the order becomes:
|
||
|
||
```
|
||
1. draw_set_transform(_pan_offset, 0.0, Vector2(_zoom, _zoom))
|
||
2. _draw_grid() (existing — behind everything)
|
||
3. _draw_silhouette_guide() (NEW — above grid, below parts)
|
||
4. part loop + labels (existing — user parts)
|
||
5. selection gizmos (existing — always on top)
|
||
```
|
||
|
||
The guide is inserted **after** `_draw_grid()` and **before** the `for part_name in _part_order` loop, so user parts render over it.
|
||
|
||
### 2d. `_draw_silhouette_guide()`
|
||
|
||
```gdscript
|
||
func _draw_silhouette_guide() -> void:
|
||
if not _show_guide:
|
||
return
|
||
|
||
var lw: float = 1.5 / _zoom # match _draw_polyline_preview stroke
|
||
var jr: float = GUIDE_JOINT_RADIUS / _zoom # constant 6px screen size
|
||
|
||
# 1. Limb / spine segments (semi-transparent)
|
||
for bone: Array in GUIDE_BONES:
|
||
var a: String = bone[0] as String
|
||
var b: String = bone[1] as String
|
||
if not (GUIDE_JOINTS.has(a) and GUIDE_JOINTS.has(b)):
|
||
continue
|
||
var pa: Vector2 = _guide_to_preview(GUIDE_JOINTS[a])
|
||
var pb: Vector2 = _guide_to_preview(GUIDE_JOINTS[b])
|
||
preview_area.draw_line(pa, pb, _guide_color(a), lw)
|
||
|
||
# 2. Head outline circle (central, semi-transparent)
|
||
var head_center: Vector2 = _guide_to_preview(GUIDE_JOINTS["Head"])
|
||
preview_area.draw_arc(
|
||
head_center,
|
||
GUIDE_HEAD_RADIUS * GUIDE_SCALE,
|
||
0.0, TAU, 48,
|
||
GUIDE_COLOR_CENTRAL, lw
|
||
)
|
||
|
||
# 3. Joint anchors (constant screen size)
|
||
for joint: String in GUIDE_JOINTS:
|
||
var p: Vector2 = _guide_to_preview(GUIDE_JOINTS[joint])
|
||
preview_area.draw_circle(p, jr, _guide_joint_color(joint))
|
||
|
||
|
||
func _guide_to_preview(master_pos: Vector2) -> Vector2:
|
||
return master_pos * GUIDE_SCALE + GUIDE_OFFSET
|
||
|
||
|
||
func _guide_color(joint_name: String) -> Color:
|
||
if GUIDE_LEFT_JOINTS.has(joint_name):
|
||
return GUIDE_COLOR_LEFT
|
||
if GUIDE_RIGHT_JOINTS.has(joint_name):
|
||
return GUIDE_COLOR_RIGHT
|
||
return GUIDE_COLOR_CENTRAL
|
||
|
||
|
||
func _guide_joint_color(joint_name: String) -> Color:
|
||
if GUIDE_LEFT_JOINTS.has(joint_name):
|
||
return GUIDE_JOINT_COLOR_LEFT
|
||
if GUIDE_RIGHT_JOINTS.has(joint_name):
|
||
return GUIDE_JOINT_COLOR_RIGHT
|
||
return GUIDE_JOINT_COLOR_CENTRAL
|
||
```
|
||
|
||
Notes:
|
||
|
||
- `draw_arc` (not a filled polygon) is used for the head so the head stays outline-only and semi-transparent; the `Head` joint dot marks the head center.
|
||
- Joint dots use `GUIDE_JOINT_RADIUS / _zoom` so they are always 6 px on screen, matching the existing vertex-handle convention (`HANDLE_RADIUS / _zoom`, `ROTATION_CIRCLE_RADIUS / _zoom`). This satisfies "radius 6.0 px" literally, independent of `GUIDE_SCALE` and zoom.
|
||
- The guide is pure drawing — **no `_gui_input` hit-testing is added**, so it can never intercept part dragging/selection.
|
||
|
||
### 2e. Color specification
|
||
|
||
| Group | Joints | RGB | Alpha (lines) | Alpha (joints) |
|
||
|---|---|---|---|---|
|
||
| Left (cyan/blue) | LeftShoulder, LeftElbow, LeftWrist, LeftKnee, LeftAnkle | `(0.35, 0.70, 1.00)` | `0.45` | `0.65` |
|
||
| Right (orange/red) | RightShoulder, RightElbow, RightWrist, RightKnee, RightAnkle | `(1.00, 0.50, 0.20)` | `0.45` | `0.65` |
|
||
| Central (white) | Hips, Neck, Head | `(1.00, 1.00, 1.00)` | `0.45` | `0.65` |
|
||
|
||
---
|
||
|
||
## 3. Master-rig joint derivation
|
||
|
||
`master_rig.tscn` is a `Skeleton2D` rooted at `Master` (Node2D at origin). The `Skeleton2D` node has `rotation = -0.0006991282` and its `Torso` bone has `rotation = 0.0006991282` — these cancel to identity at sub-pixel precision, so world-space rest positions are computed as a straightforward hierarchy traversal of the `Bone2D` rest transforms (with the visual `Body/…` `Line2D`/circle nodes and `IK_Targets/*` `Marker2D` used as cross-checks).
|
||
|
||
| # | Joint | Source node | Rest position (world) | Rounded |
|
||
|---|---|---|---|---|
|
||
| 1 | Hips | `Skeleton2D/Torso` origin | `(0, 0)` | `(0, 0)` |
|
||
| 2 | Neck | `Skeleton2D/Torso/Head` origin | `(-0.13, -391.51)` | `(0, -391.5)` |
|
||
| 3 | Head | head circle center (`Body/Head`, radius 100) | `(-0.04, -463.51)` | `(0, -463.5)` |
|
||
| 4 | LeftShoulder | `Torso/LeftUpperArm` origin | `(0, -248)` | `(0, -248)` |
|
||
| 5 | RightShoulder | `Torso/RightUpperArm` origin | `(0, -248)` | `(0, -248)` |
|
||
| 6 | LeftElbow | `Torso/LeftUpperArm/LeftLowerArm` origin | `(-167.8, -256.0)` | `(-168, -256)` |
|
||
| 7 | RightElbow | `Torso/RightUpperArm/RightLowerArm` origin | `(167.8, -256.0)` | `(168, -256)` |
|
||
| 8 | LeftWrist | `IK_Targets/Left_Hand` (== forearm bone tip) | `(-168, -456)` | `(-168, -456)` |
|
||
| 9 | RightWrist | `IK_Targets/Right_Hand` | `(168, -456)` | `(168, -456)` |
|
||
| 10 | LeftKnee | `Torso/LeftUpperLeg/LeftLowerLeg` origin | `(-94.96, 176.01)` | `(-95, 176)` |
|
||
| 11 | RightKnee | `Torso/RightUpperLeg/RightLowerLeg` origin | `(94.96, 176.01)` | `(95, 176)` |
|
||
| 12 | LeftAnkle | `IK_Targets/Left_Leg` (== shin bone tip) | `(-96, 376)` | `(-96, 376)` |
|
||
| 13 | RightAnkle | `IK_Targets/Right_Leg` | `(96, 376)` | `(96, 376)` |
|
||
|
||
Derivation notes (reproducible):
|
||
|
||
- **Neck** = `Head` bone origin `(-0.12882307, -391.5079)`. This is the head/spine pivot. (The visual torso `Line2D` renders from `(0,0)` to `(0,-400)`; the neck bone sits ~8.5 px inside that top end, i.e. inside the head circle.)
|
||
- **Head** = head circle center. The head circle `Body/Head` (`Node2D` with the embedded `@tool` script, `radius = 100`) is driven by `RemoteTransform2D` under the `Head` bone at local offset `(0.0503, -72.0)`: center ≈ `(0, -391.5) + (0.05, -72) = (0, -463.5)`.
|
||
- **Elbows** = `LeftLowerArm`/`RightLowerArm` bone origins. World = parent arm origin `(0,-248)` rotated by the arm rest rotation `0.0477 rad` applied to the local offset `(±168, 0)` → `(±167.8, -256.0)`.
|
||
- **Wrists/Ankles** = the `TwoBoneIK` targets (`Left_Hand`, `Right_Hand`, `Left_Leg`, `Right_Leg` in `IK_Targets/`), which match the forearm/shin bone tips in rest pose. Forearm: elbow `(±168,-256)` → wrist `(±168,-456)` (200 px straight down). Shin: knee `(±95,176)` → ankle `(±96,376)` (200 px, ≈vertical).
|
||
|
||
### Coordinate mapping to preview space
|
||
|
||
Master-rig space is large (≈ 336 × 940 px: x ∈ [-168, 168], y ∈ [-563.5, 376]). Preview world space is the "panel pixel" space where default parts live (`DEFAULT_POSITIONS` around x ∈ [130, 170], y ∈ [40, 210]).
|
||
|
||
**User decision: 1:1 scale.** `GUIDE_SCALE = 1.0` + `GUIDE_OFFSET = (170, 580)` anchors the guide's hips at world `(170, 580)` so the head top lands just below the panel title at default zoom/pan (`y ≈ 16.5`), while the full figure (≈ 336 × 940 px, feet at `y ≈ 956`) extends past the panel bottom and must be reached by panning/zooming out — the accepted "dwarfs the default starter parts" trade-off:
|
||
|
||
| Guide joint | Preview-world position |
|
||
|---|---|
|
||
| Hips | `(170, 580)` |
|
||
| Neck | `(170, 188.5)` |
|
||
| Head center (r 100) | `(170, 116.5)` |
|
||
| Shoulders | `(170, 332)` |
|
||
| Elbows | `(2, 324)` / `(338, 324)` |
|
||
| Wrists | `(2, 124)` / `(338, 124)` |
|
||
| Knees | `(75, 756)` / `(265, 756)` |
|
||
| Ankles | `(74, 956)` / `(266, 956)` |
|
||
|
||
The user aligns their parts to the guide by dragging them onto it (or pans/zooms the guide to a convenient spot — it is a world-space fixture, so panning moves it with the grid). `GUIDE_SCALE`/`GUIDE_OFFSET` remain the two tunable constants if a different size is wanted later.
|
||
|
||
---
|
||
|
||
## 4. StickmanEditor — UI & persistence
|
||
|
||
### 4a. View menu
|
||
|
||
In `_setup_menu_bar()`, extend the existing View `PopupMenu` (currently only "Reset Views", id 0) with a **checkable** item:
|
||
|
||
```
|
||
View
|
||
──────────────
|
||
Reset Views (id 0)
|
||
──────────────
|
||
Show Pose Guide (✓) (id 1, checkable)
|
||
```
|
||
|
||
Mirror the existing Snap-to-Grid pattern: store the view menu as a member (`var _view_menu: PopupMenu`), connect `about_to_popup` to refresh the label, and toggle state in `_on_view_menu_id_pressed`.
|
||
|
||
```gdscript
|
||
# _setup_menu_bar()
|
||
var view_menu: PopupMenu = PopupMenu.new()
|
||
view_menu.name = "ViewMenu"
|
||
view_menu.add_item("Reset Views", 0)
|
||
view_menu.add_separator()
|
||
view_menu.add_item(_guide_menu_label(), 1)
|
||
view_menu.set_item_checked(1, _show_guide)
|
||
view_menu.id_pressed.connect(_on_view_menu_id_pressed)
|
||
view_menu.about_to_popup.connect(_on_view_menu_about_to_popup)
|
||
_menu_bar.add_child(view_menu)
|
||
_menu_bar.set_menu_title(_menu_bar.get_menu_count() - 1, "View")
|
||
_view_menu = view_menu
|
||
```
|
||
|
||
```gdscript
|
||
func _on_view_menu_id_pressed(id: int) -> void:
|
||
match id:
|
||
0: # Reset Views
|
||
for panel in _body_part_panels.values():
|
||
if panel is BodyPartPanel:
|
||
(panel as BodyPartPanel).reset_view()
|
||
_whole_preview.reset_view()
|
||
1: # Show Pose Guide (checkable)
|
||
_show_guide = not _show_guide
|
||
_save_settings()
|
||
_broadcast_settings()
|
||
_update_guide_menu_item()
|
||
|
||
|
||
func _on_view_menu_about_to_popup() -> void:
|
||
_update_guide_menu_item()
|
||
|
||
|
||
func _update_guide_menu_item() -> void:
|
||
if _view_menu:
|
||
_view_menu.set_item_checked(1, _show_guide)
|
||
|
||
|
||
func _guide_menu_label() -> String:
|
||
return "Show Pose Guide"
|
||
```
|
||
|
||
(Use `set_item_checked()` + a label for the checkable item; the existing Snap-to-Grid item uses a text `[√]` prefix instead — either is acceptable, but `set_item_checked` is the cleaner Godot idiom. This spec uses `set_item_checked`.)
|
||
|
||
### 4b. Settings state + broadcast
|
||
|
||
Add `var _show_guide: bool = true` to the editor, extend `_load_settings()` / `_save_settings()` with the `show_pose_guide` key, and push the value to the preview in `_broadcast_settings()`:
|
||
|
||
```gdscript
|
||
# _load_settings()
|
||
_show_guide = bool(d.get("show_pose_guide", true))
|
||
|
||
# _save_settings()
|
||
var data := {
|
||
"version": SETTINGS_VERSION,
|
||
"grid_size": _grid_size,
|
||
"snap_to_grid": _snap_enabled,
|
||
"recent_colors": _recent_colors,
|
||
"show_pose_guide": _show_guide,
|
||
}
|
||
|
||
# _broadcast_settings() (append)
|
||
_whole_preview.set_show_guide(_show_guide)
|
||
```
|
||
|
||
Because `_broadcast_settings()` is already called in `_ready()` (after `_load_settings()`) and whenever grid/snap changes, the guide visibility is correctly re-applied on startup and on every toggle.
|
||
|
||
---
|
||
|
||
## 5. Files Modified
|
||
|
||
| File | Changes |
|
||
|---|---|
|
||
| `scripts/whole_stickman_preview.gd` | Add guide constants, `_show_guide` state, `set_show_guide()`, `_draw_silhouette_guide()` + `_guide_to_preview()`/`_guide_color()`/`_guide_joint_color()` helpers; call guide draw between grid and parts in `_on_preview_draw()`. |
|
||
| `scripts/stickman_editor.gd` | Add `_show_guide` state, `_view_menu` member, "Show Pose Guide" checkable View menu item (id 1), `_on_view_menu_about_to_popup`/`_update_guide_menu_item`, settings key `show_pose_guide`, broadcast to preview. |
|
||
| `docs/phase7_spec.md` | This file. |
|
||
| `README.md` | Document the guide, the View menu item, and the settings key (see §7). |
|
||
|
||
No `.tscn` changes are required.
|
||
|
||
---
|
||
|
||
## 6. Edge Cases & Constraints
|
||
|
||
- **Guide vs. pan/zoom** — the guide is drawn under `draw_set_transform(_pan_offset, 0, Vector2(_zoom, _zoom))`, so it pans/zooms with everything else. Joint dots use `6.0 / _zoom`, staying a constant 6 px on screen (consistent with existing handles).
|
||
- **Guide is never interactive** — no hit-testing is added; left-click part selection and right-click context menus are unaffected. The guide cannot block a part the user is trying to grab (parts render over it and win hit-tests).
|
||
- **Clipping** — `PreviewArea` already has `clip_contents = true`, so the guide clips to the panel bounds like the grid and parts.
|
||
- **Empty preview** — the guide renders even when no parts exist (it is independent of `_part_shapes`), which is desirable: the user sees the pose before drawing anything.
|
||
- **Min zoom (0.3)** — the figure shrinks but joint dots stay 6 px; dots will appear relatively large vs. the figure at extreme zoom-out. This matches existing handle behavior and is acceptable for an anchor aid.
|
||
- **Head circle vs. neck** — the head circle (r 100 in preview space at 1:1) overlaps the neck joint, faithfully mirroring `master_rig.tscn` (head center sits 72 px below the neck in master space). This is intentional.
|
||
- **`Reset Views`** — resets zoom/pan only; it does **not** change guide visibility (the toggle is independent state).
|
||
- **Clear / Load** — do not touch `_show_guide`; the toggle is a persistent editor preference, not figure state.
|
||
- **Settings missing/corrupt** — `show_pose_guide` falls back to `true` (the new default), matching `_load_settings()`'s existing behavior.
|
||
|
||
---
|
||
|
||
## 7. Testing / Verification
|
||
|
||
There is no automated test suite in the repo (only a sample figure `stickmen/test.stk`). Verification is manual in the Godot editor, plus the project parse check:
|
||
|
||
1. **Load check** — run the project; no parse errors; main scene opens.
|
||
2. **Toggle** — View → Show Pose Guide is checked by default and the guide is visible; clicking it unchecks it and hides the guide; clicking again re-shows it.
|
||
3. **Visuals** — with the guide on: 13 joint dots; left limbs cyan/blue, right limbs orange/red, spine+head white; everything semi-transparent (see-through grid behind it).
|
||
4. **Draw order** — draw a part; confirm it renders **over** the guide; confirm the guide renders **over** the grid.
|
||
5. **Pan/zoom** — middle-drag and wheel-zoom the preview; the guide tracks the grid and parts exactly; joint dots stay ~6 px.
|
||
6. **Non-interference** — with the guide visible, left-click-drag a part and right-click for the context menu; behavior is unchanged.
|
||
7. **Persistence** — the guide is on by default; disable it, close and reopen the project; it stays disabled (settings.json contains `"show_pose_guide": false`).
|
||
8. **Clear/Load** — clearing or loading a figure leaves the guide setting unchanged.
|
||
|
||
> Note: `..\Godot_v4.7.1-stable_win64_console.exe . --check-only` could not be run — the executable is not present under `C:\Godot4\` and no shell tool is available to the architect. The developer should run it before implementation.
|
||
|
||
---
|
||
|
||
## 8. README Updates
|
||
|
||
- **Overview / menu section** — add "Show Pose Guide" under View; describe the color-coded, semi-transparent silhouette and its 13 joint anchors.
|
||
- **New subsection** (e.g. "Pose Silhouette Guide") — explain purpose (align parts to the future `Skeleton2D` rig pivots from `master_rig.tscn`), the color coding (left = cyan, right = orange, central = white), that it's drawn behind parts and tracks pan/zoom, and that it's a view aid not saved to `.stk`.
|
||
- **Settings table** — add `show_pose_guide` key (bool, default `true`).
|
||
- **Menu bar structure diagram** — add the View → Show Pose Guide entry.
|
||
|
||
---
|
||
|
||
## 9. Recommended Implementation Order
|
||
|
||
1. `whole_stickman_preview.gd` — constants, `_show_guide`, `set_show_guide()`, draw helpers, wire into `_on_preview_draw()`.
|
||
2. `stickman_editor.gd` — `_show_guide` state + View menu item + handlers.
|
||
3. Settings persistence (`_load_settings`/`_save_settings`/`_broadcast_settings`).
|
||
4. Manual verification (list in §7) + `--check-only`.
|
||
5. README update.
|