- 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.
13 KiB
13 KiB
AGENTS.md — stickman (Godot 4.4)
Project type
- Godot 4.4 2D/GUI project (Forward Plus renderer)
- No CLI build/test/lint commands; open in the Godot editor to run
- Main scene:
res://scenes/stickman_editor.tscn(set asrun/main_sceneinproject.godot)
Project overview
Stickman Studio is an editor tool for drawing and assembling stick figures. It is a
Control-based GUI (not physics/animation) in its current phase. Body-part vector shapes
are authored per-panel (each panel supports multiple shapes with Z-ordering) and
assembled in a "Whole Stickman" preview that supports translation, rotation, and scale.
Required addon
- Scalable Vector Shapes 2D (v2.27.7) at
addons/curved_lines_2d/- Declared dependency for the project. The current editor UI does not instantiate it directly,
but keep it present — it is required for the legacy
stick.tscnrig.
- Declared dependency for the project. The current editor UI does not instantiate it directly,
but keep it present — it is required for the legacy
Architecture
scripts/stickman_editor.gd—extends Control; the main controller. Owns the menu bar, save/load/clear flow, JSON (de)serialization, and populates the 10 body-part panels. WritesFILE_VERSION "1.4"; auto-migrates"1.0"–"1.3"files on load. Coordinates cross-panel selection so only one shape is selected at a time (shape_selected→ deselect others). Collects per-part{shapes[], position, rotation, scale, pivot, length}for save/load.- Phase 8 save export: writes top-level
proportions(hardcoded master-rig rest-pose constants 168/200/200/200/391.5 via thePROPORTIONSconst) and per-partpivot/lengthcomputed from the panel's local shape bounding box (_compute_part_pivot_length()):pivot= bbox center,length= bbox width for the 4 arm parts (X_AXIS_PARTS) and bbox height otherwise.pivot/length/proportionsare write-only metadata — never read back on load, recomputed on every save. - Phase 6 recent colors: stores
_recent_colors: Array[String](max 8, most-recent-first), loads fromsettings.json(recent_colorskey) in_load_settings(), saves on each color selection via_save_settings(), and broadcasts to all 10 panels via_broadcast_recent_colors(). _on_color_selected(color, part_name): deduplicates, inserts the hex string at the front, trims to 8 entries, saves settings, then broadcasts to all panels._broadcast_recent_colors(): pushes_recent_colorsto every panel viaBodyPartPanel.set_recent_colors(_recent_colors).- Phase 6 status bar (cursor coords):
_process()pollsget_global_mouse_position()each frame, checks each panel viais_cursor_over_drawing(global_pos)and the preview viais_cursor_over_preview(global_pos), converts to world space viaglobal_to_world(global_pos), and writes"X: ### Y: ###"to_status_cursor_coords. - Phase 6 snap status:
_status_snap_statusdisplays"SNAP: ON"/"SNAP: OFF", set in_ready()and re-synced when snap is toggled (_on_edit_menu_id_pressed). - Phase 7 pose guide toggle: stores
_show_guide: bool(defaulttrue), persisted tosettings.jsonunder theshow_pose_guidekey (defaulttrue) via_load_settings()/_save_settings(). The View menu item (id 1) is a dynamic, text-only label (no checkmark): "Hide Pose Guide" while the guide is visible, "Show Pose Guide" while hidden, set via_guide_menu_label()._update_guide_menu_item()refreshes only the item text; it is synced on theabout_to_popupsignal (_on_view_menu_about_to_popup) and again at the end of_load_settings()(so a persistedshow_pose_guide: falseshows "Show Pose Guide" immediately at startup)._on_view_menu_id_pressed(id 1) toggles the state, saves, and broadcasts._broadcast_settings()pushes the value to the preview viaWholeStickmanPreview.set_show_guide(_show_guide)(called in_ready()after_load_settings()).
- Phase 8 save export: writes top-level
scripts/body_part_panel.gd—class_name BodyPartPanel,extends PanelContainer. Reusable per-part editor. Public API:set_shape_data(data: Variant)— import shape data (Array or single Dictionary; used on Load/Clear)get_shape_data() -> Array[Dictionary]— export array of{shape_type, points, color, closed, vertex_flags}clear_shape()— reset panel, clears all shapes (does not emitshape_changed)select()— mark the panel's topmost shape as selected (white outline highlight)deselect()— clear selection and cancel any in-progress vertex dragsignal shape_changed(shapes: Array)— emitted when any shape is created, modified, deleted, or reorderedsignal shape_selected()— emitted on left-click; the editor deselects all other panelssignal color_selected(color: Color)— emitted when the color is confirmed (OK button)set_recent_colors(colors_hex: Array)— clears existing ColorPicker presets and populates with the given hex colorsis_cursor_over_drawing(global_pos: Vector2) -> bool— true ifglobal_posis over the drawing surfaceglobal_to_world(global_pos: Vector2) -> Vector2— maps a global position to drawing ("world") space- Phase 2 vertex editing: left-click on a shape selects it; drag a vertex handle to
reshape in real time; with a shape selected, right-click near an outline edge offers
"Create Point" (inserts a vertex flagged
1at the edge midpoint). Original vertices are filled circles; user-created vertices are hollow rectangles. - Phase 4 multi-shape: panels store a
shapes[]array. Right-click context menu includes "Send Back" (id 7) and "Bring Forward" (id 8) for Z-ordering. "Delete" (id 5) removes the specific shape under the mouse._selected_shape_idxtracks which shape is active for vertex editing. - Per-panel zoom: mouse wheel multiplies
_zoomby 1.10, clamped to[0.3, 3.0]; drawing and input hit-testing both run in world space viadraw_set_transform. - Phase 6 touchpad:
_gui_inputhandlesInputEventMagnifyGesture(pinch zoom) andInputEventPanGesture(2-finger drag panning), both checked beforeInputEventMouseButton. Pan gesture delta is multiplied by 3.0 for speed parity with mouse panning. - Phase 6 cursor-centered zoom: both mouse wheel and pinch zoom adjust
_pan_offsetso the world point under the cursor stays fixed during zoom. - Selection gizmos always on top: bounding box, rotation circle, and scale
crosses for the selected part are drawn in a second pass after all parts,
via
_selected_gizmo_bounds, so they always render in front.
scripts/whole_stickman_preview.gd—class_name WholeStickmanPreview,extends Control; the assembly preview. Owns per-part position/rotation/scale, Z-order (_part_order), selection + gizmos, grid drawing, and pan/zoom. Public API includesset_body_parts(),set_show_guide(enabled: bool),reset_view(), andis_cursor_over_preview(global_pos).- Phase 7 pose silhouette guide:
set_show_guide()stores_show_guide: bool(defaulttrue) and redraws;_draw_silhouette_guide()is called in_on_preview_draw()after the part loop and before the drag highlight/selection gizmos, so it renders above the grid and in front of user parts (ghosting over them), below the selection gizmos and drag highlight. The guide is centered at the default view and afterReset Views, computed at draw time from the live preview size via_guide_to_preview()=(master_pos - GUIDE_FIGURE_CENTER) * GUIDE_SCALE + preview_area.size * 0.5, so it also re-centers on window resize; it remains a world-space fixture that moves with pan/zoom. Joint positions are hardcoded constants derived from themaster_rig.tscnrest pose (GUIDE_JOINTS: 13 anchors Head/Neck/Shoulders/Elbows/Wrists/Hips/Knees/Ankles;GUIDE_SCALE = 1.0,GUIDE_FIGURE_CENTER = (0, -93.75),GUIDE_HEAD_RADIUS = 100.0). Color-coded: left limbs cyan-blueColor(0.35, 0.70, 1.00), right limbs orange-redColor(1.00, 0.50, 0.20), central spine/head white, with lines at alpha0.45and joint dots at alpha0.65. Joint dots useGUIDE_JOINT_RADIUS / _zoom(6 px constant screen size). The guide is pure drawing — no hit-testing is added, so it never intercepts part dragging/selection.
- Phase 7 pose silhouette guide:
scripts/stk_rig_adapter.gd—class_name StkRigAdapter,extends RefCounted; a standalone runtime adapter (Phase 8, not referenced by the editor).static func apply(stk_data, rig)fits an instantiatedmaster_rig.tscnto a loaded.stkdictionary, calling three private helpers in order:_fit_bones(re-fits the 8 limbBone2Dlengths + lower-bone origins),_recalibrate_ik(repositions theIK_Targets/Left|Right_HandandLeft|Right_Legtargets), and_mount_shapes(mounts.stkshapes onto theBody/*visual nodes — open →Line2D, closed →Polygon2Dfill +Line2Doutline, width 16). Targetsmaster_rig.tscnnode paths; every node lookup is null-guarded (missing node →push_warning+ skip, never crash). Consumed by a future runtime pipeline.- Scenes:
scenes/stickman_editor.tscn— main editor layout; unique-name nodes (%Prefix) used for typed@onreadyaccess:%MenuBar,%StickmanNameEdit,%LeftColumn,%CenterColumn,%WholeStickmanPreview,%SaveDialog,%LoadDialog,%ClearConfirmDialog,%ErrorDialog,%StatusBar,%CursorCoords,%SnapStatus.- Phase 6 status bar:
StatusBaris anHBoxContainerbottom-anchored at 28 px height containingCursorCoords(left) andSnapStatus(right);MainLayoutoffset_bottomis-28.0to leave room for it.
- Phase 6 status bar:
scenes/body_part_panel.tscn— instantiated 10× at runtime (5 per column). Each panel setssize_flags_vertical = SIZE_EXPAND_FILLso the panels expand to fill the column height in their parent VBoxContainer.
Body-part data model
- 10 internal part keys (ordered):
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. - Shape dictionary:
{ "shape_type": String, "points": Array[{x,y}], "color": "#hex", "closed": bool, "vertex_flags": Array[int] }. shape_typevalues:"line","rectangle","circle",""(empty). Descriptive tag in Phase 2 — rendering usesclosed.closed:true= filled + closed outline,false= open outline-only.vertex_flags: same length aspoints;0= original vertex (filled circle),1= user-created via "Create Point" (hollow rectangle).- Phase 4: A panel stores a
shapes[]array of shape dictionaries. Z-order = array position (first = back, last = front). Per-part data includes{shapes[], position, rotation, scale}. - Phase 8: per-part data adds
pivot{x, y}(local bounding-box center = rotation origin) andlength(float, bbox extent along the segment axis) for format v1.4; the.stkroot also gains a top-levelproportionsobject (5 rig bone lengths). All three are write-only metadata recomputed on every save — never read back on load. - The JSON
.stkformat is defined inREADME.md(versioned"1.4", extensible;"1.0"–"1.3"files auto-migrate on load).
settings.json (Phase 6)
- Persisted editor preferences written to
settings.jsonvia_save_settings()and loaded in_load_settings(). - Keys:
version,grid_size,snap_to_grid,recent_colors,show_pose_guide. recent_colors: Array[String]— the last up-to-8 selected hex colors, most-recent-first. Populated on load and flushed on every color selection.show_pose_guide: bool— whether the pose silhouette guide is visible in the Whole Stickman preview. Defaulttrue. Loaded in_load_settings()and flushed on every toggle via_broadcast_settings().
Legacy scene (do not delete)
stick.tscn— the original rigged/animated figure usingSkeleton2D+ IK targets +RemoteTransform2D+Line2Dlimbs, plus an embedded@toolscript drawing the head circle. Animations: "walk", "walk_to", "RESET". Not the current main scene; kept for future animation/rigging phases.
Editor conventions
.godot/is gitignored; never edit it manually.- Scene files (
*.tscn) and.importfiles are text-based; use Godot's editor for complex changes. - UID references (Godot 4 native) exist in scenes; do not change them by hand.
- Use standard Godot
Controlnodes where applicable. - Use
class_namefor globally referenced scripts (BodyPartPanel,WholeStickmanPreview). - Prefer
%UniqueNameaccess over exported node paths instickman_editor.gd/body_part_panel.gd. - Keep the
.stkJSON format backward compatible — never change the meaning of an existing key.