@volter/editor-blender 0.1.149 → 0.1.151

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 +3 -7
  13. package/contributions/blender-node-editor.document.tsx +28 -180
  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 +100 -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 +15 -90
  23. package/contributions/blender-properties-tab.tsx +3 -50
  24. package/contributions/blender-properties-view.tsx +15 -147
  25. package/contributions/blender-remedy.ts +14 -0
  26. package/contributions/blender-runtime-skin.ts +103 -99
  27. package/contributions/blender-runtime.document.tsx +217 -339
  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 +142 -332
  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 +43 -23
  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 +63 -0
  40. package/contributions/blender.status.tsx +9 -145
  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 +12 -32
  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 -30
  64. package/host/blender-runtime-host.ts +541 -372
  65. package/host/scene-sound.ts +143 -0
  66. package/node/blender-mcp.ts +49 -93
  67. package/package.json +1209 -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 -43
  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/model-play-extension.ts +173 -0
  78. package/src/node-view-state.ts +3 -25
  79. package/src/play-mode.ts +64 -38
  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,48 +1,4 @@
1
- /**
2
- * BLENDER'S RENDER TAB, as a Properties section (WORK.md §Blender in the tab
3
- * is Blender, "Inspection parity", I2). Blender's own panels over the RNA
4
- * door, with the generic view beneath under "All properties".
5
- *
6
- * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_render.py` at the
7
- * engine's pin — `classes` (`:1186-1236`) is the panel order, `bl_label` the
8
- * title, each `layout.prop(…)` an entry, `bl_parent_id` a `sub`. Blender's UI
9
- * layer is never run, ported or recorded.
10
- *
11
- * THIS TAB IS WHERE THE ENGINE POLLS BECOME REAL, and it is the ONE `poll`
12
- * this transcription reads. Every other condition is dropped because RNA
13
- * already answers it — a property a datablock does not carry does not draw.
14
- * `COMPAT_ENGINES` is different in kind: `scene.eevee` and `scene.display`
15
- * exist under every engine, so nothing about the datablock says whether
16
- * Blender would draw the panel. Each panel below carries Blender's own LIST
17
- * (`COMPAT_ENGINES`, once per class) and it is checked against the ENGINE's
18
- * live answer — `rna_context`'s `engine`, which is `scene.render.engine`,
19
- * which is what `context.engine` is. The three ids a script can name here are
20
- * CYCLES, BLENDER_EEVEE and BLENDER_WORKBENCH (`session.py::_register_engine`);
21
- * the factory engine is BLENDER_EEVEE (`wasm/BUNDLE.json`), so the EEVEE
22
- * panels are what a fresh file draws. Cycles' own panels are the add-on's, not
23
- * `bl_ui`'s, and are therefore not in this file's specification.
24
- *
25
- * SEVERAL PANELS DRAW TWO DATABLOCKS IN ONE LIST — `RENDER_PT_eevee_film`
26
- * (`:757`) takes `filter_size` and `film_transparent` off `scene.render` and
27
- * `use_overscan`/`overscan_size` off `scene.eevee`, in that order. The
28
- * qualified property form keeps the row in Blender's position while naming
29
- * the datablock it comes from (`BlenderCuratedProperty`); the context door
30
- * names every one of them as this tab's own paths.
31
- *
32
- * WHAT THE TRANSCRIPTION DELIBERATELY DROPS:
33
- * - every operator and preset menu: `RENDER_PT_format_presets`,
34
- * `RENDER_PT_eevee_raytracing_presets` (`:380`), the light-probe bake
35
- * buttons. Inspection parity, not editing parity.
36
- * - `template_curve_mapping` (the motion-blur shutter curve `:283`, the
37
- * colour-management curves `:185`) and `template_colormanaged_view_settings`
38
- * — curve widgets, which this view does not draw; the datablock is one
39
- * drill away in the generic view beneath.
40
- * - `RENDER_PT_hydra_debug` (`:1165`): its engine is HYDRA_STORM, which this
41
- * build does not register, and its poll also wants a developer-UI
42
- * preference.
43
- *
44
- * It stands when: `buttons_context_path_scene` — a scene always resolves.
45
- */
1
+ /** Blender’s Render Properties tab follows scripts/startup/bl_ui/properties_render.py at the engine pin. Curated panels read the RNA context’s datablocks; the generic view remains under All properties. Match tab availability and engine-specific panels against the live context. */
46
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
47
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
48
4
 
