cozyclay 3.0.2 → 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 +95 -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-task-scopes.js +69 -0
- package/src/bus-task-targets.js +48 -0
- package/src/bus-tasks.js +181 -0
- package/src/command-bus.js +15 -40
- package/src/command-history.js +36 -0
- package/src/commands/cast.js +10 -3
- package/src/commands/motion.js +2 -1
- package/src/commands/motion3-compile.js +51 -27
- package/src/commands/motion3.js +13 -7
- package/src/commands/project.js +1 -1
- package/src/commands/shared.js +9 -0
- 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/anchors.js +33 -5
- package/src/motion3/compiler/compile.js +27 -4
- package/src/motion3/compiler/finger-close.js +137 -0
- package/src/motion3/compiler/grip.js +9 -3
- package/src/motion3/compiler/limbs.js +18 -3
- package/src/motion3/compiler/pose.js +6 -0
- package/src/motion3/compiler/socket-grip.js +34 -10
- 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/docs.js +1 -1
- package/src/motion3/fix/index.js +7 -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 +5 -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 +21 -1
- package/src/motion3/schema/fields.js +3 -3
- package/src/motion3/schema/topics.js +11 -10
- package/src/motion3/schema/types.js +5 -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/basic.js +61 -13
- package/src/motion3/skills/catch.js +71 -0
- package/src/motion3/skills/index.js +13 -4
- package/src/motion3/skills/info.js +14 -1
- package/src/motion3/skills/reach-human.js +139 -0
- package/src/motion3/skills/stance.js +92 -0
- package/src/motion3/skills/throw.js +72 -0
- package/src/motion3/sockets.js +21 -4
- package/src/motion3/views.js +28 -5
- 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 +3 -3
- package/src/studio-elements.js +1 -1
- package/src/studio-error-codes.js +1 -1
- package/src/studio-motion-visual.js +4 -4
- 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-DbKG0NgD.js +0 -6
- package/dist/assets/app-34i-iUy1.js +0 -5088
- package/dist/assets/app-f-fkV5uP.css +0 -1
- package/dist/assets/compile-worker-DYpa2nvO.js +0 -5
- package/dist/assets/first-shot-handoff-CrGZ3n_O.js +0 -1
- package/dist/assets/landing-mq5bnqFE.js +0 -1
- package/dist/assets/scene-asset-sync-7J-w5oNh.js +0 -20
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +0 -1
- package/dist/assets/workflow-dUhurYUc.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
|
@@ -203,8 +203,7 @@
|
|
|
203
203
|
.v2-preferences .v2-preferences__select:focus-visible,
|
|
204
204
|
.v2-preferences .v2-preferences__toggle:focus-visible,
|
|
205
205
|
.v2-preferences .v2-preferences__primary-button:focus-visible,
|
|
206
|
-
.v2-preferences .v2-preferences__reset:focus-visible
|
|
207
|
-
.v2-preferences .v2-preferences__help button:focus-visible {
|
|
206
|
+
.v2-preferences .v2-preferences__reset:focus-visible {
|
|
208
207
|
outline: none;
|
|
209
208
|
box-shadow: var(--prefs-focus-ring, 0 0 0 1px var(--select, #e8a33d));
|
|
210
209
|
}
|
|
@@ -506,36 +505,6 @@
|
|
|
506
505
|
font: 500 var(--prefs-secondary-size, 11px) var(--mono, 'JetBrains Mono');
|
|
507
506
|
}
|
|
508
507
|
|
|
509
|
-
.v2-preferences .v2-preferences__help {
|
|
510
|
-
padding-top: var(--sp-3, 16px);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
.v2-preferences .v2-preferences__help h4 {
|
|
514
|
-
margin: 0 0 var(--sp-1, 8px);
|
|
515
|
-
color: var(--text-3, #636369);
|
|
516
|
-
font-size: var(--prefs-secondary-size, 11px);
|
|
517
|
-
font-weight: 500;
|
|
518
|
-
letter-spacing: 0;
|
|
519
|
-
text-transform: none;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
.v2-preferences .v2-preferences__help button {
|
|
523
|
-
min-height: var(--h-control, 28px);
|
|
524
|
-
padding: 0 var(--sp-2, 12px);
|
|
525
|
-
border: 0;
|
|
526
|
-
border-radius: var(--r-1, 4px);
|
|
527
|
-
background: var(--field-fill, var(--surface-raised, #1e1e21));
|
|
528
|
-
color: var(--text-2, #9b9ba1);
|
|
529
|
-
font: inherit;
|
|
530
|
-
font-size: var(--prefs-secondary-size, 11px);
|
|
531
|
-
cursor: pointer;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
.v2-preferences .v2-preferences__help button:hover {
|
|
535
|
-
background: var(--surface-raised, #1e1e21);
|
|
536
|
-
color: var(--text-1, #e4e4e7);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
508
|
.v2-preferences .v2-preferences__motion-setup {
|
|
540
509
|
max-width: var(--prefs-control-width, 240px);
|
|
541
510
|
display: grid;
|
package/src/shell/shell.css
CHANGED
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
--shell-center-left: calc(var(--shell-left) + var(--shell-gap) * 2);
|
|
25
25
|
--shell-center-right: calc(var(--shell-right) + var(--shell-gap) * 2);
|
|
26
26
|
--shell-center-bottom: calc(var(--shell-dock) + var(--shell-status-h) + var(--shell-dock-bottom) + var(--shell-gap));
|
|
27
|
+
/* Top edge of the open stage: the cut-out the frame mask leaves under the
|
|
28
|
+
top bar (.workspace::after). */
|
|
29
|
+
--shell-stage-top: calc(var(--shell-gap) * 2 + 40px);
|
|
27
30
|
/* The shot card's picture. The slot is the room the card leaves between
|
|
28
31
|
its 48px head and the 84px of ratios + footer under it; the picture is
|
|
29
32
|
the shot aspect fitted into that slot and centred in it, so the stage
|
|
@@ -242,7 +245,7 @@
|
|
|
242
245
|
this frame through their glass, and on the darker --surface-0 they
|
|
243
246
|
went a step darker than designed. */
|
|
244
247
|
background: var(--shell-frame, var(--surface-panel));
|
|
245
|
-
--shell-view-top:
|
|
248
|
+
--shell-view-top: var(--shell-stage-top);
|
|
246
249
|
--shell-cut-w: calc(100% - var(--shell-center-left) - var(--shell-center-right));
|
|
247
250
|
--shell-view-h: max(0px, calc(100% - var(--shell-view-top) - var(--shell-center-bottom)));
|
|
248
251
|
--shell-shot-w: var(--shot-pic-w);
|
|
@@ -268,6 +271,20 @@
|
|
|
268
271
|
}
|
|
269
272
|
.app[data-dock-tab="assets"]:not([data-embed-mode]) .workspace::after,
|
|
270
273
|
.app[data-shot-look]:not([data-embed-mode]) .workspace::after { --shell-shot-w: 0px; --shell-shot-h: 0px; }
|
|
274
|
+
/* Look through: the panels and the frame mask cover the canvas outside the
|
|
275
|
+
open stage, and inside it the tool rails (44px, a gutter off each side,
|
|
276
|
+
glass.css) and the top-centre exit pill (styles.css) float over it. The
|
|
277
|
+
shot pane is the open stage less those bands, the same on both sides so
|
|
278
|
+
its centre is the stage centre. DualRender fits the shot aspect into
|
|
279
|
+
this pane, centred, with bars (fitAspect); the gizmo and the guides map
|
|
280
|
+
onto the same rect. Drawn under the panels or the rails, the frame lost
|
|
281
|
+
picture the export keeps. */
|
|
282
|
+
.app[data-shot-look]:not([data-embed-mode]) .vp-main,
|
|
283
|
+
.app[data-shot-look]:not([data-embed-mode]) .shot-guides.lookthrough {
|
|
284
|
+
--shot-look-x: calc(var(--shell-gap) * 2 + 44px);
|
|
285
|
+
--shot-look-y: 36px;
|
|
286
|
+
inset: calc(var(--shell-stage-top) + var(--shot-look-y)) calc(var(--shell-center-right) + var(--shot-look-x)) calc(var(--shell-center-bottom) + var(--shot-look-y)) calc(var(--shell-center-left) + var(--shot-look-x));
|
|
287
|
+
}
|
|
271
288
|
.app[data-dock-tab="assets"]:not([data-embed-mode]) .vp-shot-preview,
|
|
272
289
|
.app[data-dock-tab="assets"]:not([data-embed-mode]) .shell-camera-splitter { display: none; }
|
|
273
290
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createContext, useContext } from "react";
|
|
2
|
+
import { playbackFrame, usePlaybackSnapshot } from "../motion/playback-frame-store.js";
|
|
2
3
|
|
|
3
4
|
// App owns the document/domain wiring. Region components consume this one
|
|
4
5
|
// render snapshot and call its named actions; they never acquire domain state
|
|
@@ -20,3 +21,7 @@ export const StudioShellContext = createContext(null);
|
|
|
20
21
|
export function useStudioShell() {
|
|
21
22
|
return useContext(StudioShellContext);
|
|
22
23
|
}
|
|
24
|
+
|
|
25
|
+
export function useStudioPlaybackFrame() {
|
|
26
|
+
return usePlaybackSnapshot(playbackFrame);
|
|
27
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Shot framing: one rule for every surface that shows the shot camera.
|
|
2
|
+
//
|
|
3
|
+
// The frustum is the shot camera's pose, its vertical FOV (camera.fov, the
|
|
4
|
+
// value a shot stores as fovDeg) and the shot aspect. The aspect only changes
|
|
5
|
+
// the horizontal extent, about the optical axis: a wider ratio extends the
|
|
6
|
+
// sides, a narrower one crops them, and the picture centre never moves. The
|
|
7
|
+
// exports render exactly that (CaptureRig: aspect = output width / height,
|
|
8
|
+
// fov untouched). The focal length shown for a ratio is that FOV read on the
|
|
9
|
+
// ratio's gate (shot.js usedSensorHeightMm), so it is a readout, not the
|
|
10
|
+
// invariant.
|
|
11
|
+
//
|
|
12
|
+
// On screen the frame is the largest centred rect of the shot aspect inside
|
|
13
|
+
// the pane, with bars around it. The dock preview pane is the card's picture
|
|
14
|
+
// slot; the Look through pane is the open stage between the panels (shell.css
|
|
15
|
+
// .vp-main under [data-shot-look]), never the whole canvas under them.
|
|
16
|
+
|
|
17
|
+
/** largest centred sub-rect of `rect` with the given aspect */
|
|
18
|
+
export function fitAspect(rect, aspect) {
|
|
19
|
+
let w = rect.w;
|
|
20
|
+
let h = w / aspect;
|
|
21
|
+
if (h > rect.h) {
|
|
22
|
+
h = rect.h;
|
|
23
|
+
w = h * aspect;
|
|
24
|
+
}
|
|
25
|
+
return { x: rect.x + (rect.w - w) / 2, y: rect.y + (rect.h - h) / 2, w, h };
|
|
26
|
+
}
|
|
@@ -5,13 +5,14 @@ import { aimAt } from "./controls.jsx";
|
|
|
5
5
|
import { SUBJECT_HEIGHT_M } from "./shot.js";
|
|
6
6
|
import { shotAtFrame } from "./cuts.js";
|
|
7
7
|
import { sampleAt } from "./sample-at.js";
|
|
8
|
+
import { playbackProbe, probeVisibility } from "./motion/playback-probe.js";
|
|
8
9
|
import { RENDER_ACTIVITY_EVENT } from "./use-render-activity.js";
|
|
9
10
|
import { PRESETS, editorFrameForSubject } from "./stage-startup-defaults.js";
|
|
10
11
|
|
|
11
12
|
/** Selection marker for the picked cast member: a Unity-style XYZ tripod
|
|
12
13
|
* plus a ground ring at the feet. X/Z arrows also drag the character on the
|
|
13
14
|
* floor (Y is display-only — characters stand on the deck). */
|
|
14
|
-
export function ShotRig({ preset, nonce, fovDeg, charA, charB, showB,
|
|
15
|
+
export function ShotRig({ preset, nonce, fovDeg, charA, charB, showB, probe: playbackProbeInput = {}, camRef, look, onMetrics, appliedPresetRef, lastPosRef }) {
|
|
15
16
|
useEffect(() => {
|
|
16
17
|
const cam = camRef.current;
|
|
17
18
|
if (!cam) return;
|
|
@@ -65,13 +66,11 @@ export function ShotRig({ preset, nonce, fovDeg, charA, charB, showB, probeX, pr
|
|
|
65
66
|
useFrame(() => {
|
|
66
67
|
const cam = camRef.current;
|
|
67
68
|
if (!cam) return;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (probe.current.z < 1 && Math.abs(probe.current.x) <= 1 && Math.abs(probe.current.y) <= 1) visible = true;
|
|
74
|
-
}
|
|
69
|
+
// During motion playback the probe follows the played root, so the
|
|
70
|
+
// "subject out of frame" caption stays honest.
|
|
71
|
+
const frame = playbackProbeInput.frameRef?.current ?? 0;
|
|
72
|
+
const current = playbackProbe(playbackProbeInput, frame, charA);
|
|
73
|
+
const visible = probeVisibility(probe.current, cam, current, SUBJECT_HEIGHT_M);
|
|
75
74
|
const previous = lastMetrics.current;
|
|
76
75
|
if (
|
|
77
76
|
Math.abs(previous.x - cam.position.x) +
|
package/src/stage-characters.jsx
CHANGED
|
@@ -8,12 +8,38 @@ import { isProxyFigure } from "./scenes.js";
|
|
|
8
8
|
import { applyPartColours } from "./part-colours.js";
|
|
9
9
|
import { applyHipsOffset, applyPose, primeBindPose } from "./poses.js";
|
|
10
10
|
import { REST_BONES } from "./stage-startup-defaults.js";
|
|
11
|
+
import { playbackFrame, usePlaybackSnapshot } from "./motion/playback-frame-store.js";
|
|
12
|
+
import { placementAt } from "./root-path.js";
|
|
13
|
+
import { shotAtFrame } from "./cuts.js";
|
|
14
|
+
import { sampleAt } from "./sample-at.js";
|
|
11
15
|
|
|
12
16
|
// Memoized: unchanged cast members skip re-rendering on every playhead tick.
|
|
13
17
|
export const Character = memo(function Character(props) {
|
|
14
18
|
return isProxyFigure(props) ? <ProxyFigure {...props} /> : <RiggedCharacter {...props} />;
|
|
15
19
|
});
|
|
16
20
|
|
|
21
|
+
export const PlaybackCharacter = memo(function PlaybackCharacter({
|
|
22
|
+
entry,
|
|
23
|
+
clip,
|
|
24
|
+
shots,
|
|
25
|
+
...props
|
|
26
|
+
}) {
|
|
27
|
+
const frame = usePlaybackSnapshot(playbackFrame);
|
|
28
|
+
const placement = placementAt(entry, frame, {
|
|
29
|
+
shotAt: (at) => shotAtFrame(shots, at),
|
|
30
|
+
takeRoot: clip ? sampleAt({ frameCount: clip.frames, motion: clip }, null, frame).subject : null,
|
|
31
|
+
});
|
|
32
|
+
return (
|
|
33
|
+
<Character
|
|
34
|
+
{...props}
|
|
35
|
+
position={[placement.x, entry.y ?? 0, placement.z]}
|
|
36
|
+
rot={placement.rot}
|
|
37
|
+
pose={clip ? null : (placement.pose ?? props.pose)}
|
|
38
|
+
posture={placement.posture}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
17
43
|
const RiggedCharacter = memo(function RiggedCharacter({ url, position, rot, tint, pose, scale = 1, onRig, pickId, partColoursEnabled = false, partColoursMode = "shaded" }) {
|
|
18
44
|
const fbx = useFBX(url);
|
|
19
45
|
const model = useMemo(() => {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { placementAt } from "./root-path.js";
|
|
2
|
+
import { shotAtFrame } from "./cuts.js";
|
|
3
|
+
|
|
4
|
+
export function characterGizmoView({ selected, characters, activeCharacterId, activeCharacter, motion, shots, activeId, frame }) {
|
|
5
|
+
if (!selected) return null;
|
|
6
|
+
const entry = characters.find((item) => item.id === activeId);
|
|
7
|
+
if (!entry || entry.hidden) return null;
|
|
8
|
+
const hasTake = activeId === activeCharacterId ? Boolean(motion) : Boolean(activeCharacter.sessionMotion);
|
|
9
|
+
const placement = hasTake ? entry : placementAt(entry, frame, { shotAt: (at) => shotAtFrame(shots, at) });
|
|
10
|
+
return { position: [placement.x, entry.y ?? 0, placement.z], rot: placement.rot };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function characterGizmoObject(gizmoView, entry) {
|
|
14
|
+
if (!gizmoView) return null;
|
|
15
|
+
return {
|
|
16
|
+
id: "__character__",
|
|
17
|
+
x: gizmoView.position[0],
|
|
18
|
+
y: gizmoView.position[1],
|
|
19
|
+
z: gizmoView.position[2],
|
|
20
|
+
height: 1.15,
|
|
21
|
+
footprint: { width: 0.6, depth: 0.6 },
|
|
22
|
+
rotY: gizmoView.rot ?? entry?.rot ?? 0,
|
|
23
|
+
scaleX: entry?.scale ?? 1,
|
|
24
|
+
scaleY: entry?.scale ?? 1,
|
|
25
|
+
scaleZ: entry?.scale ?? 1,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function playbackSceneWithTrack(base, subjectTrack, followTrack) {
|
|
30
|
+
return { ...base, subjectTrack, cameraTrack: followTrack };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function followCameraActive(mode, track, frame, preview, ikMode, waypointMode, posing) {
|
|
34
|
+
return mode !== "keys" && Boolean(track?.[frame]) && (preview || (!ikMode && !waypointMode && !posing));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function updateShotMetrics({ position, visible, playing, setCameraPos, setSubjectVisible, cameraRef, visibleRef }) {
|
|
38
|
+
cameraRef.current = { x: position.x, y: position.y, z: position.z };
|
|
39
|
+
if (!playing) setCameraPos((prev) => Math.abs(prev.x - position.x) + Math.abs(prev.y - position.y) + Math.abs(prev.z - position.z) > 1e-4
|
|
40
|
+
? { x: position.x, y: position.y, z: position.z } : prev);
|
|
41
|
+
visibleRef.current = visible;
|
|
42
|
+
if (!playing) setSubjectVisible((prev) => (prev === visible ? prev : visible));
|
|
43
|
+
}
|
package/src/stage-labels-ko.js
CHANGED
|
@@ -119,9 +119,6 @@ export const SCENE_RENDERER_LABELS_KO = new Map([
|
|
|
119
119
|
["cylinder", ko("cylinder", "원기둥")],
|
|
120
120
|
["cone", ko("cone", "원뿔")],
|
|
121
121
|
["plane", ko("plane", "평면")],
|
|
122
|
-
["chair", ko("chair", "의자")],
|
|
123
|
-
["car", ko("car", "자동차")],
|
|
124
|
-
["aircraft", ko("aircraft", "비행기")],
|
|
125
122
|
[CUTOUT_KIND, ko("cutout", "컷아웃")],
|
|
126
123
|
[MESH_KIND, ko("Mesh", "모델")],
|
|
127
124
|
["sculpt", ko("Sculpt", "조형")],
|
|
@@ -134,9 +131,6 @@ export const SCENE_OBJECT_NAME_LABELS_KO = new Map([
|
|
|
134
131
|
["Cylinder", ko("Cylinder", "원기둥")],
|
|
135
132
|
["Cone", ko("Cone", "원뿔")],
|
|
136
133
|
["Plane", ko("Plane", "평면")],
|
|
137
|
-
["Chair", ko("Chair", "의자")],
|
|
138
|
-
["Car", ko("Car", "자동차")],
|
|
139
|
-
["Plane (aircraft)", ko("Plane (aircraft)", "비행기")],
|
|
140
134
|
["Mesh", ko("Mesh", "모델")],
|
|
141
135
|
["Model", ko("Model", "모델")],
|
|
142
136
|
]);
|
|
@@ -32,16 +32,10 @@ export const SHOT_ASPECT_PRESETS = Object.freeze({
|
|
|
32
32
|
"12:7": Object.freeze({ label: "12:7", aspect: SHOT_ASPECT_RATIOS["12:7"], width: 1536, height: 896, title: "12:7 video-inbetweener ratio" }),
|
|
33
33
|
"fal 480P": Object.freeze({ label: "fal 480P", aspect: SHOT_ASPECT_RATIOS["fal 480P"], width: 1664, height: 960, title: "Fal H3 480P canvas (832x480)" }),
|
|
34
34
|
});
|
|
35
|
-
// Pre-generated clip shipped with the build so a bridge-less session (a hosted
|
|
36
|
-
// static demo, or `npm run dev:ui`) still shows real generated motion.
|
|
37
|
-
// Root-absolute on purpose: the studio is served from "/app/" while these
|
|
38
|
-
// public files stay at the site root, so a page-relative path would resolve
|
|
39
|
-
// to "/app/models/..." and 404. Vite's base is "/" (own apex domain), so a
|
|
40
|
-
// leading slash is now the correct — and only — form that works from both
|
|
41
|
-
// the dev server and the built site.
|
|
42
35
|
// Per-character rig model: the stage entry's `model` is the FBX file stem in
|
|
43
|
-
// public/models
|
|
44
|
-
//
|
|
36
|
+
// public/models. Root-absolute on purpose: the studio is served from "/app/"
|
|
37
|
+
// while public files stay at the site root, so a page-relative path would
|
|
38
|
+
// resolve to "/app/models/..." and 404.
|
|
45
39
|
export const characterModelUrl = (model) => `/models/${model}.fbx`;
|
|
46
40
|
|
|
47
41
|
/** Shipped rig names as the operator says them, not as the files spell them. */
|
package/src/starter-scenes.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
// Starter scenes ship inside the build (public/scenes
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Starter scenes ship inside the build (public/scenes/<id>.cclayproject) and
|
|
2
|
+
// are referred to by id: `npx cozyclay --scene <id>`, `?scene=<id>` and the
|
|
3
|
+
// chooser's template cards. The build ships none today; an unknown id resolves
|
|
4
|
+
// to null, and the studio ignores it.
|
|
5
5
|
|
|
6
|
-
export const STARTER_SCENES = Object.freeze([
|
|
7
|
-
Object.freeze({
|
|
8
|
-
id: "city-block",
|
|
9
|
-
name: "City Block",
|
|
10
|
-
blurb: "The set from the cozyclay.org tutorial: an alley, parked cars, one character mid-walk.",
|
|
11
|
-
}),
|
|
12
|
-
]);
|
|
6
|
+
export const STARTER_SCENES = Object.freeze([]);
|
|
13
7
|
|
|
14
8
|
function starterSceneById(id) {
|
|
15
9
|
return STARTER_SCENES.find((scene) => scene.id === id) ?? null;
|
|
@@ -91,9 +91,9 @@ function basisAxis(spec, reference, state) {
|
|
|
91
91
|
}
|
|
92
92
|
function support(spec, state) {
|
|
93
93
|
const id = spec.onObject ?? (typeof spec.support === 'object' ? spec.support.objectId : null);
|
|
94
|
-
if (!id) return { y: state.floorY, label: 'floor' };
|
|
94
|
+
if (!id) return { y: state.floorY ?? 0, label: 'floor' }; // live app state carries no floorY; the 3.0 floor is flat at 0
|
|
95
95
|
const object = entityById(state, id);
|
|
96
|
-
if (!object.renderer || object.renderer === 'cutout' || ['sphere', 'capsule', 'cone', '
|
|
96
|
+
if (!object.renderer || object.renderer === 'cutout' || ['sphere', 'capsule', 'cone', 'empty', 'sculpt'].includes(object.renderer) || object.path || object.attach || Math.abs(object.rotX) > EPS || Math.abs(object.rotZ) > EPS || isEffectivelyHidden(object, state.objects, state.characters)) fail('TARGET_NOT_READY', 'Support must be a stationary solid upright surface.');
|
|
97
97
|
return { y: object.y + supportHeightForObject(object) * object.scaleY, label: `object:${id}`, object };
|
|
98
98
|
}
|
|
99
99
|
function facingYaw(spec, entity, state) {
|
|
@@ -8,7 +8,6 @@ import { wrongType, unknownField, missingField, arrayRefusal, duplicateItem, str
|
|
|
8
8
|
// both pull in three.js, which the agent sidecar must not load. The protocol test pins the lists to their owners.
|
|
9
9
|
const PREVIS_MODES = ["storyboard", "animation"], POSTURES = ["stand", "sit", "lie"];
|
|
10
10
|
|
|
11
|
-
export const STUDIO_PROTOCOL_VERSION = "studio-agent-v1";
|
|
12
11
|
// Sized for the compact index of up to 400 entities (~100 bytes each) beside
|
|
13
12
|
// 24 detailed rows; a small scene stays far below it.
|
|
14
13
|
export const STUDIO_CONTEXT_MAX_BYTES = 64 * 1024;
|
|
@@ -30,7 +29,6 @@ export const STUDIO_TOOLS = Object.freeze(Object.keys(STUDIO_TOOL_LABELS));
|
|
|
30
29
|
/** One patch target kind per authored commit domain: character→cast,
|
|
31
30
|
* object→objects, shot→shot, stage→stage. */
|
|
32
31
|
export const STUDIO_PATCH_KINDS = Object.freeze(["character", "object", "shot", "stage"]);
|
|
33
|
-
export const STUDIO_PATCH_DOMAINS = Object.freeze({ character: "cast", object: "objects", shot: "shot", stage: "stage" });
|
|
34
32
|
export { STUDIO_ERROR_CODES, refusalCode };
|
|
35
33
|
export const STUDIO_VARIANTS = freezeStudioData({
|
|
36
34
|
selectionKinds: ["scene", "object", "character", "rig", "camera"], modes: ["scene", "pose", "camera", "motion"], shotModes: ["keys", "follow", "rail"],
|
|
@@ -47,7 +45,6 @@ export class StudioProtocolError extends Error {
|
|
|
47
45
|
constructor(code, message, details = {}) { super(message); this.name = "StudioProtocolError"; this.code = code; this.details = details; }
|
|
48
46
|
toJSON() { return { code: this.code, message: this.message, ...(Object.keys(this.details).length ? { details: this.details } : {}) }; }
|
|
49
47
|
}
|
|
50
|
-
export const studioError = (code, message, details) => ({ ok: false, error: new StudioProtocolError(code, message, details).toJSON() });
|
|
51
48
|
export const utf8ByteLength = (text) => new TextEncoder().encode(text).byteLength;
|
|
52
49
|
export function freezeStudioData(value) {
|
|
53
50
|
if (value && typeof value === "object" && !Object.isFrozen(value)) { for (const child of Object.values(value)) freezeStudioData(child); Object.freeze(value); }
|
|
@@ -302,8 +299,7 @@ const receiptVariants = {
|
|
|
302
299
|
installed: object({ ...authoredReceipt, status: literal("installed"), jobId: id, artifactId: id, installed, verification }, {
|
|
303
300
|
// Who accepted an unverified take: the user's explicit button, or the
|
|
304
301
|
// runtime's advisory install policy. Exactly these two may admit one.
|
|
305
|
-
mutated: literal(true), explicitUnverifiedAcceptance: bool, acceptance: literal("advisory-policy"),
|
|
306
|
-
repairs: object({ autoPhysicsInvocations: integer(0, 1), fixCollisionsInvocations: integer(0, 1), remaining: integer(0, 2) }), ...batchDetails,
|
|
302
|
+
mutated: literal(true), explicitUnverifiedAcceptance: bool, acceptance: literal("advisory-policy"), ...batchDetails,
|
|
307
303
|
}),
|
|
308
304
|
undone: object({ ...authoredReceipt, status: literal("undone"), undoneReceiptId: id, restoredTargets: array(guardSchema, 100, 1) }, { mutated: literal(true), ...batchDetails }),
|
|
309
305
|
};
|
|
@@ -382,7 +378,6 @@ export function validateStudioSchema(schema, value, code = "INVALID_ARGUMENT", p
|
|
|
382
378
|
throw new Error("Unsupported internal Studio schema.");
|
|
383
379
|
}
|
|
384
380
|
export const validateStudioIdentity = value => validateStudioSchema(identity, value, "INVALID_IDENTITY");
|
|
385
|
-
export const validateFrameRange = value => freezeStudioData(validateStudioSchema(range, value, "INVALID_RANGE"));
|
|
386
381
|
|
|
387
382
|
export function mandatoryStudioTargetIds(context) {
|
|
388
383
|
return [...new Set([
|
|
@@ -544,6 +539,6 @@ export function validateReceipt(value) {
|
|
|
544
539
|
if (previous !== frameCount || r.installed.durationSeconds !== frameCount / 24 || r.verification.range.startFrame !== 0 || r.verification.range.endFrameExclusive !== frameCount || r.verification.evaluatedFrames > frameCount) fail("INVALID_RECEIPT", "Installed schedule and verification coverage disagree.");
|
|
545
540
|
if (r.verification.status === "verified" && r.verification.evaluatedFrames !== frameCount) fail("INVALID_RECEIPT", "Verified motion requires whole-clip coverage.");
|
|
546
541
|
if (r.verification.status === "unverified" && r.explicitUnverifiedAcceptance !== true && r.acceptance !== "advisory-policy") fail("INVALID_RECEIPT", "Unverified installation requires explicit user or advisory-policy acceptance.");
|
|
547
|
-
} else if (r.installed || r.verification || (r.jobId && !["started", "completed"].includes(r.status)) || r.artifactId || r.
|
|
542
|
+
} else if (r.installed || r.verification || (r.jobId && !["started", "completed"].includes(r.status)) || r.artifactId || r.explicitUnverifiedAcceptance !== undefined) fail("INVALID_RECEIPT", "Installation evidence is exclusive to installed receipts.");
|
|
548
543
|
return freezeStudioData(r);
|
|
549
544
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The Studio's agent binding: inspection, admission and the agent's commands
|
|
2
2
|
// over the editor's native state. App.jsx supplies the ports (reads, commits,
|
|
3
3
|
// history, the action registry); this module owns no React or renderer state.
|
|
4
|
+
import { taskTargetPorts } from "./bus-task-targets.js";
|
|
4
5
|
import { createCommandBus } from "./command-bus.js";
|
|
5
6
|
import { readElementDocument, elementReadback, elementPatchArgs, elementTarget, elementPatchReceipt } from "./commands/elements.js";
|
|
6
7
|
import { physicsKeyStamp } from "./motion/physics-review.js";
|
|
@@ -220,15 +221,14 @@ export function createStudioAppBinding(ports) {
|
|
|
220
221
|
}, recordAction: (...args) => ports.recordAction(...args), beginAction: (...args) => ports.beginAction(...args),
|
|
221
222
|
readback: actionReadback, remember, receipt: id => receipts.get(id), isRetained: receipt => ports.isRetained(receipt),
|
|
222
223
|
canUndo: receipt => ports.canUndo(receipt), undo: () => ports.undo(), redo: () => ports.redo(),
|
|
223
|
-
history: redo => ports.history?.(redo), finishHistoryGesture: () => ports.finishHistoryGesture?.(),
|
|
224
|
-
readTarget: id => { refresh(); return tokens.get(id)?.token; },
|
|
224
|
+
history: redo => ports.history?.(redo), finishHistoryGesture: () => ports.finishHistoryGesture?.(), readTarget: id => { refresh(); return tokens.get(id)?.token; }, ...taskTargetPorts(ports),
|
|
225
225
|
captureToasts: listener => ports.captureToasts?.(listener), showRefusal: message => ports.showRefusal?.(message), emit: event => ports.emitCommandEvent?.(event),
|
|
226
226
|
} });
|
|
227
227
|
return actionBus;
|
|
228
228
|
}
|
|
229
229
|
function runAction(request, args) {
|
|
230
230
|
const { name: _name, args: _args, ...options } = request;
|
|
231
|
-
const result = commandBus().run(args.action, args.args, { ...options, origin: "agent", turnId: request.turnId, confirmationToken: args.confirmationToken ?? request.confirmationToken });
|
|
231
|
+
const result = commandBus().run(args.action, args.args, { ...options, origin: "agent", turnId: request.turnId, taskId: request.taskId, confirmationToken: args.confirmationToken ?? request.confirmationToken });
|
|
232
232
|
const answer = receipt => receipt.nextHost ? { ...receipt, host: receipt.nextHost } : receipt;
|
|
233
233
|
return result?.then ? result.then(answer) : answer(result);
|
|
234
234
|
}
|
package/src/studio-elements.js
CHANGED
|
@@ -34,7 +34,7 @@ const entries = [
|
|
|
34
34
|
|
|
35
35
|
{ path: "character.sessionMotion", type: "array", persisted: false, undoDomain: "cast", normalizer: "createCharacterEntry", note: "dropped by createCharacterEntry" },
|
|
36
36
|
{ path: "character.ikKeys", type: "array", persisted: false, undoDomain: "cast", normalizer: "createCharacterEntry", actions: ["character.setIkKey", "character.removeIkKey", "character.clearIkKeys"], note: "dropped by createCharacterEntry; keys travel as JSON quaternions/positions" },
|
|
37
|
-
{ path: "object.renderer", type: "enum", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", enum: ["cube", "sphere", "capsule", "cylinder", "cone", "plane", "
|
|
37
|
+
{ path: "object.renderer", type: "enum", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", enum: ["cube", "sphere", "capsule", "cylinder", "cone", "plane", "empty", "light", "effect", "text"], note: "light is made from catalogue kind point-light or spot-light; effect (smoke, fire, rain, trail...) from kind effect; text (words, a sign, a logo) from kind text" },
|
|
38
38
|
{ path: "object.position", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: -240, y: 0, z: -240 }, max: { x: 240, y: 240, z: 240 }, note: "y is the object's base: y=0 rests on the floor; height/supportY rise from it" },
|
|
39
39
|
{ path: "object.rotation", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: -180, y: -180, z: -180 }, max: { x: 180, y: 180, z: 180 }, angle: true, note: "rot/rotX/rotZ degrees, wrapped at the upper bound" },
|
|
40
40
|
{ path: "object.scale", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: 0.01, y: 0.01, z: 0.01 }, max: { x: 100, y: 100, z: 100 } },
|
|
@@ -4,7 +4,7 @@ export const STUDIO_ERROR_CODES = Object.freeze([
|
|
|
4
4
|
"INVALID_ARGUMENT", "INVALID_CONTEXT", "INVALID_IDENTITY", "INVALID_TURN_ID", "INVALID_SESSION_ID", "INVALID_RECEIPT", "INVALID_RANGE", "INVALID_REQUEST",
|
|
5
5
|
"UNKNOWN_TOOL", "UNKNOWN_VARIANT", "DUPLICATE_NAME", "CONTEXT_LIMIT", "CONTEXT_TOO_LARGE", "AMBIGUOUS_TARGET", "AMBIGUOUS_BASIS", "TARGET_NOT_READY", "TARGET_BUSY",
|
|
6
6
|
"STALE_TARGET", "STALE_SCENE", "STALE_ENVIRONMENT", "STALE_CURSOR", "CAPABILITY_MISSING", "LIVE_HUB_UNAVAILABLE", "AUTH_REQUIRED", "GENERATION_LIMIT", "RATE_LIMITED", "BACKEND_UNAVAILABLE",
|
|
7
|
-
"VERIFICATION_FAILED", "REPAIR_REGRESSED", "CANCELLED", "UNCERTAIN_APPLY", "UNDO_CONFLICT", "CONFIRMATION_REQUIRED", "TIMEOUT", "UNDO_EXPIRED", "INTERNAL_ERROR",
|
|
7
|
+
"VERIFICATION_FAILED", "REPAIR_REGRESSED", "CANCELLED", "UNCERTAIN_APPLY", "UNDO_CONFLICT", "CONFIRMATION_REQUIRED", "TIMEOUT", "UNDO_EXPIRED", "INTERNAL_ERROR", "SCOPE_DENIED", "SCOPE_BUSY",
|
|
8
8
|
]);
|
|
9
9
|
/**
|
|
10
10
|
* A refusal's code. A protocol error carries its own; a JavaScript runtime error (TypeError, ReferenceError, RangeError,
|
|
@@ -9,7 +9,7 @@ import { compileSpec } from "./motion3/pipeline.js";
|
|
|
9
9
|
import { motionScene } from "./motion3/scene.js";
|
|
10
10
|
import { skeletonFor } from "./motion3/rigs/index.js";
|
|
11
11
|
import { beatFrames, grabReleases, traceSeries, TRACE_COLORS } from "./motion3/beats.js";
|
|
12
|
-
import { depthViews, projectPoint, trailPoints, viewCameras } from "./motion3/views.js";
|
|
12
|
+
import { cameraAt, depthViews, projectPoint, trailPoints, viewCameras } from "./motion3/views.js";
|
|
13
13
|
import { checkRef } from "./motion3/refs.js";
|
|
14
14
|
import { renderWithGhosts } from "./ghost-set.js";
|
|
15
15
|
import { GHOST_COLORS, sheetGhostSettings } from "./view-ghosts.js";
|
|
@@ -92,14 +92,14 @@ function viewSheet(args, r, { s, frames, span, grid, picks, ports, added }) {
|
|
|
92
92
|
if (!cameras.length) throw new StudioProtocolError("TARGET_NOT_READY", "views found no compiled frame of the take in the range.");
|
|
93
93
|
const cells = cameras.flatMap(cam => frames.map(frame => ({ cam, frame }))), target = args.ghosts ? s.targets.get(r.characterId) : null;
|
|
94
94
|
if (args.ghosts && !target?.rig) throw new StudioProtocolError("TARGET_NOT_READY", "ghosts pose a copy of the character's rig; it has not loaded in the editor yet.");
|
|
95
|
-
const sheet = buildContactSheet(cells, ({ cam, frame }) => {
|
|
96
|
-
const render = () => ports.renderFrameBuffer(frame, { eye: cam.eye, quaternion: cam.quaternion, fovDeg: cam.fovDeg, output: { width: side, height: side } });
|
|
95
|
+
const sheet = buildContactSheet(cells, ({ cam: view, frame }) => {
|
|
96
|
+
const cam = cameraAt(view, frame), render = () => ports.renderFrameBuffer(frame, { eye: cam.eye, quaternion: cam.quaternion, fovDeg: cam.fovDeg, output: { width: side, height: side } });
|
|
97
97
|
const tile = target ? renderWithGhosts({ rig: target.rig, take: target.motion, objects: s.objects }, frame, render) : render();
|
|
98
98
|
const dots = trails.flatMap(t => t.points.map(({ f, p }) => [f, projectPoint(cam, p, tile.width, tile.height), rgb(t.color)])
|
|
99
99
|
.filter(([, q]) => q).map(([f, [x, y], color]) => ({ x, y, color, r: f === frame ? 4 : 1.5, alpha: f <= frame ? 1 : 0.35, ring: f === frame })));
|
|
100
100
|
return { ...tile, data: paintDots(tile.data, tile.width, tile.height, dots) };
|
|
101
101
|
}, grid);
|
|
102
|
-
const views = cameras.map(cam => ({ name: cam.name, eye: round2(cam.eye), target: round2(cam.target) }));
|
|
102
|
+
const views = cameras.map(cam => ({ name: cam.name, eye: round2(cam.eye), target: round2(cam.target), ...(cam.ride ? { rides: "focus" } : {}) }));
|
|
103
103
|
const ref = { frames, layout: grid.layout, views, ...(added ? { addedView: added } : {}), focus: focus.length ? focus : "character", ...(trails.length ? { trails: trails.map(({ ref: of, color }) => ({ ref: of, color })) } : {}), ...(picks ? { picks } : {}),
|
|
104
104
|
...(target ? { ghosts: { ...sheetGhostSettings(), ...GHOST_COLORS } } : {}) };
|
|
105
105
|
return [{ dataUrl: ports.encodePng(sheet.data, sheet), width: sheet.width, height: sheet.height, frames, layout: grid.layout, views: views.map(v => v.name) }, ref];
|
package/src/styles/themes.css
CHANGED
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
--float-head: 48px;
|
|
14
14
|
--sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "IBM Plex Sans", "Pretendard", "Noto Sans KR", "Apple SD Gothic Neo", "Malgun Gothic", system-ui, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
15
15
|
--mono: "SF Mono", ui-monospace, "JetBrains Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
16
|
+
/* Shell popovers (project menu, dropdowns, Outliner context menu): one
|
|
17
|
+
solid surface and one layer above every panel and rail. They are
|
|
18
|
+
portaled to <body> and exclusive (src/popover.js), so the open one is
|
|
19
|
+
always on top. The top bar's menus use the same layer. */
|
|
20
|
+
--z-popover: 90;
|
|
21
|
+
--popover-surface: var(--surface-raised);
|
|
22
|
+
--popover-border: var(--row-divider);
|
|
23
|
+
--popover-radius: var(--r-2, 6px);
|
|
24
|
+
--popover-blur: none;
|
|
25
|
+
--popover-shadow: 0 8px 24px rgba(0,0,0,.45);
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
/* Dark: the v2 surfaces with a deep clay-orange accent and shaded panels. */
|
|
@@ -73,6 +83,9 @@
|
|
|
73
83
|
--surface-bar: #f4f4f6;
|
|
74
84
|
--surface-panel: #f7f7f9;
|
|
75
85
|
--surface-raised: #ffffff;
|
|
86
|
+
--popover-surface: rgba(255,255,255,.96);
|
|
87
|
+
--popover-blur: blur(24px) saturate(1.6);
|
|
88
|
+
--popover-shadow: 0 10px 30px rgba(30,32,40,.16), 0 1px 3px rgba(30,32,40,.08);
|
|
76
89
|
--surface-active: #e4e4e9;
|
|
77
90
|
--field: #ececf0;
|
|
78
91
|
--field-deep: #e6e6eb;
|