cozyclay 1.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +171 -0
- package/bin/agent/agent-routes.mjs +74 -70
- package/bin/agent/agent-runner.mjs +10 -3
- package/bin/agent/motion-runtime.mjs +32 -260
- package/bin/agent/providers.mjs +46 -3
- package/bin/agent/session-store.mjs +41 -5
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-tools.mjs +48 -11
- package/bin/cozyclay.mjs +26 -8
- package/bin/live/cli.mjs +22 -3
- package/bin/mcp-runtime.mjs +35 -20
- package/bin/telemetry-state.mjs +5 -2
- package/bin/update-check.mjs +15 -0
- package/dist/app/index.html +6 -6
- package/dist/assets/analytics-MVZOEW8O.js +1 -0
- package/dist/assets/app-C1sdVbg4.css +1 -0
- package/dist/assets/app-CA55tcI3.js +4876 -0
- package/dist/assets/{demo-B-eg1CT8.js → demo-ByeYp-dY.js} +1 -1
- package/dist/assets/{first-shot-handoff-CGgnvNu8.js → first-shot-handoff-DgT28ZiV.js} +1 -1
- package/dist/assets/{landing-IbRp3FnQ.js → landing-BaXkpFip.js} +1 -1
- package/dist/assets/shot-prompt-CbqWcWYi.css +1 -0
- package/dist/assets/shot-prompt-Yrt99wZx.js +17 -0
- package/dist/assets/{ticket-BgIzOBOf.js → ticket-BcmsnHn5.js} +1 -1
- package/dist/assets/workflow-DRdwoDBT.css +1 -0
- package/dist/assets/workflow-dNPGY9xz.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/fonts/IBM-Plex-Sans-OFL.txt +92 -0
- package/dist/fonts/JetBrains-Mono-OFL.txt +93 -0
- package/dist/fonts/README.md +13 -9
- package/dist/fonts/ibm-plex-sans-400-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-500-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-600-latin.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-400-latin.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-500-latin.woff2 +0 -0
- package/dist/index.html +7 -7
- package/dist/privacy/index.html +13 -8
- package/dist/sitemap.xml +6 -6
- package/dist/workflow/index.html +5 -5
- package/mcp/LIVE-PROTOCOL.md +1 -1
- package/mcp/live-hub.mjs +35 -93
- package/mcp/mesh-file.mjs +48 -0
- package/mcp/server.mjs +6 -77
- package/mcp/tool-handlers.mjs +224 -377
- package/package.json +2 -1
- package/src/App.jsx +1273 -9075
- package/src/analytics.js +393 -9
- package/src/app-context.js +211 -0
- package/src/app-stage.jsx +25 -24
- package/src/ardy/auto-fix-panel.css +108 -0
- package/src/ardy/collision-blockers.js +9 -3
- package/src/ardy/fix-collisions.js +10 -66
- package/src/ardy/ground.js +4 -4
- package/src/ardy/ik-drag.js +101 -0
- package/src/ardy/ik-key-json.js +43 -0
- package/src/ardy/ik.js +122 -76
- package/src/ardy/physics-panel.css +2 -0
- package/src/ardy/physics-panel.jsx +17 -5
- package/src/ardy/platform-fit-panel.css +64 -0
- package/src/ardy/platform-fit-panel.jsx +40 -0
- package/src/ardy/platform-fit.js +326 -0
- package/src/ardy/playback.js +166 -20
- package/src/ardy/range-pin.js +299 -0
- package/src/ardy/timeline-coordinates.js +26 -0
- package/src/ardy/timeline.css +1199 -0
- package/src/ardy/timeline.jsx +149 -41
- package/src/ardy/waypoints.js +2 -2
- package/src/asset-pane.css +462 -0
- package/src/asset-pane.jsx +461 -232
- package/src/command-bus.js +349 -0
- package/src/commands/ai.js +31 -0
- package/src/commands/cast.js +140 -0
- package/src/commands/elements/character.js +13 -0
- package/src/commands/elements/motion.js +14 -0
- package/src/commands/elements/object.js +22 -0
- package/src/commands/elements/scene.js +16 -0
- package/src/commands/elements/shot.js +9 -0
- package/src/commands/elements/stage.js +11 -0
- package/src/commands/elements.js +155 -0
- package/src/commands/export.js +20 -0
- package/src/commands/index.js +40 -0
- package/src/commands/motion.js +231 -0
- package/src/commands/objects.js +174 -0
- package/src/commands/project.js +53 -0
- package/src/commands/scene.js +81 -0
- package/src/commands/shared.js +19 -0
- package/src/commands/shot.js +103 -0
- package/src/commands/stage.js +17 -0
- package/src/commands/view.js +42 -0
- package/src/document-store.js +144 -0
- package/src/domains/cast.js +1007 -0
- package/src/domains/motion.js +3219 -0
- package/src/domains/objects.js +932 -0
- package/src/domains/scenes.js +817 -0
- package/src/domains/shots.js +404 -0
- package/src/domains/stage.js +93 -0
- package/src/dualview.jsx +113 -57
- package/src/facing-marks.js +3 -0
- package/src/first-success-guide.jsx +14 -14
- package/src/grid-view.js +18 -5
- package/src/hierarchy-model.js +24 -16
- package/src/hierarchy-panel.css +386 -0
- package/src/hierarchy-panel.jsx +150 -21
- package/src/{fal-motion-client.js → i2v-motion-client.js} +13 -13
- package/src/i2v-motion-studio.jsx +180 -0
- package/src/ik-camera.js +3 -0
- package/src/main.jsx +6 -0
- package/src/motion/generation.js +105 -0
- package/src/motion-readiness-ui.jsx +4 -4
- package/src/motion-readiness.js +11 -0
- package/src/motion-trail.js +366 -9
- package/src/object-gizmo.jsx +7 -1
- package/src/otio.js +2 -1
- package/src/panels/CameraPanel.jsx +60 -0
- package/src/panels/CharacterTransformPanel.jsx +48 -0
- package/src/panels/EnvironmentPanel.jsx +40 -0
- package/src/panels/Foldout.jsx +32 -0
- package/src/panels/LightPanel.jsx +24 -0
- package/src/panels/ObjectTransformPanel.jsx +554 -0
- package/src/panels/PosePanel.jsx +97 -0
- package/src/panels/ProjectPanel.jsx +33 -0
- package/src/panels/PromptBlocksPanel.jsx +442 -0
- package/src/panels/PropsPanel.jsx +70 -0
- package/src/panels/ReferenceImageField.jsx +91 -0
- package/src/panels/RigControlPanel.jsx +78 -0
- package/src/panels/RigPanel.jsx +37 -0
- package/src/panels/SubjectBox.jsx +47 -0
- package/src/panels/SubjectsPanel.jsx +39 -0
- package/src/panels/VideoCapturePanel.jsx +180 -0
- package/src/panels/details.css +1171 -0
- package/src/panels/motion.css +197 -0
- package/src/panels/pose.css +39 -0
- package/src/planview.jsx +60 -31
- package/src/posestudio.jsx +59 -4
- package/src/project-browser.css +1073 -0
- package/src/project-browser.jsx +284 -123
- package/src/range-pin-object-transform.js +19 -0
- package/src/range-pin-panel.css +518 -0
- package/src/range-pin-panel.jsx +344 -0
- package/src/result-modal.jsx +5 -5
- package/src/room.jsx +79 -51
- package/src/scene-objects.js +37 -1
- package/src/scenes.js +5 -0
- package/src/semantic-edit.js +2 -0
- package/src/settings-menu.jsx +39 -105
- package/src/shell/BottomDock.jsx +450 -0
- package/src/shell/DetailsSlot.jsx +513 -0
- package/src/shell/LibrarySlot.jsx +86 -0
- package/src/shell/MenuBar.jsx +601 -0
- package/src/shell/OutlinerSlot.jsx +50 -0
- package/src/shell/PreferencesDialog.jsx +538 -0
- package/src/shell/PreferencesSlot.jsx +40 -0
- package/src/shell/StatusBar.jsx +125 -0
- package/src/shell/StudioShell.jsx +92 -0
- package/src/shell/TopBar.jsx +129 -0
- package/src/shell/ViewportToolbar.jsx +656 -0
- package/src/shell/agent-glass.css +296 -0
- package/src/shell/dock.css +93 -0
- package/src/shell/glass-regions.css +985 -0
- package/src/shell/glass.css +687 -0
- package/src/shell/log-store.js +52 -0
- package/src/shell/mode.css +9 -0
- package/src/shell/preferences.css +651 -0
- package/src/shell/shell.css +282 -0
- package/src/shell/studio-shell-context.js +19 -0
- package/src/shell/topbar.css +435 -0
- package/src/shell/viewport.css +451 -0
- package/src/store/authored-intent.js +22 -0
- package/src/store/runtime-adapters.js +92 -0
- package/src/store/scene-stage.js +21 -0
- package/src/store/use-document-store.js +14 -0
- package/src/studio-actions.js +210 -0
- package/src/studio-agent-commands.js +41 -271
- package/src/studio-agent-context.js +39 -10
- package/src/studio-agent-motion.js +122 -373
- package/src/studio-agent-protocol.js +67 -31
- package/src/studio-app-binding.js +379 -0
- package/src/studio-contact-sheet.js +73 -0
- package/src/studio-elements.js +58 -51
- package/src/styles/themes.css +200 -0
- package/src/styles/tokens.css +69 -0
- package/src/styles.css +460 -1742
- package/src/theme.js +44 -0
- package/src/timeline-extent.js +16 -0
- package/src/trail-key-conflicts.js +39 -0
- package/src/trail-pick.js +59 -0
- package/src/ui.jsx +8 -8
- package/src/use-case-question.jsx +66 -0
- package/src/workflow/AgentPanel.jsx +82 -69
- package/src/workflow/agent-client.js +12 -2
- package/src/workflow/agent-panel.css +34 -32
- package/src/workflow/cozy-scene-node.css +2 -0
- package/src/workflow/workflow.css +2 -0
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/bridge.mjs +164 -146
- package/tools/ardy/visual-qa.mjs +5 -5
- package/tools/bench/EXP3.md +61 -0
- package/tools/bench/cclay_bench_extract_incam.py +125 -0
- package/tools/bench/cclay_bench_extract_obs.py +204 -0
- package/tools/bench/cclay_bench_runner.py +42 -0
- package/tools/bench/cube-contact.mjs +162 -0
- package/tools/bench/exp3.mjs +152 -0
- package/tools/bench/extract-bench-lib.mjs +216 -0
- package/tools/bench/extract-bench.mjs +302 -0
- package/tools/bench/fal-generate.mjs +88 -0
- package/tools/bench/fit/README.md +189 -0
- package/tools/bench/fit/camera.mjs +59 -0
- package/tools/bench/fit/contact.mjs +419 -0
- package/tools/bench/fit/footlock.mjs +234 -0
- package/tools/bench/fit/motion.mjs +85 -0
- package/tools/bench/fit/pin.mjs +27 -0
- package/tools/bench/fit/remote.mjs +105 -0
- package/tools/bench/fit-bench.mjs +126 -0
- package/tools/bench/fit-sanity.mjs +45 -0
- package/tools/bench/metrics.mjs +313 -0
- package/tools/bench/obs/depth.mjs +258 -0
- package/tools/bench/obs/extrinsics.mjs +175 -0
- package/tools/bench/obs/fit_mannequin_betas.py +340 -0
- package/tools/bench/obs/ground.mjs +294 -0
- package/tools/bench/obs/heading.mjs +151 -0
- package/tools/bench/obs/ladder.mjs +555 -0
- package/tools/bench/obs/mannequin-betas.json +124 -0
- package/tools/bench/obs/remote.mjs +83 -0
- package/tools/bench/obs/rest_joints.py +52 -0
- package/tools/bench/obs/ybot-targets.mjs +49 -0
- package/tools/bench/obs-bench.mjs +520 -0
- package/tools/bench/score.mjs +343 -0
- package/tools/bench/summarize.mjs +86 -0
- package/tools/dev/pages/privacy.html +13 -8
- package/tools/gt-render/browser.mjs +159 -0
- package/tools/gt-render/camera-math.mjs +276 -0
- package/tools/gt-render/page.mjs +267 -0
- package/tools/gt-render/render.mjs +514 -0
- package/tools/gt-render/scene-box.mjs +31 -0
- package/tools/gt-render/take-transform.mjs +87 -0
- package/tools/morphgs/assets/gen_truth.py +39 -0
- package/tools/morphgs/assets/mesh_ori_rig.txt +27 -0
- package/tools/morphgs/assets/playback-check.mjs +30 -0
- package/tools/morphgs/demo-gate.sh +26 -0
- package/tools/morphgs/fbx2morphgs.mjs +68 -0
- package/tools/morphgs/morphgs-to-cskel27.mjs +105 -0
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +76 -0
- package/tools/morphgs/setup-on-cluster.sh +126 -0
- package/tools/qa/css-rule-usage.mjs +283 -0
- package/tools/qa/studio-control-count.mjs +70 -32
- package/tools/run-tests.mjs +134 -12
- package/tools/track/DESIGN.md +513 -0
- package/tools/track/backfill-provenance.mjs +116 -0
- package/tools/track/budget.mjs +8 -0
- package/tools/track/check-rig.mjs +209 -0
- package/tools/track/diagnostics.schema.json +60 -0
- package/tools/track/export-rig.mjs +256 -0
- package/tools/track/fallback.mjs +7 -0
- package/tools/track/fk-parity-fixture.mjs +161 -0
- package/tools/track/gate.mjs +349 -0
- package/tools/track/masks.mjs +318 -0
- package/tools/track/metrics.mjs +201 -0
- package/tools/track/publish-obs.mjs +108 -0
- package/tools/track/py/check_env.py +61 -0
- package/tools/track/py/eval_lr.py +229 -0
- package/tools/track/py/lr_viterbi.py +294 -0
- package/tools/track/py/masks.py +405 -0
- package/tools/track/py/objective.py +382 -0
- package/tools/track/py/rig.py +508 -0
- package/tools/track/py/scene.py +326 -0
- package/tools/track/py/test_joint_indices.py +123 -0
- package/tools/track/py/test_lr_viterbi.py +195 -0
- package/tools/track/py/test_masks.py +229 -0
- package/tools/track/py/test_rig.py +288 -0
- package/tools/track/py/test_scene.py +354 -0
- package/tools/track/py/test_track.py +328 -0
- package/tools/track/py/track.py +411 -0
- package/tools/track/remote.mjs +140 -0
- package/tools/track/rig-dump.mjs +227 -0
- package/tools/track/run-box-tests.mjs +14 -0
- package/tools/track/run-box.mjs +168 -0
- package/tools/track/setup-box.sh +59 -0
- package/tools/track/study-2d.mjs +728 -0
- package/dist/assets/analytics-B1hnH66c.js +0 -1
- package/dist/assets/app-BWusbqgO.js +0 -4861
- package/dist/assets/app-qKDo4PBX.css +0 -1
- package/dist/assets/shot-prompt-CHUtw6af.js +0 -17
- package/dist/assets/shot-prompt-C_g2BHVc.css +0 -1
- package/dist/assets/workflow-CVwzMhz2.js +0 -179
- package/dist/assets/workflow-DsudxKHF.css +0 -1
- package/src/fal-motion-studio.jsx +0 -180
- package/src/scene-history.js +0 -129
package/src/ardy/timeline.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { frameFromClientX, groupKeyRuns, KEY_RUN_MIN, motionTrimRange, promptMoveStartFrame, shotBlockGeometry } from "./timeline-coordinates.js";
|
|
2
|
+
import { frameFromClientX, groupKeyRuns, KEY_RUN_MIN, motionTrimRange, promptMoveStartFrame, rangePinBandGeometry, shotBlockGeometry } from "./timeline-coordinates.js";
|
|
3
3
|
import { motionSegmentSpeedForFrames } from "./motion-edit.js";
|
|
4
4
|
import { createPlaybackClock } from "./playback-clock.js";
|
|
5
5
|
import { promptResizeFrame } from "./timeline-resize.js";
|
|
@@ -8,6 +8,7 @@ import { buildRail, craneHeightAt } from "../camera-follow.js";
|
|
|
8
8
|
import { pathMetrics } from "../object-path.js";
|
|
9
9
|
import { flatTiming, timingIsFlat, envelopeDrag, insertCut, removeCut, CUT_MIN_GAP } from "../speed-envelope.js";
|
|
10
10
|
import { checkShotAgainstPreset, presetById } from "../model-presets.js";
|
|
11
|
+
import "./timeline.css";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* ARDY Viser-style animation timeline — the live motion workspace.
|
|
@@ -46,10 +47,10 @@ const TRACKS = [
|
|
|
46
47
|
"Shots",
|
|
47
48
|
];
|
|
48
49
|
const TRACK_LABELS_KO = {
|
|
49
|
-
Prompts: ko("
|
|
50
|
-
"Full-Body": ko("
|
|
51
|
-
"2D Root": ko("
|
|
52
|
-
Shots: ko("
|
|
50
|
+
Prompts: ko("Prompt", "프롬프트"),
|
|
51
|
+
"Full-Body": ko("Body", "바디"),
|
|
52
|
+
"2D Root": ko("Root path", "루트 경로"),
|
|
53
|
+
Shots: ko("Camera Cuts", "카메라 컷"),
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
/** IK keys live on the Full-Body lane: one marker per keyed frame, holding
|
|
@@ -71,19 +72,6 @@ const framePct = (f, count) => (count > 1 ? f / (count - 1) : 0);
|
|
|
71
72
|
// displayed total duration, as it is in an actual frame sequence).
|
|
72
73
|
const formatTimelineSeconds = (seconds) => `${Math.max(0, Number(seconds) || 0).toFixed(2)}s`;
|
|
73
74
|
|
|
74
|
-
// Keep related timeline controls visually together without another stylesheet
|
|
75
|
-
// dependency. The head is a single flex row, so each group reads like one
|
|
76
|
-
// instrument instead of a string of unrelated buttons.
|
|
77
|
-
const TL_HEAD_GROUP_STYLE = {
|
|
78
|
-
display: "inline-flex",
|
|
79
|
-
alignItems: "center",
|
|
80
|
-
gap: 5,
|
|
81
|
-
padding: "2px 4px",
|
|
82
|
-
border: "1px solid var(--line)",
|
|
83
|
-
borderRadius: 6,
|
|
84
|
-
background: "rgba(0, 0, 0, .12)",
|
|
85
|
-
};
|
|
86
|
-
|
|
87
75
|
const CAMERA_BLOCK_DEFAULTS = {
|
|
88
76
|
distance: 3,
|
|
89
77
|
height: 1.6,
|
|
@@ -971,6 +959,10 @@ export default function Timeline({
|
|
|
971
959
|
// Full-Body lane. Frames are already on the timeline's 24 fps clock.
|
|
972
960
|
motion = null, // { frames, label } | null
|
|
973
961
|
ikFrames = [], // sorted full-body key frames
|
|
962
|
+
rangePins = [], // active character's inclusive pin ranges
|
|
963
|
+
previewRanges = [], // amber preview spans shown under the Body track
|
|
964
|
+
selectedPinId = null,
|
|
965
|
+
pendingPinRange = null,
|
|
974
966
|
footSnap = true, // feet stay planted while the body moves
|
|
975
967
|
bodyContact = true, // body markers stay above the floor
|
|
976
968
|
shots = [],
|
|
@@ -996,9 +988,9 @@ export default function Timeline({
|
|
|
996
988
|
onPromptMove,
|
|
997
989
|
onPromptRemove,
|
|
998
990
|
onClearMotion,
|
|
999
|
-
onIkToggle,
|
|
1000
991
|
onIkKeyframeAdd,
|
|
1001
992
|
onIkKeyframeRemove,
|
|
993
|
+
onPinSelect,
|
|
1002
994
|
onFootSnapToggle,
|
|
1003
995
|
onBodyContactToggle,
|
|
1004
996
|
onCameraMoveSelect,
|
|
@@ -1064,7 +1056,7 @@ export default function Timeline({
|
|
|
1064
1056
|
// The window key/interval handlers register once; the latest callbacks
|
|
1065
1057
|
// are read through a ref so they never go stale mid-playback.
|
|
1066
1058
|
const handlers = useRef({});
|
|
1067
|
-
handlers.current = { onScrub, onAdvance, onStep, onPlayToggle, onWaypointToggle, onMarkerSelect, onMarkerRemove, onRootKeyframeAdd, onPromptAdd, onPromptSelect, onPromptChange, onPromptResize, onPromptMove, onPromptRemove,
|
|
1059
|
+
handlers.current = { onScrub, onAdvance, onStep, onPlayToggle, onWaypointToggle, onMarkerSelect, onMarkerRemove, onRootKeyframeAdd, onPromptAdd, onPromptSelect, onPromptChange, onPromptResize, onPromptMove, onPromptRemove, onIkKeyframeAdd, onIkKeyframeRemove, onPinSelect, onFootSnapToggle, onBodyContactToggle, onCameraMoveSelect, onCameraKeyframeAdd, onCameraKeyframeMove, onCameraKeyframeRemove, onCameraBlockSelect, onCameraBlockChange, onCameraPreview, onCameraRailDrawToggle, onCameraRailDelete, onObjectPathDrawToggle, onObjectPathChange, onObjectPathClear, onObjectTimingGestureStart, onObjectTimingGestureEnd, onShotSelect, onShotBoundaryMove, onShotRename, onShotRemove, onShotDuplicate, onShotCut, onShotSplit, onShotMove, onMotionTrim, onMotionTrimReset, onMotionCut, onMotionSpeedChange, onMotionSegmentRemove, onEditGestureStart };
|
|
1068
1060
|
|
|
1069
1061
|
// Trackpad/wheel zoom over the FRAME ruler lane only. React registers
|
|
1070
1062
|
// onWheel as passive, so a synthetic onWheel could never preventDefault —
|
|
@@ -1212,6 +1204,10 @@ export default function Timeline({
|
|
|
1212
1204
|
// the raw list draws a wall of diamonds. Collapse consecutive frames into
|
|
1213
1205
|
// runs first; only runs shorter than KEY_RUN_MIN stay diamonds.
|
|
1214
1206
|
const ikRuns = useMemo(() => groupKeyRuns(ikFrames), [ikFrames]);
|
|
1207
|
+
const pinBands = useMemo(() => rangePins.map((pin) => ({
|
|
1208
|
+
pin,
|
|
1209
|
+
geometry: rangePinBandGeometry(pin, frameCount, displayFrameCount),
|
|
1210
|
+
})).filter((entry) => entry.geometry), [displayFrameCount, frameCount, rangePins]);
|
|
1215
1211
|
const moveRef = useRef(null);
|
|
1216
1212
|
const suppressPromptClickRef = useRef(false);
|
|
1217
1213
|
const resizeRef = useRef(null);
|
|
@@ -1718,6 +1714,8 @@ export default function Timeline({
|
|
|
1718
1714
|
// The motion-layer track tools (Prompts +, the Full-Body Cut, the trim and
|
|
1719
1715
|
// retime grips) edit the take, so Scene and Camera never render them.
|
|
1720
1716
|
const motionTools = workflowMode === "motion";
|
|
1717
|
+
const activeShotForCut = shots[activeShotIdx] ?? null;
|
|
1718
|
+
const canCutShot = Boolean(activeShotForCut && frame > activeShotForCut.startFrame && frame <= activeShotForCut.endFrame);
|
|
1721
1719
|
// A trim or retime grip edits ONE segment: it belongs to the segment the
|
|
1722
1720
|
// playhead selects. The trim preview clip carries the live gesture, so it
|
|
1723
1721
|
// keeps its grips while the pointer is down.
|
|
@@ -1728,10 +1726,11 @@ export default function Timeline({
|
|
|
1728
1726
|
};
|
|
1729
1727
|
|
|
1730
1728
|
return (
|
|
1731
|
-
<section className={"timeline" + (expanded ? "" : " collapsed") + (!shots.length ? " empty-shots" : "")} aria-label={ko("Animation timeline", "애니메이션 타임라인")}>
|
|
1729
|
+
<section className={"timeline v2-sequencer" + (expanded ? "" : " collapsed") + (!shots.length ? " empty-shots" : "")} data-testid="sequencer" aria-label={ko("Animation timeline", "애니메이션 타임라인")}>
|
|
1732
1730
|
{expanded ? (
|
|
1733
1731
|
<>
|
|
1734
1732
|
<div className="tl-head">
|
|
1733
|
+
<strong className="tl-title">{ko("Sequencer", "시퀀서")}</strong>
|
|
1735
1734
|
<div className="tl-transport" aria-label={ko("Playback transport", "재생 컨트롤")}>
|
|
1736
1735
|
<button
|
|
1737
1736
|
type="button"
|
|
@@ -1760,11 +1759,74 @@ export default function Timeline({
|
|
|
1760
1759
|
>
|
|
1761
1760
|
›
|
|
1762
1761
|
</button>
|
|
1763
|
-
<span className="tl-readout" aria-live="polite">
|
|
1764
|
-
<b>{
|
|
1762
|
+
<span className="tl-readout" data-testid="sequencer-frame-readout" aria-live="polite">
|
|
1763
|
+
<b>{String(Math.max(0, frame)).padStart(4, "0")}</b><span> / {String(Math.max(0, frameCount)).padStart(4, "0")} · {fps}fps</span>
|
|
1765
1764
|
</span>
|
|
1766
1765
|
</div>
|
|
1767
|
-
<div className="tl-
|
|
1766
|
+
<div className="tl-sequencer-actions" role="group" aria-label={ko("Sequencer actions", "시퀀서 작업")}>
|
|
1767
|
+
{motionTools ? (<>
|
|
1768
|
+
<button
|
|
1769
|
+
type="button"
|
|
1770
|
+
className="tl-seq-action cut"
|
|
1771
|
+
data-testid="sequencer-cut"
|
|
1772
|
+
disabled={!selectedMotionSegment || frame <= 0 || frame >= motion.frames}
|
|
1773
|
+
title={ko("Cut the Body clip at the playhead", "재생 헤드에서 바디 클립 자르기")}
|
|
1774
|
+
onClick={() => handlers.current.onMotionCut?.()}
|
|
1775
|
+
>
|
|
1776
|
+
{ko("Cut", "컷")}
|
|
1777
|
+
</button>
|
|
1778
|
+
<button
|
|
1779
|
+
type="button"
|
|
1780
|
+
className="tl-seq-action"
|
|
1781
|
+
data-testid="sequencer-add-block"
|
|
1782
|
+
title={ko("Add a prompt block at the playhead", "재생 헤드에 프롬프트 블록 추가")}
|
|
1783
|
+
onClick={() => handlers.current.onPromptAdd?.(frame)}
|
|
1784
|
+
>
|
|
1785
|
+
{ko("+ Block", "+ 블록")}
|
|
1786
|
+
</button>
|
|
1787
|
+
</>) : workflowMode === "pose" || ikMode ? (
|
|
1788
|
+
<button
|
|
1789
|
+
type="button"
|
|
1790
|
+
className="tl-seq-action cut"
|
|
1791
|
+
data-testid="sequencer-cut"
|
|
1792
|
+
disabled={!selectedMotionSegment || frame <= 0 || frame >= (motion?.frames ?? 0)}
|
|
1793
|
+
onClick={() => handlers.current.onMotionCut?.()}
|
|
1794
|
+
>
|
|
1795
|
+
{ko("Cut", "컷")}
|
|
1796
|
+
</button>
|
|
1797
|
+
) : (<>
|
|
1798
|
+
<button
|
|
1799
|
+
type="button"
|
|
1800
|
+
className="tl-seq-action cut"
|
|
1801
|
+
data-testid="sequencer-cut"
|
|
1802
|
+
disabled={!canCutShot}
|
|
1803
|
+
title={ko("Split the selected Camera Cut at the playhead", "선택한 카메라 컷을 재생 헤드에서 나눕니다")}
|
|
1804
|
+
onClick={() => activeShotForCut && handlers.current.onShotSplit?.(activeShotForCut.id)}
|
|
1805
|
+
>
|
|
1806
|
+
{ko("Cut", "컷")}
|
|
1807
|
+
</button>
|
|
1808
|
+
<button
|
|
1809
|
+
type="button"
|
|
1810
|
+
className="tl-seq-action rail"
|
|
1811
|
+
data-testid="sequencer-draw-rail"
|
|
1812
|
+
title={ko("Draw or redraw the selected camera rail", "선택한 카메라 레일을 그리거나 다시 그립니다")}
|
|
1813
|
+
onClick={() => handlers.current.onCameraRailDrawToggle?.()}
|
|
1814
|
+
>
|
|
1815
|
+
{ko("Draw rail", "레일 그리기")}
|
|
1816
|
+
</button>
|
|
1817
|
+
<button
|
|
1818
|
+
type="button"
|
|
1819
|
+
className="tl-seq-action"
|
|
1820
|
+
data-testid="sequencer-add-track"
|
|
1821
|
+
disabled={shotCutDisabled}
|
|
1822
|
+
title={ko("Add a Camera Cut track block", "카메라 컷 트랙 블록 추가")}
|
|
1823
|
+
onClick={() => handlers.current.onShotCut?.()}
|
|
1824
|
+
>
|
|
1825
|
+
{ko("+ Track", "+ 트랙")}
|
|
1826
|
+
</button>
|
|
1827
|
+
</>) }
|
|
1828
|
+
</div>
|
|
1829
|
+
<div className="tl-head-group tl-view-tools" role="group" aria-label={ko("Timeline view tools", "타임라인 보기 도구")}>
|
|
1768
1830
|
<button
|
|
1769
1831
|
type="button"
|
|
1770
1832
|
className={"tl-btn zoom" + (zoom !== ZOOM_DEFAULT ? " on" : "")}
|
|
@@ -1779,28 +1841,18 @@ export default function Timeline({
|
|
|
1779
1841
|
className={"tl-btn wp" + (waypointMode ? " on" : "")}
|
|
1780
1842
|
aria-pressed={waypointMode}
|
|
1781
1843
|
aria-label={ko("Root path mode", "루트 경로 모드")}
|
|
1782
|
-
title={ko("Enable or disable
|
|
1844
|
+
title={ko("Enable or disable root path constraints (P)", "루트 경로 제약 켜기/끄기 (P)")}
|
|
1783
1845
|
onClick={() => handlers.current.onWaypointToggle?.()}
|
|
1784
1846
|
>
|
|
1785
1847
|
{isKo ? `웨이포인트 ${waypointMode ? "켜짐" : "꺼짐"}` : `Waypoint ${waypointMode ? "on" : "off"}`}
|
|
1786
1848
|
</button>
|
|
1787
1849
|
</div>
|
|
1788
|
-
<div className="tl-head-group tl-pose-tools" role="group" aria-label={ko("Pose correction tools", "포즈 보정 도구")}
|
|
1850
|
+
<div className="tl-head-group tl-pose-tools" role="group" aria-label={ko("Pose correction tools", "포즈 보정 도구")}>
|
|
1789
1851
|
{/* No rig, no pose editing: the group says why instead of
|
|
1790
1852
|
offering a button that cannot do anything (R3). */}
|
|
1791
1853
|
{ikDisabled && !ikMode ? (
|
|
1792
1854
|
<span className="tl-pose-hint">{ko("Load a rig to edit poses", "리그를 로드하면 포즈를 편집할 수 있어요")}</span>
|
|
1793
1855
|
) : (<>
|
|
1794
|
-
<button
|
|
1795
|
-
type="button"
|
|
1796
|
-
className={"tl-btn ik" + (ikMode ? " on" : "")}
|
|
1797
|
-
aria-pressed={ikMode}
|
|
1798
|
-
aria-label={ko("Inverse kinematics", "역운동학")}
|
|
1799
|
-
title={ko("IK mode — drag a wrist / ankle handle; keys land on the Full-Body lane. With a motion loaded, keys correct it layer-style", "IK 모드 — 손목이나 발목 핸들을 드래그하세요. 키는 전신 레인에 찍히며, 모션을 불러온 뒤에는 레이어 방식으로 보정합니다")}
|
|
1800
|
-
onClick={() => handlers.current.onIkToggle?.()}
|
|
1801
|
-
>
|
|
1802
|
-
{isKo ? `IK ${ikMode ? "켜짐" : "꺼짐"}` : `IK ${ikMode ? "on" : "off"}`}
|
|
1803
|
-
</button>
|
|
1804
1856
|
{/* Foot snap and Body contact only reinterpret an IK drag, so
|
|
1805
1857
|
they live for exactly as long as IK does (R7). Two
|
|
1806
1858
|
independent bits, two toggles. */}
|
|
@@ -1829,7 +1881,7 @@ export default function Timeline({
|
|
|
1829
1881
|
</>)}
|
|
1830
1882
|
</div>
|
|
1831
1883
|
{(selectedMotionSegment || onClearMotion) && (
|
|
1832
|
-
<div className="tl-head-group tl-motion-tools" role="group" aria-label={ko("Motion controls", "모션 컨트롤")}
|
|
1884
|
+
<div className="tl-head-group tl-motion-tools" role="group" aria-label={ko("Motion controls", "모션 컨트롤")}>
|
|
1833
1885
|
{selectedMotionSegment && (
|
|
1834
1886
|
<label className="tl-motion-speed-editor">
|
|
1835
1887
|
<span>{ko(`Segment ${motionSegments.indexOf(selectedMotionSegment) + 1} speed`, `구간 ${motionSegments.indexOf(selectedMotionSegment) + 1} 배율`)}</span>
|
|
@@ -1971,9 +2023,23 @@ export default function Timeline({
|
|
|
1971
2023
|
onTimingGestureEnd={() => handlers.current.onObjectTimingGestureEnd?.()}
|
|
1972
2024
|
/>
|
|
1973
2025
|
) : TRACKS.map((name) => (
|
|
1974
|
-
<div className={"tl-track" + (name === "Prompts" ? " prompts" : "") + (name === IK_LANE ? " ik" : "") + (name === SHOTS_LANE ? " shots" : "")} key={name}>
|
|
1975
|
-
<span className="tl-track-label">
|
|
1976
|
-
{
|
|
2026
|
+
<div className={"tl-track" + (name === "Prompts" ? " prompts" : "") + (name === IK_LANE ? " ik" : "") + (name === SHOTS_LANE ? " shots" : "")} data-track-id={name} key={name}>
|
|
2027
|
+
<span className="tl-track-label tl-track-tree">
|
|
2028
|
+
{name === IK_LANE ? (
|
|
2029
|
+
<span className="tl-track-label-stack">
|
|
2030
|
+
<span className="tl-track-subrow"><span className="tl-track-chip body" data-lane-label="Body">{ko("Body", "바디")}</span></span>
|
|
2031
|
+
<span className="tl-track-subrow indent"><span className="tl-track-chip pins" data-lane-label="Pins">{ko("Pins", "핀")}</span></span>
|
|
2032
|
+
<span className="tl-track-subrow indent"><span className="tl-track-chip ik" data-lane-label="IK keys">{ko("IK keys", "IK 키")}</span></span>
|
|
2033
|
+
</span>
|
|
2034
|
+
) : name === SHOTS_LANE ? (
|
|
2035
|
+
<span className="tl-track-label-stack">
|
|
2036
|
+
<span className="tl-track-subrow"><span className="tl-track-chip camera" data-lane-label="Camera Cuts">{ko("Camera Cuts", "카메라 컷")}</span></span>
|
|
2037
|
+
<span className="tl-track-subrow indent"><span className="tl-track-chip rail" data-lane-label="Rail">{ko("Rail", "레일")}</span></span>
|
|
2038
|
+
<span className="tl-track-subrow indent"><span className="tl-track-chip crane" data-lane-label="Crane">{ko("Crane", "크레인")}</span></span>
|
|
2039
|
+
</span>
|
|
2040
|
+
) : (
|
|
2041
|
+
<span className="tl-track-subrow"><span className={"tl-track-chip " + (name === "2D Root" ? "root" : "prompt")} data-lane-label={TRACK_LABELS_KO[name]}>{TRACK_LABELS_KO[name]}</span></span>
|
|
2042
|
+
)}
|
|
1977
2043
|
{trackOwner && (name === "Prompts" || name === "2D Root") && <em className="tl-track-owner">{trackOwner}</em>}
|
|
1978
2044
|
{name === "2D Root" && pathSpeed && (
|
|
1979
2045
|
<em
|
|
@@ -2035,6 +2101,13 @@ export default function Timeline({
|
|
|
2035
2101
|
{gridFrames.map((f) => (
|
|
2036
2102
|
<i key={f} className="tl-grid" style={{ "--tl-f": framePct(f, displayFrameCount) }} aria-hidden="true" />
|
|
2037
2103
|
))}
|
|
2104
|
+
{name === IK_LANE && previewRanges.map((range, index) => {
|
|
2105
|
+
const startFrame = Number.isFinite(Number(range?.startFrame)) ? Number(range.startFrame) : Number(range?.start ?? 0);
|
|
2106
|
+
const endFrame = Number.isFinite(Number(range?.endFrame)) ? Number(range.endFrame) : Number(range?.end ?? startFrame);
|
|
2107
|
+
const start = Math.max(0, Math.min(displayFrameCount - 1, Math.round(startFrame)));
|
|
2108
|
+
const end = Math.max(start, Math.min(displayFrameCount - 1, Math.round(endFrame)));
|
|
2109
|
+
return <span key={`preview:${index}`} className="tl-preview-range" style={{ "--tl-f-start": framePct(start, displayFrameCount), "--tl-f-end": framePct(end, displayFrameCount) }} aria-label={ko(`Preview range ${start}–${end}`, `미리보기 범위 ${start}–${end}`)} />;
|
|
2110
|
+
})}
|
|
2038
2111
|
{name === SHOTS_LANE && shots.length === 0 && (
|
|
2039
2112
|
<div className="tl-shot-empty">
|
|
2040
2113
|
<span>{ko("No shots yet — use + Add shot in the lane header to create one.", "아직 샷이 없습니다 — 레인 헤더의 + 샷 추가로 만들어 보세요.")}</span>
|
|
@@ -2232,7 +2305,42 @@ export default function Timeline({
|
|
|
2232
2305
|
</div>
|
|
2233
2306
|
);
|
|
2234
2307
|
})}
|
|
2235
|
-
{name === IK_LANE &&
|
|
2308
|
+
{name === IK_LANE && pinBands.map(({ pin, geometry }) => {
|
|
2309
|
+
return (
|
|
2310
|
+
<button
|
|
2311
|
+
key={`pin:${pin.id}`}
|
|
2312
|
+
type="button"
|
|
2313
|
+
className={`tl-pin-band${selectedPinId === pin.id ? " selected" : ""}`}
|
|
2314
|
+
style={{
|
|
2315
|
+
"--tl-f-start": geometry.startPct,
|
|
2316
|
+
"--tl-f-end": geometry.endPct,
|
|
2317
|
+
"--tl-pin-ramp-start": geometry.rampStartPct,
|
|
2318
|
+
"--tl-pin-ramp-end": geometry.rampEndPct,
|
|
2319
|
+
"--tl-pin-color": "var(--select, #e8a33d)",
|
|
2320
|
+
}}
|
|
2321
|
+
title={ko(`${pin.track} pin · frames ${pin.startFrame}–${pin.endFrame}`, `${pin.track} 고정 · ${pin.startFrame}–${pin.endFrame}프레임`)}
|
|
2322
|
+
onPointerDown={(event) => {
|
|
2323
|
+
if (event.button !== 0) return;
|
|
2324
|
+
event.stopPropagation();
|
|
2325
|
+
handlers.current.onPinSelect?.(pin.id);
|
|
2326
|
+
}}
|
|
2327
|
+
>
|
|
2328
|
+
<i className="tl-pin-band-ramp start" aria-hidden="true" />
|
|
2329
|
+
<i className="tl-pin-band-core" aria-hidden="true" />
|
|
2330
|
+
<i className="tl-pin-band-ramp end" aria-hidden="true" />
|
|
2331
|
+
</button>
|
|
2332
|
+
);
|
|
2333
|
+
})}
|
|
2334
|
+
{name === IK_LANE && pendingPinRange && (() => {
|
|
2335
|
+
const geometry = rangePinBandGeometry(pendingPinRange, frameCount, displayFrameCount);
|
|
2336
|
+
if (!geometry) return null;
|
|
2337
|
+
return <span
|
|
2338
|
+
className="tl-pin-pending"
|
|
2339
|
+
style={{ "--tl-f-start": geometry.startPct, "--tl-f-end": geometry.endPct }}
|
|
2340
|
+
aria-label={ko(`Pending pin range ${geometry.startFrame}–${geometry.endFrame}`, `대기 중인 고정 범위 ${geometry.startFrame}–${geometry.endFrame}`)}
|
|
2341
|
+
/>;
|
|
2342
|
+
})()}
|
|
2343
|
+
{name === IK_LANE && displayMotionSegments.map((segment, index) => (
|
|
2236
2344
|
<div
|
|
2237
2345
|
key={segment.id}
|
|
2238
2346
|
className={"tl-motion-clip" + (trimPreview ? " trimming" : "") + (segment.previewSpeed !== undefined ? " retiming" : "") + (selectedMotionSegment?.id === segment.id ? " selected" : "")}
|
package/src/ardy/waypoints.js
CHANGED
|
@@ -344,8 +344,8 @@ export function judgeAuthoredPath(rootPath, fps, clipFrames, { chained = false }
|
|
|
344
344
|
if (!chained && clipFrames / fps > PATH_LIMITS.clipMaxS) {
|
|
345
345
|
errors.push(
|
|
346
346
|
isKo
|
|
347
|
-
? `이동 경로는 전체 클립을 한 번의 모델 호출로 만들어요.
|
|
348
|
-
: `a root path generates the whole clip in one model call, and
|
|
347
|
+
? `이동 경로는 전체 클립을 한 번의 모델 호출로 만들어요. 모션 모델이 학습한 길이는 ${PATH_LIMITS.clipMaxS}초까지예요 — 클립을 ${PATH_LIMITS.clipMaxS}초로 줄이거나 프롬프트를 블록으로 나눠 이어 만들기를 사용해 주세요`
|
|
348
|
+
: `a root path generates the whole clip in one model call, and the motion model's trained window is ${PATH_LIMITS.clipMaxS} s — shorten the clip to ${PATH_LIMITS.clipMaxS} s, or split the prompt into blocks so the path rides a chained rollout`,
|
|
349
349
|
);
|
|
350
350
|
}
|
|
351
351
|
const tail = (clipFrames - 1 - (ordered[ordered.length - 1]?.frame ?? 0)) / fps;
|