@@ -1,38 +1,4 @@
1
- /**
2
- * BLENDER'S SCENE TAB, as a Properties section (WORK.md §Blender in the tab
3
- * is Blender, "Inspection parity", I2). Blender's own panels over the RNA
4
- * door, with the generic view beneath under "All properties".
5
- *
6
- * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_scene.py` at the
7
- * engine's pin — its `classes` tuple (`:471-488`) is the panel order,
8
- * `bl_label` the title, each `layout.prop(scene, "x")` an entry in the order
9
- * the draw function lists it, `bl_options = {'DEFAULT_CLOSED'}` a `closed`
10
- * and `bl_parent_id` a `sub`. Blender's UI layer is never run, ported or
11
- * recorded.
12
- *
13
- * SEVERAL OF THESE PANELS DRAW A SUB-STRUCT RATHER THAN THE SCENE: Units is
14
- * `scene.unit_settings`, Rigid Body World is `scene.rigidbody_world`, Light
15
- * Probes is `scene.eevee`. The context door names each of those beside the
16
- * scene as one of this tab's own datablocks (`session.py::rna_context`), the
17
- * way it already named the Output tab's `image_settings`, and each panel says
18
- * which it reads with `from`. An absent one — a file with no rigid body world
19
- * — is an absent path, and the panel does not draw: the same answer
20
- * `RigidBodySubPanel.poll` gives (`:361-367`), from the door rather than from
21
- * a copy of its condition.
22
- *
23
- * WHAT THE TRANSCRIPTION DELIBERATELY DROPS:
24
- * - `SCENE_PT_context_scene` (`:37`, `bl_label = ""`) — the scene ID
25
- * template, which is the host's identity row here.
26
- * - the operator rows: `rigidbody.world_add`/`_remove` (`:357-359`), the
27
- * keying-set list's add/remove, `object.lightprobe_cache_bake` (`:447`).
28
- * Inspection parity, not editing parity (ARCHITECTURE-CORE §Blender north
29
- * star) — an operator is not a property and this view draws properties.
30
- * - `SCENE_PT_custom_props` (`:467`). Custom properties are IDProperties;
31
- * they are not in `bl_rna.properties` and the door answers RNA. Same
32
- * reading as the Object tab's.
33
- *
34
- * It stands when: `buttons_context_path_scene` — a scene always resolves.
35
- */
1
+
36
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
37
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
38
4
  import { resolveBlenderSubject } from './blender-properties-model';
@@ -1,52 +1,4 @@
1
- /**
2
- * BLENDER'S TEXTURE TAB, as a Properties section (WORK.md §Blender in the tab
3
- * is Blender, "Inspection parity", I2). Blender's own panels over the RNA
4
- * door, with the generic view beneath under "All properties".
5
- *
6
- * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_texture.py` at the
7
- * engine's pin — `classes` (`:958-986`) is the panel order and `bl_label` the
8
- * title — AND `space_buttons/buttons_texture.cc`, which is the only thing
9
- * that can say WHICH texture this tab is about. Blender's UI layer is never
10
- * run, ported or recorded.
11
- *
12
- * THIS TAB HAS NO DATABLOCK OF ITS OWN; IT HAS A USER.
13
- * `buttons_texture_users_from_context` (`buttons_texture.cc:244-366`) walks
14
- * the context for every struct holding a texture pointer, in this order: the
15
- * scene's compositing node tree ("Compositor"), the active line style's slots
16
- * and node tree ("Line Style"), the object's modifiers ("Modifiers"), the
17
- * ACTIVE particle system's `part.mtex[]` ("Particles"), the object's force
18
- * field when its type is TEXTURE ("Fields"), and the active paint brush's own
19
- * two slots ("Brush"). `buttons_texture_context_compute` (`:369`) then takes
20
- * user `ct->index` — 0 unless a person picks another from the menu — and the
21
- * texture it points at is `context.texture`. The door mirrors that walk in
22
- * the C's own order and names the resolved texture as this tab's first path,
23
- * the holder as its second, and `bpy.data.textures` as its third; the whole
24
- * user list is on the context as `textureUsers` for anything that wants it.
25
- *
26
- * A LIGHT IS NOT A TEXTURE USER AT THIS PIN, and it is worth saying because
27
- * older Blenders make it a reasonable guess: the C never looks at `ob->data`.
28
- * Lamp textures went with 2.8's renderer rewrite. Measured, not remembered.
29
- *
30
- * THE TYPE PANELS ARE THE `when` MECHANISM'S OTHER HOME. `TextureTypePanel`
31
- * (`:167`) is `tex.type == cls.tex_type and not tex.use_nodes`, once per type
32
- * — every one of those properties lives on the same `Texture` struct under
33
- * every type, so RNA cannot answer it and the condition is read from the
34
- * engine's live value.
35
- *
36
- * WHAT THE TRANSCRIPTION DELIBERATELY DROPS:
37
- * - `TEXTURE_PT_preview` (`:68`) and `TEXTURE_PT_context` (`:99`) — a
38
- * `template_preview` render and the user-picker/ID templates. The picker
39
- * is an editing widget; what it selects is the context door's answer here.
40
- * - `TEXTURE_PT_node` (`:143`), `template_node_view` over the active texture
41
- * node — the read-only node view is I5.
42
- * - `template_image` (`:404`) and `template_color_ramp` (`:910`) — an image
43
- * browser and a ramp widget; the `image` pointer and the `color_ramp`
44
- * pointer stand and drill.
45
- * - `TEXTURE_PT_custom_props` (`:953`), IDProperties rather than RNA.
46
- *
47
- * It stands when: the door's mirror of `buttons_texture_context_compute`
48
- * finds a user, or the file holds textures at all.
49
- */
1
+ /** Blender’s Texture Properties tab follows scripts/startup/bl_ui/properties_texture.py at the engine pin. Curated panels read the RNA context’s datablocks; the generic view remains under All properties. Match tab availability and engine-specific panels against the live context. */
50
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
51
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
52
4
 
