@volter/editor-blender 0.1.141 → 0.1.142
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 +11 -0
- package/README.md +1 -1
- package/contributions/blender-animation-editors.tsx +387 -0
- package/contributions/blender-game-panel.tsx +25 -24
- package/contributions/blender-header-menus.tsx +2 -2
- package/contributions/blender-icons.source.mjs +1 -1
- package/contributions/blender-model-opening.tsx +3 -3
- package/contributions/blender-node-editor.document.tsx +1 -1
- package/contributions/blender-outliner-authoring.ts +4 -4
- package/contributions/blender-outliner-model.ts +1 -1
- package/contributions/blender-play-skin.ts +48 -6
- package/contributions/blender-pose.ts +7 -0
- package/contributions/blender-properties-context.ts +1 -1
- package/contributions/blender-properties-model.ts +2 -2
- package/contributions/blender-properties-tab.tsx +2 -2
- package/contributions/blender-properties-view.tsx +1 -1
- package/contributions/blender-runtime-skin.ts +35 -1
- package/contributions/blender-runtime.document.tsx +17 -13
- package/contributions/blender-screenshot.menu.ts +3 -3
- package/contributions/blender-timeline-geometry.ts +2 -2
- package/contributions/blender-timeline.document.tsx +41 -10
- package/contributions/blender-uv-editor.document.tsx +1 -1
- package/contributions/blender-version.status.tsx +2 -2
- package/contributions/blender.command.ts +3 -3
- package/contributions/blender.environment.ts +2 -2
- package/contributions/blender.keymap.ts +2 -2
- package/contributions/blender.status.tsx +2 -2
- package/contributions/blender.style.ts +3 -3
- package/contributions/model.layout.ts +2 -2
- package/contributions/models.finder.ts +1 -1
- package/contributions/properties-data.inspector.tsx +1 -1
- package/contributions/properties-scene.inspector.tsx +1 -1
- package/contributions/sculpt.layout.ts +1 -1
- package/contributions/shading.layout.ts +1 -1
- package/contributions/texture.layout.ts +1 -1
- package/contributions/uv-editing.layout.ts +1 -1
- package/dist-node/serving.mjs +2 -2
- package/host/blender-runtime-host.ts +4 -4
- package/node/blender-mcp.ts +3 -3
- package/package.json +6 -6
- package/serving/blender-routes.ts +4 -4
- package/serving/index.ts +2 -2
- package/src/layouts.tsx +3 -3
- package/src/looks.ts +1 -1
- package/src/play-live.ts +27 -0
- package/src/play-mode.ts +23 -15
- package/src/presentation.ts +2 -2
- package/src/regions.ts +1 -1
- package/src/timeline-view-state.ts +7 -1
- package/src/uv-view-state.ts +1 -1
- package/view/blender-view-routes.ts +2 -2
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* The BLENDER style bundle — Blender's greys and widget blue as a palette
|
|
3
3
|
* document (`blender.palette.json`, the same v3 shape a person imports), its
|
|
4
4
|
* own opaque material, and Blender's chrome regions
|
|
5
|
-
* (`@volter/
|
|
5
|
+
* (`@volter/sdk/looks`, a `workspace.style` contribution).
|
|
6
6
|
*
|
|
7
7
|
* The material transcribes Blender's default theme: widgets round at 0.2 of
|
|
8
8
|
* their height (4px on a 20px widget), areas and panels are flat with no
|
|
9
9
|
* shadow, and only menus and popovers cast one (`menu_shadow_fac` 0.5 over
|
|
10
10
|
* `menu_shadow_width` 4). The `large` radius is our reading — Blender rounds
|
|
11
11
|
* its popovers the same as its menus. The density is measured from Blender
|
|
12
|
-
* 5.2's own frames at 1x
|
|
12
|
+
* 5.2's own frames at 1x:
|
|
13
13
|
* 26px top bar and area headers, 24px status bar, and — re-measured against
|
|
14
14
|
* the frames widget by widget — EVERY widget 20px tall, button/field/toggle
|
|
15
15
|
* alike, so all three control sizes are 20 rather than a 18/20/24 ramp
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* A glyph is EDITED in `blender-icons.source.mjs` and the JSON re-emitted by
|
|
38
38
|
* running it; a path `d` is not a thing a person edits by hand.
|
|
39
39
|
*/
|
|
40
|
-
import type { IconSetContribution, StyleContribution } from '@volter/
|
|
40
|
+
import type { IconSetContribution, StyleContribution } from '@volter/sdk/looks';
|
|
41
41
|
import iconsJson from './blender.icons.json';
|
|
42
42
|
import palette from './blender.palette.json';
|
|
43
43
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The MODEL workspace — Blender-shaped modeling: a dominant document, outliner
|
|
3
|
-
* above properties (`@volter/
|
|
3
|
+
* above properties (`@volter/sdk/looks`, a `workspace.layout`
|
|
4
4
|
* contribution). Its arrangement carries Blender 5.2's own MODELING-workspace
|
|
5
5
|
* proportions; `../src/layouts.tsx` states the measurement and the host's
|
|
6
6
|
* fixed-pane clamp that bounds it.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { WorkspaceLayoutContribution } from '@volter/
|
|
9
|
+
import type { WorkspaceLayoutContribution } from '@volter/sdk/looks';
|
|
10
10
|
import { BLENDER_REGIONS } from '../src/regions';
|
|
11
11
|
|
|
12
12
|
export const point = 'workspace.layout';
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* the TypeScript mesh kit was the modeling engine while Blender could not run
|
|
13
13
|
* in the tab; it now does, and a model is Blender data).
|
|
14
14
|
*
|
|
15
|
-
* Select it in `volter.adapter.ts`:
|
|
15
|
+
* Select it in `editor/volter.adapter.ts`:
|
|
16
16
|
*
|
|
17
17
|
* documents: { find: [{ finder: 'modelsFromBlendFiles', include: ['src/models/**\/*.blend'] }] }
|
|
18
18
|
*
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* It stands when: `buttons_context_path_data` — the object HAS data (an Empty
|
|
32
32
|
* usually has none).
|
|
33
33
|
*/
|
|
34
|
-
import type { ToolContributionNode } from '@volter/
|
|
34
|
+
import type { ToolContributionNode } from '@volter/sdk/contributions';
|
|
35
35
|
import { blenderPropertiesState, resolveBlenderSubject } from './blender-properties-model';
|
|
36
36
|
import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
|
|
37
37
|
import type { BlenderCuratedPanel } from './blender-properties-view';
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
|
|
37
37
|
import type { BlenderCuratedPanel } from './blender-properties-view';
|
|
38
38
|
import { resolveBlenderSubject } from './blender-properties-model';
|
|
39
|
-
import type { ToolContributionNode } from '@volter/
|
|
39
|
+
import type { ToolContributionNode } from '@volter/sdk/contributions';
|
|
40
40
|
|
|
41
41
|
/** `properties_scene.py`, panel by panel. Line numbers are that file's. */
|
|
42
42
|
const CURATED: readonly BlenderCuratedPanel[] = [
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* chrome); it keeps its OWN persisted layout the moment a person touches it.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { WorkspaceLayoutContribution } from '@volter/
|
|
8
|
+
import type { WorkspaceLayoutContribution } from '@volter/sdk/looks';
|
|
9
9
|
import { BLENDER_REGIONS } from '../src/regions';
|
|
10
10
|
|
|
11
11
|
export const point = 'workspace.layout';
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
* contribution exists because the node view does.
|
|
53
53
|
*/
|
|
54
54
|
|
|
55
|
-
import type { WorkspaceLayoutContribution } from '@volter/
|
|
55
|
+
import type { WorkspaceLayoutContribution } from '@volter/sdk/looks';
|
|
56
56
|
import { BLENDER_REGIONS } from '../src/regions';
|
|
57
57
|
|
|
58
58
|
export const point = 'workspace.layout';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* layer stack (see `sculpt.layout.ts` for why).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { WorkspaceLayoutContribution } from '@volter/
|
|
7
|
+
import type { WorkspaceLayoutContribution } from '@volter/sdk/looks';
|
|
8
8
|
|
|
9
9
|
export const point = 'workspace.layout';
|
|
10
10
|
export const layout: WorkspaceLayoutContribution = {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* this contribution exists because the UV view does.
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
|
-
import type { WorkspaceLayoutContribution } from '@volter/
|
|
48
|
+
import type { WorkspaceLayoutContribution } from '@volter/sdk/looks';
|
|
49
49
|
import { BLENDER_REGIONS } from '../src/regions';
|
|
50
50
|
|
|
51
51
|
export const point = 'workspace.layout';
|
package/dist-node/serving.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { tmpdir as tmpdir2 } from "node:os";
|
|
|
6
6
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
7
7
|
import { createBrotliDecompress } from "node:zlib";
|
|
8
8
|
|
|
9
|
-
// ../
|
|
9
|
+
// ../sdk/src/project/output-roots.ts
|
|
10
10
|
var PROJECT_OUTPUT_ROOTS = ["public", "references"];
|
|
11
11
|
function normalizeSlashes(path) {
|
|
12
12
|
const slashed = path.replaceAll("\\", "/");
|
|
@@ -24,7 +24,7 @@ function projectOutputRootOf(path) {
|
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// ../
|
|
27
|
+
// ../sdk/src/session/relative-path-guard.ts
|
|
28
28
|
function relativePathRejection(path) {
|
|
29
29
|
if (!path || path.trim() === "") return "empty";
|
|
30
30
|
if (path.includes("\0")) return "nul";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* the Code-OSS frame, item 6). Its thirteen verbs are
|
|
5
5
|
* `../contributions/blender.command.ts`, a `workspace.command` contribution;
|
|
6
6
|
* what it needs of the running editor it asks THROUGH THE DOOR,
|
|
7
|
-
* `@volter/
|
|
7
|
+
* `@volter/sdk/host` — the document context it
|
|
8
8
|
* presents into, the session it lives and dies with. It imports no host module
|
|
9
9
|
* (WORKBENCH.md §The invariants, "Direction": a package imports only the SDK
|
|
10
10
|
* and other packages' exports), which is what lets this lane ship against a
|
|
@@ -43,9 +43,9 @@ import type {
|
|
|
43
43
|
} from '@volter/blender-engine/browser/rna';
|
|
44
44
|
import { createBlenderDisplayTransform, blenderDisplaySettingsForRender } from '@volter/blender-engine/browser/three/blender-display-transform';
|
|
45
45
|
import { BlenderRuntimeView } from '@volter/blender-engine/browser/three/blender-runtime-view';
|
|
46
|
-
import type { EditorCommandResult } from '@volter/
|
|
47
|
-
import { editorHost } from '@volter/
|
|
48
|
-
import { invokeViewVerb, type ViewVerbContribution } from '@volter/
|
|
46
|
+
import type { EditorCommandResult } from '@volter/sdk/commands';
|
|
47
|
+
import { editorHost } from '@volter/sdk/host';
|
|
48
|
+
import { invokeViewVerb, type ViewVerbContribution } from '@volter/sdk/views';
|
|
49
49
|
import {
|
|
50
50
|
captureSceneImage,
|
|
51
51
|
captureSceneLinear,
|
package/node/blender-mcp.ts
CHANGED
|
@@ -22,9 +22,9 @@ import {
|
|
|
22
22
|
ListPromptsRequestSchema,
|
|
23
23
|
ListToolsRequestSchema,
|
|
24
24
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
25
|
-
import { EditorClient, type EditorView } from '@volter/
|
|
25
|
+
import { EditorClient, type EditorView } from '@volter/sdk';
|
|
26
26
|
import blenderBundle from '@volter/blender-engine/wasm/BUNDLE.json';
|
|
27
|
-
import { projectOutputRootOf } from '@volter/
|
|
27
|
+
import { projectOutputRootOf } from '@volter/sdk/project/output-roots';
|
|
28
28
|
import blenderDefaults from './blender-mcp-defaults.json';
|
|
29
29
|
import blenderTools from './blender-mcp-tools.json';
|
|
30
30
|
|
|
@@ -266,7 +266,7 @@ class TabSession {
|
|
|
266
266
|
if (!this.#connecting) {
|
|
267
267
|
this.#connecting = (async () => {
|
|
268
268
|
await this.ensureEditor();
|
|
269
|
-
const { resolveSession } = await import('@volter/
|
|
269
|
+
const { resolveSession } = await import('@volter/live');
|
|
270
270
|
const session = await resolveSession(this.project);
|
|
271
271
|
this.#port = session.port;
|
|
272
272
|
this.#origin = `http://127.0.0.1:${session.port}`;
|
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.142",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
@@ -72,15 +72,15 @@
|
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
75
|
-
"@volter/blender-engine": "0.1.
|
|
76
|
-
"@volter/editor-threejs": "0.5.
|
|
75
|
+
"@volter/blender-engine": "0.1.140",
|
|
76
|
+
"@volter/editor-threejs": "0.5.203",
|
|
77
77
|
"zod": "^4.3.6",
|
|
78
78
|
"@fortawesome/free-solid-svg-icons": "^7.2.0"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@volter/
|
|
82
|
-
"@volter/
|
|
83
|
-
"@volter/
|
|
81
|
+
"@volter/live": "0.5.203",
|
|
82
|
+
"@volter/project": "0.5.203",
|
|
83
|
+
"@volter/sdk": "0.5.203",
|
|
84
84
|
"react": "^19.0.0",
|
|
85
85
|
"react-dom": "^19.0.0",
|
|
86
86
|
"three": "^0.180.0",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* bytes it produced reach the project (a shipped output, the session's `.blend`). They were
|
|
6
6
|
* the kit's own routes; the kit now names no Blender, and reaches these only because the
|
|
7
7
|
* product that composes this package declares its serving module. Writes go through the kit's
|
|
8
|
-
* services (`@volter/
|
|
8
|
+
* services (`@volter/sdk/session/project-serving`), so they are attributed and recorded
|
|
9
9
|
* like every other project write.
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -16,9 +16,9 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
|
16
16
|
import { tmpdir } from 'node:os';
|
|
17
17
|
import { dirname, join } from 'node:path';
|
|
18
18
|
import { createBrotliDecompress } from 'node:zlib';
|
|
19
|
-
import { projectOutputRootOf } from '@volter/
|
|
20
|
-
import { isContainedRelativePath } from '@volter/
|
|
21
|
-
import type { ProjectServingServices } from '@volter/
|
|
19
|
+
import { projectOutputRootOf } from '@volter/sdk/project/output-roots';
|
|
20
|
+
import { isContainedRelativePath } from '@volter/sdk/session/relative-path-guard';
|
|
21
|
+
import type { ProjectServingServices } from '@volter/sdk/session/project-serving';
|
|
22
22
|
import type { Plugin } from 'vite';
|
|
23
23
|
import {
|
|
24
24
|
BLENDER_WALI_ARTIFACT,
|
package/serving/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@volter/editor-blender`'s server half (`package.json#volter.serving`, the project-serving door in
|
|
3
|
-
* `@volter/
|
|
3
|
+
* `@volter/sdk/session/project-serving`): the routes the Blender in the tab reads and
|
|
4
4
|
* writes through.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ProjectServingModule } from '@volter/
|
|
7
|
+
import type { ProjectServingModule } from '@volter/sdk/session/project-serving';
|
|
8
8
|
import { blenderRoutesPlugin } from './blender-routes';
|
|
9
9
|
import { BLENDER_WALI_ARTIFACT, BLENDER_WASM_FILES, blenderWasmStatus } from './blender-wasm-artifact';
|
|
10
10
|
|
package/src/layouts.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The BLENDER layout — the composition a models project's adapter declares
|
|
3
|
-
* (`volter.adapter.ts`: `editor: { Layout: ModelLayout }`, the door
|
|
4
|
-
* `@volter/
|
|
3
|
+
* (`editor/volter.adapter.ts`: `editor: { Layout: ModelLayout }`, the door
|
|
4
|
+
* `@volter/sdk/layouts` documents). `ModelArrangement` names the Model
|
|
5
5
|
* workspace and the chrome regions it shows; Sculpt and Texture open on it too
|
|
6
6
|
* — their reserved panels (the brush rail, the layer stack) ship WITH their
|
|
7
7
|
* programs, never as empty chrome, and until then nothing about a sculpt
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
EditorHeader,
|
|
22
22
|
Workspace,
|
|
23
23
|
type WorkspaceArrangement,
|
|
24
|
-
} from '@volter/
|
|
24
|
+
} from '@volter/sdk/layouts';
|
|
25
25
|
import { BLENDER_REGIONS } from './regions';
|
|
26
26
|
|
|
27
27
|
export const ModelArrangement: WorkspaceArrangement = {
|
package/src/looks.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Blender skew's LOOK and BINDINGS as importable values — `@volter/editor-blender/looks`,
|
|
3
|
-
* the public surface a project's `volter.adapter.ts` declares them from
|
|
3
|
+
* the public surface a project's `editor/volter.adapter.ts` declares them from
|
|
4
4
|
* (ARCHITECTURE-CORE §Adapters and contributions are code, not configs):
|
|
5
5
|
*
|
|
6
6
|
* editor: { Layout: ModelLayout, style: blenderStyle, keymap: blenderKeymap }
|
package/src/play-live.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE RUNNING GAME'S ANIMATION, for the animation editors. A game poses its copy of the model
|
|
3
|
+
* (`../contributions/blender-play-skin.ts`); while it runs, the Action Editor and the NLA editor
|
|
4
|
+
* show what it is playing, read-only, the way a game engine's animator view highlights the live
|
|
5
|
+
* state. The run sets the slot when its animation starts and clears it when it ends.
|
|
6
|
+
*/
|
|
7
|
+
import type { PlayAnimation } from '../contributions/blender-play-skin';
|
|
8
|
+
|
|
9
|
+
let current: PlayAnimation | null = null;
|
|
10
|
+
const listeners = new Set<() => void>();
|
|
11
|
+
|
|
12
|
+
export function setLiveAnimation(animation: PlayAnimation | null): void {
|
|
13
|
+
if (current === animation) return;
|
|
14
|
+
current = animation;
|
|
15
|
+
for (const listener of [...listeners]) listener();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The running game's animation, or null when no game runs. */
|
|
19
|
+
export function liveAnimation(): PlayAnimation | null {
|
|
20
|
+
return current;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Fires when a game's animation starts or ends (not on each frame: readers poll while it runs). */
|
|
24
|
+
export function subscribeLiveAnimation(listener: () => void): () => void {
|
|
25
|
+
listeners.add(listener);
|
|
26
|
+
return () => listeners.delete(listener);
|
|
27
|
+
}
|
package/src/play-mode.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GAME OR
|
|
3
|
-
*
|
|
4
|
-
* button at the top … allow the user to flip between game and movie mode").
|
|
2
|
+
* GAME OR ANIMATION — what a model document's bottom area shows. It is not mutually exclusive:
|
|
3
|
+
* animation is used by games too.
|
|
5
4
|
*
|
|
6
|
-
* - **
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* - **Animation** is Blender's animation editors: the Timeline, the Dope Sheet's Action Editor and
|
|
6
|
+
* the NLA editor (`../contributions/blender-animation-editors.tsx`). While a game runs they show
|
|
7
|
+
* what the game is animating, read-only.
|
|
8
|
+
* - **Game** is the Game panel (`../contributions/blender-game-panel.tsx`): the ONE Play, and the
|
|
9
|
+
* run's pause, step, speed, restart and clock.
|
|
10
|
+
*
|
|
11
|
+
* They are two views of one bottom area, not two exclusive states: switching to Animation leaves a
|
|
12
|
+
* running game running.
|
|
10
13
|
*
|
|
11
14
|
* The switch is the bottom area's own, at the leading edge of its header row in the Game panel
|
|
12
15
|
* and the Timeline alike (`PlayModeSwitch`, `blender-game-panel.tsx`); this module is the state
|
|
13
16
|
* both sides read. A document opens as a
|
|
14
17
|
* Game when its model has a play script (`*.play.ts` beside the `.blend`, as the Play tool finds
|
|
15
|
-
* it — `DocumentPlayExtension.hasScript`) and
|
|
18
|
+
* it — `DocumentPlayExtension.hasScript`) and in Animation otherwise, and the person's own pick
|
|
16
19
|
* then stands for that document for the browser session, kept the way `area-split.ts` keeps the
|
|
17
20
|
* split, per document id — in session storage rather than local, because the default is what a
|
|
18
21
|
* fresh session should start from.
|
|
@@ -26,9 +29,14 @@
|
|
|
26
29
|
import {
|
|
27
30
|
documentPlayExtension,
|
|
28
31
|
subscribeDocumentPlayExtensions,
|
|
29
|
-
} from '@volter/
|
|
32
|
+
} from '@volter/sdk/kit/document-play-extension';
|
|
33
|
+
|
|
34
|
+
export type ModelPlayMode = 'game' | 'animation';
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
/** A mode as asked for, or null for anything else. */
|
|
37
|
+
export function playModeOf(value: unknown): ModelPlayMode | null {
|
|
38
|
+
return value === 'game' || value === 'animation' ? value : null;
|
|
39
|
+
}
|
|
32
40
|
|
|
33
41
|
const STORAGE_PREFIX = 'volter.blender.play-mode:';
|
|
34
42
|
const listeners = new Set<() => void>();
|
|
@@ -47,7 +55,7 @@ function publish(): void {
|
|
|
47
55
|
function stored(documentId: string): ModelPlayMode | null {
|
|
48
56
|
try {
|
|
49
57
|
const value = globalThis.sessionStorage?.getItem(STORAGE_PREFIX + documentId);
|
|
50
|
-
return value
|
|
58
|
+
return playModeOf(value);
|
|
51
59
|
} catch {
|
|
52
60
|
return null;
|
|
53
61
|
}
|
|
@@ -103,18 +111,18 @@ export function modelDocumentSource(documentId: string): string | undefined {
|
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
/**
|
|
106
|
-
* THE MODE A DOCUMENT IS IN:
|
|
114
|
+
* THE MODE A DOCUMENT IS IN: Animation whenever no Play tool is installed (there is no game to
|
|
107
115
|
* switch to), else the person's pick, else Game exactly when the model has a play script. A
|
|
108
116
|
* script not yet looked for counts as none, so a document opens on the Timeline and turns to
|
|
109
117
|
* the Game panel when the answer arrives.
|
|
110
118
|
*/
|
|
111
119
|
export function modelPlayMode(documentId: string): ModelPlayMode {
|
|
112
120
|
const extension = documentPlayExtension('model');
|
|
113
|
-
if (extension === null) return '
|
|
121
|
+
if (extension === null) return 'animation';
|
|
114
122
|
const pick = chosenPlayMode(documentId);
|
|
115
123
|
if (pick !== null) return pick;
|
|
116
124
|
const blend = sources.get(documentId);
|
|
117
|
-
return blend !== undefined && extension.hasScript?.(blend) === true ? 'game' : '
|
|
125
|
+
return blend !== undefined && extension.hasScript?.(blend) === true ? 'game' : 'animation';
|
|
118
126
|
}
|
|
119
127
|
|
|
120
128
|
export function playModeVersion(): number {
|
|
@@ -128,7 +136,7 @@ export function playModeVersion(): number {
|
|
|
128
136
|
* takes (`useSyncExternalStore(subscribePlayMode, playModeVersion)`): a script found after the
|
|
129
137
|
* first render, a run starting or stopping, a restart. Handing the tool's publications straight to
|
|
130
138
|
* the reader's listener re-ran a snapshot that had not changed, so React kept the old render — a
|
|
131
|
-
* document with a play script stayed in
|
|
139
|
+
* document with a play script stayed in Animation, and the Game panel kept showing Stop after the game
|
|
132
140
|
* had stopped. One subscription to the tool's registry, held while anything reads this store,
|
|
133
141
|
* bumps the version and then tells the readers.
|
|
134
142
|
*/
|
package/src/presentation.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HOW THE BLENDER STAGE BEHAVES, as its starting presentation (`@volter/
|
|
2
|
+
* HOW THE BLENDER STAGE BEHAVES, as its starting presentation (`@volter/sdk/kit/
|
|
3
3
|
* viewport-presentation`): this package builds the `model` stage, so it states the stage's
|
|
4
4
|
* function there, beneath every person's choice and never in the look (ARCHITECTURE.md rule 7).
|
|
5
5
|
*/
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
DOCUMENT_STUDIO_PRESET,
|
|
8
8
|
registerStartingPresentation,
|
|
9
9
|
type ViewportCamera,
|
|
10
|
-
} from '@volter/
|
|
10
|
+
} from '@volter/sdk/kit/viewport-presentation';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* A 3D View's field of view for its lens, as Blender computes it (`BKE_camera_params_from_view3d`,
|
package/src/regions.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* which regions exist and what a hierarchy row spells are function, the workspace's, never the
|
|
4
4
|
* look's). Each of this package's layouts spreads these beneath its own.
|
|
5
5
|
*/
|
|
6
|
-
import type { WorkspaceLayoutContribution } from '@volter/
|
|
6
|
+
import type { WorkspaceLayoutContribution } from '@volter/sdk/looks';
|
|
7
7
|
|
|
8
8
|
export const BLENDER_REGIONS: NonNullable<WorkspaceLayoutContribution['regions']> = {
|
|
9
9
|
workspaceTabs: 'shown',
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* is about the DRAWING.
|
|
11
11
|
*
|
|
12
12
|
* WHAT DRIVES IT IS NOT A SESSION VERB. U8's ruling 1 (2026-09-19): every view
|
|
13
|
-
* publishes `volter.<view>.<verb>` commands through `@volter/
|
|
13
|
+
* publishes `volter.<view>.<verb>` commands through `@volter/sdk/views`, one
|
|
14
14
|
* table behind the frame's command service and standalone `cyclotron edit`'s
|
|
15
15
|
* session verb — so this view adds no `blender-*` verb of its own.
|
|
16
16
|
*
|
|
@@ -90,8 +90,13 @@ export interface TimelineViewState {
|
|
|
90
90
|
* object regardless, in Blender and here.
|
|
91
91
|
*/
|
|
92
92
|
readonly onlySelected: boolean;
|
|
93
|
+
/** Which of Blender's animation editors the bottom area shows in Animation mode: the
|
|
94
|
+
* Timeline, the Dope Sheet's Action Editor, or the NLA editor (Blender's editor-type menu). */
|
|
95
|
+
readonly editor: AnimationEditor;
|
|
93
96
|
}
|
|
94
97
|
|
|
98
|
+
export type AnimationEditor = 'timeline' | 'action' | 'nla';
|
|
99
|
+
|
|
95
100
|
const INITIAL: TimelineViewState = {
|
|
96
101
|
transform: { startFrame: -5, pixelsPerFrame: 8 },
|
|
97
102
|
refusal: null,
|
|
@@ -99,6 +104,7 @@ const INITIAL: TimelineViewState = {
|
|
|
99
104
|
framedAt: 0,
|
|
100
105
|
drawn: null,
|
|
101
106
|
onlySelected: true,
|
|
107
|
+
editor: 'timeline',
|
|
102
108
|
};
|
|
103
109
|
|
|
104
110
|
let state: TimelineViewState = INITIAL;
|
package/src/uv-view-state.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* surface nothing in the product could read or drive.
|
|
7
7
|
*
|
|
8
8
|
* WHAT DRIVES IT IS NOT A SESSION VERB. U8's ruling 1 (2026-09-19): every view
|
|
9
|
-
* publishes `volter.<view>.<verb>` commands through `@volter/
|
|
9
|
+
* publishes `volter.<view>.<verb>` commands through `@volter/sdk/views`,
|
|
10
10
|
* one table behind the frame's command service and standalone `cyclotron edit`'s
|
|
11
11
|
* session verb — so this view adds NO `blender-*` verb of its own, which is
|
|
12
12
|
* exactly what the ruling asked the remaining I5 views to stop paying for.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@volter/editor-blender`'s routes in a LIMITED VIEW (`package.json#volter.viewServing`,
|
|
3
|
-
* `@volter/
|
|
3
|
+
* `@volter/sdk/session/limited-view`): the same requests the Blender in the tab makes of
|
|
4
4
|
* its session (`serving/blender-routes.ts`), answered in the page against the project's files in
|
|
5
5
|
* memory. The engine's own bytes are not here: they are fixed for a build, so the serving module
|
|
6
6
|
* declares them (`viewSnapshotRoutes`) and the view ships what the session answered.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* memory, readable by the editor and the workbench like any other file, gone on reload.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { ViewRoute, ViewServingModule, ViewServingServices } from '@volter/
|
|
13
|
+
import type { ViewRoute, ViewServingModule, ViewServingServices } from '@volter/sdk/session/limited-view';
|
|
14
14
|
|
|
15
15
|
/** The largest chunk a document save sends (`document-chunks.mts`'s cut bound). */
|
|
16
16
|
const DOCUMENT_CHUNK_MAX = 4 * 1024 * 1024;
|