@dailies-studio/shot-editor 0.1.0 → 0.2.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/dist/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- package/src/stage/MoveGizmo.tsx +0 -411
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
// One node's value, drawn as a thumbnail — the stage half of the material
|
|
2
|
+
// workspace's node previews (bridgeTypes: `requestNodePreviews`).
|
|
3
|
+
//
|
|
4
|
+
// ── WHY IT IS STRUCTURALLY UNREACHABLE FROM AN EXPORT ────────────────────
|
|
5
|
+
//
|
|
6
|
+
// `StageApp` mounts this only while `exporting === null` and the material
|
|
7
|
+
// workspace names a preview target, so in export framing it is not a component
|
|
8
|
+
// that declines to run — it is a component that does not exist. The CLI render
|
|
9
|
+
// page never mounts `StageApp` at all, so the whole file is unreachable there.
|
|
10
|
+
// `bridgeClient.requestNodePreviews` refuses on the same two conditions, which
|
|
11
|
+
// is a belt over that structure rather than the only guard.
|
|
12
|
+
//
|
|
13
|
+
// ── A QUAD, NOT THE SPECIMEN MESH ────────────────────────────────────────
|
|
14
|
+
//
|
|
15
|
+
// A thumbnail is a VALUE MAP, not shading. The quad shows the whole UV domain
|
|
16
|
+
// identically for every mesh bound to the graph, which buys three things: the
|
|
17
|
+
// picture answers "what is in this branch" rather than "what does it look like
|
|
18
|
+
// on this model", the rig cache keys on the GRAPH alone, and a 400k-triangle
|
|
19
|
+
// GLB is never drawn 64 times for a wall of 96px pictures. `createDesignPlaneGeometry`
|
|
20
|
+
// is the same quad the plane primitive uses, so uv (0,0) is the top-left corner
|
|
21
|
+
// in both.
|
|
22
|
+
//
|
|
23
|
+
// ── ONE ATLAS, ONE READBACK ──────────────────────────────────────────────
|
|
24
|
+
//
|
|
25
|
+
// Every cell is rendered into ONE render target under a scissor, and the whole
|
|
26
|
+
// target comes back in a single `readRenderTargetPixelsAsync`. That call is a
|
|
27
|
+
// GPU stall — the fence has to settle before the buffer is readable — so the
|
|
28
|
+
// cost of a batch is one stall regardless of how many nodes it holds. Sixty-four
|
|
29
|
+
// separate readbacks would be sixty-four stalls on the canvas the author is
|
|
30
|
+
// still dragging in.
|
|
31
|
+
//
|
|
32
|
+
// ── CELL 0 IS AN ORIENTATION PROBE ───────────────────────────────────────
|
|
33
|
+
//
|
|
34
|
+
// The two backends hand back rows in opposite orders. WebGL reads through
|
|
35
|
+
// `gl.readPixels`, whose origin is bottom-left, so row 0 is the BOTTOM of the
|
|
36
|
+
// target; WebGPU's `copyTextureToBuffer` copies a top-left-origin texture, so
|
|
37
|
+
// row 0 is the TOP. Nothing in the renderer normalises that, and a wrong guess
|
|
38
|
+
// is not a crash — it is every thumbnail silently upside down, on whichever
|
|
39
|
+
// backend nobody happened to test.
|
|
40
|
+
//
|
|
41
|
+
// So the flip is MEASURED, never assumed: cell 0 paints `uv().y` as grey, and
|
|
42
|
+
// the readback is asked which way that ramp runs. Not future-proofing: the
|
|
43
|
+
// preview stage takes the WebGPU-first default and lands on whichever backend
|
|
44
|
+
// the host browser has (packages/shot/src/stageBackend.ts), so both row
|
|
45
|
+
// orders are live today.
|
|
46
|
+
//
|
|
47
|
+
// ── NOT A `useFrame` ─────────────────────────────────────────────────────
|
|
48
|
+
//
|
|
49
|
+
// Work happens inside a request and nowhere else, so playback and scrubbing
|
|
50
|
+
// cost this file exactly nothing. The renderer's viewport is saved and restored
|
|
51
|
+
// around the batch, and the scissor test is turned back off, because a batch
|
|
52
|
+
// that walked away with a 96px viewport would draw the next frame into one
|
|
53
|
+
// corner of the canvas.
|
|
54
|
+
import { Suspense, useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
55
|
+
import type { ReactElement } from "react";
|
|
56
|
+
import { useLoader, useThree } from "@react-three/fiber";
|
|
57
|
+
import { Color, FrontSide, Mesh, OrthographicCamera, RenderTarget, Scene, Vector4 } from "three";
|
|
58
|
+
import type { Material } from "three";
|
|
59
|
+
import { MeshBasicNodeMaterial } from "three/webgpu";
|
|
60
|
+
import { uv, vec3, vec4 } from "three/tsl";
|
|
61
|
+
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
62
|
+
import {
|
|
63
|
+
compileMaterialGraphNodePreview,
|
|
64
|
+
createDesignPlaneGeometry,
|
|
65
|
+
materialGraphSourceOf,
|
|
66
|
+
primitiveMaterialGraphSource,
|
|
67
|
+
useShotTextures,
|
|
68
|
+
} from "@dailies-studio/shot";
|
|
69
|
+
import type {
|
|
70
|
+
CompiledMaterialGraphPreview,
|
|
71
|
+
MaterialGraphFrame,
|
|
72
|
+
MaterialGraphSourceChannels,
|
|
73
|
+
} from "@dailies-studio/shot";
|
|
74
|
+
import {
|
|
75
|
+
DEFAULT_PRIMITIVE_COLOR,
|
|
76
|
+
isGraphMaterial,
|
|
77
|
+
materialBindingForSlot,
|
|
78
|
+
materialGraphNodeSockets,
|
|
79
|
+
materialGraphSemanticSignature,
|
|
80
|
+
} from "@dailies-studio/shot/doc";
|
|
81
|
+
import type {
|
|
82
|
+
Doc3D,
|
|
83
|
+
Layer3D,
|
|
84
|
+
MaterialGraph3D,
|
|
85
|
+
MaterialGraphBinding,
|
|
86
|
+
} from "@dailies-studio/shot/doc";
|
|
87
|
+
import { useStageAssetResolver } from "@dailies-studio/runtime";
|
|
88
|
+
import type {
|
|
89
|
+
MaterialPreviewTarget,
|
|
90
|
+
NodePreviewBatch,
|
|
91
|
+
NodePreviewRequest,
|
|
92
|
+
NodePreviewResult,
|
|
93
|
+
} from "../bridgeTypes.js";
|
|
94
|
+
import type { StageClient } from "./bridgeClient.js";
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The renderer surface this file uses, as a STRUCTURAL type.
|
|
98
|
+
*
|
|
99
|
+
* Named rather than imported so the list of calls a node preview makes is
|
|
100
|
+
* readable in one place, and so a test can hand in a plain object without
|
|
101
|
+
* standing up a GL context. `useThree` types `gl` as the WebGL renderer while
|
|
102
|
+
* the shot stage runs the node renderer, so a cast happens either way.
|
|
103
|
+
*/
|
|
104
|
+
interface PreviewRenderer {
|
|
105
|
+
render(scene: Scene, camera: OrthographicCamera): void;
|
|
106
|
+
setRenderTarget(target: RenderTarget | null): void;
|
|
107
|
+
setViewport(x: number, y: number, width: number, height: number): void;
|
|
108
|
+
setScissor(x: number, y: number, width: number, height: number): void;
|
|
109
|
+
setScissorTest(enabled: boolean): void;
|
|
110
|
+
getViewport(target: Vector4): Vector4;
|
|
111
|
+
readRenderTargetPixelsAsync(
|
|
112
|
+
target: RenderTarget,
|
|
113
|
+
x: number,
|
|
114
|
+
y: number,
|
|
115
|
+
width: number,
|
|
116
|
+
height: number,
|
|
117
|
+
): Promise<ArrayBufferView>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Everything a batch reads, sampled when the batch actually starts rather
|
|
121
|
+
* than when it was requested — a parked request must draw the CURRENT
|
|
122
|
+
* document, not the one that was on screen when the chrome asked. */
|
|
123
|
+
interface PreviewContext {
|
|
124
|
+
doc: Doc3D;
|
|
125
|
+
layer: Layer3D | null;
|
|
126
|
+
/** The surface channels the graph's `source-*` nodes read, or null when the
|
|
127
|
+
* layer, the model or the slot could not be resolved. */
|
|
128
|
+
source: MaterialGraphSourceChannels | null;
|
|
129
|
+
textures: ReturnType<typeof useShotTextures>;
|
|
130
|
+
gl: PreviewRenderer;
|
|
131
|
+
/** Master frame, or def-local under a component context — whichever the
|
|
132
|
+
* stage's clock is on. Time-varying nodes read it. */
|
|
133
|
+
frame: number;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function NodePreviews({
|
|
137
|
+
client,
|
|
138
|
+
doc,
|
|
139
|
+
layers,
|
|
140
|
+
target,
|
|
141
|
+
}: {
|
|
142
|
+
client: StageClient;
|
|
143
|
+
doc: Doc3D;
|
|
144
|
+
/** The container the stage is drawing: the open definition under a component
|
|
145
|
+
* context, the playhead's scene otherwise. The same value `StageTools` gets,
|
|
146
|
+
* so the two can never disagree about which layer an id names. */
|
|
147
|
+
layers: { layersById: Readonly<Record<string, Layer3D>> } | null;
|
|
148
|
+
target: MaterialPreviewTarget;
|
|
149
|
+
}): ReactElement {
|
|
150
|
+
const layer = layers?.layersById[target.layerId] ?? null;
|
|
151
|
+
const mesh = layer?.kind === "mesh" ? layer.mesh : undefined;
|
|
152
|
+
const resolveAsset = useStageAssetResolver();
|
|
153
|
+
const assetPath = mesh?.asset === undefined ? undefined : doc.assets[mesh.asset]?.path;
|
|
154
|
+
const url = assetPath === undefined ? null : resolveAsset(assetPath);
|
|
155
|
+
// A primitive has no source `Material` object to capture, so its channels are
|
|
156
|
+
// synthesised from the one colour it carries — exactly what `mesh.tsx` does
|
|
157
|
+
// for the specimen, so the thumbnail's `source-color` and the specimen's are
|
|
158
|
+
// the same value.
|
|
159
|
+
const primitiveSource = useMemo(
|
|
160
|
+
() =>
|
|
161
|
+
mesh === undefined || mesh.asset !== undefined
|
|
162
|
+
? null
|
|
163
|
+
: primitiveMaterialGraphSource(
|
|
164
|
+
new Color().setStyle(mesh.color ?? DEFAULT_PRIMITIVE_COLOR),
|
|
165
|
+
FrontSide,
|
|
166
|
+
),
|
|
167
|
+
[mesh],
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (url !== null) {
|
|
171
|
+
return (
|
|
172
|
+
<Suspense fallback={null}>
|
|
173
|
+
<ModelPreviewSource
|
|
174
|
+
client={client}
|
|
175
|
+
doc={doc}
|
|
176
|
+
layer={layer}
|
|
177
|
+
url={url}
|
|
178
|
+
slot={target.slot}
|
|
179
|
+
/>
|
|
180
|
+
</Suspense>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return <PreviewRig client={client} doc={doc} layer={layer} source={primitiveSource} />;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* A model layer's source channels, taken off the loaded glTF.
|
|
188
|
+
*
|
|
189
|
+
* `useLoader` answers from fiber's GLOBAL url cache, which the specimen mesh
|
|
190
|
+
* has already filled — so this suspends for one commit at most on a model that
|
|
191
|
+
* is already on screen, and never re-downloads. While it is suspended nothing
|
|
192
|
+
* registers a preview source and the bridge answers null, which is the same
|
|
193
|
+
* "not ready yet" the chrome already handles.
|
|
194
|
+
*/
|
|
195
|
+
function ModelPreviewSource({
|
|
196
|
+
client,
|
|
197
|
+
doc,
|
|
198
|
+
layer,
|
|
199
|
+
url,
|
|
200
|
+
slot,
|
|
201
|
+
}: {
|
|
202
|
+
client: StageClient;
|
|
203
|
+
doc: Doc3D;
|
|
204
|
+
layer: Layer3D | null;
|
|
205
|
+
url: string;
|
|
206
|
+
slot: number | null;
|
|
207
|
+
}): ReactElement {
|
|
208
|
+
const gltf = useLoader(GLTFLoader, url);
|
|
209
|
+
const source = useMemo(() => modelSlotSource(gltf, slot), [gltf, slot]);
|
|
210
|
+
return <PreviewRig client={client} doc={doc} layer={layer} source={source} />;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The glTF material a slot names, as channels.
|
|
215
|
+
*
|
|
216
|
+
* `parser.associations` is the LOADER's own mapping and the only authority on
|
|
217
|
+
* which source material is index N — a glTF material's name is optional and not
|
|
218
|
+
* unique, so a name match would be the implicit binding this schema refused.
|
|
219
|
+
* The layer-wide binding (`slot: null`) takes the lowest index, because a
|
|
220
|
+
* whole-model preview has no one submesh to speak for.
|
|
221
|
+
*/
|
|
222
|
+
function modelSlotSource(gltf: unknown, slot: number | null): MaterialGraphSourceChannels | null {
|
|
223
|
+
const associations = (
|
|
224
|
+
gltf as { parser?: { associations?: Map<object, { materials?: number }> } }
|
|
225
|
+
).parser?.associations;
|
|
226
|
+
if (associations === undefined) return null;
|
|
227
|
+
let lowest: { index: number; material: Material } | null = null;
|
|
228
|
+
for (const [key, link] of associations) {
|
|
229
|
+
const index = link?.materials;
|
|
230
|
+
// Meshes and nodes ride the same map with their own keys, so "has a
|
|
231
|
+
// material index" is what identifies a material entry.
|
|
232
|
+
if (typeof index !== "number") continue;
|
|
233
|
+
const material = key as Material;
|
|
234
|
+
if (slot !== null) {
|
|
235
|
+
if (index === slot) return materialGraphSourceOf(material);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (lowest === null || index < lowest.index) lowest = { index, material };
|
|
239
|
+
}
|
|
240
|
+
return lowest === null ? null : materialGraphSourceOf(lowest.material);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** The registration and the atlas. Renders nothing: everything it owns lives in
|
|
244
|
+
* three objects the React tree has no opinion about. */
|
|
245
|
+
function PreviewRig({
|
|
246
|
+
client,
|
|
247
|
+
doc,
|
|
248
|
+
layer,
|
|
249
|
+
source,
|
|
250
|
+
}: {
|
|
251
|
+
client: StageClient;
|
|
252
|
+
doc: Doc3D;
|
|
253
|
+
layer: Layer3D | null;
|
|
254
|
+
source: MaterialGraphSourceChannels | null;
|
|
255
|
+
}): null {
|
|
256
|
+
const gl = useThree((state) => state.gl) as unknown as PreviewRenderer;
|
|
257
|
+
const textures = useShotTextures();
|
|
258
|
+
|
|
259
|
+
// Read through a REF so the registration below stays a mount-only effect.
|
|
260
|
+
// Re-registering on every commit would swap the source out from under a batch
|
|
261
|
+
// that is already awaiting its readback, and the stage commits on every seek.
|
|
262
|
+
const live = useRef<Omit<PreviewContext, "frame">>({ doc, layer, source, textures, gl });
|
|
263
|
+
live.current = { doc, layer, source, textures, gl };
|
|
264
|
+
|
|
265
|
+
const rig = useMemo(createNodePreviewRig, []);
|
|
266
|
+
useEffect(() => () => rig.dispose(), [rig]);
|
|
267
|
+
|
|
268
|
+
useLayoutEffect(() => {
|
|
269
|
+
// THE CLOCK, the playhead's one live answer: the stage's React state
|
|
270
|
+
// holds a frame only for an export's capture loop.
|
|
271
|
+
const context = (): PreviewContext => ({
|
|
272
|
+
...live.current,
|
|
273
|
+
frame: client.clock.get().frame,
|
|
274
|
+
});
|
|
275
|
+
client.setNodePreviewSource((request) => rig.run(request, context));
|
|
276
|
+
return () => client.setNodePreviewSource(null);
|
|
277
|
+
}, [client, rig]);
|
|
278
|
+
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
interface NodePreviewRig {
|
|
283
|
+
run(
|
|
284
|
+
request: NodePreviewRequest,
|
|
285
|
+
context: () => PreviewContext,
|
|
286
|
+
): Promise<NodePreviewBatch | null>;
|
|
287
|
+
dispose(): void;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** One compiled node head, or the sentence saying why there is none. */
|
|
291
|
+
type CellPlan =
|
|
292
|
+
| { nodeId: string; rig: CompiledMaterialGraphPreview; message?: undefined }
|
|
293
|
+
| { nodeId: string; rig?: undefined; message: string };
|
|
294
|
+
|
|
295
|
+
function createNodePreviewRig(): NodePreviewRig {
|
|
296
|
+
const scene = new Scene();
|
|
297
|
+
// The unit quad exactly fills this frustum, so a cell's picture IS the whole
|
|
298
|
+
// UV domain with no border and no letterboxing to reason about.
|
|
299
|
+
const camera = new OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0, 1);
|
|
300
|
+
const geometry = createDesignPlaneGeometry(1, 1);
|
|
301
|
+
const quad = new Mesh(geometry);
|
|
302
|
+
// Off the near plane. At z = 0 the quad sits exactly ON it, which some drivers
|
|
303
|
+
// clip and some do not — a black thumbnail on one machine and not the next.
|
|
304
|
+
quad.position.z = -0.5;
|
|
305
|
+
quad.frustumCulled = false;
|
|
306
|
+
scene.add(quad);
|
|
307
|
+
|
|
308
|
+
// The orientation probe: grey ramping with uv.y, which the design-space quad
|
|
309
|
+
// puts at 0 along the TOP edge. See the header.
|
|
310
|
+
const probe = new MeshBasicNodeMaterial();
|
|
311
|
+
probe.colorNode = vec4(vec3(uv().y), 1);
|
|
312
|
+
probe.toneMapped = false;
|
|
313
|
+
probe.depthTest = false;
|
|
314
|
+
probe.depthWrite = false;
|
|
315
|
+
|
|
316
|
+
const atlas = new RenderTarget(1, 1, { depthBuffer: false });
|
|
317
|
+
const savedViewport = new Vector4();
|
|
318
|
+
|
|
319
|
+
/** Compiled heads, keyed by node and socket, valid for ONE semantic
|
|
320
|
+
* signature. A literal drag or an override change moves a uniform, so it
|
|
321
|
+
* answers from here; a topology change mints a new signature and every head
|
|
322
|
+
* is disposed. */
|
|
323
|
+
const rigs = new Map<string, CompiledMaterialGraphPreview>();
|
|
324
|
+
let rigsFor: string | null = null;
|
|
325
|
+
|
|
326
|
+
/** Latest wins. At most one request is parked, and a batch whose successor
|
|
327
|
+
* already exists resolves null rather than painting an answer the chrome is
|
|
328
|
+
* about to replace. */
|
|
329
|
+
let running = false;
|
|
330
|
+
let parked: {
|
|
331
|
+
request: NodePreviewRequest;
|
|
332
|
+
context: () => PreviewContext;
|
|
333
|
+
resolve: (batch: NodePreviewBatch | null) => void;
|
|
334
|
+
} | null = null;
|
|
335
|
+
|
|
336
|
+
function disposeRigs(): void {
|
|
337
|
+
for (const head of rigs.values()) head.dispose();
|
|
338
|
+
rigs.clear();
|
|
339
|
+
rigsFor = null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function key(nodeId: string, socket: string): string {
|
|
343
|
+
return `${nodeId}::${socket}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function headFor(
|
|
347
|
+
graph: MaterialGraph3D,
|
|
348
|
+
binding: MaterialGraphBinding,
|
|
349
|
+
source: MaterialGraphSourceChannels,
|
|
350
|
+
lighting: Doc3D["materialLighting"],
|
|
351
|
+
nodeId: string,
|
|
352
|
+
): CellPlan {
|
|
353
|
+
const node = graph.nodesById[nodeId];
|
|
354
|
+
if (node === undefined || typeof node.kind !== "string") {
|
|
355
|
+
return { nodeId, message: `Node "${nodeId}" is not in this material graph.` };
|
|
356
|
+
}
|
|
357
|
+
if (node.scope !== undefined) {
|
|
358
|
+
return {
|
|
359
|
+
nodeId,
|
|
360
|
+
message: `Node "${nodeId}" sits inside a graph function, and a function body has no call-site arguments to bind, so it has no value to preview on its own.`,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const socket = materialGraphNodeSockets(graph, node).outputs[0];
|
|
364
|
+
if (socket === undefined) {
|
|
365
|
+
return { nodeId, message: `Node "${nodeId}" has no output socket to preview.` };
|
|
366
|
+
}
|
|
367
|
+
const cached = rigs.get(key(nodeId, socket.id));
|
|
368
|
+
if (cached !== undefined) return { nodeId, rig: cached };
|
|
369
|
+
try {
|
|
370
|
+
const head = compileMaterialGraphNodePreview({
|
|
371
|
+
graph,
|
|
372
|
+
binding,
|
|
373
|
+
source,
|
|
374
|
+
// No IBL: the preview head is an unlit `MeshBasicNodeMaterial` that
|
|
375
|
+
// paints the socket's value straight out, so an environment map would
|
|
376
|
+
// be built and bound for a material that cannot read it.
|
|
377
|
+
environment: null,
|
|
378
|
+
lighting,
|
|
379
|
+
nodeId,
|
|
380
|
+
socket: socket.id,
|
|
381
|
+
});
|
|
382
|
+
rigs.set(key(nodeId, socket.id), head);
|
|
383
|
+
return { nodeId, rig: head };
|
|
384
|
+
} catch (error) {
|
|
385
|
+
return { nodeId, message: messageOf(error) };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function draw(
|
|
390
|
+
request: NodePreviewRequest,
|
|
391
|
+
context: PreviewContext,
|
|
392
|
+
): Promise<NodePreviewBatch | null> {
|
|
393
|
+
const graph = context.doc.materials?.[request.materialId];
|
|
394
|
+
if (graph === undefined) return null;
|
|
395
|
+
const signature = materialGraphSemanticSignature(graph);
|
|
396
|
+
if (signature !== rigsFor) {
|
|
397
|
+
disposeRigs();
|
|
398
|
+
rigsFor = signature;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const size = request.size;
|
|
402
|
+
const refuse = (message: string): NodePreviewBatch => ({
|
|
403
|
+
materialId: request.materialId,
|
|
404
|
+
semanticSignature: signature,
|
|
405
|
+
size,
|
|
406
|
+
results: request.nodeIds.map((nodeId) => ({ nodeId, bitmap: null, message })),
|
|
407
|
+
});
|
|
408
|
+
const textures = context.textures;
|
|
409
|
+
if (textures === null) {
|
|
410
|
+
return refuse("The shot runtime's texture cache is not mounted, so this graph cannot be sampled.");
|
|
411
|
+
}
|
|
412
|
+
const source = context.source;
|
|
413
|
+
if (source === null) {
|
|
414
|
+
return refuse(
|
|
415
|
+
request.slot === null
|
|
416
|
+
? "This layer has no source material, so its node previews have no channels to sample."
|
|
417
|
+
: `This model has no material slot ${request.slot}, so its node previews have no channels to sample.`,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const binding = bindingFor(context.layer, request);
|
|
422
|
+
const plans = request.nodeIds.map((nodeId) =>
|
|
423
|
+
headFor(graph, binding, source, context.doc.materialLighting, nodeId),
|
|
424
|
+
);
|
|
425
|
+
// Nothing compiled: every answer is already a sentence, so the GPU is never
|
|
426
|
+
// touched and the batch costs no stall at all.
|
|
427
|
+
if (plans.every((plan) => plan.rig === undefined)) {
|
|
428
|
+
return {
|
|
429
|
+
materialId: request.materialId,
|
|
430
|
+
semanticSignature: signature,
|
|
431
|
+
size,
|
|
432
|
+
results: plans.flatMap((plan) =>
|
|
433
|
+
plan.rig === undefined ? [{ nodeId: plan.nodeId, bitmap: null, message: plan.message }] : [],
|
|
434
|
+
),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Cell 0 is the probe, so the grid is sized for one more picture than the
|
|
439
|
+
// request asks for. Square-ish, because a single row of 65 cells at 256px
|
|
440
|
+
// is 16640 texels wide and past every device's texture limit.
|
|
441
|
+
const cells = plans.length + 1;
|
|
442
|
+
const cols = Math.ceil(Math.sqrt(cells));
|
|
443
|
+
const rows = Math.ceil(cells / cols);
|
|
444
|
+
const width = cols * size;
|
|
445
|
+
const height = rows * size;
|
|
446
|
+
atlas.setSize(width, height);
|
|
447
|
+
|
|
448
|
+
const frame: MaterialGraphFrame = {
|
|
449
|
+
graph,
|
|
450
|
+
binding,
|
|
451
|
+
sceneTimeSeconds: (context.frame * context.doc.fps.den) / context.doc.fps.num,
|
|
452
|
+
// Neutral, and deliberately: a thumbnail shows the NODE's value, so a
|
|
453
|
+
// layer's fade would make two identical branches read differently on two
|
|
454
|
+
// layers.
|
|
455
|
+
opacity: 1,
|
|
456
|
+
shading: context.layer?.kind === "mesh" ? context.layer.mesh.shading : undefined,
|
|
457
|
+
lighting: context.doc.materialLighting,
|
|
458
|
+
// A PRIMITIVE's base colour is a live uniform (its `source-color` node
|
|
459
|
+
// reads this every write), so a colour drag repaints the thumbnails
|
|
460
|
+
// without recompiling. A model's source colours are frozen at load.
|
|
461
|
+
...(context.layer?.kind === "mesh" && context.layer.mesh.asset === undefined
|
|
462
|
+
? { sourceColor: source.color }
|
|
463
|
+
: {}),
|
|
464
|
+
textures,
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const gl = context.gl;
|
|
468
|
+
gl.getViewport(savedViewport);
|
|
469
|
+
gl.setRenderTarget(atlas);
|
|
470
|
+
gl.setScissorTest(true);
|
|
471
|
+
// Visual row-major with row 0 at the TOP; three normalises `setViewport` to
|
|
472
|
+
// a bottom-left origin on both backends, so the y flip is here and only
|
|
473
|
+
// here. Where each cell lands in the READBACK is decided after the probe
|
|
474
|
+
// has been read.
|
|
475
|
+
const drawCell = (index: number, material: Material): void => {
|
|
476
|
+
const x = (index % cols) * size;
|
|
477
|
+
const y = height - (Math.floor(index / cols) + 1) * size;
|
|
478
|
+
gl.setViewport(x, y, size, size);
|
|
479
|
+
gl.setScissor(x, y, size, size);
|
|
480
|
+
quad.material = material;
|
|
481
|
+
gl.render(scene, camera);
|
|
482
|
+
};
|
|
483
|
+
drawCell(0, probe);
|
|
484
|
+
plans.forEach((plan, i) => {
|
|
485
|
+
if (plan.rig === undefined) return;
|
|
486
|
+
plan.rig.write(frame);
|
|
487
|
+
drawCell(i + 1, plan.rig.material);
|
|
488
|
+
});
|
|
489
|
+
gl.setScissorTest(false);
|
|
490
|
+
// The presenter sets a viewport per pane every frame, but only while it is
|
|
491
|
+
// mounted — a camera-only layout leaves whatever was set last standing, so
|
|
492
|
+
// a batch that walked away with a 96px viewport would draw the next frame
|
|
493
|
+
// into one corner of the canvas.
|
|
494
|
+
gl.setViewport(savedViewport.x, savedViewport.y, savedViewport.z, savedViewport.w);
|
|
495
|
+
// LAST, so nothing between here and the next frame can draw into the atlas.
|
|
496
|
+
gl.setRenderTarget(null);
|
|
497
|
+
|
|
498
|
+
const data = await gl.readRenderTargetPixelsAsync(atlas, 0, 0, width, height);
|
|
499
|
+
const bytes = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);
|
|
500
|
+
const image = new ImageData(bytes, width, height);
|
|
501
|
+
const flipY = readsBottomUp(bytes, width, height, size);
|
|
502
|
+
|
|
503
|
+
const results: NodePreviewResult[] = [];
|
|
504
|
+
for (const [i, plan] of plans.entries()) {
|
|
505
|
+
if (plan.rig === undefined) {
|
|
506
|
+
results.push({ nodeId: plan.nodeId, bitmap: null, message: plan.message });
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const cell = i + 1;
|
|
510
|
+
const sx = (cell % cols) * size;
|
|
511
|
+
const row = Math.floor(cell / cols);
|
|
512
|
+
const sy = flipY ? height - (row + 1) * size : row * size;
|
|
513
|
+
results.push({
|
|
514
|
+
nodeId: plan.nodeId,
|
|
515
|
+
bitmap: await createImageBitmap(image, sx, sy, size, size, {
|
|
516
|
+
imageOrientation: flipY ? "flipY" : "none",
|
|
517
|
+
// The atlas is opaque (every head writes alpha 1), so premultiplying
|
|
518
|
+
// would be a no-op that costs a copy.
|
|
519
|
+
premultiplyAlpha: "none",
|
|
520
|
+
}),
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
return { materialId: request.materialId, semanticSignature: signature, size, results };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function drain(
|
|
527
|
+
request: NodePreviewRequest,
|
|
528
|
+
context: () => PreviewContext,
|
|
529
|
+
resolve: (batch: NodePreviewBatch | null) => void,
|
|
530
|
+
): Promise<void> {
|
|
531
|
+
let batch: NodePreviewBatch | null = null;
|
|
532
|
+
try {
|
|
533
|
+
batch = await draw(request, context());
|
|
534
|
+
} catch (error) {
|
|
535
|
+
// A batch that throws is a diagnostic, never a stage failure: the author
|
|
536
|
+
// is mid-edit and the thumbnails are the least important thing on screen.
|
|
537
|
+
console.warn("[shot] a node preview batch failed", error);
|
|
538
|
+
}
|
|
539
|
+
const next = parked;
|
|
540
|
+
parked = null;
|
|
541
|
+
if (next === null) {
|
|
542
|
+
running = false;
|
|
543
|
+
resolve(batch);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
// Superseded while it was drawing, so this answer is already stale. Close
|
|
547
|
+
// the bitmaps rather than hand back pictures nobody will paint.
|
|
548
|
+
closeBatch(batch);
|
|
549
|
+
resolve(null);
|
|
550
|
+
void drain(next.request, next.context, next.resolve);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return {
|
|
554
|
+
run(request, context) {
|
|
555
|
+
return new Promise<NodePreviewBatch | null>((resolve) => {
|
|
556
|
+
if (running) {
|
|
557
|
+
parked?.resolve(null);
|
|
558
|
+
parked = { request, context, resolve };
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
running = true;
|
|
562
|
+
void drain(request, context, resolve);
|
|
563
|
+
});
|
|
564
|
+
},
|
|
565
|
+
dispose() {
|
|
566
|
+
parked?.resolve(null);
|
|
567
|
+
parked = null;
|
|
568
|
+
disposeRigs();
|
|
569
|
+
atlas.dispose();
|
|
570
|
+
geometry.dispose();
|
|
571
|
+
probe.dispose();
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Which way the readback runs, measured off cell 0's ramp.
|
|
578
|
+
*
|
|
579
|
+
* The probe is painted into the atlas's visual TOP-LEFT cell, so it lands in
|
|
580
|
+
* buffer rows [0, size) when the backend hands rows back top-down, and in
|
|
581
|
+
* [height - size, height) when it hands them back bottom-up. Both candidate
|
|
582
|
+
* slots are sampled and the one holding the steeper dark-to-light ramp is the
|
|
583
|
+
* probe. Ties fall to top-down, which is the WebGPU answer.
|
|
584
|
+
*
|
|
585
|
+
* ponytail: a node whose own thumbnail happens to be a perfect inverted ramp in
|
|
586
|
+
* the opposite candidate slot could out-vote the probe. Fixing that needs a
|
|
587
|
+
* second probe cell reserved at the grid's other end; the failure mode is one
|
|
588
|
+
* upside-down batch, so it is not worth the layout it would cost.
|
|
589
|
+
*/
|
|
590
|
+
function readsBottomUp(
|
|
591
|
+
bytes: Uint8ClampedArray,
|
|
592
|
+
width: number,
|
|
593
|
+
height: number,
|
|
594
|
+
size: number,
|
|
595
|
+
): boolean {
|
|
596
|
+
const x = Math.floor(size / 2);
|
|
597
|
+
const red = (row: number): number => bytes[(row * width + x) * 4] ?? 0;
|
|
598
|
+
const topDown = red(size - 1) - red(0);
|
|
599
|
+
const bottomUp = red(height - size) - red(height - 1);
|
|
600
|
+
return bottomUp > topDown;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* The binding the request's slot renders through.
|
|
605
|
+
*
|
|
606
|
+
* Falls back to a bare binding on the requested graph whenever the layer no
|
|
607
|
+
* longer names it — a request can outlive a rebind by one round trip, and
|
|
608
|
+
* overrides are keyed by the parameter ids of the graph they were authored
|
|
609
|
+
* against, so carrying the old ones over would write a stale value into an
|
|
610
|
+
* unrelated uniform.
|
|
611
|
+
*/
|
|
612
|
+
function bindingFor(layer: Layer3D | null, request: NodePreviewRequest): MaterialGraphBinding {
|
|
613
|
+
const material = layer?.kind === "mesh" ? layer.mesh.material : undefined;
|
|
614
|
+
if (!isGraphMaterial(material)) return { kind: "graph", materialId: request.materialId };
|
|
615
|
+
const bound = materialBindingForSlot(material, request.slot);
|
|
616
|
+
return bound.materialId === request.materialId
|
|
617
|
+
? bound
|
|
618
|
+
: { kind: "graph", materialId: request.materialId };
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function closeBatch(batch: NodePreviewBatch | null): void {
|
|
622
|
+
if (batch === null) return;
|
|
623
|
+
for (const result of batch.results) result.bitmap?.close();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function messageOf(error: unknown): string {
|
|
627
|
+
return error instanceof Error ? error.message : String(error);
|
|
628
|
+
}
|