@@ -1,32 +1,4 @@
1
- /**
2
- * BLENDER'S VIEW LAYER TAB, as a Properties section (WORK.md §Blender in the
3
- * tab is Blender, "Inspection parity", I2). Blender's own panels over the RNA
4
- * door, with the generic view beneath under "All properties".
5
- *
6
- * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_view_layer.py` at the
7
- * engine's pin — `classes` (`:345-360`) is the panel order, `bl_label` the
8
- * title, each `layout.prop(view_layer, "x")` an entry. Blender's UI layer is
9
- * never run, ported or recorded.
10
- *
11
- * THE PASSES PANEL IS TWO PANELS UNDER TWO ENGINES, and that is why this tab
12
- * reads `COMPAT_ENGINES`. `VIEWLAYER_PT_eevee_layer_passes_data` (`:111`) and
13
- * `VIEWLAYER_PT_workbench_layer_passes_data` (`:137`) carry the SAME
14
- * `bl_label` ("Data") and differ only by engine — Workbench shows three
15
- * passes where EEVEE shows seven, off the same `ViewLayer` datablock. Nothing
16
- * about the datablock could say which; `rna_context`'s `engine` does. The
17
- * Light panel (`:156`) is EEVEE-only for the same reason, and it also reads
18
- * `view_layer.eevee`, which the context door names beside the layer.
19
- *
20
- * WHAT THE TRANSCRIPTION DELIBERATELY DROPS: `VIEWLAYER_PT_context_layer`
21
- * (`:51`, `bl_label = ""`) — the layer search template, the host's identity
22
- * row here; `VIEWLAYER_PT_layer_custom_props` (`:340`), IDProperties rather
23
- * than RNA; and the AOV / light-group add-remove operators, which are
24
- * editing rather than inspection. The two lists themselves are here as their
25
- * collections.
26
- *
27
- * It stands when: `buttons_context_path_view_layer` — the window's view layer
28
- * always resolves.
29
- */
1
+ /** Blender’s View Layer Properties tab follows scripts/startup/bl_ui/properties_view_layer.py at the engine pin. Curated panels read the RNA context’s datablocks; the generic view remains under All properties. Match tab availability and engine-specific panels against the live context. */
30
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
31
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
32
4
 
