@volter/editor-sdk 0.5.57
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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +19 -0
- package/package.json +90 -0
- package/src/account.ts +210 -0
- package/src/chrome.ts +83 -0
- package/src/client.ts +1547 -0
- package/src/commands.ts +66 -0
- package/src/contributions.ts +985 -0
- package/src/document-probe.ts +237 -0
- package/src/editor-view.ts +220 -0
- package/src/extension.ts +40 -0
- package/src/generations.ts +178 -0
- package/src/host.ts +1167 -0
- package/src/http-transport.browser.ts +14 -0
- package/src/http-transport.node.ts +19 -0
- package/src/index.ts +128 -0
- package/src/layout-arrangements.ts +5 -0
- package/src/layouts.tsx +108 -0
- package/src/looks.ts +14 -0
- package/src/project/output-roots.ts +73 -0
- package/src/project/tab-census.ts +149 -0
- package/src/project-tool-catalog.ts +96 -0
- package/src/selection.tsx +108 -0
- package/src/services.ts +18 -0
- package/src/session/build-report.ts +19 -0
- package/src/session/collaboration-types.ts +262 -0
- package/src/session/command-table.ts +333 -0
- package/src/session/discovery.ts +90 -0
- package/src/session/editor-brand.ts +73 -0
- package/src/session/editor-compatibility.ts +248 -0
- package/src/session/editor-control-lifecycle.ts +68 -0
- package/src/session/editor-control-protocol.ts +5 -0
- package/src/session/entrypoint-selection-readers.ts +66 -0
- package/src/session/entrypoint-selection-source.ts +120 -0
- package/src/session/game-css-scope.ts +30 -0
- package/src/session/product-create.ts +24 -0
- package/src/session/product-locator.ts +389 -0
- package/src/session/project-module-url.ts +245 -0
- package/src/session/registry-format.ts +203 -0
- package/src/session/relative-path-guard.ts +56 -0
- package/src/session/source-glob.ts +15 -0
- package/src/session/tool-contribution-convention.ts +116 -0
- package/src/session/workbench-locator.ts +650 -0
- package/src/session.ts +41 -0
- package/src/share.ts +160 -0
- package/src/tools/errors.ts +91 -0
- package/src/tools/provider-execution.ts +70 -0
- package/src/tools/registry.ts +341 -0
- package/src/tools/types.ts +159 -0
- package/src/transport.ts +97 -0
- package/src/types.ts +1581 -0
- package/src/views.ts +164 -0
- package/src/widgets/design-system.ts +93 -0
- package/src/widgets/editor-appearance.ts +149 -0
- package/src/widgets/editor-material.ts +83 -0
- package/src/widgets/icon-set-registry.ts +105 -0
- package/src/widgets/index.ts +71 -0
- package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
- package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
- package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
- package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
- package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
- package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
- package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
- package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
- package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
- package/src/widgets/inspector-widgets/color-utils.ts +201 -0
- package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
- package/src/widgets/inspector-widgets/index.ts +24 -0
- package/src/widgets/inspector-widgets/shared.tsx +140 -0
- package/src/widgets/interactive-edit-scope.ts +33 -0
- package/src/widgets/patterns/Dialog.tsx +129 -0
- package/src/widgets/patterns/Fields.tsx +44 -0
- package/src/widgets/patterns/List.tsx +25 -0
- package/src/widgets/patterns/StateSurface.tsx +40 -0
- package/src/widgets/patterns/Surfaces.tsx +122 -0
- package/src/widgets/patterns/Tabs.tsx +80 -0
- package/src/widgets/patterns/Toolbar.tsx +72 -0
- package/src/widgets/patterns/Tree.tsx +72 -0
- package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
- package/src/widgets/primitives/Button.tsx +62 -0
- package/src/widgets/primitives/ColorInput.tsx +78 -0
- package/src/widgets/primitives/DraftTextInput.tsx +63 -0
- package/src/widgets/primitives/EditorIcon.tsx +157 -0
- package/src/widgets/primitives/FormControls.tsx +88 -0
- package/src/widgets/primitives/HoverPreview.tsx +96 -0
- package/src/widgets/primitives/JsonInput.tsx +113 -0
- package/src/widgets/primitives/Layout.tsx +100 -0
- package/src/widgets/primitives/Menu.tsx +140 -0
- package/src/widgets/primitives/NumberInput.tsx +169 -0
- package/src/widgets/primitives/Panel.tsx +80 -0
- package/src/widgets/primitives/SectionHeader.tsx +77 -0
- package/src/widgets/primitives/Text.tsx +54 -0
- package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
- package/src/widgets/primitives/Tooltip.tsx +204 -0
- package/src/widgets/primitives/Vec3Input.tsx +70 -0
- package/src/widgets/primitives/banner-tones.ts +32 -0
- package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
- package/src/widgets/primitives/editor-icons.ts +245 -0
- package/src/widgets/primitives/panel-header-styles.ts +42 -0
- package/src/widgets/theme.ts +2633 -0
- package/src/widgets/z-index.ts +25 -0
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props for ordinary React components registered at editor contribution
|
|
3
|
+
* points. There is deliberately no extension class or lifecycle: package
|
|
4
|
+
* metadata names a module and the editor renders its default export.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { AuthoringAdapter } from '@volter/editor-project/adapter';
|
|
8
|
+
import type { GenerationAccountProjection } from '@volter/editor-sdk/account';
|
|
9
|
+
import type { GenerationJob } from '@volter/editor-sdk/generations';
|
|
10
|
+
import type {
|
|
11
|
+
AnimationClip,
|
|
12
|
+
Camera,
|
|
13
|
+
ColorRepresentation,
|
|
14
|
+
Group,
|
|
15
|
+
Intersection,
|
|
16
|
+
Object3D,
|
|
17
|
+
Ray,
|
|
18
|
+
Scene,
|
|
19
|
+
ToneMapping,
|
|
20
|
+
WebGLRenderer,
|
|
21
|
+
} from 'three';
|
|
22
|
+
import type { EditorClient } from './client.js';
|
|
23
|
+
import type { ProjectToolCatalogEntry } from './types.js';
|
|
24
|
+
|
|
25
|
+
/** Stable, format-neutral selection data available to inspector contributions. */
|
|
26
|
+
export interface ToolContributionNode {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly label: string;
|
|
29
|
+
readonly role?: string;
|
|
30
|
+
readonly secondaryLabel?: string;
|
|
31
|
+
readonly kind: string;
|
|
32
|
+
readonly parentId: string | null;
|
|
33
|
+
readonly childIds: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* One disposable native Three.js build supplied to the editor's generic
|
|
38
|
+
* preview surface. This is a host-lifetime boundary, not a model format or
|
|
39
|
+
* procedural-asset base class: project code still constructs ordinary
|
|
40
|
+
* Object3D and AnimationClip instances directly.
|
|
41
|
+
*/
|
|
42
|
+
export interface ToolObject3DPreviewSource {
|
|
43
|
+
readonly root: Object3D;
|
|
44
|
+
/**
|
|
45
|
+
* Optional authored roots for the document hierarchy when `root` is a
|
|
46
|
+
* presentation/lifetime container rather than authored content itself.
|
|
47
|
+
* Framing, ticking and disposal still own `root`; this list only scopes the
|
|
48
|
+
* shared Hierarchy. Omitted means `root`, preserving the native tree.
|
|
49
|
+
*/
|
|
50
|
+
readonly hierarchyRoots?: readonly Object3D[];
|
|
51
|
+
readonly animations?: readonly AnimationClip[];
|
|
52
|
+
/**
|
|
53
|
+
* The source's OWN tick, and the host drives it EXACTLY ONCE per mount — for
|
|
54
|
+
* the bounded design-time settle that gives a physics-owned body the pose it
|
|
55
|
+
* actually rests in (`authoring/design-time-settle.ts`). It is never a frame
|
|
56
|
+
* loop: content time does not advance on a design-time surface, so a source
|
|
57
|
+
* that animates from here shows its settled pose and then holds it. Play is
|
|
58
|
+
* where a world runs.
|
|
59
|
+
*/
|
|
60
|
+
update?(deltaSeconds: number): void;
|
|
61
|
+
dispose(): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Direct native handles for optional project-owned preview presentation. */
|
|
65
|
+
export interface ToolObject3DPreviewContext {
|
|
66
|
+
readonly scene: Scene;
|
|
67
|
+
readonly camera: Camera;
|
|
68
|
+
readonly renderer: WebGLRenderer;
|
|
69
|
+
/**
|
|
70
|
+
* A cloned Object3D graph, never the source root itself. Geometry,
|
|
71
|
+
* materials, and textures are shared read-only with the disposable source;
|
|
72
|
+
* clone a resource before applying preview-only mutations to it.
|
|
73
|
+
*/
|
|
74
|
+
readonly model: Object3D;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ToolObject3DPreviewExtension {
|
|
78
|
+
/** When present, replaces the viewer's default render call for this frame. */
|
|
79
|
+
render?(deltaSeconds: number): void;
|
|
80
|
+
/** Resize project-owned composers and render targets with the host viewport. */
|
|
81
|
+
resize?(width: number, height: number, pixelRatio: number): void;
|
|
82
|
+
/** Release every light, ground mesh, pass, target, and listener added here. */
|
|
83
|
+
dispose(): void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ToolObject3DPreviewProps {
|
|
87
|
+
/** Build a fresh source. The host disposes it after its preview snapshot. */
|
|
88
|
+
readonly build: () => ToolObject3DPreviewSource;
|
|
89
|
+
readonly displayName?: string;
|
|
90
|
+
readonly height?: number | string;
|
|
91
|
+
readonly background?: ColorRepresentation;
|
|
92
|
+
readonly showSkeleton?: boolean;
|
|
93
|
+
readonly cameraDirection?: readonly [number, number, number];
|
|
94
|
+
/** Host diagnostic grid. Defaults off with setupPreview, on otherwise. */
|
|
95
|
+
readonly showGrid?: boolean;
|
|
96
|
+
/** Host studio lights. Defaults off with setupPreview, on otherwise. */
|
|
97
|
+
readonly useDefaultLighting?: boolean;
|
|
98
|
+
/** Explicit renderer exposure. Custom presentation otherwise owns it. */
|
|
99
|
+
readonly exposure?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Optional raw Three.js presentation hook for project-specific lights,
|
|
102
|
+
* ground, or postprocessing. Host grid/studio lights default off when this
|
|
103
|
+
* is present. It is preview chrome and is never exported.
|
|
104
|
+
*/
|
|
105
|
+
readonly setupPreview?: (
|
|
106
|
+
context: ToolObject3DPreviewContext,
|
|
107
|
+
) => ToolObject3DPreviewExtension | undefined;
|
|
108
|
+
readonly active?: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Per-document overrides of the editor's standard viewport dressing — explicit
|
|
113
|
+
* opt-outs and opt-ins, never re-implementations. Omitted means the standard
|
|
114
|
+
* look (`standard-viewport-dressing.ts`).
|
|
115
|
+
*/
|
|
116
|
+
export interface ToolViewportDressing {
|
|
117
|
+
/** `false` skips the RoomEnvironment IBL — and, with it, the stage's
|
|
118
|
+
* environment INTENSITY: a document that opts out of image-based lighting
|
|
119
|
+
* is lit by its own lights alone. Default on. */
|
|
120
|
+
readonly environment?: boolean;
|
|
121
|
+
/** `false` skips the gradient backdrop. Default on (an explicit flat
|
|
122
|
+
* `background` color or the asset studio stage also skips it). */
|
|
123
|
+
readonly background?: boolean;
|
|
124
|
+
/** `false` skips the key light — and the editor's own design-time light
|
|
125
|
+
* rig with it, because both answer the same question and a document that
|
|
126
|
+
* says "I light myself" is answering it. Default on; a source that
|
|
127
|
+
* authors its own lights skips the key regardless — authored lighting
|
|
128
|
+
* always wins. */
|
|
129
|
+
readonly keyLight?: boolean;
|
|
130
|
+
/** `true` adds the ground grid (content standing on y=0). Default off. */
|
|
131
|
+
readonly grid?: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* An object the STAGE parents to its own camera, so everything under it
|
|
134
|
+
* turns with the view instead of standing still in the world.
|
|
135
|
+
*
|
|
136
|
+
* It exists for VIEW-SPACE LIGHTING — Blender's Solid mode has no world
|
|
137
|
+
* light at all, only four studio lights stated in view space, which is why
|
|
138
|
+
* its shading reads the same however the model is orbited. A directional
|
|
139
|
+
* light here states its direction in the camera's own space (three's camera
|
|
140
|
+
* looks down −Z) and keeps its `target` under this object at the origin.
|
|
141
|
+
*
|
|
142
|
+
* The stage owns the parenting AND the teardown: a document hands the
|
|
143
|
+
* object over and its own `dispose` never touches the camera.
|
|
144
|
+
*/
|
|
145
|
+
readonly viewLocked?: Object3D;
|
|
146
|
+
/**
|
|
147
|
+
* THE STAGE'S VIEW TRANSFORM — three's `WebGLRenderer.toneMapping`. Omitted
|
|
148
|
+
* means the editor's own, ACES.
|
|
149
|
+
*
|
|
150
|
+
* A document whose source system states its own transform passes that one
|
|
151
|
+
* instead: Blender's factory scene is AgX (`view_settings.view_transform`),
|
|
152
|
+
* and this product's Blender RENDER path already photographs through
|
|
153
|
+
* `AgXToneMapping` — so before this existed the viewport was the one
|
|
154
|
+
* surface in the chain running a different curve from the thing it frames.
|
|
155
|
+
* It is not a look preference: the curve decides how a shading range lands
|
|
156
|
+
* on screen, and two curves over one radiance are two different pictures.
|
|
157
|
+
*/
|
|
158
|
+
readonly toneMapping?: ToneMapping;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* A native source graph hosted by the editor's normal Three.js authoring
|
|
163
|
+
* viewport, hierarchy, selection, and Inspector. Unlike Object3DPreview this
|
|
164
|
+
* surface exposes the live graph as the active workspace document context.
|
|
165
|
+
*/
|
|
166
|
+
export interface ToolObject3DAuthoringProps {
|
|
167
|
+
readonly documentId: string;
|
|
168
|
+
readonly sourcePath: string;
|
|
169
|
+
/**
|
|
170
|
+
* Produce the graph to author. Called ONCE PER ACTIVATION, not once per
|
|
171
|
+
* document: the host tears its scene down whenever the document goes inactive
|
|
172
|
+
* (an ordinary tab switch) and calls this again on the way back, disposing
|
|
173
|
+
* whatever the previous call returned. So a caller must pick one of two
|
|
174
|
+
* shapes, and there is no third:
|
|
175
|
+
*
|
|
176
|
+
* - a FACTORY — build a fresh graph every call, and let the returned
|
|
177
|
+
* `dispose` free it (what the model/entity asset documents do); or
|
|
178
|
+
* - an OWNED graph — return the same root every call with an EMPTY `dispose`,
|
|
179
|
+
* and free it from the caller's own lifetime instead (what the 3D
|
|
180
|
+
* components board and the story turntable do, because their graph is
|
|
181
|
+
* async to create).
|
|
182
|
+
*
|
|
183
|
+
* Returning a graph you cannot rebuild synchronously AND a `dispose` that
|
|
184
|
+
* really frees it is the third shape, and it renders a black panel on the
|
|
185
|
+
* second activation.
|
|
186
|
+
*/
|
|
187
|
+
readonly build: () => ToolObject3DPreviewSource;
|
|
188
|
+
readonly displayName?: string;
|
|
189
|
+
/** Explicit flat scene background. Omit it to inherit the editor's standard
|
|
190
|
+
* viewport dressing (environment, gradient backdrop, key light). */
|
|
191
|
+
readonly background?: ColorRepresentation;
|
|
192
|
+
/**
|
|
193
|
+
* The Asset Lab's IMPORT AUDIT on this document's inspector — the Geometry
|
|
194
|
+
* block (triangle counts, GPU estimate, LODs, collision, invalid values)
|
|
195
|
+
* and the Source row. Default on: it is what an imported model's inspector
|
|
196
|
+
* is for. A modeling document that carries its own data panel (the mesh
|
|
197
|
+
* document's Data / Modifiers sections) passes `false`, the way Blender's
|
|
198
|
+
* Object Data tab is the mesh's own, not an importer's report.
|
|
199
|
+
*/
|
|
200
|
+
readonly audit?: boolean;
|
|
201
|
+
readonly cameraDirection?: readonly [number, number, number];
|
|
202
|
+
/** Per-document overrides of the standard viewport dressing. */
|
|
203
|
+
readonly dressing?: ToolViewportDressing;
|
|
204
|
+
/** How far the OPENING view stands back from a fit of the content: `1`
|
|
205
|
+
* fills the view (the default); Blender's startup view has its cube at
|
|
206
|
+
* about a third of that, so the Model document opens at `3`. Frame
|
|
207
|
+
* (numpad .) still fits exactly. */
|
|
208
|
+
readonly openingFit?: number;
|
|
209
|
+
/**
|
|
210
|
+
* Optional binding from live native clips back to ordinary project source.
|
|
211
|
+
* Project code only serializes its own TypeScript shape; the editor owns the
|
|
212
|
+
* checksum-guarded write and records it in the canonical project history.
|
|
213
|
+
* Without this binding the animation workspace remains honestly read-only.
|
|
214
|
+
*/
|
|
215
|
+
/**
|
|
216
|
+
* Project-owned serialization for this authored document. Each serializer
|
|
217
|
+
* returns the exact bytes of one ordinary project file; the host writes all
|
|
218
|
+
* resources as one checksum-guarded, failure-atomic history transaction.
|
|
219
|
+
* The project owns the format and the host never interprets its contents.
|
|
220
|
+
*/
|
|
221
|
+
readonly persistence?: ToolObject3DDocumentPersistence;
|
|
222
|
+
/** Project-owned serialization of this document into ONE source module,
|
|
223
|
+
* written through the editor's source seam (see the binding's docs). */
|
|
224
|
+
readonly documentSource?: ToolDocumentSourceBinding;
|
|
225
|
+
/** Replace the default native-tree projection with a semantic adapter. The
|
|
226
|
+
* default adapter is provided for delegation, so a project can add terrain
|
|
227
|
+
* layers, bones, or mesh elements without rebuilding Object3D projection. */
|
|
228
|
+
readonly authoring?: ToolObject3DDocumentAuthoringFactory;
|
|
229
|
+
/** Project-owned direct manipulation hosted by the editor's input and
|
|
230
|
+
* transient-overlay lifecycle. Completed gestures commit `persistence`; an
|
|
231
|
+
* Escape, unmount, thrown callback, or failed write calls `cancel`. */
|
|
232
|
+
readonly interaction?: ToolObject3DDocumentInteraction;
|
|
233
|
+
/**
|
|
234
|
+
* The editor's orange SELECTION SILHOUETTE around whatever the hierarchy
|
|
235
|
+
* has selected. Default on. A document that owns a sub-object editing mode
|
|
236
|
+
* passes `false` while that mode is live: the silhouette is an OBJECT-level
|
|
237
|
+
* affordance, and Blender draws none in Edit Mode. It is live — a document
|
|
238
|
+
* may flip it as its own mode changes, and the outline appears or goes with
|
|
239
|
+
* the next frame.
|
|
240
|
+
*/
|
|
241
|
+
readonly selectionOutline?: boolean;
|
|
242
|
+
readonly active?: boolean;
|
|
243
|
+
/**
|
|
244
|
+
* This document's own counts, drawn in the VIEWPORT OVERLAY under the view
|
|
245
|
+
* and subject lines — Blender's home for them (`sculpting.png`: `Vertices
|
|
246
|
+
* 8` / `Faces 6` below `User Perspective` / `(1) Cube | Cube`, never in the
|
|
247
|
+
* header band). The host owns the block's geometry and ink; the document
|
|
248
|
+
* owns which counts exist and what they are called, because only it knows
|
|
249
|
+
* what it is counting. Omit it and no block is drawn.
|
|
250
|
+
*/
|
|
251
|
+
readonly statistics?: readonly ToolViewportStatistic[];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** One row of the viewport overlay's statistics block: Blender's label column
|
|
255
|
+
* and its value column. */
|
|
256
|
+
export interface ToolViewportStatistic {
|
|
257
|
+
/** Stable row identity — the React key, never drawn. */
|
|
258
|
+
readonly id: string;
|
|
259
|
+
/** Blender's own noun where one exists (`Vertices`, `Faces`). */
|
|
260
|
+
readonly label: string;
|
|
261
|
+
readonly value: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** The native document state handed back to project-owned serializers. */
|
|
265
|
+
export interface ToolObject3DDocumentState {
|
|
266
|
+
readonly root: Object3D;
|
|
267
|
+
readonly animations: readonly AnimationClip[];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** One project-owned artifact participating in an Asset Lab commit. */
|
|
271
|
+
export interface ToolObject3DDocumentResource {
|
|
272
|
+
/** Project-root-relative destination. */
|
|
273
|
+
readonly path: string;
|
|
274
|
+
/** MIME type recorded in canonical history. */
|
|
275
|
+
readonly contentType?: string;
|
|
276
|
+
/** `null` removes the file in the same atomic transaction. */
|
|
277
|
+
readonly serialize: (
|
|
278
|
+
document: ToolObject3DDocumentState,
|
|
279
|
+
) => string | Uint8Array | null | Promise<string | Uint8Array | null>;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface ToolObject3DDocumentPersistence {
|
|
283
|
+
/** Default undo/redo label. A completed gesture may supply a narrower one. */
|
|
284
|
+
readonly label?: string;
|
|
285
|
+
/** Non-empty, path-unique set of files owned by this document. */
|
|
286
|
+
readonly resources: readonly ToolObject3DDocumentResource[];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface ToolObject3DDocumentAuthoringContext extends ToolObject3DDocumentState {
|
|
290
|
+
readonly documentId: string;
|
|
291
|
+
readonly sourcePath: string;
|
|
292
|
+
readonly scene: Scene;
|
|
293
|
+
readonly defaultAdapter: AuthoringAdapter;
|
|
294
|
+
/**
|
|
295
|
+
* Serialize this project-owned document through its declared persistence
|
|
296
|
+
* resources and record the change in canonical history. Rejects when the
|
|
297
|
+
* document is read-only or the atomic write fails.
|
|
298
|
+
*/
|
|
299
|
+
readonly commit: (label?: string) => Promise<boolean>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface ToolObject3DDocumentAuthoring {
|
|
303
|
+
readonly adapter: AuthoringAdapter;
|
|
304
|
+
dispose?(): void;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export type ToolObject3DDocumentAuthoringFactory = (
|
|
308
|
+
context: ToolObject3DDocumentAuthoringContext,
|
|
309
|
+
) => ToolObject3DDocumentAuthoring;
|
|
310
|
+
|
|
311
|
+
/** Adapter-native hit data for a project-owned Asset Lab gesture. */
|
|
312
|
+
export interface ToolObject3DPointerEvent {
|
|
313
|
+
readonly pointerId: number;
|
|
314
|
+
readonly button: number;
|
|
315
|
+
readonly buttons: number;
|
|
316
|
+
readonly clientX: number;
|
|
317
|
+
readonly clientY: number;
|
|
318
|
+
readonly altKey: boolean;
|
|
319
|
+
readonly ctrlKey: boolean;
|
|
320
|
+
readonly metaKey: boolean;
|
|
321
|
+
readonly shiftKey: boolean;
|
|
322
|
+
readonly ray: Ray;
|
|
323
|
+
readonly hits: readonly Intersection<Object3D>[];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** One gesture owns exact rollback to its pre-begin document state. */
|
|
327
|
+
export interface ToolObject3DGesture {
|
|
328
|
+
readonly label?: string;
|
|
329
|
+
update(event: ToolObject3DPointerEvent): void;
|
|
330
|
+
/** Finalize the project-owned native state before the host serializes it. */
|
|
331
|
+
commit(event: ToolObject3DPointerEvent): void | Promise<void>;
|
|
332
|
+
/** Restore exact pre-begin native state. Must be safe after partial commit. */
|
|
333
|
+
cancel(): void | Promise<void>;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export interface ToolObject3DInteractionContext {
|
|
337
|
+
readonly root: Object3D;
|
|
338
|
+
readonly scene: Scene;
|
|
339
|
+
readonly renderer: WebGLRenderer;
|
|
340
|
+
/** Host-owned group removed on document teardown. Project code owns GPU
|
|
341
|
+
* resources it adds and releases them from the extension's `dispose`. */
|
|
342
|
+
readonly overlay: Group;
|
|
343
|
+
/** Live camera getter; projection can change while the document is open. */
|
|
344
|
+
readonly camera: () => Camera;
|
|
345
|
+
/** False means gestures will not begin because no persistence binding exists. */
|
|
346
|
+
readonly writable: boolean;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export interface ToolObject3DInteractionExtension {
|
|
350
|
+
/** Return null to yield this pointer to the ordinary viewport controls. */
|
|
351
|
+
begin(event: ToolObject3DPointerEvent): ToolObject3DGesture | null;
|
|
352
|
+
/**
|
|
353
|
+
* Optional pre-sample phase. Restore any transforms changed by the previous
|
|
354
|
+
* frame's refinement here; the host advances content time after this call
|
|
355
|
+
* and invokes {@link update} with the resulting native pose.
|
|
356
|
+
*/
|
|
357
|
+
prepareFrame?(deltaSeconds: number): void;
|
|
358
|
+
/**
|
|
359
|
+
* Optional per-frame refinement after the host has advanced content time.
|
|
360
|
+
* Use this for project-owned constraints and overlay presentation.
|
|
361
|
+
*/
|
|
362
|
+
update?(deltaSeconds: number): void;
|
|
363
|
+
dispose(): void;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export interface ToolObject3DDocumentInteraction {
|
|
367
|
+
setup(context: ToolObject3DInteractionContext): ToolObject3DInteractionExtension;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Project-owned serialization of a WHOLE Asset Lab document into one ordinary
|
|
372
|
+
* source file, for a
|
|
373
|
+
* document whose truth is a TypeScript module (a modeling session's mesh
|
|
374
|
+
* module). The editor owns the checksum-guarded whole-file write through the
|
|
375
|
+
* source seam and records it in canonical history; executable source never
|
|
376
|
+
* travels through {@link ToolObject3DAuthoringProps.persistence}'s resource
|
|
377
|
+
* route, which the server refuses by rule. A document declares either this or
|
|
378
|
+
* `persistence`, never both. MUST be referentially stable across renders.
|
|
379
|
+
*/
|
|
380
|
+
export interface ToolDocumentSourceBinding {
|
|
381
|
+
/** Project-relative source file below src/. */
|
|
382
|
+
readonly path: string;
|
|
383
|
+
/** Undo/redo label shown by the editor history. */
|
|
384
|
+
readonly label?: string;
|
|
385
|
+
/** The exact bytes of the module for this document state — or `null` for
|
|
386
|
+
* "nothing changed, write nothing", so a gesture that ended without an
|
|
387
|
+
* edit leaves the file byte-for-byte alone. */
|
|
388
|
+
readonly serialize: (document: ToolObject3DDocumentState) => string | null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Format-neutral identity for a project-owned Asset Lab document.
|
|
392
|
+
*
|
|
393
|
+
* This surface deliberately accepts no React children. Project tools may run
|
|
394
|
+
* a different React major than the editor, so their native UI stays in their
|
|
395
|
+
* own runtime while this editor-owned subject publishes the standard Asset
|
|
396
|
+
* Lab context, selection floor, and Inspector identity row beside it.
|
|
397
|
+
*/
|
|
398
|
+
export interface ToolAssetDocumentProps {
|
|
399
|
+
/** Stable id of the open workspace document. */
|
|
400
|
+
readonly documentId: string;
|
|
401
|
+
/** Subject name shown by the Inspector. */
|
|
402
|
+
readonly title: string;
|
|
403
|
+
/** Ecosystem-neutral asset kind shown by the Inspector. */
|
|
404
|
+
readonly type: string;
|
|
405
|
+
/** Optional provenance or concise state shown with the identity row. */
|
|
406
|
+
readonly status?: string;
|
|
407
|
+
/** Whether this is the active center document. */
|
|
408
|
+
readonly active?: boolean;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Editor-owned native surfaces available to ordinary project React tools. */
|
|
412
|
+
export interface ToolContributionSurfaces {
|
|
413
|
+
readonly AssetDocument: import('react').ComponentType<ToolAssetDocumentProps>;
|
|
414
|
+
readonly Object3DPreview: import('react').ComponentType<ToolObject3DPreviewProps>;
|
|
415
|
+
readonly Object3DAuthoring: import('react').ComponentType<ToolObject3DAuthoringProps>;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** One bounded visual-history sample from the live Play recording. */
|
|
419
|
+
export interface ToolContributionRecordingFrame {
|
|
420
|
+
/** Monotonic within this recording, including after older frames are evicted. */
|
|
421
|
+
readonly sequence: number;
|
|
422
|
+
/** Position on the recording clock, measured from `startedAt`. */
|
|
423
|
+
readonly mediaTimeMs: number;
|
|
424
|
+
/** Wall-clock capture time, for correlating project-owned event streams. */
|
|
425
|
+
readonly capturedAt: string;
|
|
426
|
+
/** Borrowed object URL. It remains valid until this frame is evicted or Play ends. */
|
|
427
|
+
readonly src: string;
|
|
428
|
+
readonly width: number;
|
|
429
|
+
readonly height: number;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Stable snapshot returned by the live recording transport. */
|
|
433
|
+
export interface ToolContributionRecordingSnapshot {
|
|
434
|
+
readonly startedAt: string;
|
|
435
|
+
readonly width: number;
|
|
436
|
+
readonly height: number;
|
|
437
|
+
/** Latest sampled position on the live recording clock. */
|
|
438
|
+
readonly liveEdgeMs: number;
|
|
439
|
+
readonly sampleFps: number;
|
|
440
|
+
readonly retentionMs: number;
|
|
441
|
+
readonly frames: readonly ToolContributionRecordingFrame[];
|
|
442
|
+
/** A preview failure never stops the full WebM recording. */
|
|
443
|
+
readonly previewError: string | null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Read-only visual history for the current Play recording.
|
|
448
|
+
*
|
|
449
|
+
* `getSnapshot` is referentially stable between notifications, so it can be
|
|
450
|
+
* passed directly to React's `useSyncExternalStore`. A `null` snapshot means
|
|
451
|
+
* Play is not currently recording (for example, a human-started Play session
|
|
452
|
+
* that did not explicitly start recording).
|
|
453
|
+
*/
|
|
454
|
+
export interface ToolContributionRecording {
|
|
455
|
+
readonly getSnapshot: () => ToolContributionRecordingSnapshot | null;
|
|
456
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** One structured row from a Gameplay Session's durable JSONL record. */
|
|
460
|
+
export interface ToolGameplaySessionEntry {
|
|
461
|
+
readonly t?: number;
|
|
462
|
+
readonly level?: string;
|
|
463
|
+
readonly source?: string;
|
|
464
|
+
readonly sub?: string;
|
|
465
|
+
readonly msg?: string;
|
|
466
|
+
readonly meta?: Readonly<Record<string, unknown>>;
|
|
467
|
+
readonly tick?: number;
|
|
468
|
+
readonly simT?: number;
|
|
469
|
+
readonly world?: string;
|
|
470
|
+
readonly simSpeed?: number;
|
|
471
|
+
readonly [field: string]: unknown;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export interface ToolGameplaySessionRecording {
|
|
475
|
+
readonly format: 'composite-webm' | 'canvas-dom';
|
|
476
|
+
readonly replay: string | null;
|
|
477
|
+
readonly file: string;
|
|
478
|
+
readonly url: string;
|
|
479
|
+
readonly startedAt: number | null;
|
|
480
|
+
readonly finalized: boolean;
|
|
481
|
+
readonly bytes: number | null;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** One Play interval, durable after Stop and editor reload. */
|
|
485
|
+
export interface ToolGameplaySession {
|
|
486
|
+
readonly id: string;
|
|
487
|
+
readonly run: string | null;
|
|
488
|
+
readonly status: 'live' | 'completed';
|
|
489
|
+
readonly startedAt: number;
|
|
490
|
+
readonly endedAt: number | null;
|
|
491
|
+
/** Real elapsed wall time. Simulation time remains an entry field. */
|
|
492
|
+
readonly durationMs: number;
|
|
493
|
+
readonly logFile: string;
|
|
494
|
+
readonly entries: readonly ToolGameplaySessionEntry[];
|
|
495
|
+
readonly recording: ToolGameplaySessionRecording | null;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export interface ToolGameplaySessionsSnapshot {
|
|
499
|
+
readonly sessions: readonly ToolGameplaySession[];
|
|
500
|
+
readonly selectedSessionId: string | null;
|
|
501
|
+
readonly selectedSession: ToolGameplaySession | null;
|
|
502
|
+
/** Shared analytics cursor, measured in real milliseconds from Play start. */
|
|
503
|
+
readonly cursorMs: number;
|
|
504
|
+
readonly liveEdgeMs: number;
|
|
505
|
+
readonly loading: boolean;
|
|
506
|
+
readonly error: string | null;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** Stable external store supplied to the built-in Analytics contribution. */
|
|
510
|
+
export interface ToolGameplaySessions {
|
|
511
|
+
readonly getSnapshot: () => ToolGameplaySessionsSnapshot;
|
|
512
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
513
|
+
readonly select: (sessionId: string) => void;
|
|
514
|
+
readonly seek: (realTimeMs: number) => void;
|
|
515
|
+
readonly refresh: () => Promise<void>;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* The live game a contribution is looking at, or `null` when nothing is
|
|
520
|
+
* playing.
|
|
521
|
+
*
|
|
522
|
+
* A dev-GUI contribution's whole subject is the RUNNING game — its stats,
|
|
523
|
+
* cheats, tuning handles and event stream all hang off the live `Game`, and
|
|
524
|
+
* there is no other door to it from a contribution (the editor's own state is
|
|
525
|
+
* not the game's). It is `unknown` deliberately: `@vgai/game-runtime`'s `Game` is
|
|
526
|
+
* the project's dependency, not this package's, so a contribution narrows it
|
|
527
|
+
* with its own import rather than making every consumer of this SDK carry the
|
|
528
|
+
* engine's types.
|
|
529
|
+
*
|
|
530
|
+
* `instanceId` is the mount id the editor's runtime instruments are pointed
|
|
531
|
+
* at (the Inspect selector). With several seats live that is the ONLY thing
|
|
532
|
+
* distinguishing two mounts of the same project, so a contribution that
|
|
533
|
+
* caches per-game state keys it on this and re-reads when it changes.
|
|
534
|
+
*/
|
|
535
|
+
export interface ToolContributionPlay {
|
|
536
|
+
readonly game: unknown;
|
|
537
|
+
readonly instanceId: string;
|
|
538
|
+
/** The editor-owned recording transport shared by every project tool. */
|
|
539
|
+
readonly recording: ToolContributionRecording;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* THE DOCUMENT HEADER REGION, as a project contribution sees it.
|
|
544
|
+
*
|
|
545
|
+
* A `workspace.document` module may `export const Toolbar` beside its default
|
|
546
|
+
* component. The editor renders it in the host-owned header strip above the
|
|
547
|
+
* document — the same strip the Game and Story documents use — with the SAME
|
|
548
|
+
* props the content receives, so a header and its body read one state. The
|
|
549
|
+
* host draws the strip identically for every document (height, divider,
|
|
550
|
+
* island treatment over a backdrop document); the contribution supplies
|
|
551
|
+
* only what goes in it. A document that hand-draws a bar inside its own body
|
|
552
|
+
* instead is a document whose header no skin, preset or region rule can
|
|
553
|
+
* reach — which is the whole reason the strip is the host's.
|
|
554
|
+
*/
|
|
555
|
+
export type ToolDocumentToolbar = import('react').ComponentType<ToolContributionProps>;
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* THE DOCUMENT SHELF REGION — Blender's tool shelf — as a project sees it:
|
|
559
|
+
* `export const Shelf` on a `workspace.document` module. The editor draws it
|
|
560
|
+
* as a vertical rail over the leading edge of the document's content box,
|
|
561
|
+
* with the same props as the body. Same contract as {@link ToolDocumentToolbar}.
|
|
562
|
+
*/
|
|
563
|
+
export type ToolDocumentShelf = import('react').ComponentType<ToolContributionProps>;
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* A document the adapter's table lists — a model, a page — as handed to the
|
|
567
|
+
* editor registered for its kind (ARCHITECTURE-CORE §The project model,
|
|
568
|
+
* "Documents, not scenes"). A `workspace.document` contribution declares the
|
|
569
|
+
* kind it edits with `export const documentKind = 'model'`; the host then
|
|
570
|
+
* opens every table entry of that kind as its OWN document — titled by the
|
|
571
|
+
* entry's label, one per entry, restored across reloads by the entry's id —
|
|
572
|
+
* and mounts the contribution with the entry here. Nothing else is looked
|
|
573
|
+
* up: the entry names the module (`source`) and the contribution does the
|
|
574
|
+
* rest through `importProjectModule`.
|
|
575
|
+
*/
|
|
576
|
+
export interface ToolDocumentEntry {
|
|
577
|
+
/** The table entry's id (`model:src/models/cage.ts`). */
|
|
578
|
+
readonly id: string;
|
|
579
|
+
readonly kind: string;
|
|
580
|
+
/** The entry's display label — the document's title (`cage`). */
|
|
581
|
+
readonly label: string;
|
|
582
|
+
/** The module that IS the document, when the entry has one. */
|
|
583
|
+
readonly source?: { readonly path: string; readonly export?: string };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export interface ToolContributionProps {
|
|
587
|
+
/** The exact registered callable this contribution presents. */
|
|
588
|
+
readonly tool: ProjectToolCatalogEntry;
|
|
589
|
+
/** Present when mounted as the editor of a table document of the kind this
|
|
590
|
+
* contribution declared (`export const documentKind`). */
|
|
591
|
+
readonly document?: ToolDocumentEntry;
|
|
592
|
+
/** Stable id of this presentation within the registered callable. */
|
|
593
|
+
readonly contributionId?: string;
|
|
594
|
+
/** Direct editor SDK client; invoke with `client.runProjectTool(tool.name, ...)`. */
|
|
595
|
+
readonly client: EditorClient;
|
|
596
|
+
/** Generic editor-owned presentation surfaces; project source remains native. */
|
|
597
|
+
readonly surfaces: ToolContributionSurfaces;
|
|
598
|
+
/** Sanitized product-account projection. Never contains an access token. */
|
|
599
|
+
readonly account: GenerationAccountProjection;
|
|
600
|
+
/** The inspected play instance, or `null` while nothing is playing. */
|
|
601
|
+
readonly play: ToolContributionPlay | null;
|
|
602
|
+
/** Present when mounted as a workspace document contribution. */
|
|
603
|
+
readonly documentId?: string;
|
|
604
|
+
/** Whether that workspace document is the active center subject. */
|
|
605
|
+
readonly active?: boolean;
|
|
606
|
+
/**
|
|
607
|
+
* Present with `documentId`. Hand the host the ONE object this document
|
|
608
|
+
* edits through — its live session — and `editor.document.run(ctx => …)`
|
|
609
|
+
* (`vgai eval`) runs a step against it in Edit mode, without play: the
|
|
610
|
+
* agent's REPL over the document. Re-publish whenever that object changes
|
|
611
|
+
* (a reload that swaps a session); the return value unpublishes.
|
|
612
|
+
*/
|
|
613
|
+
readonly publishContext?: (context: unknown) => () => void;
|
|
614
|
+
/**
|
|
615
|
+
* Raise a card on the editor's bottom-right notification stack — the ONE
|
|
616
|
+
* shape an event takes (ARCHITECTURE-CORE §Editor chrome, "Notices take
|
|
617
|
+
* VS Code's shape"): a refusal, a failed write, a no-op the user should
|
|
618
|
+
* hear about. Never paint these into a document's own chrome. Plain
|
|
619
|
+
* `info` hides itself; warnings and errors stay until dismissed. The
|
|
620
|
+
* return value dismisses the card early.
|
|
621
|
+
*/
|
|
622
|
+
readonly notify?: (notice: ToolNotice) => () => void;
|
|
623
|
+
/**
|
|
624
|
+
* Tell the host what this contribution is about to do on the page's main
|
|
625
|
+
* thread — `work('building src/models/x.ts')` before running a module's
|
|
626
|
+
* `build()`, `work(null)` after. A command that times out meanwhile is
|
|
627
|
+
* refused naming that work instead of "the page never answered".
|
|
628
|
+
*/
|
|
629
|
+
readonly work?: (label: string | null) => void;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** A card for {@link ToolContributionProps.notify}. */
|
|
633
|
+
export interface ToolNotice {
|
|
634
|
+
readonly tone: 'info' | 'warning' | 'error';
|
|
635
|
+
/** One line, bold — what happened. */
|
|
636
|
+
readonly title: string;
|
|
637
|
+
/** The rest, plain — what it means, what to do. */
|
|
638
|
+
readonly detail?: string;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* A `workspace.utility`'s props — the shared shape, except that `tool` may be
|
|
643
|
+
* absent.
|
|
644
|
+
*
|
|
645
|
+
* It is its own type rather than a relaxation of {@link ToolContributionProps}
|
|
646
|
+
* because only the two PRESENTING points earn the relaxation (this one and
|
|
647
|
+
* {@link ToolInspectorContributionProps}): a record-reading drawer panel (a
|
|
648
|
+
* log, a run timeline) presents what HAPPENED rather than one callable's
|
|
649
|
+
* output, so there is no honest name to put there and the loader stopped
|
|
650
|
+
* demanding a false one. Widening the shared props instead would hand every
|
|
651
|
+
* RUNNING contribution a `tool` it must now null-check while its own point
|
|
652
|
+
* still guarantees one.
|
|
653
|
+
*/
|
|
654
|
+
export interface ToolUtilityContributionProps extends Omit<ToolContributionProps, 'tool'> {
|
|
655
|
+
/** The callable this utility declared, when it declared one at all. */
|
|
656
|
+
readonly tool?: ProjectToolCatalogEntry;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* A game's analytics body. The editor owns session selection, real-time
|
|
661
|
+
* transport, and optional video preview; this component owns only the charts
|
|
662
|
+
* and readouts that give the selected session meaning for this game.
|
|
663
|
+
*
|
|
664
|
+
* `play` is intentionally absent. Historical analysis consumes the durable
|
|
665
|
+
* Gameplay Session record, so the type itself prevents an Analytics surface
|
|
666
|
+
* from accidentally depending on a live module graph.
|
|
667
|
+
*/
|
|
668
|
+
export interface ToolAnalyticsContributionProps
|
|
669
|
+
extends Omit<ToolContributionProps, 'tool' | 'play'> {
|
|
670
|
+
readonly tool?: ProjectToolCatalogEntry;
|
|
671
|
+
readonly gameplaySessions: ToolGameplaySessions;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* A `selection.inspector`'s props. `tool` is OPTIONAL here for the same reason
|
|
676
|
+
* it is on a utility: a section may present state the editor already has —
|
|
677
|
+
* readings, verbs a game registered as debug commands — and drive no single
|
|
678
|
+
* registered callable at all. A section that DOES commit through one still
|
|
679
|
+
* declares it and still gets the resolved entry.
|
|
680
|
+
*
|
|
681
|
+
* A module may also `export const icon = '<glyph name>'` — the NAME of the
|
|
682
|
+
* glyph that stands for this section wherever the Inspector shows one (the
|
|
683
|
+
* Properties tab rail, the section-icon strip). A name, never an icon
|
|
684
|
+
* object: the active icon set paints it, and a set may give that name its
|
|
685
|
+
* own category ink (`IconSetContribution` `tone`). Give a section that
|
|
686
|
+
* means something its OWN name (`properties-data`), the way the Outliner's
|
|
687
|
+
* rows do, because a tone is keyed by name and a shared name would tint
|
|
688
|
+
* every section that borrowed it. Omitted, the host draws its generic tool
|
|
689
|
+
* glyph.
|
|
690
|
+
*/
|
|
691
|
+
export interface ToolInspectorContributionProps extends Omit<ToolContributionProps, 'tool'> {
|
|
692
|
+
/** The callable this section declared, when it declared one at all. */
|
|
693
|
+
readonly tool?: ProjectToolCatalogEntry;
|
|
694
|
+
readonly node: ToolContributionNode | null;
|
|
695
|
+
readonly nodeId: string | null;
|
|
696
|
+
/** The matched adapter — the same value `match` was handed. Adapter-native
|
|
697
|
+
* API; import its concrete type when a section reads more than the node
|
|
698
|
+
* (a model section reads the document's source path and root). */
|
|
699
|
+
readonly adapter: unknown;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/** Stable editor projection of one selected project or external-library asset. */
|
|
703
|
+
export interface ToolContributionAsset {
|
|
704
|
+
readonly path: string;
|
|
705
|
+
readonly name: string;
|
|
706
|
+
readonly kind: string;
|
|
707
|
+
readonly origin: 'project' | 'library';
|
|
708
|
+
readonly sourcePath?: string;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/** An `asset.inspector`'s props — the same relaxation as the two other
|
|
712
|
+
* PRESENTING points ({@link ToolUtilityContributionProps},
|
|
713
|
+
* {@link ToolInspectorContributionProps}): it presents what an ASSET is, not
|
|
714
|
+
* one callable's output, so `tool` may be absent. */
|
|
715
|
+
/**
|
|
716
|
+
* A verb an `asset.inspector` contribution offers for the asset it is
|
|
717
|
+
* showing — the SAME thing a built-in Inspector button is: it appears in the
|
|
718
|
+
* panel's identity row, `editor.inspect().quickActions` lists it, and
|
|
719
|
+
* `editor.runAction(id)` runs exactly this `run`. A section publishes its
|
|
720
|
+
* verbs with {@link ToolAssetInspectorContributionProps.setActions} when it
|
|
721
|
+
* knows them, which is what gives a project's own Inspector door a place in
|
|
722
|
+
* the control API instead of a mouse only.
|
|
723
|
+
*/
|
|
724
|
+
export interface ToolAssetInspectorAction {
|
|
725
|
+
/** Stable id, unique within the contribution (`shot.open`). */
|
|
726
|
+
readonly id: string;
|
|
727
|
+
/** Accessible name and tooltip. */
|
|
728
|
+
readonly title: string;
|
|
729
|
+
/** Visible button text. */
|
|
730
|
+
readonly label?: string;
|
|
731
|
+
readonly disabled?: boolean;
|
|
732
|
+
readonly run: () => void | Promise<void>;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
export interface ToolAssetInspectorContributionProps extends Omit<ToolContributionProps, 'tool'> {
|
|
736
|
+
readonly tool?: ProjectToolCatalogEntry;
|
|
737
|
+
readonly asset: ToolContributionAsset | null;
|
|
738
|
+
/**
|
|
739
|
+
* Publish the verbs this section offers for the asset it is showing — see
|
|
740
|
+
* {@link ToolAssetInspectorAction}. Call it with the list when the answer
|
|
741
|
+
* is known (a section that probes asynchronously calls it from the effect
|
|
742
|
+
* that resolves), and with `[]` when the answer is no. The editor renders
|
|
743
|
+
* them in the identity row and serves them to
|
|
744
|
+
* `editor.inspect().quickActions` / `editor.runAction(id)`, so the panel
|
|
745
|
+
* and the control API always show one list. Not calling it means "no
|
|
746
|
+
* verbs", which is what a section that only displays should do.
|
|
747
|
+
*/
|
|
748
|
+
readonly setActions: (actions: readonly ToolAssetInspectorAction[]) => void;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** Optional named export required by `asset.inspector` contributions. */
|
|
752
|
+
export type ToolAssetInspectorContributionMatch = (asset: ToolContributionAsset | null) => boolean;
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Provider-owned presentation mounted inside the editor-owned generation
|
|
756
|
+
* result document. The host owns job state, billing, and acceptance; this
|
|
757
|
+
* component only interprets the native poll result.
|
|
758
|
+
*/
|
|
759
|
+
export interface ToolGenerationResultContributionProps extends ToolContributionProps {
|
|
760
|
+
readonly job: GenerationJob;
|
|
761
|
+
readonly result: unknown;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Required named export for `generation.result` contributions. A single poll
|
|
766
|
+
* callable may serve many native operations, so presentation selection is
|
|
767
|
+
* based on the durable job and the provider's unmodified poll result rather
|
|
768
|
+
* than registration order or a host-owned media taxonomy.
|
|
769
|
+
*/
|
|
770
|
+
export type ToolGenerationResultContributionMatch = (
|
|
771
|
+
job: GenerationJob,
|
|
772
|
+
result: unknown,
|
|
773
|
+
) => boolean;
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* What the inspector is showing AROUND the node a `selection.inspector`
|
|
777
|
+
* contribution is being matched against.
|
|
778
|
+
*
|
|
779
|
+
* It exists for one question a `node`/`adapter` pair cannot answer: with
|
|
780
|
+
* NOTHING selected, `node` is `null` on every surface alike — an open Asset
|
|
781
|
+
* Lab document's empty state and the play surface's Game subject are the same
|
|
782
|
+
* two arguments. A contribution that matches `node === null` therefore matched
|
|
783
|
+
* BOTH, and the empty-state subject of whatever document happened to be open
|
|
784
|
+
* grew sections belonging to another surface entirely.
|
|
785
|
+
*
|
|
786
|
+
* `nullSubjectId` is the id of the empty-state subject actually being composed
|
|
787
|
+
* (`inspection/null-subject.ts`), so a contribution scopes itself POSITIVELY —
|
|
788
|
+
* `ctx.nullSubjectId === 'game'` — rather than by guessing from the adapter.
|
|
789
|
+
* It is `null` whenever a node IS selected, which is the honest answer: there
|
|
790
|
+
* is no empty-state subject in that composition.
|
|
791
|
+
*/
|
|
792
|
+
export interface ToolInspectorContributionMatchContext {
|
|
793
|
+
readonly nullSubjectId: string | null;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/** Optional named export required by `selection.inspector` contributions. */
|
|
797
|
+
export type ToolInspectorContributionMatch = (
|
|
798
|
+
node: ToolContributionNode | null,
|
|
799
|
+
/** Adapter-native API. Import its concrete type when a contribution needs it. */
|
|
800
|
+
adapter: unknown,
|
|
801
|
+
context: ToolInspectorContributionMatchContext,
|
|
802
|
+
) => boolean;
|
|
803
|
+
|
|
804
|
+
// ---------------------------------------------------------------------------
|
|
805
|
+
// Project modules, through the host's own door
|
|
806
|
+
// ---------------------------------------------------------------------------
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* How THIS HOST loads a project module for a contribution: the dev/packaged
|
|
810
|
+
* host serves it through its own Vite (`/@fs/…`, with a URL a live re-import
|
|
811
|
+
* can stamp). The host registers one loader per project (`tool-loader.ts`); a
|
|
812
|
+
* contribution that needs a project module — a model's Edit Mesh door
|
|
813
|
+
* importing `build()` — asks {@link importProjectModule} and never spells a
|
|
814
|
+
* tier's mechanics itself.
|
|
815
|
+
*/
|
|
816
|
+
export interface ProjectModuleLoader {
|
|
817
|
+
readonly import: (path: string) => Promise<Record<string, unknown>>;
|
|
818
|
+
/** The module's servable URL, or `null` on a tier that serves none. */
|
|
819
|
+
readonly url: (path: string) => URL | null;
|
|
820
|
+
/** The module's SOURCE TEXT as it is on disk (or in storage) right now —
|
|
821
|
+
* what a contribution that writes the module back (a model's mesh
|
|
822
|
+
* editor appending a line) patches. */
|
|
823
|
+
readonly source: (path: string) => Promise<string>;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/** ONE loader across every copy of this module — see `host.ts` for why the
|
|
827
|
+
* packaged runtime holds two SDK instances, and `layouts.tsx` for the
|
|
828
|
+
* `Symbol.for` precedent. */
|
|
829
|
+
const LOADER_KEY = Symbol.for('vgai.editor.project-module-loader');
|
|
830
|
+
const loaders = globalThis as typeof globalThis & {
|
|
831
|
+
[LOADER_KEY]?: ProjectModuleLoader | null;
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
/** Host side: install (or clear) the active project's module loader. */
|
|
835
|
+
export function registerProjectModuleLoader(loader: ProjectModuleLoader | null): void {
|
|
836
|
+
loaders[LOADER_KEY] = loader;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/** Contribution side: a project module (`src/models/cage.ts`), loaded the
|
|
840
|
+
* way this tier loads project modules. Refuses by name with no host. */
|
|
841
|
+
export function importProjectModule(path: string): Promise<Record<string, unknown>> {
|
|
842
|
+
if (!loaders[LOADER_KEY]) {
|
|
843
|
+
return Promise.reject(
|
|
844
|
+
new Error(`importProjectModule(${path}): no host has registered a project module loader`),
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
return loaders[LOADER_KEY].import(path);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/** The module's servable URL on this host, or `null` when it has none. */
|
|
851
|
+
export function projectModuleUrl(path: string): URL | null {
|
|
852
|
+
return loaders[LOADER_KEY]?.url(path) ?? null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/** Contribution side: the module's current source text. Refuses by name with no host. */
|
|
856
|
+
export function readProjectModuleSource(path: string): Promise<string> {
|
|
857
|
+
if (!loaders[LOADER_KEY]) {
|
|
858
|
+
return Promise.reject(
|
|
859
|
+
new Error(`readProjectModuleSource(${path}): no host has registered a project module loader`),
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
return loaders[LOADER_KEY].source(path);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// ---------------------------------------------------------------------------
|
|
866
|
+
// The transform a stage transforms through
|
|
867
|
+
// ---------------------------------------------------------------------------
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* THE MODAL TRANSFORM A STAGE OWNS — the door the host's transform tools drive
|
|
871
|
+
* on a stage that is not the editor viewport's own.
|
|
872
|
+
*
|
|
873
|
+
* WHY IT EXISTS, measured rather than argued (2026-09-19, cold `--template
|
|
874
|
+
* models` scaffold): the host's `ToolStrip` draws Move / Rotate / Scale /
|
|
875
|
+
* Transform over every stage that paints a three surface, and on a Model
|
|
876
|
+
* document all four lit and nothing happened — `Transform` lit at boot,
|
|
877
|
+
* offering a tool that could never act. Eight controls in the two places a
|
|
878
|
+
* person looks first, one of them lit.
|
|
879
|
+
*
|
|
880
|
+
* TWO SEPARATE CAUSES, and only the second one is this door's:
|
|
881
|
+
* 1 THE TOOLS WROTE THE WRONG STORE. Every stage owns an `EditorShellStore`
|
|
882
|
+
* (`stage-store-registry.ts`); the strip wrote the SHELL's, which no
|
|
883
|
+
* document stage reads. That is fixed in the host (`ToolStrip`'s own note
|
|
884
|
+
* carries the prefab-story measurement) and it is why a session-painted
|
|
885
|
+
* stage now takes the ordinary GIZMO arm.
|
|
886
|
+
* 2 A MESH MODULE DOES NOT TRANSFORM THROUGH A GIZMO AT ALL. Blender's Edit
|
|
887
|
+
* Mode Move/Rotate/Scale act on the element selection and are its
|
|
888
|
+
* `G`/`R`/`S`; ours already do, through `MeshEditSession.beginTransform`
|
|
889
|
+
* from the Mesh menu and the keys, while the document's own adapter is a
|
|
890
|
+
* read-only projection of the datablock that a gizmo could not write. So
|
|
891
|
+
* the stage DECLARES how it transforms and the host's three single-channel
|
|
892
|
+
* tools run that, instead of the host guessing from the document's kind.
|
|
893
|
+
*
|
|
894
|
+
* THERE IS NO `combined` KIND, and that is the tool shelf's own rule applied
|
|
895
|
+
* to the fourth button: Blender's all-handles Transform IS a gizmo, and a
|
|
896
|
+
* modal transform has no twin for it, so the host does not draw it over a
|
|
897
|
+
* door (the same reason ten of Blender's twenty-one tools are absent; the
|
|
898
|
+
* 21-row table that said which lived in the deleted Edit Mesh document and is
|
|
899
|
+
* in git — `packages/mesh/contributions/mesh-edit-document.tsx` before
|
|
900
|
+
* 2026-09-19).
|
|
901
|
+
*
|
|
902
|
+
* NEITHER ARE THE HEADER'S TRANSFORM WELLS (orientation, pivot/anchor, snap,
|
|
903
|
+
* options) over a door. Every one of them configures a GIZMO — measured
|
|
904
|
+
* 2026-09-19: `transformSpace`'s only functional reader is the viewport's
|
|
905
|
+
* `controls.setSpace`, and `pivotMode`/`gizmoAnchor`/`snapEnabled` are read
|
|
906
|
+
* only by that viewport and the 2D canvas overlay. A modal door has no
|
|
907
|
+
* orientation, no pivot choice and no snapping to configure (the mesh kit
|
|
908
|
+
* transforms about the selection's median on global axes, always), so the host
|
|
909
|
+
* draws no wells over one. Wiring a well here means giving the door a
|
|
910
|
+
* parameter first.
|
|
911
|
+
*/
|
|
912
|
+
export type StageTransformKind = 'translate' | 'rotate' | 'scale';
|
|
913
|
+
|
|
914
|
+
export interface StageTransformDoor {
|
|
915
|
+
/** Arm this stage's own modal transform. The stage narrates its own refusal
|
|
916
|
+
* (nothing selected, another gesture running); the host never paraphrases
|
|
917
|
+
* it. */
|
|
918
|
+
readonly begin: (kind: StageTransformKind) => void;
|
|
919
|
+
/** The kind armed right now, or `null`. This is what LIGHTS a tool: these
|
|
920
|
+
* are modal operators, not persistent tool modes, so between gestures
|
|
921
|
+
* nothing is lit. */
|
|
922
|
+
readonly armed: () => StageTransformKind | null;
|
|
923
|
+
/** `useSyncExternalStore` pair with {@link armed}. */
|
|
924
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
interface StageTransformRegistry {
|
|
928
|
+
readonly doors: Map<string, StageTransformDoor>;
|
|
929
|
+
version: number;
|
|
930
|
+
readonly listeners: Set<() => void>;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/** ONE registry across every copy of this module, for the same reason
|
|
934
|
+
* {@link registerProjectModuleLoader} needs one — see its note. */
|
|
935
|
+
const STAGE_TRANSFORM_KEY = Symbol.for('vgai.editor.stage-transform-doors');
|
|
936
|
+
const stageTransforms = globalThis as typeof globalThis & {
|
|
937
|
+
[STAGE_TRANSFORM_KEY]?: StageTransformRegistry;
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
function stageTransformRegistry(): StageTransformRegistry {
|
|
941
|
+
const existing = stageTransforms[STAGE_TRANSFORM_KEY];
|
|
942
|
+
if (existing) return existing;
|
|
943
|
+
const created: StageTransformRegistry = { doors: new Map(), version: 0, listeners: new Set() };
|
|
944
|
+
stageTransforms[STAGE_TRANSFORM_KEY] = created;
|
|
945
|
+
return created;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function notifyStageTransforms(registry: StageTransformRegistry): void {
|
|
949
|
+
registry.version++;
|
|
950
|
+
for (const listener of [...registry.listeners]) listener();
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/** A mounted stage declares the transform it transforms through — the shape
|
|
954
|
+
* `stage-store-registry.ts` uses for the store a stage runs on. Re-registering
|
|
955
|
+
* the same document replaces it; the returned unregister drops it. */
|
|
956
|
+
export function registerStageTransform(documentId: string, door: StageTransformDoor): () => void {
|
|
957
|
+
const registry = stageTransformRegistry();
|
|
958
|
+
registry.doors.set(documentId, door);
|
|
959
|
+
notifyStageTransforms(registry);
|
|
960
|
+
return () => {
|
|
961
|
+
if (registry.doors.get(documentId) !== door) return;
|
|
962
|
+
registry.doors.delete(documentId);
|
|
963
|
+
notifyStageTransforms(registry);
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/** The transform that document's stage owns, or `null` when it declares none
|
|
968
|
+
* (every stage whose transform is the editor viewport's own gizmo). */
|
|
969
|
+
export function stageTransformDoor(documentId: string | null): StageTransformDoor | null {
|
|
970
|
+
return documentId === null ? null : (stageTransformRegistry().doors.get(documentId) ?? null);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/** `useSyncExternalStore` shape — a stage declaring or dropping its door
|
|
974
|
+
* changes what the shelf and the header draw. */
|
|
975
|
+
export function subscribeStageTransforms(listener: () => void): () => void {
|
|
976
|
+
const registry = stageTransformRegistry();
|
|
977
|
+
registry.listeners.add(listener);
|
|
978
|
+
return () => {
|
|
979
|
+
registry.listeners.delete(listener);
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
export function stageTransformsVersion(): number {
|
|
984
|
+
return stageTransformRegistry().version;
|
|
985
|
+
}
|