cozyclay 2.1.0 → 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 +65 -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 +22 -6
- 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-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
- package/dist/assets/{landing-BSesrCpL.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/llms.txt +14 -0
- 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 +174 -2639
- 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 +48 -419
- 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/PoseImagePanel.jsx +1 -1
- 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 +11 -53
- package/src/shell/CompositionGuideButton.jsx +20 -0
- 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/ShotPreviewHeader.jsx +17 -0
- package/src/shell/TopBar.jsx +5 -83
- package/src/shell/ViewportToolbar.jsx +23 -93
- 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/shell/viewport.css +12 -0
- 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 +46 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +46 -73
- package/src/studio-app-binding.js +29 -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/headless-studio.mjs +85 -0
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +33 -83
- 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 +9 -124
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-B41FmPYI.js +0 -1
- package/dist/assets/app-Do-Og5Pu.css +0 -1
- package/dist/assets/app-DwP-tl2x.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-Dt0qZfS7.js +0 -1
- package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-jqMhqL88.js +0 -1
- package/dist/assets/workflow-Br-K7Srs.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 -159
- 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,547 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* line-edit-job.mjs — the composition layer for a ProjFlow line edit.
|
|
3
|
-
*
|
|
4
|
-
* Wave 1 landed four independent pieces: a runner that can drive the box
|
|
5
|
-
* (runner.mjs), a wrapper that runs one edit and returns raw hml22 positions
|
|
6
|
-
* (generate.mjs `lineEditOnBox`), a skeleton converter both ways
|
|
7
|
-
* (hml22-to-cskel27.mjs), and a draw UI. NONE of them touches a cclay take.
|
|
8
|
-
* This file is the only place where a drawn line becomes an edited npz, and it
|
|
9
|
-
* is deliberately a pure async function rather than another CLI: the bridge
|
|
10
|
-
* imports it the way it already imports handleExtract, so there is no second
|
|
11
|
-
* process, no second argv contract, and no second copy of the frame arithmetic.
|
|
12
|
-
*
|
|
13
|
-
* THE PIPELINE, in one place because every step's rounding depends on the one
|
|
14
|
-
* before it:
|
|
15
|
-
*
|
|
16
|
-
* take.npz (cskel27, 24 fps)
|
|
17
|
-
* -> cskel27ToHml22Positions pure index gather, lossless
|
|
18
|
-
* -> resample 24 -> 20 fps linear, positions only
|
|
19
|
-
* -> source.npy (T20,22,3) what driver.py derives its T from
|
|
20
|
-
* -> lineEditOnBox the GPU round trip
|
|
21
|
-
* -> hml22ToCskel27Motion rotation lift, 20 fps
|
|
22
|
-
* -> retimeMotion(.., 24) the same call Kimodo's 30 fps output makes
|
|
23
|
-
* -> replaceMotionSegment outside the range: the ORIGINAL bytes
|
|
24
|
-
* -> out.npz
|
|
25
|
-
*
|
|
26
|
-
* THE CLOCK RULE, stated once and used everywhere: every 24 -> 20 conversion is
|
|
27
|
-
* `Math.round(frames24 * 20 / 24)` — the same half-up rounding for the clip
|
|
28
|
-
* length and for BOTH ends of frameRange, so the range can never scale to a
|
|
29
|
-
* span the resampled source does not contain. (Kimodo's cliGenFrames truncates
|
|
30
|
-
* instead, because it must agree with a python `int()` on the box. Nothing
|
|
31
|
-
* truncates here: driver.py derives T from the source npy THIS file writes, so
|
|
32
|
-
* the source length is authoritative and the only requirement is that one rule
|
|
33
|
-
* is used consistently. Rounding is the one that keeps the resampled clip the
|
|
34
|
-
* same DURATION as the take, which is what the artist sees.)
|
|
35
|
-
*
|
|
36
|
-
* WHY LINEAR AND NOT NEAREST for the 24 -> 20 source resample: driver.py turns
|
|
37
|
-
* those frames into hard identity rows (`build_preserve_rows`), so a resampled
|
|
38
|
-
* frame is not a hint, it is an observation the sampler must satisfy exactly.
|
|
39
|
-
* Nearest-neighbour would alias — every 6th frame duplicated — and the sampler
|
|
40
|
-
* would faithfully reproduce a stutter that is not in the take.
|
|
41
|
-
*
|
|
42
|
-
* SPLICING IS A HARD CUT, ON PURPOSE. Outside the edit range the output is the
|
|
43
|
-
* source take byte for byte (replaceMotionSegment copies the original arrays);
|
|
44
|
-
* inside it, the converted result. No crossfade is applied. Gate GP2 asks
|
|
45
|
-
* whether the seams pop, and a crossfade applied up front would make that
|
|
46
|
-
* question unanswerable — so the job MEASURES the seams instead
|
|
47
|
-
* (`seamStartDelta` / `seamEndDelta` against the take's own median frame delta)
|
|
48
|
-
* and hands the numbers to the caller. A blend window is a follow-up knob to be
|
|
49
|
-
* turned on evidence, not a silent default.
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
53
|
-
import { tmpdir } from "node:os";
|
|
54
|
-
import { join } from "node:path";
|
|
55
|
-
|
|
56
|
-
import { motionArraysToNpzMembers, replaceMotionSegment, writeNpz } from "../ardy/npz.mjs";
|
|
57
|
-
import { readNpz } from "../kimodo/read-npz.mjs";
|
|
58
|
-
import { retimeMotion } from "../../src/ardy/retime.js";
|
|
59
|
-
import { cskel27ToHml22Positions, hml22ToCskel27Motion } from "./hml22-to-cskel27.mjs";
|
|
60
|
-
import { GEN_FPS, NUM_JOINTS, lineEditOnBox, writeNpyFloat32 } from "./generate.mjs";
|
|
61
|
-
import { nativeMotionPath } from "./runner.mjs";
|
|
62
|
-
|
|
63
|
-
/** The app/timeline/bridge clock. Kept as a parameter everywhere below so the
|
|
64
|
-
* tests can pin the arithmetic without a 24 fps fixture, but there is exactly
|
|
65
|
-
* one production value and the bridge passes it explicitly. */
|
|
66
|
-
export const APP_FPS = 24;
|
|
67
|
-
|
|
68
|
-
const CSKEL_JOINTS = 27;
|
|
69
|
-
|
|
70
|
-
/* ------------------------------------------------------------------------- */
|
|
71
|
-
/* the clock */
|
|
72
|
-
/* ------------------------------------------------------------------------- */
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* App frames (24 fps) -> generation frames (20 fps). THE rounding rule.
|
|
76
|
-
*
|
|
77
|
-
* Used for the clip length and for both ends of frameRange. Exported so the
|
|
78
|
-
* verify file can pin it directly instead of inferring it from a spliced take.
|
|
79
|
-
*/
|
|
80
|
-
export function toGenFrames(appFrames, { appFps = APP_FPS, genFps = GEN_FPS } = {}) {
|
|
81
|
-
if (!Number.isFinite(appFrames)) throw new Error(`toGenFrames: ${JSON.stringify(appFrames)} is not a frame count`);
|
|
82
|
-
return Math.round(appFrames * (genFps / appFps));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* The generation-clock frame range driver.py receives, from the app-clock
|
|
87
|
-
* half-open range the artist selected.
|
|
88
|
-
*
|
|
89
|
-
* Two conversions happen here and both are worth naming:
|
|
90
|
-
* 1. HALF-OPEN -> INCLUSIVE. Every range that crosses the bridge is
|
|
91
|
-
* [start, end) — preserve.editRanges, the prompt schedule, C6 itself.
|
|
92
|
-
* driver.py's is INCLUSIVE (`0 <= start <= end < frames`, span =
|
|
93
|
-
* end - start + 1), so the exclusive end becomes `end - 1` here, once.
|
|
94
|
-
* 2. The clamp. `end` must stay strictly inside the source's T (the driver
|
|
95
|
-
* indexes it), and the span must stay >= 2 frames or the "line" is one
|
|
96
|
-
* pose. A range ending exactly at the clip length is the common case —
|
|
97
|
-
* the artist selected to the end of the take — and it lands on T20, which
|
|
98
|
-
* is one past the last row.
|
|
99
|
-
*/
|
|
100
|
-
export function scaleFrameRange({ startFrame, endFrame }, genFrames, { appFps = APP_FPS, genFps = GEN_FPS } = {}) {
|
|
101
|
-
if (!Number.isInteger(startFrame) || !Number.isInteger(endFrame) || startFrame < 0 || endFrame <= startFrame) {
|
|
102
|
-
throw new Error(`scaleFrameRange: ${startFrame}..${endFrame} is not a non-empty half-open range`);
|
|
103
|
-
}
|
|
104
|
-
if (!Number.isInteger(genFrames) || genFrames < 2) {
|
|
105
|
-
throw new Error(`scaleFrameRange: the generation clip needs at least 2 frames, got ${genFrames}`);
|
|
106
|
-
}
|
|
107
|
-
const scale = (frame) => toGenFrames(frame, { appFps, genFps });
|
|
108
|
-
// start can never sit on the last two rows: the span below needs somewhere
|
|
109
|
-
// to end, and a two-frame edit at the very tail is still a two-frame edit.
|
|
110
|
-
const start = Math.min(Math.max(scale(startFrame), 0), genFrames - 2);
|
|
111
|
-
const endExclusive = Math.min(Math.max(scale(endFrame), start + 2), genFrames);
|
|
112
|
-
return { start, end: endExclusive - 1 };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* `pins3d` on the app clock -> `pins3d` on the generation clock.
|
|
117
|
-
*
|
|
118
|
-
* ONE APP PIN BECOMES THE GENERATION FRAMES THAT APP FRAME IS REBUILT FROM,
|
|
119
|
-
* which is usually two, and that is the whole subtlety of this function.
|
|
120
|
-
*
|
|
121
|
-
* The naive version rounded to a single generation frame and it was MEASURED
|
|
122
|
-
* wrong end to end: the box satisfied the pin exactly (9.5e-7 m at generation
|
|
123
|
-
* frame 83) and the finished take still missed it by 27 cm. Nothing was broken
|
|
124
|
-
* in between — retimeMotion rebuilds app frame f from generation frames
|
|
125
|
-
* `floor(f/ratio)` and `floor(f/ratio)+1`, blended by the fraction, and app
|
|
126
|
-
* frame 100 sits at generation 83.33. So the take at frame 100 is two thirds of
|
|
127
|
-
* an exactly-pinned frame and one third of its UNPINNED neighbour, and that
|
|
128
|
-
* neighbour is 0.2 m away, because a lone hard observation with free
|
|
129
|
-
* neighbours is a spike (the paper's own Plain Masking ablation, which it
|
|
130
|
-
* reports as the worst configuration).
|
|
131
|
-
*
|
|
132
|
-
* Constraining BOTH bracketing frames makes the authored statement survive the
|
|
133
|
-
* clock change: whatever the blend weight is, it is a blend of two frames that
|
|
134
|
-
* both hold the pinned position, so the app frame the artist pinned lands on
|
|
135
|
-
* the value they placed. It is not padding — it is the smallest set of
|
|
136
|
-
* generation frames whose values determine the app frame that was authored.
|
|
137
|
-
* Measured after the change: 27.1 cm -> 0.3 cm at the pinned frame.
|
|
138
|
-
*
|
|
139
|
-
* The exact-frame case (one app frame in six lands on a generation frame with
|
|
140
|
-
* no remainder) emits a single frame, because there is no second frame in the
|
|
141
|
-
* blend to hold.
|
|
142
|
-
*
|
|
143
|
-
* POSITIONS ARE NOT TOUCHED. Resampling the source is a time operation; a pin
|
|
144
|
-
* is a point in space that happens to name a moment, and the only thing the
|
|
145
|
-
* clock change can do to it is move which frames it names.
|
|
146
|
-
*
|
|
147
|
-
* Two app pins can claim one generation frame (they are 1.2 app frames apart at
|
|
148
|
-
* most when that happens), and two rows on one frame would be two contradictory
|
|
149
|
-
* answers for the sampler to average. The LAST pin wins, deliberately: pins
|
|
150
|
-
* arrive in ascending frame order, so "the later one wins" is the same rule the
|
|
151
|
-
* app's own place-a-pin-twice behaviour follows.
|
|
152
|
-
*/
|
|
153
|
-
export function scalePins(pins, genFrames, { appFps = APP_FPS, genFps = GEN_FPS } = {}) {
|
|
154
|
-
if (!Array.isArray(pins) || pins.length === 0) {
|
|
155
|
-
throw new Error("scalePins: pins3d must be a non-empty array");
|
|
156
|
-
}
|
|
157
|
-
if (!Number.isInteger(genFrames) || genFrames < 1) {
|
|
158
|
-
throw new Error(`scalePins: the generation clip needs at least 1 frame, got ${genFrames}`);
|
|
159
|
-
}
|
|
160
|
-
const byFrame = new Map();
|
|
161
|
-
const clamp = (frame) => Math.min(genFrames - 1, Math.max(0, frame));
|
|
162
|
-
for (const pin of pins) {
|
|
163
|
-
if (!pin || !Number.isInteger(pin.frame) || !Array.isArray(pin.position) || pin.position.length !== 3) {
|
|
164
|
-
throw new Error(`scalePins: ${JSON.stringify(pin)} is not a { frame, position:[x,y,z] } pin`);
|
|
165
|
-
}
|
|
166
|
-
const position = pin.position.map(Number);
|
|
167
|
-
// retimeMotion's own walk, spelled the same way: s = f / (genFps/appFps).
|
|
168
|
-
const exact = (pin.frame * genFps) / appFps;
|
|
169
|
-
const low = clamp(Math.floor(exact));
|
|
170
|
-
const high = clamp(exact === low ? low : low + 1);
|
|
171
|
-
byFrame.set(low, { frame: low, position });
|
|
172
|
-
if (high !== low) byFrame.set(high, { frame: high, position });
|
|
173
|
-
}
|
|
174
|
-
return [...byFrame.values()].sort((a, b) => a.frame - b.frame);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Resample a flat [T, J, 3] position buffer onto `outFrames` rows by linear
|
|
179
|
-
* interpolation.
|
|
180
|
-
*
|
|
181
|
-
* The walk is retimeMotion's own (`s = f / ratio`, floor, clamp, lerp) rather
|
|
182
|
-
* than an arc-length or endpoint-matched resample, so the two directions of the
|
|
183
|
-
* 24 <-> 20 trip agree about where frame f sits in time. The one deliberate
|
|
184
|
-
* difference: `ratio` is the FRAME-COUNT ratio rather than the fps ratio, so a
|
|
185
|
-
* clip whose rounded 20 fps length is a hair off 5/6 of its 24 fps length is
|
|
186
|
-
* still covered end to end instead of stopping short. Positions only: the
|
|
187
|
-
* source npy has no rotations and the driver wants none.
|
|
188
|
-
*/
|
|
189
|
-
export function resamplePositions(positions, { joints, inFrames, outFrames }) {
|
|
190
|
-
if (positions.length !== inFrames * joints * 3) {
|
|
191
|
-
throw new Error(
|
|
192
|
-
`resamplePositions: expected ${inFrames * joints * 3} values for ${inFrames} frames, got ${positions.length}`
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
if (!Number.isInteger(outFrames) || outFrames < 1) {
|
|
196
|
-
throw new Error(`resamplePositions: outFrames must be a positive integer, got ${outFrames}`);
|
|
197
|
-
}
|
|
198
|
-
const out = new Float32Array(outFrames * joints * 3);
|
|
199
|
-
const ratio = outFrames === inFrames ? 1 : outFrames / inFrames;
|
|
200
|
-
for (let frame = 0; frame < outFrames; frame += 1) {
|
|
201
|
-
const s = frame / ratio;
|
|
202
|
-
const i0 = Math.min(Math.floor(s), inFrames - 1);
|
|
203
|
-
const i1 = Math.min(i0 + 1, inFrames - 1);
|
|
204
|
-
const t = Math.min(Math.max(s - i0, 0), 1);
|
|
205
|
-
const dst = frame * joints * 3;
|
|
206
|
-
const src0 = i0 * joints * 3;
|
|
207
|
-
const src1 = i1 * joints * 3;
|
|
208
|
-
for (let index = 0; index < joints * 3; index += 1) {
|
|
209
|
-
out[dst + index] = positions[src0 + index] * (1 - t) + positions[src1 + index] * t;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return out;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/* ------------------------------------------------------------------------- */
|
|
216
|
-
/* the take on disk */
|
|
217
|
-
/* ------------------------------------------------------------------------- */
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Read a cclay take npz into the motion-array shape every tool in this repo
|
|
221
|
-
* passes around.
|
|
222
|
-
*
|
|
223
|
-
* readNpz (tools/kimodo/read-npz.mjs) rather than decodeMotionNpz: the browser
|
|
224
|
-
* decoder is async and byte-cap hardened for untrusted input, which is the
|
|
225
|
-
* wrong trade for a file this process wrote itself, and the bridge already uses
|
|
226
|
-
* both readers for exactly that split.
|
|
227
|
-
*/
|
|
228
|
-
export function readTakeNpz(path) {
|
|
229
|
-
const members = readNpz(path);
|
|
230
|
-
for (const required of ["local_rot_mats", "root_positions", "posed_joints"]) {
|
|
231
|
-
if (!members[required]) throw new Error(`readTakeNpz: ${path} is missing ${required}`);
|
|
232
|
-
}
|
|
233
|
-
const rot = members.local_rot_mats;
|
|
234
|
-
const root = members.root_positions;
|
|
235
|
-
const posed = members.posed_joints;
|
|
236
|
-
if (rot.shape.length !== 4 || rot.shape[1] !== CSKEL_JOINTS || rot.shape[2] !== 3 || rot.shape[3] !== 3) {
|
|
237
|
-
throw new Error(`readTakeNpz: local_rot_mats must be [T,27,3,3], got [${rot.shape}]`);
|
|
238
|
-
}
|
|
239
|
-
if (posed.shape.length !== 3 || posed.shape[1] !== CSKEL_JOINTS || posed.shape[2] !== 3) {
|
|
240
|
-
throw new Error(`readTakeNpz: posed_joints must be [T,27,3], got [${posed.shape}]`);
|
|
241
|
-
}
|
|
242
|
-
if (root.shape.length !== 2 || root.shape[1] !== 3) {
|
|
243
|
-
throw new Error(`readTakeNpz: root_positions must be [T,3], got [${root.shape}]`);
|
|
244
|
-
}
|
|
245
|
-
const frames = rot.shape[0];
|
|
246
|
-
if (posed.shape[0] !== frames || root.shape[0] !== frames) {
|
|
247
|
-
throw new Error(
|
|
248
|
-
`readTakeNpz: members disagree on frame count (${frames}, ${posed.shape[0]}, ${root.shape[0]})`
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
// The bridge writes fps into every take it produces; a take without one is
|
|
252
|
-
// not a cclay take, and guessing 24 would silently retime the edit.
|
|
253
|
-
if (!members.fps) throw new Error(`readTakeNpz: ${path} has no fps member`);
|
|
254
|
-
const fps = Math.round(members.fps.data[0]);
|
|
255
|
-
if (!Number.isInteger(fps) || fps < 1) throw new Error(`readTakeNpz: ${path} has fps ${fps}`);
|
|
256
|
-
const motion = {
|
|
257
|
-
frames,
|
|
258
|
-
fps,
|
|
259
|
-
rotMats: rot.data,
|
|
260
|
-
rootPos: root.data,
|
|
261
|
-
posedJoints: posed.data,
|
|
262
|
-
};
|
|
263
|
-
if (members.person_scale) {
|
|
264
|
-
const personScale = members.person_scale.data[0];
|
|
265
|
-
if (personScale > 0 && personScale !== 1) motion.personScale = personScale;
|
|
266
|
-
}
|
|
267
|
-
return motion;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/* ------------------------------------------------------------------------- */
|
|
271
|
-
/* the splice and its seams */
|
|
272
|
-
/* ------------------------------------------------------------------------- */
|
|
273
|
-
|
|
274
|
-
/** Largest per-joint travel between two consecutive frames of a take, in
|
|
275
|
-
* metres. The unit gate GP2 is written in ("no frame-to-frame pop bigger than
|
|
276
|
-
* the take's own median frame delta"). */
|
|
277
|
-
export function maxJointStep(posedJoints, frame, { joints = CSKEL_JOINTS } = {}) {
|
|
278
|
-
if (frame <= 0) return 0;
|
|
279
|
-
let worst = 0;
|
|
280
|
-
const current = frame * joints * 3;
|
|
281
|
-
const previous = current - joints * 3;
|
|
282
|
-
for (let joint = 0; joint < joints; joint += 1) {
|
|
283
|
-
const step = Math.hypot(
|
|
284
|
-
posedJoints[current + joint * 3] - posedJoints[previous + joint * 3],
|
|
285
|
-
posedJoints[current + joint * 3 + 1] - posedJoints[previous + joint * 3 + 1],
|
|
286
|
-
posedJoints[current + joint * 3 + 2] - posedJoints[previous + joint * 3 + 2]
|
|
287
|
-
);
|
|
288
|
-
if (step > worst) worst = step;
|
|
289
|
-
}
|
|
290
|
-
return worst;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/** The take's own median frame delta — the yardstick a seam is judged against.
|
|
294
|
-
* Median, not mean: one real footfall must not raise the bar for the whole
|
|
295
|
-
* clip. */
|
|
296
|
-
export function medianFrameStep(motion, { joints = CSKEL_JOINTS } = {}) {
|
|
297
|
-
if (motion.frames < 2) return 0;
|
|
298
|
-
const steps = [];
|
|
299
|
-
for (let frame = 1; frame < motion.frames; frame += 1) {
|
|
300
|
-
steps.push(maxJointStep(motion.posedJoints, frame, { joints }));
|
|
301
|
-
}
|
|
302
|
-
steps.sort((a, b) => a - b);
|
|
303
|
-
const middle = steps.length >> 1;
|
|
304
|
-
return steps.length % 2 ? steps[middle] : (steps[middle - 1] + steps[middle]) / 2;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Write `edited` into `take` over the half-open app-frame range, and measure
|
|
309
|
-
* what the two cuts cost.
|
|
310
|
-
*
|
|
311
|
-
* `edited` may be one frame short or long of the take (the 24 -> 20 -> 24 round
|
|
312
|
-
* trip is not a bijection: 117 app frames scale to 98 generation frames, which
|
|
313
|
-
* scale back to 118). The tail index is clamped rather than the range refused —
|
|
314
|
-
* the discrepancy is at most one frame at the very end of the clip and only
|
|
315
|
-
* matters when the edit reaches it, whereas refusing would make whole clip
|
|
316
|
-
* lengths un-editable. `framesClamped` in the returned meta says when it
|
|
317
|
-
* happened, so the case is visible rather than silent.
|
|
318
|
-
*/
|
|
319
|
-
export function spliceEditedRange(take, edited, { startFrame, endFrame }) {
|
|
320
|
-
if (!Number.isInteger(startFrame) || !Number.isInteger(endFrame) || startFrame < 0 || endFrame <= startFrame) {
|
|
321
|
-
throw new Error(`spliceEditedRange: ${startFrame}..${endFrame} is not a non-empty half-open range`);
|
|
322
|
-
}
|
|
323
|
-
if (endFrame > take.frames) {
|
|
324
|
-
throw new Error(`spliceEditedRange: range ends at ${endFrame}, past the take's ${take.frames} frames`);
|
|
325
|
-
}
|
|
326
|
-
const span = endFrame - startFrame;
|
|
327
|
-
const slice = {
|
|
328
|
-
frames: span,
|
|
329
|
-
fps: take.fps,
|
|
330
|
-
rotMats: new Float32Array(span * CSKEL_JOINTS * 9),
|
|
331
|
-
rootPos: new Float32Array(span * 3),
|
|
332
|
-
posedJoints: new Float32Array(span * CSKEL_JOINTS * 3),
|
|
333
|
-
};
|
|
334
|
-
let framesClamped = 0;
|
|
335
|
-
for (let index = 0; index < span; index += 1) {
|
|
336
|
-
const wanted = startFrame + index;
|
|
337
|
-
const source = Math.min(wanted, edited.frames - 1);
|
|
338
|
-
if (source !== wanted) framesClamped += 1;
|
|
339
|
-
slice.rotMats.set(
|
|
340
|
-
edited.rotMats.subarray(source * CSKEL_JOINTS * 9, (source + 1) * CSKEL_JOINTS * 9),
|
|
341
|
-
index * CSKEL_JOINTS * 9
|
|
342
|
-
);
|
|
343
|
-
slice.rootPos.set(edited.rootPos.subarray(source * 3, (source + 1) * 3), index * 3);
|
|
344
|
-
slice.posedJoints.set(
|
|
345
|
-
edited.posedJoints.subarray(source * CSKEL_JOINTS * 3, (source + 1) * CSKEL_JOINTS * 3),
|
|
346
|
-
index * CSKEL_JOINTS * 3
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
// replaceMotionSegment copies the base arrays and overwrites the span, so
|
|
350
|
-
// every frame outside it is the source take's own bytes, unconditionally.
|
|
351
|
-
const spliced = replaceMotionSegment(take, slice, startFrame);
|
|
352
|
-
const median = medianFrameStep(take);
|
|
353
|
-
const seams = {
|
|
354
|
-
// The frame AT the range start is the first edited pose, so the jump into
|
|
355
|
-
// it is the opening seam; endFrame is the first frame that is source
|
|
356
|
-
// again, so the jump into IT is the closing seam. A range that starts at
|
|
357
|
-
// frame 0 or ends at the clip has only one real seam; the other is 0.
|
|
358
|
-
seamStartDelta: maxJointStep(spliced.posedJoints, startFrame),
|
|
359
|
-
seamEndDelta: endFrame < spliced.frames ? maxJointStep(spliced.posedJoints, endFrame) : 0,
|
|
360
|
-
medianFrameDelta: median,
|
|
361
|
-
};
|
|
362
|
-
seams.seamStartRatio = median > 0 ? seams.seamStartDelta / median : null;
|
|
363
|
-
seams.seamEndRatio = median > 0 ? seams.seamEndDelta / median : null;
|
|
364
|
-
return { motion: spliced, framesClamped, ...seams };
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/* ------------------------------------------------------------------------- */
|
|
368
|
-
/* the job */
|
|
369
|
-
/* ------------------------------------------------------------------------- */
|
|
370
|
-
|
|
371
|
-
/** C6's flat 9-element row-major R -> the 3x3 buildLineRequest validates. The
|
|
372
|
-
* app sends flat (it is what a Matrix3 hands over); driver.py reshapes to (3,3)
|
|
373
|
-
* either way, but generate.mjs refuses anything that is not nested, and this is
|
|
374
|
-
* the seam where the two spellings meet. */
|
|
375
|
-
function nestR(R) {
|
|
376
|
-
if (Array.isArray(R) && R.length === 3 && R.every((row) => Array.isArray(row) && row.length === 3)) return R;
|
|
377
|
-
if (!Array.isArray(R) || R.length !== 9) {
|
|
378
|
-
throw new Error("lineEdit.camera.R must be 9 numbers (row-major) or a 3x3 array");
|
|
379
|
-
}
|
|
380
|
-
return [R.slice(0, 3), R.slice(3, 6), R.slice(6, 9)];
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Run one line edit end to end and write the edited take.
|
|
385
|
-
*
|
|
386
|
-
* @param {object} options
|
|
387
|
-
* @param {object} options.lineEdit the C6 block exactly as the app sent it —
|
|
388
|
-
* app-clock `frameRange {startFrame, endFrame}` (half-open), normalised
|
|
389
|
-
* points2d, flat-R camera. Nothing in it is on the generation clock.
|
|
390
|
-
* @param {string} options.takePath the stored cclay npz for `sourceMotion`.
|
|
391
|
-
* @param {string} options.outputPath where the edited cclay npz goes.
|
|
392
|
-
* @param {boolean} [options.preview] 20 ODE steps instead of 100.
|
|
393
|
-
* @param {number} [options.seed]
|
|
394
|
-
* @param {number} [options.appFps=24]
|
|
395
|
-
* @param {(line: string) => void} [options.onStatus] box output, line by line.
|
|
396
|
-
* @param {Function} [options.runLineEdit=lineEditOnBox] the box call. Injected
|
|
397
|
-
* so the composition can be tested without a GPU: everything around it is
|
|
398
|
-
* arithmetic on real npz files, and that arithmetic is where the bugs are.
|
|
399
|
-
* @returns {Promise<object>} meta: clocks, frame counts, seam deltas, timings,
|
|
400
|
-
* and the driver's own exactness numbers under `box`.
|
|
401
|
-
*/
|
|
402
|
-
export async function runLineEditJob({
|
|
403
|
-
lineEdit,
|
|
404
|
-
takePath,
|
|
405
|
-
outputPath,
|
|
406
|
-
preview = false,
|
|
407
|
-
seed,
|
|
408
|
-
steps,
|
|
409
|
-
appFps = APP_FPS,
|
|
410
|
-
onStatus,
|
|
411
|
-
runLineEdit = lineEditOnBox,
|
|
412
|
-
} = {}) {
|
|
413
|
-
if (!lineEdit || typeof lineEdit !== "object") throw new Error("runLineEditJob: lineEdit is required");
|
|
414
|
-
if (!takePath) throw new Error("runLineEditJob: takePath is required");
|
|
415
|
-
if (!outputPath) throw new Error("runLineEditJob: outputPath is required");
|
|
416
|
-
const started = Date.now();
|
|
417
|
-
const timings = {};
|
|
418
|
-
|
|
419
|
-
// --- 1. the take ---------------------------------------------------------
|
|
420
|
-
const take = readTakeNpz(takePath);
|
|
421
|
-
if (take.fps !== appFps) {
|
|
422
|
-
throw new Error(`runLineEditJob: the take is ${take.fps} fps; this bridge's clock is ${appFps}`);
|
|
423
|
-
}
|
|
424
|
-
const range = lineEdit.frameRange || {};
|
|
425
|
-
const startFrame = range.startFrame;
|
|
426
|
-
const endFrame = range.endFrame;
|
|
427
|
-
if (!Number.isInteger(startFrame) || !Number.isInteger(endFrame) || startFrame < 0 || endFrame <= startFrame) {
|
|
428
|
-
throw new Error(`runLineEditJob: frameRange ${startFrame}..${endFrame} is not a non-empty half-open range`);
|
|
429
|
-
}
|
|
430
|
-
// Re-checked against the FILE, not against the duration the request claimed:
|
|
431
|
-
// the bridge validated the range against `duration * 24`, and a take that was
|
|
432
|
-
// spliced or imported can disagree with it.
|
|
433
|
-
if (endFrame > take.frames) {
|
|
434
|
-
throw new Error(
|
|
435
|
-
`runLineEditJob: frameRange ends at ${endFrame} but the source take has ${take.frames} frames`
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
timings.readMs = Date.now() - started;
|
|
439
|
-
|
|
440
|
-
// --- 2. 24 -> 20, positions only ----------------------------------------
|
|
441
|
-
const genFrames = Math.max(2, toGenFrames(take.frames, { appFps, genFps: GEN_FPS }));
|
|
442
|
-
const sourceHml22 = resamplePositions(
|
|
443
|
-
cskel27ToHml22Positions({ frames: take.frames, posedJoints: take.posedJoints }),
|
|
444
|
-
{ joints: NUM_JOINTS, inFrames: take.frames, outFrames: genFrames }
|
|
445
|
-
);
|
|
446
|
-
|
|
447
|
-
// --- 3. the range on the generation clock -------------------------------
|
|
448
|
-
const genRange = scaleFrameRange({ startFrame, endFrame }, genFrames, { appFps, genFps: GEN_FPS });
|
|
449
|
-
const line = lineEdit.pins3d
|
|
450
|
-
? {
|
|
451
|
-
track: lineEdit.track,
|
|
452
|
-
frameRange: genRange,
|
|
453
|
-
// NO CAMERA, and none is invented: a pin is already in the take's own
|
|
454
|
-
// joint space (the app converts from viewport world with
|
|
455
|
-
// worldPointToClip, the exact inverse of the trail projection), so the
|
|
456
|
-
// positions go through UNCHANGED. cskel27ToHml22Positions is a pure
|
|
457
|
-
// index gather with no coordinate transform — that is the whole reason
|
|
458
|
-
// the wire can carry take-space metres and the driver can compare them
|
|
459
|
-
// against source.npy directly.
|
|
460
|
-
pins3d: scalePins(lineEdit.pins3d, genFrames, { appFps, genFps: GEN_FPS }),
|
|
461
|
-
prompt: lineEdit.prompt ?? "",
|
|
462
|
-
}
|
|
463
|
-
: {
|
|
464
|
-
track: lineEdit.track,
|
|
465
|
-
frameRange: genRange,
|
|
466
|
-
points2d: lineEdit.points2d,
|
|
467
|
-
camera: { ...lineEdit.camera, R: nestR(lineEdit.camera?.R) },
|
|
468
|
-
prompt: lineEdit.prompt ?? "",
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
// --- 4. the box ----------------------------------------------------------
|
|
472
|
-
const workDir = await mkdtemp(join(tmpdir(), "cclay-line-edit-"));
|
|
473
|
-
let result;
|
|
474
|
-
try {
|
|
475
|
-
const sourceNpy = join(workDir, "source.npy");
|
|
476
|
-
writeNpyFloat32(sourceNpy, sourceHml22, [genFrames, NUM_JOINTS, 3]);
|
|
477
|
-
const boxStarted = Date.now();
|
|
478
|
-
result = await runLineEdit({
|
|
479
|
-
sourceMotionNpy: sourceNpy,
|
|
480
|
-
line,
|
|
481
|
-
preview,
|
|
482
|
-
...(steps === undefined ? {} : { steps }),
|
|
483
|
-
...(seed === undefined ? {} : { seed }),
|
|
484
|
-
// The raw hml22 result is kept beside the take for the same reason
|
|
485
|
-
// Kimodo keeps its native npz: it is what a LATER edit would source
|
|
486
|
-
// from, and the cskel27 file we write below is a lift of it.
|
|
487
|
-
nativeOut: nativeMotionPath(outputPath),
|
|
488
|
-
onLine: onStatus,
|
|
489
|
-
});
|
|
490
|
-
timings.boxMs = Date.now() - boxStarted;
|
|
491
|
-
} finally {
|
|
492
|
-
await rm(workDir, { recursive: true, force: true });
|
|
493
|
-
}
|
|
494
|
-
if (!result || !result.positions) throw new Error("runLineEditJob: the box returned no positions");
|
|
495
|
-
if (result.frames !== genFrames) {
|
|
496
|
-
// driver.py edits IN PLACE on the source's own T; a different count means
|
|
497
|
-
// the source npy and the result describe different clips and the splice
|
|
498
|
-
// below would be aligned to the wrong frames.
|
|
499
|
-
throw new Error(
|
|
500
|
-
`runLineEditJob: the box returned ${result.frames} frames for a ${genFrames}-frame source`
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// --- 5. 22 -> 27, then 20 -> 24 -----------------------------------------
|
|
505
|
-
const convertStarted = Date.now();
|
|
506
|
-
const converted = hml22ToCskel27Motion({ positions: result.positions, fps: result.fps ?? GEN_FPS });
|
|
507
|
-
// The same call Kimodo's 30 fps output makes on its way to the timeline;
|
|
508
|
-
// rotations slerp, the root lerps, and every other joint is re-derived by FK
|
|
509
|
-
// so bone lengths never enter the blend.
|
|
510
|
-
const retimed = retimeMotion({ ...converted, fps: converted.fps }, appFps);
|
|
511
|
-
timings.convertMs = Date.now() - convertStarted;
|
|
512
|
-
|
|
513
|
-
// --- 6. splice and write -------------------------------------------------
|
|
514
|
-
const spliced = spliceEditedRange(take, retimed, { startFrame, endFrame });
|
|
515
|
-
writeNpz(outputPath, motionArraysToNpzMembers({
|
|
516
|
-
frames: spliced.motion.frames,
|
|
517
|
-
fps: spliced.motion.fps,
|
|
518
|
-
rotMats: spliced.motion.rotMats,
|
|
519
|
-
rootPos: spliced.motion.rootPos,
|
|
520
|
-
posedJoints: spliced.motion.posedJoints,
|
|
521
|
-
personScale: spliced.motion.personScale,
|
|
522
|
-
}));
|
|
523
|
-
timings.totalMs = Date.now() - started;
|
|
524
|
-
|
|
525
|
-
return {
|
|
526
|
-
output: outputPath,
|
|
527
|
-
nativeMotion: result.nativeNpy ?? null,
|
|
528
|
-
track: lineEdit.track,
|
|
529
|
-
frames: spliced.motion.frames,
|
|
530
|
-
fps: spliced.motion.fps,
|
|
531
|
-
appFrameRange: { startFrame, endFrame },
|
|
532
|
-
genFps: GEN_FPS,
|
|
533
|
-
genFrames,
|
|
534
|
-
// Reported INCLUSIVE, the way driver.py received it, so a box log and this
|
|
535
|
-
// meta can be compared without translating.
|
|
536
|
-
genFrameRange: genRange,
|
|
537
|
-
retimedFrames: retimed.frames,
|
|
538
|
-
framesClamped: spliced.framesClamped,
|
|
539
|
-
seamStartDelta: spliced.seamStartDelta,
|
|
540
|
-
seamEndDelta: spliced.seamEndDelta,
|
|
541
|
-
medianFrameDelta: spliced.medianFrameDelta,
|
|
542
|
-
seamStartRatio: spliced.seamStartRatio,
|
|
543
|
-
seamEndRatio: spliced.seamEndRatio,
|
|
544
|
-
timings,
|
|
545
|
-
box: result.meta ?? null,
|
|
546
|
-
};
|
|
547
|
-
}
|