@@ -1,48 +1,11 @@
1
- /**
2
- * BLENDER'S WORLD TAB, as a Properties section (WORK.md §Blender in the tab
3
- * is Blender, "Inspection parity", I2). Blender's own panels over the RNA
4
- * door, with the generic view beneath under "All properties".
5
- *
6
- * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_world.py` at the
7
- * engine's pin — `classes` (`:266-278`) is the panel order, `bl_label` the
8
- * title, each `layout.prop(world, "x")` an entry. Blender's UI layer is never
9
- * run, ported or recorded.
10
- *
11
- * SURFACE AND VOLUME ARE A NODE TREE, AND THIS IS NOT THE NODE VIEW.
12
- * `EEVEE_WORLD_PT_surface` (`:109`) asks the world's node tree for its EEVEE
13
- * output node and draws that node's `Surface` input with
14
- * `template_node_view`; Volume (`:140`) is the same call on the `Volume`
15
- * input. Reading those values as a flat list means walking the tree back
16
- * through its links, which is I5's read-only node view (§Inspection parity,
17
- * I5) — half of it here would be a second, worse implementation of the same
18
- * walk. Exactly the reading the Material tab already made for its own Surface
19
- * panel. What stands in these two panels is what the WORLD datablock itself
20
- * holds: `use_nodes` and the `node_tree` pointer, which drills into the
21
- * generic view, plus the volume flag Blender's own panel reads off the world
22
- * (`use_eevee_finite_volume`, `:159`).
23
- *
24
- * THE TAB'S TONE IS THE SHADING RED. `UI_icons.hh:193` declares WORLD under
25
- * `DEF_ICON_SHADING`, the same group as Material — corrected from a guess
26
- * during I2's first landing, and the reason the scene group is four tabs
27
- * rather than six.
28
- *
29
- * WHAT THE TRANSCRIPTION DELIBERATELY DROPS: `WORLD_PT_context_world`
30
- * (`:24`, `bl_label = ""`) — the world ID template, the host's identity row
31
- * here; the `world.convert_volume_to_mesh` operator (`:165`); and
32
- * `WORLD_PT_custom_props` (`:99`), IDProperties rather than RNA.
33
- *
34
- * It stands when: `buttons_context_path_world` returns true from the scene
35
- * alone, so the tab stands even when the scene holds no world — and then
36
- * every panel here finds no datablock and none of them draw.
37
- */
1
+ /** Blender’s World Properties tab follows scripts/startup/bl_ui/properties_world.py at the engine pin. Curated panels read the RNA context’s datablocks; the generic view remains under All properties. Match tab availability and engine-specific panels against the live context. */
38
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
39
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
40
4
 
41
5
  /** `properties_world.py`, panel by panel. Line numbers are that file's. */
