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
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { useStudioShell } from "./studio-shell-context.js";
|
|
2
|
+
import { ko } from "../locale.js";
|
|
3
|
+
import { sceneObjectNameDisplayKo, HIERARCHY_INSPECTOR_TITLES, CHARACTER_MODEL_LABELS } from "../app-stage.jsx";
|
|
4
|
+
import { CUTOUT_KIND } from "../scene-objects.js";
|
|
5
|
+
import LightPanel from "../panels/LightPanel.jsx";
|
|
6
|
+
import CameraPanel from "../panels/CameraPanel.jsx";
|
|
7
|
+
import SubjectsPanel from "../panels/SubjectsPanel.jsx";
|
|
8
|
+
import CharacterTransformPanel from "../panels/CharacterTransformPanel.jsx";
|
|
9
|
+
import RigPanel from "../panels/RigPanel.jsx";
|
|
10
|
+
import PosePanel from "../panels/PosePanel.jsx";
|
|
11
|
+
import VideoCapturePanel from "../panels/VideoCapturePanel.jsx";
|
|
12
|
+
import PromptBlocksPanel from "../panels/PromptBlocksPanel.jsx";
|
|
13
|
+
import RigControlPanel from "../panels/RigControlPanel.jsx";
|
|
14
|
+
import EnvironmentPanel from "../panels/EnvironmentPanel.jsx";
|
|
15
|
+
import PropsPanel from "../panels/PropsPanel.jsx";
|
|
16
|
+
import ObjectTransformPanel from "../panels/ObjectTransformPanel.jsx";
|
|
17
|
+
import { ASSET_IMAGE_TYPES } from "../scene-assets.js";
|
|
18
|
+
import { PoseStudioPanel } from "../posestudio.jsx";
|
|
19
|
+
import { DEFAULT_POSE } from "../poses.js";
|
|
20
|
+
import "../panels/details.css";
|
|
21
|
+
|
|
22
|
+
const characterIndexForRow = (rowId, characters) => rowId === "characterA" ? 0
|
|
23
|
+
: rowId === "characterB" ? 1
|
|
24
|
+
: characters.findIndex((entry) => `character:${entry.id}` === rowId);
|
|
25
|
+
const characterTitle = (index) => ko(`Character ${index + 1}`, `인물 ${index + 1}`);
|
|
26
|
+
|
|
27
|
+
/** The 2a selection header: the selected item's name and a "Kind · detail"
|
|
28
|
+
* type line. Null when nothing that owns settings is selected. */
|
|
29
|
+
function detailsSelection({ selectedHierarchyId: id, selectedSceneObject, rigSelection, characters, shot, inspectorHasContent }) {
|
|
30
|
+
if (!inspectorHasContent) return null;
|
|
31
|
+
if (selectedSceneObject) {
|
|
32
|
+
const kind = selectedSceneObject.renderer === CUTOUT_KIND ? ko("Cutout", "컷아웃") : ko("Mesh", "메시");
|
|
33
|
+
return { name: sceneObjectNameDisplayKo(selectedSceneObject.name), type: `${ko("Prop", "소품")} · ${kind}` };
|
|
34
|
+
}
|
|
35
|
+
if (rigSelection) {
|
|
36
|
+
const index = characterIndexForRow(rigSelection.rowId, characters);
|
|
37
|
+
const kind = rigSelection.token === "rig" ? ko("Rig", "리그") : ko("Bone", "본");
|
|
38
|
+
return { name: HIERARCHY_INSPECTOR_TITLES[rigSelection.token] ?? kind, type: index >= 0 ? `${kind} · ${characterTitle(index)}` : kind };
|
|
39
|
+
}
|
|
40
|
+
if (id === "camera") return { name: HIERARCHY_INSPECTOR_TITLES.camera, type: `${ko("Camera", "카메라")} · ${shot.focalMm}mm` };
|
|
41
|
+
if (id === "characterA" || id === "characterB" || id.startsWith("character:")) {
|
|
42
|
+
const index = characterIndexForRow(id, characters);
|
|
43
|
+
const model = CHARACTER_MODEL_LABELS[characters[index]?.model];
|
|
44
|
+
return { name: characterTitle(Math.max(index, 0)), type: model ? `${ko("Character", "인물")} · ${model}` : ko("Character", "인물") };
|
|
45
|
+
}
|
|
46
|
+
const types = {
|
|
47
|
+
shot: ko("Scene", "장면"),
|
|
48
|
+
light: ko("Light", "조명"),
|
|
49
|
+
characters: ko("Folder", "폴더"),
|
|
50
|
+
environment: ko("Environment", "환경"),
|
|
51
|
+
props: ko("Folder", "폴더"),
|
|
52
|
+
};
|
|
53
|
+
return { name: HIERARCHY_INSPECTOR_TITLES[id] ?? ko("Selection", "선택 항목"), type: types[id] ?? "" };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default function DetailsSlot() {
|
|
57
|
+
const {
|
|
58
|
+
selectedHierarchyId, sceneSaveError, studioAgentError, embedMode, studioAgentMode,
|
|
59
|
+
setStudioAgentMode, AgentPanel, setAgentCollapsed, scenes, activeSceneId,
|
|
60
|
+
buildStudioAgentContext, highlightAgentTargets, generateI2vMotionFromUi, selectedSceneObject, rigSelection,
|
|
61
|
+
inspectorActionsOpen, setInspectorActionsOpen, runStudioAction, deleteSelectedSceneObject, inspectorHasContent,
|
|
62
|
+
keyLightSelected, keyLight, isCameraSelection,
|
|
63
|
+
shot, moveSequence, cameraKeys, activeShot,
|
|
64
|
+
isCharacterSelection, showB, characters, openStudio,
|
|
65
|
+
posing, workflowMode, activeChar,
|
|
66
|
+
activeCharIndex, i2vMotionModel, i2vMotionActions, setI2vMotionStudioOpen,
|
|
67
|
+
selectablePoses, ikMode, ikApplyPoseAsKey, motion, setStudioPick,
|
|
68
|
+
appContext, removePose, setPhotoPoseError, photoPoseFileRef, photoPoseState,
|
|
69
|
+
photoPoseError, activeRig, saveCurrentPose, multiModelStatus, multiModelStage,
|
|
70
|
+
multiModelFileRef, chooseMultiModelFile, multiModelSource, multiModelUrl, setMultiModelUrl,
|
|
71
|
+
useMultiModelUrl, pasteMultiModelUrl, multiModelProgress, multiModelError, multiModelFootage,
|
|
72
|
+
extractMultiModelMotion, multiModelExtract, multiModelTake, multiModelExtractProgress, multiModelExtractError,
|
|
73
|
+
bridge, promptBlocksReveal, promptClips, selectedPromptId, setSelectedPromptId,
|
|
74
|
+
setArdyPrompt, setTlFrame, tlFrameCount, ardySeed,
|
|
75
|
+
changeArdySeed, lineEditMode, toggleLineEditMode, linePreviewUrl, lineCurve,
|
|
76
|
+
lineDrifted, lineTrack, setLineTrack, linePinMode, setLinePinMode,
|
|
77
|
+
linePins, lineClipFrames, lineEditRange, setLineRange, lineRadius,
|
|
78
|
+
changeLineRadius, lineCurveDirty, lineEditFrom, lineEditTo, lineCurvePointCount,
|
|
79
|
+
lineDriftHint, lineCurveHidden, linePreviewBusy, linePreviewMs, linePreviewError,
|
|
80
|
+
generationBusy, bridgeChecking, lineReadinessState, runLineEdit, openMotionSetup,
|
|
81
|
+
recheckMotionHealth, resetLineCurve, exitLineEditMode, readinessState, ardyRunning,
|
|
82
|
+
cancelArdy, ardyStatus, ardyOutcome, tlFrame,
|
|
83
|
+
isRigSelection, ikChains, ikFocus, footSnap, collisionCleanupSupported,
|
|
84
|
+
autoPhysicsRunning, physicsProgress, physicsPreview,
|
|
85
|
+
physicsShow, physicsOptions, platformFitRunning, platformFitProgress, platformFitLast,
|
|
86
|
+
platformFitApplied, changePhysicsOptions, runAutoPhysics, showPhysicsPreview, applyPhysicsPreview,
|
|
87
|
+
cancelPhysicsPreview, ikEditTool, setIkEditTool, showTrails, setShowTrails,
|
|
88
|
+
trailFalloffS, setTrailFalloffS, trailEdit, runTrailRegeneration, trailReadinessState,
|
|
89
|
+
rangePins, rangePinResiduals, rangePinSelection, rangePinPartPick, rangePinPreview,
|
|
90
|
+
sceneObjects, setRangePinSelection, setRangePinPartPick, previewRangePinDraft, applyRangePinDraft,
|
|
91
|
+
deleteRangePin, hasEnvSheet, environment, style, environmentImage,
|
|
92
|
+
inspectorDrop, cutoutInputRef, meshInputRef, importCutout,
|
|
93
|
+
importMesh, selectHierarchy, snapEnabled, setSnapEnabled,
|
|
94
|
+
attachTargetLabel, beginSceneTransaction, endSceneTransaction,
|
|
95
|
+
matteCanvasRef, matteStats, matteMode, setMatteMode, matteEditorRef,
|
|
96
|
+
matteTolerance, setMatteTolerance, matteBrush, setMatteBrush, matteShrink,
|
|
97
|
+
setMatteShrink, matteFeather, setMatteFeather, matteBusy,
|
|
98
|
+
autoColor, recentObjectColors, rememberSceneObjectColor, objectColorDraft, setObjectColorDraft,
|
|
99
|
+
posePhotoFile, posingIndex, posingChar, charA, allPoses,
|
|
100
|
+
studioPick, posingClosing, closeStudio, castDomain, setToast,
|
|
101
|
+
savePose, beginWorkspaceResize,
|
|
102
|
+
} = useStudioShell();
|
|
103
|
+
const selection = detailsSelection({ selectedHierarchyId, selectedSceneObject, rigSelection, characters, shot, inspectorHasContent });
|
|
104
|
+
return (
|
|
105
|
+
<aside className="panel hierarchy-sidebar inspector-sidebar" data-inspector={selectedHierarchyId}>
|
|
106
|
+
{/* Save failures live above the tab content, not inside the Props
|
|
107
|
+
card: that card is hidden whenever any hierarchy node is
|
|
108
|
+
selected, and saves fire exactly while objects are being
|
|
109
|
+
edited — the one case where a failure line inside it is
|
|
110
|
+
invisible. As a sibling of the tab panes this line stays
|
|
111
|
+
on screen for every selection and every tab until the
|
|
112
|
+
next successful write clears it (plan §8.4); the one-shot
|
|
113
|
+
toast still announces each failure episode. */}
|
|
114
|
+
{sceneSaveError && (
|
|
115
|
+
<p className="scene-save-error" role="status">
|
|
116
|
+
{sceneSaveError}
|
|
117
|
+
</p>
|
|
118
|
+
)}
|
|
119
|
+
{studioAgentError && <p className="scene-save-error" role="alert">{studioAgentError}</p>}
|
|
120
|
+
{/* #570: the Agent no longer replaces Details. It docks under it in
|
|
121
|
+
the right column, and Window › Agent folds it away again. */}
|
|
122
|
+
<section className="inspector-pane">
|
|
123
|
+
<div className="inspector-heading details-panel-head">
|
|
124
|
+
{/* #570: the selection is the panel's title; "Details" only
|
|
125
|
+
names an empty panel. */}
|
|
126
|
+
{selection ? (
|
|
127
|
+
<div className="details-selection" data-testid="details-selection">
|
|
128
|
+
<span className="details-selection-name inspector-heading-selection">{selection.name}</span>
|
|
129
|
+
{selection.type && <span className="details-selection-type">{selection.type}</span>}
|
|
130
|
+
</div>
|
|
131
|
+
) : <strong className="details-panel-title">{ko("Details", "세부 정보")}</strong>}
|
|
132
|
+
{selectedSceneObject && (
|
|
133
|
+
<div className="inspector-actions-wrap">
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
className="inspector-actions-trigger"
|
|
137
|
+
aria-label={ko("Object actions", "오브젝트 작업")}
|
|
138
|
+
aria-expanded={inspectorActionsOpen}
|
|
139
|
+
onClick={() => setInspectorActionsOpen((open) => !open)}
|
|
140
|
+
>
|
|
141
|
+
⋮
|
|
142
|
+
</button>
|
|
143
|
+
{inspectorActionsOpen && (
|
|
144
|
+
<div className="inspector-actions-menu" role="menu">
|
|
145
|
+
<button type="button" role="menuitem" onClick={() => { runStudioAction("object.duplicate"); setInspectorActionsOpen(false); }}>
|
|
146
|
+
{ko("Duplicate", "복제")}
|
|
147
|
+
</button>
|
|
148
|
+
<button type="button" role="menuitem" onClick={() => { deleteSelectedSceneObject(); setInspectorActionsOpen(false); }}>
|
|
149
|
+
{ko("Delete", "삭제")}
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
<div className="inspector-scroll">
|
|
157
|
+
{/* Nothing is selected that owns settings — say so rather than
|
|
158
|
+
showing an empty column the user has to interpret. */}
|
|
159
|
+
{!inspectorHasContent && (
|
|
160
|
+
<p className="inspector-empty" data-inspector-empty role="status">
|
|
161
|
+
{ko(
|
|
162
|
+
"Select something in the hierarchy — the scene, the camera, a character, the environment or a prop — and its settings appear here.",
|
|
163
|
+
"계층에서 항목을 고르면 — 씨, 카메라, 캐릭터, 환경, 소품 — 그 설정이 여기 나타납니다.",
|
|
164
|
+
)}
|
|
165
|
+
</p>
|
|
166
|
+
)}
|
|
167
|
+
{/* Shot TYPE presets live in the viewport toolbar dropdown — not
|
|
168
|
+
duplicated here. */}
|
|
169
|
+
|
|
170
|
+
{/* Camera animation is authored against the same playhead as motion,
|
|
171
|
+
so keep its controls beside the Motion tools as well as Shot setup. */}
|
|
172
|
+
<LightPanel keyLightSelected={keyLightSelected} keyLight={keyLight} />
|
|
173
|
+
{/* Lens, Recenter and Record used to live here as well as in the
|
|
174
|
+
viewport camera bar and the topbar Export menu. One home each
|
|
175
|
+
(#193, R1): framing is the bar's job, delivery is Export's, and
|
|
176
|
+
selecting the camera now switches to Camera mode so the bar's
|
|
177
|
+
controls are on screen when this panel opens. */}
|
|
178
|
+
<CameraPanel isCameraSelection={isCameraSelection} shot={shot} moveSequence={moveSequence} cameraKeys={cameraKeys} activeShot={activeShot} />
|
|
179
|
+
|
|
180
|
+
<SubjectsPanel
|
|
181
|
+
isCharacterSelection={isCharacterSelection}
|
|
182
|
+
showB={showB}
|
|
183
|
+
characters={characters}
|
|
184
|
+
openStudio={openStudio}
|
|
185
|
+
posing={posing}
|
|
186
|
+
/>
|
|
187
|
+
|
|
188
|
+
{/* Scene mode: the viewport gizmo and Move/Rotate/Scale are the primary
|
|
189
|
+
path, so the numeric form starts folded (R5). Motion mode hides
|
|
190
|
+
those tools, so the same foldout becomes the open Placement row —
|
|
191
|
+
where the body stands on stage, which is all Motion can restage.
|
|
192
|
+
Foldout reads defaultOpen once, so the key remounts it per mode. */}
|
|
193
|
+
<CharacterTransformPanel
|
|
194
|
+
workflowMode={workflowMode}
|
|
195
|
+
isCharacterSelection={isCharacterSelection}
|
|
196
|
+
activeChar={activeChar}
|
|
197
|
+
/>
|
|
198
|
+
|
|
199
|
+
{/* Rig and Pose are chosen once when a character is cast and then left
|
|
200
|
+
alone, so they open on demand — Subject and Prompt are the panels
|
|
201
|
+
you actually work in. */}
|
|
202
|
+
<RigPanel
|
|
203
|
+
isCharacterSelection={isCharacterSelection}
|
|
204
|
+
activeChar={activeChar}
|
|
205
|
+
/>
|
|
206
|
+
|
|
207
|
+
<PosePanel
|
|
208
|
+
isCharacterSelection={isCharacterSelection}
|
|
209
|
+
activeCharIndex={activeCharIndex}
|
|
210
|
+
i2vMotionModel={i2vMotionModel}
|
|
211
|
+
i2vMotionActions={i2vMotionActions}
|
|
212
|
+
setI2vMotionStudioOpen={setI2vMotionStudioOpen}
|
|
213
|
+
selectablePoses={selectablePoses}
|
|
214
|
+
activeChar={activeChar}
|
|
215
|
+
ikMode={ikMode}
|
|
216
|
+
ikApplyPoseAsKey={ikApplyPoseAsKey}
|
|
217
|
+
motion={motion}
|
|
218
|
+
setStudioPick={setStudioPick}
|
|
219
|
+
setToast={appContext.notify}
|
|
220
|
+
removePose={removePose}
|
|
221
|
+
setPhotoPoseError={setPhotoPoseError}
|
|
222
|
+
photoPoseFileRef={photoPoseFileRef}
|
|
223
|
+
photoPoseState={photoPoseState}
|
|
224
|
+
photoPoseError={photoPoseError}
|
|
225
|
+
activeRig={activeRig}
|
|
226
|
+
saveCurrentPose={saveCurrentPose}
|
|
227
|
+
/>
|
|
228
|
+
|
|
229
|
+
<VideoCapturePanel
|
|
230
|
+
isCharacterSelection={isCharacterSelection}
|
|
231
|
+
multiModelStatus={multiModelStatus}
|
|
232
|
+
multiModelStage={multiModelStage}
|
|
233
|
+
multiModelFileRef={multiModelFileRef}
|
|
234
|
+
chooseMultiModelFile={chooseMultiModelFile}
|
|
235
|
+
multiModelSource={multiModelSource}
|
|
236
|
+
multiModelUrl={multiModelUrl}
|
|
237
|
+
setMultiModelUrl={setMultiModelUrl}
|
|
238
|
+
useMultiModelUrl={useMultiModelUrl}
|
|
239
|
+
pasteMultiModelUrl={pasteMultiModelUrl}
|
|
240
|
+
multiModelProgress={multiModelProgress}
|
|
241
|
+
multiModelError={multiModelError}
|
|
242
|
+
multiModelFootage={multiModelFootage}
|
|
243
|
+
extractMultiModelMotion={extractMultiModelMotion}
|
|
244
|
+
multiModelExtract={multiModelExtract}
|
|
245
|
+
multiModelTake={multiModelTake}
|
|
246
|
+
multiModelExtractProgress={multiModelExtractProgress}
|
|
247
|
+
multiModelExtractError={multiModelExtractError}
|
|
248
|
+
activeChar={activeChar}
|
|
249
|
+
bridge={bridge}
|
|
250
|
+
/>
|
|
251
|
+
<PromptBlocksPanel
|
|
252
|
+
isCharacterSelection={isCharacterSelection}
|
|
253
|
+
promptBlocksReveal={promptBlocksReveal}
|
|
254
|
+
promptClips={promptClips}
|
|
255
|
+
selectedPromptId={selectedPromptId}
|
|
256
|
+
setSelectedPromptId={setSelectedPromptId}
|
|
257
|
+
setArdyPrompt={setArdyPrompt}
|
|
258
|
+
setTlFrame={setTlFrame}
|
|
259
|
+
tlFrameCount={tlFrameCount}
|
|
260
|
+
ardySeed={ardySeed}
|
|
261
|
+
changeArdySeed={changeArdySeed}
|
|
262
|
+
motion={motion}
|
|
263
|
+
lineEditMode={lineEditMode}
|
|
264
|
+
toggleLineEditMode={toggleLineEditMode}
|
|
265
|
+
linePreviewUrl={linePreviewUrl}
|
|
266
|
+
lineCurve={lineCurve}
|
|
267
|
+
lineDrifted={lineDrifted}
|
|
268
|
+
lineTrack={lineTrack}
|
|
269
|
+
setLineTrack={setLineTrack}
|
|
270
|
+
linePinMode={linePinMode}
|
|
271
|
+
setLinePinMode={setLinePinMode}
|
|
272
|
+
linePins={linePins}
|
|
273
|
+
lineClipFrames={lineClipFrames}
|
|
274
|
+
lineEditRange={lineEditRange}
|
|
275
|
+
setLineRange={setLineRange}
|
|
276
|
+
lineRadius={lineRadius}
|
|
277
|
+
changeLineRadius={changeLineRadius}
|
|
278
|
+
lineCurveDirty={lineCurveDirty}
|
|
279
|
+
lineEditFrom={lineEditFrom}
|
|
280
|
+
lineEditTo={lineEditTo}
|
|
281
|
+
lineCurvePointCount={lineCurvePointCount}
|
|
282
|
+
lineDriftHint={lineDriftHint}
|
|
283
|
+
lineCurveHidden={lineCurveHidden}
|
|
284
|
+
linePreviewBusy={linePreviewBusy}
|
|
285
|
+
linePreviewMs={linePreviewMs}
|
|
286
|
+
linePreviewError={linePreviewError}
|
|
287
|
+
generationBusy={generationBusy}
|
|
288
|
+
bridgeChecking={bridgeChecking}
|
|
289
|
+
bridge={bridge}
|
|
290
|
+
lineReadinessState={lineReadinessState}
|
|
291
|
+
runLineEdit={runLineEdit}
|
|
292
|
+
openMotionSetup={openMotionSetup}
|
|
293
|
+
recheckMotionHealth={recheckMotionHealth}
|
|
294
|
+
resetLineCurve={resetLineCurve}
|
|
295
|
+
exitLineEditMode={exitLineEditMode}
|
|
296
|
+
readinessState={readinessState}
|
|
297
|
+
ardyRunning={ardyRunning}
|
|
298
|
+
cancelArdy={cancelArdy}
|
|
299
|
+
ardyStatus={ardyStatus}
|
|
300
|
+
ardyOutcome={ardyOutcome}
|
|
301
|
+
/>
|
|
302
|
+
|
|
303
|
+
<RigControlPanel
|
|
304
|
+
isRigSelection={isRigSelection}
|
|
305
|
+
rigSelection={rigSelection}
|
|
306
|
+
ikChains={ikChains}
|
|
307
|
+
ikFocus={ikFocus}
|
|
308
|
+
footSnap={footSnap}
|
|
309
|
+
ikMode={ikMode}
|
|
310
|
+
collisionCleanupSupported={collisionCleanupSupported}
|
|
311
|
+
motion={motion}
|
|
312
|
+
autoPhysicsRunning={autoPhysicsRunning}
|
|
313
|
+
physicsProgress={physicsProgress}
|
|
314
|
+
physicsPreview={physicsPreview}
|
|
315
|
+
physicsShow={physicsShow}
|
|
316
|
+
physicsOptions={physicsOptions}
|
|
317
|
+
platformFitRunning={platformFitRunning}
|
|
318
|
+
platformFitProgress={platformFitProgress}
|
|
319
|
+
platformFitLast={platformFitLast}
|
|
320
|
+
platformFitApplied={platformFitApplied}
|
|
321
|
+
tlFrame={tlFrame}
|
|
322
|
+
changePhysicsOptions={changePhysicsOptions}
|
|
323
|
+
runAutoPhysics={runAutoPhysics}
|
|
324
|
+
showPhysicsPreview={showPhysicsPreview}
|
|
325
|
+
applyPhysicsPreview={applyPhysicsPreview}
|
|
326
|
+
cancelPhysicsPreview={cancelPhysicsPreview}
|
|
327
|
+
setTlFrame={setTlFrame}
|
|
328
|
+
ikEditTool={ikEditTool}
|
|
329
|
+
setIkEditTool={setIkEditTool}
|
|
330
|
+
showTrails={showTrails}
|
|
331
|
+
setShowTrails={setShowTrails}
|
|
332
|
+
trailFalloffS={trailFalloffS}
|
|
333
|
+
setTrailFalloffS={setTrailFalloffS}
|
|
334
|
+
trailEdit={trailEdit}
|
|
335
|
+
generationBusy={generationBusy}
|
|
336
|
+
bridgeChecking={bridgeChecking}
|
|
337
|
+
bridge={bridge}
|
|
338
|
+
runTrailRegeneration={runTrailRegeneration}
|
|
339
|
+
trailReadinessState={trailReadinessState}
|
|
340
|
+
openMotionSetup={openMotionSetup}
|
|
341
|
+
recheckMotionHealth={recheckMotionHealth}
|
|
342
|
+
rangePins={rangePins}
|
|
343
|
+
rangePinResiduals={rangePinResiduals}
|
|
344
|
+
rangePinSelection={rangePinSelection}
|
|
345
|
+
rangePinPartPick={rangePinPartPick}
|
|
346
|
+
rangePinPreview={rangePinPreview}
|
|
347
|
+
objects={sceneObjects}
|
|
348
|
+
setRangePinSelection={setRangePinSelection}
|
|
349
|
+
setRangePinPartPick={setRangePinPartPick}
|
|
350
|
+
previewRangePinDraft={previewRangePinDraft}
|
|
351
|
+
applyRangePinDraft={applyRangePinDraft}
|
|
352
|
+
deleteRangePin={deleteRangePin}
|
|
353
|
+
/>
|
|
354
|
+
|
|
355
|
+
<EnvironmentPanel
|
|
356
|
+
selectedHierarchyId={selectedHierarchyId}
|
|
357
|
+
hasEnvSheet={hasEnvSheet}
|
|
358
|
+
environment={environment}
|
|
359
|
+
style={style}
|
|
360
|
+
environmentImage={environmentImage}
|
|
361
|
+
setToast={appContext.notify}
|
|
362
|
+
/>
|
|
363
|
+
|
|
364
|
+
<PropsPanel
|
|
365
|
+
selectedHierarchyId={selectedHierarchyId}
|
|
366
|
+
inspectorDrop={inspectorDrop}
|
|
367
|
+
cutoutInputRef={cutoutInputRef}
|
|
368
|
+
meshInputRef={meshInputRef}
|
|
369
|
+
importCutout={importCutout}
|
|
370
|
+
importMesh={importMesh}
|
|
371
|
+
sceneObjects={sceneObjects}
|
|
372
|
+
selectHierarchy={selectHierarchy}
|
|
373
|
+
/>
|
|
374
|
+
|
|
375
|
+
<ObjectTransformPanel
|
|
376
|
+
selectedSceneObject={selectedSceneObject}
|
|
377
|
+
snapEnabled={snapEnabled}
|
|
378
|
+
setSnapEnabled={setSnapEnabled}
|
|
379
|
+
attachTargetLabel={attachTargetLabel}
|
|
380
|
+
sceneObjects={sceneObjects}
|
|
381
|
+
beginSceneTransaction={beginSceneTransaction}
|
|
382
|
+
endSceneTransaction={endSceneTransaction}
|
|
383
|
+
matteCanvasRef={matteCanvasRef}
|
|
384
|
+
matteStats={matteStats}
|
|
385
|
+
matteMode={matteMode}
|
|
386
|
+
setMatteMode={setMatteMode}
|
|
387
|
+
matteEditorRef={matteEditorRef}
|
|
388
|
+
matteTolerance={matteTolerance}
|
|
389
|
+
setMatteTolerance={setMatteTolerance}
|
|
390
|
+
matteBrush={matteBrush}
|
|
391
|
+
setMatteBrush={setMatteBrush}
|
|
392
|
+
matteShrink={matteShrink}
|
|
393
|
+
setMatteShrink={setMatteShrink}
|
|
394
|
+
matteFeather={matteFeather}
|
|
395
|
+
setMatteFeather={setMatteFeather}
|
|
396
|
+
matteBusy={matteBusy}
|
|
397
|
+
autoColor={autoColor}
|
|
398
|
+
recentObjectColors={recentObjectColors}
|
|
399
|
+
rememberSceneObjectColor={rememberSceneObjectColor}
|
|
400
|
+
objectColorDraft={objectColorDraft}
|
|
401
|
+
setObjectColorDraft={setObjectColorDraft}
|
|
402
|
+
/>
|
|
403
|
+
</div>
|
|
404
|
+
{selectedSceneObject && (
|
|
405
|
+
<div className="inspector-footer">
|
|
406
|
+
<span>{ko("Delete or Backspace to remove", "Delete 또는 Backspace로 삭제")}</span>
|
|
407
|
+
</div>
|
|
408
|
+
)}
|
|
409
|
+
</section>
|
|
410
|
+
{/* #570: the Agent always sits at the foot of the right column.
|
|
411
|
+
Collapsed it is one 44px bar; Window › Agent, Cmd/Ctrl+B and
|
|
412
|
+
the bar's own chevron fold it open and shut. */}
|
|
413
|
+
{!embedMode && !studioAgentMode && (
|
|
414
|
+
<button
|
|
415
|
+
type="button"
|
|
416
|
+
className="studio-agent-bar"
|
|
417
|
+
data-testid="studio-agent-bar"
|
|
418
|
+
aria-expanded="false"
|
|
419
|
+
title={ko("Open Agent (Cmd/Ctrl+B)", "에이전트 열기 (Cmd/Ctrl+B)")}
|
|
420
|
+
onClick={() => setStudioAgentMode(true)}
|
|
421
|
+
>
|
|
422
|
+
<svg className="studio-agent-spark" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 2.25c.4 2.9 1.85 4.35 4.75 4.75-2.9.4-4.35 1.85-4.75 4.75-.4-2.9-1.85-4.35-4.75-4.75 2.9-.4 4.35-1.85 4.75-4.75z M12.75 11.5v2.5 M11.5 12.75H14" /></svg>
|
|
423
|
+
<span className="studio-agent-bar-title">{ko("Agent", "에이전트")}</span>
|
|
424
|
+
<span className="studio-agent-bar-hint">{ko("Ask the agent…", "에이전트에게 요청하기…")}</span>
|
|
425
|
+
<svg viewBox="0 0 10 10" aria-hidden="true"><path d="m2.5 6 2.5-2.5L7.5 6" /></svg>
|
|
426
|
+
</button>
|
|
427
|
+
)}
|
|
428
|
+
{!embedMode && <div className="studio-agent-inspector" hidden={!studioAgentMode}>
|
|
429
|
+
<div
|
|
430
|
+
className="workspace-splitter shell-splitter shell-agent-splitter"
|
|
431
|
+
role="separator"
|
|
432
|
+
aria-orientation="horizontal"
|
|
433
|
+
aria-label={ko("Resize Details and Agent", "세부 정보와 에이전트 크기 조절")}
|
|
434
|
+
onPointerDown={(event) => beginWorkspaceResize("agent", event)}
|
|
435
|
+
/>
|
|
436
|
+
<button
|
|
437
|
+
type="button"
|
|
438
|
+
className="studio-agent-collapse"
|
|
439
|
+
aria-expanded="true"
|
|
440
|
+
aria-label={ko("Collapse Agent", "에이전트 접기")}
|
|
441
|
+
title={ko("Collapse Agent (Cmd/Ctrl+B)", "에이전트 접기 (Cmd/Ctrl+B)")}
|
|
442
|
+
onClick={() => setStudioAgentMode(false)}
|
|
443
|
+
>
|
|
444
|
+
<svg viewBox="0 0 10 10" aria-hidden="true"><path d="m2.5 4 2.5 2.5L7.5 4" /></svg>
|
|
445
|
+
</button>
|
|
446
|
+
<AgentPanel embedded hidden={!studioAgentMode} surface="studio" defaultCollapsed onCollapsedChange={setAgentCollapsed}
|
|
447
|
+
sceneName={scenes.find((entry) => entry.id === activeSceneId)?.name ?? ko("Untitled Scene", "제목 없는 씬")}
|
|
448
|
+
buildContext={buildStudioAgentContext} onReceipt={highlightAgentTargets}
|
|
449
|
+
onI2vAction={(instruction) => void generateI2vMotionFromUi(instruction)} />
|
|
450
|
+
</div>}
|
|
451
|
+
{/* The reference-photo picker sits outside the panel so re-mounting
|
|
452
|
+
the studio cannot cancel an in-flight read. */}
|
|
453
|
+
<input
|
|
454
|
+
ref={photoPoseFileRef}
|
|
455
|
+
className="multimodel-file-input"
|
|
456
|
+
type="file"
|
|
457
|
+
accept={ASSET_IMAGE_TYPES.join(",")}
|
|
458
|
+
data-pose-photo-input
|
|
459
|
+
onChange={(event) => {
|
|
460
|
+
const file = event.target.files?.[0];
|
|
461
|
+
event.target.value = ""; // the same photo must be re-pickable after an error
|
|
462
|
+
if (file) posePhotoFile(file);
|
|
463
|
+
}}
|
|
464
|
+
/>
|
|
465
|
+
{/* Pose Studio docks under the inspector instead of floating over
|
|
466
|
+
the shot: the viewport keeps the posed character unobstructed. */}
|
|
467
|
+
{posing && (
|
|
468
|
+
<PoseStudioPanel
|
|
469
|
+
docked
|
|
470
|
+
subject={posingIndex >= 0 ? posingIndex + 1 : 1}
|
|
471
|
+
model={posingChar?.model ?? charA.model}
|
|
472
|
+
poses={allPoses}
|
|
473
|
+
selectedId={studioPick}
|
|
474
|
+
closing={posingClosing}
|
|
475
|
+
motionActive={Boolean(motion)}
|
|
476
|
+
ikCorrection={ikMode && Boolean(motion) && posingChar?.id === activeChar.id}
|
|
477
|
+
onSelect={setStudioPick}
|
|
478
|
+
onApply={(selectedPoseId) => {
|
|
479
|
+
const pose = selectablePoses.find((p) => p.id === selectedPoseId);
|
|
480
|
+
if (pose) {
|
|
481
|
+
// IK mode over a take, on the active character: key the
|
|
482
|
+
// pose as a correction instead of erasing the motion.
|
|
483
|
+
if (ikMode && posingChar?.id === activeChar.id && ikApplyPoseAsKey(pose)) {
|
|
484
|
+
closeStudio();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const hadMotion = Boolean(motion);
|
|
488
|
+
if (posingChar) castDomain.run('character.setPose', { characterId: posingChar.id, pose: pose.id, clearMotion: hadMotion });
|
|
489
|
+
closeStudio();
|
|
490
|
+
setToast(hadMotion ? ko("Cleared the current motion and applied the pose", "현재 모션을 지우고 포즈를 적용했어요") : ko("Pose applied", "포즈를 적용했어요"));
|
|
491
|
+
} else {
|
|
492
|
+
setToast(ko("Couldn't find the selected pose — pick again", "선택한 포즈를 찾지 못했어요. 다시 골라 주세요"));
|
|
493
|
+
}
|
|
494
|
+
}}
|
|
495
|
+
onReset={() => {
|
|
496
|
+
if (posingChar) castDomain.run('character.setPose', { characterId: posingChar.id, pose: DEFAULT_POSE.id, clearMotion: Boolean(motion) });
|
|
497
|
+
setStudioPick(DEFAULT_POSE.id);
|
|
498
|
+
setToast(ko("Back to the default pose", "기본 포즈로 돌아왔어요"));
|
|
499
|
+
}}
|
|
500
|
+
onSave={savePose}
|
|
501
|
+
onPhoto={() => {
|
|
502
|
+
setPhotoPoseError("");
|
|
503
|
+
photoPoseFileRef.current?.click();
|
|
504
|
+
}}
|
|
505
|
+
photoState={photoPoseState}
|
|
506
|
+
photoError={photoPoseError}
|
|
507
|
+
onDelete={removePose}
|
|
508
|
+
onClose={closeStudio}
|
|
509
|
+
/>
|
|
510
|
+
)}
|
|
511
|
+
</aside>
|
|
512
|
+
);
|
|
513
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useStudioShell } from "./studio-shell-context.js";
|
|
3
|
+
import { ko } from "../locale.js";
|
|
4
|
+
import AssetPane from "../asset-pane.jsx";
|
|
5
|
+
|
|
6
|
+
const CONTENT_COLLAPSED_KEY = "cozyclay.dock.content-collapsed.v1";
|
|
7
|
+
|
|
8
|
+
function readContentCollapsed() {
|
|
9
|
+
try {
|
|
10
|
+
return globalThis.localStorage?.getItem(CONTENT_COLLAPSED_KEY) === "1";
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeContentCollapsed(value) {
|
|
17
|
+
try {
|
|
18
|
+
globalThis.localStorage?.setItem(CONTENT_COLLAPSED_KEY, value ? "1" : "0");
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.warn(`[cozyclay] could not store ${CONTENT_COLLAPSED_KEY}`, error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// #570: the object library (Content | Log) is the dock's Assets tab. It stays
|
|
25
|
+
// mounted while the Animation tab is up, hidden.
|
|
26
|
+
export default function LibrarySlot({ active = true }) {
|
|
27
|
+
const {
|
|
28
|
+
setBottomTab, beginAssetDrag, shelfImageIds, shelfMeshIds,
|
|
29
|
+
manageAssetStorage, setManageAssetStorage, unusedAssetIds, usedAssetIds, usageCounts,
|
|
30
|
+
projectAssetGraphSignature, assetTrash, deleteUnusedAsset, undoDeletedAsset, deletingAssetId,
|
|
31
|
+
projectManifest, takeVersions, loadTakeVersion, shots, selectTimelineShot, allPoses,
|
|
32
|
+
spawnCharacter, addSceneObject, runStudioAction,
|
|
33
|
+
} = useStudioShell();
|
|
34
|
+
const [collapsed, setCollapsed] = useState(readContentCollapsed);
|
|
35
|
+
const [shelfShown, setShelfShown] = useState(false);
|
|
36
|
+
|
|
37
|
+
// App scans imported assets only while they are on screen.
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const visible = active && shelfShown;
|
|
40
|
+
setBottomTab(visible ? "assets" : "timeline");
|
|
41
|
+
}, [active, shelfShown, setBottomTab]);
|
|
42
|
+
|
|
43
|
+
// Double-click in Content: the same payloads the drag carries, placed at
|
|
44
|
+
// the origin through the named domain actions.
|
|
45
|
+
function placeAssetAtOrigin(payload) {
|
|
46
|
+
const origin = { x: 0, z: 0 };
|
|
47
|
+
if (payload.kind === "character") spawnCharacter(payload.id, origin.x, origin.z);
|
|
48
|
+
else if (payload.kind === "object") addSceneObject(payload.objectKind, origin);
|
|
49
|
+
else if (payload.kind === "image" || payload.kind === "mesh") {
|
|
50
|
+
runStudioAction("asset.import", { assetId: payload.assetId, placeAs: payload.kind === "mesh" ? "mesh" : "cutout", placement: origin });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<section className="assets-pane studio-library" aria-label={ko("Library", "라이브러리")} data-content-collapsed={collapsed || undefined} hidden={!active}>
|
|
56
|
+
<AssetPane
|
|
57
|
+
onAssetGrab={beginAssetDrag}
|
|
58
|
+
onAssetPlace={placeAssetAtOrigin}
|
|
59
|
+
imageAssetIds={shelfImageIds}
|
|
60
|
+
meshAssetIds={shelfMeshIds}
|
|
61
|
+
manageStorage={manageAssetStorage}
|
|
62
|
+
onManageStorageToggle={() => setManageAssetStorage((current) => !current)}
|
|
63
|
+
unusedAssetIds={unusedAssetIds}
|
|
64
|
+
usedAssetIds={usedAssetIds}
|
|
65
|
+
usageCounts={usageCounts}
|
|
66
|
+
graphSignature={projectAssetGraphSignature}
|
|
67
|
+
trashCount={assetTrash.length}
|
|
68
|
+
onDeleteUnusedAsset={deleteUnusedAsset}
|
|
69
|
+
onUndoDelete={undoDeletedAsset}
|
|
70
|
+
deletingAssetId={deletingAssetId}
|
|
71
|
+
resourceManifest={projectManifest}
|
|
72
|
+
shots={shots}
|
|
73
|
+
takeVersions={takeVersions}
|
|
74
|
+
poses={allPoses}
|
|
75
|
+
onShotOpen={selectTimelineShot}
|
|
76
|
+
onTakeOpen={loadTakeVersion}
|
|
77
|
+
collapsed={collapsed}
|
|
78
|
+
onCollapsedChange={(next) => {
|
|
79
|
+
setCollapsed(next);
|
|
80
|
+
writeContentCollapsed(next);
|
|
81
|
+
}}
|
|
82
|
+
onShelfVisibleChange={setShelfShown}
|
|
83
|
+
/>
|
|
84
|
+
</section>
|
|
85
|
+
);
|
|
86
|
+
}
|