@volter/editor-blender 0.1.4 → 0.1.6
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.
|
@@ -166,6 +166,10 @@ let frameSubscription: (() => void) | null = null;
|
|
|
166
166
|
const inFlight = new Set<string>();
|
|
167
167
|
/** The subject the last context was read for, so a re-render does not refetch. */
|
|
168
168
|
let readFor: string | null = null;
|
|
169
|
+
/** Reads belong to a model revision and selected subject, not merely a path.
|
|
170
|
+
* A late answer for a deleted duplicate must not overwrite the new selection. */
|
|
171
|
+
let readGeneration = 0;
|
|
172
|
+
let contextRequest = 0;
|
|
169
173
|
|
|
170
174
|
function publish(next: Partial<BlenderPropertiesState>, tabsMayHaveChanged = false): void {
|
|
171
175
|
state = { ...state, ...next };
|
|
@@ -204,8 +208,10 @@ export function blenderPropertiesState(): BlenderPropertiesState {
|
|
|
204
208
|
* values it had. */
|
|
205
209
|
function onEngineMoved(): void {
|
|
206
210
|
const subject = readFor;
|
|
211
|
+
readGeneration += 1;
|
|
212
|
+
inFlight.clear();
|
|
207
213
|
readFor = null;
|
|
208
|
-
publish({ views: new Map() });
|
|
214
|
+
publish({ context: null, views: new Map(), error: null, loading: subject !== null });
|
|
209
215
|
if (subject !== null) scheduleContextRead(subject);
|
|
210
216
|
}
|
|
211
217
|
|
|
@@ -264,11 +270,17 @@ function watchFrames(): void {
|
|
|
264
270
|
async function readContext(key: string): Promise<void> {
|
|
265
271
|
const collection = key.startsWith('collection:') ? key.slice('collection:'.length) : null;
|
|
266
272
|
const object = collection === null && key !== '' ? key : null;
|
|
273
|
+
if (readFor !== key) {
|
|
274
|
+
readGeneration += 1;
|
|
275
|
+
inFlight.clear();
|
|
276
|
+
}
|
|
277
|
+
const generation = readGeneration;
|
|
278
|
+
const request = ++contextRequest;
|
|
267
279
|
readFor = key;
|
|
268
|
-
publish({ loading: true, object, collection });
|
|
280
|
+
publish({ loading: true, object, collection, context: null, error: null, views: new Map() });
|
|
269
281
|
try {
|
|
270
282
|
const context = await blenderRnaContext(object ?? undefined, collection ?? undefined);
|
|
271
|
-
if (
|
|
283
|
+
if (generation !== readGeneration || request !== contextRequest) return;
|
|
272
284
|
// ALWAYS a tab-rail notification, and that is a correction rather than a
|
|
273
285
|
// convenience: the gate a tab matches on is BOTH the subject this context
|
|
274
286
|
// was read for and the tab list, so comparing only the list missed the
|
|
@@ -279,7 +291,7 @@ async function readContext(key: string): Promise<void> {
|
|
|
279
291
|
// disappeared on the first selection and only came back on a re-select.
|
|
280
292
|
publish({ context, loading: false, error: null }, true);
|
|
281
293
|
} catch (error) {
|
|
282
|
-
if (
|
|
294
|
+
if (generation !== readGeneration || request !== contextRequest) return;
|
|
283
295
|
publish({ context: null, loading: false, error: describe(error) }, true);
|
|
284
296
|
}
|
|
285
297
|
}
|
|
@@ -305,8 +317,10 @@ export function blenderRnaViewFor(path: string): BlenderRnaView | undefined {
|
|
|
305
317
|
if (held !== undefined) return held;
|
|
306
318
|
if (inFlight.has(path) || !blenderSessionStarted()) return undefined;
|
|
307
319
|
inFlight.add(path);
|
|
320
|
+
const generation = readGeneration;
|
|
308
321
|
void blenderRna(path)
|
|
309
322
|
.then((view) => {
|
|
323
|
+
if (generation !== readGeneration) return;
|
|
310
324
|
inFlight.delete(path);
|
|
311
325
|
if (view === null) return;
|
|
312
326
|
const views = new Map(state.views);
|
|
@@ -314,6 +328,7 @@ export function blenderRnaViewFor(path: string): BlenderRnaView | undefined {
|
|
|
314
328
|
publish({ views, error: null });
|
|
315
329
|
})
|
|
316
330
|
.catch((error: unknown) => {
|
|
331
|
+
if (generation !== readGeneration) return;
|
|
317
332
|
inFlight.delete(path);
|
|
318
333
|
publish({ error: describe(error) });
|
|
319
334
|
});
|
|
@@ -650,13 +650,24 @@ let captureLifetime: AbortController | null = null;
|
|
|
650
650
|
* terminate, and the host door only exists inside a running editor. */
|
|
651
651
|
let watchingSessionEnd = false;
|
|
652
652
|
|
|
653
|
+
/** Diagnostics must not prevent teardown if the host has already detached. */
|
|
654
|
+
function beginBlenderWork(label: string): () => void {
|
|
655
|
+
try {
|
|
656
|
+
const end = editorHost().session.beginWork(`Blender: ${label}`);
|
|
657
|
+
return () => { try { end(); } catch { /* preserve the operation's result */ } };
|
|
658
|
+
} catch { return () => {}; }
|
|
659
|
+
}
|
|
660
|
+
|
|
653
661
|
function terminateBlenderRuntime(): void {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
662
|
+
const end = beginBlenderWork('invalidating history and releasing runtime');
|
|
663
|
+
try {
|
|
664
|
+
invalidateBlenderHistory();
|
|
665
|
+
captureLifetime?.abort();
|
|
666
|
+
captureLifetime = null;
|
|
667
|
+
runtime?.terminate();
|
|
668
|
+
runtime = null;
|
|
669
|
+
lastCapture = null;
|
|
670
|
+
} finally { end(); }
|
|
660
671
|
}
|
|
661
672
|
|
|
662
673
|
// THE ENGINE DIES WITH THE SESSION, whether or not the tab can. A page told
|
|
@@ -707,6 +718,7 @@ export function blenderRuntime(): BlenderRuntime {
|
|
|
707
718
|
captureLifetime = lifetime;
|
|
708
719
|
let photographing = false;
|
|
709
720
|
runtime = new BlenderRuntime({
|
|
721
|
+
work: beginBlenderWork,
|
|
710
722
|
history: (entries) => {
|
|
711
723
|
const owner = runtime;
|
|
712
724
|
if (!owner) throw new Error('Blender history arrived before its runtime');
|
|
@@ -736,7 +748,9 @@ export function blenderRuntime(): BlenderRuntime {
|
|
|
736
748
|
},
|
|
737
749
|
present: async (frame, description, capture) => {
|
|
738
750
|
const documentId = presentationDocumentId();
|
|
739
|
-
const
|
|
751
|
+
const endWait = beginBlenderWork('waiting for Model presenter');
|
|
752
|
+
let view: RuntimeView;
|
|
753
|
+
try { view = await runtimeView(); } finally { endWait(); }
|
|
740
754
|
lifetime.signal.throwIfAborted();
|
|
741
755
|
if (presentationDocumentId() !== documentId)
|
|
742
756
|
throw new Error('Blender document changed before its frame could be presented');
|
|
@@ -747,7 +761,11 @@ export function blenderRuntime(): BlenderRuntime {
|
|
|
747
761
|
// still-running worker (`blender-runtime-view.ts::applyFrame` says how).
|
|
748
762
|
// A document that answers `applyFrame` without one reports no `held` at
|
|
749
763
|
// all, rather than a wrong `null`.
|
|
750
|
-
const
|
|
764
|
+
const endApply = beginBlenderWork('applying frame to Model');
|
|
765
|
+
const applied = (() => {
|
|
766
|
+
try { return view.applyFrame(frame) as { held?: unknown } | null | undefined; }
|
|
767
|
+
finally { endApply(); }
|
|
768
|
+
})();
|
|
751
769
|
const reports = typeof applied === 'object' && applied !== null && 'held' in applied;
|
|
752
770
|
const held = reports ? (applied.held as { session: string; revision: number } | null) : null;
|
|
753
771
|
const answer = (capture: unknown): PresentAnswer => ({
|
|
@@ -893,6 +911,7 @@ export function blenderRuntime(): BlenderRuntime {
|
|
|
893
911
|
await snapshot.prepare(renderCamera);
|
|
894
912
|
assertBinding();
|
|
895
913
|
const captureOptions = {
|
|
914
|
+
effect: snapshot.effect,
|
|
896
915
|
width: render.width,
|
|
897
916
|
height: render.height,
|
|
898
917
|
transparent: render.transparent === true,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@volter/editor-blender",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
4
|
"license": "AGPL-3.0-only AND GPL-3.0-or-later",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.6",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
]
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@volter/blender-engine": "0.1.
|
|
65
|
-
"@volter/editor-threejs": "0.5.
|
|
64
|
+
"@volter/blender-engine": "0.1.6",
|
|
65
|
+
"@volter/editor-threejs": "0.5.63",
|
|
66
66
|
"zod": "^4.3.6"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@volter/editor-sdk": "
|
|
70
|
-
"@volter/editor-project": "
|
|
69
|
+
"@volter/editor-sdk": "0.5.63",
|
|
70
|
+
"@volter/editor-project": "0.5.63",
|
|
71
71
|
"react": "^19.0.0",
|
|
72
72
|
"three": "^0.180.0"
|
|
73
73
|
},
|