Add StkRigAdapter for runtime skeleton fitting and shape mounting

- 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.
This commit is contained in:
2026-08-18 17:12:54 -04:00
parent 1eeeb64b39
commit 48d98ce0fe
13 changed files with 1994 additions and 108 deletions
+114
View File
@@ -42,6 +42,56 @@ const DEFAULT_PART_ORDER: PackedStringArray = [
"right_upper_leg", "right_lower_leg"
]
# 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_FIGURE_CENTER: Vector2 = Vector2(0.0, -93.75) # master-space bbox center of the 1:1 guide figure
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
]
enum Interaction { NONE, TRANSLATE, ROTATE, SCALE }
# ---------------------------------------------------------------------------
@@ -95,6 +145,9 @@ var _context_menu: PopupMenu
# Phase 6: selection gizmos rendered on top
var _selected_gizmo_bounds: Rect2 = Rect2()
# Phase 7: pose silhouette guide (default ON per user decision)
var _show_guide: bool = true
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
@@ -212,6 +265,12 @@ func set_snap_enabled(enabled: bool) -> void:
_snap_enabled = enabled
# Phase 7
func set_show_guide(enabled: bool) -> void:
_show_guide = enabled
preview_area.queue_redraw()
func reset_view() -> void:
_zoom = 1.0
_pan_offset = Vector2.ZERO
@@ -323,6 +382,9 @@ func _on_preview_draw() -> void:
if bounds.has_area():
_selected_gizmo_bounds = bounds
# Phase 7: ghost the silhouette guide IN FRONT of parts (below selection affordances)
_draw_silhouette_guide()
# Highlight dragged part
if not _dragging_part.is_empty() and _interaction == Interaction.TRANSLATE:
var bounds: Variant = _part_bounds.get(_dragging_part)
@@ -373,6 +435,58 @@ func _draw_grid() -> void:
y += gs
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_FIGURE_CENTER) * GUIDE_SCALE + preview_area.size * 0.5
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
func _draw_polyline_preview(pts: PackedVector2Array, color: Color, closed: bool) -> void:
for i: int in range(pts.size() - 1):
preview_area.draw_line(pts[i], pts[i + 1], color, 1.5)