42
6
  const CURATED: readonly BlenderCuratedPanel[] = [
43
7
  {
44
- // EEVEE_WORLD_PT_surface, :109 — see the header for why this is the
45
- // world's own node properties rather than the shader's inputs.
8
+ // EEVEE_WORLD_PT_surface, :109. Read the world’s node properties; shader inputs belong to its node tree.
46
9
  title: 'Surface',
47
10
  engine: ['BLENDER_EEVEE'],
48
11
  properties: ['use_nodes', 'node_tree'],
@@ -1,56 +1,4 @@
1
- /**
2
- * The SHADING workspace — Blender's own: the 3D viewport over the Shader
3
- * Editor, with the Outliner above the Properties editor on the right.
4
- *
5
- * THE PROPORTIONS ARE BLENDER'S, MEASURED THROUGH bpy on the engine in the
6
- * tab (2026-09-19), not eyeballed from a screenshot:
7
- * `bpy.data.workspaces['Shading'].screens[0].areas` answers, in a 1920-wide
8
- * window, PROPERTIES `x1579 y23 w339 h839`, OUTLINER `x1579 y865 w339 h189`,
9
- * VIEW_3D `x294 y540 w1282 h514`, NODE_EDITOR `x294 y23 w1282 h514`,
10
- * FILE_BROWSER `x2 y540 w289 h514` and IMAGE_EDITOR `x2 y23 w289 h514`.
11
- *
12
- * AND THE ARRANGEMENT IS A LIVE CAPTURE, not a hand-derived one
13
- * (`workspace-preset-layouts.ts`: "Shipped defaults are authored by arranging
14
- * live and snapshotting `toJSON`" — never hand-written JSON). Hand-deriving it
15
- * from Model's cost exactly what that rule exists to prevent: sizes authored
16
- * against Model's 928-px grid were applied to an 824-px container, the dock
17
- * measured `grid 1920×802 in a 1920×824 container` and warned that it was
18
- * "forcing one relayout" (measured live, 2026-09-19 — and measured again on
19
- * three Model/Sculpt switches with no Shading in them, which raised nothing,
20
- * so the warning was this arrangement's own). What ships is the settled grid,
21
- * with two sizes set back to the measurement: the vertical split to an exact
22
- * 50/50 of the grid's own height, and the right column to Model's 306/173/755,
23
- * which is the same column and already carries Blender's 0.177/0.184.
24
- *
25
- * Two readings out of that, and both are in `shading-arrangement.json`:
26
- * the right column is 339/1920 = 0.177 of the window with the Outliner taking
27
- * 189/1028 = 0.184 of it — the SAME column the Modeling workspace has, which
28
- * is why this arrangement is Model's with one change; and the centre column is
29
- * split EXACTLY 50/50, 514 over 514 of the 1031 px between the top bar and the
30
- * status bar. On the captured 928-px grid that is 464/464, which is the size
31
- * `volter:bottom-center` was already carrying at `"visible": false`.
32
- *
33
- * WHICH AREA THE SHADER EDITOR IS, AND WHAT IT IS MADE OF. Blender's node
34
- * editor is the area BELOW the viewport — and an AREA IS AN EDITOR GROUP
35
- * (orchestrator ruling 2026-09-19), not the bottom utility drawer. So Shading
36
- * declares it as an `areas` entry holding the `blender-node-editor.document`
37
- * contribution, the dock puts it in `volter:area:shader` below the centre at
38
- * Blender's own 50/50, and the frame's layout host puts the same document in
39
- * a second VS Code editor group. The drawer is `hidden` here, as it is in
40
- * Model: Blender's Shading screen has no utility strip.
41
- *
42
- * WHAT IS NOT REPRODUCED, named rather than silently dropped: Blender's
43
- * Shading workspace also carries a File Browser over an Image Editor in a
44
- * 289-px LEFT column (15.0 % of the window). Neither editor exists here yet —
45
- * the image/texture view is I5's Texture Paint unit and the asset browser is
46
- * a different surface — and §Editor chrome's rule is that a reserved panel
47
- * ships WITH its capability, never as empty chrome. The column arrives with
48
- * the editors that fill it.
49
- *
50
- * THE STRIP GROWS ONLY AS A VIEW LANDS (§Editor chrome; WORK.md I5). Shading
51
- * appears in the workspace strip because this contribution exists, and this
52
- * contribution exists because the node view does.
53
- */
1
+ /** The Shading workspace places the Shader Editor below the 3D view in equal halves, with Outliner over Properties at right. The arrangement is a captured dock layout. File Browser and Image Editor areas are omitted until their editors exist. */
54
2
 
55
3
  import type { WorkspaceLayoutContribution } from '@volter/sdk/looks';
56
4
  import { BLENDER_REGIONS } from '../src/regions';
@@ -71,32 +19,9 @@ export const layout: WorkspaceLayoutContribution = {
71
19
  header: 'shown',
72
20
  shelf: 'shown',
73
21
  inspector: 'properties',
74
- // THE SAME AS MODEL: no drawer. Blender's Shading screen has no fourth
75
- // area beyond the viewport, the node editor and the right column, and the
76
- // node editor is not a drawer utility — it is an AREA, which is an EDITOR
77
- // GROUP (the ruling below). Leaving the drawer `shown` would paint an
78
- // empty utility strip Blender's own screen does not have.
22
+ // Hide the utility drawer; the Shader Editor is a document area.
79
23
  drawer: 'hidden',
80
24
  },
81
- // THE NODE EDITOR IS AN EDITOR GROUP BELOW THE MODEL DOCUMENT.
82
- //
83
- // RULED 2026-09-19 (orchestrator): "A Blender editor AREA is an editor
84
- // group; the drawer holds utilities." Blender's Shading screen splits the
85
- // centre TOP AND BOTTOM — VIEW_3D `x294 y540 w1282 h514` over NODE_EDITOR
86
- // `x294 y23 w1282 h514`, measured through bpy on the engine — and both the
87
- // dock and the Code-OSS frame have exactly that shape in editor groups. So
88
- // the Shader Editor is a `workspace.document` this workspace opens into
89
- // `volter:area:shader`, not a `workspace.utility` in the drawer.
90
- //
91
- // AND THAT IS WHAT RETIRED THE DRAWER RACE BY CONSTRUCTION. Switching to UV
92
- // Editing kept opening the Shader Editor — and the cause was NOT the dock
93
- // ordering the previous landing named: `uv-editing-arrangement.json`'s
94
- // mechanical substitution had missed two of the seven occurrences (the
95
- // panel record's `params.utilityId` and its `title`), so the leaf carrying
96
- // the UV panel's id rendered the NODE editor and called itself "Shader
97
- // Editor". A view that is not in the drawer cannot have that defect at all.
98
- //
99
- // THE RATIO IS BLENDER'S OWN: 514 of the 1028 px the centre column spans,
100
- // an exact 0.5 to within the 1-px splitter.
25
+ // Open the Shader Editor in the bottom editor group at half the centre-column height.
101
26
  areas: [{ id: 'shader', document: 'blender-node-editor.document', place: 'below', ratio: 0.5 }],
102
27
  };
@@ -1,49 +1,4 @@
1
- /**
2
- * The UV EDITING workspace — Blender's own: the UV editor BESIDE the 3D
3
- * viewport, with the Outliner over the Properties editor on the right.
4
- *
5
- * THE PROPORTIONS ARE BLENDER'S, MEASURED THROUGH bpy on the engine in the tab
6
- * (2026-09-19), not eyeballed from a screenshot:
7
- * `bpy.data.workspaces['UV Editing'].screens[0].areas` answers, in a 1920-wide
8
- * window, PROPERTIES `x1579 y23 w339 h839`, OUTLINER `x1579 y865 w339 h189`,
9
- * IMAGE_EDITOR `x2 y23 w786 h1031` with `SpaceImage.mode == 'UV'`, and VIEW_3D
10
- * `x791 y23 w785 h1031`.
11
- *
12
- * Three readings out of that:
13
- * - the right column is the SAME column Modeling and Shading have — 339/1920
14
- * = 0.177 of the window, the Outliner taking 189/1028 = 0.184 of it — which
15
- * is why this arrangement is Model's with one change;
16
- * - the centre is split SIDE BY SIDE, not top-and-bottom: 786 and 785 of the
17
- * 1571 px left of the right column, an exact 50/50 to within the 1-px
18
- * splitter. This is the FIRST I5 workspace whose editor is not Blender's
19
- * bottom area — Shading's node editor is (`workspace-regions.ts` says so
20
- * where it explains why Model hides the bottom strip), and the UV editor is
21
- * a left half of the centre;
22
- * - the UV editor opens in UV mode, which is what makes its title "UV Editor"
23
- * rather than "Image Editor".
24
- *
25
- * WHICH REGION THE UV EDITOR IS — and the deviation this file used to record
26
- * is GONE, because the ruling removed its cause. It said: this host has ONE
27
- * utility region, the bottom area, so a utility could not claim a leaf beside
28
- * the centre and the UV editor took the bottom at the height Blender gives
29
- * its width. **RULED 2026-09-19 (orchestrator): a Blender editor AREA is an
30
- * editor group; the drawer holds utilities.** The UV editor is not a utility
31
- * at all now — it is a `workspace.document` this workspace opens into
32
- * `volter:area:uv`, LEFT of the model document at Blender's own 786/1571, and
33
- * under the frame the layout host puts the same document in a second VS Code
34
- * editor group. Nothing is deviating and nothing is owed to U10 here.
35
- *
36
- * WHAT IS NOT REPRODUCED, also named: Blender's UV editor shows the IMAGE
37
- * behind the tile. Measured on both probe files — `arena-weapons.blend` has
38
- * four materials, all `use_nodes`, and NOT ONE image texture node; the whole
39
- * file has zero images, as does `arena-vanguard.blend` — so there is nothing
40
- * for a backdrop to show and the view says so in its own warning rather than
41
- * implying an empty tile is a drawn one.
42
- *
43
- * THE STRIP GROWS ONLY AS A VIEW LANDS (§Editor chrome; WORK.md I5). UV
44
- * Editing appears in the workspace strip because this contribution exists, and
45
- * this contribution exists because the UV view does.
46
- */
1
+ /** UV Editing places the UV Editor left of the 3D view, with Outliner over Properties at right. The UV view opens in UV mode and names a missing image backdrop in its warning. */
47
2
 
48
3
  import type { WorkspaceLayoutContribution } from '@volter/sdk/looks';
49
4
  import { BLENDER_REGIONS } from '../src/regions';
@@ -68,29 +23,7 @@ export const layout: WorkspaceLayoutContribution = {
68
23
  // is a utility strip. The UV editor is an AREA — an editor GROUP — below.
69
24
  drawer: 'hidden',
70
25
  },
71
- // THE UV EDITOR IS AN EDITOR GROUP TO THE LEFT OF THE MODEL DOCUMENT.
72
- //
73
- // RULED 2026-09-19 (orchestrator): "A Blender editor AREA is an editor
74
- // group; the drawer holds utilities." Blender's UV Editing screen splits the
75
- // centre SIDE BY SIDE, with the image editor on the LEFT: IMAGE_EDITOR
76
- // `x2 y23 w786 h1031` (mode `UV`) and VIEW_3D `x791 y23 w785 h1031`,
77
- // measured through bpy on the engine in a 1920-wide window.
78
- //
79
- // THE RATIO IS 786 of the 1571 px left of the 339-px right column — 0.5003,
80
- // an exact half to within the 1-px splitter. It is stated to four places
81
- // because it is a measurement, not a preference; the dock uses it once, to
82
- // size the group the first time this workspace stands up, and a capture of
83
- // the settled grid wins afterwards.
26
+ // Open the UV Editor in the left editor group at 786/1571 ≈ 0.5003 of the centre width.
84
27
  areas: [{ id: 'uv', document: 'blender-uv-editor.document', place: 'left', ratio: 0.5003 }],
85
- // THE ARRANGEMENT IS A LIVE CAPTURE, re-taken 2026-09-19 after the areas
86
- // ruling. The one it replaced was Shading's capture with the node view's
87
- // utility id substituted by hand — and that substitution MISSED TWO of the
88
- // seven occurrences: the panel record's `params.utilityId` and its `title`
89
- // both still said `blender-node-editor.utility` / "Shader Editor", so the
90
- // leaf keyed by the UV panel's id rendered the NODE editor. That is the
91
- // whole of the "switching to UV Editing opens the Shader Editor" defect the
92
- // previous landing recorded as a dock ordering race; it was a bad derived
93
- // blob, which is exactly what `workspace-preset-layouts.ts` forbids
94
- // ("shipped defaults are authored by arranging live and snapshotting
95
- // `toJSON` — never hand-written JSON"). This one is captured.
28
+ // The arrangement is a captured dock layout for these UV editor documents.
96
29
  };
