@volter/editor-blender 0.1.150 → 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.
Files changed (93) hide show
  1. package/LICENSE +18 -9
  2. package/README.md +5 -5
  3. package/contributions/blender-animation-editors.tsx +17 -69
  4. package/contributions/blender-display-settings.ts +36 -0
  5. package/contributions/blender-export-gltf.ts +228 -0
  6. package/contributions/blender-export.verbs.ts +39 -0
  7. package/contributions/blender-gltf-export.verbs.ts +312 -0
  8. package/contributions/blender-header-menus.tsx +37 -133
  9. package/contributions/blender-icon-trace.mjs +8 -98
  10. package/contributions/blender-icons.source.mjs +96 -860
  11. package/contributions/blender-mixer-pose.ts +369 -0
  12. package/contributions/blender-model-opening.tsx +26 -56
  13. package/contributions/blender-node-editor.document.tsx +30 -181
  14. package/contributions/blender-node-geometry.ts +44 -230
  15. package/contributions/blender-node-panels.source.mjs +7 -121
  16. package/contributions/blender-outliner-authoring.ts +61 -415
  17. package/contributions/blender-outliner-model.ts +15 -112
  18. package/contributions/blender-play-area.tsx +99 -0
  19. package/contributions/blender-playback-movie.ts +409 -0
  20. package/contributions/blender-pose.ts +18 -368
  21. package/contributions/blender-properties-context.ts +1 -1
  22. package/contributions/blender-properties-model.ts +59 -91
  23. package/contributions/blender-properties-tab.tsx +3 -50
  24. package/contributions/blender-properties-view.tsx +39 -181
  25. package/contributions/blender-remedy.ts +14 -0
  26. package/contributions/blender-runtime-skin.ts +103 -99
  27. package/contributions/blender-runtime.document.tsx +170 -618
  28. package/contributions/blender-scene-mixer.ts +51 -0
  29. package/contributions/blender-screenshot.service.ts +28 -0
  30. package/contributions/blender-timeline-geometry.ts +16 -136
  31. package/contributions/blender-timeline.document.tsx +145 -333
  32. package/contributions/blender-uv-editor.document.tsx +48 -112
  33. package/contributions/blender-uv-geometry.ts +14 -179
  34. package/contributions/blender-version.status.tsx +45 -31
  35. package/contributions/blender.environment.ts +2 -2
  36. package/contributions/blender.icons.json +0 -9
  37. package/contributions/blender.keymap.ts +9 -51
  38. package/contributions/blender.node-panels.json +4 -13
  39. package/contributions/blender.page.ts +64 -0
  40. package/contributions/blender.status.tsx +8 -150
  41. package/contributions/blender.style.ts +25 -152
  42. package/contributions/blender.verbs.ts +52 -0
  43. package/contributions/model.layout.ts +2 -34
  44. package/contributions/models.finder.ts +3 -30
  45. package/contributions/properties-bone-constraints.inspector.tsx +1 -16
  46. package/contributions/properties-bone.inspector.tsx +2 -31
  47. package/contributions/properties-collection.inspector.tsx +1 -34
  48. package/contributions/properties-constraints.inspector.tsx +1 -21
  49. package/contributions/properties-data.inspector.tsx +2 -39
  50. package/contributions/properties-material.inspector.tsx +2 -33
  51. package/contributions/properties-modifiers.inspector.tsx +2 -30
  52. package/contributions/properties-object.inspector.tsx +2 -44
  53. package/contributions/properties-output.inspector.tsx +1 -27
  54. package/contributions/properties-particles.inspector.tsx +2 -46
  55. package/contributions/properties-physics.inspector.tsx +2 -41
  56. package/contributions/properties-render.inspector.tsx +1 -45
  57. package/contributions/properties-scene.inspector.tsx +1 -35
  58. package/contributions/properties-texture.inspector.tsx +1 -49
  59. package/contributions/properties-view-layer.inspector.tsx +1 -29
  60. package/contributions/properties-world.inspector.tsx +2 -39
  61. package/contributions/shading.layout.ts +3 -78
  62. package/contributions/uv-editing.layout.ts +3 -70
  63. package/dist-node/serving.mjs +13 -32
  64. package/host/blender-runtime-host.ts +795 -412
  65. package/host/scene-sound.ts +143 -0
  66. package/node/blender-mcp.ts +52 -95
  67. package/package.json +1221 -46
  68. package/producticons/blender-icons.otf +0 -0
  69. package/producticons/blender-product-icon-theme.json +129 -0
  70. package/serving/blender-routes.ts +9 -48
  71. package/serving/blender-wasm-artifact.ts +29 -166
  72. package/serving/index.ts +1 -1
  73. package/src/api.ts +9 -0
  74. package/src/layouts.tsx +1 -17
  75. package/src/looks.ts +1 -12
  76. package/src/model-document-saving.ts +25 -0
  77. package/src/node-view-state.ts +3 -25
  78. package/src/play-mode.ts +13 -42
  79. package/src/play-tool.ts +44 -0
  80. package/src/presentation.ts +6 -18
  81. package/src/regions.ts +3 -43
  82. package/src/start-document.ts +15 -0
  83. package/src/timeline-view-state.ts +4 -39
  84. package/src/uv-view-state.ts +4 -20
  85. package/themes/blender.json +299 -0
  86. package/view/blender-view-routes.ts +1 -1
  87. package/contributions/blender-game-panel.tsx +0 -809
  88. package/contributions/blender-palette.source.mjs +0 -350
  89. package/contributions/blender-play-skin.ts +0 -508
  90. package/contributions/blender-screenshot.menu.ts +0 -32
  91. package/contributions/blender.command.ts +0 -109
  92. package/contributions/blender.palette.json +0 -102
  93. package/src/play-live.ts +0 -27
