Files
stickman/README.md
T
ryan 1252728c06 feat: Implement Stickman Factory and Test Harness for runtime rigging
- Added StickmanFactory to load and instantiate .stk files into master_rig.tscn.
- Created a Test Harness scene for debugging bone scales, vector drawing offsets, and IK limits.
- Updated stk_rig_adapter to fit head bone and mount head shapes as full geometry.
- Enhanced README and PROJECT documentation to reflect new features and usage.
- Introduced UI elements for file selection and visual debugging in the Test Harness.
- Refactored code to improve clarity and maintainability, including removal of unused functions.
2026-08-18 21:44:56 -04:00

488 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Stickman Studio
A Godot 4.4 editor tool for creating and assembling stick figures. Stickman Studio lets you draw individual body-part shapes, edit vertices, assemble them into a whole stickman, and save/load your figure to a JSON file (`.stk`) for later use or animation scripting.
## Overview
The editor is organized as **11 sub-windows** in a 3-column layout:
- **Left column (5)** — Head, Left Upper Arm, Left Lower Arm, Left Upper Leg, Left Lower Leg
- **Center column (5)** — Torso, Right Upper Arm, Right Lower Arm, Right Upper Leg, Right Lower Leg
- **Right column (1)** — **Whole Stickman** preview
The editor is driven by three menus: **File** (Save / Load / Clear), **Edit** (Configure Grid... / Snap to Grid), and **View** (Reset Views / Hide or Show Pose Guide).
Each body-part panel is an independent vector drawing surface with per-panel zoom **and panning (middle-mouse drag)**. A configurable background grid helps align vertices, and snap-to-grid can be enabled for both vertex dragging and whole-figure assembly. **Each panel supports multiple shapes** with Z-ordering controls (Send Back / Bring Forward) and shape-level Copy/Paste and Mirror operations. The Whole Stickman panel assembles every part into one figure — treating all shapes in a panel as a single unit — and lets you reposition, rotate, and scale each part, reorder the parts (Z-order), and mirror them.
Drawing surfaces **clip** their content to the panel bounds: shapes and parts no longer render outside a panel's borders when dragged or panned. A selected shape can be **dragged to reposition** it within its panel.
## Requirements
| Item | Version |
|---|---|
| Engine | Godot 4.4 or newer |
| Addon | Scalable Vector Shapes 2D (`addons/curved_lines_2d/`) |
> The legacy `stick.tscn` scene (a rigged walk animation using `Skeleton2D` + IK) remains in the project root for reference but is **no longer the main scene**.
## Running the project
No CLI build, test, or lint commands are used. Run the project from the Godot editor:
1. Open the project folder in **Godot 4.4**.
2. Press **F5**, or open and run `res://scenes/stickman_editor.tscn`.
The main scene is configured as `run/main_scene="res://scenes/stickman_editor.tscn"` in `project.godot`.
## Quick Start
### 1. Create a body part
1. Locate one of the 10 body-part panels (e.g. **Head**).
2. **Right-click** anywhere inside its drawing area to open the context menu.
3. Choose a starter shape:
- **Line** — a 120 px horizontal segment (2 vertices, open shape).
- **Rectangle** — a 100 × 60 px rectangle (4 vertices, closed shape).
- **Circle** — a 12-segment polygon (12 vertices, closed shape, radius 40).
The shape draws immediately and syncs to the Whole Stickman panel.
Rendering behavior:
- **Closed shapes** (`closed: true`) fill their interior with the chosen color (`draw_colored_polygon`) and draw a 2 px outline.
- **Open shapes** (`closed: false`) render as an outline-only segment with no fill.
Body-part panels expand vertically to fill their column height.
### 2. Edit vertices
Each panel supports vertex-level editing:
1. **Select a shape****Left-click** anywhere inside a shape to select it. The outline turns white.
2. **Create a vertex** — With a shape selected, **right-click** on its outline and choose "Create Point". A new vertex (shown as a hollow rectangle) is added at the nearest edge midpoint.
3. **Drag vertices****Left-click** on any vertex handle (filled circle = original, hollow rectangle = user-created) and drag to reshape. The shape conforms in real time. Release to apply. With **Snap to Grid** enabled, the dragged vertex jumps to the nearest grid intersection.
4. **Delete a vertex****Right-click** on a vertex handle and choose **Remove Point** (see *Deleting* below).
5. **Deselect** — Left-click outside the shape or select a different panel. Only one shape is selected at a time.
**Move a whole shape (Phase 5):**
1. **Left-click** a shape **not on a vertex handle** and drag to reposition the entire shape within the panel.
2. Release to commit. All vertices translate together; the shape's Z-order is unchanged.
3. With **Snap to Grid** enabled, the dragged shape jumps to the nearest grid intersection on release.
### 3. Zoom & Pan
Scroll the mouse wheel inside any body-part panel or the Whole Stickman preview to zoom. Pan the view with the middle-mouse button:
- **Wheel up** — zoom in by 10%
- **Wheel down** — zoom out by 10%
- **Middle-mouse drag** — pan the view in any direction
- **Range** — 30% (minimum) to 300% (maximum)
- Vertex handles remain visible at all zoom levels.
- Each panel has independent zoom and pan offset.
- Panning takes priority over vertex dragging (middle-mouse press cancels any in-progress drag).
- **Content is clipped to the panel bounds (Phase 5)** — shapes, grid, and gizmos cannot render outside a panel's drawing area, even when panned or dragged beyond its edge. This keeps each panel self-contained.
- Use **View → Reset Views** to restore all panels to 100% zoom and the origin pan offset.
### 4. Assemble a stickman
1. Create shapes for every part you want in the figure.
2. In the **Whole Stickman** panel, **drag any part** to reposition it in the figure.
- Parts get a generous hit area and show a yellow highlight while being dragged.
- A short label (`H`, `T`, `LUA`, `RUA`, `LUL`, `RLL`, …) marks each part.
- Use the mouse wheel to zoom in/out and middle-mouse to pan for precise positioning.
- With **Snap to Grid** enabled, the mouse pointer snaps to the nearest grid intersection and the part follows.
3. Optionally type a name in the **Stickman Name** field at the top.
4. **Right-click a selected part (Phase 5)** to reorder or mirror it:
- **Send Back / Bring Forward** — change the part's draw order in the preview.
- **Mirror X / Mirror Y** — flip the part around its bounding-box center.
### 5. Pose Silhouette Guide
The Whole Stickman preview can render a **pose silhouette guide**: a semi-transparent, color-coded stick figure that marks the rest-pose pivot joints of the future rig defined by `master_rig.tscn`. Its 13 joint anchors (Head, Neck, Left/Right Shoulder, Left/Right Elbow, Left/Right Wrist, Hips, Left/Right Knee, Left/Right Ankle) correspond to the exact pivot locations a future `Skeleton2D` rig will use, so you can align your body-part shapes to the same pivots.
The guide is a **view aid only**: it is drawn above the grid **and in front of user parts** (ghosting over them), moves with pan/zoom, is **not** hit-testable (it never intercepts part dragging or selection), and is **not** saved to the `.stk` file.
**Color coding** (both lines and joint dots use these color groups):
| Group | Joints | Color | Line alpha | Joint alpha |
|---|---|---|---|---|
| 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` |
- **Lines** (limbs + spine) render semi-transparent with line alpha `0.45`; the **head outline** is a white semi-transparent circle of radius 100 (1:1 `master_rig.tscn` scale) centered on the Head joint.
- **Joint dots** are constant **6 px** on screen at every zoom level (`GUIDE_JOINT_RADIUS / _zoom`).
- The guide is **ON by default**. Toggle it via **View → Hide Pose Guide** / **View → Show Pose Guide** (the menu item is a dynamic, text-only label — no checkmark — reading "Hide Pose Guide" while the guide is visible and "Show Pose Guide" while it is hidden); the setting persists to `user://settings.json` (`show_pose_guide`).
**Centered at the default view:** the guide's figure bounding box is centered at the preview-area center, computed at draw time from the live preview size (`GUIDE_FIGURE_CENTER = (0, -93.75)`; `_guide_to_preview()` maps `master_pos` to `(master_pos - GUIDE_FIGURE_CENTER) * GUIDE_SCALE + preview_area.size * 0.5`). It is centered at startup and after **Reset Views** (zoom 1 / pan 0), and re-centers automatically when the window is resized. Because it remains a **world-space fixture**, panning/zooming moves it with the grid and parts.
**1:1 master-rig scale (deliberate):** the guide matches `master_rig.tscn` proportions exactly (a figure of ≈ 336 × 940 px, roughly x ∈ [-168, 168], y ∈ [-563.5, 376]). Because the default starter parts are small, the guide intentionally **dwarfs** them. At 1:1 zoom the ~940 px figure is taller than a typical panel, so the head and feet clip equally above/below the fold when centered — accepted. `GUIDE_SCALE`/`GUIDE_FIGURE_CENTER` are the tunables if a different size or placement is wanted later.
### 6. Grid & Panning
All 10 body-part panels and the Whole Stickman preview share a global background grid (default cell size **15 px**, Phase 5; previously 5 px):
1. Click **Edit → Configure Grid...** and set the grid size.
- **SpinBox** accepts 1100 px per cell.
- The setting is applied to every panel simultaneously and persists to `user://settings.json`.
2. Toggle **Edit → Snap to Grid** to enable snapping (Phase 5: the checkable menu item now reliably shows its ✓ checkmark when toggled):
- On **vertices** — while dragging a vertex handle, it jumps to the nearest grid intersection.
- On **assembly** — while dragging a part in the Whole Stickman preview, the pointer snaps and the part follows.
- On **shape drag** — while dragging a whole shape (Phase 5), it snaps on release.
- Existing vertices are *not* retroactively snapped; snapping only applies to new drags.
3. **Pan** — hold the **middle-mouse button** and drag anywhere in a panel to pan the view.
- Panning uses the same transform as zoom, and each panel keeps an independent offset.
- **View → Reset Views** restores 100% zoom and the origin offset on all panels.
4. Grid lines are drawn behind shapes and scale with pan/zoom, so they stay aligned to world coordinates.
### 7. Changing Colors
Colors are easy to change on any existing shape:
1. **Right-click** on a shape (either selected or with the cursor over it) and choose **Color...**.
2. A **ColorPicker** popup appears with the shape's current color pre-loaded.
3. Adjust the color — the shape updates live as a preview.
4. Click **OK** to commit the change or **Cancel** to revert to the original color.
The chosen color is stored in the shape's `color` field and is saved/loaded with the `.stk` file.
### 8. Deleting
Two levels of deletion are available:
**Delete an entire shape:**
1. **Right-click** on a shape (either selected or with the cursor over it) and choose **Delete**.
- Delete is only offered when a shape exists and the mouse is over it.
2. The shape is removed from the panel and the Whole Stickman preview.
**Remove a single vertex:**
1. **Right-click** on a vertex handle and choose **Remove Point**.
2. If 2 vertices remain, the shape becomes an open **line** (`closed = false`).
3. If 1 vertex or fewer remains, the entire shape is cleared.
### 9. Multiple Shapes & Z-Ordering
Each body-part panel can contain **more than one shape**. New shapes are created via the standard right-click context menu and are drawn on top of existing shapes. Shapes are drawn in Z-order (last in the list = frontmost).
**Z-ordering controls:**
- **Right-click** on a shape and choose **Send Back** to move it one step behind (earlier in draw order).
- **Right-click** on a shape and choose **Bring Forward** to move it one step ahead (later in draw order).
Z-order is preserved in the `.stk` save file (shapes are stored in draw order within the `shapes` array).
Only one shape within a panel can be selected at a time for vertex editing. Left-click a shape to select it; right-click also selects the shape under the mouse for context menu operations.
**Shape Copy & Paste (Phase 5):**
1. **Right-click** on a shape and choose **Copy** to place a deep copy of the shape on the editor-wide clipboard.
2. **Right-click** (on empty space or a shape) and choose **Paste** to insert the clipboard shape at the click position.
- Paste only appears while the clipboard is populated.
- The clipboard **persists after pasting** and is shared across **all 10 body-part panels** — a shape copied in one panel can be pasted into any other.
- The pasted shape becomes the newly selected (topmost) shape.
- **Copy → Clear → Paste** semantics: the clipboard survives figure clear.
**Shape Mirroring (Phase 5):**
1. **Right-click** on a selected shape and choose **Mirror X** or **Mirror Y**.
2. The shape's vertices are mirrored around the shape's bounding-box center.
3. Mirroring recomputes the `points` array in place — no extra fields are stored; the result is saved as normal vertex data.
### 10. Whole Stickman Manipulation (Selection, Rotation, Scale)
The Whole Stickman preview treats all shapes in a body-part panel as **one combined object**. Each part can be independently translated, rotated, and scaled.
**Selection:**
- **Left-click** on any part in the Whole Stickman preview to select it.
- A **white bounding box** appears around the selected part, along with manipulation gizmos.
- Click empty space to deselect.
**Translation:**
- **Left-click and drag** a selected part to reposition it. Snap-to-grid applies to the pointer position when enabled.
**Rotation:**
- When a part is selected, a **filled circle** appears centered below the bounding box.
- **Click and drag** the rotation circle to rotate the part around its center.
- Hold **Ctrl** while rotating to snap to **15-degree increments** (relative to the original 0° position).
- Rotation is **absolute**: 0° always means the original orientation the object was created with.
- The accumulated rotation value is saved in the `.stk` file.
**Scale:**
- When a part is selected, **cross/plus markers** appear at all four corners of the bounding box.
- **Click and drag** any corner cross to freely scale the part.
- Hold **Ctrl** while scaling to **lock the aspect ratio** (scale uniformly).
- Grid snapping snaps the corner position to the nearest grid intersection.
- Scale is **relative to the created size**: (1.0, 1.0) = original size.
- The scale value is saved in the `.stk` file. Scale may be **negative** for mirrored parts.
- **Part mirroring (Phase 5)** — right-click a selected part and choose **Mirror X** or **Mirror Y** to mirror it around its bounding-box center. This is implemented by negating the part's scale factor along the chosen axis (no vertex data is changed).
**Part Z-ordering (Phase 5):**
- Right-click a selected part and choose **Send Back** or **Bring Forward** to move it one step within the preview's draw order.
- Selection respects Z-order: hit-testing runs **front-to-back**, so the frontmost part under the pointer is selected.
- Part Z-order is saved in the `.stk` file as the top-level `part_order` array.
### 11. Save
1. Click **File → Save**.
2. Choose a location and name. The default extension is `.stk` (appended automatically if omitted).
3. Click **Save**. The figure is written as JSON.
### 12. Load
1. Click **File → Load**.
2. Select a `.stk` file.
3. On success, all body-part panels and the Whole Stickman preview are populated. On failure, an error dialog reports the problem (missing file, parse error, or unsupported version).
> v1.0, v1.1, v1.2, and v1.3 files are automatically migrated to v1.4 on load (v1.0/v1.1 single shapes wrapped in a `shapes` array, rotation defaults to 0, scale defaults to (1,1); files without `part_order` fall back to the default part order). `pivot`, `length`, and `proportions` are **write-only** metadata recomputed from live shapes on every save — never read back — so old files load unchanged and gain these keys on their next save.
### 13. Clear
1. Click **File → Clear**.
2. A confirmation dialog warns that the current stickman will be cleared.
3. Confirm to reset all panels and the preview (zoom resets to 100%, pan offset to origin).
### 14. Test Harness & Stickman Factory (Phase 9)
Phase 9 adds the **runtime pipeline** that turns a saved `.stk` file into a live, rigged `master_rig.tscn` instance — plus a standalone staging scene for debugging it in isolation. **Neither is wired into the editor.**
**Stickman Factory (`res://scripts/stickman_factory.gd`)** — the runtime entry point:
| Method | Signature | Behavior |
|---|---|---|
| `load_stk` | `static func load_stk(path: String) -> Dictionary` | Reads a `.stk` file via `FileAccess` + `JSON.parse_string`. Returns `{}` and `push_warning` on failure. |
| `spawn_from_data` | `static func spawn_from_data(stk_data: Dictionary) -> Node2D` | Instantiates `res://master_rig.tscn`, calls `StkRigAdapter.apply(stk_data, rig)`, returns the rig root. |
| `spawn` | `static func spawn(path: String) -> Node2D` | Chains `load_stk()``spawn_from_data()`. Returns `null` on empty data. |
The factory is the intended runtime API: `StickmanFactory.spawn("res://stickmen/basic.stk")` yields a rigged `Node2D` ready to add to the scene tree. `StkRigAdapter` (Phase 8, extended by Phase 9) now also fits the head bone (`Head.position.y = -proportions.torso_length`) and mounts the head as **full geometry** like every other part (clearing its inline `@tool` circle script).
**Test Harness (`res://scenes/test_harness.tscn`)** — run via **F6** on the scene (NOT via the main editor scene). Controls:
- **Open .stk…** — `FileDialog` filtered to `*.stk`; quick-select buttons for `res://stickmen/break.stk`, `res://stickmen/basic.stk`, `res://stickmen/test.stk`.
- **Show Bones / Show IK Handles** — checkboxes toggling the debug overlay.
- **Loaded filename** — status label showing the currently loaded file.
- **Pan / zoom** — middle-mouse drag to pan, mouse-wheel to zoom the `Camera2D`; the camera recenters on each spawn.
- **IK drag** — click and drag any of the 4 limb `Marker2D` IK targets (`IK_Targets/Left_Hand`, `Right_Hand`, `Left_Leg`, `Right_Leg`); the scene's `SkeletonModificationStack2D` TwoBoneIK flexes the limb live. The harness enables the modification stack after each spawn.
Debug overlay (a world-space `Node2D` `_draw()`): bone lines drawn between each `Bone2D` global origin and its parent's (color-coded left cyan / right orange / central white, with joint dots) when **Show Bones** is on; colored markers at the four IK targets when **Show IK Handles** is on. Each load frees the previous rig and spawns a fresh one.
## File format (`.stk`)
Files are UTF-8 JSON, pretty-printed with tab indentation. The format is versioned and designed to remain **backward/forward compatible** — new fields can be added without breaking older files.
```json
{
"version": "1.4",
"stickman_name": "Bob",
"part_order": [
"head",
"torso",
"left_upper_arm",
"left_lower_arm",
"right_upper_arm",
"right_lower_arm",
"left_upper_leg",
"left_lower_leg",
"right_upper_leg",
"right_lower_leg"
],
"proportions": {
"upper_arm_length": 168.0,
"lower_arm_length": 200.0,
"upper_leg_length": 200.0,
"lower_leg_length": 200.0,
"torso_length": 391.5
},
"body_parts": {
"head": {
"shapes": [
{
"shape_type": "circle",
"closed": true,
"points": [
{ "x": 120, "y": 40 },
{ "x": 124, "y": 39 }
],
"color": "#000000",
"vertex_flags": [0, 0, 1, 0]
}
],
"position": { "x": 150, "y": 40 },
"rotation": 0.0,
"scale": { "x": 1.0, "y": 1.0 },
"pivot": { "x": 122.0, "y": 39.5 },
"length": 10.0
}
},
"metadata": {
"created_at": "2026-08-05T12:00:00",
"modified_at": "2026-08-05T12:00:00"
}
}
```
### Root object
| Key | Type | Description |
|---|---|---|
| `version` | `string` | Format version. Currently `"1.4"`. Loading supports `"1.0"``"1.4"` (auto-migrated). |
| `stickman_name` | `string` | Optional display name for the figure. |
| `part_order` | `array[string]` | **Phase 5.** Render/Z-order of parts in the Whole Stickman preview, front-to-back semantics per array position (first = back, last = front). Absent on v1.0v1.2 files; defaults to the internal part-key order when missing. |
| `proportions` | `object` | **Phase 8.** Rig bone lengths used by the runtime `StkRigAdapter`. Object with 5 float keys: `upper_arm_length` (168.0), `lower_arm_length` (200.0), `upper_leg_length` (200.0), `lower_leg_length` (200.0), `torso_length` (391.5). Hardcoded **master-rig rest-pose constants** (from `master_rig.tscn`), not measured from the user's shapes. Write-only metadata — never read back on load. |
| `body_parts` | `object` | Map of `body_part_name → shape` objects. Keyed by the 10 internal part names below. |
| `metadata.created_at` | `string` | Timestamp written on save. |
| `metadata.modified_at` | `string` | Timestamp written on save. |
### Part keys
`head`, `torso`, `left_upper_arm`, `left_lower_arm`, `right_upper_arm`, `right_lower_arm`, `left_upper_leg`, `left_lower_leg`, `right_upper_leg`, `right_lower_leg`
### Part object
Each body part is an object with the following keys:
| Key | Type | Description |
|---|---|---|
| `shapes` | `array` | Array of shape objects (see below). Draw order = array order (first = back, last = front). Wrapped as a single-element array when migrating v1.0/v1.1 files. |
| `position` | `object` | `{ "x": float, "y": float }` position of the part within the Whole Stickman preview. |
| `rotation` | `float` | Rotation in degrees. `0.0` = original orientation. Defaults to `0.0` for v1.0/v1.1 files. |
| `scale` | `object` | `{ "x": float, "y": float }` scale factors relative to created size. `1.0` = original size. May be **negative** (Phase 5) to represent mirroring along an axis. Defaults to `{ "x": 1.0, "y": 1.0 }` for older files. |
| `pivot` | `object` | **Phase 8.** `{ "x": float, "y": float }` — the **local rotation origin**, i.e. the bounding-box center of all shape points in the part's local drawing space (before `position` is applied). Always written for all 10 parts; `{ "x": 0.0, "y": 0.0 }` for an empty part. Write-only metadata — never read back on load. |
| `length` | `float` | **Phase 8.** The part's bounding-box extent along its **segment axis**, in local pixels: **width** (`max_x - min_x`) for the 4 arm parts, **height** (`max_y - min_y`) for torso, legs, and head. `0.0` for an empty part. Write-only metadata — never read back on load. |
### Shape object
Each entry in the `shapes` array:
| Key | Type | Description |
|---|---|---|
| `shape_type` | `string` | `"line"`, `"rectangle"`, `"circle"`, or `""` (empty). Descriptive tag only — rendering uses `closed`. |
| `closed` | `bool` | Whether the polygon is closed (filled) or open (outline-only). |
| `points` | `array` | Array of `{ "x": float, "y": float }` vertex positions **local to the drawing area**. |
| `color` | `string` | Hex colour, e.g. `"#000000"`. Changeable in-app via right-click → **Color...** (Phase 3). |
| `vertex_flags` | `array[int]` | Same length as `points`. `0` = original vertex, `1` = user-created via "Create Point". Controls handle appearance. |
> On load, parts missing from the file fall back to built-in default preview positions. v1.0 files have `closed` inferred from `shape_type` and `vertex_flags` default to all zeros. v1.0/v1.1 files have their single shape wrapped in a `shapes` array and default `rotation`/`scale` applied. Files without `part_order` (v1.0v1.2) use the default part-key order. Note that shape mirroring (Phase 5) rewrites the `points` array in place, so no new shape fields are required — the resulting vertices load identically across all supported versions.
## Settings file (`user://settings.json`)
Phase 3 introduces a global settings file stored in Godot's **user data** directory (`user://`), separate from the `.stk` figure files. It persists the grid configuration and is versioned for future extensibility:
```json
{
"version": "1.0",
"grid_size": 15,
"snap_to_grid": false,
"show_pose_guide": true
}
```
### Settings keys
| Key | Type | Default | Description |
|---|---|---|---|
| `version` | `string` | `"1.0"` | Settings file version (for future extensibility). |
| `grid_size` | `int` | `15` | Grid interval in pixels, applied equally to width and height. Default changed from `5` to `15` in Phase 5. Clamped to 1100 on load. |
| `snap_to_grid` | `bool` | `false` | Whether snap-to-grid is active. |
| `show_pose_guide` | `bool` | `true` | Whether the pose silhouette guide is visible in the Whole Stickman preview. |
Behavior:
- **Load** — read on editor startup; if the file is missing or fails to parse, defaults (`grid_size = 15`, `snap_to_grid = false`, `show_pose_guide = true`) are used silently (no error dialog).
- **Save** — written whenever the user changes the grid size, toggles Snap to Grid, or toggles the pose guide.
- **Scope** — global; all body-part panels and the Whole Stickman preview share the same grid size and snap setting.
- **Pan offsets are NOT persisted** — they reset on load, clear, and "Reset Views".
## Project structure
| Path | Purpose |
|---|---|
| `res://project.godot` | Engine config; sets main scene to the editor and enabled features. |
| `res://scenes/stickman_editor.tscn` | **Main scene** — editor layout, File/Edit/View menu bar, dialogs (`GridConfigDialog` + SpinBox), column containers (unique-name nodes). |
| `res://scenes/body_part_panel.tscn` | Reusable single body-part editor panel (title, drawing area, context menu, `ColorPickerPopup`); expands vertically in its column. |
| `res://scripts/stickman_editor.gd` | Editor controller — File/Edit/View menu actions, save/load/clear, JSON v1.4 serialization with multi-shape/rotation/scale, `part_order`, and Phase 8 `proportions`/`pivot`/`length`, `settings.json` load/save, editor-wide shape clipboard (Copy/Paste across panels), broadcast of grid/snap settings to panels, Reset Views, populates panels, coordinates selection across panels. |
| `res://scripts/stk_rig_adapter.gd` | **Phase 8, extended by Phase 9.** Standalone runtime adapter (`class_name StkRigAdapter`, `static func apply(stk_data, rig)`): fits an instantiated `master_rig.tscn` to a loaded `.stk` by re-fitting the 8 limb bones (`Skeleton2D/Torso/...` `Bone2D` lengths + lower-bone origins), recalibrating the IK targets (`IK_Targets/Left|Right_Hand`, `Left|Right_Leg`), mounting the `.stk` shapes onto the `Body/*` visual nodes (open shapes → `Line2D`, closed → `Polygon2D` fill + `Line2D` outline, width 16), and (Phase 9) fitting the head bone (`Head.position.y = -proportions.torso_length`) while mounting the head as **full geometry** — it clears the head's inline `@tool` circle script and mounts `.stk` head shapes as `Line2D`/`Polygon2D`. **Not used by the editor** — consumed by the runtime pipeline. |
| `res://scripts/stickman_factory.gd` | **Phase 9.** Runtime entry point (`class_name StickmanFactory`, `extends RefCounted`); a static factory that turns a `.stk` file into a live, rigged `master_rig.tscn` instance. `load_stk(path)` reads + parses the file (`{}` + `push_warning` on failure); `spawn_from_data(stk_data)` instantiates `res://master_rig.tscn` and calls `StkRigAdapter.apply(stk_data, rig)`; `spawn(path)` chains them (`null` on empty data). **Not used by the editor.** |
| `res://scripts/test_harness.gd` | **Phase 9.** Standalone staging scene (run via **F6** on `res://scenes/test_harness.tscn`, not wired into the editor) for debugging bone scales, vector-drawing offsets, and IK limits in isolation. Top UI bar: "Open .stk…" / quick-select buttons (`stickmen/break.stk`, `stickmen/basic.stk`, `stickmen/test.stk`), "Show Bones" / "Show IK Handles" toggles, loaded-filename label. `SubViewport` world + enabled `Camera2D` (middle-mouse pan, wheel zoom, recenter on spawn); each load frees the previous rig and spawns a fresh one via `StickmanFactory.spawn()`. A world-space debug overlay draws bone lines and IK-target markers; the 4 limb `Marker2D` IK targets are click-draggable, flexing limbs live via `SkeletonModificationStack2D` TwoBoneIK (enabled after each spawn). |
| `res://scenes/test_harness.tscn` | **Phase 9.** Standalone staging scene backing `scripts/test_harness.gd` (run via **F6**; not wired into the editor). |
| `res://scripts/body_part_panel.gd` | Multi-shape creation, vertex editing, shape dragging, per-panel zoom & pan, grid drawing & snap-to-grid, ColorPicker, shape/vertex delete, Z-ordering (Send Back / Bring Forward), shape Copy/Paste, shape Mirror X/Y, drawing (fill + outline for closed shapes). |
| `res://scripts/whole_stickman_preview.gd` | Assembly preview, drag-to-reposition, part selection with white bounding box, rotation gizmo (circle below box) with Ctrl 15° snap, scale gizmo (corner crosses) with Ctrl aspect lock, part Z-ordering (Send Back / Bring Forward) via `part_order`, part Mirror X/Y (scale negation), zoom & pan, grid drawing & snap-to-grid, pose silhouette guide (Phase 7), part hit-bounds, labels. |
| `res://addons/curved_lines_2d/` | Scalable Vector Shapes 2D addon (v2.27.7) — required dependency. |
| `res://stick.tscn` | **Legacy** rigged/animated stick figure scene (Skeleton2D + IK). Not used by the editor. |
| `res://AGENTS.md` | Guidance for AI agents working in this codebase. |
### Menu bar structure
```
File Edit View
──────────── ──────────────────── ───────────
Save Configure Grid... Reset Views
Load ───────── ─────────────
──────── Snap to Grid (check) Hide/Show Pose Guide
Clear
```
- **File** — Save, Load, Clear (Phase 1).
- **Edit** — `Configure Grid...` (dialog with a 1100 SpinBox) and the checkable `Snap to Grid` toggle (Phase 3).
- **View** — `Reset Views` (resets zoom to 100% and pan offset to origin on every panel) (Phase 3) and the dynamic text-only `Hide/Show Pose Guide` toggle (Phase 7): reads **"Hide Pose Guide"** while the guide is visible and **"Show Pose Guide"** while it is hidden.
### Context menu (per body-part panel)
The right-click context menu is context-sensitive. Item IDs:
| ID | Label | Shown when |
|----|-------|------------|
| 0 | Line | Empty space right-click |
| 1 | Rectangle | Empty space right-click |
| 2 | Circle | Empty space right-click |
| 3 | Create Point | Shape under mouse, near outline |
| 4 | Color... | Shape under mouse |
| 5 | Delete | Shape under mouse |
| 6 | Remove Point | Right-click on a vertex handle |
| 7 | Send Back | Shape under mouse |
| 8 | Bring Forward | Shape under mouse |
| 9 | Copy | Shape under mouse (Phase 5) |
| 10 | Paste | Shape or empty space right-click; shown only when the clipboard is populated (Phase 5) |
| 11 | Mirror X | Shape under mouse (Phase 5) |
| 12 | Mirror Y | Shape under mouse (Phase 5) |
### Context menu (Whole Stickman preview)
Right-clicking a selected part opens the preview's context menu:
| ID | Label | Description |
|----|-------|-------------|
| 0 | Send Back | Move the selected part one step back in `part_order` (Phase 5) |
| 1 | Bring Forward | Move the selected part one step forward in `part_order` (Phase 5) |
| 2 | Mirror X | Mirror the selected part around its bounding-box center (scale X negated) (Phase 5) |
| 3 | Mirror Y | Mirror the selected part around its bounding-box center (scale Y negated) (Phase 5) |
### Signal flow
```
BodyPartPanel.shape_changed(data) ---(bound to part_name)---> stickman_editor
| |
| _on_body_part_shape_changed |
+--------------------------> WholeStickmanPreview |
.set_body_parts(all_data) |
^ |
WholeStickmanPreview.part_moved(name, pos) (drag handling) --+
BodyPartPanel.shape_selected() ---(bound to part_name)---> stickman_editor
|
_on_body_part_shape_selected
(deselects all other panels)
```
- `BodyPartPanel` emits `shape_changed(shapes_array)` whenever a shape is created, a vertex is dragged (on release), a color is committed via the ColorPicker (on OK), a shape is deleted, a vertex is removed via "Remove Point", or Z-order changes via "Send Back"/"Bring Forward". The signal carries the full `Array[Dictionary]` of all shapes in the panel. Note: `clear_shape()` does **not** emit the signal; the editor's Clear flow refreshes the preview directly.
- `BodyPartPanel` emits `shape_selected` when left-clicked; the editor deselects all other panels.
- `stickman_editor.gd` collects shape data from all 10 panels and forwards it to `WholeStickmanPreview.set_body_parts()`.
- `WholeStickmanPreview` emits `part_moved(part_name, new_position)` during translation drags. Rotation and scale are stored internally and polled by the editor at save time via `get_part_rotation()` / `get_part_scale()`. Part Z-order is polled the same way via `get_part_order()` and saved to the top-level `part_order` field (Phase 5).
- Shape Copy/Paste (Phase 5) is owned by the editor: `_copy_shape()` stores a deep duplicate in `_shape_clipboard`, `_broadcast_clipboard_state()` tells every panel whether Paste should be offered, and `_paste_shape()` inserts the clipboard into the target panel at the click position. The clipboard persists after pasting and is shared across all panels.
> **Phase 4:** Each body part can have multiple shapes in Z-order. The Whole Stickman preview treats all shapes in a panel as one combined object, with selection (white bounding box), rotation (circle gizmo below box), and scale (cross gizmos at corners). The `.stk` format evolved to v1.2 with a `shapes` array per part plus `rotation` and `scale` fields. Rotation snaps to 15° with Ctrl; scale locks aspect ratio with Ctrl.
> **Phase 5:** Adds drawing-surface clipping, shape dragging, an editor-wide shape clipboard (Copy/Paste across panels), shape Mirror X/Y (vertex recompute), and preview object Z-ordering (Send Back/Bring Forward) with Mirror X/Y (scale negation). The format evolved to v1.3: a new top-level `part_order` array stores preview Z-order; scale may be negative for mirrored parts; shape mirroring stores no new fields because it rewrites `points`. The default grid size changed from 5 px to 15 px, and the Snap to Grid checkmark now renders correctly when toggled. v1.0v1.2 files remain backward compatible and are migrated on load.
> **Phase 7:** Adds the pose silhouette guide to the Whole Stickman preview — a semi-transparent, color-coded stick figure (left cyan-blue, right orange-red, central white) whose 13 joint anchors match the rest-pose pivots of the future `master_rig.tscn` rig, drawn above the grid and in front of user parts (ghosting over them, below the selection gizmos and drag highlight), centered in the preview at the default view. Toggleable via the dynamic **View → Hide/Show Pose Guide** label (on by default, persisted to `settings.json`). The guide is a view aid only: **no `.stk` format change** — `FILE_VERSION` stays `"1.3"`.
> **Phase 8:** The `.stk` export evolved to **v1.4** with write-only metadata the editor never reads back. A top-level `proportions` object stores the 5 master-rig rest-pose bone lengths (`upper_arm_length` 168.0, `lower_arm_length` 200.0, `upper_leg_length` 200.0, `lower_leg_length` 200.0, `torso_length` 391.5) — hardcoded constants from `master_rig.tscn`, not measured from the user's shapes. Each `body_parts` entry gains `pivot` (local bounding-box center = rotation origin) and `length` (bbox extent along the segment axis: width for arms, height for torso/legs/head). v1.0v1.3 files load unchanged and gain these keys on their next save. A new standalone `res://scripts/stk_rig_adapter.gd` (`class_name StkRigAdapter`) fits an instantiated `master_rig.tscn` to a loaded `.stk` (bone fitting + IK recalibration + visual shape mount); it is **not** used by the editor and is reserved for a future runtime pipeline.
> **Phase 9:** Adds the **runtime pipeline** for turning a `.stk` file into a live, rigged `master_rig.tscn` instance, plus a standalone staging scene to debug it. A new `res://scripts/stickman_factory.gd` (`class_name StickmanFactory`) provides the runtime entry point: `load_stk(path)` reads/parses a `.stk` (`FileAccess` + `JSON.parse_string`, `{}` + `push_warning` on failure), `spawn_from_data(stk_data)` instantiates `master_rig.tscn` and applies `StkRigAdapter.apply(stk_data, rig)`, and `spawn(path)` chains them (`null` on empty data). `StkRigAdapter` is extended to also fit the head bone (`Head.position.y = -proportions.torso_length`) and to mount the head as **full geometry** like every other part — clearing its inline `@tool` circle script and mounting `.stk` head shapes as `Line2D`/`Polygon2D` (removed the dead `_mount_head_circle`, `_compute_shapes_bbox`, and `_first_shape_color` helpers). A new standalone scene/resource pair, `res://scenes/test_harness.tscn` + `res://scripts/test_harness.gd` (run via **F6**), loads `.stk` files (open dialog + quick-select for `stickmen/break.stk`, `stickmen/basic.stk`, `stickmen/test.stk`), toggles a world-space debug overlay (bone lines + IK-target markers), pans/zooms a `Camera2D`, and lets you click-drag the 4 limb IK targets with live TwoBoneIK flexing (`SkeletonModificationStack2D` enabled on spawn). Neither the factory nor the harness is wired into the editor. **No `.stk` format change** — `FILE_VERSION` stays `"1.4"`.