@volter/editor-blender 0.1.77 → 0.1.79

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.
@@ -61,6 +61,7 @@ import {
61
61
  blenderExecute,
62
62
  blenderViewShading,
63
63
  openModelDocumentBlend,
64
+ modelDocumentMayOpen,
64
65
  } from '../host/blender-runtime-host';
65
66
  import { BlenderObjectModeHeader } from './blender-header-menus';
66
67
  import { blenderOutlinerAuthoringFor, createBlenderOutlinerAuthoring } from './blender-outliner-authoring';
@@ -215,7 +216,16 @@ export default function BlenderModelDocument(props: ToolContributionProps) {
215
216
  const unbind = bindModelDocument(binding);
216
217
  const publish = () => { unpublish = callbacks.current.publishContext?.(view); };
217
218
  setOpened(null);
218
- void (async () => {
219
+ // Native focus can arrive after the contributed pane mounts. A declined
220
+ // open is not an in-flight load: retry when the host activates this model,
221
+ // rather than leaving "Opening model…" latched forever. Utility focus
222
+ // after a successful open must not tear down the model's published view.
223
+ const documents = editorHost().documents;
224
+ let starting = false;
225
+ let finished = false;
226
+ const open = async () => {
227
+ if (cancelled || starting || finished || (binding && !modelDocumentMayOpen(binding))) return;
228
+ starting = true;
219
229
  try {
220
230
  if (binding) {
221
231
  if (!await openModelDocumentBlend(binding, publish)) return;
@@ -223,18 +233,27 @@ export default function BlenderModelDocument(props: ToolContributionProps) {
223
233
  // The standing Model document is the explicit blender-start target.
224
234
  publish();
225
235
  }
226
- if (!cancelled) setOpened({ key, error: null });
236
+ if (!cancelled) {
237
+ finished = true;
238
+ setOpened({ key, error: null });
239
+ }
227
240
  } catch (error) {
228
241
  if (cancelled) return;
242
+ finished = true;
229
243
  unpublish?.();
230
244
  unpublish = undefined;
231
245
  const detail = error instanceof Error ? error.message : String(error);
232
246
  setOpened({ key, error: detail });
233
247
  callbacks.current.notify?.({ tone: 'error', title: `Blender could not open ${blend ?? 'the model'}`, detail });
248
+ } finally {
249
+ starting = false;
234
250
  }
235
- })();
251
+ };
252
+ const unsubscribe = documents.subscribe(() => { void open(); });
253
+ void open();
236
254
  return () => {
237
255
  cancelled = true;
256
+ unsubscribe();
238
257
  unpublish?.();
239
258
  unbind();
240
259
  };
@@ -315,6 +315,15 @@ export function blenderPresentationDocumentId(): string {
315
315
  return presentationDocumentId();
316
316
  }
317
317
 
318
+ /** The selected pane may load while a utility area (Timeline, for example)
319
+ * has keyboard focus. Another model remains an ownership barrier. */
320
+ export function modelDocumentMayOpen(binding: ModelDocumentBinding): boolean {
321
+ if (boundModel !== binding) return false;
322
+ const active = editorHost().documents.activeId();
323
+ const anotherModel = active?.startsWith('document:model:') || active === BLENDER_RUNTIME_DOCUMENT_ID;
324
+ return !anotherModel || active === binding.documentId;
325
+ }
326
+
318
327
  /**
319
328
  * OPEN A `.blend` IN THE ENGINE — the Model document's own call, the WS-F save
320
329
  * path run backwards. `session.py` opens the named file at start and saves
@@ -333,7 +342,7 @@ export async function openModelDocumentBlend(
333
342
  if (!host.session.open()) throw new Error('Opening a model requires an open project session.');
334
343
  const project = host.projectLocalState.projectRootPath();
335
344
  if (project === null) throw new Error('Opening a model requires a project path.');
336
- if (boundModel !== binding || host.documents.activeId() !== binding.documentId) return false;
345
+ if (!modelDocumentMayOpen(binding)) return false;
337
346
  const session = blenderRuntime();
338
347
  // start claims its resource synchronously; its first frame may arrive before
339
348
  // the returned promise resolves. The getter above rejects a conflicting file.
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.77",
5
+ "version": "0.1.79",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
@@ -70,14 +70,14 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "@modelcontextprotocol/sdk": "1.30.0",
73
- "@volter/blender-engine": "0.1.75",
74
- "@volter/editor-threejs": "0.5.137",
73
+ "@volter/blender-engine": "0.1.77",
74
+ "@volter/editor-threejs": "0.5.139",
75
75
  "zod": "^4.3.6"
76
76
  },
77
77
  "peerDependencies": {
78
- "@volter/editor-live": "0.5.137",
79
- "@volter/editor-project": "0.5.137",
80
- "@volter/editor-sdk": "0.5.137",
78
+ "@volter/editor-live": "0.5.139",
79
+ "@volter/editor-project": "0.5.139",
80
+ "@volter/editor-sdk": "0.5.139",
81
81
  "react": "^19.0.0",
82
82
  "react-dom": "^19.0.0",
83
83
  "three": "^0.180.0",