cozyclay 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +40 -113
- package/bin/agent/agent-runner.mjs +9 -10
- package/bin/agent/cliproxy-provider.mjs +135 -0
- package/bin/agent/credential-store.mjs +30 -7
- package/bin/agent/pi-tools.mjs +4 -1
- package/bin/agent/provider-keys.mjs +32 -0
- package/bin/agent/provider-login.mjs +110 -0
- package/bin/agent/provider-routes.mjs +99 -0
- package/bin/agent/providers.mjs +19 -97
- package/bin/agent/script-tool.mjs +17 -4
- package/bin/agent/studio-motion-reference.mjs +23 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-rebase-text.mjs +32 -0
- package/bin/agent/studio-rebase.mjs +26 -39
- package/bin/agent/studio-tools.mjs +9 -28
- package/bin/agent/tool-arguments.mjs +49 -0
- package/bin/agent/turn-frames.mjs +66 -0
- package/bin/codex-auth.mjs +9 -2
- package/bin/cozyclay.mjs +10 -116
- package/bin/live/cli.mjs +3 -3
- package/bin/mcp-runtime.mjs +2 -12
- package/bin/package-signature.mjs +0 -4
- package/dist/app/index.html +7 -6
- package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
- package/dist/assets/analytics-C9sUnndd.js +1 -0
- package/dist/assets/app-CCh--1Hc.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.js → landing-DjHD8u7k.js} +1 -1
- package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-C4yKH1p4.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +3 -3
- package/dist/sw.js +3 -6
- package/dist/workflow/index.html +5 -4
- package/mcp/LIVE-PROTOCOL.md +7 -47
- package/mcp/README.md +16 -26
- package/mcp/live-hub.mjs +6 -10
- package/mcp/package.json +1 -3
- package/mcp/tool-handlers.mjs +4 -168
- package/package.json +3 -22
- package/src/App.jsx +150 -2624
- package/src/agent-progress.js +79 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +5 -0
- package/src/app-stage.jsx +2 -34
- package/src/asset-pane.jsx +6 -5
- package/src/camera-block.js +21 -0
- package/src/camera-direction.js +120 -0
- package/src/character-kind.js +3 -9
- package/src/command-bus.js +8 -6
- package/src/commands/cast.js +5 -34
- package/src/commands/elements/character.js +1 -1
- package/src/commands/elements/motion.js +1 -6
- package/src/commands/elements/object.js +6 -2
- package/src/commands/elements/shot.js +4 -1
- package/src/commands/elements.js +38 -14
- package/src/commands/index.js +3 -1
- package/src/commands/kits.js +37 -0
- package/src/commands/motion-timing.js +57 -0
- package/src/commands/motion.js +9 -152
- package/src/commands/motion3-compile.js +65 -0
- package/src/commands/motion3.js +207 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +49 -11
- package/src/commands/stage.js +14 -1
- package/src/commands/view.js +4 -1
- package/src/document-mirrors.js +42 -0
- package/src/document-store.js +4 -0
- package/src/domains/cast.js +19 -194
- package/src/domains/motion/motion-range-pin-rules.js +1 -1
- package/src/domains/motion/take-source.js +51 -0
- package/src/domains/motion.js +60 -1838
- package/src/domains/scenes.js +12 -55
- package/src/domains/shots.js +2 -2
- package/src/domains/stage.js +4 -2
- package/src/draft-ghost.js +46 -0
- package/src/draft-ghost.jsx +129 -0
- package/src/dualview.jsx +4 -3
- package/src/effects/EffectsLayer.jsx +112 -0
- package/src/effects/effect-meshes.js +232 -0
- package/src/effects/effect-record.js +103 -0
- package/src/effects/emitter.js +59 -0
- package/src/effects/simulate.js +261 -0
- package/src/effects/state.js +59 -0
- package/src/execution-telemetry.js +2 -3
- package/src/first-success-guide.jsx +5 -5
- package/src/ghost-set.js +257 -0
- package/src/held-object-edit.jsx +113 -0
- package/src/held-object-pose.js +175 -0
- package/src/hierarchy-model.js +1 -1
- package/src/hierarchy-panel.jsx +1 -1
- package/src/imported-mesh.jsx +103 -0
- package/src/kits/held.js +84 -0
- package/src/kits/index.js +70 -0
- package/src/kits/smoking.js +57 -0
- package/src/light-blockers.js +93 -0
- package/src/motion/beat-board-model.js +84 -0
- package/src/motion/beat-board.css +124 -0
- package/src/motion/beat-board.jsx +59 -0
- package/src/motion/center-of-mass.js +66 -0
- package/src/motion/fix-collisions.js +850 -0
- package/src/motion/physics-review.js +118 -0
- package/src/{ardy → motion}/physics-support.js +1 -76
- package/src/motion/rig-snapshot.js +22 -0
- package/src/motion/take-segments.jsx +140 -0
- package/src/{ardy → motion}/timeline-coordinates.js +0 -21
- package/src/{ardy → motion}/timeline.css +1 -2
- package/src/{ardy → motion}/timeline.jsx +42 -413
- package/src/motion/waypoints.js +76 -0
- package/src/motion3/beats.js +83 -0
- package/src/motion3/body.js +158 -0
- package/src/motion3/bones.js +47 -0
- package/src/motion3/carried.js +47 -0
- package/src/motion3/compile-job.js +75 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +105 -0
- package/src/motion3/compiler/arm-orient.js +128 -0
- package/src/motion3/compiler/channels.js +125 -0
- package/src/motion3/compiler/compile.js +205 -0
- package/src/motion3/compiler/gait-rhythm.js +97 -0
- package/src/motion3/compiler/grip.js +171 -0
- package/src/motion3/compiler/head-steady.js +48 -0
- package/src/motion3/compiler/hold.js +134 -0
- package/src/motion3/compiler/idle.js +113 -0
- package/src/motion3/compiler/limbs.js +170 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +162 -0
- package/src/motion3/compiler/quat.js +49 -0
- package/src/motion3/compiler/skeleton.js +74 -0
- package/src/motion3/compiler/validate-common.js +32 -0
- package/src/motion3/compiler/validate-goals.js +118 -0
- package/src/motion3/compiler/validate-objects.js +143 -0
- package/src/motion3/compiler/validate-parts.js +116 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +73 -0
- package/src/motion3/compiler/walk-pelvis.js +275 -0
- package/src/motion3/compiler/walk.js +207 -0
- package/src/motion3/compiler/wrist-clamp.js +32 -0
- package/src/motion3/delta.js +47 -0
- package/src/motion3/describe.js +174 -0
- package/src/motion3/docs.js +27 -0
- package/src/motion3/edit-carry.js +21 -0
- package/src/motion3/fix/ease.js +16 -0
- package/src/motion3/fix/ground.js +37 -0
- package/src/motion3/fix/index.js +52 -0
- package/src/motion3/fix/push.js +71 -0
- package/src/motion3/fix/reach.js +46 -0
- package/src/motion3/fix/retime.js +79 -0
- package/src/motion3/fix/self.js +211 -0
- package/src/motion3/fix/slide.js +70 -0
- package/src/motion3/fix/turn.js +51 -0
- package/src/motion3/follow.js +39 -0
- package/src/motion3/free-motion.js +173 -0
- package/src/motion3/grip-choice.js +130 -0
- package/src/motion3/hold-plan.js +157 -0
- package/src/motion3/json-patch.js +77 -0
- package/src/motion3/key-aim.js +105 -0
- package/src/motion3/key-edit.js +82 -0
- package/src/motion3/lenient.js +127 -0
- package/src/motion3/object-keys.js +236 -0
- package/src/motion3/object-pose.js +182 -0
- package/src/motion3/object-tracks.js +12 -0
- package/src/motion3/pipeline.js +102 -0
- package/src/motion3/playback.js +51 -0
- package/src/motion3/probes.js +91 -0
- package/src/motion3/qa/body-checks.js +34 -0
- package/src/motion3/qa/check-names.js +14 -0
- package/src/motion3/qa/checks.js +121 -0
- package/src/motion3/qa/contact-checks.js +51 -0
- package/src/motion3/qa/explain.js +80 -0
- package/src/motion3/qa/fmt.js +5 -0
- package/src/motion3/qa/foot-checks.js +72 -0
- package/src/motion3/qa/geometry.js +77 -0
- package/src/motion3/qa/goals.js +191 -0
- package/src/motion3/qa/ground.js +60 -0
- package/src/motion3/qa/hints-contact.js +179 -0
- package/src/motion3/qa/hints-goal.js +160 -0
- package/src/motion3/qa/hints-knee.js +24 -0
- package/src/motion3/qa/hints-motion.js +209 -0
- package/src/motion3/qa/hints-object.js +91 -0
- package/src/motion3/qa/index.js +42 -0
- package/src/motion3/qa/key-checks.js +82 -0
- package/src/motion3/qa/near.js +96 -0
- package/src/motion3/qa/object-checks.js +135 -0
- package/src/motion3/qa/patch.js +161 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +218 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +120 -0
- package/src/motion3/repair-tune.js +121 -0
- package/src/motion3/repair.js +83 -0
- package/src/motion3/report.js +67 -0
- package/src/motion3/rigs/index.js +14 -0
- package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/x-bot-tpose.json +1 -0
- package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/y-bot-tpose.json +1 -0
- package/src/motion3/scene.js +60 -0
- package/src/motion3/schema/brief.js +89 -0
- package/src/motion3/schema/fields.js +44 -0
- package/src/motion3/schema/spec-examples.js +46 -0
- package/src/motion3/schema/topics.js +111 -0
- package/src/motion3/schema/types.js +147 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/skills/basic.js +82 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/index.js +83 -0
- package/src/motion3/skills/info.js +65 -0
- package/src/motion3/skills/smoke-kit.js +109 -0
- package/src/motion3/skills/smoke.js +160 -0
- package/src/motion3/spec-store.js +59 -0
- package/src/motion3/take-edit.js +155 -0
- package/src/motion3/take.js +63 -0
- package/src/motion3/timeline.js +127 -0
- package/src/motion3/views.js +121 -0
- package/src/motion3/walk-plan.js +119 -0
- package/src/object-catalog.jsx +7 -1
- package/src/object-colors.js +98 -0
- package/src/object-gizmo-parts.js +115 -0
- package/src/object-gizmo.jsx +37 -122
- package/src/object-glow.js +139 -0
- package/src/object-hidden.js +33 -0
- package/src/object-look.js +196 -0
- package/src/object-size.js +10 -0
- package/src/object-travel.js +71 -6
- package/src/panels/CameraPanel.jsx +7 -7
- package/src/panels/HdrEnvironmentControls.jsx +59 -0
- package/src/panels/LightPanel.jsx +25 -4
- package/src/panels/ObjectLookPanel.jsx +88 -0
- package/src/panels/PosePanel.jsx +2 -17
- package/src/panels/RigControlPanel.jsx +5 -53
- package/src/panels/SubjectBox.jsx +4 -1
- package/src/panels/details.css +14 -0
- package/src/posestudio.jsx +4 -27
- package/src/project-browser.jsx +3 -4
- package/src/project-resources.js +18 -31
- package/src/project.js +13 -104
- package/src/props.jsx +25 -280
- package/src/rail-dot-drag.js +23 -0
- package/src/render-passes.js +10 -0
- package/src/resource-status.jsx +1 -0
- package/src/result-modal.jsx +4 -19
- package/src/room.jsx +20 -12
- package/src/scene-assets.js +8 -6
- package/src/scene-light-place.js +72 -0
- package/src/scene-lights.jsx +148 -0
- package/src/scene-objects.js +31 -139
- package/src/scenes.js +15 -105
- package/src/semantic-edit.js +2 -5
- package/src/set-pieces.jsx +183 -0
- package/src/settings-menu.jsx +3 -11
- package/src/shell/BottomDock.jsx +9 -51
- package/src/shell/DetailsSlot.jsx +16 -200
- package/src/shell/GhostsMenuGroup.jsx +74 -0
- package/src/shell/LibrarySlot.jsx +1 -3
- package/src/shell/MenuBar.jsx +3 -3
- package/src/shell/PreferencesDialog.jsx +15 -264
- package/src/shell/PreferencesSlot.jsx +2 -8
- package/src/shell/TopBar.jsx +5 -84
- package/src/shell/ViewportToolbar.jsx +10 -70
- package/src/shell/agent-glass.css +19 -6
- package/src/shell/details-selection.js +42 -0
- package/src/shell/ghosts-menu.css +22 -0
- package/src/shell/glass-regions.css +29 -3
- package/src/shell/preferences.css +6 -1
- package/src/shot-authoring.js +4 -7
- package/src/soft-shadows.js +37 -0
- package/src/stage-attach-frames.js +12 -12
- package/src/stage-canvas-rigs.jsx +3 -3
- package/src/stage-capture.jsx +42 -8
- package/src/stage-characters.jsx +22 -216
- package/src/stage-editor-gizmos.jsx +6 -6
- package/src/stage-environment.js +93 -0
- package/src/stage-hdr-builtins.js +107 -0
- package/src/stage-hdr-image.js +116 -0
- package/src/stage-hdr-library.js +70 -0
- package/src/stage-hdr-map.js +94 -0
- package/src/stage-hdr.js +130 -0
- package/src/stage-lighting.js +149 -0
- package/src/stage-occlusion.js +210 -0
- package/src/stage-sky.jsx +159 -0
- package/src/stage-startup-defaults.js +5 -9
- package/src/studio-actions.js +12 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +41 -71
- package/src/studio-app-binding.js +25 -45
- package/src/studio-asset-list.js +17 -0
- package/src/studio-contact-sheet.js +37 -5
- package/src/studio-elements.js +27 -7
- package/src/studio-entity-bounds.js +18 -0
- package/src/studio-error-codes.js +8 -0
- package/src/studio-export-framing.js +152 -0
- package/src/studio-lighting-check.js +103 -0
- package/src/studio-motion-visual.js +150 -0
- package/src/studio-schema-errors.js +80 -0
- package/src/studio-support-gap.js +74 -0
- package/src/styles.css +1 -19
- package/src/timeline-defaults.js +5 -0
- package/src/timeline-extent.js +8 -20
- package/src/tutorial-walk.js +17 -0
- package/src/ui-scrub.js +1 -1
- package/src/use-shot-camera-tracks.js +12 -0
- package/src/view-ghosts.js +97 -0
- package/src/view-ghosts.jsx +49 -0
- package/src/workflow/AgentPanel.jsx +47 -166
- package/src/workflow/AgentProgressCard.jsx +107 -0
- package/src/workflow/AgentReplyText.jsx +37 -0
- package/src/workflow/AgentTurnSteps.jsx +84 -0
- package/src/workflow/LoginPanel.jsx +140 -0
- package/src/workflow/WorkflowBuilder.jsx +7 -23
- package/src/workflow/agent-client.js +54 -51
- package/src/workflow/agent-panel.css +71 -1
- package/src/workflow/agent-progress-card.css +167 -0
- package/src/workflow/agent-progress-view.js +47 -0
- package/src/workflow/agent-turn-steps.css +113 -0
- package/src/workflow/agent-turn.js +191 -0
- package/src/workflow/login-client.js +34 -0
- package/src/workflow/motion-input.js +18 -31
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/reply-markdown.js +69 -0
- package/src/workflow/scene-asset-sync.js +8 -8
- package/tools/agent-session-stats.mjs +61 -0
- package/tools/dev-full.mjs +5 -56
- package/tools/eslint-baseline.json +12 -13
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +31 -81
- package/tools/motion3/extract-body.mjs +103 -0
- package/tools/motion3/extract-rig.mjs +85 -0
- package/tools/process-supervisor.mjs +0 -88
- package/tools/run-tests.mjs +8 -124
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-C9pMpW7s.js +0 -1
- package/dist/assets/app-B_2XB9rw.css +0 -1
- package/dist/assets/app-DAuvKPP8.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-CvKuBgy5.js +0 -1
- package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-BPk2Ni95.js +0 -1
- package/dist/assets/workflow-jJRBR94Z.js +0 -179
- package/dist/demo/walk-then-stop.npz +0 -0
- package/mcp/ardy-prompts.mjs +0 -218
- package/src/ardy/auto-fix-panel.css +0 -108
- package/src/ardy/auto-physics.js +0 -1194
- package/src/ardy/client.js +0 -153
- package/src/ardy/convert.js +0 -313
- package/src/ardy/cskel27-neutral.js +0 -39
- package/src/ardy/cskel27.js +0 -77
- package/src/ardy/export.js +0 -165
- package/src/ardy/fix-collisions.js +0 -2257
- package/src/ardy/foot-lock.js +0 -230
- package/src/ardy/motion-calibration.js +0 -118
- package/src/ardy/motion-edit.js +0 -213
- package/src/ardy/motion-url.js +0 -55
- package/src/ardy/mp4-duration.js +0 -102
- package/src/ardy/npz.js +0 -595
- package/src/ardy/physics-panel.css +0 -21
- package/src/ardy/physics-panel.jsx +0 -89
- package/src/ardy/physics-refine.js +0 -111
- package/src/ardy/physics-review.js +0 -567
- package/src/ardy/physics-temporal.js +0 -54
- package/src/ardy/platform-fit-panel.css +0 -64
- package/src/ardy/platform-fit-panel.jsx +0 -41
- package/src/ardy/platform-fit.js +0 -326
- package/src/ardy/playback.js +0 -711
- package/src/ardy/pose-pin.js +0 -97
- package/src/ardy/prompt-clips.js +0 -30
- package/src/ardy/retime.js +0 -211
- package/src/ardy/root-drop.js +0 -369
- package/src/ardy/timeline-resize.js +0 -13
- package/src/ardy/to-cskel27.js +0 -225
- package/src/ardy/trim.js +0 -33
- package/src/ardy/waypoints.js +0 -369
- package/src/ardy-bridge-contract.js +0 -281
- package/src/domains/motion/motion-video-capture.js +0 -664
- package/src/i2v-motion-client.js +0 -77
- package/src/i2v-motion-studio.jsx +0 -180
- package/src/line-edit.js +0 -1348
- package/src/motion/generation.js +0 -105
- package/src/motion-readiness-ui.jsx +0 -67
- package/src/motion-readiness.js +0 -40
- package/src/motion-resources.js +0 -177
- package/src/motion-store.js +0 -65
- package/src/motion-trail.js +0 -601
- package/src/multimodel-ingest.js +0 -394
- package/src/panels/PromptBlocksPanel.jsx +0 -444
- package/src/panels/VideoCapturePanel.jsx +0 -181
- package/src/pose-extract/browser.js +0 -42
- package/src/pose-extract/detector.js +0 -100
- package/src/pose-extract/fit.js +0 -227
- package/src/pose-extract/image-frame.js +0 -51
- package/src/pose-extract/index.js +0 -58
- package/src/pose-extract/keyframes.js +0 -74
- package/src/pose-extract/landmarks.js +0 -162
- package/src/pose-extract/mirror.js +0 -153
- package/src/pose-extract/take.js +0 -131
- package/src/pose-extract/video-frames.js +0 -91
- package/src/take-recipe.js +0 -165
- package/src/trail-key-conflicts.js +0 -39
- package/src/trail-pick.js +0 -59
- package/src/use-i2v-capture-modal.js +0 -15
- package/tools/ardy/BRIDGE.md +0 -37
- package/tools/ardy/GVHMR-NPZ.md +0 -68
- package/tools/ardy/README.md +0 -124
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/artifacts.mjs +0 -34
- package/tools/ardy/bridge.mjs +0 -1732
- package/tools/ardy/bvh-cskel27.mjs +0 -1265
- package/tools/ardy/cclay_constrained_generate.py +0 -561
- package/tools/ardy/cclay_gvhmr_worker.py +0 -264
- package/tools/ardy/cclay_motion_edit.py +0 -510
- package/tools/ardy/cclay_sequence_generate.py +0 -705
- package/tools/ardy/cclay_worker.py +0 -304
- package/tools/ardy/dump-npz.py +0 -205
- package/tools/ardy/extract-rest.mjs +0 -299
- package/tools/ardy/extract.mjs +0 -507
- package/tools/ardy/footage.mjs +0 -467
- package/tools/ardy/gvhmr-floor.mjs +0 -102
- package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
- package/tools/ardy/gvhmr_fastpath.py +0 -168
- package/tools/ardy/gvhmr_trajectory.py +0 -240
- package/tools/ardy/merge-text-encoder.py +0 -160
- package/tools/ardy/mocap-metrics.mjs +0 -200
- package/tools/ardy/mocap-quality-gate.mjs +0 -110
- package/tools/ardy/motion-stabilize.mjs +0 -348
- package/tools/ardy/npz.mjs +0 -414
- package/tools/ardy/pose-to-npz.mjs +0 -106
- package/tools/ardy/prompt-limits.mjs +0 -5
- package/tools/ardy/run-edit-on-box.sh +0 -73
- package/tools/ardy/run-local.mjs +0 -233
- package/tools/ardy/run-on-box.sh +0 -593
- package/tools/ardy/run-sequence-on-box.sh +0 -177
- package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
- package/tools/ardy/runners/index.mjs +0 -18
- package/tools/ardy/runners/local.mjs +0 -455
- package/tools/ardy/runners/proc.mjs +0 -125
- package/tools/ardy/runners/remote.mjs +0 -252
- package/tools/ardy/setup-local.mjs +0 -189
- package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
- package/tools/ardy/setup-text-encoder.py +0 -306
- package/tools/ardy/smpl-cskel27.mjs +0 -178
- package/tools/ardy/visual-qa.mjs +0 -196
- package/tools/ardy/vq-car.mjs +0 -52
- package/tools/bench/EXP3.md +0 -61
- package/tools/bench/cclay_bench_extract_incam.py +0 -125
- package/tools/bench/cclay_bench_extract_obs.py +0 -204
- package/tools/bench/cclay_bench_runner.py +0 -42
- package/tools/bench/cube-contact.mjs +0 -162
- package/tools/bench/exp3.mjs +0 -152
- package/tools/bench/extract-bench-lib.mjs +0 -216
- package/tools/bench/extract-bench.mjs +0 -302
- package/tools/bench/fal-generate.mjs +0 -88
- package/tools/bench/fit/README.md +0 -189
- package/tools/bench/fit/camera.mjs +0 -59
- package/tools/bench/fit/contact.mjs +0 -419
- package/tools/bench/fit/footlock.mjs +0 -234
- package/tools/bench/fit/motion.mjs +0 -85
- package/tools/bench/fit/pin.mjs +0 -27
- package/tools/bench/fit/remote.mjs +0 -105
- package/tools/bench/fit-bench.mjs +0 -126
- package/tools/bench/fit-sanity.mjs +0 -45
- package/tools/bench/metrics.mjs +0 -313
- package/tools/bench/obs/depth.mjs +0 -258
- package/tools/bench/obs/extrinsics.mjs +0 -175
- package/tools/bench/obs/fit_mannequin_betas.py +0 -340
- package/tools/bench/obs/ground.mjs +0 -294
- package/tools/bench/obs/heading.mjs +0 -151
- package/tools/bench/obs/ladder.mjs +0 -555
- package/tools/bench/obs/mannequin-betas.json +0 -124
- package/tools/bench/obs/remote.mjs +0 -83
- package/tools/bench/obs/rest_joints.py +0 -52
- package/tools/bench/obs/ybot-targets.mjs +0 -49
- package/tools/bench/obs-bench.mjs +0 -520
- package/tools/bench/score.mjs +0 -343
- package/tools/bench/summarize.mjs +0 -86
- package/tools/demo-worker/README.md +0 -137
- package/tools/demo-worker/api-client.mjs +0 -233
- package/tools/demo-worker/generate.mjs +0 -301
- package/tools/demo-worker/index.mjs +0 -466
- package/tools/demo-worker/sign-core.mjs +0 -112
- package/tools/demo-worker/sign.mjs +0 -161
- package/tools/gt-render/browser.mjs +0 -182
- package/tools/gt-render/camera-math.mjs +0 -276
- package/tools/gt-render/page.mjs +0 -267
- package/tools/gt-render/render.mjs +0 -514
- package/tools/gt-render/scene-box.mjs +0 -31
- package/tools/gt-render/take-transform.mjs +0 -87
- package/tools/kimodo/compare-seam.mjs +0 -108
- package/tools/kimodo/constraints.mjs +0 -244
- package/tools/kimodo/edit.mjs +0 -259
- package/tools/kimodo/effector-constraints.mjs +0 -200
- package/tools/kimodo/generate.mjs +0 -646
- package/tools/kimodo/local-output.mjs +0 -265
- package/tools/kimodo/measure-pose.mjs +0 -65
- package/tools/kimodo/measure-preserve.mjs +0 -899
- package/tools/kimodo/measure-waypoints.mjs +0 -55
- package/tools/kimodo/mlx-generate.py +0 -111
- package/tools/kimodo/pose-constraints.mjs +0 -248
- package/tools/kimodo/preserve-mask.mjs +0 -543
- package/tools/kimodo/read-npz.mjs +0 -189
- package/tools/kimodo/run-edit-on-box.mjs +0 -155
- package/tools/kimodo/run-sequence-on-box.mjs +0 -178
- package/tools/kimodo/runner.mjs +0 -284
- package/tools/kimodo/seam-batch.mjs +0 -77
- package/tools/kimodo/setup-local.mjs +0 -30
- package/tools/kimodo/setup-on-box.sh +0 -339
- package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
- package/tools/morphgs/assets/gen_truth.py +0 -39
- package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
- package/tools/morphgs/assets/playback-check.mjs +0 -30
- package/tools/morphgs/demo-gate.sh +0 -26
- package/tools/morphgs/fbx2morphgs.mjs +0 -68
- package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
- package/tools/morphgs/setup-on-cluster.sh +0 -126
- package/tools/projflow/driver.py +0 -1069
- package/tools/projflow/generate.mjs +0 -805
- package/tools/projflow/hml22-to-cskel27.mjs +0 -735
- package/tools/projflow/line-edit-job.mjs +0 -547
- package/tools/projflow/replay.mjs +0 -452
- package/tools/projflow/runner.mjs +0 -269
- package/tools/projflow/service.mjs +0 -742
- package/tools/track/DESIGN.md +0 -513
- package/tools/track/backfill-provenance.mjs +0 -116
- package/tools/track/budget.mjs +0 -8
- package/tools/track/check-rig.mjs +0 -209
- package/tools/track/diagnostics.schema.json +0 -60
- package/tools/track/export-rig.mjs +0 -256
- package/tools/track/fallback.mjs +0 -7
- package/tools/track/fk-parity-fixture.mjs +0 -161
- package/tools/track/gate.mjs +0 -349
- package/tools/track/masks.mjs +0 -318
- package/tools/track/metrics.mjs +0 -201
- package/tools/track/publish-obs.mjs +0 -108
- package/tools/track/py/check_env.py +0 -61
- package/tools/track/py/eval_lr.py +0 -229
- package/tools/track/py/lr_viterbi.py +0 -294
- package/tools/track/py/masks.py +0 -405
- package/tools/track/py/objective.py +0 -382
- package/tools/track/py/rig.py +0 -508
- package/tools/track/py/scene.py +0 -326
- package/tools/track/py/test_joint_indices.py +0 -123
- package/tools/track/py/test_lr_viterbi.py +0 -195
- package/tools/track/py/test_masks.py +0 -229
- package/tools/track/py/test_rig.py +0 -288
- package/tools/track/py/test_scene.py +0 -354
- package/tools/track/py/test_track.py +0 -328
- package/tools/track/py/track.py +0 -411
- package/tools/track/remote.mjs +0 -140
- package/tools/track/rig-dump.mjs +0 -227
- package/tools/track/run-box-tests.mjs +0 -14
- package/tools/track/run-box.mjs +0 -168
- package/tools/track/setup-box.sh +0 -59
- package/tools/track/study-2d.mjs +0 -728
- /package/src/{ardy → motion}/collision-blockers.js +0 -0
- /package/src/{ardy → motion}/ground.js +0 -0
- /package/src/{ardy → motion}/ik-drag.js +0 -0
- /package/src/{ardy → motion}/ik-key-json.js +0 -0
- /package/src/{ardy → motion}/ik.js +0 -0
- /package/src/{ardy → motion}/physics-surface.js +0 -0
- /package/src/{ardy → motion}/playback-clock.js +0 -0
- /package/src/{ardy → motion}/range-pin.js +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeSceneObject } from '../../scene-objects.js';
|
|
1
|
+
import { normalizeSceneObject, updateSceneObject } from '../../scene-objects.js';
|
|
2
2
|
import { STUDIO_ELEMENTS } from '../../studio-elements.js';
|
|
3
3
|
import { registerElementKind } from '../elements.js';
|
|
4
4
|
|
|
@@ -19,4 +19,8 @@ function normalizeObjectSet(record) {
|
|
|
19
19
|
// The owning collection consumes this lifecycle intent before persistence.
|
|
20
20
|
return record.remove === true ? { ...item, remove: true } : item;
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
// A set that moves or turns a record carries its descendants exactly as object.update, the
|
|
23
|
+
// inspector and the gizmo do (updateSceneObject): rigidly about the record's pivot, scale not carried.
|
|
24
|
+
const TRANSFORM = ['x', 'y', 'z', 'rotX', 'rot', 'rotZ'];
|
|
25
|
+
const carryDescendants = (rows, id, after) => after ? updateSceneObject(rows, id, Object.fromEntries(TRANSFORM.map(key => [key, after[key]]))) : rows;
|
|
26
|
+
registerElementKind('object', { collection: true, documentKey: 'objects', elements, normalize: normalizeObjectSet, carry: carryDescendants });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cameraKeyWarnings } from '../../camera-direction.js';
|
|
1
2
|
import { createShotAuthoringDocument } from '../../shot-authoring.js';
|
|
2
3
|
import { STUDIO_ELEMENTS } from '../../studio-elements.js';
|
|
3
4
|
import { registerElementKind } from '../elements.js';
|
|
@@ -5,5 +6,7 @@ import { registerElementKind } from '../elements.js';
|
|
|
5
6
|
function normalizeShot(record) {
|
|
6
7
|
return createShotAuthoringDocument({ shots: [record], frameCount: Math.max(24, record.endFrame + 1) }).shots[0];
|
|
7
8
|
}
|
|
9
|
+
// A shot.set that writes camera keys hears when they halt the move, like frame_shot does.
|
|
8
10
|
registerElementKind('shot', { collection: true, documentKey: 'shots',
|
|
9
|
-
elements: STUDIO_ELEMENTS.filter(row => row.path.startsWith('shot.')), normalize: normalizeShot
|
|
11
|
+
elements: STUDIO_ELEMENTS.filter(row => row.path.startsWith('shot.')), normalize: normalizeShot,
|
|
12
|
+
warn: (shot, set) => (Object.hasOwn(set, 'cameraKeys') ? cameraKeyWarnings(shot) : []) });
|
package/src/commands/elements.js
CHANGED
|
@@ -97,24 +97,34 @@ export function elementPatchArgs(kind, args) {
|
|
|
97
97
|
});
|
|
98
98
|
return collection ? ops.length === 1 ? ops[0] : { ops } : ops.reduce((patch, op) => mergeElementSet(patch, op.set), {});
|
|
99
99
|
}
|
|
100
|
+
// Unasked, an optional structured value (a glow, a light) is listed only where
|
|
101
|
+
// the item has one, so every plain object's receipt stays as short as before.
|
|
102
|
+
const listed = (element, item, paths) => paths ? paths.includes(element.path) : element.type !== 'object' || readElement(item, element.path) != null;
|
|
100
103
|
export function elementReadback(kind, item, paths, document) {
|
|
101
|
-
return patchElements(projectionKind(kind)).filter(element =>
|
|
104
|
+
return patchElements(projectionKind(kind)).filter(element => listed(element, item, paths)).map(element => {
|
|
102
105
|
const path = element.path;
|
|
103
106
|
if (element.readback) return { path, ...element.readback(item, document) };
|
|
104
107
|
const value = readElement(item, path);
|
|
105
108
|
if (value === null || value === undefined || value === '') return { path, text: null };
|
|
106
109
|
if (element.type === 'image') return { path, bytes: utf8ByteLength(value) };
|
|
110
|
+
// A small structured value (a light, a glow) reads back whole, as compact JSON.
|
|
111
|
+
if (element.type === 'object') return { path, text: JSON.stringify(value).slice(0, 512) };
|
|
107
112
|
if (element.type === 'vec3') return { path, vec: value };
|
|
108
113
|
if (element.type === 'array') return { path, count: Array.isArray(value) ? value.length : value.points?.length ?? 0 };
|
|
109
114
|
return { path, [typeof value === 'number' ? 'number' : typeof value === 'boolean' ? 'flag' : 'text']: value };
|
|
110
115
|
});
|
|
111
116
|
}
|
|
112
117
|
// Normalizer-added defaults are allowed; changed/clamped requested members
|
|
113
|
-
// are reported as dropped, just like the native patch planners.
|
|
114
|
-
|
|
118
|
+
// are reported as dropped, just like the native patch planners. A number that
|
|
119
|
+
// landed within float noise (a wrap leaves -17.529999999999973 for -17.53),
|
|
120
|
+
// or an angle that landed one or more whole turns away (190 kept as -170), survives.
|
|
121
|
+
const turn = (delta, angle) => angle ? delta - 360 * Math.round(delta / 360) : delta;
|
|
122
|
+
function survives(requested, actual, angle = false) {
|
|
123
|
+
if (typeof requested === 'number' && typeof actual === 'number') return requested === actual
|
|
124
|
+
|| Math.abs(turn(requested - actual, angle)) <= 1e-9 * Math.max(1, Math.abs(requested), Math.abs(actual));
|
|
115
125
|
if (requested === null || typeof requested !== 'object') return requested === actual;
|
|
116
|
-
if (Array.isArray(requested)) return Array.isArray(actual) && requested.length === actual.length && requested.every((value, index) => survives(value, actual[index]));
|
|
117
|
-
return actual !== null && typeof actual === 'object' && !Array.isArray(actual) && Object.entries(requested).every(([key, value]) => survives(value, actual[key]));
|
|
126
|
+
if (Array.isArray(requested)) return Array.isArray(actual) && requested.length === actual.length && requested.every((value, index) => survives(value, actual[index], angle));
|
|
127
|
+
return actual !== null && typeof actual === 'object' && !Array.isArray(actual) && Object.entries(requested).every(([key, value]) => survives(value, actual[key], angle));
|
|
118
128
|
}
|
|
119
129
|
export function elementPatchReceipt(receipt, request, projection) {
|
|
120
130
|
const kind = request?.args.ops[0].target.kind;
|
|
@@ -124,32 +134,46 @@ export function elementPatchReceipt(receipt, request, projection) {
|
|
|
124
134
|
const item = elementTarget(kind, value, target.id, receipt.host.sceneId);
|
|
125
135
|
const droppedPaths = Object.entries(set).filter(([key, requested]) => {
|
|
126
136
|
const element = patchElements(kind).find(element => element.path === `${kind}.${key}`);
|
|
127
|
-
return !element.readback && !survives(requested, readElement(item, element.path));
|
|
137
|
+
return !element.readback && !survives(requested, readElement(item, element.path), element.angle === true);
|
|
128
138
|
}).map(([key]) => `${kind}.${key}`);
|
|
129
139
|
return { index, status: droppedPaths.length ? 'partial' : receipt.authored ? 'applied' : 'noop', ...(droppedPaths.length ? { droppedPaths } : {}) };
|
|
130
140
|
});
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
|
|
141
|
+
// A dropped path makes the receipt "partial" whenever it published one revision: an applied
|
|
142
|
+
// patch, or one inside an agent turn (which the bus answers "completed"). A composite that moved
|
|
143
|
+
// the revision further stays "completed", and its ops name the dropped paths.
|
|
144
|
+
const partial = receipt.authored && ops.some(op => op.status === 'partial') && receipt.revision.after === receipt.revision.before + 1 && !receipt.nextHost;
|
|
145
|
+
// The protocol's partial variant is a patch receipt, not an action or composite receipt.
|
|
146
|
+
const { action, summary, kind: composite, ...base } = receipt;
|
|
147
|
+
// A row the edit carried along (a moved group's part) reads back where it went: its vec3 transform paths.
|
|
148
|
+
const targets = new Set(request.args.ops.map(({ target }) => target.id));
|
|
149
|
+
const moved = patchElements(kind).filter(element => element.type === 'vec3').map(element => element.path);
|
|
150
|
+
const carried = receipt.affectedIds.filter(id => !targets.has(id) && elementTarget(kind, value, id, receipt.host.sceneId)).map(id => ({ id, paths: moved }));
|
|
134
151
|
return { ...(partial ? base : receipt), ops, status: partial ? 'partial' : receipt.status,
|
|
135
|
-
delta: !receipt.authored ? [] : request.args.ops.
|
|
136
|
-
patched: elementReadback(kind, elementTarget(kind, value,
|
|
137
|
-
} })) };
|
|
152
|
+
delta: !receipt.authored ? [] : [...request.args.ops.map(({ target, set }) => ({ id: target.id, paths: Object.keys(set).map(key => `${kind}.${key}`) })), ...carried]
|
|
153
|
+
.slice(0, 8).map(({ id, paths }) => ({ id, after: { patched: elementReadback(kind, elementTarget(kind, value, id, receipt.host.sceneId), paths, value) } })) };
|
|
138
154
|
}
|
|
139
155
|
export function registerElementSet(registry, ports, declaration) {
|
|
140
156
|
const kind = declaration.id.slice(0, declaration.id.indexOf('.'));
|
|
141
|
-
const { normalize, collection } = kinds.get(kind);
|
|
157
|
+
const { normalize, collection, carry, warn } = kinds.get(kind);
|
|
142
158
|
registry.register({ ...declaration, available: () => true, run(args) {
|
|
143
159
|
const domain = ports.storeDomain(declaration.undoDomain ?? kind);
|
|
144
160
|
let next = domain.read();
|
|
145
161
|
const ops = collection ? args.ops ?? [args] : [{ id: ports.state().activeSceneId, set: args }];
|
|
162
|
+
const carried = new Set(), warnings = [];
|
|
146
163
|
for (const { id, set } of ops) {
|
|
147
164
|
const before = collection ? next.find(row => row.id === id) : next;
|
|
148
165
|
if (!before) throw new StudioProtocolError('TARGET_NOT_READY', `Unknown ${kind} id: ${id}`);
|
|
149
166
|
const after = normalize(mergeElementSet(before, storedElementSet(kind, set)));
|
|
167
|
+
if (warn) warnings.push(...warn({ ...after, id }, set));
|
|
168
|
+
// A kind whose rows hang off each other (a group's parts) moves the rows its target carries.
|
|
169
|
+
if (collection && carry) {
|
|
170
|
+
const rows = carry(next, id, after);
|
|
171
|
+
rows.forEach((row, index) => { if (row !== next[index] && row.id !== id) carried.add(row.id); });
|
|
172
|
+
next = rows;
|
|
173
|
+
}
|
|
150
174
|
next = collection ? next.map(row => row.id === id ? { ...after, id } : row) : after;
|
|
151
175
|
}
|
|
152
176
|
if (JSON.stringify(next) !== JSON.stringify(domain.read())) domain.write(next);
|
|
153
|
-
return { affectedIds: [...new Set(ops.map(op => op.id))], summary: `Updated ${kind}
|
|
177
|
+
return { affectedIds: [...new Set([...ops.map(op => op.id), ...carried])], summary: `Updated ${kind}.`, ...(warnings.length ? { warnings } : {}) };
|
|
154
178
|
} });
|
|
155
179
|
}
|
package/src/commands/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createStudioActionRegistry, STUDIO_ACTIONS } from "../studio-actions.js
|
|
|
8
8
|
import * as shot from "./shot.js";
|
|
9
9
|
import * as cast from "./cast.js";
|
|
10
10
|
import * as motion from "./motion.js";
|
|
11
|
+
import * as motion3 from "./motion3.js";
|
|
11
12
|
import * as objects from "./objects.js";
|
|
12
13
|
import * as view from "./view.js";
|
|
13
14
|
import * as scene from "./scene.js";
|
|
@@ -15,8 +16,9 @@ import * as project from "./project.js";
|
|
|
15
16
|
import * as exporting from "./export.js";
|
|
16
17
|
import * as ai from "./ai.js";
|
|
17
18
|
import * as stage from "./stage.js";
|
|
19
|
+
import * as kits from "./kits.js";
|
|
18
20
|
|
|
19
|
-
export const COMMAND_MODULES = Object.freeze({ shot, cast, motion, objects, view, scene, project, export: exporting, ai, stage });
|
|
21
|
+
export const COMMAND_MODULES = Object.freeze({ shot, cast, motion, motion3, objects, kits, view, scene, project, export: exporting, ai, stage });
|
|
20
22
|
|
|
21
23
|
export function commandDeclarations(modules = COMMAND_MODULES) {
|
|
22
24
|
return Object.values(modules).flatMap(module => module.declarations.map(declaration => ({
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// object.createKit: a ready-made prop (src/kits/) built as one group, an Empty root at the grip
|
|
2
|
+
// plus its parts, written as one undo entry. It is a job so the answer carries its output:
|
|
3
|
+
// { rootId, parts: { role: id }, bind, props?, skill? }. bind is the spec.objects snippet a take
|
|
4
|
+
// pastes: the root's carry (at/along pin it to the prop origin) and the switch entries of the
|
|
5
|
+
// parts that light up (docs/motion-api-v3.1.md C15).
|
|
6
|
+
import { buildKit, KIT_NAMES } from "../kits/index.js";
|
|
7
|
+
import { fail } from "./shared.js";
|
|
8
|
+
|
|
9
|
+
const KIT_HELP = `Create a ready-made, correctly scaled prop as one group (an Empty root at the grip plus its parts), one undo entry. kit: ${KIT_NAMES.join(", ")} (inspect_studio scope catalogue lists each with its parts). `
|
|
10
|
+
+ "position { x, y, z, rot? } places the root (world m, yaw degrees; default the origin, lifted so nothing is under the floor); name names the root. "
|
|
11
|
+
+ "Answers rootId, parts { role: id } and bind: the spec.objects snippet to paste into a motion spec (the root's carry: prop, at, along, plus switch entries { visible, level } that show the lit parts). "
|
|
12
|
+
+ "Kits without a skill also answer props (the grip prop to paste into spec.props). The cigarette, lighter and exhale need no paste: smokeCigarette args.kit { cig, lighter, exhale? } adds the entries itself "
|
|
13
|
+
+ "(exhale: a puff that follows the mouth, no placing). "
|
|
14
|
+
+ "Example: { kit: \"cigarette\" } -> { rootId: \"empty-3\", parts: { body: \"sculpt-2\", ember: \"sphere\", light: \"point-light\", smoke: \"effect\" }, bind: { \"empty-3\": { prop: \"cig\", at: [0, 0, 0], along: [0, 0, 1], visible: \"cig_vis\" }, "
|
|
15
|
+
+ "sphere: { visible: \"ember_vis\", level: \"ember\" }, \"point-light\": { visible: \"ember_vis\", level: \"ember\" }, effect: { visible: \"ember_vis\" } } }; "
|
|
16
|
+
+ "then motion.compile use [{ skill: \"smokeCigarette\", f0: 0, args: { kit: { cig: \"empty-3\", lighter: \"<lighter rootId>\" } } }].";
|
|
17
|
+
|
|
18
|
+
const number = { type: "number" };
|
|
19
|
+
const input = (properties, required = Object.keys(properties)) => ({ type: "object", properties, required, additionalProperties: false });
|
|
20
|
+
|
|
21
|
+
export const declarations = Object.freeze([
|
|
22
|
+
{ id: "object.createKit", label: "Create a prop kit", kind: "job", domain: "objects", description: KIT_HELP,
|
|
23
|
+
input: input({ kit: { type: "string", enum: KIT_NAMES }, name: { type: "string", minLength: 1, maxLength: 120 }, position: input({ x: number, y: number, z: number, rot: number }, []) }, ["kit"]) },
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
export function register(registry, ports) {
|
|
27
|
+
const owned = () => ports.storeDomain("objects");
|
|
28
|
+
const available = () => Boolean(ports.storeDomain?.("objects")) || "The objects document owner is not mounted.";
|
|
29
|
+
registry.register({ ...declarations[0], available, run({ kit, name, position }, context) {
|
|
30
|
+
const before = owned().read(), built = buildKit(kit, before, { name, position });
|
|
31
|
+
if (built.error) fail("INVALID_ARGUMENT", built.error);
|
|
32
|
+
const write = () => owned().write([...before, ...built.records]);
|
|
33
|
+
if (context?.commit) context.commit(write); else write();
|
|
34
|
+
const { records, ...output } = built, ids = records.map(row => row.id);
|
|
35
|
+
return { affectedIds: ids, output, summary: `Created the ${kit} kit ${built.rootId} (${Object.entries(built.parts).map(([role, id]) => `${role} ${id}`).join(", ")}).` };
|
|
36
|
+
} });
|
|
37
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Timing edits on an installed take (take-edit.js): cut it into segments, play a segment faster
|
|
2
|
+
// or slower, drop one, trim the ends, or restore the full take. The compiled take stays as it
|
|
3
|
+
// is; the edit rides its descriptor, so Undo, save/load and export all see the same timing.
|
|
4
|
+
// IK correction keys pinned to timeline frames move with the poses they address.
|
|
5
|
+
import { fail, characterOf } from "./shared.js";
|
|
6
|
+
import { createTakeEdit, isIdentityEdit, remapFrameKeys, removeSegment, setSegmentSpeed, splitTakeEdit, takeEditDuration, takeEditLayout, trimTakeEdit } from "../motion3/take-edit.js";
|
|
7
|
+
|
|
8
|
+
const id = { type: "string", minLength: 1 }, frame = { type: "integer", minimum: 0 };
|
|
9
|
+
const timing = (actionId, label, description, properties) => ({ id: actionId, label, description, kind: "mutation", undoDomain: "motion",
|
|
10
|
+
input: { type: "object", properties: { characterId: id, ...properties }, required: ["characterId", ...Object.keys(properties)], additionalProperties: false } });
|
|
11
|
+
const SEGMENTS = "The receipt output lists the segments ({ id, sourceStart, sourceEnd, speed, timelineStart, timelineEnd }) and the new frame count.";
|
|
12
|
+
|
|
13
|
+
export const timingDeclarations = [
|
|
14
|
+
timing("motion.cut", "Cut motion segment", `Cut the installed take into two segments at a timeline frame, so each part can get its own speed. ${SEGMENTS}`, { frame }),
|
|
15
|
+
timing("motion.setSegmentSpeed", "Set segment speed", `Play one segment faster or slower: 0.1-4 in 0.1 steps (0.5 = half-speed slow motion, 2 = double speed). The timeline grows or shrinks with it. ${SEGMENTS}`,
|
|
16
|
+
{ segmentId: id, speed: { type: "number", minimum: 0.1, maximum: 4 } }),
|
|
17
|
+
timing("motion.removeSegment", "Remove motion segment", `Drop one segment from the take; the rest close the gap. ${SEGMENTS}`, { segmentId: id }),
|
|
18
|
+
timing("motion.trim", "Trim motion", `Keep timeline frames start..end (inclusive) of the take. ${SEGMENTS}`, { start: frame, end: frame }),
|
|
19
|
+
timing("motion.resetTiming", "Restore full take timing", `Undo every cut, speed change and trim: the take plays as compiled. ${SEGMENTS}`, {}),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/** The take descriptor with this edit; an identity edit leaves no key (authored intent stays plain). */
|
|
23
|
+
function withEdit(take, edit, frames) {
|
|
24
|
+
const next = { ...take };
|
|
25
|
+
delete next.edit;
|
|
26
|
+
return isIdentityEdit(edit, frames) ? next : { ...next, edit };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function nextEdit(action, edit, args, frames) {
|
|
30
|
+
const known = () => edit.some(segment => segment.id === args.segmentId) || fail("INVALID_ARGUMENT", `No segment ${args.segmentId}; segments: ${edit.map(segment => segment.id).join(", ")}.`);
|
|
31
|
+
if (action === "motion.cut") {
|
|
32
|
+
const next = splitTakeEdit(edit, args.frame);
|
|
33
|
+
return next !== edit ? next : fail("INVALID_RANGE", `Frame ${args.frame} is not inside a segment (0 < frame < ${takeEditDuration(edit)}, not on a segment start).`);
|
|
34
|
+
}
|
|
35
|
+
if (action === "motion.setSegmentSpeed") { known(); return setSegmentSpeed(edit, args.segmentId, args.speed); }
|
|
36
|
+
if (action === "motion.removeSegment") { known(); return edit.length > 1 ? removeSegment(edit, args.segmentId) : fail("INVALID_ARGUMENT", "Use motion.clear to remove the last segment."); }
|
|
37
|
+
if (action === "motion.trim") {
|
|
38
|
+
try { return trimTakeEdit(edit, args.start, args.end); } catch (error) { return fail("INVALID_RANGE", error.message); }
|
|
39
|
+
}
|
|
40
|
+
return createTakeEdit(frames);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function registerTiming(registry, ports, available) {
|
|
44
|
+
const owner = () => ports.storeDomain("motion");
|
|
45
|
+
for (const declaration of timingDeclarations) registry.register({ ...declaration, available, run(args) {
|
|
46
|
+
const { characterId } = args;
|
|
47
|
+
characterOf(ports, characterId);
|
|
48
|
+
const row = owner().layer(characterId), full = owner().fullMotionFor(characterId);
|
|
49
|
+
if (!row.take || !full) fail("TARGET_NOT_READY", "Load a take for this character first.");
|
|
50
|
+
const previous = row.take.edit ?? createTakeEdit(full.frames), edit = nextEdit(declaration.id, previous, args, full.frames);
|
|
51
|
+
if (edit !== previous) {
|
|
52
|
+
owner().writeLayer(characterId, { take: withEdit(row.take, edit, full.frames), ikKeys: remapFrameKeys(row.ikKeys, previous, edit) });
|
|
53
|
+
ports.storeDomain("cast")?.syncTimeline?.();
|
|
54
|
+
}
|
|
55
|
+
return { affectedIds: [characterId], summary: declaration.label, output: { frames: takeEditDuration(edit), segments: takeEditLayout(edit) } };
|
|
56
|
+
} });
|
|
57
|
+
}
|
package/src/commands/motion.js
CHANGED
|
@@ -2,16 +2,11 @@
|
|
|
2
2
|
import { STUDIO_IK_CHAIN_TRACKS, studioActionDeclaration } from "../studio-actions.js";
|
|
3
3
|
import { fail, characterOf } from "./shared.js";
|
|
4
4
|
import { elementSetSchema, registerElementSet } from './elements.js';
|
|
5
|
+
import { registerTiming, timingDeclarations } from './motion-timing.js';
|
|
5
6
|
import './elements/motion.js';
|
|
6
7
|
import './elements/character.js';
|
|
7
|
-
import { generationArgs } from '../motion/generation.js';
|
|
8
|
-
import { applyRootDrop, normalizeRootDrop } from '../ardy/root-drop.js';
|
|
9
|
-
import { characterScaleFor } from '../ardy/npz.js';
|
|
10
|
-
import { I2V_MOTION_DURATIONS } from '../i2v-motion-client.js';
|
|
11
|
-
import { createMotionEdit, trimMotionEdit, splitMotionEdit, setMotionSegmentSpeed, removeMotionSegment } from '../ardy/motion-edit.js';
|
|
12
8
|
const id = { type: 'string', minLength: 1 }, frame = { type: 'integer', minimum: 0 };
|
|
13
9
|
const input = (properties, required = Object.keys(properties)) => ({ type: 'object', properties, required, additionalProperties: false });
|
|
14
|
-
const drop = { oneOf: [{ type: 'null' }, input({ from_s: { type: 'number', minimum: 0 }, to_s: { type: 'number', exclusiveMinimum: 0 }, meters: { type: 'number', exclusiveMinimum: 0, maximum: 30 } })] };
|
|
15
10
|
const mutation = (id, label, properties, required) => ({ id, label, description: label, kind: 'mutation', undoDomain: 'motion', input: input(properties, required) });
|
|
16
11
|
const setInput = elementSetSchema('motion');
|
|
17
12
|
// A collection transaction has no single native character target. Normalize
|
|
@@ -26,108 +21,28 @@ const rangePins = [
|
|
|
26
21
|
mutation('motion.rangePin.apply', 'Apply range pin', { characterId: id, pin: rangePin, replaceExisting: { type: 'boolean', default: false } }, ['characterId', 'pin']),
|
|
27
22
|
mutation('motion.rangePin.remove', 'Remove range pin', { characterId: id, pinId: id }),
|
|
28
23
|
];
|
|
29
|
-
const
|
|
30
|
-
{ id: 'motion.platformFit.run', label: 'Fit motion to platforms', description: 'Analyse scene platforms and prepare or apply an IK correction.', kind: 'job', domain: 'motion', timeoutMs: 300000, input: input({ characterId: id, preview: { type: 'boolean', default: false } }, ['characterId']) },
|
|
31
|
-
{ id: 'motion.platformFit.remove', label: 'Remove platform fit', description: 'Apply or remove the reviewed platform-fit correction.', kind: 'job', domain: 'motion', timeoutMs: 300000, input: input({ characterId: id, apply: { type: 'boolean', default: false } }, ['characterId']) },
|
|
32
|
-
];
|
|
33
|
-
const edits = [
|
|
34
|
-
{ ...mutation('motion.set', 'Set take fields', {}), input: setInput },
|
|
35
|
-
mutation('motion.trim', 'Trim motion', { characterId: id, start: frame, end: frame }),
|
|
36
|
-
mutation('motion.resetTrim', 'Restore full take', { characterId: id }),
|
|
37
|
-
mutation('motion.cut', 'Cut motion segment', { characterId: id, frame }),
|
|
38
|
-
mutation('motion.setSegmentSpeed', 'Set segment speed', { characterId: id, id, speed: { type: 'number', minimum: 0.05, maximum: 8 } }),
|
|
39
|
-
mutation('motion.removeSegment', 'Remove motion segment', { characterId: id, id }),
|
|
40
|
-
mutation('motion.fixCollisions', 'Fix body collisions', { characterId: id, scope: { type: 'string', enum: ['frame', 'clip'], default: 'frame' } }, ['characterId']),
|
|
41
|
-
];
|
|
42
|
-
const generate = { id: 'motion.generate', label: 'Generate motion', description: 'Generate the named character through the editor pipeline, following its prompt blocks, root path, pose pins and take lineage.',
|
|
43
|
-
kind: 'job', domain: 'motion', generation: 'motion', background: true, timeoutMs: 1000,
|
|
44
|
-
input: input({ characterId: id, drop, blocks: elementSetSchema('character').properties.set.properties.layer.properties.promptClips,
|
|
45
|
-
durationSeconds: { type: 'number', minimum: 1, maximum: 1200 }, seed: { type: 'integer', minimum: 0, maximum: 2147483647 } }, ['characterId']) };
|
|
46
|
-
const prepared = { ...mutation('motion.applyPrepared', 'Apply prepared motion edit', { characterId: id, token: id }), exposure: 'ui-only' };
|
|
47
|
-
const loads = [
|
|
48
|
-
{ id: 'motion.replace', label: 'Replace take', input: input({ characterId: id, url: id, prompt: { type: 'string', default: '' },
|
|
49
|
-
blocks: { type: 'array', maxItems: 120, items: input({ startFrame: frame, endFrame: { ...frame, minimum: 1 }, prompt: { type: 'string' } }) },
|
|
50
|
-
drop,
|
|
51
|
-
}, ['characterId', 'url']) },
|
|
52
|
-
{ id: 'motion.loadVersion', label: 'Restore take version', input: input({ characterId: id, motionUrl: id }) },
|
|
53
|
-
].map(entry => ({ ...entry, description: entry.label, kind: 'job', domain: 'motion' }));
|
|
24
|
+
const edits = [{ ...mutation('motion.set', 'Set take fields', {}), input: setInput }];
|
|
54
25
|
const tools = [
|
|
55
|
-
mutation('motion.applyPhysics', 'Apply reviewed physics', { characterId: id }),
|
|
56
|
-
mutation('motion.editTrail', 'Edit motion trail', { characterId: id, track: { type: 'string', enum: ['hips', 'leftFoot', 'rightFoot', 'leftHand', 'rightHand', 'head'], default: 'hips' }, grabFrame: frame, radiusFrames: { ...frame, minimum: 1 }, delta: input({ x: { type: 'number' }, y: { type: 'number' }, z: { type: 'number' } }) }, ['characterId', 'grabFrame', 'radiusFrames', 'delta']),
|
|
57
26
|
mutation('ik.applyPose', 'Key full-body pose', { characterId: id, frame, pose: { type: 'object', properties: { bones: { type: 'object', properties: {}, required: [], additionalProperties: true }, rootY: { type: 'number' } }, required: ['bones'], additionalProperties: true } }),
|
|
58
27
|
];
|
|
59
|
-
const physics = { id: 'motion.autoPhysics', label: 'Review motion physics', description: 'Analyse real rig motion and optionally apply one retained correction.', kind: 'job', domain: 'motion',
|
|
60
|
-
input: input({ characterId: id, apply: { type: 'boolean', default: true }, strength: { type: 'number', minimum: 0, maximum: 1, default: 1 },
|
|
61
|
-
protectedFrames: { type: 'array', items: frame, default: [] }, overrides: { type: 'array', default: [], items: input({
|
|
62
|
-
site: { type: 'string', enum: ['leftFoot', 'rightFoot', 'leftHand', 'rightHand', 'leftKnee', 'rightKnee'] }, start: frame, end: frame,
|
|
63
|
-
mode: { type: 'string', enum: ['plant', 'free'] },
|
|
64
|
-
}) },
|
|
65
|
-
}, ['characterId']) };
|
|
66
28
|
// These only queue the existing editor producers. Their eventual take
|
|
67
29
|
// publication is owned above; generation-pipeline unification remains #444.
|
|
68
|
-
const queued = ['motion.commitLineEdit', 'motion.regenerateTrail'].map(id => ({ id, label: id === 'motion.commitLineEdit' ? 'Commit line edit' : 'Regenerate trail edit',
|
|
69
|
-
description: 'Queue the current editor draft through the existing generation producer.', kind: 'transient', exposure: 'ui-only', input: input({ characterId: { type: 'string' } }) }));
|
|
70
30
|
const legacyIk = ['character.setIkKey', 'character.removeIkKey', 'character.clearIkKeys'].map(studioActionDeclaration);
|
|
71
31
|
const ik = legacyIk.map((entry, index) => ({ ...entry, id: ['ik.setKey', 'ik.removeKey', 'ik.clearKeys'][index] }));
|
|
72
32
|
|
|
73
|
-
const clear = { id: 'motion.clear', label: 'Clear motion', description: 'Clear the
|
|
33
|
+
const clear = { id: 'motion.clear', label: 'Clear motion', description: 'Clear the character\'s compiled take and its IK corrections.',
|
|
74
34
|
kind: 'mutation', undoDomain: 'motion', input: { type: 'object', properties: { characterId: { type: 'string' } }, required: ['characterId'], additionalProperties: false } };
|
|
75
|
-
const
|
|
76
|
-
input: input({ instruction: { oneOf: [{ const: '' }, { type: 'string', maxLength: 3700 }] }, promptOverride: { oneOf: [{ const: '' }, { type: 'string', maxLength: 3700 }] }, duration: { oneOf: I2V_MOTION_DURATIONS.map(value => ({ const: value })) } }, []) };
|
|
77
|
-
export const declarations = Object.freeze([videoDraft, generate, ...queued, prepared, ...loads, physics, ...tools, ...rangePins, ...platformFit, ...edits, ...legacyIk, ...ik, clear, ...["motion.generateAllBlocks", "motion.generateFromVideo"].map(studioActionDeclaration)]);
|
|
35
|
+
export const declarations = Object.freeze([...tools, ...rangePins, ...edits, ...timingDeclarations, ...legacyIk, ...ik, clear]);
|
|
78
36
|
|
|
79
37
|
export function register(registry, ports) {
|
|
80
38
|
const owner = () => ports.storeDomain('motion');
|
|
81
39
|
const mounted = () => Boolean(ports.storeDomain?.('motion')) || 'The motion owner is not mounted.';
|
|
82
40
|
const take = characterId => { characterOf(ports, characterId); return owner().motionFor(characterId) ?? fail('TARGET_NOT_READY', 'Load a take for this character first.'); };
|
|
83
|
-
registry.register({ ...videoDraft, available: mounted, run(args) {
|
|
84
|
-
owner().setVideoDraft(args);
|
|
85
|
-
return { affectedIds: [], summary: videoDraft.label };
|
|
86
|
-
} });
|
|
87
|
-
registry.registerToolAlias('generate_motion', generate.id, generationArgs);
|
|
88
|
-
registry.register({ ...generate, available: mounted, target: args => args.characterId, async run(args, context) {
|
|
89
|
-
characterOf(ports, args.characterId);
|
|
90
|
-
if (owner().isGenerating()) fail('TARGET_BUSY', 'A motion generation is already running.');
|
|
91
|
-
if (args.drop && !normalizeRootDrop(args.drop)) fail('INVALID_ARGUMENT', 'Invalid drop.');
|
|
92
|
-
if (args.blocks) context.run('character.setPromptBlocks', { characterId: args.characterId, blocks: args.blocks });
|
|
93
|
-
const generationContext = !args.drop ? context : { ...context, commit: apply => context.commit(() => {
|
|
94
|
-
const result = apply(), take = owner().motionFor(args.characterId), layer = owner().layer(args.characterId);
|
|
95
|
-
owner().replace(args.characterId, applyRootDrop(take, args.drop, { worldScale: characterScaleFor(take) }), {
|
|
96
|
-
recipe: layer.takeRecipe, versions: layer.takeVersions, ikKeys: layer.ikKeys,
|
|
97
|
-
});
|
|
98
|
-
owner().writeLayer(args.characterId, { committedIkEdits: layer.committedIkEdits });
|
|
99
|
-
return result;
|
|
100
|
-
}) };
|
|
101
|
-
await owner().generate(args, generationContext);
|
|
102
|
-
return { affectedIds: [args.characterId], summary: 'Generated motion.' };
|
|
103
|
-
} });
|
|
104
|
-
for (const declaration of queued) registry.register({ ...declaration, available: mounted, run({ characterId }) {
|
|
105
|
-
characterOf(ports, characterId);
|
|
106
|
-
if (ports.state().activeCharacterId !== characterId) fail('TARGET_NOT_READY', 'Select the character that owns this editor draft.');
|
|
107
|
-
if (declaration.id === 'motion.commitLineEdit') owner().requestLineEdit(); else owner().requestTrailRegeneration();
|
|
108
|
-
return { affectedIds: [], summary: declaration.label };
|
|
109
|
-
} });
|
|
110
|
-
registry.register({ ...prepared, available: mounted, run({ characterId, token }) {
|
|
111
|
-
characterOf(ports, characterId); const result = owner().applyPrepared(token);
|
|
112
|
-
return { affectedIds: result?.affectedIds ?? [characterId], summary: prepared.label };
|
|
113
|
-
} });
|
|
114
|
-
for (const declaration of loads) registry.register({ ...declaration, available: mounted, target: args => args.characterId, async run(args, context) {
|
|
115
|
-
characterOf(ports, args.characterId); await owner().loadRemote(args, context);
|
|
116
|
-
return { affectedIds: [args.characterId], summary: declaration.label };
|
|
117
|
-
} });
|
|
118
|
-
registry.register({ ...physics, available: mounted, target: args => args.characterId, async run(args, context) {
|
|
119
|
-
characterOf(ports, args.characterId);
|
|
120
|
-
return { affectedIds: [args.characterId], summary: physics.label, output: await owner().autoPhysics(args.characterId, args, context) };
|
|
121
|
-
} });
|
|
122
41
|
for (const declaration of tools) registry.register({ ...declaration, available: mounted, run(args) {
|
|
123
42
|
characterOf(ports, args.characterId);
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (args.frame >= ports.state().frameCount) fail('INVALID_RANGE', 'IK frame is outside the timeline.');
|
|
128
|
-
for (const angles of Object.values(args.pose.bones)) if (!Array.isArray(angles) || angles.length !== 3 || !angles.every(Number.isFinite)) fail('INVALID_ARGUMENT', 'Pose bones require three finite rotation angles.');
|
|
129
|
-
owner().keyPose(args.characterId, args.frame, args.pose);
|
|
130
|
-
}
|
|
43
|
+
if (args.frame >= ports.state().frameCount) fail('INVALID_RANGE', 'IK frame is outside the timeline.');
|
|
44
|
+
for (const angles of Object.values(args.pose.bones)) if (!Array.isArray(angles) || angles.length !== 3 || !angles.every(Number.isFinite)) fail('INVALID_ARGUMENT', 'Pose bones require three finite rotation angles.');
|
|
45
|
+
owner().keyPose(args.characterId, args.frame, args.pose);
|
|
131
46
|
return { affectedIds: [args.characterId], summary: declaration.label };
|
|
132
47
|
} });
|
|
133
48
|
for (const declaration of rangePins) registry.register({ ...declaration, available: mounted, run(args) {
|
|
@@ -137,37 +52,11 @@ export function register(registry, ports) {
|
|
|
137
52
|
: owner().removePin(args.characterId, args.pinId);
|
|
138
53
|
return { affectedIds: [args.characterId], summary: declaration.label, ...(output ? { output } : {}) };
|
|
139
54
|
} });
|
|
140
|
-
registry.register({ ...platformFit[0], available: mounted, target: args => args.characterId, async run(args, context) {
|
|
141
|
-
characterOf(ports, args.characterId);
|
|
142
|
-
const output = await owner().platformFitRun(args.characterId, context, { preview: args.preview === true });
|
|
143
|
-
return { affectedIds: [args.characterId], summary: platformFit[0].label, output };
|
|
144
|
-
} });
|
|
145
|
-
registry.register({ ...platformFit[1], available: mounted, target: args => args.characterId, run(args, context) {
|
|
146
|
-
characterOf(ports, args.characterId);
|
|
147
|
-
const output = owner().platformFitRemove(args.characterId, context, { apply: args.apply === true });
|
|
148
|
-
return { affectedIds: [args.characterId], summary: platformFit[1].label, ...(output ? { output } : {}) };
|
|
149
|
-
} });
|
|
150
55
|
registerElementSet({ register(entry) { registry.register({ ...entry, available: mounted, run(args) {
|
|
151
56
|
for (const op of args.ops ?? [args]) take(op.id);
|
|
152
57
|
return entry.run(args);
|
|
153
58
|
} }); } }, ports, edits[0]);
|
|
154
|
-
|
|
155
|
-
const { characterId } = args; characterOf(ports, characterId);
|
|
156
|
-
if (declaration.id === 'motion.fixCollisions') owner().fix(characterId, args.scope);
|
|
157
|
-
else {
|
|
158
|
-
const current = take(characterId), full = owner().fullMotionFor(characterId);
|
|
159
|
-
let segments = current.editSegments;
|
|
160
|
-
if (declaration.id === 'motion.trim') {
|
|
161
|
-
if (args.start > args.end || args.end >= current.frames) fail('INVALID_RANGE', 'Trim range is outside the take.');
|
|
162
|
-
segments = trimMotionEdit(segments, args.start, args.end);
|
|
163
|
-
} else if (declaration.id === 'motion.resetTrim') segments = createMotionEdit(full.frames);
|
|
164
|
-
else if (declaration.id === 'motion.cut') segments = splitMotionEdit(segments, args.frame);
|
|
165
|
-
else if (declaration.id === 'motion.setSegmentSpeed') segments = setMotionSegmentSpeed(segments, args.id, args.speed);
|
|
166
|
-
else { if (segments.length <= 1) fail('INVALID_ARGUMENT', 'Use motion.clear to remove the final segment.'); segments = removeMotionSegment(segments, args.id); }
|
|
167
|
-
if (segments !== current.editSegments) owner().editSegments(characterId, segments);
|
|
168
|
-
}
|
|
169
|
-
return { affectedIds: [characterId], summary: declaration.label };
|
|
170
|
-
} });
|
|
59
|
+
registerTiming(registry, ports, mounted);
|
|
171
60
|
for (const [index, declaration] of [...legacyIk, ...ik].entries()) registry.register({ ...declaration,
|
|
172
61
|
available: state => state.characters.length > 0 || 'There are no characters in this scene.', run(args) {
|
|
173
62
|
const character = characterOf(ports, args.characterId), kind = index % 3;
|
|
@@ -194,38 +83,6 @@ export function register(registry, ports) {
|
|
|
194
83
|
if (ports.state().activeCharacterId !== characterId) fail('TARGET_NOT_READY', 'Select this character before clearing its take.');
|
|
195
84
|
ports.clearMotionNative();
|
|
196
85
|
}
|
|
197
|
-
return { affectedIds: [characterId], summary: 'Cleared motion
|
|
198
|
-
} });
|
|
199
|
-
registry.register({ ...studioActionDeclaration("motion.generateAllBlocks"), target: () => ports.state().activeCharacterId,
|
|
200
|
-
available: state => state.generating ? "A motion generation is already running."
|
|
201
|
-
: !state.motionReady ? "The motion backend is not ready."
|
|
202
|
-
: state.promptBlockCount === 0 ? "The active character has no prompt block with text; write them with patch_elements character.promptBlocks." : true,
|
|
203
|
-
run: (_args, context) => {
|
|
204
|
-
const { activeCharacterId, promptBlockCount } = ports.state();
|
|
205
|
-
const shown = ports.runAllPromptBlocks(context) ?? [];
|
|
206
|
-
if (shown?.then) return shown.then(() => ({ affectedIds: activeCharacterId ? [activeCharacterId] : [], summary: `Generated motion from ${promptBlockCount} prompt blocks.` }));
|
|
207
|
-
// The generation queues synchronously or not at all; when it does not,
|
|
208
|
-
// the editor's last toast names the refusal (rig not loaded, a root
|
|
209
|
-
// waypoint outside the clip, an over-long block, a line-edit draft).
|
|
210
|
-
if (!ports.state().generating) fail("TARGET_NOT_READY", shown.length ? `Generation not started: ${shown.at(-1)}` : "The editor did not start the generation; check the active character's rig and prompt blocks.");
|
|
211
|
-
return { affectedIds: activeCharacterId ? [activeCharacterId] : [], summary: `Started generating the active character's motion from ${promptBlockCount} prompt block${promptBlockCount === 1 ? "" : "s"}.` };
|
|
212
|
-
} });
|
|
213
|
-
// AI-video motion: the agent panel's Generate motion (generateI2vMotion
|
|
214
|
-
// "act"), awaited to its clip. The AI motion card shows every failure it meets, so
|
|
215
|
-
// a refusal is silent in the UI and tells the model the reason in English.
|
|
216
|
-
registry.register({ ...studioActionDeclaration("motion.generateFromVideo"), domain: "motion", target: () => ports.state().activeCharacterId,
|
|
217
|
-
available: ({ i2vMotion }) => !i2vMotion.enabled ? "AI video motion (Fal) is not enabled for this account."
|
|
218
|
-
: !["idle", "done", "error", "failed"].includes(i2vMotion.status) ? "An AI video motion generation is already running; wait for it to finish."
|
|
219
|
-
: i2vMotion.dailyRemaining === 0 ? "The account's daily AI video generations are used up." : true,
|
|
220
|
-
run: async ({ instruction }, context) => {
|
|
221
|
-
const outcome = await ports.generateI2vMotion("act", instruction, context);
|
|
222
|
-
if (outcome.failed) fail("TARGET_NOT_READY", outcome.failed);
|
|
223
|
-
const { job, footage, dailyRemaining } = outcome;
|
|
224
|
-
if (!job.video?.url) fail("TARGET_NOT_READY", "The AI video model finished without returning a video.");
|
|
225
|
-
return { affectedIds: [], output: { videoUrl: job.video.url, resolution: job.resolution ?? null, durationSeconds: job.resultDuration ?? job.duration ?? null,
|
|
226
|
-
ingested: Boolean(footage), frames: footage?.frames ?? null, fps: footage?.fps ?? null, dailyRemaining },
|
|
227
|
-
summary: footage
|
|
228
|
-
? `The AI video (${job.resolution}, ${footage.frames} frames at ${footage.fps} fps) is ingested as Video capture footage and the timeline now spans it; its motion becomes a take once GVHMR extraction runs in the Video capture panel.`
|
|
229
|
-
: `The AI video is ready at ${job.video.url}, but ingesting it as footage failed; the Video capture panel shows why.` };
|
|
86
|
+
return { affectedIds: [characterId], summary: 'Cleared motion.' };
|
|
230
87
|
} });
|
|
231
88
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Where a motion compile runs. In the browser: one module Web Worker (motion3/compile-worker.js), started
|
|
2
|
+
// on the first compile and reused, jobs answered in arrival order, so a compile costs the worker's time
|
|
3
|
+
// and never blocks the viewport (the main thread runs the compiler ~4x slower and freezes while it does).
|
|
4
|
+
// Where there is no Worker (Node, tests) or the worker fails: the same job on the calling thread,
|
|
5
|
+
// synchronously, so `registry.run("motion.compile", ...)` there still answers at once. The answer is the
|
|
6
|
+
// same either way (compile-job.js runs the same code on the same plain inputs).
|
|
7
|
+
import { runCompileJob } from "../motion3/compile-job.js";
|
|
8
|
+
import { StudioProtocolError } from "../studio-agent-protocol.js";
|
|
9
|
+
|
|
10
|
+
/** A job error (or a worker's serialized one) as the Studio's protocol error when it carries a code. */
|
|
11
|
+
function rethrow(error) {
|
|
12
|
+
if (error?.code && !(error instanceof StudioProtocolError)) throw new StudioProtocolError(error.code, error.message);
|
|
13
|
+
if (error instanceof Error) throw error;
|
|
14
|
+
throw Object.assign(new Error(error?.message ?? String(error)), { name: error?.name ?? "Error" });
|
|
15
|
+
}
|
|
16
|
+
const local = job => { try { return runCompileJob(job); } catch (error) { return rethrow(error); } };
|
|
17
|
+
|
|
18
|
+
/** The browser's module worker, or null where there is none. */
|
|
19
|
+
function browserWorker() {
|
|
20
|
+
if (typeof Worker !== "function") return null;
|
|
21
|
+
return new Worker(new URL("../motion3/compile-worker.js", import.meta.url), { type: "module", name: "motion-compile" });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* createCompileRunner({ spawn }) -> { run(job) -> answer | Promise<answer>, takeWarning() -> string | null }
|
|
26
|
+
* spawn() -> a Worker-like object (postMessage, onmessage, onerror, terminate) or null for the calling thread.
|
|
27
|
+
*/
|
|
28
|
+
export function createCompileRunner({ spawn = browserWorker } = {}) {
|
|
29
|
+
let worker, dead = false, warning = null, next = 0;
|
|
30
|
+
const pending = new Map();
|
|
31
|
+
/** The worker failed (would not start, or crashed): every job from now on, and those in flight, run here. */
|
|
32
|
+
const fallBack = reason => {
|
|
33
|
+
dead = true;
|
|
34
|
+
worker?.terminate?.();
|
|
35
|
+
worker = null;
|
|
36
|
+
warning ??= `The motion compile worker stopped (${reason}); compiling on the main thread, which is slower and blocks the editor while it runs.`;
|
|
37
|
+
for (const [id, { job, resolve, reject }] of pending) { pending.delete(id); try { resolve(local(job)); } catch (error) { reject(error); } }
|
|
38
|
+
};
|
|
39
|
+
const started = () => {
|
|
40
|
+
if (worker || dead) return worker;
|
|
41
|
+
try { worker = spawn(); } catch (error) { fallBack(error?.message ?? String(error)); return null; }
|
|
42
|
+
if (!worker) { dead = true; return null; }
|
|
43
|
+
worker.onmessage = ({ data: { id, value, error } }) => {
|
|
44
|
+
const job = pending.get(id);
|
|
45
|
+
if (!job) return;
|
|
46
|
+
pending.delete(id);
|
|
47
|
+
if (error) { try { rethrow(error); } catch (thrown) { job.reject(thrown); } } else job.resolve(value);
|
|
48
|
+
};
|
|
49
|
+
worker.onerror = event => { event?.preventDefault?.(); fallBack(event?.message || "worker error"); };
|
|
50
|
+
worker.onmessageerror = () => fallBack("an answer could not be read");
|
|
51
|
+
return worker;
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
run(job) {
|
|
55
|
+
if (!started()) return local(job);
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const id = ++next;
|
|
58
|
+
pending.set(id, { job, resolve, reject });
|
|
59
|
+
try { worker.postMessage({ id, job }); } catch (error) { fallBack(error?.message ?? String(error)); }
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
/** The fallback note, once (a receipt warning), or null. */
|
|
63
|
+
takeWarning() { const note = warning; if (note) warning = ""; return note || null; },
|
|
64
|
+
};
|
|
65
|
+
}
|