@volter/editor-threejs 0.5.211 → 0.5.212
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/NOTICE +7 -2
- package/README.md +8 -13
- package/contributions/animation-mixers.service.ts +2 -4
- package/contributions/animation-timeline.utility.tsx +1 -2
- package/contributions/three.page.ts +50 -0
- package/dist-node/serving.mjs +84 -3
- package/dist-node/shared-three.mjs +145 -0
- package/package.json +296 -16
- package/serving/animation-stamp.ts +1 -1
- package/serving/index.ts +22 -5
- package/serving/model-import-conversion.ts +6 -3
- package/serving/shared-three.ts +176 -0
- package/src/adapter/constraint.ts +1 -18
- package/src/adapter/object3d-authoring-subject.ts +1 -12
- package/src/adapter/reflection-probe.ts +1 -9
- package/src/adapter/three-contract.ts +6 -60
- package/src/animation/live-mixers.ts +0 -4
- package/src/api.ts +7 -0
- package/src/asset-loaders.ts +2 -178
- package/src/capture/photograph-sampling.ts +333 -0
- package/src/capture/scene.ts +1 -1
- package/src/ecs/object-marks.ts +6 -14
- package/src/ecs/user-data.ts +3 -138
- package/src/kit/animation/three-clips-subject.ts +19 -42
- package/src/kit/asset-compare-core.ts +161 -0
- package/src/kit/asset-compare.ts +8 -27
- package/src/kit/asset-preview-command.ts +14 -19
- package/src/kit/asset-preview-framing.ts +8 -67
- package/src/kit/asset-preview.ts +109 -342
- package/src/kit/asset-workflow/model-inspection.ts +11 -154
- package/src/kit/asset-workflow/preview-resource-lifetime.ts +25 -0
- package/src/kit/authoring/component-instance-root.ts +3 -39
- package/src/kit/authoring/live-object-transform.ts +1 -30
- package/src/kit/authoring/object3d-document-session-registry.ts +7 -15
- package/src/kit/authoring/object3d-document-session.ts +471 -208
- package/src/kit/authoring/shell-viewport-policy.ts +18 -24
- package/src/kit/authoring/source-object3d-authoring-adapter.ts +6 -48
- package/src/kit/authoring/three-projection-core.ts +11 -40
- package/src/kit/authoring/viewport-pick-context.ts +5 -12
- package/src/kit/authoring/viewport-raycast.ts +15 -93
- package/src/kit/authoring/world-hidden-viewport.ts +8 -61
- package/src/kit/capture-camera-pose.ts +68 -0
- package/src/kit/collaboration-presence.ts +41 -0
- package/src/kit/components/CameraInfo.tsx +4 -6
- package/src/kit/components/InspectorObjectPreview.tsx +3 -30
- package/src/kit/components/Object3DDocumentToolbar.css +75 -0
- package/src/kit/components/Object3DDocumentToolbar.tsx +18 -84
- package/src/kit/components/Object3DDocumentViewport.tsx +5 -8
- package/src/kit/components/StageHost.css +40 -0
- package/src/kit/components/StageHost.tsx +181 -404
- package/src/kit/components/StageOverlays.tsx +1 -2
- package/src/kit/components/StatsOverlay.tsx +5 -17
- package/src/kit/components/ViewportFurniture.tsx +27 -154
- package/src/kit/components/ViewportOverlay.tsx +13 -17
- package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
- package/src/kit/components/ViewportShadingMenu.tsx +2 -42
- package/src/kit/components/ViewportViewMenu.tsx +7 -18
- package/src/kit/components/asset-viewers/EntityModelDocument.tsx +4 -13
- package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +5 -5
- package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +24 -102
- package/src/kit/components/asset-viewers/LutAssetDocument.tsx +6 -6
- package/src/kit/components/asset-viewers/Object3DPreview.tsx +10 -45
- package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +2 -2
- package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +11 -10
- package/src/kit/components/asset-viewers/shader-source.ts +144 -0
- package/src/kit/components/asset-viewers/three-asset-viewers.tsx +2 -14
- package/src/kit/components/object3d-contribution-surfaces.tsx +1 -1
- package/src/kit/components/stage-keyboard.tsx +6 -12
- package/src/kit/components/stage-overlay-set.tsx +11 -20
- package/src/kit/components/stage-presence-markers.ts +9 -32
- package/src/kit/components/stage-transform-chrome.ts +4 -4
- package/src/kit/components/stage-transform-tools.tsx +7 -14
- package/src/kit/components/standard-viewport-dressing.ts +47 -167
- package/src/kit/components/world-root-binding.ts +2 -7
- package/src/kit/constraint-helper.ts +1 -1
- package/src/kit/document-renderer-session.ts +114 -0
- package/src/kit/editor-presentation-activity.ts +58 -0
- package/src/kit/editor-shell-store.ts +31 -403
- package/src/kit/editor-viewport.ts +476 -1762
- package/src/kit/entity-lod.ts +3 -16
- package/src/kit/entity-object.ts +1 -39
- package/src/kit/hierarchy-mark-reader.ts +13 -30
- package/src/kit/instanced-presentation.ts +1 -47
- package/src/kit/live-gesture-lock.ts +35 -0
- package/src/kit/live-module-source.ts +12 -52
- package/src/kit/model-thumbnail.ts +7 -35
- package/src/kit/play-camera-flight.ts +7 -16
- package/src/kit/projection/three.ts +13 -366
- package/src/kit/retained-document-states.ts +91 -0
- package/src/kit/scene-document-viewport.ts +3 -3
- package/src/kit/scene-framing.ts +11 -152
- package/src/kit/scene-view-fog.ts +2 -47
- package/src/kit/spatial-handle-visuals.ts +19 -28
- package/src/kit/stories/three-story-model.ts +5 -33
- package/src/kit/three-canvas-render.ts +3 -4
- package/src/kit/three-hierarchy-row-media.ts +3 -11
- package/src/kit/three-inspection-media.ts +6 -6
- package/src/kit/three-integration.ts +18 -18
- package/src/kit/three-state.ts +2 -19
- package/src/kit/three-viewport/camera-fit.ts +2 -6
- package/src/kit/three-viewport/selection-brackets.ts +5 -20
- package/src/kit/three-viewport/selection-outline.ts +4 -56
- package/src/kit/three-viewport/skeleton-helper.ts +1 -32
- package/src/kit/three-viewport/source-color.ts +1 -33
- package/src/kit/three-viewport/studio-environment.ts +4 -55
- package/src/kit/three-viewport-presentation.ts +23 -0
- package/src/kit/transport-strip.tsx +147 -0
- package/src/kit/viewport-actions.ts +8 -50
- package/src/kit/viewport-authoring-policy.ts +5 -52
- package/src/kit/viewport-commands.ts +28 -57
- package/src/kit/viewport-hotkeys.ts +29 -31
- package/src/kit/viewport-status-facet.ts +4 -5
- package/src/kit/viewport-surface-status.tsx +44 -0
- package/src/loader.ts +8 -71
- package/src/object3d-contributions.ts +18 -34
- package/src/viewport/clip-planes.ts +4 -32
- package/src/viewport/content-bounds.ts +13 -142
- package/src/viewport/editor-layers.ts +4 -26
- package/src/viewport/environment.ts +1 -1
- package/src/viewport/preview-renderer.ts +7 -45
- package/src/viewport/renderer-ownership.ts +14 -62
- package/src/viewport-api.ts +14 -33
- package/src/viewport-door.ts +16 -86
- package/contributions/three-integration.service.ts +0 -13
- package/src/adapter/hierarchy-marks.ts +0 -156
- package/src/adapter/ingest/scene-capture.ts +0 -843
- package/src/adapter/ingest/structural-ids.ts +0 -139
- package/src/adapter/ingest/visible-capture-window.ts +0 -302
- package/src/adapter/renderer-config.ts +0 -115
- package/src/asset-parse-error.ts +0 -29
- package/src/host-hierarchy-objects.ts +0 -31
- package/src/kit/authoring/design-time-settle.ts +0 -343
- package/src/kit/camera-authoring.ts +0 -175
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
Tooltip,
|
|
11
11
|
} from '@volter/sdk/widgets';
|
|
12
12
|
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
13
|
-
import type { ShellStore } from '@volter/sdk/
|
|
13
|
+
import type { ShellStore } from '@volter/sdk/shell-store';
|
|
14
14
|
import {
|
|
15
15
|
setThreeViewportProjection,
|
|
16
16
|
subscribeThreeViewportPresentation,
|
|
17
17
|
threeViewportPresentation,
|
|
18
|
-
} from '
|
|
18
|
+
} from '../three-viewport-presentation';
|
|
19
19
|
import {
|
|
20
20
|
object3DDocumentSession,
|
|
21
21
|
object3DDocumentSessionsVersion,
|
|
@@ -25,22 +25,11 @@ import {
|
|
|
25
25
|
const NO_SESSION_SUBSCRIBE = () => () => {};
|
|
26
26
|
const ZERO = () => 0;
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
* A DOCUMENT STAGE IS PAINTED BY ITS SESSION (`Object3DDocumentSession.camera()`), and the
|
|
35
|
-
* editor-wide projection (`setThreeViewportProjection`) reaches only the world root's stage
|
|
36
|
-
* (`world-root-stage.ts` is its one subscriber): over a document the Perspective and
|
|
37
|
-
* Orthographic buttons changed nothing, the defect `ViewportFurniture`'s projection toggle
|
|
38
|
-
* records. With `documentId` naming a stage that has a session, the menu drives that session,
|
|
39
|
-
* as the document toolbar's view menu does; the world root keeps the editor-wide value.
|
|
40
|
-
*
|
|
41
|
-
* `label` draws the trigger as the view's NAME (the look's `stage.chrome.viewName`: Godot's
|
|
42
|
-
* "⋮ Perspective", Unreal's "Perspective" pill) instead of the camera icon; `kebab` puts
|
|
43
|
-
* Godot's three dots before it. */
|
|
28
|
+
/**
|
|
29
|
+
* Editor-camera projection and axis presets for the stage’s own shell. A documentId targets its
|
|
30
|
+
* Object3DDocumentSession; the world root uses the editor-wide projection. label shows the view
|
|
31
|
+
* name instead of a camera icon, and kebab prefixes the label with three dots.
|
|
32
|
+
*/
|
|
44
33
|
export function ViewportViewMenu({
|
|
45
34
|
shell,
|
|
46
35
|
documentId,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
createAssetPreviewSnapshot,
|
|
9
9
|
disposeAssetPreviewSnapshot,
|
|
10
10
|
} from '../../asset-preview';
|
|
11
|
-
import { useEditorStore } from '@volter/sdk/
|
|
11
|
+
import { useEditorStore } from '@volter/sdk/editor-runtime';
|
|
12
12
|
import { Object3DDocumentViewport } from '../StageHost';
|
|
13
13
|
|
|
14
14
|
function entityClips(source: THREE.Object3D): THREE.AnimationClip[] {
|
|
@@ -21,17 +21,8 @@ function entityClips(source: THREE.Object3D): THREE.AnimationClip[] {
|
|
|
21
21
|
return [...discovered];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
* reparented (`createAssetPreviewSnapshot` copies it).
|
|
27
|
-
*
|
|
28
|
-
* The object comes in already resolved, so this component has no way to fail
|
|
29
|
-
* for a thing that is on screen — it is the honest shared body of both the
|
|
30
|
-
* Asset Lab's entity document ({@link EntityModelDocument}, which resolves the
|
|
31
|
-
* scene entity and owns the "no longer available" case) and the inspector's
|
|
32
|
-
* preview section (`components/InspectorObjectPreview.tsx`), whose subject the
|
|
33
|
-
* composer has already proved previewable.
|
|
34
|
-
*/
|
|
24
|
+
/** Isolated projection of an already-resolved Object3D, shared by entity documents and Inspector
|
|
25
|
+
* previews. Snapshot the source so its graph stays parented to its owner. */
|
|
35
26
|
export function Object3DSnapshotDocument({
|
|
36
27
|
documentId,
|
|
37
28
|
source,
|
|
@@ -76,7 +67,7 @@ export function Object3DSnapshotDocument({
|
|
|
76
67
|
chromeless={chromeless}
|
|
77
68
|
// The chromeless host is the inspector's preview, which remounts on
|
|
78
69
|
// every selection — it draws through the shared renderer instead of
|
|
79
|
-
// building one per click
|
|
70
|
+
// building one per click. A full
|
|
80
71
|
// document lives as long as its panel and keeps its own.
|
|
81
72
|
rendererLane={chromeless ? 'inspector-preview' : 'own'}
|
|
82
73
|
assetType="entity"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { editorHost } from '@volter/sdk/host';
|
|
1
2
|
import { faCloudSun, faSliders } from '@fortawesome/free-solid-svg-icons';
|
|
2
3
|
import { NumberInput, themeVars } from '@volter/sdk/widgets';
|
|
3
4
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
@@ -5,10 +6,9 @@ import * as THREE from 'three';
|
|
|
5
6
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
6
7
|
import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
|
|
7
8
|
import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
|
|
8
|
-
import type { InspectionSection } from '@volter/sdk/
|
|
9
|
-
import { CONTRIBUTED_SECTION_ORDER, PROPERTIES_SECTION_ORDER } from '@volter/sdk/
|
|
10
|
-
import { AssetEditorShell } from '@volter/sdk/
|
|
11
|
-
import { subscribeProjectAsset } from '@volter/sdk/kit/components/asset-editor-persistence';
|
|
9
|
+
import type { InspectionSection } from '@volter/sdk/inspection-model';
|
|
10
|
+
import { CONTRIBUTED_SECTION_ORDER, PROPERTIES_SECTION_ORDER } from '@volter/sdk/inspection-model';
|
|
11
|
+
import { AssetEditorShell } from '@volter/sdk/components/AssetEditorShell';
|
|
12
12
|
|
|
13
13
|
interface EnvironmentImage {
|
|
14
14
|
readonly texture: THREE.DataTexture;
|
|
@@ -296,7 +296,7 @@ export function EnvironmentAssetDocument({
|
|
|
296
296
|
|
|
297
297
|
useEffect(() => {
|
|
298
298
|
if (!active) return;
|
|
299
|
-
return
|
|
299
|
+
return editorHost().files.subscribeAsset(assetPath, () => setRevision((value) => value + 1));
|
|
300
300
|
}, [active, assetPath]);
|
|
301
301
|
|
|
302
302
|
useEffect(() => {
|
|
@@ -1,57 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Lab document that RE-EXECUTES ON EVERY SAVE with the camera untouched —
|
|
5
|
-
* "watch it model".
|
|
2
|
+
* Live Object3D document for authored project modules. Probe the export before mounting a viewport;
|
|
3
|
+
* modules with other shapes use the caller's source viewer.
|
|
6
4
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* framing, the document toolbar, the ordinary Object3D inspector subject), and
|
|
11
|
-
* the contract it resolves is the bake lane's, unchanged
|
|
12
|
-
* (`live-module-source.ts`).
|
|
5
|
+
* A stable build callback returns one owned Group. Rebuilds replace its child, preserving the
|
|
6
|
+
* stage's camera, renderer and adapter. This component owns child disposal; the viewport's returned
|
|
7
|
+
* graph disposer is empty so the graph has one teardown path.
|
|
13
8
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `Object3DDocumentViewport` re-runs its whole lifetime effect — renderer,
|
|
17
|
-
* scene, camera, adapter — whenever the `build` callback's IDENTITY changes.
|
|
18
|
-
* That is how the parametric Builder documents rebuild
|
|
19
|
-
* (`catalog/project-source/editor/tools/builder-document.tsx`: a param edit makes
|
|
20
|
-
* a new `build`, the viewport tears down and stands back up), and it is
|
|
21
|
-
* exactly why those documents snap the camera back to `cameraDirection` on
|
|
22
|
-
* every slider nudge. A modeling loop cannot do that: the whole point is to
|
|
23
|
-
* keep looking at the model from where you put yourself while it changes
|
|
24
|
-
* underneath you.
|
|
25
|
-
*
|
|
26
|
-
* So `build` here is STABLE FOR THE DOCUMENT'S LIFETIME. It returns a
|
|
27
|
-
* container `THREE.Group` this component owns, and a rebuild swaps that
|
|
28
|
-
* container's single CHILD. The viewport never learns a rebuild happened; the
|
|
29
|
-
* camera, the renderer and the GPU state are never touched. This is the
|
|
30
|
-
* "OWNED graph" shape `ToolObject3DAuthoringProps.build` documents (the same
|
|
31
|
-
* one the 3D board and the story turntable take, for the same reason: the
|
|
32
|
-
* graph is async to create), so the returned `dispose` is deliberately empty
|
|
33
|
-
* and THIS component's effect is the one teardown path.
|
|
34
|
-
*
|
|
35
|
-
* ## The probe decides which document this is
|
|
36
|
-
*
|
|
37
|
-
* Detection is structural, never a filename convention — the quarks precedent
|
|
38
|
-
* (`JsonAssetDocument.tsx`). The module is imported and its export resolved
|
|
39
|
-
* ONCE before anything mounts; only an `Object3D` gets the live viewport, and
|
|
40
|
-
* anything else falls through to the `fallback` the caller passed (the
|
|
41
|
-
* ordinary source viewer). Mounting the viewport first and retreating would
|
|
42
|
-
* flash a 3D panel at every `.ts` file in the project.
|
|
43
|
-
*
|
|
44
|
-
* ## A broken save is never a blank pane
|
|
45
|
-
*
|
|
46
|
-
* Once live, a failing rebuild KEEPS THE LAST GOOD MODEL on screen, dimmed,
|
|
47
|
-
* under a banner carrying the message and stack. The document does not
|
|
48
|
-
* unmount, the workspace does not crash, and the next good save clears the
|
|
49
|
-
* banner in place. A failure at FIRST open has no last-good root to keep, so
|
|
50
|
-
* it shows the source viewer with the same banner above it and recovers to the
|
|
51
|
-
* live view when the module compiles.
|
|
9
|
+
* A failed rebuild keeps the last good model dimmed under a diagnostic banner. A failure before
|
|
10
|
+
* the first model uses the source viewer with that banner. A successful build clears the failure.
|
|
52
11
|
*/
|
|
53
12
|
|
|
54
|
-
import { beginLiveModuleRevision } from '@volter/sdk/
|
|
13
|
+
import { beginLiveModuleRevision } from '@volter/sdk/project-module-url';
|
|
55
14
|
import { themeVars } from '@volter/sdk/widgets';
|
|
56
15
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
57
16
|
import * as THREE from 'three';
|
|
@@ -60,7 +19,7 @@ import {
|
|
|
60
19
|
object3DDocumentSession,
|
|
61
20
|
registerObject3DDocumentPreparation,
|
|
62
21
|
} from '../../authoring/object3d-document-session-registry';
|
|
63
|
-
import { editorConsole } from '@volter/sdk/
|
|
22
|
+
import { editorConsole } from '@volter/sdk/editor-console';
|
|
64
23
|
import {
|
|
65
24
|
buildLiveModuleObject3D,
|
|
66
25
|
LiveModuleError,
|
|
@@ -71,18 +30,12 @@ import {
|
|
|
71
30
|
projectModuleChangeMatches,
|
|
72
31
|
projectModuleTransformError,
|
|
73
32
|
subscribeProjectModuleChange,
|
|
74
|
-
} from '@volter/sdk/
|
|
75
|
-
import { notifyWorkspaceDocumentSelectionChanged } from '@volter/sdk/
|
|
33
|
+
} from '@volter/sdk/project-module-changes';
|
|
34
|
+
import { notifyWorkspaceDocumentSelectionChanged } from '@volter/sdk/workspace-document-registry';
|
|
76
35
|
import { Object3DDocumentViewport } from '../Object3DDocumentViewport';
|
|
77
36
|
import { STANDARD_COMPONENT_CAMERA_DIRECTION } from '../standard-viewport-dressing';
|
|
78
37
|
|
|
79
|
-
/**
|
|
80
|
-
* The asset-document host (`components/asset-documents.tsx`) hands its content
|
|
81
|
-
* a plain BLOCK container of the panel's full height, not a flex parent — so
|
|
82
|
-
* `flex: 1` on this component's own root measures ZERO and the viewport
|
|
83
|
-
* canvas comes up 1241×0 (measured, 2026-08-29). Height 100% is what fills it;
|
|
84
|
-
* the flex column below it is this component's own layout.
|
|
85
|
-
*/
|
|
38
|
+
/** Fill the asset host’s block container by height; this component owns the flex column inside it. */
|
|
86
39
|
const FILL = {
|
|
87
40
|
height: '100%',
|
|
88
41
|
width: '100%',
|
|
@@ -91,33 +44,21 @@ const FILL = {
|
|
|
91
44
|
position: 'relative',
|
|
92
45
|
} as const;
|
|
93
46
|
|
|
94
|
-
/**
|
|
95
|
-
* child with no grow measures 1×388 (also measured) — the mirror of the
|
|
96
|
-
* block-parent trap above. */
|
|
47
|
+
/** Grow this child along both axes within FILL. */
|
|
97
48
|
const FILL_CHILD = { flex: 1, minWidth: 0, minHeight: 0, display: 'flex' } as const;
|
|
98
49
|
|
|
99
|
-
/**
|
|
100
|
-
* decided yet. `fallback` covers the last two the same way — the caller's
|
|
101
|
-
* ordinary viewer — and `reason` is why. */
|
|
50
|
+
/** Whether this module is being probed, showing a live model, or using its ordinary source viewer. */
|
|
102
51
|
type ProbeState =
|
|
103
52
|
| { readonly status: 'probing' }
|
|
104
|
-
| { readonly status: 'live'
|
|
105
|
-
| { readonly status: 'fallback'
|
|
53
|
+
| { readonly status: 'live' }
|
|
54
|
+
| { readonly status: 'fallback' };
|
|
106
55
|
|
|
107
56
|
interface BuildFailure {
|
|
108
57
|
readonly message: string;
|
|
109
58
|
readonly stack?: string | undefined;
|
|
110
59
|
}
|
|
111
60
|
|
|
112
|
-
/**
|
|
113
|
-
* What the banner says. A transform failure PREFERS the server's own reason:
|
|
114
|
-
* the browser's `import()` rejection for a module the dev server refused to
|
|
115
|
-
* transform is the opaque `Failed to fetch dynamically imported module: <url>`
|
|
116
|
-
* (measured), while Vite reported `Unexpected ";"` at line:col with a source
|
|
117
|
-
* frame on the HMR channel — and the second one is the only one a person
|
|
118
|
-
* mid-edit can act on. The rejection then becomes the detail line, because it
|
|
119
|
-
* is still the truth about what this document tried to do.
|
|
120
|
-
*/
|
|
61
|
+
/** Prefer the dev server’s transform diagnostic to the opaque import rejection; retain the rejection as detail. */
|
|
121
62
|
function describeRefusal(refusal: LiveModuleRefusal, modulePath: string): BuildFailure {
|
|
122
63
|
if (refusal.kind !== 'import-failed') return { message: refusal.message };
|
|
123
64
|
const transform = projectModuleTransformError(modulePath);
|
|
@@ -140,8 +81,7 @@ export function LiveModuleDocument({
|
|
|
140
81
|
readonly modulePath: string;
|
|
141
82
|
readonly displayName: string;
|
|
142
83
|
readonly active: boolean;
|
|
143
|
-
/** Shown
|
|
144
|
-
* failing to import at first open). */
|
|
84
|
+
/** Shown while probing, for non-model exports, or when the first import fails. */
|
|
145
85
|
readonly fallback: React.ReactNode;
|
|
146
86
|
}) {
|
|
147
87
|
const [probe, setProbe] = useState<ProbeState>({ status: 'probing' });
|
|
@@ -185,19 +125,7 @@ export function LiveModuleDocument({
|
|
|
185
125
|
mounted = root;
|
|
186
126
|
mountedDispose = dispose;
|
|
187
127
|
container.add(root);
|
|
188
|
-
//
|
|
189
|
-
// build result's `animations` onto the built root "so the clip list
|
|
190
|
-
// travels with the graph the document mounts and every reader downstream
|
|
191
|
-
// (the Object3D document session, its animation transport) finds it where
|
|
192
|
-
// it finds a GLTF's" — but the graph this document MOUNTS is the stable
|
|
193
|
-
// container, and every one of those readers reads `root.animations` on
|
|
194
|
-
// the document root exactly (`ToolObject3DAuthoring`'s
|
|
195
|
-
// `source.root.animations.length > 0` gate builds the mixer and the
|
|
196
|
-
// transport; `model-inspection.ts`'s `root.animations.map` builds the
|
|
197
|
-
// Animation section). Measured on a rigged module with two looping clips:
|
|
198
|
-
// no transport, no Animation section, `editor.inspect()` listing only
|
|
199
|
-
// Preview/Geometry/Rig/Materials/Source. Copying the reference
|
|
200
|
-
// here is what makes the container the same subject the child is.
|
|
128
|
+
// Copy animations to the stable container, which is the document root inspected by the transport and Animation section.
|
|
201
129
|
container.animations = root.animations;
|
|
202
130
|
// The hierarchy/inspector project the live graph on every render, so the
|
|
203
131
|
// swap is visible the moment the panels re-render — but nothing else
|
|
@@ -209,7 +137,7 @@ export function LiveModuleDocument({
|
|
|
209
137
|
// Not a model module, and never was: this is an ordinary source file.
|
|
210
138
|
// Silent by design — the project is full of them.
|
|
211
139
|
if (refusal.kind === 'not-a-model' && !hasLastGood) {
|
|
212
|
-
setProbe({ status: 'fallback'
|
|
140
|
+
setProbe({ status: 'fallback' });
|
|
213
141
|
return;
|
|
214
142
|
}
|
|
215
143
|
const described = describeRefusal(refusal, modulePath);
|
|
@@ -218,7 +146,7 @@ export function LiveModuleDocument({
|
|
|
218
146
|
// surface, not document-local noise. It retires on the next page load
|
|
219
147
|
// that does not reproduce it (`server/console-ledger.ts`).
|
|
220
148
|
editorConsole.error(`${modulePath}: ${described.message}`, 'live-module');
|
|
221
|
-
if (!hasLastGood) setProbe({ status: 'fallback'
|
|
149
|
+
if (!hasLastGood) setProbe({ status: 'fallback' });
|
|
222
150
|
};
|
|
223
151
|
|
|
224
152
|
/** A save that lands mid-import supersedes this one; its own swap wins. */
|
|
@@ -247,7 +175,7 @@ export function LiveModuleDocument({
|
|
|
247
175
|
captureFailure = null;
|
|
248
176
|
clearProjectModuleTransformError(modulePath);
|
|
249
177
|
setFailure(null);
|
|
250
|
-
setProbe({ status: 'live'
|
|
178
|
+
setProbe({ status: 'live' });
|
|
251
179
|
};
|
|
252
180
|
|
|
253
181
|
const rebuild = (): Promise<void> => {
|
|
@@ -278,14 +206,8 @@ export function LiveModuleDocument({
|
|
|
278
206
|
};
|
|
279
207
|
}, [container, documentId, modulePath, projectRoot]);
|
|
280
208
|
|
|
281
|
-
/**
|
|
282
|
-
*
|
|
283
|
-
* never move the camera, which is the one thing this document exists to
|
|
284
|
-
* avoid. The viewport registers its session in its own mount effect, a
|
|
285
|
-
* render AFTER the state flip that mounts it, so this waits for the session
|
|
286
|
-
* rather than assuming it: bounded, because "no session after a second" is a
|
|
287
|
-
* mount failure, not something to poll forever.
|
|
288
|
-
*/
|
|
209
|
+
/** Frame the first built model once the viewport session mounts, waiting at most 61 animation-frame turns.
|
|
210
|
+
* Rebuilds retain the person's camera. */
|
|
289
211
|
useEffect(() => {
|
|
290
212
|
if (probe.status !== 'live' || framedRef.current) return;
|
|
291
213
|
let frame = 0;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import { editorHost } from '@volter/sdk/host';
|
|
1
2
|
import { faCircleHalfStroke, faSliders } from '@fortawesome/free-solid-svg-icons';
|
|
2
3
|
import { Checkbox, NumberInput, themeVars } from '@volter/sdk/widgets';
|
|
3
4
|
import { EffectComposer, EffectPass, LUT3DEffect, RenderPass } from 'postprocessing';
|
|
4
5
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
6
|
import * as THREE from 'three';
|
|
6
7
|
import { LUTCubeLoader } from 'three/addons/loaders/LUTCubeLoader.js';
|
|
7
|
-
import { readProjectTextFile } from '@volter/sdk/
|
|
8
|
-
import type { InspectionSection } from '@volter/sdk/
|
|
9
|
-
import { CONTRIBUTED_SECTION_ORDER, PROPERTIES_SECTION_ORDER } from '@volter/sdk/
|
|
10
|
-
import { AssetEditorShell } from '@volter/sdk/
|
|
11
|
-
import { subscribeProjectAsset } from '@volter/sdk/kit/components/asset-editor-persistence';
|
|
8
|
+
import { readProjectTextFile } from '@volter/sdk/api/project-source';
|
|
9
|
+
import type { InspectionSection } from '@volter/sdk/inspection-model';
|
|
10
|
+
import { CONTRIBUTED_SECTION_ORDER, PROPERTIES_SECTION_ORDER } from '@volter/sdk/inspection-model';
|
|
11
|
+
import { AssetEditorShell } from '@volter/sdk/components/AssetEditorShell';
|
|
12
12
|
|
|
13
13
|
interface CubeLut {
|
|
14
14
|
readonly title: string | null;
|
|
@@ -282,7 +282,7 @@ export function LutAssetDocument({
|
|
|
282
282
|
|
|
283
283
|
useEffect(() => {
|
|
284
284
|
if (!active) return;
|
|
285
|
-
return
|
|
285
|
+
return editorHost().files.subscribeAsset(assetPath, () => setRevision((value) => value + 1));
|
|
286
286
|
}, [active, assetPath]);
|
|
287
287
|
|
|
288
288
|
useEffect(() => {
|
|
@@ -6,30 +6,21 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
6
6
|
import {
|
|
7
7
|
createOwnedObjectPreviewSnapshot,
|
|
8
8
|
createPerspectiveAssetPreviewCamera,
|
|
9
|
-
disposeProjectAssetModel,
|
|
10
|
-
loadProjectAssetModel,
|
|
11
9
|
measureAssetPreview,
|
|
12
10
|
type NativeObjectPreviewSource,
|
|
13
11
|
type OwnedObjectPreviewSnapshot,
|
|
14
12
|
} from '../../asset-preview';
|
|
15
|
-
import { registerPreviewResource } from '
|
|
16
|
-
import { registerDesignTimeSurface } from '@volter/sdk/
|
|
13
|
+
import { registerPreviewResource } from '../../asset-workflow/preview-resource-lifetime';
|
|
14
|
+
import { registerDesignTimeSurface } from '@volter/sdk/coverage/design-time-surfaces';
|
|
17
15
|
import {
|
|
18
16
|
isEditorPresentationActive,
|
|
19
17
|
subscribeEditorPresentationActivity,
|
|
20
|
-
} from '
|
|
21
|
-
import {
|
|
22
|
-
createModelPreviewSource,
|
|
23
|
-
loadModelThumbnailObject,
|
|
24
|
-
modelThumbnailFormat,
|
|
25
|
-
} from '../../model-thumbnail';
|
|
18
|
+
} from '../../editor-presentation-activity';
|
|
26
19
|
|
|
27
20
|
export interface Object3DPreviewProps {
|
|
28
|
-
readonly assetPath?: string | undefined;
|
|
29
|
-
readonly materialPath?: string | undefined;
|
|
30
21
|
readonly displayName?: string | undefined;
|
|
31
22
|
readonly active?: boolean | undefined;
|
|
32
|
-
readonly sourceFactory
|
|
23
|
+
readonly sourceFactory: () => NativeObjectPreviewSource;
|
|
33
24
|
readonly showSkeleton?: boolean | undefined;
|
|
34
25
|
readonly background?: THREE.ColorRepresentation | undefined;
|
|
35
26
|
readonly cameraDirection?: readonly [number, number, number] | undefined;
|
|
@@ -65,8 +56,6 @@ let previewCardSequence = 0;
|
|
|
65
56
|
|
|
66
57
|
/** Compact, non-authoring Object3D preview for cards, catalogs, and thumbnails. */
|
|
67
58
|
export function Object3DPreview({
|
|
68
|
-
assetPath,
|
|
69
|
-
materialPath,
|
|
70
59
|
displayName = 'Object3D preview',
|
|
71
60
|
active = true,
|
|
72
61
|
sourceFactory,
|
|
@@ -103,9 +92,8 @@ export function Object3DPreview({
|
|
|
103
92
|
const scene = new THREE.Scene();
|
|
104
93
|
const camera = new THREE.PerspectiveCamera(35, 1, 0.01, 1000);
|
|
105
94
|
const controls = new OrbitControls(camera, renderer.domElement);
|
|
106
|
-
const
|
|
95
|
+
const timer = new THREE.Timer();
|
|
107
96
|
let mixer: THREE.AnimationMixer | null = null;
|
|
108
|
-
let ownedModel: THREE.Object3D | null = null;
|
|
109
97
|
let ownedFactory: OwnedObjectPreviewSnapshot | null = null;
|
|
110
98
|
let extension: ToolObject3DPreviewExtension | undefined;
|
|
111
99
|
let sparkRenderer: SparkRenderer | null = null;
|
|
@@ -192,29 +180,8 @@ export function Object3DPreview({
|
|
|
192
180
|
|
|
193
181
|
const load = async () => {
|
|
194
182
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
await install(ownedFactory.root);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (!assetPath) throw new Error('Compact Object3D preview has no source.');
|
|
201
|
-
const format = modelThumbnailFormat(assetPath);
|
|
202
|
-
if (!format) throw new Error(`No native Three.js loader supports ${assetPath}.`);
|
|
203
|
-
const loaded =
|
|
204
|
-
format === 'glb' || format === 'gltf' || format === 'spz'
|
|
205
|
-
? await loadProjectAssetModel(assetPath, controller.signal)
|
|
206
|
-
: await loadModelThumbnailObject({
|
|
207
|
-
url: assetPath,
|
|
208
|
-
format,
|
|
209
|
-
...(materialPath ? { materialUrl: materialPath } : {}),
|
|
210
|
-
});
|
|
211
|
-
ownedModel = createModelPreviewSource(loaded);
|
|
212
|
-
if (controller.signal.aborted || disposed) {
|
|
213
|
-
disposeProjectAssetModel(ownedModel);
|
|
214
|
-
ownedModel = null;
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
await install(ownedModel);
|
|
183
|
+
ownedFactory = createOwnedObjectPreviewSnapshot(sourceFactory);
|
|
184
|
+
await install(ownedFactory.root);
|
|
218
185
|
} catch (caught) {
|
|
219
186
|
if (!controller.signal.aborted && !disposed) {
|
|
220
187
|
setError(caught instanceof Error ? caught.message : String(caught));
|
|
@@ -245,7 +212,8 @@ export function Object3DPreview({
|
|
|
245
212
|
// PRESENTATION ONLY. The mixer is never advanced here — see
|
|
246
213
|
// `poseAtFirstFrame`; `delta` drives the project's own presentation
|
|
247
214
|
// extension and the orbit damping, both editor-owned.
|
|
248
|
-
|
|
215
|
+
timer.update();
|
|
216
|
+
const delta = Math.min(timer.getDelta(), 0.1);
|
|
249
217
|
controls.update();
|
|
250
218
|
if (sparkRenderer) scene.add(sparkRenderer);
|
|
251
219
|
try {
|
|
@@ -261,7 +229,7 @@ export function Object3DPreview({
|
|
|
261
229
|
cancelAnimationFrame(frame);
|
|
262
230
|
frame = 0;
|
|
263
231
|
} else if (!disposed && frame === 0) {
|
|
264
|
-
|
|
232
|
+
timer.update();
|
|
265
233
|
render();
|
|
266
234
|
}
|
|
267
235
|
});
|
|
@@ -293,7 +261,6 @@ export function Object3DPreview({
|
|
|
293
261
|
} else material?.dispose();
|
|
294
262
|
}
|
|
295
263
|
if (ownedFactory) ownedFactory.dispose();
|
|
296
|
-
if (ownedModel) disposeProjectAssetModel(ownedModel);
|
|
297
264
|
if (container.contains(renderer.domElement)) container.removeChild(renderer.domElement);
|
|
298
265
|
releaseResizeObserver();
|
|
299
266
|
releaseAnimationFrame();
|
|
@@ -301,11 +268,9 @@ export function Object3DPreview({
|
|
|
301
268
|
};
|
|
302
269
|
}, [
|
|
303
270
|
active,
|
|
304
|
-
assetPath,
|
|
305
271
|
background,
|
|
306
272
|
cameraDirection,
|
|
307
273
|
exposure,
|
|
308
|
-
materialPath,
|
|
309
274
|
setupPreview,
|
|
310
275
|
showGrid,
|
|
311
276
|
showSkeleton,
|
|
@@ -16,7 +16,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
16
16
|
import type * as THREE from 'three';
|
|
17
17
|
import { type ParticleSystem, QuarksLoader } from 'three.quarks';
|
|
18
18
|
import { quarksParticleSystems } from '../../authoring/quarks-particle-systems';
|
|
19
|
-
import {
|
|
19
|
+
import { projectPathOfServedAsset } from '@volter/sdk/project/output-roots';
|
|
20
20
|
import { Object3DDocumentViewport } from '../StageHost';
|
|
21
21
|
import { threeObject } from '../../../adapter/three-contract';
|
|
22
22
|
|
|
@@ -445,7 +445,7 @@ export async function loadNativeQuarksJsonSource(
|
|
|
445
445
|
return null;
|
|
446
446
|
}
|
|
447
447
|
return isNativeQuarksJson(json)
|
|
448
|
-
? { text, json, projectPath:
|
|
448
|
+
? { text, json, projectPath: projectPathOfServedAsset(assetPath) }
|
|
449
449
|
: null;
|
|
450
450
|
}
|
|
451
451
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { editorHost } from '@volter/sdk/host';
|
|
1
2
|
import { faCode, faSliders, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
|
|
2
3
|
import { Checkbox, ColorInput, NumberInput, Select, themeVars } from '@volter/sdk/widgets';
|
|
3
4
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
5
|
import * as THREE from 'three';
|
|
5
|
-
import { readProjectTextFile } from '@volter/sdk/
|
|
6
|
-
import type { InspectionSection } from '@volter/sdk/
|
|
7
|
-
import { CONTRIBUTED_SECTION_ORDER, PROPERTIES_SECTION_ORDER } from '@volter/sdk/
|
|
8
|
-
import { AssetEditorShell } from '@volter/sdk/
|
|
9
|
-
import { subscribeProjectAsset } from '@volter/sdk/kit/components/asset-editor-persistence';
|
|
6
|
+
import { readProjectTextFile } from '@volter/sdk/api/project-source';
|
|
7
|
+
import type { InspectionSection } from '@volter/sdk/inspection-model';
|
|
8
|
+
import { CONTRIBUTED_SECTION_ORDER, PROPERTIES_SECTION_ORDER } from '@volter/sdk/inspection-model';
|
|
9
|
+
import { AssetEditorShell } from '@volter/sdk/components/AssetEditorShell';
|
|
10
10
|
import {
|
|
11
11
|
FALLBACK_FRAGMENT_SHADER,
|
|
12
12
|
FALLBACK_VERTEX_SHADER,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
type ShaderUniformDescriptor,
|
|
19
19
|
shaderCompanionPath,
|
|
20
20
|
shaderStageFromPath,
|
|
21
|
-
} from '
|
|
21
|
+
} from './shader-source';
|
|
22
22
|
|
|
23
23
|
type PreviewGeometry = 'sphere' | 'box' | 'plane';
|
|
24
24
|
type UniformPreviewValue = number | boolean | string | number[];
|
|
@@ -267,7 +267,7 @@ function ShaderPreview({
|
|
|
267
267
|
const observer = new ResizeObserver(resize);
|
|
268
268
|
observer.observe(host);
|
|
269
269
|
|
|
270
|
-
const
|
|
270
|
+
const timer = new THREE.Timer();
|
|
271
271
|
try {
|
|
272
272
|
renderer.compile(scene, camera);
|
|
273
273
|
renderer.render(scene, camera);
|
|
@@ -294,7 +294,8 @@ function ShaderPreview({
|
|
|
294
294
|
|
|
295
295
|
const render = () => {
|
|
296
296
|
if (disposed) return;
|
|
297
|
-
|
|
297
|
+
timer.update();
|
|
298
|
+
const elapsed = timer.getElapsed();
|
|
298
299
|
for (const descriptor of descriptors) {
|
|
299
300
|
const uniform = uniforms[descriptor.name];
|
|
300
301
|
if (!uniform) continue;
|
|
@@ -535,8 +536,8 @@ export function ShaderAssetDocument({
|
|
|
535
536
|
const reload = () => setFileRevision((revision) => revision + 1);
|
|
536
537
|
const companionPath = shaderCompanionPath(assetPath);
|
|
537
538
|
const unsubscribers = [
|
|
538
|
-
|
|
539
|
-
...(companionPath ? [
|
|
539
|
+
editorHost().files.subscribeAsset(assetPath, reload),
|
|
540
|
+
...(companionPath ? [editorHost().files.subscribeAsset(companionPath, reload)] : []),
|
|
540
541
|
];
|
|
541
542
|
return () => {
|
|
542
543
|
for (const unsubscribe of unsubscribers) unsubscribe();
|