cozyclay 3.0.3 → 3.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/CHANGELOG.md +67 -0
- package/INSTALL_FOR_AGENTS.md +146 -0
- package/README.md +74 -192
- package/THIRD_PARTY_NOTICES.md +0 -21
- package/bin/agent/agent-routes.mjs +22 -56
- package/bin/agent/agent-runner.mjs +5 -3
- package/bin/agent/main-agent.mjs +70 -0
- package/bin/agent/main-prompt.mjs +17 -0
- package/bin/agent/session-store.mjs +2 -2
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-task-routes.mjs +185 -0
- package/bin/agent/studio-tasks.mjs +156 -0
- package/bin/agent/studio-tools.mjs +3 -2
- package/bin/agent/studio-turn-transaction.mjs +76 -0
- package/bin/cozyclay.mjs +5 -7
- package/dist/app/index.html +7 -6
- package/dist/assets/TaskBoard-DdDXMgZm.js +1 -0
- package/dist/assets/app-B2BpfCl2.js +5088 -0
- package/dist/assets/app-BlwOpest.css +1 -0
- package/dist/assets/compile-worker-D0pKioa0.js +5 -0
- package/dist/assets/landing-y3EqDgSZ.js +1 -0
- package/dist/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
- package/dist/assets/motion3-compile-B887n77J.js +1 -0
- package/dist/assets/scene-asset-sync-COGOlF5T.css +1 -0
- package/dist/assets/scene-asset-sync-CQzEdsqu.js +13 -0
- package/dist/assets/task-board-BKaM7d-9.js +14 -0
- package/dist/assets/workflow-B8Dhzexj.js +179 -0
- package/dist/assets/x-bot-tpose-body-BusyR3eg.json +1 -0
- package/dist/assets/y-bot-tpose-body-Dy1VKChZ.json +1 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/greybox-to-video/index.html +2 -3
- package/dist/index.html +7 -57
- package/dist/llms.txt +2 -1
- package/dist/previs-software/index.html +2 -2
- package/dist/privacy/index.html +3 -7
- package/dist/seedance-camera-control/index.html +2 -2
- package/dist/sitemap.xml +5 -5
- package/dist/sw.js +5 -1
- package/dist/thumbs/blank-stage-dark.webp +0 -0
- package/dist/thumbs/blank-stage-light.webp +0 -0
- package/dist/workflow/index.html +5 -4
- package/mcp/mesh-file.mjs +3 -1
- package/mcp/server.mjs +3 -0
- package/package.json +3 -3
- package/src/App.jsx +50 -281
- package/src/analytics.js +3 -148
- package/src/app-stage.jsx +1 -1
- package/src/asset-pane.jsx +5 -10
- package/src/auto-flat.js +10 -0
- package/src/bus-tasks.js +11 -5
- package/src/command-bus.js +1 -1
- package/src/commands/motion3-compile.js +51 -27
- package/src/commands/motion3.js +3 -3
- package/src/commands/project.js +1 -1
- package/src/commands/shot.js +16 -4
- package/src/controls.jsx +0 -11
- package/src/cuts.js +11 -0
- package/src/domains/cast.js +3 -2
- package/src/domains/motion.js +3 -10
- package/src/domains/playback.js +32 -0
- package/src/domains/scenes.js +3 -22
- package/src/domains/shots.js +5 -5
- package/src/dualview.jsx +11 -15
- package/src/hierarchy-panel.css +18 -4
- package/src/hierarchy-panel.jsx +12 -11
- package/src/kits/held.js +3 -2
- package/src/light-blockers.js +2 -2
- package/src/main.jsx +5 -0
- package/src/motion/ground.js +5 -14
- package/src/motion/playback-frame-store.js +79 -0
- package/src/motion/playback-probe.js +16 -0
- package/src/motion/playback-subject.js +7 -0
- package/src/motion/timeline.css +43 -5
- package/src/motion/timeline.jsx +4 -3
- package/src/motion3/body.js +37 -5
- package/src/motion3/carried.js +11 -3
- package/src/motion3/compile-job.js +1 -1
- package/src/motion3/compiler/compile.js +7 -1
- package/src/motion3/compiler/finger-close.js +137 -0
- package/src/motion3/compiler/limbs.js +18 -3
- package/src/motion3/compiler/pose.js +6 -0
- package/src/motion3/compiler/validate-objects.js +17 -2
- package/src/motion3/compiler/validate-parts.js +2 -1
- package/src/motion3/compiler/vocab.js +4 -4
- package/src/motion3/describe.js +2 -2
- package/src/motion3/fix/index.js +5 -3
- package/src/motion3/flight.js +38 -0
- package/src/motion3/free-motion.js +10 -7
- package/src/motion3/load.js +125 -0
- package/src/motion3/mass.js +23 -0
- package/src/motion3/object-keys.js +11 -6
- package/src/motion3/object-pose.js +11 -3
- package/src/motion3/pipeline.js +3 -0
- package/src/motion3/qa/explain.js +3 -1
- package/src/motion3/qa/grasp-checks.js +123 -0
- package/src/motion3/qa/hints-grasp.js +71 -0
- package/src/motion3/qa/index.js +3 -1
- package/src/motion3/qa/object-checks.js +7 -2
- package/src/motion3/qa/patch.js +2 -1
- package/src/motion3/qa/spec-checks.js +10 -1
- package/src/motion3/schema/fields.js +3 -3
- package/src/motion3/schema/topics.js +9 -8
- package/src/motion3/schema/types.js +4 -1
- package/src/motion3/seat.js +84 -0
- package/src/motion3/settle.js +11 -6
- package/src/motion3/shapes.js +70 -0
- package/src/motion3/skills/catch.js +71 -0
- package/src/motion3/skills/index.js +10 -3
- package/src/motion3/skills/info.js +13 -0
- package/src/motion3/skills/throw.js +72 -0
- package/src/object-catalog.jsx +0 -4
- package/src/object-gizmo.jsx +2 -1
- package/src/object-path.js +1 -1
- package/src/panels/ProjectPanel.jsx +42 -21
- package/src/panels/details.css +15 -4
- package/src/popover.js +37 -0
- package/src/posestudio.jsx +15 -10
- package/src/project-browser-art.jsx +19 -0
- package/src/project-browser.css +74 -121
- package/src/project-browser.jsx +36 -60
- package/src/props.jsx +4 -7
- package/src/retired-kinds.js +37 -0
- package/src/scene-objects.js +3 -7
- package/src/scenes.js +4 -5
- package/src/settings-menu.jsx +1 -20
- package/src/shell/BottomDock.jsx +9 -9
- package/src/shell/MenuBar.jsx +1 -9
- package/src/shell/PreferencesDialog.jsx +3 -4
- package/src/shell/ViewportToolbar.jsx +3 -3
- package/src/shell/add-camera-shot.js +11 -0
- package/src/shell/glass-regions.css +3 -2
- package/src/shell/glass.css +0 -5
- package/src/shell/lens-scrub-math.js +51 -0
- package/src/shell/lens-scrub.css +73 -0
- package/src/shell/lens-scrub.jsx +98 -0
- package/src/shell/mode.css +6 -5
- package/src/shell/preferences.css +1 -32
- package/src/shell/shell.css +18 -1
- package/src/shell/studio-shell-context.js +5 -0
- package/src/shot-frame.js +26 -0
- package/src/stage-canvas-rigs.jsx +7 -8
- package/src/stage-characters.jsx +26 -0
- package/src/stage-gizmo.js +43 -0
- package/src/stage-labels-ko.js +0 -6
- package/src/stage-startup-defaults.js +3 -9
- package/src/starter-scenes.js +5 -11
- package/src/studio-agent-commands.js +2 -2
- package/src/studio-agent-protocol.js +2 -7
- package/src/studio-app-binding.js +1 -1
- package/src/studio-elements.js +1 -1
- package/src/styles/themes.css +13 -0
- package/src/styles.css +6 -674
- package/src/text/text-object.jsx +1 -1
- package/src/ui.jsx +8 -16
- package/src/use-embed-bridge.js +3 -6
- package/src/use-render-activity.js +3 -1
- package/src/view-ghosts.jsx +5 -3
- package/src/viewport-visible-rect.js +37 -0
- package/src/workflow/AgentPanel.jsx +75 -42
- package/src/workflow/AgentProgressCard.jsx +1 -1
- package/src/workflow/TaskBoard.jsx +102 -0
- package/src/workflow/TaskInline.jsx +114 -0
- package/src/workflow/agent-client.js +11 -25
- package/src/workflow/agent-panel.css +14 -1
- package/src/workflow/attachment-image.js +10 -3
- package/src/workflow/attachment-video.js +91 -0
- package/src/workflow/task-board.css +98 -0
- package/src/workflow/task-board.js +169 -0
- package/tools/check-eslint.mjs +2 -1
- package/tools/check-knip.mjs +2 -1
- package/tools/dev/pages/greybox-to-video.html +2 -3
- package/tools/dev/pages/previs-software.html +2 -2
- package/tools/dev/pages/privacy.html +3 -7
- package/tools/dev/pages/seedance-camera-control.html +2 -2
- package/tools/dev-full.mjs +11 -2
- package/tools/eslint-baseline.json +6 -6
- package/tools/knip-baseline.json +0 -11
- package/tools/loc-baseline.json +30 -31
- package/tools/metrics-snapshot.mjs +43 -5
- package/tools/render-start-thumbs.mjs +175 -0
- package/tools/run-tests.mjs +7 -14
- package/tools/sitemap.mjs +1 -1
- package/dist/assets/analytics-BibyE5ar.js +0 -6
- package/dist/assets/app-DhRJ-PBz.js +0 -5088
- package/dist/assets/app-f-fkV5uP.css +0 -1
- package/dist/assets/compile-worker-DqiA4AaN.js +0 -5
- package/dist/assets/first-shot-handoff-Qx7SKl38.js +0 -1
- package/dist/assets/landing-DYg87MDY.js +0 -1
- package/dist/assets/scene-asset-sync-DBYQBR0T.js +0 -20
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +0 -1
- package/dist/assets/workflow-BSA22Ofr.js +0 -179
- package/dist/scenes/city-block.cclayproject +0 -1
- package/src/burn-in.js +0 -91
- package/src/camera-tutorial.jsx +0 -520
- package/src/facing-marks.js +0 -88
- package/src/first-shot-handoff.js +0 -44
- package/src/panels/motion.css +0 -197
- package/src/set-pieces.jsx +0 -183
- package/src/tutorial-analytics.js +0 -86
- package/src/tutorial-walk.js +0 -17
- package/src/use-camera-tutorial.js +0 -50
- package/src/use-semantic-state.js +0 -12
- package/tools/metrics/archive-traffic.mjs +0 -138
- package/tools/perf/ik-camera-drag.mjs +0 -138
package/src/App.jsx
CHANGED
|
@@ -42,7 +42,6 @@ import { browserSculptImages } from "./commands/sculpt-images.js";
|
|
|
42
42
|
import { sculptAgentClient, sculptModelLabel } from "./sculpt-mode.js";
|
|
43
43
|
import { useSculptMode } from "./use-sculpt-mode.js";
|
|
44
44
|
import { useFirstSuccessPrompt } from "./use-first-success-prompt.js";
|
|
45
|
-
import { useCameraTutorial } from "./use-camera-tutorial.js";
|
|
46
45
|
import { useEmbedBridge } from "./use-embed-bridge.js";
|
|
47
46
|
import { useAssetShelf } from "./use-asset-shelf.js";
|
|
48
47
|
import { StudioProtocolError } from "./studio-agent-protocol.js";
|
|
@@ -120,9 +119,6 @@ import {
|
|
|
120
119
|
import ProjectBrowser, { ProjectNameDialog } from "./project-browser.jsx";
|
|
121
120
|
import FirstSuccessGuide from "./first-success-guide.jsx";
|
|
122
121
|
import UseCaseQuestion from "./use-case-question.jsx";
|
|
123
|
-
import { CameraTutorial } from "./camera-tutorial.jsx";
|
|
124
|
-
import { createTutorialAnalytics } from "./tutorial-analytics.js";
|
|
125
|
-
import { cameraTutorialSuppressed, createFirstShotHandoff, rememberCameraTutorialTerminal } from "./first-shot-handoff.js";
|
|
126
122
|
import ObjectGizmo from "./object-gizmo.jsx";
|
|
127
123
|
import { useHeldObjectEdit } from "./held-object-edit.jsx";
|
|
128
124
|
|
|
@@ -141,7 +137,7 @@ import {
|
|
|
141
137
|
pathMetrics,
|
|
142
138
|
strokeToPathPoints,
|
|
143
139
|
} from "./object-path.js";
|
|
144
|
-
import { carriedPointAt, sceneObjectsAt } from "./object-travel.js"; import { objectTracksOf } from "./motion3/object-tracks.js"; import {
|
|
140
|
+
import { carriedPointAt, sceneObjectsAt } from "./object-travel.js"; import { objectTracksOf } from "./motion3/object-tracks.js"; import { ViewGhosts } from "./view-ghosts.jsx"; import { DraftGhost } from "./draft-ghost.jsx";
|
|
145
141
|
import { routeOwnerFor } from "./route-owner.js";
|
|
146
142
|
import {
|
|
147
143
|
analyticsActive,
|
|
@@ -157,7 +153,7 @@ import {
|
|
|
157
153
|
} from "./analytics.js";
|
|
158
154
|
import { ko, isKo } from "./locale.js";
|
|
159
155
|
import { characterCapabilities, kindRefusal, proxyFigureBounds } from "./character-kind.js";
|
|
160
|
-
import { STARTER_SCENES } from "./starter-scenes.js";
|
|
156
|
+
import { STARTER_SCENES, resolveSceneParam } from "./starter-scenes.js";
|
|
161
157
|
import { PART_COLOURS } from "./part-colours.js";
|
|
162
158
|
import {
|
|
163
159
|
DEFAULT_POSE,
|
|
@@ -217,11 +213,14 @@ import {
|
|
|
217
213
|
shotAtFrame,
|
|
218
214
|
shotIndexAtFrame,
|
|
219
215
|
} from "./cuts.js";
|
|
216
|
+
import { playbackFrame } from "./motion/playback-frame-store.js";
|
|
217
|
+
import { usePlaybackSubjectPosition } from "./motion/playback-subject.js";
|
|
218
|
+
import { characterGizmoObject as buildCharacterGizmoObject, characterGizmoView, followCameraActive, playbackSceneWithTrack, updateShotMetrics } from "./stage-gizmo.js";
|
|
220
219
|
import {
|
|
221
220
|
CameraGlide,
|
|
222
221
|
CameraRailScenePreview,
|
|
223
222
|
CaptureRig,
|
|
224
|
-
|
|
223
|
+
PlaybackCharacter,
|
|
225
224
|
ContextLossGuard,
|
|
226
225
|
CraneHandles,
|
|
227
226
|
DEFAULT_CAMERA_POSITION,
|
|
@@ -381,35 +380,9 @@ export default function App() {
|
|
|
381
380
|
getBus: () => studioBindingRef.current.bus,
|
|
382
381
|
notify: (...args) => setToast(...args),
|
|
383
382
|
}));
|
|
384
|
-
const { embedMode, playgroundMode
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
if (!cameraTutorialQuery || cameraTutorialStarted.current) return;
|
|
388
|
-
// Storyboard projects never auto-open the seven-step camera tutorial.
|
|
389
|
-
if (scenesDomain.previsMode === "storyboard") return;
|
|
390
|
-
cameraTutorialStarted.current = true;
|
|
391
|
-
void startCameraTutorialRef.current?.({ source: cameraTutorialEntry });
|
|
392
|
-
}, [cameraTutorialQuery, cameraTutorialEntry]);
|
|
393
|
-
useEffect(() => {
|
|
394
|
-
const onTutorial = (event) => {
|
|
395
|
-
if (event.detail?.open === false) {
|
|
396
|
-
setCameraTutorial(false);
|
|
397
|
-
cameraTutorialAnalytics.current?.dismiss();
|
|
398
|
-
rememberCameraTutorialTerminal(cameraTutorialCompletedRef.current ? "completed" : "dismissed");
|
|
399
|
-
tutorialProjectEpochRef.current += 1;
|
|
400
|
-
setTutorialSeedPending(false);
|
|
401
|
-
setCameraTutorialHandoff(null);
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
if (scenesDomain.previsMode === "storyboard") return;
|
|
405
|
-
void startCameraTutorialRef.current?.({ source: event.detail?.source ?? "settings" });
|
|
406
|
-
};
|
|
407
|
-
window.addEventListener("cozyclay:camera-tutorial", onTutorial);
|
|
408
|
-
return () => window.removeEventListener("cozyclay:camera-tutorial", onTutorial);
|
|
409
|
-
}, []);
|
|
410
|
-
useEffect(() => {
|
|
411
|
-
if (cameraTutorial) trackFeature("camera_tutorial");
|
|
412
|
-
}, [cameraTutorial]);
|
|
383
|
+
const { embedMode, playgroundMode } = useEmbedBridge();
|
|
384
|
+
const exportMenuTriggerRef = useRef(null);
|
|
385
|
+
const exportShotIdRef = useRef(null);
|
|
413
386
|
useEffect(() => {
|
|
414
387
|
if (!embedMode) return undefined;
|
|
415
388
|
// The Workflow page's Scene node embeds the studio as its preview, so the
|
|
@@ -472,7 +445,7 @@ export default function App() {
|
|
|
472
445
|
if (!firstEditRef.current) {
|
|
473
446
|
const firstEdit = createFirstEditTracker(track);
|
|
474
447
|
// Observe the existing semantic boundary once, even after its telemetry
|
|
475
|
-
// gate is satisfied. The
|
|
448
|
+
// gate is satisfied. The semantic hook itself stays unchanged.
|
|
476
449
|
firstEditRef.current = (surface, domain, before, after) => {
|
|
477
450
|
if (before !== after) sceneRevisionRef.current += 1;
|
|
478
451
|
studioBindingRef.current?.invalidate(domain, before, after);
|
|
@@ -493,8 +466,6 @@ export default function App() {
|
|
|
493
466
|
// One semantic hook for authored UI and programmatic mutations. Passive
|
|
494
467
|
// setters intentionally bypass it (navigation, load, seed, restore, history).
|
|
495
468
|
const markSemanticEdit = (domain, before, after) => {
|
|
496
|
-
tutorialProjectEpochRef.current += 1;
|
|
497
|
-
if (tutorialSeedEpochRef.current !== null) setTutorialSeedPending(false);
|
|
498
469
|
return firstEditRef.current(playgroundMode ? "playground" : "craft", domain, before, after);
|
|
499
470
|
};
|
|
500
471
|
const craftActionTrackedRef = useRef(false);
|
|
@@ -506,31 +477,11 @@ export default function App() {
|
|
|
506
477
|
track(playgroundMode ? "playground:first_action" : "craft:first_action", { action_kind: actionKind });
|
|
507
478
|
};
|
|
508
479
|
useEffect(() => {
|
|
509
|
-
if (!playgroundMode) return
|
|
480
|
+
if (!playgroundMode) return;
|
|
510
481
|
track("playground:opened");
|
|
511
482
|
// The landing page keeps a loading veil over the iframe until the
|
|
512
483
|
// studio has actually mounted; a bare `load` fires far too early.
|
|
513
484
|
window.parent?.postMessage({ type: "cozyclay:playground-ready" }, "*");
|
|
514
|
-
// Camera gestures feed the landing page's tutorial checklist, and the
|
|
515
|
-
// checklist points back at one control (the shot look-through) by hint.
|
|
516
|
-
const onNav = (event) => window.parent?.postMessage({ type: "cozyclay:playground-nav", kind: event.detail?.kind, key: event.detail?.key ?? null }, "*");
|
|
517
|
-
const onSignal = (event) => window.parent?.postMessage({ type: "cozyclay:playground-nav", kind: event.detail?.kind }, "*");
|
|
518
|
-
window.addEventListener("cozyclay:playground-signal", onSignal);
|
|
519
|
-
const onHint = (event) => {
|
|
520
|
-
if (event.source !== window.parent || event.origin !== window.location.origin) return;
|
|
521
|
-
if (event.data?.type === "cozyclay:playground-hint") setPlaygroundHint(typeof event.data.kind === "string" ? event.data.kind : null);
|
|
522
|
-
if (event.data?.type === "cozyclay:playground-export") {
|
|
523
|
-
// The visitor keeps what they made: the landing page turns this
|
|
524
|
-
// into a .cclayproject download they can open after npx cozyclay.
|
|
525
|
-
playgroundExportRef.current?.("City Block").then(
|
|
526
|
-
(serialized) => window.parent?.postMessage({ type: "cozyclay:playground-export-result", serialized }, "*"),
|
|
527
|
-
(error) => window.parent?.postMessage({ type: "cozyclay:playground-export-result", error: String(error?.message ?? error) }, "*"),
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
window.addEventListener("cozyclay:nav", onNav);
|
|
532
|
-
window.addEventListener("message", onHint);
|
|
533
|
-
return () => { window.removeEventListener("cozyclay:nav", onNav); window.removeEventListener("cozyclay:playground-signal", onSignal); window.removeEventListener("message", onHint); };
|
|
534
485
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
535
486
|
}, []);
|
|
536
487
|
const [startup] = useState(loadSceneStartup);
|
|
@@ -649,26 +600,11 @@ export default function App() {
|
|
|
649
600
|
// bridge read this global, and the render path is still PlayView's.
|
|
650
601
|
globalThis.playMode = preview;
|
|
651
602
|
const playMode = preview;
|
|
652
|
-
// #441 moved shot authoring into commands and dropped the Shot and Rail
|
|
653
|
-
// signals the landing checklist waits for. Read them from the authored state,
|
|
654
|
-
// as the studio tutorial does (#586): a shot added here, and a rail drawn.
|
|
655
|
-
// Declared before the play signal: a drawn rail starts playback in the same
|
|
656
|
-
// commit, and the checklist expects Rail before Play.
|
|
657
|
-
const playgroundShotCount = useRef(null);
|
|
658
|
-
useEffect(() => {
|
|
659
|
-
if (!playgroundMode) return;
|
|
660
|
-
if (playgroundShotCount.current !== null && shots.length > playgroundShotCount.current) {
|
|
661
|
-
window.parent?.postMessage({ type: "cozyclay:playground-nav", kind: "shot" }, "*");
|
|
662
|
-
}
|
|
663
|
-
playgroundShotCount.current = shots.length;
|
|
664
|
-
}, [playgroundMode, shots.length]);
|
|
665
|
-
const playgroundRailReady = playgroundMode && shots.some((shot) => Array.isArray(shot.camera?.cameraRail) && shot.camera.cameraRail.length >= 2);
|
|
666
|
-
useEffect(() => {
|
|
667
|
-
if (playgroundRailReady) window.parent?.postMessage({ type: "cozyclay:playground-nav", kind: "rail" }, "*");
|
|
668
|
-
}, [playgroundRailReady]);
|
|
669
603
|
// Preview is the player for the finished motion: entering starts playback,
|
|
670
604
|
// leaving pauses it. The editor view stays the manipulation surface.
|
|
671
605
|
const [tlPlaying, setTlPlaying] = useState(false);
|
|
606
|
+
// Playback ticks stay outside React; stopping commits the live frame back to
|
|
607
|
+
// the edit-time state so exports and agent reads see the stopped frame.
|
|
672
608
|
useEffect(() => {
|
|
673
609
|
if (playgroundMode && tlPlaying) window.parent?.postMessage({ type: "cozyclay:playground-nav", kind: "play" }, "*");
|
|
674
610
|
}, [playgroundMode, tlPlaying]);
|
|
@@ -684,9 +620,6 @@ export default function App() {
|
|
|
684
620
|
// The Workflow page embeds this Studio as the Scene node's preview; that
|
|
685
621
|
// preview must show what the node captures on Run: the shot camera's view.
|
|
686
622
|
const [lookThroughShot, setLookThroughShot] = useState(embedMode);
|
|
687
|
-
useEffect(() => {
|
|
688
|
-
if (playgroundMode && lookThroughShot) window.parent?.postMessage({ type: "cozyclay:playground-nav", kind: "shot" }, "*");
|
|
689
|
-
}, [playgroundMode, lookThroughShot]);
|
|
690
623
|
useEffect(() => {
|
|
691
624
|
if (!lookThroughShot || embedMode) return undefined;
|
|
692
625
|
const onKey = (event) => {
|
|
@@ -1135,7 +1068,8 @@ export default function App() {
|
|
|
1135
1068
|
get motionFullRef() { return motionFullRef; },
|
|
1136
1069
|
get poseMemberAtFrame() { return poseMemberAtFrame; },
|
|
1137
1070
|
get posedRig() { return posedRig; },
|
|
1138
|
-
|
|
1071
|
+
// Pose mode enters from the view the author is looking through (motion.js toggleIkMode).
|
|
1072
|
+
get poserCamRef() { return poserCamRef; }, get editorCamRef() { return editorCamRef; }, get editorLook() { return editorLook; },
|
|
1139
1073
|
get poserLook() { return poserLook; },
|
|
1140
1074
|
get posing() { return posing; },
|
|
1141
1075
|
get posingChar() { return posingChar; },
|
|
@@ -1163,9 +1097,9 @@ export default function App() {
|
|
|
1163
1097
|
get tlFrame() { return tlFrame; },
|
|
1164
1098
|
get tlFrameCount() { return tlFrameCount; },
|
|
1165
1099
|
get tlFrameRef() { return tlFrameRef; },
|
|
1100
|
+
get propFrameRef() { return propFrameRef; },
|
|
1166
1101
|
|
|
1167
1102
|
|
|
1168
|
-
get tutorialProjectEpochRef() { return tutorialProjectEpochRef; },
|
|
1169
1103
|
get waitForRig() { return waitForRig; },
|
|
1170
1104
|
get waypointMode() { return waypointMode; },
|
|
1171
1105
|
get waypoints() { return waypoints; },
|
|
@@ -1392,7 +1326,6 @@ export default function App() {
|
|
|
1392
1326
|
const scenesDomain = useScenes(appContext.forRender({
|
|
1393
1327
|
get activeSceneIdRef() { return activeSceneIdRef; },
|
|
1394
1328
|
get actorStageRef() { return actorStageRef; },
|
|
1395
|
-
get cameraTutorialQuery() { return cameraTutorialQuery; },
|
|
1396
1329
|
get customPoses() { return customPoses; },
|
|
1397
1330
|
get dirtyRef() { return dirtyRef; },
|
|
1398
1331
|
get exportShotIdRef() { return exportShotIdRef; },
|
|
@@ -1414,8 +1347,6 @@ export default function App() {
|
|
|
1414
1347
|
get saveFailureToastRef() { return saveFailureToastRef; },
|
|
1415
1348
|
get setActiveCharacterId() { return setActiveCharacterId; },
|
|
1416
1349
|
get setActiveWaypointId() { return setActiveWaypointId; },
|
|
1417
|
-
get setCameraTutorial() { return setCameraTutorial; },
|
|
1418
|
-
get setCameraTutorialHandoff() { return setCameraTutorialHandoff; },
|
|
1419
1350
|
get setFirstSuccessGuideOpen() { return setFirstSuccessGuideOpen; },
|
|
1420
1351
|
get setHasCharSheet() { return setHasCharSheet; },
|
|
1421
1352
|
get setMovePlaying() { return setMovePlaying; },
|
|
@@ -1428,7 +1359,6 @@ export default function App() {
|
|
|
1428
1359
|
get setShots() { return setShots; },
|
|
1429
1360
|
get setTlFrame() { return setTlFrame; },
|
|
1430
1361
|
get setTlFrameCount() { return setTlFrameCount; },
|
|
1431
|
-
get setTutorialSeedPending() { return setTutorialSeedPending; },
|
|
1432
1362
|
get setWaypoints() { return setWaypoints; },
|
|
1433
1363
|
get setWorkspaceLayout() { return setWorkspaceLayout; },
|
|
1434
1364
|
get shotDocumentRef() { return shotDocumentRef; },
|
|
@@ -1439,9 +1369,6 @@ export default function App() {
|
|
|
1439
1369
|
get studioDocumentEpochRef() { return studioDocumentEpochRef; },
|
|
1440
1370
|
get studioHistoryRef() { return studioHistoryRef; },
|
|
1441
1371
|
get studioSceneEpochRef() { return studioSceneEpochRef; },
|
|
1442
|
-
get tutorialProjectEpochRef() { return tutorialProjectEpochRef; },
|
|
1443
|
-
get tutorialSeedEpochRef() { return tutorialSeedEpochRef; },
|
|
1444
|
-
get tutorialStarterRef() { return tutorialStarterRef; },
|
|
1445
1372
|
get castDomain() { return castDomain; },
|
|
1446
1373
|
get objectsDomain() { return objectsDomain; },
|
|
1447
1374
|
}));
|
|
@@ -1821,11 +1748,6 @@ export default function App() {
|
|
|
1821
1748
|
const [mode, setMode] = useState("image");
|
|
1822
1749
|
const [imageModel, setImageModel] = useState("gpt_image_2");
|
|
1823
1750
|
|
|
1824
|
-
useEffect(() => {
|
|
1825
|
-
if (playgroundMode && movePlaying) window.parent?.postMessage({ type: "cozyclay:playground-nav", kind: "play" }, "*");
|
|
1826
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1827
|
-
}, [playgroundMode, movePlaying]);
|
|
1828
|
-
|
|
1829
1751
|
// Object travel-path drawing: the same Top-View stroke gesture as the rail,
|
|
1830
1752
|
// aimed at the selected object instead of the shot camera.
|
|
1831
1753
|
const [pathDraw, setPathDraw] = useState(false);
|
|
@@ -1843,9 +1765,9 @@ export default function App() {
|
|
|
1843
1765
|
// The frame props follow. Live playback keeps it at the playhead; the
|
|
1844
1766
|
// offscreen export drives it per captured frame without re-rendering.
|
|
1845
1767
|
const propFrameRef = useRef(0);
|
|
1846
|
-
|
|
1847
1768
|
const [cameraPos, setCameraPos] = useState(DEFAULT_CAMERA_POSITION);
|
|
1848
1769
|
const [subjectVisible, setSubjectVisible] = useState(true);
|
|
1770
|
+
const subjectVisibleRef = useRef(true);
|
|
1849
1771
|
const mcpCaptureRef = useRef(null);
|
|
1850
1772
|
const liveControlRef = useRef(null);
|
|
1851
1773
|
const [liveWorkspaceHandle, setLiveWorkspaceHandle] = useState(null);
|
|
@@ -2058,7 +1980,11 @@ export default function App() {
|
|
|
2058
1980
|
}
|
|
2059
1981
|
|
|
2060
1982
|
|
|
2061
|
-
const renderActive = useRenderActivity(tlPlaying || movePlaying)
|
|
1983
|
+
const renderActive = useRenderActivity(tlPlaying || movePlaying, { playing: tlPlaying, commit: shotsDomain.commitPlaybackFrame, onStop: () => {
|
|
1984
|
+
const position = shotCameraPosRef.current;
|
|
1985
|
+
if (position) setCameraPos(position);
|
|
1986
|
+
setSubjectVisible(subjectVisibleRef.current);
|
|
1987
|
+
} });
|
|
2062
1988
|
// the clip length on the production clock
|
|
2063
1989
|
|
|
2064
1990
|
// Keep the imperative frame in step with the playhead for live playback;
|
|
@@ -2348,96 +2274,18 @@ export default function App() {
|
|
|
2348
2274
|
};
|
|
2349
2275
|
}, []);
|
|
2350
2276
|
|
|
2351
|
-
const tutorialInitialSnapshotRef = useRef(null);
|
|
2352
|
-
if (tutorialInitialSnapshotRef.current === null) tutorialInitialSnapshotRef.current = collectProjectSnapshot("Tutorial");
|
|
2353
|
-
|
|
2354
|
-
playgroundExportRef.current = collectProjectSerialized;
|
|
2355
|
-
|
|
2356
|
-
function closeCameraTutorial(reason = null) {
|
|
2357
|
-
const terminal = reason ?? (cameraTutorialCompletedRef.current ? "completed" : "dismissed");
|
|
2358
|
-
rememberCameraTutorialTerminal(terminal);
|
|
2359
|
-
tutorialProjectEpochRef.current += 1;
|
|
2360
|
-
setTutorialSeedPending(false);
|
|
2361
|
-
cameraTutorialHandoff?.dismiss();
|
|
2362
|
-
setCameraTutorialHandoff(null);
|
|
2363
|
-
cameraTutorialAnalytics.current?.dismiss();
|
|
2364
|
-
cameraTutorialAnalytics.current = null;
|
|
2365
|
-
cameraTutorialCompletedRef.current = false;
|
|
2366
|
-
setCameraTutorial(false);
|
|
2367
|
-
setCameraTutorialStep(null);
|
|
2368
|
-
}
|
|
2369
|
-
|
|
2370
|
-
function openExportMenuForShot(shotId) {
|
|
2371
|
-
const target = shots.find((entry) => entry.id === shotId);
|
|
2372
|
-
if (!target) return;
|
|
2373
|
-
exportShotIdRef.current = target.id;
|
|
2374
|
-
const trigger = exportMenuTriggerRef.current;
|
|
2375
|
-
if (trigger) {
|
|
2376
|
-
const box = trigger.getBoundingClientRect();
|
|
2377
|
-
const menuWidth = Math.min(340, window.innerWidth - 16);
|
|
2378
|
-
setExportMenuAnchor({
|
|
2379
|
-
top: box.bottom + 6,
|
|
2380
|
-
right: Math.min(Math.max(8, window.innerWidth - box.right), Math.max(8, window.innerWidth - menuWidth - 8)),
|
|
2381
|
-
});
|
|
2382
|
-
}
|
|
2383
|
-
setExportMenuOpen(true);
|
|
2384
|
-
cameraTutorialHandoff?.dismiss();
|
|
2385
|
-
setCameraTutorialHandoff(null);
|
|
2386
|
-
}
|
|
2387
|
-
|
|
2388
|
-
/** The camera tutorial's single entry (#209), for both /app/?tutorial=camera
|
|
2389
|
-
* and the Settings ▾ item.
|
|
2390
|
-
*
|
|
2391
|
-
* The seven steps teach Shot / Rail / Play, which need a set and somebody
|
|
2392
|
-
* moving through it. On cozyclay.org they get both for free: the landing
|
|
2393
|
-
* playground opens the city-block starter and the hosted-demo seed below
|
|
2394
|
-
* loads the walk take because a statically served build has no motion
|
|
2395
|
-
* bridge. A local session HAS a bridge, so that seed is skipped and the
|
|
2396
|
-
* tutorial used to open on whatever was loaded — usually an empty room.
|
|
2397
|
-
* This puts the studio in the landing page's state explicitly. */
|
|
2398
|
-
async function startCameraTutorial({ source = "settings" } = {}) {
|
|
2399
|
-
if (embedMode || playgroundMode || tutorialLoadingRef.current) return;
|
|
2400
|
-
// QA hook, same spirit as window.__cozyclayRenders: which door the tutorial
|
|
2401
|
-
// came in by, so a headless run can prove both of them land here.
|
|
2402
|
-
window.__cozyclayTutorialSource = source;
|
|
2403
|
-
// Only a pristine, newly created document receives the sample. An
|
|
2404
|
-
// existing project (even unnamed), or a restart, keeps all current work.
|
|
2405
|
-
const seed = !tutorialStarterRef.current && startupCreatedScene && projectName === null
|
|
2406
|
-
&& !projectDirty && !cameraTutorialSuppressed()
|
|
2407
|
-
&& tutorialInitialSnapshotRef.current === collectProjectSnapshot("Tutorial");
|
|
2408
|
-
if (seed) {
|
|
2409
|
-
tutorialLoadingRef.current = true;
|
|
2410
|
-
try {
|
|
2411
|
-
const opened = await openStarterScene("city-block", "tutorial");
|
|
2412
|
-
if (opened) {
|
|
2413
|
-
tutorialStarterRef.current = true;
|
|
2414
|
-
tutorialSeedEpochRef.current = tutorialProjectEpochRef.current;
|
|
2415
|
-
setTutorialSeedPending(true);
|
|
2416
|
-
exitPreview();
|
|
2417
|
-
setTlFrame(0);
|
|
2418
|
-
}
|
|
2419
|
-
} finally {
|
|
2420
|
-
tutorialLoadingRef.current = false;
|
|
2421
|
-
}
|
|
2422
|
-
}
|
|
2423
|
-
setProjectStartupOpen(false);
|
|
2424
|
-
setFirstSuccessGuideOpen(false);
|
|
2425
|
-
setCameraTutorialHandoff(createFirstShotHandoff());
|
|
2426
|
-
// Explicit start, including while already open, resets the existing
|
|
2427
|
-
// done/walked component state. Passive renders never create an attempt.
|
|
2428
|
-
cameraTutorialAnalytics.current = createTutorialAnalytics({ surface: "studio", startSource: source });
|
|
2429
|
-
setCameraTutorialAttempt((attempt) => attempt + 1);
|
|
2430
|
-
setCameraTutorial(true);
|
|
2431
|
-
cameraTutorialCompletedRef.current = false;
|
|
2432
|
-
}
|
|
2433
|
-
startCameraTutorialRef.current = startCameraTutorial;
|
|
2434
|
-
|
|
2435
2277
|
const starterOpened = useRef(false);
|
|
2436
2278
|
useEffect(() => {
|
|
2437
2279
|
if (starterOpened.current || playgroundMode) return;
|
|
2438
2280
|
const requested = new URLSearchParams(globalThis.location?.search || "").get("scene");
|
|
2439
2281
|
if (!requested) return;
|
|
2440
2282
|
starterOpened.current = true;
|
|
2283
|
+
// An id this build does not ship (a removed starter, a typo) is ignored:
|
|
2284
|
+
// the studio opens as it would without ?scene=.
|
|
2285
|
+
if (!resolveSceneParam(requested)) {
|
|
2286
|
+
console.warn(`cozyclay: ?scene=${requested} is not a starter scene in this build; ignored.`);
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2441
2289
|
void openStarterScene(requested, "launch");
|
|
2442
2290
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2443
2291
|
}, []);
|
|
@@ -2731,6 +2579,8 @@ export default function App() {
|
|
|
2731
2579
|
});
|
|
2732
2580
|
return [{
|
|
2733
2581
|
id: entry.id,
|
|
2582
|
+
entry,
|
|
2583
|
+
clip,
|
|
2734
2584
|
model: entry.model,
|
|
2735
2585
|
...(isProxyFigure(entry) ? {} : { url: characterModelUrl(entry.model) }),
|
|
2736
2586
|
position: [placement.x, entry.y ?? 0, placement.z],
|
|
@@ -2754,33 +2604,14 @@ export default function App() {
|
|
|
2754
2604
|
// character so the motion tab keeps working, but a move gizmo hanging in
|
|
2755
2605
|
// the viewport while the camera or a prop is selected reads as a stray
|
|
2756
2606
|
// widget.
|
|
2757
|
-
const gizmoView =
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
const hasTake = activeChar.id === activeCharacterId ? Boolean(motion) : Boolean(activeChar.sessionMotion);
|
|
2762
|
-
const placement = hasTake ? entry : placementAt(entry, tlFrame, { shotAt: frame => shotAtFrame(shots, frame) });
|
|
2763
|
-
return { position: [placement.x, entry.y ?? 0, placement.z], rot: placement.rot };
|
|
2764
|
-
}, [characters, activeChar.id, motion, shots, tlFrame, selectedHierarchyId]);
|
|
2607
|
+
const gizmoView = characterGizmoView({
|
|
2608
|
+
selected: Boolean(charIdFromHierarchyId(selectedHierarchyId)), characters, activeCharacterId,
|
|
2609
|
+
activeCharacter: activeChar, motion, shots, activeId: activeChar.id, frame: tlFrame,
|
|
2610
|
+
});
|
|
2765
2611
|
// The cast rides the SAME gizmo as scene objects — one movement grammar
|
|
2766
2612
|
// for everything on stage. The proxy hands ObjectGizmo the object shape
|
|
2767
2613
|
// it expects; `height` puts the pivot at the hips like a prop's centre.
|
|
2768
|
-
const characterGizmoObject =
|
|
2769
|
-
if (!gizmoView) return null;
|
|
2770
|
-
const entry = characters.find((item) => item.id === activeChar.id);
|
|
2771
|
-
return {
|
|
2772
|
-
id: "__character__",
|
|
2773
|
-
x: gizmoView.position[0],
|
|
2774
|
-
y: gizmoView.position[1],
|
|
2775
|
-
z: gizmoView.position[2],
|
|
2776
|
-
height: 1.15,
|
|
2777
|
-
footprint: { width: 0.6, depth: 0.6 },
|
|
2778
|
-
rotY: gizmoView.rot ?? entry?.rot ?? 0,
|
|
2779
|
-
scaleX: entry?.scale ?? 1,
|
|
2780
|
-
scaleY: entry?.scale ?? 1,
|
|
2781
|
-
scaleZ: entry?.scale ?? 1,
|
|
2782
|
-
};
|
|
2783
|
-
}, [gizmoView, characters, activeChar.id]);
|
|
2614
|
+
const characterGizmoObject = buildCharacterGizmoObject(gizmoView, characters.find((item) => item.id === activeChar.id));
|
|
2784
2615
|
|
|
2785
2616
|
/* --------------------- per-character layer buffers ---------------------
|
|
2786
2617
|
* waypoints / motion above are the EDITING BUFFER of the
|
|
@@ -3263,16 +3094,6 @@ export default function App() {
|
|
|
3263
3094
|
);
|
|
3264
3095
|
}
|
|
3265
3096
|
|
|
3266
|
-
// Observe the existing export UI boundary, independently of telemetry.
|
|
3267
|
-
// The first transition covers ordinary menu exports and fast failures too;
|
|
3268
|
-
// progress updates do not write storage on every encoded frame.
|
|
3269
|
-
const hasExportAttempt = exportStatus !== null;
|
|
3270
|
-
useEffect(() => {
|
|
3271
|
-
if (!hasExportAttempt) return;
|
|
3272
|
-
rememberCameraTutorialTerminal("export_started");
|
|
3273
|
-
setCameraTutorialHandoff(null);
|
|
3274
|
-
}, [hasExportAttempt]);
|
|
3275
|
-
|
|
3276
3097
|
function downloadOtioCutList() {
|
|
3277
3098
|
if (!shots.length) {
|
|
3278
3099
|
setToast(ko("Add at least one Shot before exporting OTIO", "OTIO를 내보내려면 샷을 하나 이상 추가하세요"));
|
|
@@ -3772,22 +3593,6 @@ export default function App() {
|
|
|
3772
3593
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3773
3594
|
}, []);
|
|
3774
3595
|
|
|
3775
|
-
// The camera tutorial's seed (#209). startCameraTutorial arms
|
|
3776
|
-
// tutorialSeedPending once the starter scene is applied, and a compiled walk
|
|
3777
|
-
// (take v3) goes on as soon as that scene's character has a parsed rig: a
|
|
3778
|
-
// state condition, not a timer. The compile runs in the worker; an edit that lands
|
|
3779
|
-
// before it answers makes the job's fence refuse the install (set dressing, no error).
|
|
3780
|
-
useEffect(() => {
|
|
3781
|
-
if (!tutorialSeedPending || !activeRig) return;
|
|
3782
|
-
const seedEpoch = tutorialSeedEpochRef.current;
|
|
3783
|
-
tutorialSeedEpochRef.current = null;
|
|
3784
|
-
setTutorialSeedPending(false);
|
|
3785
|
-
if (seedEpoch === null || seedEpoch !== tutorialProjectEpochRef.current) return;
|
|
3786
|
-
const spec = tutorialWalkSpec(activeChar); // the take is set dressing, never an error to act on
|
|
3787
|
-
if (spec && runStudioAction("motion.compile", { characterId: activeChar.id, spec })) { setTlFrame(0); setTlPlaying(false); }
|
|
3788
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3789
|
-
}, [tutorialSeedPending, activeRig]);
|
|
3790
|
-
|
|
3791
3596
|
// Drive every cast member from ITS OWN clip on the shared playhead. The
|
|
3792
3597
|
// active character's buffer motion and the stored session motions of the
|
|
3793
3598
|
// others all advance together; characters without a clip keep their pose.
|
|
@@ -3868,7 +3673,7 @@ export default function App() {
|
|
|
3868
3673
|
// (test/motion3/qa-motion3-*-browser.mjs). Harmless in normal use.
|
|
3869
3674
|
useEffect(() => {
|
|
3870
3675
|
window.__cozyclay = {
|
|
3871
|
-
runStudioAction, setFrame: setTlFrame,
|
|
3676
|
+
runStudioAction, setFrame: setTlFrame, setPlaying: setTlPlaying,
|
|
3872
3677
|
rigA: activeRig, motion, tlFrame, frameCount: tlFrameCount, playing: tlPlaying, ikMode, ikChains, ikFocus, contactRadii: ikChains?.values().next().value?.contactRadii ?? null, ik: ikStateRef.current,
|
|
3873
3678
|
rangePins, rangePinResiduals,
|
|
3874
3679
|
rangePinApplySpec: motionDomain.rangePinApplySpec,
|
|
@@ -4050,11 +3855,7 @@ export default function App() {
|
|
|
4050
3855
|
return { x: at.x, z: at.z };
|
|
4051
3856
|
};
|
|
4052
3857
|
}, [charA.parent, charA.y, sceneObjects, tlFrameCount, tlFps]);
|
|
4053
|
-
const motionPos =
|
|
4054
|
-
if (!motion && !subjectCarry) return null;
|
|
4055
|
-
const subject = sampleAt(playbackSceneBase, null, tlFrame).subject;
|
|
4056
|
-
return subjectCarry ? subjectCarry(subject, tlFrame) : subject;
|
|
4057
|
-
}, [motion, subjectCarry, playbackSceneBase, tlFrame]);
|
|
3858
|
+
const motionPos = usePlaybackSubjectPosition({ motion, subjectCarry, scene: playbackSceneBase, frame: tlFrame });
|
|
4058
3859
|
|
|
4059
3860
|
// The subject's full per-frame scene trajectory — what the follow camera
|
|
4060
3861
|
// is derived from. Without a loaded motion, and riding nothing, the subject
|
|
@@ -4075,11 +3876,7 @@ export default function App() {
|
|
|
4075
3876
|
// A rail shot with an aim holds its cast (or a world point) instead of the subject.
|
|
4076
3877
|
const followTrack = useShotCameraTracks({ shots, subjectTrack, subjectId: charA.id, fps: tlFps, yawDeg: charA.rot, filmback, characters, objects: sceneObjects, frameCount: tlFrameCount, activeId: activeChar.id, motion });
|
|
4077
3878
|
|
|
4078
|
-
const playbackScene = useMemo(() => (
|
|
4079
|
-
...playbackSceneBase,
|
|
4080
|
-
subjectTrack,
|
|
4081
|
-
cameraTrack: followTrack,
|
|
4082
|
-
}), [playbackSceneBase, subjectTrack, followTrack]);
|
|
3879
|
+
const playbackScene = useMemo(() => playbackSceneWithTrack(playbackSceneBase, subjectTrack, followTrack), [playbackSceneBase, subjectTrack, followTrack]);
|
|
4083
3880
|
|
|
4084
3881
|
// Browser acceptance seam: it invokes the exact production exporter but
|
|
4085
3882
|
// suppresses the download, returning only serializable evidence.
|
|
@@ -4120,8 +3917,7 @@ export default function App() {
|
|
|
4120
3917
|
|
|
4121
3918
|
// The follow camera owns the shot camera in the same situations key
|
|
4122
3919
|
// following would: never while an authoring mode holds the viewport.
|
|
4123
|
-
const followCamActive =
|
|
4124
|
-
activeCamera.mode !== "keys" && !!followTrack?.[tlFrame] && (preview || (!ikMode && !waypointMode && !posing));
|
|
3920
|
+
const followCamActive = followCameraActive(activeCamera.mode, followTrack, tlPlaying ? playbackFrame() : tlFrame, preview, ikMode, waypointMode, posing);
|
|
4125
3921
|
|
|
4126
3922
|
// Implied locomotion speed per authored segment, on the timeline clock
|
|
4127
3923
|
// (m/s is physical, so the judge always uses tlFps). Shown in the
|
|
@@ -4824,7 +4620,7 @@ export default function App() {
|
|
|
4824
4620
|
return (
|
|
4825
4621
|
<AppContext.Provider value={appContext}>
|
|
4826
4622
|
<StudioShellContext.Provider value={shellContext}>
|
|
4827
|
-
<StudioShell className={"app" + (renderActive ? "" : " render-idle")} style={workspaceStyle} data-workflow-mode={workflowMode} data-previs-mode={scenesDomain.previsMode} data-embed-mode={embedMode ? "playview" : playgroundMode ? "playground" : undefined} data-
|
|
4623
|
+
<StudioShell className={"app" + (renderActive ? "" : " render-idle")} style={workspaceStyle} data-workflow-mode={workflowMode} data-previs-mode={scenesDomain.previsMode} data-embed-mode={embedMode ? "playview" : playgroundMode ? "playground" : undefined} data-rail-draw={railDraw ? 1 : undefined} data-shot-look={lookThroughShot && !embedMode ? 1 : undefined} data-plan-draw={railDraw || pathDraw || waypointMode ? 1 : undefined}
|
|
4828
4624
|
viewport={
|
|
4829
4625
|
<div className="viewport" data-drop={viewportDrop.over ? "over" : undefined} {...viewportDrop.handlers}
|
|
4830
4626
|
onWheel={(event) => {
|
|
@@ -4843,26 +4639,6 @@ export default function App() {
|
|
|
4843
4639
|
</div>
|
|
4844
4640
|
)}
|
|
4845
4641
|
|
|
4846
|
-
{/* Sits under the mode tabs and left of the Top-View inset, over the
|
|
4847
|
-
stage it is teaching. The overlay itself never takes the pointer
|
|
4848
|
-
(styles.css) — every step is completed in the studio underneath. */}
|
|
4849
|
-
{cameraTutorial && !embedMode && (
|
|
4850
|
-
<CameraTutorial
|
|
4851
|
-
key={cameraTutorialAttempt}
|
|
4852
|
-
analytics={cameraTutorialAnalytics.current}
|
|
4853
|
-
previewing={lookThroughShot}
|
|
4854
|
-
previsMode={scenesDomain.previsMode}
|
|
4855
|
-
shotCount={shots.length}
|
|
4856
|
-
railReady={shots.some((shot) => Array.isArray(shot.camera?.cameraRail) && shot.camera.cameraRail.length >= 2)}
|
|
4857
|
-
onStepChange={setCameraTutorialStep}
|
|
4858
|
-
onComplete={() => { cameraTutorialCompletedRef.current = true; cameraTutorialHandoff?.complete(); }}
|
|
4859
|
-
onClose={() => closeCameraTutorial()}
|
|
4860
|
-
handoff={cameraTutorialHandoff}
|
|
4861
|
-
shotId={activeShot?.id ?? null}
|
|
4862
|
-
onOpenExport={openExportMenuForShot}
|
|
4863
|
-
onContinue={() => closeCameraTutorial()}
|
|
4864
|
-
/>
|
|
4865
|
-
)}
|
|
4866
4642
|
<div className="stage" id="stage" ref={stageRef} data-render-loop={renderActive ? "always" : "demand"}>
|
|
4867
4643
|
{/* Shadows were off, so every castShadow in props.jsx was inert and
|
|
4868
4644
|
nothing on the open stage ever touched the floor. A contact
|
|
@@ -4998,7 +4774,10 @@ export default function App() {
|
|
|
4998
4774
|
take={{ frameCount: tlFrameCount, fps: tlFps, objectTracks }}
|
|
4999
4775
|
registryRef={carrierRegistryRef}
|
|
5000
4776
|
>
|
|
5001
|
-
<
|
|
4777
|
+
<PlaybackCharacter
|
|
4778
|
+
entry={view.entry}
|
|
4779
|
+
clip={view.clip}
|
|
4780
|
+
shots={shots}
|
|
5002
4781
|
url={view.url}
|
|
5003
4782
|
{...{ model: view.model, posture: view.posture }}
|
|
5004
4783
|
position={view.position}
|
|
@@ -5055,19 +4834,10 @@ export default function App() {
|
|
|
5055
4834
|
charA={charA}
|
|
5056
4835
|
charB={charB}
|
|
5057
4836
|
showB={showB}
|
|
5058
|
-
|
|
5059
|
-
probeZ={motionPos ? motionPos.z : charA.z}
|
|
4837
|
+
probe={{ scene: playbackSceneBase, frameRef: propFrameRef, carry: subjectCarry }}
|
|
5060
4838
|
camRef={shotCamRef}
|
|
5061
4839
|
look={look}
|
|
5062
|
-
onMetrics={(p, visible) => {
|
|
5063
|
-
shotCameraPosRef.current = { x: p.x, y: p.y, z: p.z };
|
|
5064
|
-
setCameraPos((prev) =>
|
|
5065
|
-
Math.abs(prev.x - p.x) + Math.abs(prev.y - p.y) + Math.abs(prev.z - p.z) > 1e-4
|
|
5066
|
-
? { x: p.x, y: p.y, z: p.z }
|
|
5067
|
-
: prev,
|
|
5068
|
-
);
|
|
5069
|
-
setSubjectVisible((prev) => (prev === visible ? prev : visible));
|
|
5070
|
-
}}
|
|
4840
|
+
onMetrics={(p, visible) => updateShotMetrics({ position: p, visible, playing: tlPlaying, setCameraPos, setSubjectVisible, cameraRef: shotCameraPosRef, visibleRef: subjectVisibleRef })}
|
|
5071
4841
|
/>
|
|
5072
4842
|
<MoveRig
|
|
5073
4843
|
playing={movePlaying}
|
|
@@ -5148,11 +4918,10 @@ export default function App() {
|
|
|
5148
4918
|
target={rangePinPreview?.target}
|
|
5149
4919
|
enabled={ikMode && ikEditTool === "pin" && Boolean(rangePinPreview?.target)}
|
|
5150
4920
|
/>
|
|
5151
|
-
{/* The
|
|
5152
|
-
|
|
5153
|
-
a clean floor instead of over 34 footprints. */}
|
|
4921
|
+
{/* The landing playground's top view: camera, cast and the rail
|
|
4922
|
+
only, so a rail is drawn on a clean floor. */}
|
|
5154
4923
|
<PlanBoard
|
|
5155
|
-
minimal={playgroundMode
|
|
4924
|
+
minimal={playgroundMode}
|
|
5156
4925
|
hostRef={planHostRef}
|
|
5157
4926
|
planCamRef={planCamRef}
|
|
5158
4927
|
shotCamRef={shotCamRef}
|
|
@@ -5447,7 +5216,7 @@ export default function App() {
|
|
|
5447
5216
|
|
|
5448
5217
|
<div
|
|
5449
5218
|
ref={shotPreviewRef}
|
|
5450
|
-
hidden={playMode ||
|
|
5219
|
+
hidden={playMode || lookThroughShot}
|
|
5451
5220
|
className="vp-pane vp-shot-preview"
|
|
5452
5221
|
style={{ "--shot-aspect": shotOutput.aspect }}
|
|
5453
5222
|
>
|