@@ -1,23 +1,4 @@
1
- /**
2
- * THE NODE VIEW'S OWN STATE, and the reason it is a module rather than a
3
- * `useState` inside the component.
4
- *
5
- * `editor.document.*` is scoped to the ACTIVE CENTER DOCUMENT by design — its
6
- * own header says so: "a selector resolving outside that document's container
7
- * is refused by a message naming the scope". The node editor is a DRAWER
8
- * utility, so nothing in the product could read or drive it: the sighted door
9
- * (`editor.captureEditorChrome`, `cyclotron screenshot editor`) photographs it, and
10
- * a photograph cannot click.
11
- *
12
- * So the view's actions are SESSION VERBS, which is the pattern the keyboard
13
- * ruling already asks for — a `volter.*` command per action rather than a raw
14
- * listener — and this module is the one place their state lives. The component
15
- * subscribes to it; `blender-node-view` (the command) reads and writes it. One
16
- * store, two readers, no second copy.
17
- *
18
- * It imports NOTHING: the host handler and the contribution both reach it, and
19
- * a store either of them could not import would not be one store.
20
- */
1
+ /** Shared node-view store for the component and blender-node-view command. Keep pan, zoom, focus, drawing measurements and refusal in one React-free module. */
21
2
 
22
3
  export interface NodeViewTransform {
23
4
  /** The tree-space point at the view's centre. */
@@ -48,7 +29,7 @@ export interface NodeViewState {
48
29
  readonly drawn: readonly DrawnNode[];
49
30
  }
50
31
 
51
- export interface DrawnNode {
32
+ interface DrawnNode {
52
33
  readonly name: string;
53
34
  readonly x: number;
54
35
  readonly yTop: number;
@@ -62,10 +43,7 @@ export interface DrawnNode {
62
43
  readonly firstSocketBelowTop: number | null;
63
44
  /** The header's colour, as drawn. */
64
45
  readonly header: string;
65
- /** SOCKET PANELS, as drawn: how many the node stood and how many of those
66
- * are collapsed. A node that declares panels and drew them FLAT instead
67
- * carries the reason in `panelFallback` — the ruling's own condition,
68
- * readable rather than merely displayed. */
46
+ /** Count drawn socket panels and collapsed panels; panelFallback names a flat-layout limitation. */
69
47
  readonly panels: number;
70
48
  readonly collapsedPanels: number;
71
49
  readonly panelFallback: string | null;
package/src/play-mode.ts CHANGED
@@ -1,35 +1,8 @@
1
- /**
2
- * GAME OR ANIMATION — what a model document's bottom area shows. It is not mutually exclusive:
3
- * animation is used by games too.
4
- *
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.
13
- *
14
- * The switch is the bottom area's own, at the leading edge of its header row in the Game panel
15
- * and the Timeline alike (`PlayModeSwitch`, `blender-game-panel.tsx`); this module is the state
16
- * both sides read. A document opens as a
17
- * Game when its model has a play script (`*.play.ts` beside the `.blend`, as the Play tool finds
18
- * it — `DocumentPlayExtension.hasScript`) and in Animation otherwise, and the person's own pick
19
- * then stands for that document for the browser session, kept the way `area-split.ts` keeps the
20
- * split, per document id — in session storage rather than local, because the default is what a
21
- * fresh session should start from.
22
- *
23
- * THE BOTTOM AREA SERVES ONE DOCUMENT, the model document on screen. The Timeline was always
24
- * that (it binds the presented-view singleton, and cannot name a document), but a game panel
25
- * needs the id to drive a run, and clicking into the panel makes the PANEL the active workspace
26
- * document — so the model document announces itself here ({@link noteModelDocument}) rather
27
- * than the panel asking the workspace which document is active.
28
- */
1
+ /** A model's bottom area shows Blender's animation editors or the Play tool's Game panel; switching leaves a running game running. Remember the person's mode per model for this browser session; otherwise a model opens in Game where the project has a game (`PlayTool.hasGame`). The model on screen announces itself, so focusing the bottom area does not change which model it serves. */
29
2
  import {
30
- documentPlayExtension,
31
- subscribeDocumentPlayExtensions,
32
- } from '@volter/sdk/kit/document-play-extension';
3
+ playTool,
4
+ subscribePlayTool,
5
+ } from './play-tool';
33
6
 
34
7
  export type ModelPlayMode = 'game' | 'animation';
35
8
 
@@ -62,7 +35,7 @@ function stored(documentId: string): ModelPlayMode | null {
62
35
  }
63
36
 
64
37
  /** The person's own pick for a document, or `null` when the default stands. */
65
- export function chosenPlayMode(documentId: string): ModelPlayMode | null {
38
+ function chosenPlayMode(documentId: string): ModelPlayMode | null {
66
39
  let value = chosen.get(documentId);
67
40
  if (value === undefined) {
68
41
  value = stored(documentId);
@@ -112,17 +85,16 @@ export function modelDocumentSource(documentId: string): string | undefined {
112
85
 
113
86
  /**
114
87
  * THE MODE A DOCUMENT IS IN: Animation whenever no Play tool is installed (there is no game to
115
- * switch to), else the person's pick, else Game exactly when the model has a play script. A
116
- * script not yet looked for counts as none, so a document opens on the Timeline and turns to
88
+ * switch to), else the person's pick, else Game exactly when the project has a game. A
89
+ * game not yet looked for counts as none, so a document opens on the Timeline and turns to
117
90
  * the Game panel when the answer arrives.
118
91
  */
119
92
  export function modelPlayMode(documentId: string): ModelPlayMode {
120
- const extension = documentPlayExtension('model');
121
- if (extension === null) return 'animation';
93
+ const tool = playTool();
94
+ if (tool === null) return 'animation';
122
95
  const pick = chosenPlayMode(documentId);
123
96
  if (pick !== null) return pick;
124
- const blend = sources.get(documentId);
125
- return blend !== undefined && extension.hasScript?.(blend) === true ? 'game' : 'animation';
97
+ return sources.has(documentId) && tool.hasGame() === true ? 'game' : 'animation';
126
98
  }
127
99
 
128
100
  export function playModeVersion(): number {
@@ -135,15 +107,14 @@ export function playModeVersion(): number {
135
107
  * THE PLAY TOOL'S ANSWERS MOVE THE VERSION TOO, because the version is the snapshot every reader
136
108
  * takes (`useSyncExternalStore(subscribePlayMode, playModeVersion)`): a script found after the
137
109
  * first render, a run starting or stopping, a restart. Handing the tool's publications straight to
138
- * the reader's listener re-ran a snapshot that had not changed, so React kept the old render — a
139
- * document with a play script stayed in Animation, and the Game panel kept showing Stop after the game
140
- * had stopped. One subscription to the tool's registry, held while anything reads this store,
110
+ * the reader's listener would re-run a snapshot that had not changed, so React would keep the old
111
+ * render. One subscription to the tool's registry, held while anything reads this store,
141
112
  * bumps the version and then tells the readers.
142
113
  */
143
114
  let stopToolWatch: (() => void) | null = null;
144
115
  export function subscribePlayMode(listener: () => void): () => void {
145
116
  listeners.add(listener);
146
- stopToolWatch ??= subscribeDocumentPlayExtensions(publish);
117
+ stopToolWatch ??= subscribePlayTool(publish);
147
118
  return () => {
148
119
  listeners.delete(listener);
149
120
  if (listeners.size === 0 && stopToolWatch) {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * PLAY, THE PROJECT'S, as editor-blender's bottom area shows it (`registerPlay` in its API, `docs/adr/plugin-apis.md`):
3
+ * a Play tool (play's) owns its run, its controls and its Game view; the bottom area shows the tool's panel under every
4
+ * document but a model in its Animation mode, and asks the tool whether the project has a game, by which a model opens
5
+ * in Game or Animation (`play-mode.ts`).
6
+ */
7
+ import type { ComponentType } from 'react';
8
+
9
+ export interface PlayTool {
10
+ /** The run's panel: Play, its transport and its log. */
11
+ readonly Panel: ComponentType;
12
+ /** Whether the project has a game to play: `null` while the tool has not looked yet. */
13
+ hasGame(): boolean | null;
14
+ /** Every change of what the tool answers. */
15
+ subscribe(listener: () => void): () => void;
16
+ }
17
+
18
+ let registered: PlayTool | null = null;
19
+ const listeners = new Set<() => void>();
20
+ function publish(): void { for (const listener of listeners) listener(); }
21
+
22
+ /** The registered Play tool, or `null` while none is. */
23
+ export function playTool(): PlayTool | null {
24
+ return registered;
25
+ }
26
+ /** A registration, and every change the registered tool announces. */
27
+ export function subscribePlayTool(listener: () => void): () => void {
28
+ listeners.add(listener);
29
+ return () => { listeners.delete(listener); };
30
+ }
31
+
32
+ /** The API's `registerPlay`: one Play tool at a time. */
33
+ export function registerPlay(tool: PlayTool): () => void {
34
+ if (registered) throw new Error('A Play tool is already registered.');
35
+ registered = tool;
36
+ const stop = tool.subscribe(publish);
37
+ publish();
38
+ return () => {
39
+ stop();
40
+ if (registered !== tool) return;
41
+ registered = null;
42
+ publish();
43
+ };
44
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HOW THE BLENDER STAGE BEHAVES, as its starting presentation (`@volter/sdk/kit/
2
+ * HOW THE BLENDER STAGE BEHAVES, as its starting presentation (`@volter/sdk/
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/sdk/kit/viewport-presentation';
10
+ } from '@volter/sdk/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`,
@@ -22,19 +22,10 @@ export function blenderViewFieldOfView(lens: number): ViewportCamera['fov'] {
22
22
  }
23
23
 
24
24
  const release = registerStartingPresentation('model', {
25
- // BLENDER'S VIEW: the factory `View3D.lens` of 50 mm (read back from Blender 5.2), a 71.5°
26
- // angle on the larger side; and its default user perspective, elevation 26.5° and azimuth
27
- // −23.8° about the up axis, SOLVED from the reference's two floor axes (projected slopes
28
- // +1.0093 for X and −0.1969 for Y in `modeling-edit-none.png`), in three's Y-up frame. A file's
29
- // own saved view and lens are its document's (`blender-runtime.document.tsx`).
25
+ // The factory Blender lens is 50 mm. The default view uses the reference floor-axis orientation in the presenter’s Y-up frame; a saved document supplies its own view and lens.
30
26
  camera: { fov: blenderViewFieldOfView(50), opening: [0.8187, 0.4458, 0.3617] },
31
27
  interaction: {
32
- // BLENDER'S SHELF OPENS ON SELECT BOX, so a selected object carries no transform gizmo until
33
- // one of the four transform tools is armed (`space_toolsystem_toolbar.py`:
34
- // `_defs_view3d_generic.select_box` is the first entry of the Object Mode `_tools_default`
35
- // tuple). Photographed at the engine's pin: `gizmo-select-box.png` has Select Box lit in the
36
- // shelf, the default cube selected and outlined, and nothing at its origin but the 3D cursor
37
- // and the object dot; `gizmo-move.png` has Move lit in the same frame.
28
+ // Open on Select Box, the first Object Mode shelf tool in space_toolsystem_toolbar.py. Show the transform gizmo only when a transform tool is armed.
38
29
  bootTool: 'select',
39
30
  // BLENDER'S SELECT BOX IS A TOUCH TEST: its object-mode box select reads the object-id buffer
40
31
  // under the rectangle, so any drawn part of an object inside it selects that object.
@@ -47,9 +38,7 @@ const release = registerStartingPresentation('model', {
47
38
  // SOLID (`clay`, and Wireframe with it) is lit by Blender's four studio lights, the document's
48
39
  // view-locked studio, and drawn under STANDARD: Blender gives Solid the display's default view
49
40
  // and none of the scene's settings (`draw_color_management.cc`, `ViewTransform`), and the sRGB
50
- // display's default is Standard (`config.ocio`). Blender 5.2's Workbench under Standard gives
51
- // the factory cube 142 / 131 / 112 in the default view and 162 on the front face, agreeing with
52
- // the viewport's own frames (141 / 129 / 111, 161). The scene's AgX is the two modes' below.
41
+ // display's default is Standard (`config.ocio`). Material Preview and Rendered use AgX below.
53
42
  all: {
54
43
  lighting: {
55
44
  source: 'studio',
@@ -93,8 +82,7 @@ const release = registerStartingPresentation('model', {
93
82
  },
94
83
  },
95
84
  overlays: {
96
- // BLENDER DRAWS THE SELECTED OBJECTS' ORIGINS (Overlays › Origins, on by default): the dot at
97
- // the default cube's centre in every reference frame (`gizmo-select-box.png`).
85
+ // Draw the selected objects’ origins when the Origins overlay is enabled.
98
86
  selection: { origins: true },
99
87
  },
100
88
  world: {
package/src/regions.ts CHANGED
@@ -7,53 +7,13 @@ import type { WorkspaceLayoutContribution } from '@volter/sdk/looks';
7
7
 
8
8
  export const BLENDER_REGIONS: NonNullable<WorkspaceLayoutContribution['regions']> = {
9
9
  workspaceTabs: 'shown',
10
- // Blender has no document tab strip over its areas (the host's own note
11
- // in `workspace-presets.ts` says so; the bundle contradicted it).
12
10
  header: 'shown',
13
11
  shelf: 'shown',
14
12
  inspector: 'properties',
15
- // Measured on `modeling.png`: Blender's top-right corner carries two quiet
16
- // wells (Scene, ViewLayer) and nothing brighter than the 216 its labels
17
- // ink at. Ours drew a yellow FPS/ms readout, an orange sparkline and a lit
18
- // audio meter there — the brightest, highest-chroma thing in the frame and
19
- // the first place the eye lands. The numbers keep their door (the Profiler
20
- // utility, which the readout's own tooltip names).
21
- telemetry: 'hidden',
22
- // Measured on `outliner.png`: Scene Collection, Collection, Camera, Cube
23
- // and Light each carry their NAME and nothing after it — no type text
24
- // anywhere in the frame. The type is the GLYPH, which is why the glyph is
25
- // coloured by category in the first place; a row that also spelled the
26
- // type would say it twice. Ours printed `Hero Box ·HeroBox` at
27
- // `content.dim` in a 10px face, one `space-2` past the name.
13
+ // Hide hierarchy type suffixes; glyph categories identify row types.
28
14
  hierarchyTypeSuffix: 'hidden',
29
- // Measured across all three Outliner frames — `outliner.png` (edit mode),
30
- // `modeling-object-none.png` (object mode, nothing selected) and
31
- // `modeling-object-selected.png` (object mode, everything selected):
32
- // Blender draws NO rule, dotted or solid, under any name in that panel, in
33
- // any selection state. Ours drew an orange dotted rule under every
34
- // component-instance name — six of this scaffold's eleven rows — which is a
35
- // mark with no counterpart in the frame at all. The instance fact keeps its
36
- // doors (the label's tooltip, Go to Callsite / Open Component Source, the
37
- // inspector's component sections); only the paint yields.
15
+ // Hide component-instance underline marks; tooltips, source navigation and inspector sections retain the instance information.
38
16
  hierarchyInstanceRule: 'hidden',
39
- // Measured on `outliner.png`: every object row carries an EYE (CSS ink
40
- // 257.5-270.5, 32 px in from the area's right edge) and a render CAMERA
41
- // (277.5-290.5, 12 px in) on a 20 px column pitch, and no lock glyph
42
- // anywhere — Blender's Disable Selection column is off by default. Ours
43
- // drew a lock 29-41 px in and the eye 6-18 px in, so the LOCK was sitting
44
- // in Blender's eye column and the eye outboard of its camera column. The
45
- // lock goes, and the render column is reserved blank so the eye lands on
46
- // Blender's own; `workspace-regions.ts` carries why it stays blank.
17
+ // Hide the selection-lock column and reserve the render-camera column so the eye occupies Blender’s column.
47
18
  hierarchyRestrictions: 'viewport+render',
48
- // Measured at native 2x on `outliner.png` AND on the Properties area of
49
- // `modeling-edit-none.png` — device x relative to each area's own left
50
- // edge, and the two agree to half a pixel: every Blender area begins with
51
- // an EDITOR-TYPE WELL at x 17..80, i.e. 8.5 CSS in and 32 CSS wide, 20
52
- // tall, its glyph and a chevron inside a 1 px `#3c3c3c` outline over a
53
- // `#272727` inner. It is there whether the area holds one editor or a
54
- // dozen, and its dropdown is how an area changes editor. Ours began with
55
- // a 203 px strip of WORDS, which is what squeezed the Outliner's search
56
- // to 64 px against Blender's 115, truncated the third tab to `Libr`, and
57
- // left the Properties area — a lone panel declaring no header controls —
58
- // with no header band at all.
59
19
  };
@@ -0,0 +1,15 @@
1
+ /** blender-start uses an explicit file first, otherwise the active Model file before the bound pane. During Save As, the active tab can lead pane binding. An unlisted-file follow keeps the session’s new file on the tab it left. */
2
+ export function startDocument(input: {
3
+ /** The file the start names, if any. */
4
+ readonly requested: string | undefined;
5
+ /** The file the editor is showing as the Model: the active tab's, else the bound pane's. */
6
+ readonly shown: string | undefined;
7
+ /** A Save As to an unlisted file, from the tab it left, for this session. */
8
+ readonly follow: { readonly from: string; readonly document: string } | null;
9
+ /** The standing Model's file, for a project with no `.blend` of its own. */
10
+ readonly fallback: string;
11
+ }): string {
12
+ if (input.requested !== undefined) return input.requested;
13
+ const shown = input.shown ?? input.fallback;
14
+ return input.follow !== null && input.follow.from === shown ? input.follow.document : shown;
15
+ }
@@ -1,32 +1,13 @@
1
- /**
2
- * THE TIMELINE'S OWN VIEW STATE — the pan/zoom of its ruler, the last refusal,
3
- * the measured box, and what it drew.
4
- *
5
- * THE PLAYBACK STATE IS NOT HERE. The frame, the play/pause and the keyframe
6
- * columns live in the presenter's `BlenderSkinDirector`
7
- * (`../contributions/blender-runtime-skin.ts`), because that object OWNS the
8
- * `AnimationMixer` and a second copy of "what frame is it" is exactly the
9
- * disagreement the whole design exists to avoid. This module holds only what
10
- * is about the DRAWING.
11
- *
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/sdk/views`, one
14
- * table behind the frame's command service and standalone `cyclotron edit`'s
15
- * session verb — so this view adds no `blender-*` verb of its own.
16
- *
17
- * It imports NOTHING, for the reason the node and UV stores do: the view and
18
- * the verb table both reach it, and a store either of them could not import
19
- * would not be one store.
20
- */
1
+ /** React-free Timeline drawing state: ruler pan/zoom, refusal, measured box and drawn geometry. Playback belongs to BlenderSkinDirector. The page registers actions as volter.<view>.<verb> commands. */
21
2
 
22
- export interface TimelineTransform {
3
+ interface TimelineTransform {
23
4
  /** The FRAME at the view's left edge, and the CSS pixels per frame. Blender's
24
5
  * own `View2D.cur` for a time editor is exactly this pair. */
25
6
  readonly startFrame: number;
26
7
  readonly pixelsPerFrame: number;
27
8
  }
28
9
 
29
- export interface TimelineDrawn {
10
+ interface TimelineDrawn {
30
11
  /** The action being played, and whose keys the summary row draws. */
31
12
  readonly action: string | null;
32
13
  readonly object: string | null;
@@ -72,23 +53,7 @@ export interface TimelineViewState {
72
53
  readonly size: { readonly w: number; readonly h: number };
73
54
  readonly framedAt: number;
74
55
  readonly drawn: TimelineDrawn | null;
75
- /**
76
- * BLENDER'S `show_keys_from_selected_only`, and it is VIEW state here for a
77
- * reason a headless Blender forces: the flag Blender's Timeline reads is
78
- * `Scene.flag & SCE_KEYS_NO_SELONLY` (`anim_filter.cc:254-270`), and this
79
- * surface is an INSPECTION surface that does not write the file — so the
80
- * filter's position is ours to hold and the data it filters on
81
- * (`Object.select_get()`) stays Blender's.
82
- *
83
- * DEFAULTS ON, as Blender's does: `SCE_KEYS_NO_SELONLY` is a NEGATIVE flag
84
- * and a new scene does not carry it, so a factory Blender's Timeline shows
85
- * the selected objects' keys only. The reference read recorded exactly that
86
- * — the probe's own Timeline drew an EMPTY summary row with the rig
87
- * unselected while its Dope Sheet drew all five keys.
88
- *
89
- * IT DECIDES WHAT THE ROW DRAWS, NEVER WHAT PLAYS. Playback moves every
90
- * object regardless, in Blender and here.
91
- */
56
+ /** Only Show Selected is Timeline view state; keyframe subjects still come from Blender’s selection. Default it on, matching Blender’s scene flag, without writing the inspection filter into the file. */
92
57
  readonly onlySelected: boolean;
93
58
  /** Which of Blender's animation editors the bottom area shows in Animation mode: the
94
59
  * Timeline, the Dope Sheet's Action Editor, or the NLA editor (Blender's editor-type menu). */
@@ -1,22 +1,6 @@
1
- /**
2
- * THE UV VIEW'S OWN STATE, and the reason it is a module rather than a
3
- * `useState` inside the component — the same reason `node-view-state.ts`
4
- * records: `editor.document.*` reaches only the ACTIVE CENTRE DOCUMENT by its
5
- * own contract, and this view is not one, so a `useState` here would be a
6
- * surface nothing in the product could read or drive.
7
- *
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/sdk/views`,
10
- * one table behind the frame's command service and standalone `cyclotron edit`'s
11
- * session verb — so this view adds NO `blender-*` verb of its own, which is
12
- * exactly what the ruling asked the remaining I5 views to stop paying for.
13
- *
14
- * It imports NOTHING, for the reason the node view's store does: the view and
15
- * the verb table both reach it, and a store either of them could not import
16
- * would not be one store.
17
- */
1
+ /** React-free UV drawing state shared by the view and page commands. Actions are registered as volter.blender-uv-view.<verb>. */
18
2
 
19
- export interface UvViewTransform {
3
+ interface UvViewTransform {
20
4
  /** The UV-space point at the view's centre. UV space is Y-UP (v increases
21
5
  * upward, as Blender's image space does); the view flips it once, at the
22
6
  * SVG transform, so nothing downstream carries a sign. */
@@ -45,12 +29,12 @@ export interface UvViewState {
45
29
  readonly drawn: UvViewDrawn | null;
46
30
  }
47
31
 
48
- export interface UvViewDrawn {
32
+ interface UvViewDrawn {
49
33
  readonly object: string | null;
50
34
  readonly mesh: string | null;
51
35
  readonly layer: string | null;
52
36
  readonly layers: readonly string[];
53
- /** Blender's mode at the read — named, never required (ruling 1). */
37
+ /** Blender’s mode at the read. */
54
38
  readonly mode: string;
55
39
  readonly loops: number;
56
40
  readonly polygons: number;