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
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// CozyClay 3 motion: the agent writes a motion spec (keys, anchors, props, fits, generators,
|
|
2
|
+
// checks, goals), the deterministic compiler bakes every frame, QA measures it, and the take is
|
|
3
|
+
// installed on the character. Cheap enough to run in a loop until QA reports nothing. The words
|
|
4
|
+
// the agent reads live in motion3/docs.js; the contracts in docs/motion-api-v3.1.md.
|
|
5
|
+
import { fail, characterOf } from "./shared.js";
|
|
6
|
+
import { motionScene } from "../motion3/scene.js";
|
|
7
|
+
import { skeletonFor } from "../motion3/rigs/index.js";
|
|
8
|
+
import { fk } from "../motion3/compiler/skeleton.js";
|
|
9
|
+
import { add, apply, ry } from "../motion3/compiler/math.js";
|
|
10
|
+
import { resolveBone } from "../motion3/bones.js";
|
|
11
|
+
import { MOTION_ACTION_HELP as HELP } from "../motion3/docs.js";
|
|
12
|
+
import { DESCRIBE_TOPICS } from "../motion3/schema/fields.js";
|
|
13
|
+
import { describe, describeMany } from "../motion3/describe.js";
|
|
14
|
+
import { createSpecStore, specIdOf, SPEC_STORE_SIZE } from "../motion3/spec-store.js";
|
|
15
|
+
import { applyPatch } from "../motion3/json-patch.js";
|
|
16
|
+
import { deltaOf } from "../motion3/delta.js";
|
|
17
|
+
import { fitOutput, reportPage } from "../motion3/report.js";
|
|
18
|
+
import { keyObject } from "../motion3/key-edit.js";
|
|
19
|
+
import { expandUse } from "../motion3/skills/index.js";
|
|
20
|
+
import { publishDraft } from "../agent-progress.js";
|
|
21
|
+
import { carryEdit } from "../motion3/edit-carry.js";
|
|
22
|
+
import { normalizeCompileArgs } from "../motion3/lenient.js";
|
|
23
|
+
import { createCompileRunner } from "./motion3-compile.js";
|
|
24
|
+
|
|
25
|
+
const TIMELINE_FPS = 24;
|
|
26
|
+
const id = { type: "string", minLength: 1 };
|
|
27
|
+
const specId = { type: "string", pattern: "^spec-[0-9a-f]{12}$" };
|
|
28
|
+
const openObject = { type: "object", properties: {}, required: [], additionalProperties: true };
|
|
29
|
+
const input = (properties, required = Object.keys(properties)) => ({ type: "object", properties, required, additionalProperties: false });
|
|
30
|
+
const list = (items, maxItems) => ({ type: "array", items, minItems: 1, maxItems });
|
|
31
|
+
const numbers = n => ({ type: "array", items: { type: "number" }, minItems: n, maxItems: n });
|
|
32
|
+
|
|
33
|
+
// readOnly: reads the scene and the take, never changes them (C10).
|
|
34
|
+
const declarations = Object.freeze([
|
|
35
|
+
{ id: "motion.compile", label: "Compile motion", kind: "job", domain: "motion", timeoutMs: 120000, description: HELP.compile,
|
|
36
|
+
// patch may be empty: base alone (or patch: []) re-measures the stored spec, with new probes; "never" is install: false
|
|
37
|
+
input: input({ characterId: id, spec: openObject, base: specId, patch: { type: "array", items: openObject, maxItems: 64 },
|
|
38
|
+
install: { oneOf: [{ type: "boolean" }, { const: "ifClean" }, { const: "never" }], default: true },
|
|
39
|
+
probes: list(openObject, 16), repair: input({ rounds: { type: "integer", minimum: 1, maximum: 8, default: 4 } }, []) }, ["characterId"]) },
|
|
40
|
+
{ id: "motion.qa", label: "Measure motion", kind: "job", domain: "motion", timeoutMs: 120000, readOnly: true, description: HELP.qa,
|
|
41
|
+
input: input({ characterId: id, checks: openObject }, ["characterId"]) },
|
|
42
|
+
{ id: "motion.sample", label: "Sample motion", kind: "job", domain: "motion", timeoutMs: 30000, readOnly: true, description: HELP.sample,
|
|
43
|
+
input: input({ characterId: id, frames: { type: "array", items: { type: "integer", minimum: 0 }, minItems: 1, maxItems: 64 },
|
|
44
|
+
bones: { type: "array", items: id, maxItems: 65 } }, ["characterId", "frames"]) },
|
|
45
|
+
{ id: "motion.describe", label: "Describe motion spec", kind: "job", domain: "motion", timeoutMs: 10000, readOnly: true, description: HELP.describe,
|
|
46
|
+
input: input({ topic: { type: "string", enum: DESCRIBE_TOPICS }, name: id,
|
|
47
|
+
topics: list({ oneOf: [{ type: "string", enum: DESCRIBE_TOPICS }, input({ topic: { type: "string", enum: DESCRIBE_TOPICS }, name: id }, ["topic"])] }, 8) }, []) },
|
|
48
|
+
{ id: "motion.report", label: "Read motion report", kind: "job", domain: "motion", timeoutMs: 10000, readOnly: true, description: HELP.report,
|
|
49
|
+
input: input({ specId, part: { type: "string", enum: ["groups", "timeline", "probes"], default: "groups" }, from: { type: "integer", minimum: 0, default: 0 } }, ["specId"]) },
|
|
50
|
+
{ id: "motion.keyObject", label: "Key held object", kind: "job", domain: "motion", timeoutMs: 120000, description: HELP.keyObject,
|
|
51
|
+
input: input({ characterId: id, objectId: id, frame: { type: "integer", minimum: 0 }, pos: numbers(3), rot: numbers(4), aim: numbers(3), roll: { type: "number" },
|
|
52
|
+
angle: { type: "number" }, remove: { type: "boolean", default: false } }, ["characterId", "objectId", "frame"]) },
|
|
53
|
+
]);
|
|
54
|
+
export { declarations };
|
|
55
|
+
|
|
56
|
+
const DEFAULT_BONES = ["Hips", "Head", "LeftHand", "RightHand", "LeftFoot", "RightFoot"];
|
|
57
|
+
// the inputs a compile fixed instead of refusing (lenient.js), kept in every answer (report.js keeps the head)
|
|
58
|
+
const MAX_ADJUSTED = 8;
|
|
59
|
+
const adjustedOf = notes => (notes.length ? { adjusted: notes.length > MAX_ADJUSTED ? [...notes.slice(0, MAX_ADJUSTED - 1), `${notes.length - MAX_ADJUSTED + 1} more`] : notes } : {});
|
|
60
|
+
const adjustedSay = notes => (notes.length ? `; ${notes.length} input${notes.length > 1 ? "s" : ""} adjusted (output.adjusted)` : "");
|
|
61
|
+
const unknownSpec = (what, value, then = "send spec") => fail("INVALID_ARGUMENT", `${what} "${value}" is not known (the editor keeps the last ${SPEC_STORE_SIZE} compiled specs this session); ${then}`);
|
|
62
|
+
|
|
63
|
+
const sameArray = (a, b) => a?.length === b?.length && Array.prototype.every.call(a, (v, i) => v === b[i]);
|
|
64
|
+
/** True when `installed` is the take `take` would install: the same spec, placement, bones and object tracks. */
|
|
65
|
+
function sameTake(installed, take) {
|
|
66
|
+
return installed?.schema === take.schema && installed.frameCount === take.frameCount && installed.rig === take.rig
|
|
67
|
+
&& specIdOf(installed.spec ?? null) === specIdOf(take.spec) && JSON.stringify(installed.placement) === JSON.stringify(take.placement)
|
|
68
|
+
&& sameArray(installed.rot, take.rot) && sameArray(installed.hips, take.hips) && JSON.stringify(installed.objects ?? {}) === JSON.stringify(take.objects);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** then(value) now, or when the promise settles (a compile answers at once in Node, later from the browser's worker). */
|
|
72
|
+
const after = (value, then) => (typeof value?.then === "function" ? value.then(then) : then(value));
|
|
73
|
+
/** Runs `run`, turning a MOTION_SPEC_INVALID error into INVALID_ARGUMENT with the exact message. */
|
|
74
|
+
function asArgument(run) {
|
|
75
|
+
try {
|
|
76
|
+
return run();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (/^MOTION_SPEC_INVALID/.test(error.message)) fail("INVALID_ARGUMENT", error.message.replace(/^MOTION_SPEC_INVALID:\s*/, ""));
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function register(registry, ports) {
|
|
84
|
+
const store = createSpecStore();
|
|
85
|
+
const owner = () => ports.storeDomain("motion");
|
|
86
|
+
const mounted = () => Boolean(ports.storeDomain?.("motion")) || "The motion owner is not mounted.";
|
|
87
|
+
const always = () => true;
|
|
88
|
+
const rigOf = character => {
|
|
89
|
+
const sk = skeletonFor(character.model);
|
|
90
|
+
if (!sk) fail("TARGET_NOT_READY", `${character.subject || character.id} is a ${character.model} body, which motion compile cannot drive; use an X Bot or Y Bot character.`);
|
|
91
|
+
if (Math.abs((character.scale ?? 1) - 1) > 1e-6) fail("TARGET_NOT_READY", "Set the character's scale back to 1 before compiling motion.");
|
|
92
|
+
return sk;
|
|
93
|
+
};
|
|
94
|
+
// one compile worker per editor in the browser, the calling thread elsewhere (motion3-compile.js)
|
|
95
|
+
const runner = createCompileRunner();
|
|
96
|
+
const workerWarning = () => { const note = runner.takeWarning(); return note ? { warnings: [note] } : {}; };
|
|
97
|
+
/**
|
|
98
|
+
* spec on the character's rig and the current scene (compile-job.js) -> { stored (the spec a take keeps and its specId
|
|
99
|
+
* names), qa, ..., take when bake is given }, at once or as a promise. opts: probes, checks, lenient, repair.
|
|
100
|
+
*/
|
|
101
|
+
const compile = (character, spec, opts = {}, bake) => {
|
|
102
|
+
rigOf(character);
|
|
103
|
+
const placed = bake && { position: [character.x ?? 0, character.y ?? 0, character.z ?? 0], yaw: character.rot ?? 0, ...bake };
|
|
104
|
+
return runner.run({ model: character.model, scene: motionScene(ports.state().objects), spec, ...opts, ...(placed ? { bake: placed } : {}) });
|
|
105
|
+
};
|
|
106
|
+
/** The whole output, kept in the store (motion.report, delta) and cut to the receipt budget (report.js). */
|
|
107
|
+
const report = (r, head, extra = {}) => {
|
|
108
|
+
const specIdValue = store.put(r.stored);
|
|
109
|
+
store.keepParts(specIdValue, { groups: r.qa.groups, timeline: r.timeline, probes: r.probes ?? {} });
|
|
110
|
+
return fitOutput({ ...head, specId: specIdValue, qa: r.qa, observations: r.observations, ...extra, ...(r.probes ? { probes: r.probes } : {}), timeline: r.timeline,
|
|
111
|
+
fixed: r.fixed, ...(r.search ? { search: r.search } : {}), ...(r.used.length ? { skills: r.used } : {}), ms: r.ms });
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* A compiled take on the character, installed as one undo entry; put() keeps the timing edit of the take it replaces
|
|
115
|
+
* (edit-carry.js) and returns what it did with it (a note for the receipt), or undefined.
|
|
116
|
+
*/
|
|
117
|
+
const put = (character, take, context) => {
|
|
118
|
+
let note;
|
|
119
|
+
const apply = () => {
|
|
120
|
+
const id = character.id, edit = owner().layer?.(id)?.take?.edit, full = edit && owner().fullMotionFor?.(id), carried = full && carryEdit(edit, full.frames ?? full.frameCount, take.frameCount);
|
|
121
|
+
owner().replace(id, { ...take, frames: take.frameCount });
|
|
122
|
+
if (carried?.edit) { owner().writeLayer(id, { take: { ...owner().layer(id).take, edit: carried.edit } }); ports.storeDomain("cast")?.syncTimeline?.(); }
|
|
123
|
+
note = carried?.note;
|
|
124
|
+
};
|
|
125
|
+
if (context?.commit) context.commit(apply); else apply();
|
|
126
|
+
return note;
|
|
127
|
+
};
|
|
128
|
+
const installed = characterId => owner().motionFor(characterId)?.schema === "cozyclay.take.v3" ? owner().motionFor(characterId)
|
|
129
|
+
: fail("TARGET_NOT_READY", "This character has no compiled take; run motion.compile first.");
|
|
130
|
+
|
|
131
|
+
// normalizeArgs: other spellings of install and repair become the declared ones before the schema check (lenient.js)
|
|
132
|
+
registry.register({ ...declarations[0], normalizeArgs: normalizeCompileArgs, available: mounted, target: args => args.characterId, run({ characterId, spec, base, patch, install: asked = true, probes, repair }, context) {
|
|
133
|
+
const install = asked === "never" ? false : asked;
|
|
134
|
+
const character = characterOf(ports, characterId);
|
|
135
|
+
if ((spec === undefined) === (base === undefined)) fail("INVALID_ARGUMENT", "Send exactly one of spec or base (the specId of a compiled spec, with patch).");
|
|
136
|
+
if (patch !== undefined && base === undefined) fail("INVALID_ARGUMENT", "patch applies to base, the specId of a compiled spec; send base with it, or the whole spec.");
|
|
137
|
+
const patched = [], source = spec ?? asArgument(() => applyPatch(store.get(base) ?? unknownSpec("base", base), patch ?? [], patched));
|
|
138
|
+
// the take is always baked: the user watches every draft, installed or not
|
|
139
|
+
return after(compile(character, source, { probes, repair }, {}), r => {
|
|
140
|
+
const { qa, take } = r, notes = [...patched, ...(r.notes ?? [])];
|
|
141
|
+
// ifClean: install only a clean final result (qa.ok includes the goals; after repair when asked)
|
|
142
|
+
const wanted = install === "ifClean" ? qa.ok : install;
|
|
143
|
+
let installs = wanted, kept = false, timing;
|
|
144
|
+
// open ranges are stored with the values the search chose, so motion.qa re-measures this take
|
|
145
|
+
if (wanted) {
|
|
146
|
+
// ifClean of the take already installed changes nothing (no undo entry; IK edits on it stay)
|
|
147
|
+
kept = install === "ifClean" && sameTake(owner().motionFor(characterId), take);
|
|
148
|
+
installs = !kept;
|
|
149
|
+
if (installs) timing = put(character, take, context);
|
|
150
|
+
}
|
|
151
|
+
const delta = base === undefined ? {} : { delta: deltaOf(store.parts(base)?.groups ?? [], qa.groups, base) };
|
|
152
|
+
const how = kept ? "; already installed, unchanged" : install === "ifClean" ? (installs ? "; installed" : "; not installed") : "";
|
|
153
|
+
const output = report(r, { frames: r.frameCount, fps: TIMELINE_FPS, installed: wanted, ...adjustedOf(notes) }, { ...delta, ...(r.repair ? { repair: r.repair } : {}), ...(timing ? { timing } : {}) });
|
|
154
|
+
// agent-progress.js: viewport ghost, timeline beats, agent panel
|
|
155
|
+
publishDraft(characterId, { take, installed: installs || kept, specId: output.specId, label: r.label, qa, timeline: r.timeline });
|
|
156
|
+
return { affectedIds: installs ? [characterId] : [], summary: `Compiled ${r.frameCount} frames; QA ${qa.ok ? "clean" : `${qa.total} issues`}${how}${adjustedSay(notes)}.`, output, ...workerWarning() };
|
|
157
|
+
});
|
|
158
|
+
} });
|
|
159
|
+
registry.register({ ...declarations[1], available: mounted, run({ characterId, checks }) {
|
|
160
|
+
const character = characterOf(ports, characterId), take = installed(characterId);
|
|
161
|
+
return after(compile(character, take.spec, { checks }), r => ({ affectedIds: [], summary: `QA ${r.qa.ok ? "clean" : `${r.qa.total} issues`}${adjustedSay(r.notes ?? [])}.`,
|
|
162
|
+
output: report(r, adjustedOf(r.notes ?? [])), ...workerWarning() }));
|
|
163
|
+
} });
|
|
164
|
+
registry.register({ ...declarations[2], available: mounted, run({ characterId, frames, bones = DEFAULT_BONES }) {
|
|
165
|
+
const character = characterOf(ports, characterId), take = installed(characterId), sk = rigOf(character);
|
|
166
|
+
const yaw = ry(take.placement?.yaw ?? 0), at = take.placement?.position ?? [0, 0, 0], B = take.bones.length;
|
|
167
|
+
const rows = frames.map(f => {
|
|
168
|
+
const ff = Math.min(take.frameCount - 1, f);
|
|
169
|
+
const quats = Array.from({ length: B }, (_, b) => Array.from(take.rot.subarray((ff * B + b) * 4, (ff * B + b) * 4 + 4)));
|
|
170
|
+
const state = fk(sk, quats, Array.from(take.hips.subarray(ff * 3, ff * 3 + 3)));
|
|
171
|
+
return { frame: ff, bones: Object.fromEntries(bones.map(name => {
|
|
172
|
+
const { bone, hint } = resolveBone(sk, name);
|
|
173
|
+
if (bone === null) fail("INVALID_ARGUMENT", `Unknown bone ${name}${hint || "."}`);
|
|
174
|
+
return [name, add(at, apply(yaw, state.P[sk.index[bone]])).map(v => Math.round(v * 1e4) / 1e4)];
|
|
175
|
+
})) };
|
|
176
|
+
});
|
|
177
|
+
return { affectedIds: [], summary: `Sampled ${rows.length} frames.`, output: { frames: rows } };
|
|
178
|
+
} });
|
|
179
|
+
registry.register({ ...declarations[3], available: always, run({ topic, name, topics }) {
|
|
180
|
+
if (topic === undefined && topics === undefined) fail("INVALID_ARGUMENT", "Send topic (and name) for one answer, or topics: [{ topic, name? }] for several in one call.");
|
|
181
|
+
if (topics) {
|
|
182
|
+
// C19: a topic beside topics joins them; a name beside topics goes to the one ask that can take it
|
|
183
|
+
const asks = [...(topic === undefined ? [] : [{ topic }]), ...topics.map(t => (typeof t === "string" ? { topic: t } : { ...t }))];
|
|
184
|
+
const open = asks.filter(a => a.name === undefined && (asks.length === 1 || a.topic === "skills"));
|
|
185
|
+
if (name !== undefined && open.length !== 1) fail("INVALID_ARGUMENT", "name goes with one topic; inside topics give each its own: topics: [{ topic, name? }].");
|
|
186
|
+
if (name !== undefined) open[0].name = name;
|
|
187
|
+
const output = asArgument(() => describeMany(asks));
|
|
188
|
+
return { affectedIds: [], summary: `Described ${output.topics.map(a => (a.name ? `${a.topic} ${a.name}` : a.topic)).join(", ")}${output.more ? `; ${output.more.length} more did not fit` : ""}.`, output };
|
|
189
|
+
}
|
|
190
|
+
return { affectedIds: [], summary: `Described ${topic}${name ? ` ${name}` : ""}.`, output: asArgument(() => describe(topic, name)) };
|
|
191
|
+
} });
|
|
192
|
+
registry.register({ ...declarations[4], available: always, run({ specId: wanted, part = "groups", from = 0 }) {
|
|
193
|
+
const page = reportPage(store.parts(wanted) ?? unknownSpec("specId", wanted, "compile it again"), wanted, part, from);
|
|
194
|
+
return { affectedIds: [], summary: `${page.items.length} ${part} from ${from}.`, output: page };
|
|
195
|
+
} });
|
|
196
|
+
// motion.keyObject (C14): one key of a held object (or a hinge angle) in the installed take's spec, compiled
|
|
197
|
+
// and installed whatever QA says (a user edit), as one undo entry
|
|
198
|
+
registry.register({ ...declarations[5], available: mounted, target: args => args.characterId, run({ characterId, ...args }, context) {
|
|
199
|
+
const character = characterOf(ports, characterId), take = installed(characterId), scene = motionScene(ports.state().objects);
|
|
200
|
+
const { spec, keyed } = asArgument(() => keyObject(take.spec, scene, args, expandUse(take.spec, { sk: rigOf(character), scene, lenient: true }).spec.objects));
|
|
201
|
+
return after(compile(character, spec, { lenient: true }, { prompt: take.prompt }), r => {
|
|
202
|
+
const timing = put(character, r.take, context);
|
|
203
|
+
return { affectedIds: [characterId], summary: `${keyed.removed ? "Removed the key of" : "Keyed"} ${keyed.objectId} at f${keyed.f}; QA ${r.qa.ok ? "clean" : `${r.qa.total} issues`}; installed.`,
|
|
204
|
+
output: report(r, { frames: r.frameCount, fps: TIMELINE_FPS, installed: true, keyed }, timing ? { timing } : {}), ...workerWarning() };
|
|
205
|
+
});
|
|
206
|
+
} });
|
|
207
|
+
}
|
package/src/commands/objects.js
CHANGED
|
@@ -8,6 +8,8 @@ import { expandSculptParts, normalizeSculptRecipe, patchSculptRecipe } from '../
|
|
|
8
8
|
import { rankingRow, scoreSculptAgainstReference } from '../sculpt-compare.js';
|
|
9
9
|
import { browserSculptImages, shrinkRgba } from './sculpt-images.js';
|
|
10
10
|
import { clampWarning } from '../receipt-findings.js';
|
|
11
|
+
import { effectPresetSet } from '../effects/effect-record.js';
|
|
12
|
+
import { withLightWarnings } from '../light-blockers.js';
|
|
11
13
|
import { STUDIO_TOOL_SCHEMAS, StudioSchemas } from '../studio-agent-protocol.js';
|
|
12
14
|
|
|
13
15
|
const id = StudioSchemas.TargetGuard.properties.targetId;
|
|
@@ -80,7 +82,7 @@ export function register(registry, ports) {
|
|
|
80
82
|
// goes to the top where it is seen, not below a long expanded group.
|
|
81
83
|
const next = args.first ? [placed, ...before] : [...before, placed];
|
|
82
84
|
owned().write(args.parent === undefined ? next : setSceneObjectParent(next, placed.id, args.parent));
|
|
83
|
-
return result(before, 'Added object.');
|
|
85
|
+
return withLightWarnings(result(before, 'Added object.'), owned().read());
|
|
84
86
|
},
|
|
85
87
|
'object.sculpt': args => {
|
|
86
88
|
const before = owned().read();
|
|
@@ -156,13 +158,13 @@ export function register(registry, ports) {
|
|
|
156
158
|
},
|
|
157
159
|
'objects.arrange': args => {
|
|
158
160
|
const plan = owned().arrange(args);
|
|
159
|
-
return { affectedIds: plan.affectedIds, summary: 'Arranged objects.', checks: plan.checks, warnings: plan.warnings };
|
|
161
|
+
return withLightWarnings({ affectedIds: plan.affectedIds, summary: 'Arranged objects.', checks: plan.checks, warnings: plan.warnings }, owned().read());
|
|
160
162
|
},
|
|
161
163
|
};
|
|
162
164
|
// Scalar scale is a convenience at the command boundary; the registry still
|
|
163
165
|
// owns all field mapping, normalization, publication and patch aliases.
|
|
164
166
|
registerElementSet({ register(entry) {
|
|
165
|
-
const expand = op => typeof op.set.scale === 'number' ? { ...op, set: { ...op.set, scale: { x: op.set.scale, y: op.set.scale, z: op.set.scale } } } : op;
|
|
167
|
+
const expand = op => effectPresetSet(typeof op.set.scale === 'number' ? { ...op, set: { ...op.set, scale: { x: op.set.scale, y: op.set.scale, z: op.set.scale } } } : op, ports.state().objects);
|
|
166
168
|
registry.register({ ...entry, available, run: args => {
|
|
167
169
|
const ops = (args.ops ?? [args]).map(expand);
|
|
168
170
|
let draft = owned().read();
|
|
@@ -173,7 +175,7 @@ export function register(registry, ports) {
|
|
|
173
175
|
if (object.attach) fail('CAPABILITY_MISSING', 'Use object.group to preserve an attached object world transform.');
|
|
174
176
|
draft = setSceneObjectParent(draft, id, parent);
|
|
175
177
|
}
|
|
176
|
-
return entry.run(args.ops ? { ops } : ops[0]);
|
|
178
|
+
return withLightWarnings(entry.run(args.ops ? { ops } : ops[0]), owned().read());
|
|
177
179
|
} });
|
|
178
180
|
} }, ports, semantic[0]);
|
|
179
181
|
for (const declaration of semantic.slice(1)) registry.register({ ...declaration, available, run: methods[declaration.id] });
|
package/src/commands/shot.js
CHANGED
|
@@ -7,6 +7,9 @@ import { track } from '../analytics.js';
|
|
|
7
7
|
import { isImageAssetId } from '../scene-assets.js';
|
|
8
8
|
import { createStableItemId, updateStableItem } from '../stable-items.js';
|
|
9
9
|
import { railFollowForNewGeometry } from '../camera-rail-schedule.js';
|
|
10
|
+
import { buildRail } from '../camera-follow.js';
|
|
11
|
+
import { cameraKeyWarnings } from '../camera-direction.js';
|
|
12
|
+
import { TIMELINE_FPS } from '../timeline-defaults.js';
|
|
10
13
|
import { studioActionDeclaration, studioActionRefusal } from '../studio-actions.js';
|
|
11
14
|
import { STUDIO_TOOL_SCHEMAS, StudioSchemas } from '../studio-agent-protocol.js';
|
|
12
15
|
import { elementSetSchema, registerElementSet } from './elements.js';
|
|
@@ -18,7 +21,7 @@ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
|
18
21
|
const number = { type: 'number' };
|
|
19
22
|
const frame = { type: 'integer', minimum: 0 };
|
|
20
23
|
const input = (properties, required = Object.keys(properties)) => ({ type: 'object', properties, required, additionalProperties: false });
|
|
21
|
-
const mutation = (id, label, schema) => ({ id, label, description
|
|
24
|
+
const mutation = (id, label, schema, description = label) => ({ id, label, description, kind: 'mutation', undoDomain: 'shot', input: schema });
|
|
22
25
|
const existing = ['shot.create', 'shot.createStill', 'shot.setCaption', 'shot.setCastOverride', 'shot.setHold', 'shot.split', 'shot.duplicate', 'shot.remove', 'shot.setRange', 'shot.setCameraRail', 'shot.clearCameraRail', 'shot.reorder'].map(studioActionDeclaration);
|
|
23
26
|
const extra = [
|
|
24
27
|
mutation('shot.set', 'Set shot fields', elementSetSchema('shot')),
|
|
@@ -30,7 +33,8 @@ const extra = [
|
|
|
30
33
|
mutation('shot.clearKeys', 'Clear camera keys', input({ shotId: id })),
|
|
31
34
|
mutation('shot.setTimeline', 'Set shot timeline', input({ frameCount: { type: 'integer', minimum: 24, maximum: 28800 } })),
|
|
32
35
|
mutation('shot.setLens', 'Set camera lens', input({ fovDeg: { ...number, minimum: 14, maximum: 90 } })),
|
|
33
|
-
mutation('shot.frame', 'Frame the shot', { ...input({ ...STUDIO_TOOL_SCHEMAS.frame_shot.properties, preset: { type: 'string' } }, []), oneOf: [STUDIO_TOOL_SCHEMAS.frame_shot, input({ preset: { type: 'string' } })] }
|
|
36
|
+
mutation('shot.frame', 'Frame the shot', { ...input({ ...STUDIO_TOOL_SCHEMAS.frame_shot.properties, preset: { type: 'string' } }, []), oneOf: [STUDIO_TOOL_SCHEMAS.frame_shot, input({ preset: { type: 'string' } })] },
|
|
37
|
+
'Frame a shot on one character (the frame_shot tool): framing.intent { size, level, view, side, focalMm } or framing.exact { position, lookAt, focalMm }. Without keyAtFrame a shot that holds one framing is reframed whole: a held shot. keyAtFrame adds a camera key there; every key eases in and out, so the camera slows to a stop at each one: key a pan or zoom from one spot with two keys (its start and end), lay a moving camera as a rail (shot.setCameraRail) and cut to a new shot for a new view. On a rail shot it puts the shot back on keys. Answers checks { derivedSize, screenFraction, clipped, behindCamera, occluded, cameraSide } for the subject at that frame.'),
|
|
34
38
|
{ ...mutation('shot.replace', 'Replace shot authoring', input({ shots: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } } })), exposure: 'ui-only' },
|
|
35
39
|
{ ...mutation('shot.captureCamera', 'Capture camera framing', input({ shotId: id }, [])), exposure: 'ui-only' },
|
|
36
40
|
// The Board's Stylize result (or null to clear it). UI-only: the picture has
|
|
@@ -40,6 +44,14 @@ const extra = [
|
|
|
40
44
|
];
|
|
41
45
|
export const declarations = Object.freeze([extra[0], ...existing, ...extra.slice(1).map(entry => entry.id === 'shot.frame' ? entry : { ...entry, exposure: 'ui-only' })]);
|
|
42
46
|
|
|
47
|
+
// shot.setRange also takes range: [startFrame, endFrameExclusive] or flat startFrame/endFrameExclusive
|
|
48
|
+
// fields; both become range: { startFrame, endFrameExclusive } before validation (registry normalizeArgs).
|
|
49
|
+
function lenientRange(args) {
|
|
50
|
+
const { range, startFrame, endFrameExclusive, ...rest } = args, flat = Object.fromEntries(Object.entries({ startFrame, endFrameExclusive }).filter(([, v]) => v !== undefined));
|
|
51
|
+
if (Array.isArray(range) && range.length === 2 && !Object.keys(flat).length) return { ...rest, range: { startFrame: range[0], endFrameExclusive: range[1] } };
|
|
52
|
+
return range === undefined && Object.keys(flat).length ? { ...rest, range: flat } : args;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
export function register(registry, ports) {
|
|
44
56
|
const owner = () => ports.storeDomain('shot');
|
|
45
57
|
const mounted = () => Boolean(ports.storeDomain?.('shot')) || 'The shots document owner is not mounted.';
|
|
@@ -97,6 +109,27 @@ export function register(registry, ports) {
|
|
|
97
109
|
let next = 0;
|
|
98
110
|
writeStills(current.map(row => row.kind === 'still' ? order[next++] : row));
|
|
99
111
|
}
|
|
112
|
+
// A rail owns the frames it covers, so the camera keys there go. Its answer
|
|
113
|
+
// names the move it makes: length, speed, height, aim and lens.
|
|
114
|
+
function setRail({ shotId, points, height, aim, aimHeight, focalMm }) {
|
|
115
|
+
const shot = shotOf(shotId), cast = [...ports.state().characters, ...ports.state().objects];
|
|
116
|
+
for (const id of Array.isArray(aim) ? aim : []) if (!cast.some(row => row.id === id)) fail('STALE_TARGET', `Aim ${id} is not a character or object in this scene.`);
|
|
117
|
+
const railFollow = railFollowForNewGeometry(shot.camera?.railFollow, shot.endFrame - shot.startFrame + 1);
|
|
118
|
+
const span = railFollow?.mode === 'range' ? [shot.startFrame + railFollow.startFrame, shot.startFrame + railFollow.endFrame] : [shot.startFrame, shot.endFrame];
|
|
119
|
+
const patch = { cameraRail: points, mode: 'rail', railFollow, ...(focalMm === undefined ? {} : { focalMm }),
|
|
120
|
+
...(height === undefined ? {} : { craneHeight: { points: typeof height === 'number' ? [{ t: 0, height }, { t: 1, height }] : height } }),
|
|
121
|
+
...(aim === undefined && aimHeight === undefined ? {} : { aim: aimOf(aim === undefined ? createCameraBlock(shot.camera).aim : aim && (Array.isArray(aim) ? { ids: aim } : { point: aim }), aimHeight) }) };
|
|
122
|
+
const keys = shot.cameraKeys.filter(key => key.frame < span[0] || key.frame > span[1]);
|
|
123
|
+
patchShot(shotId, row => ({ ...row, cameraKeys: keys, camera: updateCameraBlock(row.camera, patch) }));
|
|
124
|
+
const camera = createCameraBlock(shotOf(shotId).camera), metres = buildRail(camera.cameraRail)?.length ?? 0, seconds = (span[1] - span[0] + 1) / TIMELINE_FPS;
|
|
125
|
+
const heights = camera.craneHeight?.points.map(point => point.height) ?? [camera.followCam.height], speed = metres / seconds;
|
|
126
|
+
const aimed = camera.aim?.ids ? `${camera.aim.ids.join(', ')}${camera.aim.height === undefined ? '' : ` at ${camera.aim.height.toFixed(2)} m`}` : (camera.aim ? `(${['x', 'y', 'z'].map(axis => camera.aim.point[axis].toFixed(2)).join(', ')})` : 'the follow subject');
|
|
127
|
+
const note = `${metres.toFixed(1)} m over ${seconds.toFixed(1)} s (${speed.toFixed(2)} m/s), height ${[...new Set([heights[0], heights.at(-1)])].map(h => h.toFixed(2)).join('-')} m, aim ${aimed}, ${camera.focalMm ? `${camera.focalMm} mm` : 'scene lens'}${shot.cameraKeys.length > keys.length ? `; ${shot.cameraKeys.length - keys.length} camera keys removed` : ''}.`;
|
|
128
|
+
const fast = speed > camera.followCam.maxDollySpeed ? [{ code: 'RAIL_TOO_FAST', id: shotId, message: `${speed.toFixed(1)} m/s is past the ${camera.followCam.maxDollySpeed} m/s dolly cap, so the camera lags; lengthen the shot or the rail.` }] : [];
|
|
129
|
+
return { note, warnings: fast };
|
|
130
|
+
}
|
|
131
|
+
// aimHeight moves a cast aim off the chest to a world height (a whole body, a face).
|
|
132
|
+
const aimOf = (aim, aimHeight) => (aim?.ids && aimHeight !== undefined ? { ids: aim.ids, height: aimHeight } : aim);
|
|
100
133
|
const methods = {
|
|
101
134
|
'shot.create': () => storyboard() ? createStill() : owner().write(current => addShotAtFrame(current, ports.state().frame, owner().state().frameCount, owner().capture())),
|
|
102
135
|
'shot.createStill': args => createStill(args),
|
|
@@ -119,9 +152,7 @@ export function register(registry, ports) {
|
|
|
119
152
|
const edges = startFrame > shot.endFrame ? [['end', endFrame], ['start', startFrame]] : [['start', startFrame], ['end', endFrame]];
|
|
120
153
|
owner().write(current => edges.reduce((rows, [edge, frame]) => resizeShot(rows, shotId, edge, frame, owner().state().frameCount), current));
|
|
121
154
|
},
|
|
122
|
-
'shot.setCameraRail':
|
|
123
|
-
cameraRail: points, mode: 'rail', railFollow: railFollowForNewGeometry(shot.camera?.railFollow, shot.endFrame - shot.startFrame + 1),
|
|
124
|
-
}) })),
|
|
155
|
+
'shot.setCameraRail': args => setRail(args),
|
|
125
156
|
'shot.clearCameraRail': ({ shotId }) => {
|
|
126
157
|
const shot = shotOf(shotId);
|
|
127
158
|
if (!createCameraBlock(shot.camera).cameraRail) fail('TARGET_NOT_READY', `${shot.name || shotId} has no camera rail.`);
|
|
@@ -159,14 +190,12 @@ export function register(registry, ports) {
|
|
|
159
190
|
};
|
|
160
191
|
registerElementSet(registry, ports, extra[0]);
|
|
161
192
|
for (const declaration of declarations.filter(row => row.id !== 'shot.set')) registry.register({ ...declaration,
|
|
193
|
+
...(declaration.id === 'shot.setRange' ? { normalizeArgs: lenientRange } : {}),
|
|
162
194
|
available: state => mounted() === true ? (availability[declaration.id] ?? (existing.includes(declaration) ? hasShots : () => true))(state) : mounted(),
|
|
163
195
|
run(args, context) {
|
|
164
|
-
if (declaration.id === 'shot.frame')
|
|
165
|
-
const plan = owner().frame(args);
|
|
166
|
-
return { affectedIds: plan.affectedIds, summary: 'Framed the shot.' };
|
|
167
|
-
}
|
|
196
|
+
if (declaration.id === 'shot.frame') return framed(args);
|
|
168
197
|
const before = owner().read(), newPanel = declaration.id === 'shot.createStill' || (declaration.id === 'shot.create' && storyboard());
|
|
169
|
-
methods[declaration.id](args);
|
|
198
|
+
const answered = methods[declaration.id](args), answer = typeof answered?.note === 'string' ? answered : null;
|
|
170
199
|
// Only the origin travels: never the caption, name or id of the panel.
|
|
171
200
|
if (newPanel) track('storyboard:panel_created', { source: context?.origin && context.origin !== 'ui' ? 'agent' : 'manual' });
|
|
172
201
|
// The owner re-normalizes every shot on write, so compare content, not
|
|
@@ -174,8 +203,17 @@ export function register(registry, ports) {
|
|
|
174
203
|
// names (100), and the first 100 stand for the batch.
|
|
175
204
|
const after = owner().read(), changed = changedIds(before.map(row => after.find(next => next.id === row.id && same(next, row)) ?? row), after).slice(0, 100);
|
|
176
205
|
const affectedIds = changed.length ? changed : [args.shotId ?? ports.state().activeSceneId];
|
|
177
|
-
|
|
206
|
+
const summary = changed.length ? `${declaration.label}: ${changed.map(id => after.find(row => row.id === id)).filter(Boolean).map(shotLabel).join('; ')}.` : `${declaration.label}.`;
|
|
207
|
+
return { affectedIds, summary: answer?.note ? `${summary.slice(0, -1)}: ${answer.note}`.slice(0, 240) : summary, ...(answer?.warnings ? { warnings: answer.warnings } : {}) };
|
|
178
208
|
},
|
|
179
209
|
});
|
|
210
|
+
// frame_shot answers what it measured, and what its keys do to a move.
|
|
211
|
+
function framed(args) {
|
|
212
|
+
const before = owner().read(), plan = owner().frame(args), shot = owner().read().find(row => row.id === plan.details.shotId);
|
|
213
|
+
const wasRail = createCameraBlock(before.find(row => row.id === shot.id)?.camera).mode === 'rail';
|
|
214
|
+
return { affectedIds: plan.affectedIds, summary: `Framed ${shotLabel(shot)}: ${plan.checks.derivedSize} at frame ${plan.details.frame}, ${shot.cameraKeys.length} camera key${shot.cameraKeys.length === 1 ? '' : 's'}.`.slice(0, 240),
|
|
215
|
+
checks: plan.checks, warnings: [...plan.warnings, ...(wasRail ? [{ code: 'RAIL_REPLACED', id: shot.id, message: 'This shot is back on camera keys; its rail no longer drives it. Re-lay it with shot.setCameraRail.' }] : []),
|
|
216
|
+
...cameraKeyWarnings(shot)] };
|
|
217
|
+
}
|
|
180
218
|
if (ports.storeDomain?.('shot')) registry.registerToolAlias('frame_shot', 'shot.frame');
|
|
181
219
|
}
|
package/src/commands/stage.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { elementSetSchema, registerElementSet } from './elements.js';
|
|
2
|
+
import { normalizeStage } from './elements/stage.js';
|
|
3
|
+
import { applyLightingRecipe, LIGHTING_KEEP_PATHS, LIGHTING_PRESETS } from '../stage-lighting.js';
|
|
2
4
|
export { STAGE_FIELDS, normalizeStage } from './elements/stage.js';
|
|
3
5
|
|
|
4
6
|
const input = elementSetSchema('stage');
|
|
5
7
|
const declaration = (id, properties = input.properties) => ({ id, label: 'Set stage', kind: 'mutation', undoDomain: 'stage',
|
|
6
8
|
description: 'Update authored stage fields, normalized by scene persistence.',
|
|
7
9
|
input: { ...input, properties } });
|
|
10
|
+
const applyLighting = { id: 'stage.applyLighting', label: 'Apply lighting', kind: 'mutation', undoDomain: 'stage',
|
|
11
|
+
description: 'Give the stage a whole lighting look in one undoable step: writes every stage.lighting field (sky, skyTop, ambient, hemiSky, hemiGround, fill, fog, exposure, shadowSoftness, envLight, ao) and the key light\'s colour, intensity and position from the recipe, then sets lighting.preset to its name. studio is the neutral grey default; day is a high sun under a blue sky; overcast is soft, shadowless grey daylight; golden a low warm sun with long shadows; dusk a dim warm horizon glow under a blue sky; night a dark blue-black sky, a dim cool moon key with soft shadows, low cool ambient and haze; interior a warm tungsten lamp in a dark room; stage a black void lit by the key alone. Every recipe but studio lights the set with a sky environment map (envLight 1) and darkens contacts and creases (ao 0.6 to 1); studio keeps both at 0. keep lists paths left as they are, e.g. ["keyLight.position"] to keep where the light stands; a recipe also clears an HDR environment (stage.lighting.hdr) unless keep has "lighting.hdr". Tweak any value afterwards with stage.set; check the result with verify_result checks ["lighting"].',
|
|
12
|
+
input: { type: 'object', properties: { preset: { type: 'string', enum: [...LIGHTING_PRESETS] },
|
|
13
|
+
keep: { type: 'array', items: { type: 'string', enum: [...LIGHTING_KEEP_PATHS] }, minItems: 1, maxItems: LIGHTING_KEEP_PATHS.length, uniqueItems: true } }, required: ['preset'], additionalProperties: false } };
|
|
8
14
|
export const declarations = Object.freeze([
|
|
9
15
|
declaration('stage.set'),
|
|
10
16
|
declaration('stage.setKeyLight', { keyLight: input.properties.keyLight }),
|
|
11
17
|
declaration('stage.setEnvironment', Object.fromEntries(['environment', 'environmentImage', 'hasEnvSheet'].map(key => [key, input.properties[key]]))),
|
|
12
18
|
declaration('stage.setStyle', { style: input.properties.style }),
|
|
13
19
|
declaration('stage.setFilmback', Object.fromEntries(['shotAspect', 'cameraPresetId', 'sensorId'].filter(key => input.properties[key]).map(key => [key, input.properties[key]]))),
|
|
20
|
+
applyLighting,
|
|
14
21
|
]);
|
|
15
22
|
export function register(registry, ports) {
|
|
16
|
-
for (const declaration of declarations) registerElementSet(registry, ports, declaration);
|
|
23
|
+
for (const declaration of declarations.filter(entry => entry !== applyLighting)) registerElementSet(registry, ports, declaration);
|
|
24
|
+
registry.register({ ...applyLighting, available: () => true, run({ preset, keep = [] }) {
|
|
25
|
+
const domain = ports.storeDomain('stage'), before = domain.read();
|
|
26
|
+
const next = normalizeStage({ ...before, ...applyLightingRecipe(before, preset, keep) });
|
|
27
|
+
if (JSON.stringify(next) !== JSON.stringify(before)) domain.write(next);
|
|
28
|
+
return { affectedIds: [ports.state().activeSceneId], summary: `Applied the ${preset} lighting recipe${keep.length ? `, keeping ${keep.join(', ')}` : ''}.` };
|
|
29
|
+
} });
|
|
17
30
|
}
|
package/src/commands/view.js
CHANGED
|
@@ -4,6 +4,7 @@ import { STUDIO_TOOL_SCHEMAS } from '../studio-agent-protocol.js';
|
|
|
4
4
|
import { ko } from '../locale.js';
|
|
5
5
|
import { shotAtFrame } from '../cuts.js';
|
|
6
6
|
import { fail } from './shared.js';
|
|
7
|
+
import { describeGhosts, setGhostSettings } from '../view-ghosts.js';
|
|
7
8
|
|
|
8
9
|
const fields = STUDIO_TOOL_SCHEMAS.operate_studio.properties;
|
|
9
10
|
const groups = { 'view.select': ['selection', 'shotId'], 'timeline.seek': ['frame'], 'timeline.play': ['playing'], 'view.setMode': ['mode', 'view'] };
|
|
@@ -11,7 +12,7 @@ const operations = Object.entries({ ...groups, 'view.update': Object.keys(fields
|
|
|
11
12
|
id, label: id, description: 'Change selection, timeline or viewer state without authoring history.', kind: 'transient',
|
|
12
13
|
input: { type: 'object', properties: Object.fromEntries(keys.map(key => [key, fields[key]])), required: [], additionalProperties: false },
|
|
13
14
|
}));
|
|
14
|
-
export const declarations = Object.freeze([...operations, ...["view.setPartColours", "view.setGuideMode", "view.setInset"].map(studioActionDeclaration)]);
|
|
15
|
+
export const declarations = Object.freeze([...operations, ...["view.setPartColours", "view.setGuideMode", "view.setInset", "view.setGhosts"].map(studioActionDeclaration)]);
|
|
15
16
|
// A storyboard project has no Motion department; every door says so the same way.
|
|
16
17
|
export const STORYBOARD_MOTION_REFUSAL = Object.freeze({ en: 'Motion tools are not part of a Storyboard project.', ko: '모션 도구는 스토리보드 프로젝트에 없어요.' });
|
|
17
18
|
export const viewCommand = args => Object.entries(groups).find(([, keys]) => Object.keys(args).every(key => keys.includes(key)))?.[0] ?? 'view.update';
|
|
@@ -45,4 +46,6 @@ export function register(registry, ports) {
|
|
|
45
46
|
viewAction("view.setPartColours", ({ mode }) => { ports.choosePartColours(mode); return `Part colours: ${mode}.`; });
|
|
46
47
|
viewAction("view.setGuideMode", ({ mode }) => { ports.setGuideMode(mode); return `Composition guide: ${mode}.`; });
|
|
47
48
|
viewAction("view.setInset", ({ collapsed }) => { ports.setInsetCollapsed(collapsed); return `Top-View inset ${collapsed ? "folded" : "unfolded"}.`; });
|
|
49
|
+
// The ghosts' setting lives with them (view-ghosts.js), not in the App: no port to reach.
|
|
50
|
+
viewAction("view.setGhosts", args => describeGhosts(setGhostSettings(args)));
|
|
48
51
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// The active scene's shot document and stage envelope, read from the stores at
|
|
2
|
+
// the moment they are used. App used to rebuild both during render, so a
|
|
3
|
+
// synchronous command read them one render behind: the Studio binding folds
|
|
4
|
+
// them into the scene revision (stage and scenes documents), the receipt
|
|
5
|
+
// answered revision N, and the next render moved the scene to N+1 by itself
|
|
6
|
+
// when the mirrors caught up. An agent's next write, admitted at the receipt's
|
|
7
|
+
// revision.after (a motion install, a cast move, a shot edit), then met
|
|
8
|
+
// STALE_SCENE although nothing had changed.
|
|
9
|
+
import { createShotAuthoringDocument } from "./shot-authoring.js";
|
|
10
|
+
import { savedTakeSource } from "./domains/motion/take-source.js";
|
|
11
|
+
|
|
12
|
+
// IK edits (Pose mode, IK keys, range pins) ride into the saved stage with each character.
|
|
13
|
+
const ikEditsOf = row => (!row || (!row.ikKeys?.length && !row.ikPins?.length) ? null : { keys: row.ikKeys, pins: row.ikPins ?? [], pinResiduals: row.ikPinResiduals ?? [] });
|
|
14
|
+
|
|
15
|
+
/** Ref-shaped mirrors (`.current`) for the scene snapshot and the stage domain.
|
|
16
|
+
* `actorStageRef.current` keeps the envelope App (or the stage domain) last set
|
|
17
|
+
* and always answers the cast as it is now: sessionMotion is stripped (baked
|
|
18
|
+
* takes are session-only and far too heavy for the stage envelope); a take v3
|
|
19
|
+
* persists as its source (spec, placement). */
|
|
20
|
+
export function createDocumentMirrors(appContext) {
|
|
21
|
+
const cast = () => appContext.storeDomain("cast")?.projection() ?? appContext.live.characters ?? [];
|
|
22
|
+
const activeWaypoints = list => (list.find(entry => entry.id === appContext.storeDomain("cast")?.activeId) ?? list[0])?.layer?.waypoints ?? [];
|
|
23
|
+
const shotDocumentRef = {
|
|
24
|
+
get current() {
|
|
25
|
+
const shots = appContext.storeDomain("shot");
|
|
26
|
+
return createShotAuthoringDocument({ shots: shots?.read() ?? [], waypoints: activeWaypoints(cast()), frameCount: shots?.state().frameCount ?? null });
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
let envelope = null;
|
|
30
|
+
const stageCharacters = () => {
|
|
31
|
+
const layers = appContext.storeDomain("motion")?.read() ?? null;
|
|
32
|
+
return cast().map(({ sessionMotion, ...entry }) => {
|
|
33
|
+
const row = layers?.find(layer => layer.id === entry.id);
|
|
34
|
+
return { ...entry, ikEdits: ikEditsOf(row), motionTake: savedTakeSource(row?.take) };
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const actorStageRef = {
|
|
38
|
+
get current() { return envelope && { ...envelope, characters: stageCharacters() }; },
|
|
39
|
+
set current(value) { envelope = value; },
|
|
40
|
+
};
|
|
41
|
+
return { shotDocumentRef, actorStageRef };
|
|
42
|
+
}
|
package/src/document-store.js
CHANGED
|
@@ -38,6 +38,10 @@ export function createDocumentStore({ owned = {}, dev = import.meta.env?.DEV ??
|
|
|
38
38
|
run(fn) { check(); running++; try { return fn(); } finally { running--; } },
|
|
39
39
|
touch(domain) { check(); requireOwner(domain); },
|
|
40
40
|
update(domain, update) { return session.run(() => write(domain, update)); },
|
|
41
|
+
// A point inside the open transaction (a script's start in an agent turn): rollback(mark)
|
|
42
|
+
// restores it and the transaction stays open; rollback() restores where it began.
|
|
43
|
+
mark() { check(); return slices; },
|
|
44
|
+
rollback(mark = before) { check(); publish(mark); },
|
|
41
45
|
cancel({ restore = true } = {}) {
|
|
42
46
|
if (active !== session) return false;
|
|
43
47
|
active = null;
|