@volter/editor-threejs 0.5.211 → 0.5.213

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.
Files changed (133) hide show
  1. package/NOTICE +7 -2
  2. package/README.md +8 -13
  3. package/contributions/animation-mixers.service.ts +2 -4
  4. package/contributions/animation-timeline.utility.tsx +1 -2
  5. package/contributions/three.page.ts +50 -0
  6. package/dist-node/serving.mjs +84 -3
  7. package/dist-node/shared-three.mjs +145 -0
  8. package/package.json +296 -16
  9. package/serving/animation-stamp.ts +1 -1
  10. package/serving/index.ts +22 -5
  11. package/serving/model-import-conversion.ts +6 -3
  12. package/serving/shared-three.ts +176 -0
  13. package/src/adapter/constraint.ts +1 -18
  14. package/src/adapter/object3d-authoring-subject.ts +1 -12
  15. package/src/adapter/reflection-probe.ts +1 -9
  16. package/src/adapter/three-contract.ts +6 -60
  17. package/src/animation/live-mixers.ts +0 -4
  18. package/src/api.ts +7 -0
  19. package/src/asset-loaders.ts +2 -178
  20. package/src/capture/photograph-sampling.ts +333 -0
  21. package/src/capture/scene.ts +1 -1
  22. package/src/ecs/object-marks.ts +6 -14
  23. package/src/ecs/user-data.ts +3 -138
  24. package/src/kit/animation/three-clips-subject.ts +19 -42
  25. package/src/kit/asset-compare-core.ts +161 -0
  26. package/src/kit/asset-compare.ts +8 -27
  27. package/src/kit/asset-preview-command.ts +14 -19
  28. package/src/kit/asset-preview-framing.ts +8 -67
  29. package/src/kit/asset-preview.ts +109 -342
  30. package/src/kit/asset-workflow/model-inspection.ts +11 -154
  31. package/src/kit/asset-workflow/preview-resource-lifetime.ts +25 -0
  32. package/src/kit/authoring/component-instance-root.ts +3 -39
  33. package/src/kit/authoring/live-object-transform.ts +1 -30
  34. package/src/kit/authoring/object3d-document-session-registry.ts +7 -15
  35. package/src/kit/authoring/object3d-document-session.ts +471 -208
  36. package/src/kit/authoring/shell-viewport-policy.ts +18 -24
  37. package/src/kit/authoring/source-object3d-authoring-adapter.ts +6 -48
  38. package/src/kit/authoring/three-projection-core.ts +11 -40
  39. package/src/kit/authoring/viewport-pick-context.ts +5 -12
  40. package/src/kit/authoring/viewport-raycast.ts +15 -93
  41. package/src/kit/authoring/world-hidden-viewport.ts +8 -61
  42. package/src/kit/capture-camera-pose.ts +68 -0
  43. package/src/kit/collaboration-presence.ts +41 -0
  44. package/src/kit/components/CameraInfo.tsx +4 -6
  45. package/src/kit/components/InspectorObjectPreview.tsx +3 -30
  46. package/src/kit/components/Object3DDocumentToolbar.css +75 -0
  47. package/src/kit/components/Object3DDocumentToolbar.tsx +18 -84
  48. package/src/kit/components/Object3DDocumentViewport.tsx +6 -11
  49. package/src/kit/components/StageHost.css +40 -0
  50. package/src/kit/components/StageHost.tsx +192 -439
  51. package/src/kit/components/StageOverlays.tsx +1 -2
  52. package/src/kit/components/StatsOverlay.tsx +5 -17
  53. package/src/kit/components/ViewportFurniture.tsx +27 -154
  54. package/src/kit/components/ViewportOverlay.tsx +13 -17
  55. package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
  56. package/src/kit/components/ViewportShadingMenu.tsx +2 -42
  57. package/src/kit/components/ViewportViewMenu.tsx +7 -18
  58. package/src/kit/components/asset-viewers/EntityModelDocument.tsx +6 -17
  59. package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +9 -11
  60. package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +31 -133
  61. package/src/kit/components/asset-viewers/LutAssetDocument.tsx +10 -12
  62. package/src/kit/components/asset-viewers/ModelAssetDocument.tsx +4 -8
  63. package/src/kit/components/asset-viewers/Object3DPreview.tsx +13 -64
  64. package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +6 -10
  65. package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +48 -65
  66. package/src/kit/components/asset-viewers/shader-source.ts +144 -0
  67. package/src/kit/components/asset-viewers/three-asset-viewers.tsx +2 -14
  68. package/src/kit/components/object3d-contribution-surfaces.tsx +5 -3
  69. package/src/kit/components/stage-keyboard.tsx +6 -12
  70. package/src/kit/components/stage-overlay-set.tsx +11 -20
  71. package/src/kit/components/stage-presence-markers.ts +9 -32
  72. package/src/kit/components/stage-transform-chrome.ts +4 -4
  73. package/src/kit/components/stage-transform-tools.tsx +7 -14
  74. package/src/kit/components/standard-viewport-dressing.ts +47 -167
  75. package/src/kit/components/world-root-binding.ts +2 -7
  76. package/src/kit/constraint-helper.ts +1 -1
  77. package/src/kit/document-renderer-session.ts +114 -0
  78. package/src/kit/editor-presentation-activity.ts +58 -0
  79. package/src/kit/editor-shell-store.ts +31 -403
  80. package/src/kit/editor-viewport.ts +486 -1762
  81. package/src/kit/entity-lod.ts +3 -16
  82. package/src/kit/entity-object.ts +1 -39
  83. package/src/kit/hierarchy-mark-reader.ts +13 -30
  84. package/src/kit/instanced-presentation.ts +1 -47
  85. package/src/kit/live-gesture-lock.ts +35 -0
  86. package/src/kit/live-module-source.ts +12 -52
  87. package/src/kit/model-thumbnail.ts +7 -35
  88. package/src/kit/play-camera-flight.ts +7 -16
  89. package/src/kit/projection/three.ts +13 -366
  90. package/src/kit/retained-document-states.ts +91 -0
  91. package/src/kit/scene-document-viewport.ts +3 -3
  92. package/src/kit/scene-framing.ts +11 -152
  93. package/src/kit/scene-view-fog.ts +2 -47
  94. package/src/kit/spatial-handle-visuals.ts +19 -28
  95. package/src/kit/stories/three-story-model.ts +5 -33
  96. package/src/kit/three-canvas-render.ts +3 -4
  97. package/src/kit/three-hierarchy-row-media.ts +3 -11
  98. package/src/kit/three-inspection-media.ts +6 -6
  99. package/src/kit/three-integration.ts +18 -18
  100. package/src/kit/three-state.ts +2 -19
  101. package/src/kit/three-viewport/camera-fit.ts +2 -6
  102. package/src/kit/three-viewport/selection-brackets.ts +5 -20
  103. package/src/kit/three-viewport/selection-outline.ts +4 -56
  104. package/src/kit/three-viewport/skeleton-helper.ts +1 -32
  105. package/src/kit/three-viewport/source-color.ts +1 -33
  106. package/src/kit/three-viewport/studio-environment.ts +4 -55
  107. package/src/kit/three-viewport-presentation.ts +23 -0
  108. package/src/kit/transport-strip.tsx +147 -0
  109. package/src/kit/viewport-actions.ts +8 -50
  110. package/src/kit/viewport-authoring-policy.ts +5 -52
  111. package/src/kit/viewport-commands.ts +28 -57
  112. package/src/kit/viewport-hotkeys.ts +29 -31
  113. package/src/kit/viewport-status-facet.ts +4 -5
  114. package/src/loader.ts +8 -71
  115. package/src/object3d-contributions.ts +18 -34
  116. package/src/viewport/clip-planes.ts +4 -32
  117. package/src/viewport/content-bounds.ts +13 -142
  118. package/src/viewport/editor-layers.ts +4 -26
  119. package/src/viewport/environment.ts +1 -1
  120. package/src/viewport/preview-renderer.ts +7 -45
  121. package/src/viewport/renderer-ownership.ts +14 -62
  122. package/src/viewport-api.ts +14 -33
  123. package/src/viewport-door.ts +16 -86
  124. package/contributions/three-integration.service.ts +0 -13
  125. package/src/adapter/hierarchy-marks.ts +0 -156
  126. package/src/adapter/ingest/scene-capture.ts +0 -843
  127. package/src/adapter/ingest/structural-ids.ts +0 -139
  128. package/src/adapter/ingest/visible-capture-window.ts +0 -302
  129. package/src/adapter/renderer-config.ts +0 -115
  130. package/src/asset-parse-error.ts +0 -29
  131. package/src/host-hierarchy-objects.ts +0 -31
  132. package/src/kit/authoring/design-time-settle.ts +0 -343
  133. package/src/kit/camera-authoring.ts +0 -175