@@ -24,7 +24,7 @@ function projectOutputRootOf(path) {
24
24
  );
25
25
  }
26
26
 
27
- // ../sdk/src/session/relative-path-guard.ts
27
+ // ../sdk/src/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";
@@ -65,8 +65,8 @@ var SHIPPED_BLENDER_WASM_DIR = (() => {
65
65
  const candidate = fileURLToPath(new URL("../../blender-engine/wasm/", import.meta.url));
66
66
  return existsSync(candidate) ? candidate.replace(/\/$/, "") : null;
67
67
  })();
68
- function blenderWasmDir(env = process.env) {
69
- const named = env[BLENDER_WASM_DIR_VARIABLE];
68
+ function blenderWasmDir() {
69
+ const named = process.env[BLENDER_WASM_DIR_VARIABLE];
70
70
  if (typeof named === "string" && named.trim() !== "") {
71
71
  const dir = named.trim();
72
72
  return isAbsolute(dir) ? dir : null;
@@ -92,8 +92,8 @@ async function skewOf(dir) {
92
92
  if (existsSync(join(dir, BLENDER_WALI_ARTIFACT))) return "wali";
93
93
  return null;
94
94
  }
95
- async function blenderWasmStatus(env = process.env) {
96
- const dir = blenderWasmDir(env);
95
+ async function blenderWasmStatus() {
96
+ const dir = blenderWasmDir();
97
97
  if (dir === null)
98
98
  return {
99
99
  available: false,
@@ -117,13 +117,13 @@ async function blenderWasmStatus(env = process.env) {
117
117
  `${dir} holds neither ${BLENDER_WASM_FILES.join(", ")} (the standalone skew) nor ${BLENDER_WALI_ARTIFACT} (the substrate skew)`
118
118
  ]
119
119
  };
120
- return skew === "wali" ? waliStatus(dir) : emscriptenStatus(dir, env);
120
+ return skew === "wali" ? waliStatus(dir) : emscriptenStatus(dir);
121
121
  }
122
122
  var BROWSER_SUBSTRATE_VARIABLE = "BROWSER_SUBSTRATE_URL";
123
- function servedFromTab(env = process.env) {
124
- return (env[BROWSER_SUBSTRATE_VARIABLE] ?? "").trim() !== "";
123
+ function servedFromTab() {
124
+ return (process.env[BROWSER_SUBSTRATE_VARIABLE] ?? "").trim() !== "";
125
125
  }
126
- async function emscriptenStatus(dir, env) {
126
+ async function emscriptenStatus(dir) {
127
127
  const sizes = {};
128
128
  const encoded = {};
129
129
  const missing = [];
@@ -136,7 +136,7 @@ async function emscriptenStatus(dir, env) {
136
136
  sizes[file] = found.size;
137
137
  if (found.encoding) encoded[file] = found.encoding;
138
138
  }
139
- return { available: missing.length === 0, skew: "emscripten", dir, sizes, encoded, missing, digests: await emscriptenDigests(dir), inTab: servedFromTab(env) };
139
+ return { available: missing.length === 0, skew: "emscripten", dir, sizes, encoded, missing, digests: await emscriptenDigests(dir), inTab: servedFromTab() };
140
140
  }
141
141
  async function emscriptenDigests(dir) {
142
142
  const digests = {};
@@ -217,7 +217,6 @@ async function runtimeIndex(dir) {
217
217
  if (cachedRuntimeIndex?.dir === dir) return cachedRuntimeIndex.index;
218
218
  const root = join(dir, BLENDER_WALI_RUNTIME);
219
219
  const files = [];
220
- let newest = 0;
221
220
  const walk = async (where) => {
222
221
  for (const entry of (await readdir(where, { withFileTypes: true })).sort(
223
222
  (left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0
@@ -235,12 +234,11 @@ async function runtimeIndex(dir) {
235
234
  mode: info.mode & 4095,
236
235
  sha256: createHash("sha256").update(await readFile(path)).digest("hex")
237
236
  });
238
- newest = Math.max(newest, info.mtimeMs);
239
237
  }
240
238
  };
241
239
  await walk(root);
242
240
  if (files.length === 0) throw new Error(`${root} is empty`);
243
- const index = { files, newestMtimeMs: newest };
241
+ const index = { files };
244
242
  cachedRuntimeIndex = { dir, index };
245
243
  return index;
246
244
  }
@@ -358,9 +356,7 @@ function blenderRoutesPlugin(services) {
358
356
  json(res, await blenderWasmStatus());
359
357
  }
360
358
  },
361
- // The substrate skew's own doors beside the standalone bundle's: Blender's runtime tree as
362
- // one index and one blob (a request per file would be ~2,300 round trips), and the
363
- // substrate's browser ESM with its bare specifiers rewritten.
359
+ // Serve the substrate runtime tree as one index and blob, plus browser ESM with rewritten bare specifiers.
364
360
  {
365
361
  method: "GET",
366
362
  match: /^\/__editor\/blender-wasm\/runtime\.idx$/,
@@ -657,20 +653,7 @@ function blenderRoutesPlugin(services) {
657
653
  }
658
654
  }
659
655
  },
660
- // ---- The session's DOCUMENT, written back OUT into the project. A Blender session holds one
661
- // `.blend`; its `save_as_mainfile` writes it into the engine's filesystem, and this is how
662
- // those bytes reach the disk the project lives on. Unlike the spool, the destination shape is
663
- // pinned BY THIS SERVER, and the write is the project's one attributed, conflict-checked
664
- // transaction: a `.blend` is the project's file like any other.
665
- //
666
- // IT ARRIVES AS A DELTA. The page cuts each save into content-defined chunks
667
- // (`blender-engine/browser/document-chunks.mts`) and posts their manifest; this answers with
668
- // the chunks it does not already hold, the page posts those, and the manifest again commits.
669
- // MEASURED 2026-09-29, native Blender 5.2 on the Stoneguard bridge: a 497 MB save, one object
670
- // moved, changed 1 of 475 one-MiB chunks, so an edit carries about a megabyte instead of the
671
- // whole file. Chunks this server reuses come from the document as it lies on disk, and each
672
- // is hashed again against the manifest before the commit: a file changed underneath (another
673
- // writer, a restore) is not drift but a missing chunk, re-sent.
656
+ // Save the session .blend through the attributed, conflict-checked project transaction. A chunk manifest requests only missing bytes. Rehash reused disk chunks at commit so an external file change requests replacement chunks.
674
657
  {
675
658
  method: "POST",
676
659
  match: /^\/__editor\/blender-document-chunk$/,