@dreamerbird/camera-studio 0.0.0-stage → 0.1.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/assets/models/Y Bot.glb +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-base-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-base-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-point-relaxed-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-point-relaxed-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-point-straight-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-point-straight-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-01-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-01-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-02-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-02-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-03-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-03-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-04-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-04-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-05-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-05-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-06-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-01-base-relaxed-06-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-bowl-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-bowl-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-card-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-card-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-claw-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-claw-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-fist-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-fist-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-grip-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-grip-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-ko-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-ko-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-nail-check-01-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-nail-check-01-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-nail-check-02-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-nail-check-02-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-offer-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-offer-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-push-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-push-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-push-the-button-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-push-the-button-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-scratch-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-scratch-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-spread-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-spread-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-write-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-02-other-write-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-cobblepot-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-cobblepot-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-fingers-crossed-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-fingers-crossed-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-five-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-five-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-four-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-four-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-four-ring-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-four-ring-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-fu-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-fu-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-index-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-index-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-martian-reactor-switch-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-martian-reactor-switch-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-one-does-not-simply-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-one-does-not-simply-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-peace-flipped-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-peace-flipped.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-peace-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-peace-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-perfect-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-perfect-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-pinky-swear-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-pinky-swear-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-rock-out-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-rock-out-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-scout-salute-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-scout-salute-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-spock-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-spock-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-stop-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-stop-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-surf-s-up-flipped-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-surf-s-up-flipped-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-surf-s-up-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-surf-s-up-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-three-hicox-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-three-hicox-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-three-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-three-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-three-ring-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-three-ring-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-thumbs-up-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-03-signs-thumbs-up-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-detonator-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-detonator-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-grip-sword-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-grip-sword-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-gun-01-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-gun-01-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-gun-02-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-gun-02-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-gun-03-l-cock-the-hammer.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-gun-03-r-cock-the-hammer.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-hadoken-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-hadoken-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-pistol-double-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-pistol-double-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-pistol-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-pistol-r.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-throw-l.png +0 -0
- package/assets/poseThumbs/qbx-hands-04-weapons-throw-r.png +0 -0
- package/dist/Anchors.d.ts +11 -0
- package/dist/Anchors.d.ts.map +1 -0
- package/dist/Anchors.js +315 -0
- package/dist/App.hostFlows.test.d.ts +2 -0
- package/dist/App.hostFlows.test.d.ts.map +1 -0
- package/dist/App.hostFlows.test.js +48 -0
- package/dist/CameraPath.d.ts +130 -0
- package/dist/CameraPath.d.ts.map +1 -0
- package/dist/CameraPath.js +920 -0
- package/dist/CameraScene.d.ts +45 -0
- package/dist/CameraScene.d.ts.map +1 -0
- package/dist/CameraScene.js +1716 -0
- package/dist/CameraScene.test.d.ts +2 -0
- package/dist/CameraScene.test.d.ts.map +1 -0
- package/dist/CameraScene.test.js +259 -0
- package/dist/PathPresetDropRig.d.ts +27 -0
- package/dist/PathPresetDropRig.d.ts.map +1 -0
- package/dist/PathPresetDropRig.js +192 -0
- package/dist/PathPresetThumb.d.ts +11 -0
- package/dist/PathPresetThumb.d.ts.map +1 -0
- package/dist/PathPresetThumb.js +85 -0
- package/dist/PipCameraSelector.d.ts +15 -0
- package/dist/PipCameraSelector.d.ts.map +1 -0
- package/dist/PipCameraSelector.js +92 -0
- package/dist/PlacementManager.d.ts +252 -0
- package/dist/PlacementManager.d.ts.map +1 -0
- package/dist/PlacementManager.js +1525 -0
- package/dist/ShotCamera.d.ts +21 -0
- package/dist/ShotCamera.d.ts.map +1 -0
- package/dist/ShotCamera.js +568 -0
- package/dist/TopToolbar.d.ts +43 -0
- package/dist/TopToolbar.d.ts.map +1 -0
- package/dist/TopToolbar.js +167 -0
- package/dist/TopToolbar.test.d.ts +2 -0
- package/dist/TopToolbar.test.d.ts.map +1 -0
- package/dist/TopToolbar.test.js +107 -0
- package/dist/agent/api.d.ts +95 -0
- package/dist/agent/api.d.ts.map +1 -0
- package/dist/agent/api.js +337 -0
- package/dist/agent/api.test.d.ts +2 -0
- package/dist/agent/api.test.d.ts.map +1 -0
- package/dist/agent/api.test.js +907 -0
- package/dist/agent/bridgeClient.d.ts +62 -0
- package/dist/agent/bridgeClient.d.ts.map +1 -0
- package/dist/agent/bridgeClient.js +277 -0
- package/dist/agent/bridgeClient.test.d.ts +2 -0
- package/dist/agent/bridgeClient.test.d.ts.map +1 -0
- package/dist/agent/bridgeClient.test.js +413 -0
- package/dist/agent/bridgeFiles.d.ts +33 -0
- package/dist/agent/bridgeFiles.d.ts.map +1 -0
- package/dist/agent/bridgeFiles.js +193 -0
- package/dist/agent/bridgeFiles.test.d.ts +2 -0
- package/dist/agent/bridgeFiles.test.d.ts.map +1 -0
- package/dist/agent/bridgeFiles.test.js +220 -0
- package/dist/agent/commands/anchors.d.ts +43 -0
- package/dist/agent/commands/anchors.d.ts.map +1 -0
- package/dist/agent/commands/anchors.js +331 -0
- package/dist/agent/commands/anchors.test.d.ts +2 -0
- package/dist/agent/commands/anchors.test.d.ts.map +1 -0
- package/dist/agent/commands/anchors.test.js +447 -0
- package/dist/agent/commands/animations.d.ts +47 -0
- package/dist/agent/commands/animations.d.ts.map +1 -0
- package/dist/agent/commands/animations.js +245 -0
- package/dist/agent/commands/animations.test.d.ts +2 -0
- package/dist/agent/commands/animations.test.d.ts.map +1 -0
- package/dist/agent/commands/animations.test.js +426 -0
- package/dist/agent/commands/assets.d.ts +34 -0
- package/dist/agent/commands/assets.d.ts.map +1 -0
- package/dist/agent/commands/assets.js +367 -0
- package/dist/agent/commands/assets.test.d.ts +2 -0
- package/dist/agent/commands/assets.test.d.ts.map +1 -0
- package/dist/agent/commands/assets.test.js +405 -0
- package/dist/agent/commands/bindings.realStudio.test.d.ts +21 -0
- package/dist/agent/commands/bindings.realStudio.test.d.ts.map +1 -0
- package/dist/agent/commands/bindings.realStudio.test.js +281 -0
- package/dist/agent/commands/cameras.d.ts +65 -0
- package/dist/agent/commands/cameras.d.ts.map +1 -0
- package/dist/agent/commands/cameras.js +498 -0
- package/dist/agent/commands/cameras.test.d.ts +2 -0
- package/dist/agent/commands/cameras.test.d.ts.map +1 -0
- package/dist/agent/commands/cameras.test.js +560 -0
- package/dist/agent/commands/delete.test.d.ts +2 -0
- package/dist/agent/commands/delete.test.d.ts.map +1 -0
- package/dist/agent/commands/delete.test.js +518 -0
- package/dist/agent/commands/exports.d.ts +12 -0
- package/dist/agent/commands/exports.d.ts.map +1 -0
- package/dist/agent/commands/exports.js +132 -0
- package/dist/agent/commands/exports.test.d.ts +2 -0
- package/dist/agent/commands/exports.test.d.ts.map +1 -0
- package/dist/agent/commands/exports.test.js +402 -0
- package/dist/agent/commands/history.d.ts +17 -0
- package/dist/agent/commands/history.d.ts.map +1 -0
- package/dist/agent/commands/history.js +96 -0
- package/dist/agent/commands/history.test.d.ts +2 -0
- package/dist/agent/commands/history.test.d.ts.map +1 -0
- package/dist/agent/commands/history.test.js +140 -0
- package/dist/agent/commands/jobs.d.ts +23 -0
- package/dist/agent/commands/jobs.d.ts.map +1 -0
- package/dist/agent/commands/jobs.js +66 -0
- package/dist/agent/commands/keyframes.d.ts +42 -0
- package/dist/agent/commands/keyframes.d.ts.map +1 -0
- package/dist/agent/commands/keyframes.js +310 -0
- package/dist/agent/commands/keyframes.realStudio.test.d.ts +11 -0
- package/dist/agent/commands/keyframes.realStudio.test.d.ts.map +1 -0
- package/dist/agent/commands/keyframes.realStudio.test.js +95 -0
- package/dist/agent/commands/keyframes.test.d.ts +2 -0
- package/dist/agent/commands/keyframes.test.d.ts.map +1 -0
- package/dist/agent/commands/keyframes.test.js +474 -0
- package/dist/agent/commands/models.d.ts +34 -0
- package/dist/agent/commands/models.d.ts.map +1 -0
- package/dist/agent/commands/models.js +137 -0
- package/dist/agent/commands/models.test.d.ts +2 -0
- package/dist/agent/commands/models.test.d.ts.map +1 -0
- package/dist/agent/commands/models.test.js +268 -0
- package/dist/agent/commands/objects.d.ts +4 -0
- package/dist/agent/commands/objects.d.ts.map +1 -0
- package/dist/agent/commands/objects.js +182 -0
- package/dist/agent/commands/objects.test.d.ts +2 -0
- package/dist/agent/commands/objects.test.d.ts.map +1 -0
- package/dist/agent/commands/objects.test.js +316 -0
- package/dist/agent/commands/paths.d.ts +111 -0
- package/dist/agent/commands/paths.d.ts.map +1 -0
- package/dist/agent/commands/paths.js +576 -0
- package/dist/agent/commands/paths.test.d.ts +2 -0
- package/dist/agent/commands/paths.test.d.ts.map +1 -0
- package/dist/agent/commands/paths.test.js +773 -0
- package/dist/agent/commands/poses.d.ts +101 -0
- package/dist/agent/commands/poses.d.ts.map +1 -0
- package/dist/agent/commands/poses.js +500 -0
- package/dist/agent/commands/poses.realStudio.test.d.ts +15 -0
- package/dist/agent/commands/poses.realStudio.test.d.ts.map +1 -0
- package/dist/agent/commands/poses.realStudio.test.js +161 -0
- package/dist/agent/commands/poses.test.d.ts +2 -0
- package/dist/agent/commands/poses.test.d.ts.map +1 -0
- package/dist/agent/commands/poses.test.js +735 -0
- package/dist/agent/commands/project.d.ts +12 -0
- package/dist/agent/commands/project.d.ts.map +1 -0
- package/dist/agent/commands/project.js +42 -0
- package/dist/agent/commands/project.test.d.ts +2 -0
- package/dist/agent/commands/project.test.d.ts.map +1 -0
- package/dist/agent/commands/project.test.js +125 -0
- package/dist/agent/commands/revision.realStudio.test.d.ts +14 -0
- package/dist/agent/commands/revision.realStudio.test.d.ts.map +1 -0
- package/dist/agent/commands/revision.realStudio.test.js +129 -0
- package/dist/agent/commands/scene.d.ts +32 -0
- package/dist/agent/commands/scene.d.ts.map +1 -0
- package/dist/agent/commands/scene.js +100 -0
- package/dist/agent/commands/scene.test.d.ts +2 -0
- package/dist/agent/commands/scene.test.d.ts.map +1 -0
- package/dist/agent/commands/scene.test.js +147 -0
- package/dist/agent/commands/selection.d.ts +21 -0
- package/dist/agent/commands/selection.d.ts.map +1 -0
- package/dist/agent/commands/selection.js +101 -0
- package/dist/agent/commands/selection.test.d.ts +2 -0
- package/dist/agent/commands/selection.test.d.ts.map +1 -0
- package/dist/agent/commands/selection.test.js +173 -0
- package/dist/agent/commands/sequence.d.ts +25 -0
- package/dist/agent/commands/sequence.d.ts.map +1 -0
- package/dist/agent/commands/sequence.js +174 -0
- package/dist/agent/commands/sequence.test.d.ts +2 -0
- package/dist/agent/commands/sequence.test.d.ts.map +1 -0
- package/dist/agent/commands/sequence.test.js +264 -0
- package/dist/agent/commands/ui.d.ts +40 -0
- package/dist/agent/commands/ui.d.ts.map +1 -0
- package/dist/agent/commands/ui.js +166 -0
- package/dist/agent/commands/view.d.ts +61 -0
- package/dist/agent/commands/view.d.ts.map +1 -0
- package/dist/agent/commands/view.js +229 -0
- package/dist/agent/commands/view.test.d.ts +2 -0
- package/dist/agent/commands/view.test.d.ts.map +1 -0
- package/dist/agent/commands/view.test.js +347 -0
- package/dist/agent/contractCoverage.test.d.ts +2 -0
- package/dist/agent/contractCoverage.test.d.ts.map +1 -0
- package/dist/agent/contractCoverage.test.js +87 -0
- package/dist/agent/deleteHandlers.d.ts +61 -0
- package/dist/agent/deleteHandlers.d.ts.map +1 -0
- package/dist/agent/deleteHandlers.js +41 -0
- package/dist/agent/errors.d.ts +24 -0
- package/dist/agent/errors.d.ts.map +1 -0
- package/dist/agent/errors.js +59 -0
- package/dist/agent/errors.test.d.ts +2 -0
- package/dist/agent/errors.test.d.ts.map +1 -0
- package/dist/agent/errors.test.js +25 -0
- package/dist/agent/expectedCommands.d.ts +9 -0
- package/dist/agent/expectedCommands.d.ts.map +1 -0
- package/dist/agent/expectedCommands.js +79 -0
- package/dist/agent/jobs.d.ts +52 -0
- package/dist/agent/jobs.d.ts.map +1 -0
- package/dist/agent/jobs.js +175 -0
- package/dist/agent/jobs.test.d.ts +2 -0
- package/dist/agent/jobs.test.d.ts.map +1 -0
- package/dist/agent/jobs.test.js +281 -0
- package/dist/agent/keyframeTracks.d.ts +57 -0
- package/dist/agent/keyframeTracks.d.ts.map +1 -0
- package/dist/agent/keyframeTracks.js +127 -0
- package/dist/agent/objectIds.d.ts +7 -0
- package/dist/agent/objectIds.d.ts.map +1 -0
- package/dist/agent/objectIds.js +20 -0
- package/dist/agent/objectIds.test.d.ts +2 -0
- package/dist/agent/objectIds.test.d.ts.map +1 -0
- package/dist/agent/objectIds.test.js +10 -0
- package/dist/agent/objectResolver.d.ts +34 -0
- package/dist/agent/objectResolver.d.ts.map +1 -0
- package/dist/agent/objectResolver.js +112 -0
- package/dist/agent/pathCommands.d.ts +78 -0
- package/dist/agent/pathCommands.d.ts.map +1 -0
- package/dist/agent/pathCommands.js +240 -0
- package/dist/agent/propAccess.d.ts +42 -0
- package/dist/agent/propAccess.d.ts.map +1 -0
- package/dist/agent/propAccess.js +181 -0
- package/dist/agent/registerCommands.d.ts +9 -0
- package/dist/agent/registerCommands.d.ts.map +1 -0
- package/dist/agent/registerCommands.js +77 -0
- package/dist/agent/registry.d.ts +33 -0
- package/dist/agent/registry.d.ts.map +1 -0
- package/dist/agent/registry.js +55 -0
- package/dist/agent/registry.test.d.ts +2 -0
- package/dist/agent/registry.test.d.ts.map +1 -0
- package/dist/agent/registry.test.js +106 -0
- package/dist/agent/runtime/animationStatus.d.ts +25 -0
- package/dist/agent/runtime/animationStatus.d.ts.map +1 -0
- package/dist/agent/runtime/animationStatus.js +13 -0
- package/dist/agent/runtime/cameraLifecycle.d.ts +37 -0
- package/dist/agent/runtime/cameraLifecycle.d.ts.map +1 -0
- package/dist/agent/runtime/cameraLifecycle.js +112 -0
- package/dist/agent/runtime/directorView.d.ts +42 -0
- package/dist/agent/runtime/directorView.d.ts.map +1 -0
- package/dist/agent/runtime/directorView.js +27 -0
- package/dist/agent/runtime/exportRuntime.d.ts +95 -0
- package/dist/agent/runtime/exportRuntime.d.ts.map +1 -0
- package/dist/agent/runtime/exportRuntime.js +227 -0
- package/dist/agent/runtime/objectRuntime.d.ts +26 -0
- package/dist/agent/runtime/objectRuntime.d.ts.map +1 -0
- package/dist/agent/runtime/objectRuntime.js +29 -0
- package/dist/agent/runtime/pathRuntime.d.ts +21 -0
- package/dist/agent/runtime/pathRuntime.d.ts.map +1 -0
- package/dist/agent/runtime/pathRuntime.js +13 -0
- package/dist/agent/runtime/placementLifecycle.d.ts +34 -0
- package/dist/agent/runtime/placementLifecycle.d.ts.map +1 -0
- package/dist/agent/runtime/placementLifecycle.js +63 -0
- package/dist/agent/runtime/placementLifecycle.test.d.ts +2 -0
- package/dist/agent/runtime/placementLifecycle.test.d.ts.map +1 -0
- package/dist/agent/runtime/placementLifecycle.test.js +68 -0
- package/dist/agent/runtime/poseSessionRuntime.d.ts +17 -0
- package/dist/agent/runtime/poseSessionRuntime.d.ts.map +1 -0
- package/dist/agent/runtime/poseSessionRuntime.js +64 -0
- package/dist/agent/runtime.d.ts +67 -0
- package/dist/agent/runtime.d.ts.map +1 -0
- package/dist/agent/runtime.js +214 -0
- package/dist/agent/runtime.test.d.ts +2 -0
- package/dist/agent/runtime.test.d.ts.map +1 -0
- package/dist/agent/runtime.test.js +142 -0
- package/dist/agent/sequenceLength.d.ts +20 -0
- package/dist/agent/sequenceLength.d.ts.map +1 -0
- package/dist/agent/sequenceLength.js +18 -0
- package/dist/agent/types.d.ts +70 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +6 -0
- package/dist/agent/validation.d.ts +7 -0
- package/dist/agent/validation.d.ts.map +1 -0
- package/dist/agent/validation.js +39 -0
- package/dist/aimTargets.d.ts +128 -0
- package/dist/aimTargets.d.ts.map +1 -0
- package/dist/aimTargets.js +367 -0
- package/dist/aimTargets.test.d.ts +2 -0
- package/dist/aimTargets.test.d.ts.map +1 -0
- package/dist/aimTargets.test.js +84 -0
- package/dist/anchorParenting.test.d.ts +2 -0
- package/dist/anchorParenting.test.d.ts.map +1 -0
- package/dist/anchorParenting.test.js +87 -0
- package/dist/anchorsRegistry.d.ts +45 -0
- package/dist/anchorsRegistry.d.ts.map +1 -0
- package/dist/anchorsRegistry.js +101 -0
- package/dist/anchorsRegistry.test.d.ts +2 -0
- package/dist/anchorsRegistry.test.d.ts.map +1 -0
- package/dist/anchorsRegistry.test.js +29 -0
- package/dist/assetLibrary/AssetLibraryPanel.d.ts +35 -0
- package/dist/assetLibrary/AssetLibraryPanel.d.ts.map +1 -0
- package/dist/assetLibrary/AssetLibraryPanel.js +544 -0
- package/dist/assetLibrary/AssetLibraryPanel.viewer.test.d.ts +2 -0
- package/dist/assetLibrary/AssetLibraryPanel.viewer.test.d.ts.map +1 -0
- package/dist/assetLibrary/AssetLibraryPanel.viewer.test.js +133 -0
- package/dist/assetLibrary/ModelPickerSlot.d.ts +24 -0
- package/dist/assetLibrary/ModelPickerSlot.d.ts.map +1 -0
- package/dist/assetLibrary/ModelPickerSlot.js +32 -0
- package/dist/assetLibrary/catalogThumbs.d.ts +7 -0
- package/dist/assetLibrary/catalogThumbs.d.ts.map +1 -0
- package/dist/assetLibrary/catalogThumbs.js +75 -0
- package/dist/assetLibrary/classify.d.ts +14 -0
- package/dist/assetLibrary/classify.d.ts.map +1 -0
- package/dist/assetLibrary/classify.js +43 -0
- package/dist/assetLibrary/classify.test.d.ts +2 -0
- package/dist/assetLibrary/classify.test.d.ts.map +1 -0
- package/dist/assetLibrary/classify.test.js +44 -0
- package/dist/assetLibrary/contract.indexeddb.test.d.ts +2 -0
- package/dist/assetLibrary/contract.indexeddb.test.d.ts.map +1 -0
- package/dist/assetLibrary/contract.indexeddb.test.js +10 -0
- package/dist/assetLibrary/contractTests.d.ts +12 -0
- package/dist/assetLibrary/contractTests.d.ts.map +1 -0
- package/dist/assetLibrary/contractTests.js +51 -0
- package/dist/assetLibrary/dispose.d.ts +4 -0
- package/dist/assetLibrary/dispose.d.ts.map +1 -0
- package/dist/assetLibrary/dispose.js +21 -0
- package/dist/assetLibrary/draco.d.ts +6 -0
- package/dist/assetLibrary/draco.d.ts.map +1 -0
- package/dist/assetLibrary/draco.js +45 -0
- package/dist/assetLibrary/draco.test.d.ts +2 -0
- package/dist/assetLibrary/draco.test.d.ts.map +1 -0
- package/dist/assetLibrary/draco.test.js +93 -0
- package/dist/assetLibrary/exportGLB.d.ts +20 -0
- package/dist/assetLibrary/exportGLB.d.ts.map +1 -0
- package/dist/assetLibrary/exportGLB.js +83 -0
- package/dist/assetLibrary/exportGLB.test.d.ts +2 -0
- package/dist/assetLibrary/exportGLB.test.d.ts.map +1 -0
- package/dist/assetLibrary/exportGLB.test.js +73 -0
- package/dist/assetLibrary/gltfLoader.d.ts +8 -0
- package/dist/assetLibrary/gltfLoader.d.ts.map +1 -0
- package/dist/assetLibrary/gltfLoader.js +11 -0
- package/dist/assetLibrary/hash.d.ts +7 -0
- package/dist/assetLibrary/hash.d.ts.map +1 -0
- package/dist/assetLibrary/hash.js +17 -0
- package/dist/assetLibrary/importModelFile.d.ts +5 -0
- package/dist/assetLibrary/importModelFile.d.ts.map +1 -0
- package/dist/assetLibrary/importModelFile.js +19 -0
- package/dist/assetLibrary/importModelFile.test.d.ts +2 -0
- package/dist/assetLibrary/importModelFile.test.d.ts.map +1 -0
- package/dist/assetLibrary/importModelFile.test.js +77 -0
- package/dist/assetLibrary/importPipeline.d.ts +61 -0
- package/dist/assetLibrary/importPipeline.d.ts.map +1 -0
- package/dist/assetLibrary/importPipeline.js +253 -0
- package/dist/assetLibrary/importPipeline.test.d.ts +2 -0
- package/dist/assetLibrary/importPipeline.test.d.ts.map +1 -0
- package/dist/assetLibrary/importPipeline.test.js +188 -0
- package/dist/assetLibrary/indexedDBBackend.d.ts +31 -0
- package/dist/assetLibrary/indexedDBBackend.d.ts.map +1 -0
- package/dist/assetLibrary/indexedDBBackend.js +134 -0
- package/dist/assetLibrary/indexedDBBackend.test.d.ts +2 -0
- package/dist/assetLibrary/indexedDBBackend.test.d.ts.map +1 -0
- package/dist/assetLibrary/indexedDBBackend.test.js +148 -0
- package/dist/assetLibrary/legacySeedCleanup.d.ts +19 -0
- package/dist/assetLibrary/legacySeedCleanup.d.ts.map +1 -0
- package/dist/assetLibrary/legacySeedCleanup.js +50 -0
- package/dist/assetLibrary/legacySeedCleanup.test.d.ts +2 -0
- package/dist/assetLibrary/legacySeedCleanup.test.d.ts.map +1 -0
- package/dist/assetLibrary/legacySeedCleanup.test.js +74 -0
- package/dist/assetLibrary/library.d.ts +122 -0
- package/dist/assetLibrary/library.d.ts.map +1 -0
- package/dist/assetLibrary/library.js +424 -0
- package/dist/assetLibrary/library.test.d.ts +2 -0
- package/dist/assetLibrary/library.test.d.ts.map +1 -0
- package/dist/assetLibrary/library.test.js +520 -0
- package/dist/assetLibrary/options.d.ts +9 -0
- package/dist/assetLibrary/options.d.ts.map +1 -0
- package/dist/assetLibrary/options.js +20 -0
- package/dist/assetLibrary/options.test.d.ts +2 -0
- package/dist/assetLibrary/options.test.d.ts.map +1 -0
- package/dist/assetLibrary/options.test.js +70 -0
- package/dist/assetLibrary/pose.d.ts +15 -0
- package/dist/assetLibrary/pose.d.ts.map +1 -0
- package/dist/assetLibrary/pose.js +41 -0
- package/dist/assetLibrary/pose.test.d.ts +2 -0
- package/dist/assetLibrary/pose.test.d.ts.map +1 -0
- package/dist/assetLibrary/pose.test.js +45 -0
- package/dist/assetLibrary/poseThumbnail.d.ts +30 -0
- package/dist/assetLibrary/poseThumbnail.d.ts.map +1 -0
- package/dist/assetLibrary/poseThumbnail.js +144 -0
- package/dist/assetLibrary/poseThumbnail.test.d.ts +2 -0
- package/dist/assetLibrary/poseThumbnail.test.d.ts.map +1 -0
- package/dist/assetLibrary/poseThumbnail.test.js +111 -0
- package/dist/assetLibrary/rig.d.ts +14 -0
- package/dist/assetLibrary/rig.d.ts.map +1 -0
- package/dist/assetLibrary/rig.js +33 -0
- package/dist/assetLibrary/rig.test.d.ts +2 -0
- package/dist/assetLibrary/rig.test.d.ts.map +1 -0
- package/dist/assetLibrary/rig.test.js +55 -0
- package/dist/assetLibrary/seedAssetsManifest.d.ts +24 -0
- package/dist/assetLibrary/seedAssetsManifest.d.ts.map +1 -0
- package/dist/assetLibrary/seedAssetsManifest.js +347 -0
- package/dist/assetLibrary/seedBuiltinPoses.d.ts +29 -0
- package/dist/assetLibrary/seedBuiltinPoses.d.ts.map +1 -0
- package/dist/assetLibrary/seedBuiltinPoses.js +114 -0
- package/dist/assetLibrary/seedBuiltinPoses.test.d.ts +2 -0
- package/dist/assetLibrary/seedBuiltinPoses.test.d.ts.map +1 -0
- package/dist/assetLibrary/seedBuiltinPoses.test.js +176 -0
- package/dist/assetLibrary/seedPoses.d.ts +8 -0
- package/dist/assetLibrary/seedPoses.d.ts.map +1 -0
- package/dist/assetLibrary/seedPoses.js +2414 -0
- package/dist/assetLibrary/seedPosesDpl.d.ts +8 -0
- package/dist/assetLibrary/seedPosesDpl.d.ts.map +1 -0
- package/dist/assetLibrary/seedPosesDpl.js +11660 -0
- package/dist/assetLibrary/seedPosesQbx.d.ts +8 -0
- package/dist/assetLibrary/seedPosesQbx.d.ts.map +1 -0
- package/dist/assetLibrary/seedPosesQbx.js +5612 -0
- package/dist/assetLibrary/seedPosesQbx.test.d.ts +2 -0
- package/dist/assetLibrary/seedPosesQbx.test.d.ts.map +1 -0
- package/dist/assetLibrary/seedPosesQbx.test.js +181 -0
- package/dist/assetLibrary/source.d.ts +45 -0
- package/dist/assetLibrary/source.d.ts.map +1 -0
- package/dist/assetLibrary/source.js +11 -0
- package/dist/assetLibrary/source.test.d.ts +2 -0
- package/dist/assetLibrary/source.test.d.ts.map +1 -0
- package/dist/assetLibrary/source.test.js +28 -0
- package/dist/assetLibrary/staticCatalogSource.d.ts +48 -0
- package/dist/assetLibrary/staticCatalogSource.d.ts.map +1 -0
- package/dist/assetLibrary/staticCatalogSource.js +140 -0
- package/dist/assetLibrary/staticCatalogSource.test.d.ts +2 -0
- package/dist/assetLibrary/staticCatalogSource.test.d.ts.map +1 -0
- package/dist/assetLibrary/staticCatalogSource.test.js +154 -0
- package/dist/assetLibrary/thumbnail.d.ts +14 -0
- package/dist/assetLibrary/thumbnail.d.ts.map +1 -0
- package/dist/assetLibrary/thumbnail.js +70 -0
- package/dist/assetLibrary/types.d.ts +54 -0
- package/dist/assetLibrary/types.d.ts.map +1 -0
- package/dist/assetLibrary/types.js +1 -0
- package/dist/assetLibrary/useModelThumbUrl.d.ts +10 -0
- package/dist/assetLibrary/useModelThumbUrl.d.ts.map +1 -0
- package/dist/assetLibrary/useModelThumbUrl.js +56 -0
- package/dist/boneAnchorWiring.test.d.ts +2 -0
- package/dist/boneAnchorWiring.test.d.ts.map +1 -0
- package/dist/boneAnchorWiring.test.js +173 -0
- package/dist/boneAnchors.d.ts +30 -0
- package/dist/boneAnchors.d.ts.map +1 -0
- package/dist/boneAnchors.js +111 -0
- package/dist/boneAnchors.test.d.ts +2 -0
- package/dist/boneAnchors.test.d.ts.map +1 -0
- package/dist/boneAnchors.test.js +98 -0
- package/dist/bootstrap.d.ts +37 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +394 -0
- package/dist/bootstrap.test.d.ts +2 -0
- package/dist/bootstrap.test.d.ts.map +1 -0
- package/dist/bootstrap.test.js +647 -0
- package/dist/cameraRegistry.d.ts +65 -0
- package/dist/cameraRegistry.d.ts.map +1 -0
- package/dist/cameraRegistry.js +151 -0
- package/dist/cameraRegistry.test.d.ts +2 -0
- package/dist/cameraRegistry.test.d.ts.map +1 -0
- package/dist/cameraRegistry.test.js +68 -0
- package/dist/config.d.ts +92 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +1 -0
- package/dist/contextMenu.d.ts +16 -0
- package/dist/contextMenu.d.ts.map +1 -0
- package/dist/contextMenu.js +10 -0
- package/dist/dropPoint.d.ts +61 -0
- package/dist/dropPoint.d.ts.map +1 -0
- package/dist/dropPoint.js +97 -0
- package/dist/dropPoint.test.d.ts +2 -0
- package/dist/dropPoint.test.d.ts.map +1 -0
- package/dist/dropPoint.test.js +174 -0
- package/dist/editorOnly.d.ts +6 -0
- package/dist/editorOnly.d.ts.map +1 -0
- package/dist/editorOnly.js +32 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/gestureProps.d.ts +99 -0
- package/dist/gestureProps.d.ts.map +1 -0
- package/dist/gestureProps.js +118 -0
- package/dist/gestureProps.test.d.ts +2 -0
- package/dist/gestureProps.test.d.ts.map +1 -0
- package/dist/gestureProps.test.js +182 -0
- package/dist/gesturePropsEditor.d.ts +27 -0
- package/dist/gesturePropsEditor.d.ts.map +1 -0
- package/dist/gesturePropsEditor.js +121 -0
- package/dist/getDeep.d.ts +8 -0
- package/dist/getDeep.d.ts.map +1 -0
- package/dist/getDeep.js +15 -0
- package/dist/gizmoRegistry.test.d.ts +2 -0
- package/dist/gizmoRegistry.test.d.ts.map +1 -0
- package/dist/gizmoRegistry.test.js +40 -0
- package/dist/hostFlows.d.ts +55 -0
- package/dist/hostFlows.d.ts.map +1 -0
- package/dist/hostFlows.js +77 -0
- package/dist/hostFlows.test.d.ts +2 -0
- package/dist/hostFlows.test.d.ts.map +1 -0
- package/dist/hostFlows.test.js +191 -0
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +271 -0
- package/dist/i18n.test.d.ts +2 -0
- package/dist/i18n.test.d.ts.map +1 -0
- package/dist/i18n.test.js +41 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/menuModel.d.ts +51 -0
- package/dist/menuModel.d.ts.map +1 -0
- package/dist/menuModel.js +111 -0
- package/dist/menuModel.test.d.ts +2 -0
- package/dist/menuModel.test.d.ts.map +1 -0
- package/dist/menuModel.test.js +197 -0
- package/dist/modalGrab.d.ts +29 -0
- package/dist/modalGrab.d.ts.map +1 -0
- package/dist/modalGrab.js +127 -0
- package/dist/outlineObjectMenu.d.ts +41 -0
- package/dist/outlineObjectMenu.d.ts.map +1 -0
- package/dist/outlineObjectMenu.js +58 -0
- package/dist/outlineObjectMenu.test.d.ts +2 -0
- package/dist/outlineObjectMenu.test.d.ts.map +1 -0
- package/dist/outlineObjectMenu.test.js +83 -0
- package/dist/pathBinding.d.ts +16 -0
- package/dist/pathBinding.d.ts.map +1 -0
- package/dist/pathBinding.js +19 -0
- package/dist/pathBinding.test.d.ts +2 -0
- package/dist/pathBinding.test.d.ts.map +1 -0
- package/dist/pathBinding.test.js +14 -0
- package/dist/pathModel.d.ts +123 -0
- package/dist/pathModel.d.ts.map +1 -0
- package/dist/pathModel.js +343 -0
- package/dist/pathModel.test.d.ts +2 -0
- package/dist/pathModel.test.d.ts.map +1 -0
- package/dist/pathModel.test.js +394 -0
- package/dist/pathPresets.d.ts +38 -0
- package/dist/pathPresets.d.ts.map +1 -0
- package/dist/pathPresets.js +180 -0
- package/dist/pathPresets.test.d.ts +2 -0
- package/dist/pathPresets.test.d.ts.map +1 -0
- package/dist/pathPresets.test.js +154 -0
- package/dist/pathRegistry.d.ts +46 -0
- package/dist/pathRegistry.d.ts.map +1 -0
- package/dist/pathRegistry.js +105 -0
- package/dist/pathRegistry.test.d.ts +2 -0
- package/dist/pathRegistry.test.d.ts.map +1 -0
- package/dist/pathRegistry.test.js +23 -0
- package/dist/pickerRegistry.d.ts +49 -0
- package/dist/pickerRegistry.d.ts.map +1 -0
- package/dist/pickerRegistry.js +17 -0
- package/dist/pickerRegistry.test.d.ts +2 -0
- package/dist/pickerRegistry.test.d.ts.map +1 -0
- package/dist/pickerRegistry.test.js +31 -0
- package/dist/pipLayout.d.ts +7 -0
- package/dist/pipLayout.d.ts.map +1 -0
- package/dist/pipLayout.js +58 -0
- package/dist/placementKey.test.d.ts +2 -0
- package/dist/placementKey.test.d.ts.map +1 -0
- package/dist/placementKey.test.js +32 -0
- package/dist/placementPoseProps.d.ts +29 -0
- package/dist/placementPoseProps.d.ts.map +1 -0
- package/dist/placementPoseProps.js +43 -0
- package/dist/placementPoseProps.test.d.ts +2 -0
- package/dist/placementPoseProps.test.d.ts.map +1 -0
- package/dist/placementPoseProps.test.js +35 -0
- package/dist/poseMode/PoseModeBar.d.ts +18 -0
- package/dist/poseMode/PoseModeBar.d.ts.map +1 -0
- package/dist/poseMode/PoseModeBar.js +127 -0
- package/dist/poseMode/PoseModeRig.d.ts +9 -0
- package/dist/poseMode/PoseModeRig.d.ts.map +1 -0
- package/dist/poseMode/PoseModeRig.js +300 -0
- package/dist/poseMode/PosePickerFrame.d.ts +42 -0
- package/dist/poseMode/PosePickerFrame.d.ts.map +1 -0
- package/dist/poseMode/PosePickerFrame.js +164 -0
- package/dist/poseMode/PosePropEditor.d.ts +30 -0
- package/dist/poseMode/PosePropEditor.d.ts.map +1 -0
- package/dist/poseMode/PosePropEditor.js +139 -0
- package/dist/poseMode/controlSpec.d.ts +26 -0
- package/dist/poseMode/controlSpec.d.ts.map +1 -0
- package/dist/poseMode/controlSpec.js +157 -0
- package/dist/poseMode/controlSpec.test.d.ts +2 -0
- package/dist/poseMode/controlSpec.test.d.ts.map +1 -0
- package/dist/poseMode/controlSpec.test.js +196 -0
- package/dist/poseMode/dragState.d.ts +16 -0
- package/dist/poseMode/dragState.d.ts.map +1 -0
- package/dist/poseMode/dragState.js +15 -0
- package/dist/poseMode/dragState.test.d.ts +2 -0
- package/dist/poseMode/dragState.test.d.ts.map +1 -0
- package/dist/poseMode/dragState.test.js +23 -0
- package/dist/poseMode/poseDisplayText.d.ts +18 -0
- package/dist/poseMode/poseDisplayText.d.ts.map +1 -0
- package/dist/poseMode/poseDisplayText.js +13 -0
- package/dist/poseMode/poseDisplayText.test.d.ts +2 -0
- package/dist/poseMode/poseDisplayText.test.d.ts.map +1 -0
- package/dist/poseMode/poseDisplayText.test.js +35 -0
- package/dist/poseMode/poseExitGuard.d.ts +8 -0
- package/dist/poseMode/poseExitGuard.d.ts.map +1 -0
- package/dist/poseMode/poseExitGuard.js +13 -0
- package/dist/poseMode/poseExitGuard.test.d.ts +2 -0
- package/dist/poseMode/poseExitGuard.test.d.ts.map +1 -0
- package/dist/poseMode/poseExitGuard.test.js +41 -0
- package/dist/poseMode/poseFrame.d.ts +11 -0
- package/dist/poseMode/poseFrame.d.ts.map +1 -0
- package/dist/poseMode/poseFrame.js +7 -0
- package/dist/poseMode/poseFrame.test.d.ts +2 -0
- package/dist/poseMode/poseFrame.test.d.ts.map +1 -0
- package/dist/poseMode/poseFrame.test.js +25 -0
- package/dist/poseMode/poseInterpolate.d.ts +4 -0
- package/dist/poseMode/poseInterpolate.d.ts.map +1 -0
- package/dist/poseMode/poseInterpolate.js +75 -0
- package/dist/poseMode/poseInterpolate.test.d.ts +2 -0
- package/dist/poseMode/poseInterpolate.test.d.ts.map +1 -0
- package/dist/poseMode/poseInterpolate.test.js +61 -0
- package/dist/poseMode/poseLibResolve.d.ts +9 -0
- package/dist/poseMode/poseLibResolve.d.ts.map +1 -0
- package/dist/poseMode/poseLibResolve.js +49 -0
- package/dist/poseMode/poseLibResolve.test.d.ts +2 -0
- package/dist/poseMode/poseLibResolve.test.d.ts.map +1 -0
- package/dist/poseMode/poseLibResolve.test.js +59 -0
- package/dist/poseMode/poseMigration.d.ts +5 -0
- package/dist/poseMode/poseMigration.d.ts.map +1 -0
- package/dist/poseMode/poseMigration.js +16 -0
- package/dist/poseMode/poseMigration.test.d.ts +2 -0
- package/dist/poseMode/poseMigration.test.d.ts.map +1 -0
- package/dist/poseMode/poseMigration.test.js +25 -0
- package/dist/poseMode/poseOptions.d.ts +45 -0
- package/dist/poseMode/poseOptions.d.ts.map +1 -0
- package/dist/poseMode/poseOptions.js +39 -0
- package/dist/poseMode/poseOptions.test.d.ts +2 -0
- package/dist/poseMode/poseOptions.test.d.ts.map +1 -0
- package/dist/poseMode/poseOptions.test.js +83 -0
- package/dist/poseMode/poseOverride.d.ts +13 -0
- package/dist/poseMode/poseOverride.d.ts.map +1 -0
- package/dist/poseMode/poseOverride.js +26 -0
- package/dist/poseMode/poseOverride.test.d.ts +2 -0
- package/dist/poseMode/poseOverride.test.d.ts.map +1 -0
- package/dist/poseMode/poseOverride.test.js +23 -0
- package/dist/poseMode/poseRetarget.d.ts +17 -0
- package/dist/poseMode/poseRetarget.d.ts.map +1 -0
- package/dist/poseMode/poseRetarget.js +53 -0
- package/dist/poseMode/poseRetarget.test.d.ts +2 -0
- package/dist/poseMode/poseRetarget.test.d.ts.map +1 -0
- package/dist/poseMode/poseRetarget.test.js +88 -0
- package/dist/poseMode/poseSession.d.ts +58 -0
- package/dist/poseMode/poseSession.d.ts.map +1 -0
- package/dist/poseMode/poseSession.js +183 -0
- package/dist/poseMode/poseSession.test.d.ts +2 -0
- package/dist/poseMode/poseSession.test.d.ts.map +1 -0
- package/dist/poseMode/poseSession.test.js +159 -0
- package/dist/poseMode/poseSessionHooks.test.d.ts +2 -0
- package/dist/poseMode/poseSessionHooks.test.d.ts.map +1 -0
- package/dist/poseMode/poseSessionHooks.test.js +49 -0
- package/dist/poseMode/poseTrack.d.ts +30 -0
- package/dist/poseMode/poseTrack.d.ts.map +1 -0
- package/dist/poseMode/poseTrack.js +155 -0
- package/dist/poseMode/poseTrack.test.d.ts +2 -0
- package/dist/poseMode/poseTrack.test.d.ts.map +1 -0
- package/dist/poseMode/poseTrack.test.js +84 -0
- package/dist/poseMode/poseValue.d.ts +22 -0
- package/dist/poseMode/poseValue.d.ts.map +1 -0
- package/dist/poseMode/poseValue.js +73 -0
- package/dist/poseMode/poseValue.test.d.ts +2 -0
- package/dist/poseMode/poseValue.test.d.ts.map +1 -0
- package/dist/poseMode/poseValue.test.js +62 -0
- package/dist/poseMode/savePose.d.ts +23 -0
- package/dist/poseMode/savePose.d.ts.map +1 -0
- package/dist/poseMode/savePose.js +29 -0
- package/dist/poseMode/selectionModes.d.ts +14 -0
- package/dist/poseMode/selectionModes.d.ts.map +1 -0
- package/dist/poseMode/selectionModes.js +39 -0
- package/dist/poseMode/selectionModes.test.d.ts +2 -0
- package/dist/poseMode/selectionModes.test.d.ts.map +1 -0
- package/dist/poseMode/selectionModes.test.js +91 -0
- package/dist/poseNameMessages.d.ts +13 -0
- package/dist/poseNameMessages.d.ts.map +1 -0
- package/dist/poseNameMessages.js +370 -0
- package/dist/poseNames.d.ts +14 -0
- package/dist/poseNames.d.ts.map +1 -0
- package/dist/poseNames.js +21 -0
- package/dist/poseNames.test.d.ts +2 -0
- package/dist/poseNames.test.d.ts.map +1 -0
- package/dist/poseNames.test.js +41 -0
- package/dist/poseStatusText.d.ts +2 -0
- package/dist/poseStatusText.d.ts.map +1 -0
- package/dist/poseStatusText.js +11 -0
- package/dist/poseStatusText.test.d.ts +2 -0
- package/dist/poseStatusText.test.d.ts.map +1 -0
- package/dist/poseStatusText.test.js +21 -0
- package/dist/presetThumbGeometry.d.ts +26 -0
- package/dist/presetThumbGeometry.d.ts.map +1 -0
- package/dist/presetThumbGeometry.js +103 -0
- package/dist/presetThumbGeometry.test.d.ts +2 -0
- package/dist/presetThumbGeometry.test.d.ts.map +1 -0
- package/dist/presetThumbGeometry.test.js +83 -0
- package/dist/selectionOutline.d.ts +42 -0
- package/dist/selectionOutline.d.ts.map +1 -0
- package/dist/selectionOutline.js +174 -0
- package/dist/selectionOutline.test.d.ts +2 -0
- package/dist/selectionOutline.test.d.ts.map +1 -0
- package/dist/selectionOutline.test.js +58 -0
- package/dist/smoke.test.d.ts +1 -0
- package/dist/smoke.test.d.ts.map +1 -0
- package/dist/smoke.test.js +17 -0
- package/dist/uiConfig.d.ts +16 -0
- package/dist/uiConfig.d.ts.map +1 -0
- package/dist/uiConfig.js +13 -0
- package/dist/uiConfig.test.d.ts +2 -0
- package/dist/uiConfig.test.d.ts.map +1 -0
- package/dist/uiConfig.test.js +98 -0
- package/dist/undoShortcuts.d.ts +28 -0
- package/dist/undoShortcuts.d.ts.map +1 -0
- package/dist/undoShortcuts.js +59 -0
- package/dist/undoShortcuts.test.d.ts +2 -0
- package/dist/undoShortcuts.test.d.ts.map +1 -0
- package/dist/undoShortcuts.test.js +37 -0
- package/dist/useCameraStudio.d.ts +3 -0
- package/dist/useCameraStudio.d.ts.map +1 -0
- package/dist/useCameraStudio.js +86 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/videoExport/BlobCollectSink.d.ts +25 -0
- package/dist/videoExport/BlobCollectSink.d.ts.map +1 -0
- package/dist/videoExport/BlobCollectSink.js +56 -0
- package/dist/videoExport/BlobCollectSink.test.d.ts +2 -0
- package/dist/videoExport/BlobCollectSink.test.d.ts.map +1 -0
- package/dist/videoExport/BlobCollectSink.test.js +78 -0
- package/dist/videoExport/encoder.d.ts +34 -0
- package/dist/videoExport/encoder.d.ts.map +1 -0
- package/dist/videoExport/encoder.js +61 -0
- package/dist/videoExport/encoder.test.d.ts +2 -0
- package/dist/videoExport/encoder.test.d.ts.map +1 -0
- package/dist/videoExport/encoder.test.js +21 -0
- package/dist/videoExport/exporter.d.ts +24 -0
- package/dist/videoExport/exporter.d.ts.map +1 -0
- package/dist/videoExport/exporter.js +74 -0
- package/dist/videoExport/sinks.d.ts +55 -0
- package/dist/videoExport/sinks.d.ts.map +1 -0
- package/dist/videoExport/sinks.js +69 -0
- package/package.json +59 -3
- package/README.md +0 -3
|
@@ -0,0 +1,1716 @@
|
|
|
1
|
+
import { editable as e, SheetProvider, PerspectiveCamera } from '@dreamerbird/r3f';
|
|
2
|
+
import { Grid, OrbitControls, GizmoHelper, GizmoViewport, } from '@react-three/drei';
|
|
3
|
+
import { getStudioSync, onChange, val } from '@dreamerbird/core';
|
|
4
|
+
// 诊断撤销栈用:模块级单例直达私有 Studio(getStudioSync 返回的是 publicApi,
|
|
5
|
+
// 空构造不存 internals,摸不到 _store._saaz——TheatreStudio.ts:121)
|
|
6
|
+
import getStudio from '@dreamerbird/studio/getStudio';
|
|
7
|
+
import { privateAPI } from '@dreamerbird/core/privateAPIs';
|
|
8
|
+
import getDeep from './getDeep';
|
|
9
|
+
import { usePrism, useVal } from '@dreamerbird/react';
|
|
10
|
+
import React, { Suspense, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
11
|
+
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
|
12
|
+
import { useT } from '@dreamerbird/studio/i18n';
|
|
13
|
+
import { I18nContext, I18nProvider, setLanguage } from '@dreamerbird/studio/i18n';
|
|
14
|
+
import { useGLTF } from '@react-three/drei';
|
|
15
|
+
import sceneGLB from './scene.glb';
|
|
16
|
+
import { Color, Euler, LinearSRGBColorSpace, MOUSE, PMREMGenerator, Raycaster, Spherical, Vector2, Vector3, } from 'three';
|
|
17
|
+
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
|
|
18
|
+
import { PlacementRig, PlacementRigErrorBoundary, PlacedModels, SelectionGizmo, deletePlacement, getPlacementEntryForObject, getPlacementInstanceForObject, isSelectSuppressed, readPlacementEntries, readPlacementPositions, useGizmoMode, usePlacementController, usePlacementSurface, useSelectionGizmoTarget, } from './PlacementManager';
|
|
19
|
+
import { CanvasContextMenu } from './contextMenu';
|
|
20
|
+
import { registerEditorOnly } from './editorOnly';
|
|
21
|
+
import { buildAnchorMenuItems, buildCameraMenuItems, buildObjectMenuItems, buildPathMenuItems, } from './menuModel';
|
|
22
|
+
import { TopToolbar } from './TopToolbar';
|
|
23
|
+
import { Anchors } from './Anchors';
|
|
24
|
+
import { collectAnchorChildResets, setAimOptionsSheet } from './aimTargets';
|
|
25
|
+
import { addAnchorToRegistry, nextAnchorKey, removeAnchor, useAnchorsList, } from './anchorsRegistry';
|
|
26
|
+
import { PipCameraSelector } from './PipCameraSelector';
|
|
27
|
+
import { usePipBottom } from './pipLayout';
|
|
28
|
+
import { bindUndoShortcuts, isInsideStudioUi } from './undoShortcuts';
|
|
29
|
+
import { AssetLibraryPanel } from './assetLibrary/AssetLibraryPanel';
|
|
30
|
+
import { ModelPickerSlot } from './assetLibrary/ModelPickerSlot';
|
|
31
|
+
import { getLibrary, useLibraryEntries } from './assetLibrary/library';
|
|
32
|
+
import { ShotCamera } from './ShotCamera';
|
|
33
|
+
import { addCameraToRegistry, getCamerasRegistry, nextCameraEntry, readCameraEntries, removeCamera, resolveActiveCamera, useCamerasList, } from './cameraRegistry';
|
|
34
|
+
import { addPathPoint, CameraPath, deletePathPoint, getPathMeta, PathEditBar, PathPointEditor, pathExists, POS_PATH_KEY, readPathPoints, samplePathPoint, usePathExists, } from './CameraPath';
|
|
35
|
+
import { addPathToRegistry, nextPathEntry, readPathEntries, getPathsRegistry, removePathFromRegistry, usePathsList, } from './pathRegistry';
|
|
36
|
+
import { resolveBoundPathKey } from './pathBinding';
|
|
37
|
+
import { deletePathDomain } from './agent/pathCommands';
|
|
38
|
+
import { formatSceneObjectId } from './agent/objectIds';
|
|
39
|
+
import { getSceneSheetObject } from './agent/runtime/objectRuntime';
|
|
40
|
+
import { bindCameraStudioSceneHandlers } from './agent/api';
|
|
41
|
+
import { registerDirectorView } from './agent/runtime/directorView';
|
|
42
|
+
import { findFreeSpot, presetPlacement } from './pathPresets';
|
|
43
|
+
import { PathPresetDropRig } from './PathPresetDropRig';
|
|
44
|
+
import { listSinks } from './videoExport/sinks';
|
|
45
|
+
import { createSceneExportController, registerSceneExportController, } from './agent/runtime/exportRuntime';
|
|
46
|
+
import { rigIdOf, collectBoneNames } from './assetLibrary/rig';
|
|
47
|
+
import { cameraOutlineMenuExtensionId, cameraOutlineObjectMenu, setCameraOutlineMenuCtx, } from './outlineObjectMenu';
|
|
48
|
+
import { pruneObjectFromOutlineCollections } from '@dreamerbird/studio/panels/OutlinePanel/outlineOps/stateOps';
|
|
49
|
+
import { createHostFlows, setDropPointContext, setHostFlowsHandle, setPlacingSnapshot, } from './hostFlows';
|
|
50
|
+
import { importModelFile } from './assetLibrary/importModelFile';
|
|
51
|
+
import { resolveControlSpec } from './poseMode/controlSpec';
|
|
52
|
+
import { getUiConfig } from './uiConfig';
|
|
53
|
+
import { enterPoseSession, getPoseSession, isModelDoubleClick, poseHitTest, requestPoseExit, usePoseSessionKey, } from './poseMode/poseSession';
|
|
54
|
+
import { getPoseKeyframeCount, setPoseValue, writePoseKeyframe, clearPoseValue, } from './poseMode/poseTrack';
|
|
55
|
+
import { savePoseFromInstance } from './poseMode/savePose';
|
|
56
|
+
import { parsePoseValue, serializeLibPose } from './poseMode/poseValue';
|
|
57
|
+
import { isPoseExitGuardedTarget } from './poseMode/poseExitGuard';
|
|
58
|
+
import { poseStatusText } from './poseStatusText';
|
|
59
|
+
function Model({ url, selectLocked }) {
|
|
60
|
+
const { nodes } = useGLTF(url);
|
|
61
|
+
const thingyObjRef = useRef(undefined);
|
|
62
|
+
// 登记为放置落点表面:放置 FBX 时射线检测只打这里和已放置的模型
|
|
63
|
+
const surfaceRef = useRef(null);
|
|
64
|
+
usePlacementSurface(surfaceRef);
|
|
65
|
+
// 登记为选中 gizmo 的附着目标:选中 Thingy 后挂三轴移动箭头
|
|
66
|
+
const thingyGroupRef = useRef(null);
|
|
67
|
+
useSelectionGizmoTarget(thingyObjRef, thingyGroupRef);
|
|
68
|
+
// 两个碎片子网格同样登记:大纲里单独选中它们时也有箭头、可按 G
|
|
69
|
+
// (视口点碎片会冒泡选中整个 Thingy,单独选只能走大纲,和 Blender 一致)
|
|
70
|
+
const debris1ObjRef = useRef(undefined);
|
|
71
|
+
const debris1Ref = useRef(null);
|
|
72
|
+
useSelectionGizmoTarget(debris1ObjRef, debris1Ref);
|
|
73
|
+
const debris2ObjRef = useRef(undefined);
|
|
74
|
+
const debris2Ref = useRef(null);
|
|
75
|
+
useSelectionGizmoTarget(debris2ObjRef, debris2Ref);
|
|
76
|
+
// 主画面点选:r3f 对网格做射线检测,点击任意子网格会冒泡到分组。
|
|
77
|
+
// 拖拽转视角后松手不算点击(r3f 的 delta 判定),两者不冲突。
|
|
78
|
+
const selectThingy = (event) => {
|
|
79
|
+
// 放置模式下点击是确认落点:不选中,也不拦截事件,让它落到
|
|
80
|
+
// PlacementRig 的地面网格上完成确认;点 gizmo 箭头穿透过来的误点也吞掉
|
|
81
|
+
if (selectLocked || isSelectSuppressed())
|
|
82
|
+
return;
|
|
83
|
+
// 姿势会话中点击其他对象不响应(规格:不抢选中)——被摆模型的选中不动
|
|
84
|
+
if (getPoseSession())
|
|
85
|
+
return;
|
|
86
|
+
event.stopPropagation();
|
|
87
|
+
const studio = getStudioSync();
|
|
88
|
+
if (studio && thingyObjRef.current) {
|
|
89
|
+
studio.setSelection([thingyObjRef.current]);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
// scene.glb 里 planet.001 是不带几何体的空分组节点,
|
|
93
|
+
// 真正有几何体的是 planet.002 / planet.003 两个子节点。
|
|
94
|
+
// 因此 Thingy 挂在分组上(e.group),拖动它即拖动整个模型。
|
|
95
|
+
return (React.createElement("group", { ref: surfaceRef, rotation: [-Math.PI / 2, 0, 0], position: [0, -7, 0], scale: 7 },
|
|
96
|
+
React.createElement(e.group, { ref: thingyGroupRef, theatreKey: "Thingy", objRef: thingyObjRef, onClick: selectThingy, rotation: [Math.PI / 13.5, -Math.PI / 5.8, Math.PI / 5.6] },
|
|
97
|
+
React.createElement(e.mesh, { theatreKey: "Thingy / Debris 2", objRef: debris2ObjRef, ref: debris2Ref, receiveShadow: true, castShadow: true, geometry: nodes.planet002.geometry, material: nodes.planet002.material }),
|
|
98
|
+
React.createElement(e.mesh, { theatreKey: "Thingy / Debris 1", objRef: debris1ObjRef, ref: debris1Ref, geometry: nodes.planet003.geometry, material: nodes.planet003.material }))));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 导演视角初始机位/瞄准点:PerspectiveCamera/Target 的 JSX position 与
|
|
102
|
+
* Agent view.reset(MainOrbitControls 登记的导演控制器)同源,改一处
|
|
103
|
+
* 两处一致。
|
|
104
|
+
*/
|
|
105
|
+
const DIRECTOR_HOME_CAMERA_POSITION = [0, 0, 16];
|
|
106
|
+
const DIRECTOR_HOME_TARGET_POSITION = [0, 0, 0];
|
|
107
|
+
/**
|
|
108
|
+
* 主画面的视角控制,操作方式与 Blender 默认键位一致:
|
|
109
|
+
* 中键拖动 = 旋转视角;Shift+中键 = 平移;Ctrl/Cmd+中键 = 平滑缩放;
|
|
110
|
+
* 滚轮 = 缩放;左键不驱动视角(留给点选,和 Blender 的分工一致)。
|
|
111
|
+
* 场景的语义是"相机始终对准 Camera / Target 对象"(PerspectiveCamera 每帧
|
|
112
|
+
* lookAt),因此平移实现为:把 OrbitControls 的新瞄准点写回 Target 对象的
|
|
113
|
+
* 位置属性,让 Target 网格跟着走——不打架,还能持久化和同步。
|
|
114
|
+
* 拖拽过程中把相机位置和瞄准点实时写回 theatre,studio 里的数据和画面
|
|
115
|
+
* 保持一致,没有 studio 时(发布的应用)只在本地生效。
|
|
116
|
+
*/
|
|
117
|
+
function MainOrbitControls({ targetRef, cameraObjRef, targetObjRef, enabled = true, }) {
|
|
118
|
+
// 用 state 而不是 ref 持有控制器:默认相机切换(makeDefault)时 drei 会
|
|
119
|
+
// 重建 OrbitControls 实例,ref 回调会再次触发,监听才能挂到活着的实例上
|
|
120
|
+
const [controls, setControls] = useState(null);
|
|
121
|
+
// 拖拽期间(尤其平移)不把 Target 网格位置同步回瞄准点:平移刚写完
|
|
122
|
+
// theatre,网格位置要等下一拍才更新,此时回读会把瞄准点拽回旧值
|
|
123
|
+
const draggingRef = useRef(false);
|
|
124
|
+
// 瞄准点跟随 Target 对象的位置(Target 在编辑器里被拖动时也能跟上)
|
|
125
|
+
useFrame(() => {
|
|
126
|
+
const target = targetRef.current;
|
|
127
|
+
if (controls &&
|
|
128
|
+
target &&
|
|
129
|
+
!draggingRef.current &&
|
|
130
|
+
!controls.target.equals(target.position)) {
|
|
131
|
+
controls.target.copy(target.position);
|
|
132
|
+
controls.update();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
useLayoutEffect(() => {
|
|
136
|
+
if (!controls)
|
|
137
|
+
return;
|
|
138
|
+
let scrub;
|
|
139
|
+
const onStart = () => {
|
|
140
|
+
draggingRef.current = true;
|
|
141
|
+
const studio = getStudioSync();
|
|
142
|
+
if (!studio)
|
|
143
|
+
return;
|
|
144
|
+
scrub = studio.scrub();
|
|
145
|
+
};
|
|
146
|
+
const onChange = () => {
|
|
147
|
+
const cameraObj = cameraObjRef.current;
|
|
148
|
+
if (!scrub || !cameraObj)
|
|
149
|
+
return;
|
|
150
|
+
const p = controls.object.position;
|
|
151
|
+
const t = controls.target;
|
|
152
|
+
try {
|
|
153
|
+
scrub.capture(({ set }) => {
|
|
154
|
+
set(cameraObj.props.position, { x: p.x, y: p.y, z: p.z });
|
|
155
|
+
const targetObj = targetObjRef.current;
|
|
156
|
+
if (targetObj) {
|
|
157
|
+
set(targetObj.props.position, { x: t.x, y: t.y, z: t.z });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
// capture 抛错后 scrub 已被标记为 Discarded(不可再用)。扔掉引用,
|
|
163
|
+
// 放弃本次拖拽的剩余改动——否则之后每个 change 事件都会重复抛错刷屏
|
|
164
|
+
console.warn('[camera] 相机 scrub 失效,本次拖拽的剩余改动被放弃', err);
|
|
165
|
+
scrub = undefined;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const onEnd = () => {
|
|
169
|
+
draggingRef.current = false;
|
|
170
|
+
const s = scrub;
|
|
171
|
+
scrub = undefined;
|
|
172
|
+
try {
|
|
173
|
+
// 编辑器视角是视图态不是场景内容:持久化+同步,但不进撤销历史
|
|
174
|
+
s?.commit(false);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
// scrub 已失效时 commit 会抛错,不能让它阻止引用清理
|
|
178
|
+
console.warn('[camera] 相机 scrub 提交失败', err);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
controls.addEventListener('start', onStart);
|
|
182
|
+
controls.addEventListener('change', onChange);
|
|
183
|
+
controls.addEventListener('end', onEnd);
|
|
184
|
+
return () => {
|
|
185
|
+
controls.removeEventListener('start', onStart);
|
|
186
|
+
controls.removeEventListener('change', onChange);
|
|
187
|
+
controls.removeEventListener('end', onEnd);
|
|
188
|
+
scrub?.discard();
|
|
189
|
+
};
|
|
190
|
+
}, [controls, cameraObjRef, targetObjRef]);
|
|
191
|
+
// Ctrl/Cmd 按住期间把中键从"旋转"临时切换为"缩放"(Blender 的 Ctrl+中键);
|
|
192
|
+
// OrbitControls 只内置了"旋转+修饰键→平移",不支持修饰键切缩放,所以自己换
|
|
193
|
+
useLayoutEffect(() => {
|
|
194
|
+
if (!controls)
|
|
195
|
+
return;
|
|
196
|
+
const toDolly = (e) => {
|
|
197
|
+
if (e.key === 'Control' || e.key === 'Meta') {
|
|
198
|
+
controls.mouseButtons.MIDDLE = MOUSE.DOLLY;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const toRotate = (e) => {
|
|
202
|
+
if (e.key === 'Control' || e.key === 'Meta') {
|
|
203
|
+
controls.mouseButtons.MIDDLE = MOUSE.ROTATE;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
// 窗口失焦时复位,避免 Ctrl 在窗外松开导致中键一直停在缩放
|
|
207
|
+
const reset = () => {
|
|
208
|
+
controls.mouseButtons.MIDDLE = MOUSE.ROTATE;
|
|
209
|
+
};
|
|
210
|
+
window.addEventListener('keydown', toDolly);
|
|
211
|
+
window.addEventListener('keyup', toRotate);
|
|
212
|
+
window.addEventListener('blur', reset);
|
|
213
|
+
return () => {
|
|
214
|
+
window.removeEventListener('keydown', toDolly);
|
|
215
|
+
window.removeEventListener('keyup', toRotate);
|
|
216
|
+
window.removeEventListener('blur', reset);
|
|
217
|
+
};
|
|
218
|
+
}, [controls]);
|
|
219
|
+
// 触控板手势对齐 Blender:双指滑动=旋转视角,捏合=缩放,鼠标滚轮保持缩放。
|
|
220
|
+
// 浏览器里三者都是 wheel 事件,区分方式:
|
|
221
|
+
// 捏合 —— 事件带 ctrlKey(macOS 的 Chrome/Safari 都这么报),放行给 OrbitControls 缩放
|
|
222
|
+
// 鼠标滚轮 —— delta 是大的整数值(Chrome 约 ±100 的倍数,Firefox 用行距模式),放行缩放
|
|
223
|
+
// 双指滑动 —— 小而密的像素 delta(常带小数),拦下来自己转成旋转。
|
|
224
|
+
// 启发式不是 100% 可靠:触控板快速甩动偶尔会被当成滚轮(变成缩放),可接受。
|
|
225
|
+
useLayoutEffect(() => {
|
|
226
|
+
if (!controls)
|
|
227
|
+
return;
|
|
228
|
+
const element = controls.domElement;
|
|
229
|
+
const spherical = new Spherical();
|
|
230
|
+
const offset = new Vector3();
|
|
231
|
+
const panOffset = new Vector3();
|
|
232
|
+
const EPS = 0.000001;
|
|
233
|
+
const isMouseWheel = (e) => e.deltaMode !== WheelEvent.DOM_DELTA_PIXEL ||
|
|
234
|
+
(Number.isInteger(e.deltaY) && Math.abs(e.deltaY) >= 50);
|
|
235
|
+
const onWheel = (e) => {
|
|
236
|
+
if (e.ctrlKey || isMouseWheel(e))
|
|
237
|
+
return;
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
e.stopImmediatePropagation();
|
|
240
|
+
// Shift+双指滑动 = 平移(Blender 的 Shift+中键):相机和瞄准点一起移,
|
|
241
|
+
// 换算与 OrbitControls 内部的 panLeft/panUp 一致,但方向取反——
|
|
242
|
+
// Blender 是"视角跟着鼠标方向走",OrbitControls 默认是"内容跟着鼠标走"
|
|
243
|
+
if (e.shiftKey) {
|
|
244
|
+
if (controls.enablePan === false)
|
|
245
|
+
return;
|
|
246
|
+
const camera = controls.object;
|
|
247
|
+
offset.copy(camera.position).sub(controls.target);
|
|
248
|
+
const targetDistance = offset.length() * Math.tan(((camera.fov / 2) * Math.PI) / 180);
|
|
249
|
+
const panX = (2 * e.deltaX * targetDistance) / element.clientHeight;
|
|
250
|
+
const panY = (2 * e.deltaY * targetDistance) / element.clientHeight;
|
|
251
|
+
panOffset
|
|
252
|
+
.setFromMatrixColumn(camera.matrix, 0)
|
|
253
|
+
.multiplyScalar(panX)
|
|
254
|
+
.add(offset.setFromMatrixColumn(camera.matrix, 1).multiplyScalar(-panY));
|
|
255
|
+
if (panOffset.lengthSq() < EPS)
|
|
256
|
+
return;
|
|
257
|
+
controls.dispatchEvent({ type: 'start' });
|
|
258
|
+
camera.position.add(panOffset);
|
|
259
|
+
controls.target.add(panOffset);
|
|
260
|
+
controls.update();
|
|
261
|
+
controls.dispatchEvent({ type: 'end' });
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (controls.enableRotate === false)
|
|
265
|
+
return;
|
|
266
|
+
// 与 OrbitControls 内部 rotateLeft/rotateUp 相同的换算(都除以高度)
|
|
267
|
+
const rotateSpeed = controls.rotateSpeed ?? 1;
|
|
268
|
+
const theta = ((2 * Math.PI * e.deltaX) / element.clientHeight) * rotateSpeed;
|
|
269
|
+
const phi = ((2 * Math.PI * e.deltaY) / element.clientHeight) * rotateSpeed;
|
|
270
|
+
offset.copy(controls.object.position).sub(controls.target);
|
|
271
|
+
spherical.setFromVector3(offset);
|
|
272
|
+
spherical.theta -= theta;
|
|
273
|
+
spherical.phi -= phi;
|
|
274
|
+
spherical.phi = Math.max(controls.minPolarAngle + EPS, Math.min(controls.maxPolarAngle - EPS, spherical.phi));
|
|
275
|
+
offset.setFromSpherical(spherical).add(controls.target);
|
|
276
|
+
// 触控板惯性流里混着大量零增量事件;位置没变化就不派生 start/end,
|
|
277
|
+
// 否则每次都提交一个空 scrub,控制台被 "Scrub is empty" 刷屏
|
|
278
|
+
if (offset.distanceToSquared(controls.object.position) < EPS)
|
|
279
|
+
return;
|
|
280
|
+
// 走和拖拽一样的事件流:start/end 包住 change,写回 theatre 的逻辑直接复用
|
|
281
|
+
controls.dispatchEvent({ type: 'start' });
|
|
282
|
+
controls.object.position.copy(offset);
|
|
283
|
+
controls.update();
|
|
284
|
+
controls.dispatchEvent({ type: 'end' });
|
|
285
|
+
};
|
|
286
|
+
// capture + stopImmediatePropagation:抢在 OrbitControls 自己的 wheel 监听之前拦下
|
|
287
|
+
element.addEventListener('wheel', onWheel, { passive: false, capture: true });
|
|
288
|
+
return () => element.removeEventListener('wheel', onWheel, { capture: true });
|
|
289
|
+
}, [controls]);
|
|
290
|
+
// Agent view.* 导演视角命令(Task 12):把 orbit/pan/dolly/reset 登记到
|
|
291
|
+
// directorView 模块表。四个操作都复用拖拽的事件流(start → 改机位/瞄准点
|
|
292
|
+
// → update → end),写回 theatre 的 scrub 逻辑随之复用;新位置先算好再
|
|
293
|
+
// 派生事件,零增量不产生空 scrub(同触控板 wheel 处理)。cleanup 由
|
|
294
|
+
// registerDirectorView 的同引用校验保证只解绑自己这一代。
|
|
295
|
+
useLayoutEffect(() => {
|
|
296
|
+
if (!controls)
|
|
297
|
+
return;
|
|
298
|
+
const spherical = new Spherical();
|
|
299
|
+
const offset = new Vector3();
|
|
300
|
+
const panOffset = new Vector3();
|
|
301
|
+
const homeCamera = new Vector3();
|
|
302
|
+
const EPS = 0.000001;
|
|
303
|
+
const controller = {
|
|
304
|
+
orbit({ deltaTheta, deltaPhi }) {
|
|
305
|
+
offset.copy(controls.object.position).sub(controls.target);
|
|
306
|
+
spherical.setFromVector3(offset);
|
|
307
|
+
spherical.theta += deltaTheta;
|
|
308
|
+
spherical.phi += deltaPhi;
|
|
309
|
+
spherical.phi = Math.max(controls.minPolarAngle + EPS, Math.min(controls.maxPolarAngle - EPS, spherical.phi));
|
|
310
|
+
offset.setFromSpherical(spherical).add(controls.target);
|
|
311
|
+
if (offset.distanceToSquared(controls.object.position) < EPS)
|
|
312
|
+
return;
|
|
313
|
+
controls.dispatchEvent({ type: 'start' });
|
|
314
|
+
controls.object.position.copy(offset);
|
|
315
|
+
controls.update();
|
|
316
|
+
controls.dispatchEvent({ type: 'end' });
|
|
317
|
+
},
|
|
318
|
+
pan({ deltaX, deltaY }) {
|
|
319
|
+
const camera = controls.object;
|
|
320
|
+
panOffset
|
|
321
|
+
.setFromMatrixColumn(camera.matrix, 0)
|
|
322
|
+
.multiplyScalar(deltaX)
|
|
323
|
+
.add(offset.setFromMatrixColumn(camera.matrix, 1).multiplyScalar(deltaY));
|
|
324
|
+
if (panOffset.lengthSq() < EPS)
|
|
325
|
+
return;
|
|
326
|
+
controls.dispatchEvent({ type: 'start' });
|
|
327
|
+
camera.position.add(panOffset);
|
|
328
|
+
controls.target.add(panOffset);
|
|
329
|
+
controls.update();
|
|
330
|
+
controls.dispatchEvent({ type: 'end' });
|
|
331
|
+
},
|
|
332
|
+
dolly({ delta }) {
|
|
333
|
+
offset.copy(controls.object.position).sub(controls.target);
|
|
334
|
+
const length = offset.length();
|
|
335
|
+
// 正值 = 靠近瞄准点;不小于轨道最小距离(兜底 0.01 防与瞄准点重合)
|
|
336
|
+
const next = Math.max(controls.minDistance || 0.01, length - delta);
|
|
337
|
+
if (Math.abs(next - length) < EPS)
|
|
338
|
+
return;
|
|
339
|
+
controls.dispatchEvent({ type: 'start' });
|
|
340
|
+
controls.object.position.copy(offset.setLength(next).add(controls.target));
|
|
341
|
+
controls.update();
|
|
342
|
+
controls.dispatchEvent({ type: 'end' });
|
|
343
|
+
},
|
|
344
|
+
reset() {
|
|
345
|
+
const camera = controls.object;
|
|
346
|
+
const atRest = camera.position.distanceToSquared(homeCamera.set(...DIRECTOR_HOME_CAMERA_POSITION)) < EPS &&
|
|
347
|
+
controls.target.distanceToSquared(offset.set(...DIRECTOR_HOME_TARGET_POSITION)) < EPS;
|
|
348
|
+
if (atRest)
|
|
349
|
+
return;
|
|
350
|
+
controls.dispatchEvent({ type: 'start' });
|
|
351
|
+
camera.position.set(...DIRECTOR_HOME_CAMERA_POSITION);
|
|
352
|
+
controls.target.set(...DIRECTOR_HOME_TARGET_POSITION);
|
|
353
|
+
controls.update();
|
|
354
|
+
controls.dispatchEvent({ type: 'end' });
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
return registerDirectorView(controller);
|
|
358
|
+
}, [controls]);
|
|
359
|
+
return (React.createElement(OrbitControls, { ref: setControls, makeDefault // 让 GizmoHelper 能读到 controls.target 并在吸附动画后复位 camera.up
|
|
360
|
+
: true, enabled: enabled, enableDamping: false, zoomSpeed: 0.5,
|
|
361
|
+
// 平移方向对齐 Blender("视角跟着鼠标走"),与 OrbitControls 默认相反;
|
|
362
|
+
// panSpeed 取负即方向反转、速度不变,Shift+中键拖动走这里
|
|
363
|
+
panSpeed: -1,
|
|
364
|
+
// 旋转同理取反对齐 Blender;触控板 wheel 旋转的公式里直接引用
|
|
365
|
+
// controls.rotateSpeed,两条路径一起翻
|
|
366
|
+
rotateSpeed: -1, mouseButtons: {
|
|
367
|
+
LEFT: -1, // 左键留给点选,不驱动视角
|
|
368
|
+
MIDDLE: MOUSE.ROTATE, // 中键旋转(Shift+中键平移由内置修饰键逻辑提供)
|
|
369
|
+
RIGHT: -1, // 右键不驱动视角
|
|
370
|
+
} }));
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* 视图右上角的方向轴部件(Blender 的导航 gizmo):点击轴头把视角吸附到
|
|
374
|
+
* 对应的正/反轴向视图(前/后/左/右/顶/底),XYZ 对应红绿蓝。
|
|
375
|
+
* 吸附是 GizmoHelper 内部逐帧的补间动画,onUpdate 每帧触发:首帧开 scrub、
|
|
376
|
+
* 逐帧 capture、动画停 200ms 后 commit,整个吸附合成一步可撤销记录。
|
|
377
|
+
* 相机的朝向不用写回——PerspectiveCamera 每帧 lookAt Target,位置对了朝向就对。
|
|
378
|
+
*/
|
|
379
|
+
function GizmoNav({ cameraObjRef, }) {
|
|
380
|
+
const camera = useThree((s) => s.camera);
|
|
381
|
+
const scrubRef = useRef(undefined);
|
|
382
|
+
const commitTimerRef = useRef(undefined);
|
|
383
|
+
const onUpdate = useCallback(() => {
|
|
384
|
+
const studio = getStudioSync();
|
|
385
|
+
const cameraObj = cameraObjRef.current;
|
|
386
|
+
if (!studio || !cameraObj)
|
|
387
|
+
return;
|
|
388
|
+
if (!scrubRef.current)
|
|
389
|
+
scrubRef.current = studio.scrub();
|
|
390
|
+
const p = camera.position;
|
|
391
|
+
try {
|
|
392
|
+
scrubRef.current.capture(({ set }) => {
|
|
393
|
+
set(cameraObj.props.position, { x: p.x, y: p.y, z: p.z });
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
// capture 抛错后 scrub 已被标记为 Discarded(不可再用)。扔掉引用,
|
|
398
|
+
// 否则吸附动画剩余每一帧都会重复抛错
|
|
399
|
+
console.warn('[camera] gizmo scrub 失效,本次吸附的剩余改动被放弃', err);
|
|
400
|
+
scrubRef.current = undefined;
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (commitTimerRef.current)
|
|
404
|
+
clearTimeout(commitTimerRef.current);
|
|
405
|
+
commitTimerRef.current = setTimeout(() => {
|
|
406
|
+
const s = scrubRef.current;
|
|
407
|
+
scrubRef.current = undefined;
|
|
408
|
+
try {
|
|
409
|
+
// 导航轴吸附同属编辑器视角:持久化,但不进撤销历史
|
|
410
|
+
s?.commit(false);
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
// scrub 已失效时 commit 会抛错,不能让它阻止引用清理
|
|
414
|
+
console.warn('[camera] gizmo scrub 提交失败', err);
|
|
415
|
+
}
|
|
416
|
+
}, 200);
|
|
417
|
+
}, [camera, cameraObjRef]);
|
|
418
|
+
useEffect(() => () => {
|
|
419
|
+
if (commitTimerRef.current)
|
|
420
|
+
clearTimeout(commitTimerRef.current);
|
|
421
|
+
}, []);
|
|
422
|
+
return (
|
|
423
|
+
// 右上角往左让出属性面板的位置,避免轴臂被面板遮住。
|
|
424
|
+
// key=相机:默认相机切换(或热更新后状态老化)时强制 gizmo 重建,
|
|
425
|
+
// 防止它内部缓存了旧相机导致朝向冻结
|
|
426
|
+
React.createElement(GizmoHelper, { key: camera.uuid, alignment: "top-right", margin: [340, 70], onUpdate: onUpdate },
|
|
427
|
+
React.createElement(GizmoViewport, { labelColor: "white" })));
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* 删除路径前收集绑定它的相机(连同机位定格),交给注册表折进清单移除的
|
|
431
|
+
* 同一个 scrub 里回退——为什么不靠 ShotCamera 的失效看护做本地删除的回退,
|
|
432
|
+
* 见 removePathFromRegistry 的注释(撤销陷阱)。
|
|
433
|
+
* 机位采样行进进度处 = 相机画面里的当前位置(进度共享在路径对象上,
|
|
434
|
+
* 一台采样全体通用);没点的路径没驱动过相机,不定格。
|
|
435
|
+
* 清单现场重读,不用渲染期快照(对齐启动自检的教训:快照有瞬窗)。
|
|
436
|
+
*/
|
|
437
|
+
function collectBoundCameraResets(sheet, shotRefs, metaKey) {
|
|
438
|
+
const registeredKeys = readPathEntries(getPathsRegistry(sheet)).map((e) => e.pathKey);
|
|
439
|
+
let freeze = null;
|
|
440
|
+
if (pathExists(sheet, metaKey) && readPathPoints(sheet, metaKey).length > 0) {
|
|
441
|
+
const meta = getPathMeta(sheet, metaKey);
|
|
442
|
+
const tmp = new Vector3();
|
|
443
|
+
if (samplePathPoint(metaKey, val(meta.props.progress), tmp)) {
|
|
444
|
+
freeze = { x: tmp.x, y: tmp.y, z: tmp.z };
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const resets = [];
|
|
448
|
+
for (const entry of readCameraEntries(getCamerasRegistry(sheet))) {
|
|
449
|
+
const obj = shotRefs.get(entry.cameraKey)?.camera.current;
|
|
450
|
+
if (!obj)
|
|
451
|
+
continue;
|
|
452
|
+
const pathKey = val(obj.props.pathKey) ?? '';
|
|
453
|
+
if (resolveBoundPathKey(pathKey, registeredKeys) !== metaKey)
|
|
454
|
+
continue;
|
|
455
|
+
resets.push({ obj, freeze });
|
|
456
|
+
}
|
|
457
|
+
return resets;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* 中性环境光贴图(RoomEnvironment):GLB 模型是 PBR 材质,只靠点光源/平行光
|
|
461
|
+
* 会发闷发暗,环境贴图才是 PBR 的主要亮度来源。内置程序生成的房间环境,
|
|
462
|
+
* 不拉外部 HDR,离线可用;强度压低,只负责"打亮",明暗关系仍归 Key Light。
|
|
463
|
+
*/
|
|
464
|
+
function EnvLighting() {
|
|
465
|
+
const gl = useThree((s) => s.gl);
|
|
466
|
+
const scene = useThree((s) => s.scene);
|
|
467
|
+
useEffect(() => {
|
|
468
|
+
const pmrem = new PMREMGenerator(gl);
|
|
469
|
+
const env = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;
|
|
470
|
+
scene.environment = env;
|
|
471
|
+
scene.environmentIntensity = 0.6;
|
|
472
|
+
return () => {
|
|
473
|
+
scene.environment = null;
|
|
474
|
+
scene.environmentIntensity = 1;
|
|
475
|
+
env.dispose();
|
|
476
|
+
pmrem.dispose();
|
|
477
|
+
};
|
|
478
|
+
}, [gl, scene]);
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* 背景色(Blender 4.x 默认视口色 #454545)。
|
|
483
|
+
* 注意:Canvas 是 linear 模式,scene.background 的 Color 会被原样写进
|
|
484
|
+
* gl.clearColor 不做 sRGB 编码,而 hex 构造时又已按 sRGB→linear 转换
|
|
485
|
+
* 过一次,直接写 hex 会暗两个数量级(看上去纯黑)。这里按 linear 原值
|
|
486
|
+
* 构造,让屏幕显示值 = 目标 sRGB 值。材质颜色不受影响(走着色器输出链)。
|
|
487
|
+
*/
|
|
488
|
+
const BACKGROUND = new Color().setRGB(0x45 / 255, 0x45 / 255, 0x45 / 255, LinearSRGBColorSpace);
|
|
489
|
+
/**
|
|
490
|
+
* Blender 式地面网格:布局参考线,编辑辅助对象(登记 editorOnly,
|
|
491
|
+
* 画中画/镜头视角取景不画它)。微抬 0.01 避免与放置面 z-fighting;
|
|
492
|
+
* 不参与射线检测,不挡点选/放置。
|
|
493
|
+
* 颜色注意:linear 模式下材质色同样被压暗 ^2.2,hex 要按目标显示色
|
|
494
|
+
* 反推(目标 #55555b → hex #9b9ba1)。
|
|
495
|
+
*/
|
|
496
|
+
function GroundGrid() {
|
|
497
|
+
const ref = useRef(null);
|
|
498
|
+
useEffect(() => registerEditorOnly(ref.current), []);
|
|
499
|
+
return (React.createElement(Grid, { ref: ref, position: [0, 0.01, 0], args: [100, 100], cellSize: 1, cellColor: "#9b9ba1", sectionSize: 5, sectionColor: "#aeaeb6", fadeDistance: 80, fadeStrength: 1, infiniteGrid: true, raycast: () => null }));
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* 添加相机的默认取景种子(2026-10-17,Task 10 抽成纯函数):
|
|
503
|
+
* 新机位/朝向都取当前导演视角(用户正看着的画面);无导演相机时无种子
|
|
504
|
+
* (组件按 theatre 属性默认值落地)。UI 顶栏「添加相机」与 Agent
|
|
505
|
+
* cameras.create(fromCurrentView) 的 Scene Runtime 绑定共用。
|
|
506
|
+
*/
|
|
507
|
+
export function captureCameraSeed(mainCam) {
|
|
508
|
+
if (!mainCam)
|
|
509
|
+
return undefined;
|
|
510
|
+
return {
|
|
511
|
+
position: val(mainCam.props.position),
|
|
512
|
+
rotation: val(mainCam.props.rotation),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* 添加锚点的默认落点(2026-10-17,Task 10 抽成纯函数):
|
|
517
|
+
* 落在当前导演视角前方约 5 单位(导演相机 -Z 前方,three 相机朝向约定);
|
|
518
|
+
* 无导演相机退场景中心上方。UI 顶栏「添加锚点」与 Agent
|
|
519
|
+
* anchors.create(缺省 position)的 Scene Runtime 绑定共用。
|
|
520
|
+
*/
|
|
521
|
+
export function captureAnchorSeed(mainCam) {
|
|
522
|
+
if (!mainCam)
|
|
523
|
+
return { position: { x: 0, y: 1, z: 0 } };
|
|
524
|
+
const p = val(mainCam.props.position);
|
|
525
|
+
const r = val(mainCam.props.rotation);
|
|
526
|
+
const forward = new Vector3(0, 0, -1).applyEuler(new Euler(r.x, r.y, r.z, 'XYZ'));
|
|
527
|
+
return {
|
|
528
|
+
position: {
|
|
529
|
+
x: p.x + forward.x * 5,
|
|
530
|
+
y: p.y + forward.y * 5,
|
|
531
|
+
z: p.z + forward.z * 5,
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* 相机场景组件(spec §3 宿主唯一渲染面,2026-10-12 由 App 改名而来):
|
|
537
|
+
* project 由宿主经 useCameraStudio 的 sceneProps 注入(bootstrap 内解析,
|
|
538
|
+
* 字符串/IProject 实例皆由配置侧处理),组件内不再 getProject。
|
|
539
|
+
* UI 装配可配见 ./uiConfig:停靠框 portal 容器 / z-index 基准 / 语言
|
|
540
|
+
* (宿主已包 I18nProvider 时语言以宿主为准,ui.locale 仅自包时生效)。
|
|
541
|
+
*/
|
|
542
|
+
export function CameraScene({ project, onFlows, onError }) {
|
|
543
|
+
const t = useT();
|
|
544
|
+
const cameraTargetRef = useRef(null);
|
|
545
|
+
const cameraObjRef = useRef(undefined);
|
|
546
|
+
const targetObjRef = useRef(undefined);
|
|
547
|
+
// 场景摄像机(镜头):独立于编辑主视角,取景用于画中画/镜头视角。
|
|
548
|
+
// 每台相机一对 objRef,按 cameraKey 取(单对 ref 会被多实例互相覆盖)
|
|
549
|
+
const shotRefsMapRef = useRef(new Map());
|
|
550
|
+
const getShotRefs = (cameraKey) => {
|
|
551
|
+
const map = shotRefsMapRef.current;
|
|
552
|
+
let refs = map.get(cameraKey);
|
|
553
|
+
if (!refs) {
|
|
554
|
+
refs = { camera: { current: undefined } };
|
|
555
|
+
map.set(cameraKey, refs);
|
|
556
|
+
}
|
|
557
|
+
return refs;
|
|
558
|
+
};
|
|
559
|
+
// 每个锚点一个 objRef(多锚点:单对 ref 会被 map 里的实例互相覆盖)。
|
|
560
|
+
// CameraScene 持有而非组件自持:删除后的 ref slot 清理由下面的看护
|
|
561
|
+
// effect 按注册表清单扫(真删本身走 anchorsRegistry.removeAnchor,
|
|
562
|
+
// forgetObject 按地址清持久数据,不再依赖这里的对象句柄)
|
|
563
|
+
const anchorRefsMapRef = useRef(new Map());
|
|
564
|
+
const getAnchorRef = (anchorKey) => {
|
|
565
|
+
const map = anchorRefsMapRef.current;
|
|
566
|
+
let ref = map.get(anchorKey);
|
|
567
|
+
if (!ref) {
|
|
568
|
+
ref = { current: undefined };
|
|
569
|
+
map.set(anchorKey, ref);
|
|
570
|
+
}
|
|
571
|
+
return ref;
|
|
572
|
+
};
|
|
573
|
+
// true = 全屏镜头视角(导演视角让位,轨道控制器/导航块停用)
|
|
574
|
+
const [shotView, setShotView] = useState(false);
|
|
575
|
+
// 画中画开关(顶栏按钮):只管编辑视角右下角的小窗,不影响镜头全屏视角
|
|
576
|
+
const [pipVisible, setPipVisible] = useState(true);
|
|
577
|
+
// 画中画底部留白:悬在时间轴面板上方,随面板高度/收拢展开自动跟
|
|
578
|
+
const pipBottom = usePipBottom();
|
|
579
|
+
// 画中画边框层是 pointerEvents:none,收不到 hover——用窗口 mousemove
|
|
580
|
+
// 按画中画的几何位置(与边框层/ShotCamera 同源)判定光标是否在它上面
|
|
581
|
+
const [pipHovered, setPipHovered] = useState(false);
|
|
582
|
+
useEffect(() => {
|
|
583
|
+
const onMove = (e) => {
|
|
584
|
+
const w = window.innerWidth;
|
|
585
|
+
const h = window.innerHeight;
|
|
586
|
+
const pipW = w * 0.28;
|
|
587
|
+
const pipH = (pipW * 9) / 16;
|
|
588
|
+
const left = w - 16 - pipW;
|
|
589
|
+
const top = h - pipBottom - pipH;
|
|
590
|
+
setPipHovered(e.clientX >= left &&
|
|
591
|
+
e.clientX <= left + pipW &&
|
|
592
|
+
e.clientY >= top &&
|
|
593
|
+
e.clientY <= top + pipH);
|
|
594
|
+
};
|
|
595
|
+
window.addEventListener('mousemove', onMove);
|
|
596
|
+
return () => window.removeEventListener('mousemove', onMove);
|
|
597
|
+
}, [pipBottom]);
|
|
598
|
+
// 视频导出:进行中的导出句柄由场景导出控制器(agent/runtime/exportRuntime)
|
|
599
|
+
// 同步进来(Esc 取消/相机被删中止/停止按钮共用);导出前的视角由控制器恢复
|
|
600
|
+
const [exporting, setExporting] = useState(false);
|
|
601
|
+
const exportHandleRef = useRef(null);
|
|
602
|
+
// project 来自 props(T9):宿主经 sceneProps 注入,bootstrap 已解析
|
|
603
|
+
const sheet = project.sheet('Scene');
|
|
604
|
+
// 「瞄准目标」下拉的动态选项(aimTargetOptions)在 studio 面板渲染时
|
|
605
|
+
// 调用,不在 React 上下文里——把当前 sheet 喂给模块级 optionsSheet,
|
|
606
|
+
// 选项函数经它读 Anchors 注册表
|
|
607
|
+
useEffect(() => setAimOptionsSheet(sheet), [sheet]);
|
|
608
|
+
const cameras = useCamerasList(sheet);
|
|
609
|
+
const anchors = useAnchorsList(sheet);
|
|
610
|
+
// 活动相机:画中画/镜头视角/导出只对它生效(右键「查看此镜头」= 设活动)。
|
|
611
|
+
// 持久化没必要(session 态);key 失效(被删)时顺位回退清单第一台
|
|
612
|
+
// (resolveActiveCamera,Task 14 抽纯函数,Agent 侧共用同一规则)
|
|
613
|
+
const [activeCameraKey, setActiveCameraKey] = useState(null);
|
|
614
|
+
const activeCamera = resolveActiveCamera(cameras, activeCameraKey);
|
|
615
|
+
// 相机被删(含对端同步):清掉它的 ref slot,不留失效引用
|
|
616
|
+
useEffect(() => {
|
|
617
|
+
const map = shotRefsMapRef.current;
|
|
618
|
+
for (const key of [...map.keys()]) {
|
|
619
|
+
if (!cameras.some((e) => e.cameraKey === key))
|
|
620
|
+
map.delete(key);
|
|
621
|
+
}
|
|
622
|
+
}, [cameras]);
|
|
623
|
+
// 锚点被删(含对端同步):清掉它的 ref slot,不留失效引用
|
|
624
|
+
useEffect(() => {
|
|
625
|
+
const map = anchorRefsMapRef.current;
|
|
626
|
+
for (const key of [...map.keys()]) {
|
|
627
|
+
if (!anchors.includes(key))
|
|
628
|
+
map.delete(key);
|
|
629
|
+
}
|
|
630
|
+
}, [anchors]);
|
|
631
|
+
const activeShotRefs = activeCamera
|
|
632
|
+
? getShotRefs(activeCamera.cameraKey)
|
|
633
|
+
: undefined;
|
|
634
|
+
// 添加相机:取景种子由 captureCameraSeed 从当前导演视角捕获
|
|
635
|
+
const addCamera = useCallback(() => {
|
|
636
|
+
const entry = nextCameraEntry(sheet, cameras);
|
|
637
|
+
addCameraToRegistry(sheet, entry, captureCameraSeed(cameraObjRef.current));
|
|
638
|
+
// 新相机落地即活动(用户刚加的就是他想看的)
|
|
639
|
+
setActiveCameraKey(entry.cameraKey);
|
|
640
|
+
}, [cameras, sheet]);
|
|
641
|
+
// 添加锚点:落点由 captureAnchorSeed 计算(导演视角前方约 5 单位)
|
|
642
|
+
const addAnchor = useCallback(() => {
|
|
643
|
+
const key = nextAnchorKey(anchors);
|
|
644
|
+
addAnchorToRegistry(sheet, key, captureAnchorSeed(cameraObjRef.current));
|
|
645
|
+
}, [anchors, sheet]);
|
|
646
|
+
// 大纲右键删除走宿主注册表删除流程(与画布右键同一套真删);
|
|
647
|
+
// extend 按 id 幂等覆盖,重复执行安全
|
|
648
|
+
useEffect(() => {
|
|
649
|
+
getStudioSync()?.extend({
|
|
650
|
+
id: cameraOutlineMenuExtensionId,
|
|
651
|
+
outlineObjectMenu: cameraOutlineObjectMenu,
|
|
652
|
+
});
|
|
653
|
+
}, []);
|
|
654
|
+
// 每渲染刷新菜单上下文:注册表清单 + 删除处理器(领域删除 + 会话
|
|
655
|
+
// 清理都走 afterSceneObjectDeleted 抽出的共用实现,与画布右键一致)。
|
|
656
|
+
// CameraScene 不卸载;卸载时清 ctx,防止悬空 sheet 引用
|
|
657
|
+
useEffect(() => {
|
|
658
|
+
setCameraOutlineMenuCtx({
|
|
659
|
+
sheet,
|
|
660
|
+
cameras,
|
|
661
|
+
anchors,
|
|
662
|
+
placements: readPlacementEntries(sheet),
|
|
663
|
+
paths,
|
|
664
|
+
handlers: {
|
|
665
|
+
removeCamera: (entry) => {
|
|
666
|
+
deleteCamera(entry);
|
|
667
|
+
},
|
|
668
|
+
removeAnchor: (key) => {
|
|
669
|
+
removeAnchor(sheet, key, collectAnchorChildResets(sheet, key));
|
|
670
|
+
afterSceneObjectDeleted('anchor', key);
|
|
671
|
+
},
|
|
672
|
+
removePlacement: (key) => {
|
|
673
|
+
deletePlacement(sheet, key);
|
|
674
|
+
afterSceneObjectDeleted('model', key);
|
|
675
|
+
},
|
|
676
|
+
removePath: (pathKey) => {
|
|
677
|
+
removePath(pathKey);
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
return () => setCameraOutlineMenuCtx(undefined);
|
|
682
|
+
});
|
|
683
|
+
// 无相机:镜头视角强制退出(没有镜头可看)
|
|
684
|
+
useEffect(() => {
|
|
685
|
+
if (!activeCamera && shotView && !exporting)
|
|
686
|
+
setShotView(false);
|
|
687
|
+
}, [activeCamera, shotView, exporting]);
|
|
688
|
+
// 导出锁定开录时的相机;它中途被删(本地/对端)就中止录制(丢弃)
|
|
689
|
+
const exportCameraKeyRef = useRef(null);
|
|
690
|
+
useEffect(() => {
|
|
691
|
+
if (exporting &&
|
|
692
|
+
!cameras.some((e) => e.cameraKey === exportCameraKeyRef.current)) {
|
|
693
|
+
exportHandleRef.current?.abort();
|
|
694
|
+
}
|
|
695
|
+
}, [exporting, cameras]);
|
|
696
|
+
// 场景导出控制器(Task 13):活动相机校验、ShotView 切换/恢复、canvas
|
|
697
|
+
// 获取、录制与异常恢复的共享核心在 agent/runtime/exportRuntime,UI 导出
|
|
698
|
+
// 菜单与 Agent exports.startVideo 经它走同一流程。getter 经 ref 读最新
|
|
699
|
+
// 会话态,控制器本身随 sheet 重建
|
|
700
|
+
const camerasForExportRef = useRef(cameras);
|
|
701
|
+
camerasForExportRef.current = cameras;
|
|
702
|
+
const activeCameraForExportRef = useRef(activeCamera);
|
|
703
|
+
activeCameraForExportRef.current = activeCamera;
|
|
704
|
+
const shotViewForExportRef = useRef(shotView);
|
|
705
|
+
shotViewForExportRef.current = shotView;
|
|
706
|
+
const exportingRef = useRef(exporting);
|
|
707
|
+
exportingRef.current = exporting;
|
|
708
|
+
const exportController = useMemo(() => createSceneExportController({
|
|
709
|
+
sheet,
|
|
710
|
+
getActiveCameraKey: () => activeCameraForExportRef.current?.cameraKey ?? null,
|
|
711
|
+
hasCamera: (cameraKey) => camerasForExportRef.current.some((e) => e.cameraKey === cameraKey),
|
|
712
|
+
setActiveCamera: (cameraKey) => setActiveCameraKey(cameraKey),
|
|
713
|
+
isExporting: () => exportingRef.current,
|
|
714
|
+
getShotView: () => shotViewForExportRef.current,
|
|
715
|
+
setShotView,
|
|
716
|
+
// 页面上只有一个 canvas(快照编辑器的在 shadow DOM 里,查不到)
|
|
717
|
+
getCanvas: () => document.querySelector('canvas'),
|
|
718
|
+
setExporting,
|
|
719
|
+
setActiveHandle: (handle) => {
|
|
720
|
+
exportHandleRef.current = handle;
|
|
721
|
+
},
|
|
722
|
+
lockExportCamera: (cameraKey) => {
|
|
723
|
+
exportCameraKeyRef.current = cameraKey;
|
|
724
|
+
},
|
|
725
|
+
}), [sheet]);
|
|
726
|
+
// 登记给 Agent exports.startVideo(getSceneExportController);卸载先
|
|
727
|
+
// dispose(abort 在录任务 → 恢复视图 → 拒绝未完成任务)再解绑,
|
|
728
|
+
// 防过期 cleanup 抹掉新代次登记由 register 的同一引用校验兜底
|
|
729
|
+
useEffect(() => {
|
|
730
|
+
const unregister = registerSceneExportController(exportController);
|
|
731
|
+
return () => {
|
|
732
|
+
exportController.dispose();
|
|
733
|
+
unregister();
|
|
734
|
+
};
|
|
735
|
+
}, [exportController]);
|
|
736
|
+
// 运镜路径编辑模式:正在编辑的路径 metaKey(同一时刻只编辑一条,
|
|
737
|
+
// 对齐 Blender 的对象/编辑模式);右键路径线或选中路径后按 Tab 进出
|
|
738
|
+
const [editingPath, setEditingPath] = useState(null);
|
|
739
|
+
const editingPathRef = useRef(null);
|
|
740
|
+
editingPathRef.current = editingPath;
|
|
741
|
+
// 选中的控制点(内部选中,和 studio 选区无关;控制点不是 theatre 对象)
|
|
742
|
+
const [selectedPathPoint, setSelectedPathPoint] = useState(null);
|
|
743
|
+
// 右键路径线弹出的菜单:记录路径和光标位置
|
|
744
|
+
const [pathMenu, setPathMenu] = useState(null);
|
|
745
|
+
// 右键场景摄像机弹出的菜单:记录是哪台相机和光标位置
|
|
746
|
+
const [cameraMenu, setCameraMenu] = useState(null);
|
|
747
|
+
// 右键锚点弹出的菜单:记录是哪个锚点和光标位置(Anchors 组件经
|
|
748
|
+
// pg:anchor-context-menu 自定义事件透传,避免层层 props)
|
|
749
|
+
const [anchorMenu, setAnchorMenu] = useState(null);
|
|
750
|
+
useEffect(() => {
|
|
751
|
+
const handler = (e) => {
|
|
752
|
+
const { key, x, y } = e.detail;
|
|
753
|
+
setAnchorMenu({ key, x, y });
|
|
754
|
+
};
|
|
755
|
+
window.addEventListener('pg:anchor-context-menu', handler);
|
|
756
|
+
return () => window.removeEventListener('pg:anchor-context-menu', handler);
|
|
757
|
+
}, []);
|
|
758
|
+
window.__cameraShot = {
|
|
759
|
+
cameraObjRef,
|
|
760
|
+
targetObjRef,
|
|
761
|
+
shotRefs: shotRefsMapRef.current,
|
|
762
|
+
activeKey: () => activeCamera?.cameraKey ?? null,
|
|
763
|
+
};
|
|
764
|
+
window.__undoDebug = {
|
|
765
|
+
stack: () => {
|
|
766
|
+
const s = getStudio();
|
|
767
|
+
const saaz = s?._store?._saaz ?? s?._saaz;
|
|
768
|
+
return saaz?._atom?.get?.()?.undoRedo?.stack ?? 'no saaz';
|
|
769
|
+
},
|
|
770
|
+
len: () => {
|
|
771
|
+
const st = window.__undoDebug.stack();
|
|
772
|
+
return Array.isArray(st) ? st.length : st;
|
|
773
|
+
},
|
|
774
|
+
top: () => {
|
|
775
|
+
const st = window.__undoDebug.stack();
|
|
776
|
+
return Array.isArray(st) ? st[0] ?? null : st;
|
|
777
|
+
},
|
|
778
|
+
};
|
|
779
|
+
window.__saaz = () => {
|
|
780
|
+
const s = getStudio();
|
|
781
|
+
return s?._store?._saaz ?? s?._saaz ?? 'no saaz';
|
|
782
|
+
};
|
|
783
|
+
const { placing, pendingEntry, startPlacingFromLibrary, placeLibraryEntryAt, cancelPlacing, confirmPlacing, clearPending, } = usePlacementController(project, sheet);
|
|
784
|
+
const [gizmoMode, setGizmoMode] = useGizmoMode();
|
|
785
|
+
// 正在摆姿势的 placementKey(无会话为 null):Esc/双击空白/播放自动退出都靠它
|
|
786
|
+
const poseSessionKey = usePoseSessionKey();
|
|
787
|
+
const [frameOpen, setFrameOpen] = useState({ model: false, pose: false, animation: false, path: false });
|
|
788
|
+
const openFrame = useCallback((kind) => setFrameOpen((f) => ({ ...f, [kind]: true })), []);
|
|
789
|
+
const toggleFrame = useCallback((kind) => setFrameOpen((f) => ({ ...f, [kind]: !f[kind] })), []);
|
|
790
|
+
const closeFrame = useCallback((kind) => setFrameOpen((f) => (f[kind] ? { ...f, [kind]: false } : f)), []);
|
|
791
|
+
// 点击模型卡片 = 立即放到默认位(原点被占沿 +X 自动错开);要指定位置用拖拽
|
|
792
|
+
const placeModelAtDefault = useCallback((entry) => {
|
|
793
|
+
// placeLibraryEntryAt 返回 {key, persisted}(Task 8,Agent
|
|
794
|
+
// models.create 的挂载等待句柄);UI 路径不等挂载完成——失败由
|
|
795
|
+
// PlacedModelErrorBoundary 上告,persisted 的 rejection 已在
|
|
796
|
+
// placeLibraryEntryAt 内兜底,这里只触发放置、忽略返回句柄
|
|
797
|
+
placeLibraryEntryAt(entry, findFreeSpot(readPlacementPositions(sheet)));
|
|
798
|
+
}, [sheet, placeLibraryEntryAt]);
|
|
799
|
+
// 拖拽态:模型拖拽复用 placing 幽灵(按下即进 placing,松手即 confirm);
|
|
800
|
+
// 路径预设拖拽挂落点标记
|
|
801
|
+
const [modelDragging, setModelDragging] = useState(false);
|
|
802
|
+
const [presetDrag, setPresetDrag] = useState(null);
|
|
803
|
+
const startModelDrag = useCallback((entry) => {
|
|
804
|
+
setModelDragging(true);
|
|
805
|
+
startPlacingFromLibrary(entry).catch((err) => {
|
|
806
|
+
console.warn('[placement] 库模型加载失败', err);
|
|
807
|
+
setModelDragging(false);
|
|
808
|
+
});
|
|
809
|
+
}, [startPlacingFromLibrary]);
|
|
810
|
+
// 右键已放置模型弹出的操作菜单:记录目标条目和光标位置
|
|
811
|
+
const [objectMenu, setObjectMenu] = useState(null);
|
|
812
|
+
// 菜单关闭回调:稳定的引用,避免 CanvasContextMenu 不必要的重渲染
|
|
813
|
+
const closeObjectMenu = useCallback(() => setObjectMenu(null), []);
|
|
814
|
+
const closePathMenu = useCallback(() => setPathMenu(null), []);
|
|
815
|
+
const closeCameraMenu = useCallback(() => setCameraMenu(null), []);
|
|
816
|
+
const closeAnchorMenu = useCallback(() => setAnchorMenu(null), []);
|
|
817
|
+
// 对象删除后的会话清理(2026-10-19,Task 14 从各删除流程抽出共用;
|
|
818
|
+
// Agent objects.delete 经 runtime handler 调同一实现):项目数据已由
|
|
819
|
+
// 领域删除函数在单个事务里清完,这里只收会话态——
|
|
820
|
+
// - 路径编辑态/控制点选择随被删路径清掉(失焦式退出:不回头重选,
|
|
821
|
+
// 对象已经没了,重选会把它又建出来);
|
|
822
|
+
// - 还指着被删对象的右键菜单关掉;
|
|
823
|
+
// - 大纲伪集合(收藏/分组名单)里摘掉该对象;
|
|
824
|
+
// - studio 选区里摘除被删对象(仅当它还在选区里,其余选择保留——
|
|
825
|
+
// 注册表行已删但组件尚未卸载的窗口里,选区不得留着已失效对象)。
|
|
826
|
+
// 活动相机回退/镜头视角强制退出/导出中止由推导与看护 effect 自动完成。
|
|
827
|
+
const afterSceneObjectDeleted = useCallback((kind, key) => {
|
|
828
|
+
const studio = getStudioSync();
|
|
829
|
+
const mounted = getSceneSheetObject(formatSceneObjectId(kind, key));
|
|
830
|
+
if (studio &&
|
|
831
|
+
mounted &&
|
|
832
|
+
studio.selection.includes(mounted)) {
|
|
833
|
+
studio.setSelection(studio.selection.filter((item) => item !== mounted));
|
|
834
|
+
}
|
|
835
|
+
if (kind === 'path') {
|
|
836
|
+
if (editingPathRef.current === key) {
|
|
837
|
+
setEditingPath(null);
|
|
838
|
+
setSelectedPathPoint(null);
|
|
839
|
+
}
|
|
840
|
+
setSelectedPathPoint((sel) => (sel?.metaKey === key ? null : sel));
|
|
841
|
+
}
|
|
842
|
+
setPathMenu((m) => (m?.metaKey === key ? null : m));
|
|
843
|
+
setCameraMenu((m) => (m?.cameraKey === key ? null : m));
|
|
844
|
+
setAnchorMenu((m) => (m?.key === key ? null : m));
|
|
845
|
+
setObjectMenu((m) => (m?.key === key ? null : m));
|
|
846
|
+
const { projectId, sheetId, sheetInstanceId } = sheet.address;
|
|
847
|
+
pruneObjectFromOutlineCollections(projectId, sheetId, sheetInstanceId, key);
|
|
848
|
+
}, [sheet]);
|
|
849
|
+
// 删除相机(右键菜单):真删——持久数据清掉、大纲行消失;
|
|
850
|
+
// 活动相机被删时 activeCamera 推导自动顺位给清单第一台
|
|
851
|
+
const deleteCamera = useCallback((entry) => {
|
|
852
|
+
removeCamera(sheet, entry);
|
|
853
|
+
afterSceneObjectDeleted('camera', entry.cameraKey);
|
|
854
|
+
}, [sheet, afterSceneObjectDeleted]);
|
|
855
|
+
// 导出目标在运行环境稳定后不再变化,useMemo 避免每次渲染重建数组
|
|
856
|
+
const exportSinks = useMemo(() => listSinks(), []);
|
|
857
|
+
// 导出错误提示通道:有宿主 onError 走宿主,否则 alert(既有行为)
|
|
858
|
+
const reportExportError = useCallback((message) => {
|
|
859
|
+
if (onError) {
|
|
860
|
+
onError({ source: 'export', code: 'unknown', message });
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
window.alert(message);
|
|
864
|
+
}
|
|
865
|
+
}, [onError]);
|
|
866
|
+
const handlePickExportSink = useCallback((sink) => {
|
|
867
|
+
// UI 菜单与 Agent 共用同一场景导出流程;前置校验失败(无活动相机/
|
|
868
|
+
// 已在导出)同步抛,录制中途失败由控制器经 onError 透传(同既有行为)
|
|
869
|
+
try {
|
|
870
|
+
exportController.startVideo({ sink, onError: reportExportError }).catch(() => { });
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
reportExportError(err instanceof Error ? err.message : String(err));
|
|
874
|
+
}
|
|
875
|
+
}, [exportController, reportExportError]);
|
|
876
|
+
const handleStopExport = useCallback(() => exportHandleRef.current?.finishEarly(), []);
|
|
877
|
+
// 跟踪 studio 选中状态,用于动作 tab 的套用与过滤
|
|
878
|
+
const [selectedPlacement, setSelectedPlacement] = useState(null);
|
|
879
|
+
const [selectionCount, setSelectionCount] = useState(0);
|
|
880
|
+
const [selectedRig, setSelectedRig] = useState(undefined);
|
|
881
|
+
const [rigLoading, setRigLoading] = useState(false);
|
|
882
|
+
useEffect(() => {
|
|
883
|
+
const studio = getStudioSync();
|
|
884
|
+
if (!studio)
|
|
885
|
+
return;
|
|
886
|
+
const sync = () => {
|
|
887
|
+
const placement = studio.selection.find((item) => getPlacementEntryForObject(item));
|
|
888
|
+
setSelectedPlacement(placement ?? null);
|
|
889
|
+
setSelectionCount(studio.selection.length);
|
|
890
|
+
// 选中了 theatre 对象 → 控制点取消选中(两套手柄不同时出现)
|
|
891
|
+
if (studio.selection.length > 0)
|
|
892
|
+
setSelectedPathPoint(null);
|
|
893
|
+
// 编辑中的路径失焦(选中切到了别的对象)→ 退出编辑模式,对齐
|
|
894
|
+
// Blender(编辑模式跟着活动物体走);尊重用户的新选中,不回头
|
|
895
|
+
// 重选路径。空选中不退:进入编辑和点选控制点时选中本就是空的
|
|
896
|
+
const editing = editingPathRef.current;
|
|
897
|
+
if (editing &&
|
|
898
|
+
studio.selection.length > 0 &&
|
|
899
|
+
!studio.selection.includes(getPathMeta(sheet, editing))) {
|
|
900
|
+
setEditingPath(null);
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
sync();
|
|
904
|
+
return studio.onSelectionChange(sync);
|
|
905
|
+
}, [sheet]);
|
|
906
|
+
// 路径注册表清单:每条已注册路径一个 CameraPath + 一套面板动作
|
|
907
|
+
const paths = usePathsList(sheet);
|
|
908
|
+
// 进入/退出路径编辑模式(右键菜单和 Tab 共用)
|
|
909
|
+
const enterPathEdit = useCallback((metaKey) => {
|
|
910
|
+
setEditingPath(metaKey);
|
|
911
|
+
// 编辑路径时清掉对象选中:对象 gizmo 和控制点 gizmo 不同时出现
|
|
912
|
+
getStudioSync()?.setSelection([]);
|
|
913
|
+
}, []);
|
|
914
|
+
const exitPathEdit = useCallback(() => {
|
|
915
|
+
const editing = editingPathRef.current;
|
|
916
|
+
setEditingPath(null);
|
|
917
|
+
setSelectedPathPoint(null);
|
|
918
|
+
// 退出编辑后重新选中路径本体(对齐 Blender:Tab 出编辑模式物体保持
|
|
919
|
+
// 选中),整体移动箭头随之出现
|
|
920
|
+
if (editing)
|
|
921
|
+
getStudioSync()?.setSelection([getPathMeta(sheet, editing)]);
|
|
922
|
+
}, [sheet]);
|
|
923
|
+
// 失焦式退出(点空白/选中切走):尊重现场,不回头重选路径
|
|
924
|
+
const exitPathEditOnBlur = useCallback(() => {
|
|
925
|
+
setEditingPath(null);
|
|
926
|
+
setSelectedPathPoint(null);
|
|
927
|
+
}, []);
|
|
928
|
+
const isPathEditing = useCallback(() => editingPathRef.current !== null, []);
|
|
929
|
+
// Tab 进出编辑模式(Blender 同款);输入框里不抢 Tab(留给焦点跳转)
|
|
930
|
+
useEffect(() => {
|
|
931
|
+
const onKey = (e) => {
|
|
932
|
+
if (e.key !== 'Tab')
|
|
933
|
+
return;
|
|
934
|
+
const target = e.target;
|
|
935
|
+
if (target &&
|
|
936
|
+
(target.tagName === 'INPUT' ||
|
|
937
|
+
target.tagName === 'TEXTAREA' ||
|
|
938
|
+
target.isContentEditable)) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
e.preventDefault();
|
|
942
|
+
if (editingPathRef.current) {
|
|
943
|
+
exitPathEdit();
|
|
944
|
+
}
|
|
945
|
+
else if (!shotView) {
|
|
946
|
+
// Tab 只在选中路径时进入编辑模式(Blender:Tab 作用于选中物;
|
|
947
|
+
// 没选中就什么都不做,避免凭空进入一个看不见的编辑态)
|
|
948
|
+
const studio = getStudioSync();
|
|
949
|
+
// 选中注册表里任意一条路径的本体 → Tab 进编辑(Blender:Tab 作用于
|
|
950
|
+
// 选中物)。先探测再取句柄:getPathMeta 查不存在会顺手创建
|
|
951
|
+
const selectedPath = studio &&
|
|
952
|
+
paths.find((e) => pathExists(sheet, e.pathKey) &&
|
|
953
|
+
studio.selection.includes(getPathMeta(sheet, e.pathKey)))?.pathKey;
|
|
954
|
+
if (selectedPath)
|
|
955
|
+
enterPathEdit(selectedPath);
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
window.addEventListener('keydown', onKey);
|
|
959
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
960
|
+
}, [sheet, shotView, enterPathEdit, exitPathEdit, paths]);
|
|
961
|
+
// 切镜头视角自动退出编辑模式(路径在镜头视角本就全部隐藏)
|
|
962
|
+
useEffect(() => {
|
|
963
|
+
if (shotView)
|
|
964
|
+
exitPathEdit();
|
|
965
|
+
}, [shotView, exitPathEdit]);
|
|
966
|
+
// 调试/自动化验证挂钩:读当前编辑态、直接进出编辑模式、选中控制点
|
|
967
|
+
// (点选 3D 小目标在自动化下太脆——相机一飘就点空,选中路径走这里)
|
|
968
|
+
useEffect(() => {
|
|
969
|
+
;
|
|
970
|
+
window.__cameraPathEdit = {
|
|
971
|
+
enter: enterPathEdit,
|
|
972
|
+
exit: exitPathEdit,
|
|
973
|
+
current: () => editingPathRef.current,
|
|
974
|
+
selectPoint: (metaKey, key) => setSelectedPathPoint(key ? { metaKey, key } : null),
|
|
975
|
+
keyframes: () => progressKeyframesRef.current,
|
|
976
|
+
};
|
|
977
|
+
}, [enterPathEdit, exitPathEdit]);
|
|
978
|
+
// 路径预设放置:点击 = 场景中心(高度跟活动相机当前 Y,无相机跟主视角,
|
|
979
|
+
// 再退回 5);拖拽 = 指针落点的完整三维(视平面/表面吸附算出,见
|
|
980
|
+
// PathPresetDropRig)。先播种再登记(没点就不出现),落地即进编辑模式。
|
|
981
|
+
// 每一步都是既有可撤销事务,撤销 = 清单行消失 + 点数据回滚
|
|
982
|
+
const pathPresetHeight = useCallback(() => {
|
|
983
|
+
const camObj = activeCamera
|
|
984
|
+
? shotRefsMapRef.current.get(activeCamera.cameraKey)?.camera.current
|
|
985
|
+
: undefined;
|
|
986
|
+
return ((camObj ? val(camObj.props.position).y : undefined) ??
|
|
987
|
+
(cameraObjRef.current
|
|
988
|
+
? val(cameraObjRef.current.props.position).y
|
|
989
|
+
: 5));
|
|
990
|
+
}, [activeCamera]);
|
|
991
|
+
const placePresetPath = useCallback((preset, at) => {
|
|
992
|
+
// 落点三维都进来:拖拽放置的高度由视平面/表面吸附决定(所见即所得),
|
|
993
|
+
// 点击放置没有落点,高度退回活动相机当前 Y
|
|
994
|
+
const center = {
|
|
995
|
+
x: at?.x ?? 0,
|
|
996
|
+
y: at?.y ?? pathPresetHeight(),
|
|
997
|
+
z: at?.z ?? 0,
|
|
998
|
+
};
|
|
999
|
+
const entry = nextPathEntry(readPathEntries(getPathsRegistry(sheet)));
|
|
1000
|
+
// 注册表说 key 空着,但同名路径可能带着旧点:撤销只退登记没退播种、
|
|
1001
|
+
// 或同步中断的部分持久化,都会留这种孤儿(启动自检只查 legacy 的
|
|
1002
|
+
// "Pos Path" 一个 key;且跨会话孤儿的值在持久态里,对象表探测
|
|
1003
|
+
// 根本看不到)。不真删就播种,addPathPoint 会把旧点当既有路径
|
|
1004
|
+
// 延伸,新路径落在旧位置(中点偏差)——注册表是唯一事实源,
|
|
1005
|
+
// 取回对象看实际点数,有点就是孤儿,真删再播种
|
|
1006
|
+
if (readPathPoints(sheet, entry.pathKey).length > 0) {
|
|
1007
|
+
removePathFromRegistry(sheet, entry);
|
|
1008
|
+
}
|
|
1009
|
+
// 落点 = 路径原点:控制点存相对落点的局部坐标,落点本身在同一次
|
|
1010
|
+
// 提交里写进位置变换——原点标记/整体移动箭头锚在落点(所见即所得),
|
|
1011
|
+
// 世界位置与直接世界坐标播种等价
|
|
1012
|
+
const { points, position } = presetPlacement(preset, center);
|
|
1013
|
+
addPathPoint(sheet, entry.pathKey, points, (set) => {
|
|
1014
|
+
set(getPathMeta(sheet, entry.pathKey).props.position, position);
|
|
1015
|
+
});
|
|
1016
|
+
addPathToRegistry(sheet, entry);
|
|
1017
|
+
enterPathEdit(entry.pathKey);
|
|
1018
|
+
}, [sheet, pathPresetHeight, enterPathEdit]);
|
|
1019
|
+
// 删除整条路径:领域函数在 agent/pathCommands(deletePathDomain,
|
|
1020
|
+
// 最后一点删除与 objects.delete 共用同一清理);UI 侧传入自己按 refs
|
|
1021
|
+
// 收集的绑定相机回退(既有行为逐行保留)。会话清理(编辑态/选中点/
|
|
1022
|
+
// 菜单/大纲集合/选区)走 afterSceneObjectDeleted 的共用实现
|
|
1023
|
+
const removePath = useCallback((metaKey) => {
|
|
1024
|
+
deletePathDomain(sheet, metaKey, {
|
|
1025
|
+
boundCameras: collectBoundCameraResets(sheet, shotRefsMapRef.current, metaKey),
|
|
1026
|
+
});
|
|
1027
|
+
afterSceneObjectDeleted('path', metaKey);
|
|
1028
|
+
}, [sheet, afterSceneObjectDeleted]);
|
|
1029
|
+
const deleteSelectedPathPoint = useCallback(() => {
|
|
1030
|
+
if (!selectedPathPoint)
|
|
1031
|
+
return;
|
|
1032
|
+
// 删掉最后一个点 = 删除整条路径("没点就不出现")
|
|
1033
|
+
if (readPathPoints(sheet, selectedPathPoint.metaKey).length <= 1) {
|
|
1034
|
+
removePath(selectedPathPoint.metaKey);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (deletePathPoint(sheet, selectedPathPoint.metaKey, selectedPathPoint.key)) {
|
|
1038
|
+
setSelectedPathPoint(null);
|
|
1039
|
+
}
|
|
1040
|
+
}, [sheet, selectedPathPoint, removePath]);
|
|
1041
|
+
// 启动自检(只在挂载时跑一次):对象在但注册表没有 = 孤儿(老结构
|
|
1042
|
+
// 残留的空路径对象),真删——"没点就不出现"。legacy 只查 Pos Path 一个
|
|
1043
|
+
// key。用非创建式探测 pathExists + 现场重读注册表,不用渲染期快照:
|
|
1044
|
+
// 响应式写法会在"添加路径后对象先于注册表行出现"的瞬间误判刚加的
|
|
1045
|
+
// 路径为孤儿并把它删掉
|
|
1046
|
+
useEffect(() => {
|
|
1047
|
+
if (pathExists(sheet, POS_PATH_KEY) &&
|
|
1048
|
+
!readPathEntries(getPathsRegistry(sheet)).some((e) => e.pathKey === POS_PATH_KEY)) {
|
|
1049
|
+
removePathFromRegistry(sheet, { pathKey: POS_PATH_KEY });
|
|
1050
|
+
}
|
|
1051
|
+
}, [sheet]);
|
|
1052
|
+
// 路径被移除(含对端同步删除)时,它的编辑态/选中点一并清掉。
|
|
1053
|
+
// 判定加"对象也没了":添加路径时对象先于注册表行出现(播种在登记
|
|
1054
|
+
// 之前),只看清单会在那个瞬间误退刚进入的编辑态
|
|
1055
|
+
useEffect(() => {
|
|
1056
|
+
if (editingPath &&
|
|
1057
|
+
!paths.some((e) => e.pathKey === editingPath) &&
|
|
1058
|
+
!pathExists(sheet, editingPath)) {
|
|
1059
|
+
exitPathEditOnBlur();
|
|
1060
|
+
}
|
|
1061
|
+
}, [sheet, editingPath, paths, exitPathEditOnBlur]);
|
|
1062
|
+
// 正在编辑的路径对象(钉帧/关键帧列表都按它算;对象不存在为 null)
|
|
1063
|
+
const editingPathExists = usePathExists(sheet, editingPath ?? '');
|
|
1064
|
+
const editingPathMeta = useMemo(() => editingPath && editingPathExists ? getPathMeta(sheet, editingPath) : null, [sheet, editingPath, editingPathExists]);
|
|
1065
|
+
// 每条路径的进度读取(进度标记用):按 key 读对应路径对象,
|
|
1066
|
+
// 对象不存在时报 0(共享进度:进度只在路径对象上)
|
|
1067
|
+
const pathProgressOf = useCallback((pathKey) => () => pathExists(sheet, pathKey)
|
|
1068
|
+
? val(getPathMeta(sheet, pathKey).props.progress)
|
|
1069
|
+
: 0, [sheet]);
|
|
1070
|
+
// 绑定到活动相机:只写 pathKey(绑定的单一直径,见 pathBinding.ts);
|
|
1071
|
+
// 这里是无相机面板时的入口(路径右键菜单)
|
|
1072
|
+
const bindPathToActiveCamera = useCallback((pathKey) => {
|
|
1073
|
+
const studio = getStudioSync();
|
|
1074
|
+
const cam = activeCamera
|
|
1075
|
+
? shotRefsMapRef.current.get(activeCamera.cameraKey)?.camera.current
|
|
1076
|
+
: undefined;
|
|
1077
|
+
if (!studio || !cam)
|
|
1078
|
+
return;
|
|
1079
|
+
const scrub = studio.scrub();
|
|
1080
|
+
scrub.capture(({ set }) => {
|
|
1081
|
+
set(cam.props.pathKey, pathKey);
|
|
1082
|
+
});
|
|
1083
|
+
scrub.commit();
|
|
1084
|
+
}, [activeCamera]);
|
|
1085
|
+
// 钉到播放头:把控制点的抵达时间变成时间轴上的标准关键帧。
|
|
1086
|
+
// 首钉先把路径的行进进度转成序列轨道(之后 set 写的就是播放头处的
|
|
1087
|
+
// 关键帧,属性面板的菱形和 theatre 默认行为一致),再在当前播放头打
|
|
1088
|
+
// 值=锚点的关键帧;拖关键帧 = 改抵达时间,删关键帧 = 解钉,撤销照常
|
|
1089
|
+
const pinPathPointAtPlayhead = useCallback((u) => {
|
|
1090
|
+
const studio = getStudioSync();
|
|
1091
|
+
if (!studio || !editingPathMeta)
|
|
1092
|
+
return;
|
|
1093
|
+
const hasTrack = typeof getDeep(privateAPI(editingPathMeta)
|
|
1094
|
+
.template.getMapOfValidSequenceTracks_forStudio()
|
|
1095
|
+
.getValue(), ['progress']) === 'string';
|
|
1096
|
+
if (!hasTrack) {
|
|
1097
|
+
studio.transaction(({ sequenceProp }) => sequenceProp(editingPathMeta.props.progress));
|
|
1098
|
+
}
|
|
1099
|
+
const scrub = studio.scrub();
|
|
1100
|
+
scrub.capture(({ set }) => set(editingPathMeta.props.progress, u));
|
|
1101
|
+
scrub.commit();
|
|
1102
|
+
}, [editingPathMeta]);
|
|
1103
|
+
// 行进进度轨道的关键帧(响应式:打帧/拖帧/删帧/撤销都反映),
|
|
1104
|
+
// 路径浮条用它列出选中控制点钉在了哪些时间
|
|
1105
|
+
const progressKeyframes = usePrism(() => editingPathMeta
|
|
1106
|
+
? privateAPI(editingPathMeta)
|
|
1107
|
+
.sheet.getSequence()
|
|
1108
|
+
.getKeyframesOfSimpleProp(editingPathMeta.props.progress)
|
|
1109
|
+
.map((k) => ({ position: k.position, value: k.value }))
|
|
1110
|
+
: [], [editingPathMeta]);
|
|
1111
|
+
// 调试挂钩读最新值用(上面 __cameraPathEdit 的 effect 声明在前,直引会 TDZ)
|
|
1112
|
+
const progressKeyframesRef = useRef(progressKeyframes);
|
|
1113
|
+
progressKeyframesRef.current = progressKeyframes;
|
|
1114
|
+
// 导出开录时机:先切到镜头视角(录 canvas 当前内容),ShotView/活动
|
|
1115
|
+
// 相机切换生效(本副作用)后通知场景导出控制器开录;无挂起导出请求时
|
|
1116
|
+
// 静默 no-op。流程主体(校验/canvas/录制/恢复)在 exportRuntime
|
|
1117
|
+
useEffect(() => {
|
|
1118
|
+
if (!shotView)
|
|
1119
|
+
return;
|
|
1120
|
+
exportController.notifySceneReady();
|
|
1121
|
+
}, [shotView, activeCamera, exportController]);
|
|
1122
|
+
// 导出中按 Esc = 取消(丢弃已录内容)
|
|
1123
|
+
useEffect(() => {
|
|
1124
|
+
if (!exporting)
|
|
1125
|
+
return;
|
|
1126
|
+
const onKey = (e) => {
|
|
1127
|
+
if (e.key === 'Escape')
|
|
1128
|
+
exportHandleRef.current?.abort();
|
|
1129
|
+
};
|
|
1130
|
+
window.addEventListener('keydown', onKey);
|
|
1131
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
1132
|
+
}, [exporting]);
|
|
1133
|
+
// 镜头视角下 Esc 返回导演视角(导出中 Esc 是取消导出,见上面的导出 effect;
|
|
1134
|
+
// 两个 effect 不共存:导出中 exporting=true 时本 effect 不挂)
|
|
1135
|
+
useEffect(() => {
|
|
1136
|
+
if (!shotView || exporting)
|
|
1137
|
+
return;
|
|
1138
|
+
const onKey = (e) => {
|
|
1139
|
+
if (e.key === 'Escape')
|
|
1140
|
+
setShotView(false);
|
|
1141
|
+
};
|
|
1142
|
+
window.addEventListener('keydown', onKey);
|
|
1143
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
1144
|
+
}, [shotView, exporting]);
|
|
1145
|
+
// 窗口级撤销/重做(spec §8.3):画布焦点下 Ctrl+Z 也能撤,直达 studio 历史
|
|
1146
|
+
useEffect(() => {
|
|
1147
|
+
const studio = getStudioSync();
|
|
1148
|
+
if (!studio)
|
|
1149
|
+
return;
|
|
1150
|
+
return bindUndoShortcuts(() => studio.undo(), () => studio.redo());
|
|
1151
|
+
}, []);
|
|
1152
|
+
// 姿势模式下 Esc/Enter 退出(提交);导出中/镜头视角的 Esc 语义优先,不抢
|
|
1153
|
+
useEffect(() => {
|
|
1154
|
+
if (!poseSessionKey || shotView || exporting)
|
|
1155
|
+
return;
|
|
1156
|
+
const onKey = (e) => {
|
|
1157
|
+
if (e.key !== 'Escape' && e.key !== 'Enter')
|
|
1158
|
+
return;
|
|
1159
|
+
// Enter 在交互元素(输入框/按钮/链接等)里是控件自身语义——按钮
|
|
1160
|
+
// Enter=click,会在保存帧之后误触按钮处理器(打帧/清除把刚存的帧
|
|
1161
|
+
// 覆盖或删轨);studio UI 内的事件也不触发(shadow DOM retarget,
|
|
1162
|
+
// e.target 失真,交给 studio 自己处理)。Esc 无文本副作用,不用守
|
|
1163
|
+
// (spec §8.1 终审 F3)
|
|
1164
|
+
if (e.key === 'Enter') {
|
|
1165
|
+
if (isPoseExitGuardedTarget(e.target))
|
|
1166
|
+
return;
|
|
1167
|
+
if (isInsideStudioUi(e))
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
requestPoseExit();
|
|
1171
|
+
};
|
|
1172
|
+
window.addEventListener('keydown', onKey);
|
|
1173
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
1174
|
+
}, [poseSessionKey, shotView, exporting]);
|
|
1175
|
+
// 双击空白退出姿势模式:落在模型上的双击由模型自己处理(进入/切换);
|
|
1176
|
+
// 落在控制球/被摆模型上不算空白(poseHitTest);只认画布,UI 面板里的
|
|
1177
|
+
// 双击不退出
|
|
1178
|
+
useEffect(() => {
|
|
1179
|
+
if (!poseSessionKey)
|
|
1180
|
+
return;
|
|
1181
|
+
const onDblClick = (e) => {
|
|
1182
|
+
if (!(e.target instanceof HTMLCanvasElement))
|
|
1183
|
+
return;
|
|
1184
|
+
if (isModelDoubleClick(e.timeStamp))
|
|
1185
|
+
return;
|
|
1186
|
+
if (poseHitTest(e))
|
|
1187
|
+
return;
|
|
1188
|
+
requestPoseExit();
|
|
1189
|
+
};
|
|
1190
|
+
window.addEventListener('dblclick', onDblClick);
|
|
1191
|
+
return () => window.removeEventListener('dblclick', onDblClick);
|
|
1192
|
+
}, [poseSessionKey]);
|
|
1193
|
+
// 序列开始播放 = mixer 接管骨骼,姿势模式自动提交退出
|
|
1194
|
+
useEffect(() => {
|
|
1195
|
+
return onChange(sheet.sequence.pointer.playing, (playing) => {
|
|
1196
|
+
if (playing && getPoseSession())
|
|
1197
|
+
requestPoseExit();
|
|
1198
|
+
});
|
|
1199
|
+
}, [sheet]);
|
|
1200
|
+
// 选中单个库来源模型时,异步查其 rig 给面板过滤动画
|
|
1201
|
+
useEffect(() => {
|
|
1202
|
+
let cancelled = false;
|
|
1203
|
+
const entry = selectedPlacement
|
|
1204
|
+
? getPlacementEntryForObject(selectedPlacement)
|
|
1205
|
+
: undefined;
|
|
1206
|
+
if (!entry?.libraryEntryId) {
|
|
1207
|
+
setSelectedRig(undefined);
|
|
1208
|
+
setRigLoading(false);
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
setRigLoading(true);
|
|
1212
|
+
const load = async () => {
|
|
1213
|
+
try {
|
|
1214
|
+
const libEntry = await getLibrary().getEntry(entry.libraryEntryId);
|
|
1215
|
+
if (cancelled)
|
|
1216
|
+
return;
|
|
1217
|
+
setSelectedRig(libEntry?.rig);
|
|
1218
|
+
}
|
|
1219
|
+
finally {
|
|
1220
|
+
if (!cancelled)
|
|
1221
|
+
setRigLoading(false);
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
void load();
|
|
1225
|
+
return () => {
|
|
1226
|
+
cancelled = true;
|
|
1227
|
+
};
|
|
1228
|
+
}, [selectedPlacement]);
|
|
1229
|
+
const applyAnimation = useCallback((animEntry) => {
|
|
1230
|
+
const studio = getStudioSync();
|
|
1231
|
+
if (!studio || !selectedPlacement)
|
|
1232
|
+
return;
|
|
1233
|
+
const scrub = studio.scrub();
|
|
1234
|
+
scrub.capture(({ set }) => {
|
|
1235
|
+
set(selectedPlacement.props.animation, animEntry.id);
|
|
1236
|
+
});
|
|
1237
|
+
scrub.commit();
|
|
1238
|
+
}, [selectedPlacement]);
|
|
1239
|
+
const applyPose = useCallback((poseEntry) => {
|
|
1240
|
+
if (!selectedPlacement)
|
|
1241
|
+
return;
|
|
1242
|
+
// 库姿势应用(spec §6.2/§7.3):写 lib 引用。有轨道时在播放头写
|
|
1243
|
+
// 关键帧(引用入帧);无轨道时写静态值,不建轨道——固定姿势不
|
|
1244
|
+
// 意外多出轨道。旧「顺手清 poseOverride」随两字段合并消失。
|
|
1245
|
+
setPoseValue(selectedPlacement, serializeLibPose(poseEntry.id));
|
|
1246
|
+
}, [selectedPlacement]);
|
|
1247
|
+
// 抽帧存姿势:从当前选中的已放置模型实例捕获骨骼状态入库。
|
|
1248
|
+
// 入库流程(rig 解析/capturePose/putEntry/缩略图)与 Agent
|
|
1249
|
+
// poses.saveCurrent 共用 poseMode/savePose.savePoseFromInstance;
|
|
1250
|
+
// 这里只负责 UI 侧的名称来源(prompt)与告警。
|
|
1251
|
+
const onSavePose = useCallback(async () => {
|
|
1252
|
+
try {
|
|
1253
|
+
const placement = selectedPlacement
|
|
1254
|
+
? getPlacementInstanceForObject(selectedPlacement)
|
|
1255
|
+
: undefined;
|
|
1256
|
+
const instance = placement?.instance;
|
|
1257
|
+
const entry = placement?.entry;
|
|
1258
|
+
if (!instance)
|
|
1259
|
+
return;
|
|
1260
|
+
const poseEntries = (await getLibrary().listEntries('pose'));
|
|
1261
|
+
const defaultName = `姿势-${poseEntries.length + 1}`;
|
|
1262
|
+
const name = window.prompt('姿势名称', defaultName)?.trim();
|
|
1263
|
+
if (!name)
|
|
1264
|
+
return;
|
|
1265
|
+
await savePoseFromInstance({
|
|
1266
|
+
library: getLibrary(),
|
|
1267
|
+
instance,
|
|
1268
|
+
libraryEntryId: entry?.libraryEntryId,
|
|
1269
|
+
name,
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
catch (err) {
|
|
1273
|
+
console.warn('[assetLibrary] 存姿势失败', err);
|
|
1274
|
+
}
|
|
1275
|
+
}, [selectedPlacement]);
|
|
1276
|
+
const canApplyAnimation = selectedPlacement !== null &&
|
|
1277
|
+
selectionCount === 1 &&
|
|
1278
|
+
getPlacementEntryForObject(selectedPlacement)?.libraryEntryId !== undefined;
|
|
1279
|
+
const canApplyPose = canApplyAnimation;
|
|
1280
|
+
const selectedPlacementInstance = selectedPlacement
|
|
1281
|
+
? getPlacementInstanceForObject(selectedPlacement)?.instance
|
|
1282
|
+
: undefined;
|
|
1283
|
+
const canSavePose = selectedPlacement !== null &&
|
|
1284
|
+
selectionCount === 1 &&
|
|
1285
|
+
selectedPlacementInstance !== undefined &&
|
|
1286
|
+
rigIdOf(selectedPlacementInstance) !== 'static';
|
|
1287
|
+
// 姿势状态行(spec §6.2):来源文案 + 帧数 + 打帧/清除姿势。
|
|
1288
|
+
// poseKeyframeCount 是命令式读取(非响应式),CameraScene 已随 selectedPoseJson
|
|
1289
|
+
// (useVal)与选中变化重渲,覆盖写帧/撤销/切换对象的刷新路径;轨道刚建立
|
|
1290
|
+
// 的那一瞬由随后的写帧值变化带动,一个渲染周期内跟上。
|
|
1291
|
+
const poseEntries = useLibraryEntries('pose');
|
|
1292
|
+
const selectedPoseJson = useVal(selectedPlacement?.props.pose) ?? '';
|
|
1293
|
+
const poseKeyframeCount = selectedPlacement
|
|
1294
|
+
? getPoseKeyframeCount(selectedPlacement)
|
|
1295
|
+
: 0;
|
|
1296
|
+
// libName 反查:状态行显示库条目名,条目缺失回退 id(告警与回退由
|
|
1297
|
+
// resolve 层负责,这里只做文案)
|
|
1298
|
+
const parsedPose = parsePoseValue(selectedPoseJson);
|
|
1299
|
+
const poseLibName = parsedPose?.kind === 'lib'
|
|
1300
|
+
? poseEntries.find((e) => e.id === parsedPose.id)?.name ?? null
|
|
1301
|
+
: null;
|
|
1302
|
+
const poseStatus = poseStatusText(selectedPoseJson, poseLibName, t);
|
|
1303
|
+
const onWritePoseKeyframe = useCallback(() => {
|
|
1304
|
+
if (selectedPlacement)
|
|
1305
|
+
writePoseKeyframe(selectedPlacement, selectedPoseJson);
|
|
1306
|
+
}, [selectedPlacement, selectedPoseJson]);
|
|
1307
|
+
const onClearPose = useCallback(() => {
|
|
1308
|
+
if (!selectedPlacement)
|
|
1309
|
+
return;
|
|
1310
|
+
// 单事务删轨 + 清静态(一个撤销步骤),与 Agent poses.clear 共用
|
|
1311
|
+
// poseTrack.clearPoseValue;空姿势是明确 no-op,不产生空撤销步
|
|
1312
|
+
clearPoseValue(selectedPlacement);
|
|
1313
|
+
}, [selectedPlacement]);
|
|
1314
|
+
// ── 宿主能力句柄(规格:host-picker-integration)────────────
|
|
1315
|
+
// 纯转发既有函数;宿主注入自己的选择框后,经 onFlows 拿到句柄,
|
|
1316
|
+
// 在选择框里调 startPlacingModel(点选)/ placeModelAt + dropPointFromClient(拖拽)。
|
|
1317
|
+
// 必须放在 return 之前:placePresetPath 等在组件体内更靠前定义,
|
|
1318
|
+
// 放解构处会触发暂时性死区。
|
|
1319
|
+
// latest-ref:规格要求句柄整个场景生命周期稳定、一次性交付。deps 直接
|
|
1320
|
+
// 进 useMemo 依赖链时,activeCamera 切换会经 pathPresetHeight →
|
|
1321
|
+
// placePresetPath 级联重建 flows、重发 onFlows;改成 ref 每次渲染刷新、
|
|
1322
|
+
// 句柄方法调用时才取最新 deps,useMemo([]) 只组装一次,无陈旧闭包。
|
|
1323
|
+
const hostFlowDepsRef = useRef(null);
|
|
1324
|
+
hostFlowDepsRef.current = {
|
|
1325
|
+
startPlacingFromLibrary,
|
|
1326
|
+
placeLibraryEntryAt,
|
|
1327
|
+
placePresetPath,
|
|
1328
|
+
addCamera,
|
|
1329
|
+
addAnchor,
|
|
1330
|
+
cancelPlacing,
|
|
1331
|
+
importModel: importModelFile,
|
|
1332
|
+
};
|
|
1333
|
+
const flows = useMemo(() => createHostFlows(() => hostFlowDepsRef.current), []);
|
|
1334
|
+
// ── Agent Scene Runtime 绑定(2026-10-19,Task 15)──────────────
|
|
1335
|
+
// 与 HostFlowsWiring 同一 effect 生命周期:挂载即把完整 handler 集绑进
|
|
1336
|
+
// 当前执行代次并 markReady(api.ready 随之兑现);卸载解绑并使该代次
|
|
1337
|
+
// 就绪失效(API_NOT_READY 换代,重挂由绑定层产出新代次;会话拆解才
|
|
1338
|
+
// API_DISPOSED)。无会话(直连宿主未走 bootstrap)时绑定层静默降级。
|
|
1339
|
+
// deps 走 latest-ref(同 hostFlowDepsRef:绑定一次、永远读最新实现,
|
|
1340
|
+
// activeCamera 切换等重渲染不重建绑定,无陈旧闭包)。
|
|
1341
|
+
const agentDepsRef = useRef(null);
|
|
1342
|
+
agentDepsRef.current = {
|
|
1343
|
+
cameras,
|
|
1344
|
+
anchors,
|
|
1345
|
+
activeCameraKey: activeCamera?.cameraKey ?? null,
|
|
1346
|
+
shotView,
|
|
1347
|
+
pipVisible,
|
|
1348
|
+
gizmoMode,
|
|
1349
|
+
selectedPathPoint,
|
|
1350
|
+
placeLibraryEntryAt,
|
|
1351
|
+
enterPathEdit,
|
|
1352
|
+
exitPathEditOnBlur,
|
|
1353
|
+
afterSceneObjectDeleted,
|
|
1354
|
+
};
|
|
1355
|
+
useEffect(() => {
|
|
1356
|
+
const deps = () => agentDepsRef.current;
|
|
1357
|
+
const handlers = {
|
|
1358
|
+
// 模型:转发 placeLibraryEntryAt(Task 8 已返回 {key, persisted},
|
|
1359
|
+
// 与 UI 点击放置同一条落地管线)
|
|
1360
|
+
createModel: ({ entry, position }) => deps().placeLibraryEntryAt(entry, position),
|
|
1361
|
+
// 相机:Task 15 决策——生产绑定不自动 setActiveCameraKey。
|
|
1362
|
+
// create/setActive 是 spec §9 的两条显式命令,Agent 调用
|
|
1363
|
+
// cameras.create 不得隐性改变会话态;自动激活是 UI 顶栏
|
|
1364
|
+
// addCamera 面向人类点击的 UX 糖,保留在 UI 包装层
|
|
1365
|
+
createCamera: ({ seed }) => {
|
|
1366
|
+
const entry = nextCameraEntry(sheet, deps().cameras);
|
|
1367
|
+
addCameraToRegistry(sheet, entry, seed ?? captureCameraSeed(cameraObjRef.current));
|
|
1368
|
+
return { key: entry.cameraKey };
|
|
1369
|
+
},
|
|
1370
|
+
setActiveCamera: ({ cameraKey }) => setActiveCameraKey(cameraKey),
|
|
1371
|
+
createAnchor: ({ position }) => {
|
|
1372
|
+
const key = nextAnchorKey(deps().anchors);
|
|
1373
|
+
addAnchorToRegistry(sheet, key, position ? { position } : captureAnchorSeed(cameraObjRef.current));
|
|
1374
|
+
return { key };
|
|
1375
|
+
},
|
|
1376
|
+
// 按 key 取已挂载模型的运行时实例(poses.saveCurrent /
|
|
1377
|
+
// poses.session.start / animations.apply 共用)
|
|
1378
|
+
getModelPlacement: ({ key }) => {
|
|
1379
|
+
const obj = getSceneSheetObject(formatSceneObjectId('model', key));
|
|
1380
|
+
return obj ? getPlacementInstanceForObject(obj) : undefined;
|
|
1381
|
+
},
|
|
1382
|
+
openPanel: ({ panel }) => openFrame(panel),
|
|
1383
|
+
closePanel: ({ panel }) => closeFrame(panel),
|
|
1384
|
+
setGizmoMode: ({ mode }) => setGizmoMode(mode),
|
|
1385
|
+
getViewState: () => ({
|
|
1386
|
+
activeCameraKey: deps().activeCameraKey,
|
|
1387
|
+
shotView: deps().shotView,
|
|
1388
|
+
pipVisible: deps().pipVisible,
|
|
1389
|
+
editingPath: editingPathRef.current,
|
|
1390
|
+
gizmoMode: deps().gizmoMode,
|
|
1391
|
+
}),
|
|
1392
|
+
setShotView: ({ on }) => setShotView(on),
|
|
1393
|
+
setPipVisible: ({ visible }) => setPipVisible(visible),
|
|
1394
|
+
startPathEditing: ({ pathKey }) => deps().enterPathEdit(pathKey),
|
|
1395
|
+
// 失焦式退出:清编辑态,不回头重选路径本体(与 handler 声明一致)
|
|
1396
|
+
exitPathEditing: () => deps().exitPathEditOnBlur(),
|
|
1397
|
+
selectPathPoint: ({ pathKey, pointKey }) => setSelectedPathPoint({ metaKey: pathKey, key: pointKey }),
|
|
1398
|
+
getPathEditingState: () => {
|
|
1399
|
+
const pathKey = editingPathRef.current;
|
|
1400
|
+
const selection = deps().selectedPathPoint;
|
|
1401
|
+
return {
|
|
1402
|
+
pathKey,
|
|
1403
|
+
pointKey: pathKey && selection?.metaKey === pathKey ? selection.key : null,
|
|
1404
|
+
};
|
|
1405
|
+
},
|
|
1406
|
+
clearPathPointSelection: () => setSelectedPathPoint(null),
|
|
1407
|
+
afterSceneObjectDeleted: ({ kind, key }) => deps().afterSceneObjectDeleted(kind, key),
|
|
1408
|
+
};
|
|
1409
|
+
return bindCameraStudioSceneHandlers(handlers);
|
|
1410
|
+
// sheet 随 project 固定(组件实例不换 project);deps 全部走 ref
|
|
1411
|
+
}, [sheet]);
|
|
1412
|
+
useEffect(() => {
|
|
1413
|
+
setPlacingSnapshot(placing);
|
|
1414
|
+
}, [placing]);
|
|
1415
|
+
// 顶栏库按钮:打开内置框体。宿主换框体走 pickerRegistry 注册
|
|
1416
|
+
// (picker-frames-registry 机制),不再经组件 props 覆盖(T9 随
|
|
1417
|
+
// CameraScene props 收紧为 {project, onFlows} 移除)
|
|
1418
|
+
const handleToggleModel = useMemo(() => () => toggleFrame('model'), [toggleFrame]);
|
|
1419
|
+
const handleTogglePath = useMemo(() => () => toggleFrame('path'), [toggleFrame]);
|
|
1420
|
+
// 稳定引用:行为不变(setPipVisible 取反),与 handleToggleModel/Path 同模式
|
|
1421
|
+
const handleTogglePip = useCallback(() => setPipVisible((v) => !v), []);
|
|
1422
|
+
return (React.createElement(SceneI18nRoot, null,
|
|
1423
|
+
React.createElement("div", { style: {
|
|
1424
|
+
height: '100vh',
|
|
1425
|
+
position: 'relative',
|
|
1426
|
+
} },
|
|
1427
|
+
React.createElement(HostFlowsWiring, { flows: flows, onFlows: onFlows }),
|
|
1428
|
+
React.createElement(TopToolbar, { gizmoMode: gizmoMode, onSelectGizmoMode: setGizmoMode, onToggleModelLibrary: handleToggleModel, onTogglePathLibrary: handleTogglePath, onAddCamera: addCamera, onAddAnchor: addAnchor, exportSinks: exportSinks, exporting: exporting, onPickExportSink: handlePickExportSink, onStopExport: handleStopExport, exportDisabled: !activeCamera, pipVisible: pipVisible, onTogglePip: handleTogglePip, pipDisabled: !activeCamera, libraryDisabled: !!placing || modelDragging || presetDrag !== null }),
|
|
1429
|
+
!shotView && activeCamera && pipVisible && (React.createElement("div", { style: {
|
|
1430
|
+
// 画中画边框:与 ShotCamera 渲染的右下角小窗同位置同尺寸
|
|
1431
|
+
position: 'absolute',
|
|
1432
|
+
right: 16,
|
|
1433
|
+
bottom: pipBottom,
|
|
1434
|
+
width: '28%',
|
|
1435
|
+
aspectRatio: '16 / 9',
|
|
1436
|
+
border: '1px solid rgba(255,255,255,0.14)',
|
|
1437
|
+
borderRadius: 2,
|
|
1438
|
+
background: 'rgba(24,24,28,0.4)',
|
|
1439
|
+
pointerEvents: 'none',
|
|
1440
|
+
zIndex: 5,
|
|
1441
|
+
} },
|
|
1442
|
+
React.createElement(PipCameraSelector, { cameras: cameras, activeKey: activeCamera.cameraKey, onSelect: setActiveCameraKey, visible: pipHovered }))),
|
|
1443
|
+
(modelDragging || presetDrag) && (React.createElement("div", { style: {
|
|
1444
|
+
position: 'absolute',
|
|
1445
|
+
bottom: 24,
|
|
1446
|
+
left: '50%',
|
|
1447
|
+
transform: 'translateX(-50%)',
|
|
1448
|
+
zIndex: 10,
|
|
1449
|
+
padding: '6px 12px',
|
|
1450
|
+
borderRadius: 6,
|
|
1451
|
+
background: 'rgba(24,24,28,0.9)',
|
|
1452
|
+
border: '1px solid rgba(255,255,255,0.15)',
|
|
1453
|
+
color: '#eee',
|
|
1454
|
+
fontSize: 12,
|
|
1455
|
+
pointerEvents: 'none',
|
|
1456
|
+
} }, "\u677E\u5F00\u9F20\u6807\u843D\u70B9,Esc/\u53F3\u952E\u53D6\u6D88")),
|
|
1457
|
+
shotView && (React.createElement("div", { style: {
|
|
1458
|
+
position: 'absolute',
|
|
1459
|
+
top: 12,
|
|
1460
|
+
left: '50%',
|
|
1461
|
+
transform: 'translateX(-50%)',
|
|
1462
|
+
padding: '4px 10px',
|
|
1463
|
+
borderRadius: 6,
|
|
1464
|
+
background: 'rgba(0,0,0,0.55)',
|
|
1465
|
+
color: 'rgba(255,255,255,0.85)',
|
|
1466
|
+
fontSize: 12,
|
|
1467
|
+
pointerEvents: 'none',
|
|
1468
|
+
userSelect: 'none',
|
|
1469
|
+
zIndex: 10,
|
|
1470
|
+
} }, "\u955C\u5934\u89C6\u89D2 \u00B7 Esc \u8FD4\u56DE\u5BFC\u6F14\u89C6\u89D2")),
|
|
1471
|
+
frameOpen.model && (React.createElement(ModelPickerSlot, { onPlaceModel: placeModelAtDefault, onDragStartModel: startModelDrag, disabled: rigLoading, onClose: () => closeFrame('model') })),
|
|
1472
|
+
frameOpen.path && (React.createElement(AssetLibraryPanel, { kind: "path", onPlacePreset: (preset) => placePresetPath(preset), onDragStartPreset: (preset) => setPresetDrag(preset), onClose: () => closeFrame('path') })),
|
|
1473
|
+
frameOpen.pose && (React.createElement(AssetLibraryPanel, { kind: "pose", onApplyPose: applyPose, onSavePose: onSavePose, canSavePose: canSavePose, applyDisabled: !canApplyPose, disabled: rigLoading, filterRig: selectedRig, poseStatus: poseStatus, poseKeyframeCount: poseKeyframeCount, onWritePoseKeyframe: onWritePoseKeyframe, onClearPose: onClearPose, onClose: () => closeFrame('pose') })),
|
|
1474
|
+
frameOpen.animation && (React.createElement(AssetLibraryPanel, { kind: "animation", onApplyAnimation: applyAnimation, applyDisabled: !canApplyAnimation, disabled: rigLoading, filterRig: selectedRig, onClose: () => closeFrame('animation') })),
|
|
1475
|
+
objectMenu && (React.createElement(CanvasContextMenu, { position: { x: objectMenu.x, y: objectMenu.y }, items: buildObjectMenuItems({
|
|
1476
|
+
mode: gizmoMode,
|
|
1477
|
+
onSelectMode: setGizmoMode,
|
|
1478
|
+
onDelete: () => {
|
|
1479
|
+
deletePlacement(sheet, objectMenu.key);
|
|
1480
|
+
afterSceneObjectDeleted('model', objectMenu.key);
|
|
1481
|
+
},
|
|
1482
|
+
// 库来源模型才有姿势/动作可套(本地遗留放置体没有库 id);
|
|
1483
|
+
// 骨架不匹配的置灰由框体自己兜
|
|
1484
|
+
...(readPlacementEntries(sheet).find((e) => e.key === objectMenu.key)?.libraryEntryId !== undefined
|
|
1485
|
+
? {
|
|
1486
|
+
onChangePose: () => openFrame('pose'),
|
|
1487
|
+
onPlayAnimation: () => openFrame('animation'),
|
|
1488
|
+
}
|
|
1489
|
+
: {}),
|
|
1490
|
+
// 摆姿势:有骨骼就显示;映射不出人形关键骨的置灰带原因
|
|
1491
|
+
// (独立于 libraryEntryId:本地人形模型也能摆)
|
|
1492
|
+
...(() => {
|
|
1493
|
+
const inst = selectedPlacement
|
|
1494
|
+
? getPlacementInstanceForObject(selectedPlacement)
|
|
1495
|
+
: undefined;
|
|
1496
|
+
const entry = selectedPlacement
|
|
1497
|
+
? getPlacementEntryForObject(selectedPlacement)
|
|
1498
|
+
: undefined;
|
|
1499
|
+
if (!inst || !entry || entry.key !== objectMenu.key)
|
|
1500
|
+
return {};
|
|
1501
|
+
if (collectBoneNames(inst.instance).length === 0)
|
|
1502
|
+
return {};
|
|
1503
|
+
const supported = resolveControlSpec(inst.instance) !== null;
|
|
1504
|
+
return {
|
|
1505
|
+
onPoseMode: () => enterPoseSession(objectMenu.key),
|
|
1506
|
+
poseModeEnabled: supported,
|
|
1507
|
+
poseModeHint: t('pg.menu.poseModeUnsupported'),
|
|
1508
|
+
// 清除姿势(删轨道+回 T 姿势):有骨骼的模型都可能有
|
|
1509
|
+
// 姿势值/轨道;对无姿势者点击=空事务,saaz 不入栈
|
|
1510
|
+
onClearPose,
|
|
1511
|
+
};
|
|
1512
|
+
})(),
|
|
1513
|
+
}, t), onClose: closeObjectMenu })),
|
|
1514
|
+
pathMenu && (React.createElement(CanvasContextMenu, { position: { x: pathMenu.x, y: pathMenu.y }, items: buildPathMenuItems({
|
|
1515
|
+
metaKey: pathMenu.metaKey,
|
|
1516
|
+
editing: editingPath === pathMenu.metaKey,
|
|
1517
|
+
onToggleEdit: () => {
|
|
1518
|
+
if (editingPath === pathMenu.metaKey) {
|
|
1519
|
+
exitPathEdit();
|
|
1520
|
+
}
|
|
1521
|
+
else {
|
|
1522
|
+
enterPathEdit(pathMenu.metaKey);
|
|
1523
|
+
}
|
|
1524
|
+
},
|
|
1525
|
+
// 有活动相机且它没绑这条时才出「绑定到活动相机」
|
|
1526
|
+
onBindToActiveCamera: (() => {
|
|
1527
|
+
const cam = activeCamera
|
|
1528
|
+
? shotRefsMapRef.current.get(activeCamera.cameraKey)?.camera
|
|
1529
|
+
.current
|
|
1530
|
+
: undefined;
|
|
1531
|
+
if (!cam)
|
|
1532
|
+
return undefined;
|
|
1533
|
+
if (val(cam.props.pathKey) === pathMenu.metaKey)
|
|
1534
|
+
return undefined;
|
|
1535
|
+
return () => bindPathToActiveCamera(pathMenu.metaKey);
|
|
1536
|
+
})(),
|
|
1537
|
+
onDelete: () => removePath(pathMenu.metaKey),
|
|
1538
|
+
}, t), onClose: closePathMenu })),
|
|
1539
|
+
cameraMenu && (React.createElement(CanvasContextMenu, { position: cameraMenu, items: buildCameraMenuItems({
|
|
1540
|
+
// 查看此镜头 = 设为活动 + 进镜头视角
|
|
1541
|
+
onViewShot: () => {
|
|
1542
|
+
setActiveCameraKey(cameraMenu.cameraKey);
|
|
1543
|
+
setShotView(true);
|
|
1544
|
+
},
|
|
1545
|
+
onDeleteCamera: () => {
|
|
1546
|
+
const entry = cameras.find((e) => e.cameraKey === cameraMenu.cameraKey);
|
|
1547
|
+
if (entry)
|
|
1548
|
+
deleteCamera(entry);
|
|
1549
|
+
},
|
|
1550
|
+
}, t), onClose: closeCameraMenu })),
|
|
1551
|
+
anchorMenu && (React.createElement(CanvasContextMenu, { position: { x: anchorMenu.x, y: anchorMenu.y }, items: buildAnchorMenuItems({
|
|
1552
|
+
onDeleteAnchor: () => {
|
|
1553
|
+
// 真删:forgetObject 按地址清持久数据(静态覆盖/时间轴
|
|
1554
|
+
// 轨道,不要求挂载句柄)+ 子锚点解绑定格折进同一事务;
|
|
1555
|
+
// 会话清理走共用实现
|
|
1556
|
+
removeAnchor(sheet, anchorMenu.key, collectAnchorChildResets(sheet, anchorMenu.key));
|
|
1557
|
+
afterSceneObjectDeleted('anchor', anchorMenu.key);
|
|
1558
|
+
},
|
|
1559
|
+
}), onClose: closeAnchorMenu })),
|
|
1560
|
+
editingPath && !shotView && (React.createElement(PathEditBar, { hasSelection: !!selectedPathPoint && selectedPathPoint.metaKey === editingPath, onAddPoint: () => {
|
|
1561
|
+
if (editingPath)
|
|
1562
|
+
addPathPoint(sheet, editingPath, undefined);
|
|
1563
|
+
}, onDeleteSelected: deleteSelectedPathPoint })),
|
|
1564
|
+
editingPath &&
|
|
1565
|
+
selectedPathPoint &&
|
|
1566
|
+
selectedPathPoint.metaKey === editingPath &&
|
|
1567
|
+
!shotView && (React.createElement(PathPointEditor, { sheet: sheet, metaKey: editingPath, pointKey: selectedPathPoint.key, onPinAtPlayhead: pinPathPointAtPlayhead, progressKeyframes: progressKeyframes })),
|
|
1568
|
+
React.createElement(Canvas, { dpr: [1.5, 2], linear: true, shadows: "percentage", gl: { preserveDrawingBuffer: true }, frameloop: "demand",
|
|
1569
|
+
// 点到空处取消选中,gizmo 随之消失;放置模式下点空处是确认落点,不动选中
|
|
1570
|
+
onPointerMissed: () => {
|
|
1571
|
+
if (!placing && !getPoseSession())
|
|
1572
|
+
getStudioSync()?.setSelection([]);
|
|
1573
|
+
// 控制点也取消选中,但路径编辑模式保持(编辑态是"粘"的)
|
|
1574
|
+
setSelectedPathPoint(null);
|
|
1575
|
+
} },
|
|
1576
|
+
React.createElement(SheetProvider, { sheet: sheet },
|
|
1577
|
+
React.createElement(HostFlowsDropPointCapture, null),
|
|
1578
|
+
React.createElement(MainOrbitControls, { targetRef: cameraTargetRef, cameraObjRef: cameraObjRef, targetObjRef: targetObjRef, enabled: !shotView }),
|
|
1579
|
+
!shotView && React.createElement(GizmoNav, { cameraObjRef: cameraObjRef }),
|
|
1580
|
+
React.createElement(ExitPathEditOnEmptyClick, { isEditing: isPathEditing, onExit: exitPathEditOnBlur }),
|
|
1581
|
+
cameras.map((entry) => (React.createElement(ShotCamera, { key: entry.cameraKey, cameraKey: entry.cameraKey, isActive: entry.cameraKey === activeCamera?.cameraKey, shotView: shotView, pipVisible: pipVisible, pipBottom: pipBottom, cameraObjRef: getShotRefs(entry.cameraKey).camera, onCameraContextMenu: (x, y) => setCameraMenu({ cameraKey: entry.cameraKey, x, y }) }))),
|
|
1582
|
+
React.createElement(Anchors, { sheet: sheet, shotView: shotView, getAnchorRef: getAnchorRef }),
|
|
1583
|
+
paths.map((entry) => (React.createElement(CameraPath, { key: entry.pathKey, metaKey: entry.pathKey,
|
|
1584
|
+
// 普通态亮紫(与预设卡片的动画轨迹同色);选中/编辑的黄色档位在 CameraPath 内部
|
|
1585
|
+
color: 0xa78bfa, hidden: shotView, editing: editingPath === entry.pathKey, selectedKey: selectedPathPoint?.metaKey === entry.pathKey
|
|
1586
|
+
? selectedPathPoint.key
|
|
1587
|
+
: null, onSelectPoint: (key) => setSelectedPathPoint(key ? { metaKey: entry.pathKey, key } : null), onContextMenu: (metaKey, x, y) => setPathMenu({ metaKey, x, y }), progressOf: pathProgressOf(entry.pathKey) }))),
|
|
1588
|
+
React.createElement("primitive", { object: BACKGROUND, attach: "background" }),
|
|
1589
|
+
React.createElement("ambientLight", { intensity: 0.3 }),
|
|
1590
|
+
React.createElement("hemisphereLight", { args: ['#ffffff', '#444444', 1.1] }),
|
|
1591
|
+
React.createElement(EnvLighting, null),
|
|
1592
|
+
React.createElement(e.directionalLight, { theatreKey: "Key Light", castShadow: true, intensity: 1.4, position: [10, 20, 10], "shadow-mapSize": [2048, 2048], "shadow-bias": -0.0001, "shadow-camera-left": -30, "shadow-camera-right": 30, "shadow-camera-top": 30, "shadow-camera-bottom": -30, "shadow-camera-far": 80 }),
|
|
1593
|
+
React.createElement(PerspectiveCamera, { theatreKey: "Camera / Camera", objRef: cameraObjRef, hideFromOutline: true, makeDefault: !shotView, position: DIRECTOR_HOME_CAMERA_POSITION, fov: 75, lookAt: cameraTargetRef }),
|
|
1594
|
+
React.createElement(e.mesh, { ref: cameraTargetRef, theatreKey: "Camera / Target", objRef: targetObjRef, hideFromOutline: true, position: DIRECTOR_HOME_TARGET_POSITION, visible: "editor" },
|
|
1595
|
+
React.createElement("boxGeometry", { attach: "geometry" }),
|
|
1596
|
+
React.createElement("meshPhongMaterial", { attach: "material", color: "red" })),
|
|
1597
|
+
React.createElement(Suspense, { fallback: null },
|
|
1598
|
+
React.createElement(Model, { url: sceneGLB, selectLocked: !!placing })),
|
|
1599
|
+
placing && (React.createElement(Suspense, { fallback: null },
|
|
1600
|
+
React.createElement(PlacementRigErrorBoundary, { onCancel: () => {
|
|
1601
|
+
cancelPlacing();
|
|
1602
|
+
setModelDragging(false);
|
|
1603
|
+
} },
|
|
1604
|
+
React.createElement(PlacementRig, { placing: placing, confirmOnPointerUp: modelDragging, onConfirm: (p) => {
|
|
1605
|
+
confirmPlacing(p);
|
|
1606
|
+
setModelDragging(false);
|
|
1607
|
+
}, onCancel: () => {
|
|
1608
|
+
cancelPlacing();
|
|
1609
|
+
setModelDragging(false);
|
|
1610
|
+
} })))),
|
|
1611
|
+
React.createElement(PlacedModels, { sheet: sheet, project: project, pendingEntry: pendingEntry, onPersisted: clearPending, selectLocked: !!placing, onObjectMenu: (key, x, y) => setObjectMenu({ key, x, y }) }),
|
|
1612
|
+
React.createElement(SelectionGizmo, { hidden: !!placing || shotView, mode: gizmoMode }),
|
|
1613
|
+
presetDrag && (React.createElement(PathPresetDropRig, { heightY: pathPresetHeight(), onConfirm: (p) => {
|
|
1614
|
+
placePresetPath(presetDrag, p);
|
|
1615
|
+
setPresetDrag(null);
|
|
1616
|
+
}, onCancel: () => setPresetDrag(null) })),
|
|
1617
|
+
React.createElement(GroundGrid, null))))));
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* i18n 嵌套判定(spec §7):宿主树已包 studio 的 I18nProvider →
|
|
1621
|
+
* 原样渲染,语言以宿主为准(ui.locale 不生效);否则自包一层,
|
|
1622
|
+
* ui.locale 仅在这个自包分支生效(经 setLanguage 应用到 studio
|
|
1623
|
+
* i18n 单例——t/translate 读本单例,子树级隔离无从谈起,自包场景
|
|
1624
|
+
* = 无宿主 i18n 的独立挂载,全局应用即预期行为)。
|
|
1625
|
+
* 抽出成组件便于脱离整树做单测(HostFlowsWiring 先例)。
|
|
1626
|
+
*/
|
|
1627
|
+
export function SceneI18nRoot({ children }) {
|
|
1628
|
+
const hostI18n = useContext(I18nContext);
|
|
1629
|
+
if (hostI18n)
|
|
1630
|
+
return React.createElement(React.Fragment, null, children);
|
|
1631
|
+
return React.createElement(SceneI18n, null, children);
|
|
1632
|
+
}
|
|
1633
|
+
/** 自包分支:I18nProvider 订阅 studio 单例语言变化;locale 一次性应用 */
|
|
1634
|
+
function SceneI18n({ children }) {
|
|
1635
|
+
const locale = getUiConfig().locale;
|
|
1636
|
+
useEffect(() => {
|
|
1637
|
+
if (!locale)
|
|
1638
|
+
return;
|
|
1639
|
+
setLanguage(locale);
|
|
1640
|
+
}, [locale]);
|
|
1641
|
+
return React.createElement(I18nProvider, null, children);
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* 路径编辑模式下点到真空 = 路径失焦,退出编辑模式(和"选中切到别的
|
|
1645
|
+
* 对象"同一条失焦规则,对齐 Blender 编辑模式跟活动物体走)。
|
|
1646
|
+
* 不挂在 Canvas 的 onPointerMissed 上:r3f 的"未命中"判定在递归命中
|
|
1647
|
+
* 和交互集合上和我们想要的不完全是一回事,自己监听画布 click、自己做
|
|
1648
|
+
* 一次射线检查,命中对象沿父链找得到事件处理器就不算空点。
|
|
1649
|
+
* 拖过的点击(转视角/gizmo/拖手柄)不算;gizmo 点按有屏蔽窗口兜底。
|
|
1650
|
+
*/
|
|
1651
|
+
function ExitPathEditOnEmptyClick({ isEditing, onExit, }) {
|
|
1652
|
+
const gl = useThree((s) => s.gl);
|
|
1653
|
+
const camera = useThree((s) => s.camera);
|
|
1654
|
+
const scene = useThree((s) => s.scene);
|
|
1655
|
+
useEffect(() => {
|
|
1656
|
+
const raycaster = new Raycaster();
|
|
1657
|
+
const ndc = new Vector2();
|
|
1658
|
+
let downAt = null;
|
|
1659
|
+
const onDown = (e) => {
|
|
1660
|
+
downAt = [e.clientX, e.clientY];
|
|
1661
|
+
};
|
|
1662
|
+
const onClick = (e) => {
|
|
1663
|
+
if (!isEditing())
|
|
1664
|
+
return;
|
|
1665
|
+
if (isSelectSuppressed())
|
|
1666
|
+
return;
|
|
1667
|
+
// 按下和抬起位置差太多 = 拖拽收尾,不是点
|
|
1668
|
+
if (downAt &&
|
|
1669
|
+
Math.hypot(e.clientX - downAt[0], e.clientY - downAt[1]) > 4)
|
|
1670
|
+
return;
|
|
1671
|
+
const rect = gl.domElement.getBoundingClientRect();
|
|
1672
|
+
ndc.set(((e.clientX - rect.left) / rect.width) * 2 - 1, -(((e.clientY - rect.top) / rect.height) * 2 - 1));
|
|
1673
|
+
raycaster.setFromCamera(ndc, camera);
|
|
1674
|
+
const hits = raycaster.intersectObjects(scene.children, true);
|
|
1675
|
+
for (const h of hits) {
|
|
1676
|
+
let o = h.object;
|
|
1677
|
+
while (o) {
|
|
1678
|
+
const handlers = o.__r3f?.handlers;
|
|
1679
|
+
if (handlers && Object.keys(handlers).length > 0)
|
|
1680
|
+
return;
|
|
1681
|
+
o = o.parent;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
onExit();
|
|
1685
|
+
};
|
|
1686
|
+
window.addEventListener('pointerdown', onDown, true);
|
|
1687
|
+
gl.domElement.addEventListener('click', onClick);
|
|
1688
|
+
return () => {
|
|
1689
|
+
window.removeEventListener('pointerdown', onDown, true);
|
|
1690
|
+
gl.domElement.removeEventListener('click', onClick);
|
|
1691
|
+
};
|
|
1692
|
+
}, [gl, camera, scene, isEditing, onExit]);
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* 宿主拖拽落点的上下文捕获:把 gl/camera 塞进 hostFlows 模块,
|
|
1697
|
+
* 宿主 dropPointFromClient 闭包读取。挂 Canvas 子树,卸载即清。
|
|
1698
|
+
*/
|
|
1699
|
+
function HostFlowsDropPointCapture() {
|
|
1700
|
+
const gl = useThree((s) => s.gl);
|
|
1701
|
+
const camera = useThree((s) => s.camera);
|
|
1702
|
+
useEffect(() => {
|
|
1703
|
+
setDropPointContext({ gl, camera });
|
|
1704
|
+
return () => setDropPointContext(null);
|
|
1705
|
+
}, [gl, camera]);
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
/** 句柄交付/清理独立成组件,便于脱离整树做单测。 */
|
|
1709
|
+
export function HostFlowsWiring({ flows, onFlows, }) {
|
|
1710
|
+
useEffect(() => {
|
|
1711
|
+
setHostFlowsHandle(flows);
|
|
1712
|
+
onFlows?.(flows);
|
|
1713
|
+
return () => setHostFlowsHandle(null);
|
|
1714
|
+
}, [flows, onFlows]);
|
|
1715
|
+
return null;
|
|
1716
|
+
}
|