@@ -0,0 +1,41 @@
1
+ import { editorHost } from '@volter/sdk/host';
2
+ import type { CollaborationPresence } from '@volter/sdk/collaboration-types';
3
+
4
+ let presence: CollaborationPresence = {
5
+ document: null,
6
+ file: null,
7
+ oid: null,
8
+ selection: [],
9
+ camera: null,
10
+ gesture: null,
11
+ };
12
+ let sent = '';
13
+ let scheduled = false;
14
+ let inFlight = false;
15
+
16
+ const PRESENCE_INTERVAL_MS = 100;
17
+
18
+ function schedulePresence(): void {
19
+ if (scheduled || inFlight) return;
20
+ scheduled = true;
21
+ globalThis.setTimeout(() => {
22
+ scheduled = false;
23
+ const serialized = JSON.stringify(presence);
24
+ if (serialized === sent) return;
25
+ sent = serialized;
26
+ inFlight = true;
27
+ void editorHost().collaboration.updatePresence(presence)
28
+ .catch(() => {
29
+ sent = '';
30
+ })
31
+ .finally(() => {
32
+ inFlight = false;
33
+ if (JSON.stringify(presence) !== sent) schedulePresence();
34
+ });
35
+ }, PRESENCE_INTERVAL_MS);
36
+ }
37
+
38
+ export function reportCollaborationPresence(patch: Partial<CollaborationPresence>): void {
39
+ presence = { ...presence, ...patch };
40
+ schedulePresence();
41
+ }
@@ -1,6 +1,6 @@
1
1
  import { EditorSurface, Inline, Text } from '@volter/sdk/widgets';
