@volter/editor-blender 0.1.151 → 0.1.153

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.
@@ -27,7 +27,7 @@ import {
27
27
  } from '../src/timeline-view-state';
28
28
  import { blenderSkin, blenderSkinVersion, subscribeBlenderSkin } from './blender-runtime-skin';
29
29
  import { PLAY_PANEL_RATIO, PlayModeSwitch } from './blender-play-area';
30
- import { modelPlayExtension, subscribeModelPlayExtension } from '../src/model-play-extension';
30
+ import { playTool, subscribePlayTool } from '../src/play-tool';
31
31
  import { AnimationEditorMenu, BlenderActionEditor, BlenderNlaEditor } from './blender-animation-editors';
32
32
  import { modelPlayMode, playModeVersion, servedModelDocument, subscribePlayMode } from '../src/play-mode';
33
33
  import {
@@ -293,17 +293,19 @@ export const TIMELINE_VIEW_VERBS: readonly ViewVerb[] = [
293
293
  /** This area's id in `model.layout.ts`, the one workspace that places this document. */
294
294
  const BOTTOM_AREA = 'timeline';
295
295
 
296
- /** The bottom area swaps animation editors for the Play tool’s Panel. Each mode applies its own area ratio; manual resizing holds until the next switch. Animation unmounts during Game mode and keeps any existing rig bind. */
296
+ /** The bottom area is the Play tool’s Panel under every document but a model in its Animation mode, which shows Blender's animation editors. Each mode applies its own area ratio; manual resizing holds until the next switch. Animation unmounts during Game mode and keeps any existing rig bind. */
297
297
  export default function BlenderBottomArea() {
298
298
  useSyncExternalStore(subscribePlayMode, playModeVersion, playModeVersion);
299
299
  useSyncExternalStore(subscribeTimelineView, timelineViewVersion, timelineViewVersion);
300
300
  const documentId = servedModelDocument();
301
- const game = documentId !== null && modelPlayMode(documentId) === 'game';
301
+ const Panel = useSyncExternalStore(subscribePlayTool, () => playTool()?.Panel ?? null, () => null);
302
+ // (Play is the project's: its Game panel stands under every document, and below a model in its Game mode; a model's
303
+ // Animation mode is Blender's animation editors)
304
+ const game = Panel !== null && (documentId === null || modelPlayMode(documentId) === 'game');
302
305
  const { editor } = timelineViewState();
303
306
  // The Timeline keeps Blender's strip; the Action Editor and NLA list channels, so they take the
304
307
  // Game panel's share, as a person would drag the area up to read them.
305
308
  const tall = game || editor !== 'timeline';
306
- const Panel = useSyncExternalStore(subscribeModelPlayExtension, () => modelPlayExtension()?.Panel ?? null, () => null);
307
309
  useEffect(() => editorHost().workspace.setAreaRatio(BOTTOM_AREA, tall ? PLAY_PANEL_RATIO : null), [tall]);
308
310
  useEffect(() => () => editorHost().workspace.setAreaRatio(BOTTOM_AREA, null), []);
309
311
  if (game) return Panel ? <Panel /> : null;
@@ -1,5 +1,6 @@
1
1
  /**
2
- * The VERSION at the far right of the status bar (`workspace.status`).
2
+ * The VERSION at the far right of the status bar: the item `version`, whose name, side and priority are this
3
+ * package's `contributes.volter.views.statusbar` entry.
3
4
  *
4
5
  * Blender's bottom row ends with a status glyph and `5.2.0` — the app's own
5
6
  * version, always on screen, so "which build am I looking at" is never a
@@ -43,13 +44,6 @@ import { editorHost } from '@volter/sdk/host';
43
44
  import { servingEngine, type ServingEngine } from '@volter/sdk/serving-engine';
44
45
  import { useEffect, useState, useSyncExternalStore } from 'react';
45
46
 
46
- export const point = 'workspace.status';
47
- export const title = 'Version';
48
- export const align = 'right';
49
- /** The back of the contributed band: last item on the right, as in Blender.
50
- * `tool-loader.ts`'s `MAX_DECLARED_ORDER` is 998. */
51
- export const order = 998;
52
-
53
47
  export default function BlenderVersionStatus() {
54
48
  const { project } = editorHost();
55
49
  const version = useSyncExternalStore(
@@ -5,16 +5,15 @@
5
5
  * this package owns, the parts of the Blender document's bottom area that play's Game panel
6
6
  * draws with, and two generic doors onto the document's Blender: its saves, each running a script on the
7
7
  * scene it wrote whose files reach the project (and a save asked for where it changed), and an asset library in its preferences. Nothing more. Its type is `@volter/editor-blender/api`'s `BlenderPageApi`. It also
8
- * registers the package's commands: its session verbs and its views' verbs.
8
+ * registers the package's commands: its session verbs and its views' verbs, and starts Blender as a project that
9
+ * holds a model opens.
9
10
  */
10
11
  import { registerVerbCommands, registerViewCommands } from '@volter/sdk/commands';
11
12
  import { registerHierarchyKinds } from '@volter/sdk/hierarchy-kind-icon';
12
13
  import type { PluginContext } from '@volter/sdk/plugins';
13
- import { flushModelDocument, NODE_VIEW_VERBS, setBlenderAssetLibrary, setModelDocumentSaveScript, subscribeModelDocumentSaved } from '../host/blender-runtime-host';
14
- import { registerModelPlay } from '../src/model-play-extension';
14
+ import { flushModelDocument, heldModelDocument, NODE_VIEW_VERBS, setBlenderAssetLibrary, setModelDocumentSaveScript, startBlenderWithProject, subscribeModelDocumentSaved } from '../host/blender-runtime-host';
15
+ import { registerPlay } from '../src/play-tool';
15
16
  import {
16
- documentOnScreen,
17
- modelDocumentSource,
18
17
  modelPlayMode,
19
18
  playModeOf,
20
19
  playModeVersion,
@@ -44,19 +43,21 @@ export function activate(context: PluginContext) {
44
43
  registerViewCommands(context, 'blender-node-view', NODE_VIEW_VERBS);
45
44
  registerViewCommands(context, 'timeline', TIMELINE_VIEW_VERBS);
46
45
  registerViewCommands(context, 'blender-uv-view', UV_VIEW_VERBS);
46
+ // Blender starts as a project with a model opens, so opening a model costs its own file's load.
47
+ context.subscriptions.push({ dispose: startBlenderWithProject() });
47
48
  return {
48
- registerModelPlay,
49
- /** The bottom area's mode, the served model document, its on-screen wait and its source. */
50
- playMode: { documentOnScreen, modelDocumentSource, modelPlayMode, playModeOf, playModeVersion, servedModelDocument, subscribePlayMode },
49
+ registerPlay,
50
+ /** The bottom area's mode and the model document it serves. */
51
+ playMode: { modelPlayMode, playModeOf, playModeVersion, servedModelDocument, subscribePlayMode },
51
52
  PlayAreaButton,
52
53
  PlayModeSwitch,
53
54
  switchPlayMode,
54
55
  /** The Timeline's chrome, which a panel in the same area draws with. */
55
56
  timelineGeometry: { HEADER_HEIGHT, TIMELINE_CHROME, TIMELINE_THEME },
56
57
  /** The model document's `.blend` as a file: a script every save runs in its own turn on the scene it wrote, each
57
- * save that landed it in the project, with what that script did and the carry of its files, and a save of the
58
- * project's `.blend` where Blender holds it changed (`flush`). */
59
- modelDocument: { setSaveScript: setModelDocumentSaveScript, subscribeSaved: subscribeModelDocumentSaved, flush: flushModelDocument },
58
+ * save that landed it in the project, with what that script did and the carry of its files, a save of the
59
+ * project's `.blend` where Blender holds it changed (`flush`), and the one it holds (`held`). */
60
+ modelDocument: { setSaveScript: setModelDocumentSaveScript, subscribeSaved: subscribeModelDocumentSaved, flush: flushModelDocument, held: heldModelDocument },
60
61
  /** An asset library every Blender session has, until its removal. */
61
62
  setAssetLibrary: setBlenderAssetLibrary,
62
63
  };
@@ -1,10 +1,4 @@
1
- /** Blender-style status hints describe this editor’s viewport mouse bindings: left selects, middle pans, right rotates. They are always visible because there is no hover-scoped keymap publisher. Use SDK theme variables without importing host implementation. */
2
-
3
- export const point = 'workspace.status';
4
- export const title = 'Mouse hints';
5
- export const align = 'left';
6
- /** First among contributed left status items, after the host's built-in items. */
7
- export const order = 0;
1
+ /** Blender-style status hints describe this editor’s viewport mouse bindings: left selects, middle pans, right rotates. They are always visible because there is no hover-scoped keymap publisher. Use SDK theme variables without importing host implementation. The item `mouseHints`, whose name, side and priority are this package's `contributes.volter.views.statusbar` entry. */
8
2
 
9
3
  /** Which button the glyph lights. */
10
4
  type MouseButton = 'left' | 'middle' | 'right';
@@ -14,15 +14,10 @@ export const finder = {
14
14
  .array(z.string().min(1))
15
15
  .min(1)
16
16
  .describe('Project-relative globs of the Blender files to list (`src/models/**/*.blend`)'),
17
- defaultIn: z
18
- .string()
19
- .regex(/^[^/].*\/$/)
20
- .optional()
21
- .describe('A project folder (`levels/`): where exactly one listed Blender file is in it, that file is the default document'),
22
17
  })
23
18
  .strict(),
24
19
  run(
25
- selection: { finder: 'modelsFromBlendFiles'; include: readonly string[]; defaultIn?: string },
20
+ _selection: { finder: 'modelsFromBlendFiles'; include: readonly string[] },
26
21
  input: { files?: readonly string[] },
27
22
  ) {
28
23
  const entries = [];
@@ -38,6 +33,8 @@ export const finder = {
38
33
  authorable: true,
39
34
  reach: { kind: 'root-mount' as const },
40
35
  source: { path },
36
+ // (the Model document opens a `.blend` not written yet as Blender's empty file, and its first save writes it)
37
+ opensUnwritten: true,
41
38
  });
42
39
  }
43
40
  if (entries.length === 0) {
@@ -50,10 +47,6 @@ export const finder = {
50
47
  reach: { kind: 'root-mount' as const },
51
48
  });
52
49
  }
53
- // (the adapter's author names the folder whose lone file is the default: a std game's one level opens as its document)
54
- const within = selection.defaultIn === undefined ? []
55
- : (input.files ?? []).filter((path) => path.endsWith('.blend') && path.startsWith(selection.defaultIn!));
56
- const only = within.length === 1 ? `model:${within[0]!}` : undefined;
57
- return { entries, notes, ...(only === undefined ? {} : { default: only }) };
50
+ return { entries, notes };
58
51
  },
59
52
  } as const;
@@ -352,7 +352,6 @@ function blenderRoutesPlugin(services) {
352
352
  method: "GET",
353
353
  match: /^\/__editor\/blender-wasm\/status$/,
354
354
  handle: async (_req, res) => {
355
- services.allowCrossOriginFrameEmbedding(res);
356
355
  json(res, await blenderWasmStatus());
357
356
  }
358
357
  },
@@ -426,7 +425,6 @@ function blenderRoutesPlugin(services) {
426
425
  match: /^\/__editor\/blender-wasm\/([^/]+)$/,
427
426
  handle: async (req, res, url) => {
428
427
  const file = decodeURIComponent(url.pathname.slice("/__editor/blender-wasm/".length));
429
- services.allowCrossOriginFrameEmbedding(res);
430
428
  const bundled = BLENDER_WASM_FILES.includes(file);
431
429
  if (!bundled && file !== BLENDER_WALI_ARTIFACT) {
432
430
  json(res, { error: `The Blender build has no ${file}.` }, 404);