@volter/editor-blender 0.1.151 → 0.1.152
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/contributions/blender-model-opening.tsx +25 -51
- package/contributions/blender-node-editor.document.tsx +2 -1
- package/contributions/blender-play-area.tsx +3 -4
- package/contributions/blender-properties-model.ts +44 -1
- package/contributions/blender-properties-view.tsx +24 -34
- package/contributions/blender-runtime.document.tsx +59 -385
- package/contributions/blender-timeline.document.tsx +6 -4
- package/contributions/blender-version.status.tsx +2 -8
- package/contributions/blender.page.ts +12 -11
- package/contributions/blender.status.tsx +1 -7
- package/contributions/models.finder.ts +4 -11
- package/dist-node/serving.mjs +0 -2
- package/host/blender-runtime-host.ts +290 -76
- package/node/blender-mcp.ts +3 -2
- package/package.json +56 -44
- package/serving/blender-routes.ts +0 -5
- package/src/api.ts +2 -2
- package/src/play-mode.ts +10 -65
- package/src/play-tool.ts +44 -0
- package/src/model-play-extension.ts +0 -173
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
49
|
-
/** The bottom area's mode
|
|
50
|
-
playMode: {
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist-node/serving.mjs
CHANGED
|
@@ -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);
|