2
2
  import { useEffect, useRef, useState } from 'react';
3
- import { useEditorStats } from '@volter/sdk/kit/editor-runtime';
3
+ import { useEditorStats } from '@volter/sdk/editor-runtime';
4
4
 
5
5
  function fmt(n: number): string {
6
6
  return n.toFixed(2);
@@ -33,14 +33,12 @@ export function CameraInfo() {
33
33
  }, [stats]);
34
34
 
35
35
  return (
36
- // P6-U6 (owner taste decision 3: HUD overlays → real islands): under
37
- // islands chrome the pill wears the shared glass-island material
38
- // (compact tier; pointer-events stays none — it is a readout, not a
39
- // control). Bars chrome keeps the pre-U6 overlay surface + blur.
36
+ // HUD overlays use the compact island material under islands chrome and the overlay surface
37
+ // under bars chrome. The readout keeps pointer events disabled.
40
38
  <EditorSurface
41
39
  variant="overlay"
42
40
  border
43
- className="volter-camera-info volter-chrome-island volter-glass-island"
41
+ className="volter-camera-info volter-chrome-island"
44
42
  data-island-scale="compact"
45
43
  >
46
44
  <Inline gap={8} align="center" style={{ whiteSpace: 'nowrap' }}>
@@ -1,49 +1,22 @@
1
1
  import type * as THREE from 'three';
2
2
  import { Object3DSnapshotDocument } from './asset-viewers/EntityModelDocument';
3
3
 
4
- /**
5
- * Inspector-sized projection of the canonical Asset Lab viewport, over a live
6
- * `Object3D` the caller already has in hand.
7
- *
8
- * It takes the OBJECT, not an id to look one up by: a subject's preview is
9
- * composed only when the thing is genuinely previewable
10
- * (`inspection/compose.ts`), and the object the composer proved exists is the
11
- * one rendered here. A scene node and a part inside an asset document are
12
- * therefore the same case — the part is not in the shell's object map, and
13
- * with an id this component could only have shown a "no longer available"
14
- * alert for a thing that is right there on screen.
15
- *
16
- * Every host passes `fill`: the section body's square box, the compact card's
17
- * round disc, and the minimized card's round thumb all own their own geometry
18
- * and this component just fills it.
19
- */
4
+ /** Fill the Inspector media slot with a preview of its already-resolved Object3D. The caller
5
+ * supplies the document identity, source object and display name. */
20
6
  export function InspectorObjectPreview({
21
7
  previewKey,
22
8
  object,
23
9
  displayName,
24
- fill = false,
25
10
  }: {
26
11
  /** Stable identity for the isolated preview document. */
27
12
  previewKey: string;
28
13
  object: THREE.Object3D;
29
14
  displayName: string;
30
- fill?: boolean;
31
15
  }) {
32
16
  return (
33
17
  <div
34
18
  data-testid="inspector-object-preview"
35
- style={
36
- fill
37
- ? { position: 'absolute', inset: 0, overflow: 'hidden' }
38
- : {
39
- width: '100%',
40
- height: 220,
41
- minHeight: 220,
42
- borderBottom: '1px solid var(--volter-structural-divider)',
43
- overflow: 'hidden',
44
- position: 'relative',
45
- }
46
- }
19
+ style={{ position: 'absolute', inset: 0, overflow: 'hidden' }}
47
20
  >
48
21
  <Object3DSnapshotDocument
49
22
  documentId={`inspector-preview:${previewKey}`}
@@ -0,0 +1,75 @@
1
+ /* Imported by `Object3DDocumentToolbar.tsx`, so this sheet loads with the toolbar's module, after the
2
+ * host's theme.css, and a declaration here wins over one theme.css makes at the same specificity. The
3
+ * toolbar's border and its labelled control's gap are therefore left to `.volter-toolbar` and
4
+ * `.volter-btn`. `padding: 0` and `min-height: auto` below differ from `.volter-toolbar`'s, and compute
5
+ * the recipe's values only because `.volter-toolbar[data-compact="true"]` (theme.css) outranks them:
6
+ * both toolbars are `EditorToolbar compact`, which emits that attribute. */
7
+ /* THE 3D DOCUMENT'S CONTROLS LIVE IN THE HOST'S HEADER STRIP now
8
+ (`WorkspaceDocumentSurface`), not floating over the stage: one row, the
9
+ view group first and the preview group pushed to the trailing edge. The
10
+ strip supplies the divider and height; these groups are plain in-flow
11
+ toolbars inside it. */
12
+ .volter-model-document-header {
13
+ display: flex;
14
+ align-items: center;
15
+ gap: var(--volter-space-2);
16
+ flex: 1 1 auto;
17
+ min-width: 0;
18
+ }
19
+
20
+ .volter-model-viewport-toolbar {
21
+ position: static;
22
+ min-height: auto;
23
+ padding: 0;
24
+ background: transparent;
25
+ box-shadow: none;
26
+ }
27
+
28
+ .volter-model-viewport-toolbar--preview {
29
+ margin-left: auto;
30
+ }
31
+
32
+ .volter-model-viewport-toolbar > * {
33
+ flex: 0 0 auto;
34
+ }
35
+
36
+ .volter-model-toolbar-labelled-control {
37
+ display: inline-flex;
38
+ align-items: center;
39
+ white-space: nowrap;
40
+ }
41
+
42
+ .volter-model-capture-preview {
43
+ position: absolute;
44
+ z-index: var(--volter-z-sticky);
45
+ top: calc(var(--volter-space-3) + var(--volter-control-default-height) + var(--volter-space-2));
46
+ right: var(--volter-space-3);
47
+ width: min(420px, calc(100% - 2 * var(--volter-space-3)));
48
+ padding: var(--volter-space-3);
49
+ }
50
+
51
+ .volter-model-capture-preview > div {
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: space-between;
55
+ margin-bottom: var(--volter-space-3);
56
+ }
57
+
58
+ .volter-model-capture-preview img {
59
+ display: block;
60
+ width: 100%;
61
+ }
62
+
63
+ .volter-model-toolbar-error {
64
+ position: absolute;
65
+ z-index: var(--volter-z-sticky);
66
+ right: var(--volter-space-3);
67
+ bottom: var(--volter-space-3);
68
+ max-width: 420px;
69
+ }
70
+
71
+ @media (width <= 720px) {
72
+ .volter-model-toolbar-labelled-control > span {
73
+ display: none;
74
+ }
75
+ }
@@ -36,18 +36,12 @@ import {
36
36
  object3DDocumentSessionsVersion,
37
37
  subscribeObject3DDocumentSessions,
38
38
  } from '../authoring/object3d-document-session-registry';
39
- import {
40
- contributedChromeVersion,
41
- contributedHeaderItems,
42
- subscribeContributedChrome,
43
- } from '@volter/sdk/kit/chrome-registry';
44
- import type { HelperVisibility } from '@volter/sdk/kit/shell-store';
45
- import { object3DDocumentWritePolicy } from '@volter/sdk/kit/object3d-document-write-policy';
46
- import { stageStore, stageStoresVersion, subscribeStageStores } from '@volter/sdk/kit/stage-store-registry';
39
+ import type { HelperVisibility } from '@volter/sdk/shell-store';
40
+ import { assetDocumentWritePolicy } from '@volter/sdk/asset-document-write-policy';
41
+ import { stageStore, stageStoresVersion, subscribeStageStores } from '@volter/sdk/stage-store-registry';
47
42
  import { viewportStageHelperKinds } from '../../viewport-door';
48
43
  import {
49
44
  type PresentationLayer,
50
- applyViewPreset,
51
45
  DOCUMENT_STUDIO_PRESET,
52
46
  setViewGridVisible,
53
47
  viewGridVisible,
@@ -55,13 +49,12 @@ import {
55
49
  stageLightsPerMode,
56
50
  startingPresentation,
57
51
  studioPresets,
58
- viewPresets,
59
52
  viewPresentationBinding,
60
53
  subscribeViewportPresentation,
61
54
  viewPresentation,
62
55
  viewportPresentationVersion,
63
- } from '@volter/sdk/kit/viewport-presentation';
64
- import { ViewportOverlaysGlyph, ViewportOverlaysMenu } from '@volter/sdk/kit/components/ViewportOverlaysMenu';
56
+ } from '@volter/sdk/viewport-presentation';
57
+ import { ViewportOverlaysGlyph, ViewportOverlaysMenu } from './ViewportOverlaysMenu';
65
58
  import {
66
59
  type ViewportDisplayModeChoice,
67
60
  ViewportDisplayModeMenu,
@@ -69,6 +62,7 @@ import {
69
62
  viewportShadingModes,
70
63
  viewportShadingSegments,
71
64
  } from './ViewportShadingMenu';
65
+ import './Object3DDocumentToolbar.css';
72
66
 
73
67
  const subscribeToNothing = () => () => undefined;
74
68
  const noSessionVersion = () => 0;
@@ -80,11 +74,9 @@ const noSessionVersion = () => 0;
80
74
  * (`View3DOverlay.show_bones`, `makesrna/intern/rna_space.cc:5125-5129`, drawn
81
75
  * by `scripts/startup/bl_ui/space_view3d.py:7161` with `text="Bones"`), and it
82
76
  * is Blender's noun that is used here rather than the Helpers vocabulary's
83
- * `Skeletons`, because this menu IS that popover — the comment on the eye below
84
- * says so. `Weights` has no Blender checkbox at all: Blender reaches weight
77
+ * `Skeletons`, because this menu uses Blender's overlay vocabulary. `Weights` has no Blender checkbox at all: Blender reaches weight
85
78
  * colours through Weight Paint MODE, and an inspection surface has no brushes
86
- * to enter one, so it is a display toggle here (WORK.md §Blender in the tab is
87
- * Blender, "Inspection parity", I4).
79
+ * to enter one, so it is a display toggle here.
88
80
  *
89
81
  * A ROW STANDS ONLY WHERE THE DOCUMENT PUT A HELPER OF THAT KIND on this stage
90
82
  * (`viewportStageHelperKinds`) — the same rule the Outliner's restriction
@@ -114,19 +106,13 @@ export function Object3DDocumentToolbar({
114
106
  object3DDocumentSessionsVersion,
115
107
  );
116
108
  const session = object3DDocumentSession(documentId);
117
- useSyncExternalStore(
118
- subscribeContributedChrome,
119
- contributedChromeVersion,
120
- contributedChromeVersion,
121
- );
122
109
  useSyncExternalStore(
123
110
  session?.subscribe ?? subscribeToNothing,
124
111
  session?.getSnapshot ?? noSessionVersion,
125
112
  session?.getSnapshot ?? noSessionVersion,
126
113
  );
127
114
  // THIS STAGE's own store and its own helpers: the shared panels read
128
- // whichever store the FOCUSED document's stage runs on (ARCHITECTURE-CORE
129
- // §One stage), and a toolbar is a control over ITS document, never over the
115
+ // whichever store the FOCUSED document's stage runs on , and a toolbar is a control over ITS document, never over the
130
116
  // world root's.
131
117
  useSyncExternalStore(subscribeStageStores, stageStoresVersion, stageStoresVersion);
132
118
  const stage = stageStore(documentId);
@@ -206,8 +192,8 @@ export function Object3DDocumentToolbar({
206
192
  try {
207
193
  // The shell owns where a framing is RECORDED (`.volter/thumbnails.json`,
208
194
  // written through the project-file route); this surface only knows the
209
- // asset and the pose. See `object3d-document-write-policy.ts`.
210
- await object3DDocumentWritePolicy().saveThumbnailFraming(
195
+ // asset and the pose. See `asset-document-write-policy.ts`.
196
+ await assetDocumentWritePolicy().saveThumbnailFraming(
211
197
  assetPath,
212
198
  reset ? undefined : session.cameraPose(),
213
199
  );
@@ -237,13 +223,7 @@ export function Object3DDocumentToolbar({
237
223
  label="Viewport view controls"
238
224
  className="volter-model-viewport-toolbar volter-model-viewport-toolbar--view"
239
225
  >
240
- {/* THE BUTTON SAYS WHAT IT DOES. `session.frame()` frames the
241
- selection when there is one and the whole subject when there is
242
- not, but it was labelled "Frame selection" unconditionally — so a
243
- tester hunting a fit-the-board control on the 3D board reported
244
- finding none, then discovered this button does exactly that
245
- (runhuman passes 87 and 89). One control, named for the job it is
246
- about to do. */}
226
+ {/* Name the framing control for the selection, or the whole subject when selection is empty. */}
247
227
  <Tooltip text={frameLabel}>
248
228
  <IconButton aria-label={frameLabel} onClick={() => session.frame()}>
249
229
  <EditorIcon icon={faExpand} size="md" />
@@ -255,11 +235,7 @@ export function Object3DDocumentToolbar({
255
235
  ref={viewRef}
256
236
  type="button"
257
237
  size="compact"
258
- // A dropdown TRIGGER is a widget, not a label: Blender's
259
- // `Object Mode v` in the same row of chrome is a 20px well
260
- // (#272727 in a 1px #3c3c3c border) while its plain menu words
261
- // — View/Select/Add — carry no chrome at all. `ghost` said this
262
- // was the second kind; it is the first.
238
+ // Use the select-like trigger variant for the mode dropdown.
263
239
  variant="secondary"
264
240
  aria-label={`Camera view: ${presentation.projection}`}
265
241
  aria-haspopup="menu"
@@ -329,12 +305,6 @@ export function Object3DDocumentToolbar({
329
305
  </IconButton>
330
306
  </Tooltip>
331
307
  ) : null}
332
- {/* A package's own controls on this document (`@volter/sdk/chrome`,
333
- `placement: 'object3d-document'`): handed the document's id; the
334
- contribution resolves its subject through its own integration. */}
335
- {contributedHeaderItems('object3d-document').map(({ id, Component }) => (
336
- <Component key={id} documentId={documentId} />
337
- ))}
338
308
  </EditorToolbar>
339
309
  <EditorToolbar
340
310
  compact
@@ -344,46 +314,15 @@ export function Object3DDocumentToolbar({
344
314
  {/* THE DISPLAY CONTROLS SIT AT THE TRAILING EDGE, which is where
345
315
  Blender's are: its 3D View header packs Visibility, Gizmo,
346
316
  Overlays, X-Ray and Shading hard against the region's right border
347
- and leaves the middle empty. Ours used to clump them after the
348
- view controls with three hundred pixels of bare header to their
349
- right and this group's `...` alone at the edge. */}
350
- {/* BLENDER'S SHADING CONTROL, one widget: the segments it has, and the
351
- chevron's popover carrying every other mode AND the lighting rows
352
- that used to be a second `Studio v` dropdown further along the row.
353
- Blender's own shading popover is where Lighting lives (Studio /
354
- MatCap / Flat), so the two controls were always one control drawn
355
- twice — measured in `modeling-edit-none.png`, its header's trailing
356
- cluster is five icon groups and no words at all. */}
317
+ and leaves the middle empty, and so does ours. */}
318
+ {/* The shading segments and popover form one widget; the popover contains the remaining modes and lighting settings. */}
357
319
  <ViewportDisplayModeMenu
358
320
  mode={presentation.mode}
359
321
  onChange={(mode) => session.setMode(mode)}
360
322
  choices={modes}
361
323
  segments={perMode ? perModeShadingSegments(declaredModes) : viewportShadingSegments}
362
324
  >
363
- {/* A NAMED VIEW, first: a whole presentation (a target engine's default viewport —
364
- its light, backdrop, overlays and tool) that the packages here contribute
365
- (`*.view.ts`). Choosing one replaces the view's own choices; the rows below then
366
- adjust it. Shown only where a package contributes one. */}
367
- {viewPresets().length > 0 ? (
368
- <label>
369
- <span>View</span>
370
- <Select
371
- aria-label="View preset"
372
- value=""
373
- onChange={(event) => {
374
- if (event.target.value) applyViewPreset(documentId, event.target.value);
375
- }}
376
- >
377
- <option value="">Choose…</option>
378
- {viewPresets().map((preset) => (
379
- <option key={preset.id} value={preset.id}>
380
- {preset.title}
381
- </option>
382
- ))}
383
- </Select>
384
- </label>
385
- ) : null}
386
- {/* LIGHTING AND EXPOSURE ARE THE VIEW'S PRESENTATION (`kit/viewport-presentation`):
325
+ {/* LIGHTING AND EXPOSURE ARE THE VIEW'S PRESENTATION (`@volter/sdk/viewport-presentation`):
387
326
  a studio preset, or the scene's own lights, and the tone's exposure. */}
388
327
  <label>
389
328
  <span>Lighting</span>
@@ -445,12 +384,7 @@ export function Object3DDocumentToolbar({
445
384
  </IconButton>
446
385
  </Tooltip>
447
386
  )}
448
- {/* THE EYE WAS THE OVERLAYS CONTROL ALL ALONG — measured by what it
449
- toggles, which is Grid and Bounds, exactly Blender's Show Overlays
450
- subject. It takes Blender's mark, and Blender's split toggle+chevron:
451
- the toggle is the MASTER flag, this stage's own helpers switch
452
- (`StageHost` takes the grid, axis lines and selection marks down
453
- with it), and each overlay below keeps its own choice under it. */}
387
+ {/* The master helpers toggle controls overlays; individual overlay choices remain stored underneath it. */}
454
388
  <ViewportOverlaysMenu
455
389
  glyph={<ViewportOverlaysGlyph />}
456
390
  {...(stage === null
@@ -460,7 +394,7 @@ export function Object3DDocumentToolbar({
460
394
  {
461
395
  id: 'grid',
462
396
  label: 'Grid',
463
- // The VIEW's grid switch (`kit/viewport-presentation`), one per view.
397
+ // The VIEW's grid switch (`@volter/sdk/viewport-presentation`), one per view.
464
398
  enabled: viewGridVisible(documentId),
465
399
  onToggle: () => setViewGridVisible(documentId, !viewGridVisible(documentId)),
466
400
  },
@@ -1,18 +1,15 @@
1
1
  /**
2
2
  * Lazy document boundary for the shared Object3D authoring surface.
3
3
  *
4
- * Registries and open-document planners need to be able to name this surface
5
- * during editor boot, but the implementation carries Asset Lab's animation,
6
- * capture, model-inspection, and source-authoring graph. Importing that graph
7
- * merely to register unopened document kinds put it on Scene's cold path.
8
- * Keep the public component shape here and evaluate the implementation only
9
- * when React actually renders an Object3D document.
4
+ * Registries and open-document planners can name this surface without importing its animation,
5
+ * capture, model-inspection and source-authoring implementation. React loads that implementation
6
+ * when it renders an Object3D document.
10
7
  */
11
8
 
12
9
  import { lazy, Suspense, useEffect } from 'react';
13
- import { announceDocumentStage } from '@volter/sdk/kit/document-viewports';
10
+ import { announceDocumentStage } from '@volter/sdk/document-viewports';
14
11
  import type { Object3DDocumentViewportProps } from './StageHost';
15
- import { OBJECT3D_SURFACE_BUILDING, ViewportSurfaceStatus } from '@volter/sdk/kit/viewport-surface-status';
12
+ import { Progress } from '@volter/sdk/progress';
16
13
 
17
14
  const LazyObject3DDocumentViewport = lazy(async () => {
18
15
  const module = await import('./StageHost');
@@ -29,9 +26,7 @@ export function Object3DDocumentViewport(props: Object3DDocumentViewportProps) {
29
26
  return (
30
27
  <Suspense
31
28
  fallback={
32
- <ViewportSurfaceStatus testId="object3d-document-loading">
33
- {OBJECT3D_SURFACE_BUILDING}
34
- </ViewportSurfaceStatus>
29
+ props.chromeless ? null : <Progress documentId={props.documentId} title="Preparing the viewport" />
35
30
  }
36
31
  >
37
32
  <LazyObject3DDocumentViewport {...props} />
@@ -0,0 +1,40 @@
1
+ /* The stage's studio floor and the asset workspace's box, applied by `StageHost.tsx`. They read only the
2
+ design system's shared tokens (`--volter-*`). */
3
+ .volter-object3d-studio-stage {
4
+ /* Canonical Asset Lab presentation floor: a cool key-light bloom behind
5
+ the subject, a faint counter-light low in frame, and a restrained edge
6
+ vignette. The Three canvas is alpha-backed, so the same stage appears in
7
+ the full Asset Editor document and the inspector's preview section. */
8
+ background-color: var(--volter-object3d-stage-base, #1d232b);
9
+ background-image:
10
+ radial-gradient(
11
+ 90% 72% at 50% 36%,
12
+ color-mix(in srgb, var(--volter-accent) 16%, rgb(122 148 177 / 22%)),
13
+ transparent 62%
14
+ ),
15
+ radial-gradient(70% 42% at 50% 92%, rgb(124 151 181 / 10%), transparent 72%),
16
+ radial-gradient(140% 112% at 50% 44%, transparent 42%, rgb(0 0 0 / 58%) 100%),
17
+ linear-gradient(
18
+ 180deg,
19
+ var(--volter-object3d-stage-top, color-mix(in srgb, var(--volter-surface-raised) 82%, #394451)) 0%,
20
+ var(--volter-object3d-stage-bottom, color-mix(in srgb, var(--volter-surface-shell) 76%, #11151b))
21
+ 100%
22
+ );
23
+ }
24
+
25
+ /* Inspector previews sit over the authored world, so retain some of that
26
+ context beneath the studio lighting. The full Asset Lab remains opaque. */
27
+ .volter-object3d-studio-stage[data-volter-chromeless="true"] {
28
+ --volter-object3d-stage-base: rgb(29 35 43 / 34%);
29
+ --volter-object3d-stage-top: rgb(57 68 81 / 40%);
30
+ --volter-object3d-stage-bottom: rgb(17 21 27 / 52%);
31
+ }
32
+
33
+ .volter-object3d-asset-workspace {
34
+ position: absolute;
35
+ inset: 0;
36
+ display: flex;
37
+ min-width: 0;
38
+ min-height: 0;
39
+ flex-direction: column;
40
+ }