@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
@@ -0,0 +1,52 @@
1
+ /** Session Blender verbs relay to volter.<verb> commands registered by the page half. Each manifest entry owns its timeout and derived-refresh policy. Mutating commands present a frame and refresh the document; handlers use only SDK host doors. */
2
+ import type { VerbHandler } from '@volter/sdk/commands';
3
+ import { handleBlenderCommand } from '../host/blender-runtime-host';
4
+
5
+ /** The lane's one handler, for every verb. */
6
+ const handle: VerbHandler = (cmd) => handleBlenderCommand(cmd as { type: string; [key: string]: unknown });
7
+
8
+ export const blenderVerbs: Readonly<Record<string, VerbHandler>> = {
9
+ // Presentation-only comparison, no engine mutation or derived frame refresh.
10
+ 'blender-draw-batching': handle,
11
+ 'blender-start': handle,
12
+ // Stop drains accepted modeling work and persists it before teardown.
13
+ 'blender-stop': handle,
14
+ 'blender-execute': handle,
15
+ 'blender-native-preview': handle,
16
+ 'blender-scene-info': handle,
17
+ 'blender-object-info': handle,
18
+ 'blender-screenshot-view': handle,
19
+ 'blender-read-file': handle,
20
+ 'blender-write-file': handle,
21
+ // File listing uses the worker-read timeout while presentation and derived reads finish.
22
+ 'blender-list-files': handle,
23
+ // RNA reads expose properties and context without presenting; RNA writes mutate, present and derive.
24
+ 'blender-rna': handle,
25
+ 'blender-rna-context': handle,
26
+ 'blender-rna-set': handle,
27
+ // Outliner reads expose the View Layer tree; restriction writes mutate, present and derive.
28
+ 'blender-outliner': handle,
29
+ // The node-tree door is read-only and requires no derivation.
30
+ 'blender-node-tree': handle,
31
+ // The UV door reads one mesh layout; no UV writer is registered here.
32
+ 'blender-uv-layout': handle,
33
+ // THE RIG AND CLIP DOORS (the Timeline). Both READS — the skin binding a
34
+ // `THREE.SkinnedMesh` is built from and the action as three.js tracks — so
35
+ // neither presents and neither owes a derivation. There is deliberately no
36
+ // writer beside them: keying, moving a key and setting a range are edits,
37
+ // and the ONE write the Timeline makes (`scene.frame_current`, once on
38
+ // pause and at scrub-end) goes through `blender-rna-set`, which already
39
+ // exists and already presents.
40
+ 'blender-action-clip': handle,
41
+ // Node-view commands inspect and navigate view state. Editing gestures return the same named refusal as pointer handlers.
42
+ 'blender-node-view': handle,
43
+ 'blender-outliner-set': handle,
44
+ // A FILM, RENDERED (`cyclotron render-movie`, a film's Blender shots): started, answering at once, then followed
45
+ // until it ends. The frames are Blender's own renders; each chunk puts the scene's render settings and frame back, so
46
+ // none presents a change to the model.
47
+ 'blender-render-movie': handle,
48
+ 'blender-render-status': handle,
49
+ // A read of whether this tab HAS a session; it never starts one — and the
50
+ // one verb that answers before a project session exists.
51
+ 'blender-status': handle,
52
+ };
@@ -1,10 +1,4 @@
1
- /**
2
- * The MODEL workspace — Blender-shaped modeling: a dominant document, outliner
3
- * above properties (`@volter/sdk/looks`, a `workspace.layout`
4
- * contribution). Its arrangement carries Blender 5.2's own MODELING-workspace
5
- * proportions; `../src/layouts.tsx` states the measurement and the host's
6
- * fixed-pane clamp that bounds it.
7
- */
1
+ /** Model workspace: a dominant document, outliner above Properties and a lower Timeline area. The workbench owns pane geometry. */
8
2
 
9
3
  import type { WorkspaceLayoutContribution } from '@volter/sdk/looks';
10
4
  import { BLENDER_REGIONS } from '../src/regions';
@@ -26,32 +20,6 @@ export const layout: WorkspaceLayoutContribution = {
26
20
  inspector: 'properties',
27
21
  drawer: 'hidden',
28
22
  },
29
- // THE TIMELINE IS THE BOTTOM AREA (owner, 2026-09-20: "doesn't blender have
30
- // a timeline at the bottom?"). It does — and the workspace this Model
31
- // document stands in is Blender's LAYOUT screen for us, not its Modeling
32
- // one: Layout is the four-area screen with the Timeline, and Modeling is the
33
- // same screen WITHOUT it (measured below).
34
- //
35
- // THE PROPORTION IS BLENDER'S OWN, measured through bpy on the installed
36
- // Blender 5.2.1 at factory settings in a 1920-wide window
37
- // (`bpy.data.workspaces['Layout'].screens[0].areas`):
38
- //
39
- // PROPERTIES x1579 y23 w339 h839
40
- // OUTLINER x1579 y865 w339 h189
41
- // DOPESHEET_EDITOR x2 y23 w1574 h74 ui_type TIMELINE ← this area
42
- // VIEW_3D x2 y100 w1574 h954
43
- //
44
- // and the same screen at a 1600×929 window gives the Timeline 63 of an
45
- // 880-px centre column. Both are the same ratio to three places: 74/1029 =
46
- // 0.0719 and 63/880 = 0.0716. The Modeling screen's own reading, for the
47
- // contrast: VIEW_3D `x2 y23 w1574 h1031` and no DOPESHEET area at all.
48
- //
49
- // It is stated to four places because it is a measurement, not a preference;
50
- // the dock uses it once, to size the group the first time this workspace
51
- // stands up, and a capture of the settled grid wins afterwards.
52
- //
53
- // IN GAME MODE THE SAME AREA HOLDS THE GAME PANEL (`../src/play-mode.ts`):
54
- // the document below draws the Timeline or the panel by the model's mode,
55
- // so the area, its proportion and a person's resize all carry over.
23
+ // The Model workspace’s bottom editor area hosts the Timeline or Game panel. Initial Timeline ratio is Blender Layout’s 74/1029 ≈ 0.0719; a person’s later resize takes precedence.
56
24
  areas: [{ id: 'timeline', document: 'blender-timeline.document', place: 'below', ratio: 0.0719 }],
57
25
  };
@@ -1,33 +1,4 @@
1
- /**
2
- * The `modelsFromBlendFiles` FINDER (ARCHITECTURE-CORE §The project model,
3
- * "A model is Blender data, and a prefab is not a model"): every `.blend`
4
- * under the selection's `include` globs is a `model` document — a Blender
5
- * file the engine in the tab opens, edits and saves. The bpy scripts that
6
- * authored it are ordinary project files beside it (`src/models/<name>.py`);
7
- * this document does not run them, a person or an agent does, through the
8
- * session's Blender (`volter blender-mcp`).
9
- *
10
- * It replaced `modelsFromMeshModules`, which listed every module exporting
11
- * `build(): THREE.BufferGeometry` (owner ruling 2026-09-19, "do the delete":
12
- * the TypeScript mesh kit was the modeling engine while Blender could not run
13
- * in the tab; it now does, and a model is Blender data).
14
- *
15
- * Select it in `editor/volter.adapter.ts`:
16
- *
17
- * documents: { find: [{ finder: 'modelsFromBlendFiles', include: ['src/models/**\/*.blend'] }] }
18
- *
19
- * THE SESSION'S OWN MODEL. A project that holds no `.blend` of its own still
20
- * needs ONE model — it is what `blender-start` presents into and what a first
21
- * bpy call models in, saved to the session's default `models/model.blend` — so
22
- * when the selection finds no file, the finder lists the standing
23
- * `blender:runtime` entry instead. Never beside a real model.
24
- *
25
- * This module is a plain object the HOST registers; it mounts no UI. It reads
26
- * the project only through `input.files` — the PATHS matching those globs.
27
- * Deliberately not `input.sources`: a `.blend` is binary, there is nothing in
28
- * it a text walk could honestly read, and the entry is the file itself. It says
29
- * so (`reads: 'files'`), and the host then lists the files without fetching them.
30
- */
1
+
31
2
 
32
3
  import { z } from 'zod';
33
4
 
@@ -43,10 +14,15 @@ export const finder = {
43
14
  .array(z.string().min(1))
44
15
  .min(1)
45
16
  .describe('Project-relative globs of the Blender files to list (`src/models/**/*.blend`)'),
17
+ defaultIn: z
18
+ .string()
19
+ .regex(/^[^/].*\/$/)
20
+ .optional()
21
+ .describe('A project folder (`levels/`): where exactly one listed Blender file is in it, that file is the default document'),
46
22
  })
47
23
  .strict(),
48
24
  run(
49
- _selection: { finder: 'modelsFromBlendFiles'; include: readonly string[] },
25
+ selection: { finder: 'modelsFromBlendFiles'; include: readonly string[]; defaultIn?: string },
50
26
  input: { files?: readonly string[] },
51
27
  ) {
52
28
  const entries = [];
@@ -74,6 +50,10 @@ export const finder = {
74
50
  reach: { kind: 'root-mount' as const },
75
51
  });
76
52
  }
77
- return { entries, notes };
53
+ // (the adapter's author names the folder whose lone file is the default: a std game's one level opens as its document)
54
+ const within = selection.defaultIn === undefined ? []
55
+ : (input.files ?? []).filter((path) => path.endsWith('.blend') && path.startsWith(selection.defaultIn!));
56
+ const only = within.length === 1 ? `model:${within[0]!}` : undefined;
57
+ return { entries, notes, ...(only === undefined ? {} : { default: only }) };
78
58
  },
79
59
  } as const;
@@ -1,19 +1,4 @@
1
- /**
2
- * BLENDER'S BONE CONSTRAINTS TAB, as a Properties section (WORK.md §Blender in
3
- * the tab is Blender, "Inspection parity", I2): the active POSE BONE's
4
- * constraint stack, with the generic view beneath under "All properties".
5
- *
6
- * It is the Object Constraints tab over a different datablock, and Blender
7
- * says so itself: `properties_constraint.py` registers
8
- * `BONE_PT_constraints` beside `OBJECT_PT_constraints` with the same
9
- * `template_constraints()` body, differing only in reading
10
- * `context.pose_bone.constraints`. So it shares the header list that module
11
- * exports rather than restating it — a second copy is a second thing to keep
12
- * in step.
13
- *
14
- * It stands when: `buttons_context_path_pose_bone` — a pose channel for the
15
- * active bone, and never in Edit mode.
16
- */
1
+ /** Blender’s Bone Constraints Properties tab follows scripts/startup/bl_ui/properties_constraint.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. */
17
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
18
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
19
4
  import { CONSTRAINT_HEADER } from './properties-constraints.inspector';
@@ -1,26 +1,4 @@
1
- /**
2
- * BLENDER'S BONE TAB, as a Properties section (WORK.md §Blender in the tab is
3
- * Blender, "Inspection parity", I2). Blender's own panels over the RNA door,
4
- * with the generic view beneath under "All properties".
5
- *
6
- * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_data_bone.py` at the
7
- * engine's pin: its `classes` tuple is the panel order, `bl_label` the title,
8
- * each `layout.prop(bone, "x")` an entry. Blender's UI layer is never run,
9
- * ported or recorded.
10
- *
11
- * TWO DATABLOCKS, AND THAT IS THE WHOLE STRUCTURE OF THIS TAB. Blender's panels
12
- * read `bone` (the armature's `Bone`, or the `EditBone` in Edit mode) for the
13
- * rest pose — head, tail, roll, envelope, collections, display — and `pchan`
14
- * (the `PoseBone`) for the POSE: its location/rotation/scale and its inverse
15
- * kinematics. `buttons_context_path_bone` and `_pose_bone` build both, and the
16
- * door hands this tab both as "Bone" and "Pose Bone", so each panel names the
17
- * one Blender's draw function reads. That is why Transform below is split in
18
- * two the way `BONE_PT_transform` (:39) is: its first half is `pchan`, its
19
- * second `bone`.
20
- *
21
- * It stands when: `buttons_context_path_bone` — an armature with an ACTIVE bone
22
- * (the edit bone in Edit mode).
23
- */
1
+ /** Blender’s Bone Properties tab follows scripts/startup/bl_ui/properties_data_bone.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. */
24
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
25
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
26
4
 
@@ -44,14 +22,7 @@ const CURATED: readonly BlenderCuratedPanel[] = [
44
22
  ],
45
23
  },
46
24
  {
47
- // BONE_PT_transform, :39, rest half (:104-110) — the bone's own geometry.
48
- // NO MATRICES. The first cut named `matrix` and `matrix_local` beside them
49
- // and the live read showed why Blender does not: a 3x3 and a 4x4 draw as
50
- // twenty-five numbered rows, which buries the five values the panel is
51
- // about. They are still in "All properties" beneath, where a matrix is
52
- // something you go looking for. (`roll` is an EditBone property, so it
53
- // draws in Edit mode and not in Object mode — the datablock's answer, not
54
- // a condition of ours.)
25
+ // BONE_PT_transform rest geometry excludes matrices; those remain under All properties. roll exists only on EditBone and draws only when that datablock exposes it.
55
26
  title: 'Transform',
56
27
  from: 'Bone',
57
28
  properties: ['head', 'tail', 'roll', 'length', 'lock'],
@@ -1,37 +1,4 @@
1
- /**
2
- * BLENDER'S COLLECTION 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_collection.py` at the
7
- * engine's pin — `classes` (`:146-155`) is the panel order and `bl_label` the
8
- * title. Blender's UI layer is never run, ported or recorded.
9
- *
10
- * THE FIRST PANEL IS CALLED "VISIBILITY" AT THIS PIN, not "Restrictions":
11
- * `COLLECTION_PT_collection_flags.bl_label` is `"Visibility"` (`:32`). Its
12
- * child View Layer (`:47`) does not read the collection at all — it reads
13
- * `view_layer.active_layer_collection` (`:56`), a different datablock, which
14
- * the context door now names beside the collection as this tab's second path.
15
- *
16
- * TWO CHECKBOXES BLENDER DRAWS INVERTED. `hide_select` is labelled
17
- * "Selectable" with `invert_checkbox=True` (`:41`) and `exclude` is labelled
18
- * "Include" the same way (`:60`). What stands here is the RNA's own value
19
- * under the RNA's own name, because inverting a value in the presentation is
20
- * exactly the fabrication the anti-shim rule forbids — the same reading the
21
- * Object tab's Visibility panel already made.
22
- *
23
- * WHAT THE TRANSCRIPTION DELIBERATELY DROPS: `COLLECTION_PT_importer`
24
- * (`:65`), whose poll is a PREFERENCE (`prefs.experimental.use_collection_
25
- * importer`) and whose body is `template_collection_importer()` — an operator
26
- * template. Exporters (`:80`) is the same widget over `collection.exporters`,
27
- * and the collection itself is real RNA (`rna_collection.cc:954`), so its row
28
- * stands. `COLLECTION_PT_collection_custom_props` (`:143`) is IDProperties
29
- * rather than RNA.
30
- *
31
- * It stands when: `buttons_context_path_collection` — the view layer's ACTIVE
32
- * collection, and never the scene's master collection, which only the engine
33
- * knows.
34
- */
1
+ /** Blender’s Collection Properties tab follows scripts/startup/bl_ui/properties_collection.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. */
35
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
36
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
37
4
 
@@ -1,24 +1,4 @@
1
- /**
2
- * BLENDER'S OBJECT CONSTRAINTS TAB, as a Properties section (WORK.md §Blender
3
- * in the tab is Blender, "Inspection parity", I2): the STACK, each constraint
4
- * its own panel with its own settings, the generic view beneath under "All
5
- * properties".
6
- *
7
- * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_constraint.py`, and
8
- * it has the same shape `properties_data_modifier.py` does: `draw` is
9
- * `layout.template_constraints()` and the per-type panels are built in C from
10
- * each constraint's RNA. So the stack is derived the same way — the shared
11
- * `Constraint` header properties named, then everything THIS constraint's own
12
- * struct declares (`andDeclared`, which reads `BlenderRnaRow.group`). A Copy
13
- * Location constraint gets `CopyLocationConstraint`'s target, subtarget, use_x
14
- * / use_y / use_z, invert flags and offset, in RNA's order.
15
- *
16
- * The ACTIVE constraint (`buttons_context_path_constraint`, reported as
17
- * `activeConstraint`) is marked.
18
- *
19
- * It stands when: `buttons_context_path_object` — Constraints shares Object's
20
- * path, so the engine grants it whenever it grants Object.
21
- */
1
+ /** Blender’s Object Constraints Properties tab follows scripts/startup/bl_ui/properties_constraint.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. */
22
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
23
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
24
4
 
@@ -1,36 +1,4 @@
1
- /**
2
- * BLENDER'S OBJECT DATA 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 of the whole datablock beneath under "All
5
- * properties".
6
- *
7
- * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_data_mesh.py`, read
8
- * at the engine's pin: its `classes` tuple is the panel ORDER, each class's
9
- * `bl_label` the title, each `layout.prop(me, "x")` an entry in the order the
10
- * draw function lists it, `bl_options = {'DEFAULT_CLOSED'}` a `closed`, and a
11
- * `template_list` a `collection` panel. Blender's UI layer is never run,
12
- * ported or recorded.
13
- *
14
- * ONE TAB, MANY DATA TYPES, ONE LIST. `buttons_context_path_data` builds this
15
- * tab for whatever the object's data IS, so the same section draws a Mesh, an
16
- * Armature, a Camera or a Light — and the panels for all of them live in one
17
- * list here, because a panel draws only where its properties exist. A Camera's
18
- * data carries no `remesh_mode`, so the Remesh panel finds nothing and renders
19
- * nothing; an Armature's carries no `uv_layers` and every mesh panel stands
20
- * down for it. That is the same answer `MeshButtonsPanel.poll` /
21
- * `ArmatureButtonsPanel.poll` give, from the datablock rather than from a copy
22
- * of their conditions — which is why there is no per-type branch in this file
23
- * and no second module per data type.
24
- *
25
- * NO "NORMALS" PANEL: the brief expected one and 5.2 does not have it. The
26
- * mesh's `DATA_PT_normals` was folded away before this pin — `has_custom_normals`
27
- * survives as the Geometry Data panel's operator condition (`:456`), and the
28
- * shading-per-face data now lives on the Attributes list. Stated rather than
29
- * invented.
30
- *
31
- * It stands when: `buttons_context_path_data` — the object HAS data (an Empty
32
- * usually has none).
33
- */
1
+ /** Blender’s Object Data Properties tab follows scripts/startup/bl_ui/properties_data_mesh.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. */
34
2
  import type { ToolContributionNode } from '@volter/sdk/contributions';
35
3
  import { blenderPropertiesState, resolveBlenderSubject } from './blender-properties-model';
36
4
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
@@ -93,12 +61,7 @@ const CURATED: readonly BlenderCuratedPanel[] = [
93
61
  ],
94
62
  },
95
63
  {
96
- // DATA_PT_customdata, :435, titled "Geometry Data". Its body is FIVE
97
- // OPERATORS and one condition (`has_custom_normals`, :456) — no
98
- // properties at all. Inspection parity is not editing parity
99
- // (ARCHITECTURE-CORE §Blender north star), so what stands here is the
100
- // condition the operators branch on, which is the only thing in the panel
101
- // a reader can learn something from.
64
+ // Geometry Data exposes the condition and selection counts; its editing operators are not drawn here.
102
65
  title: 'Geometry Data',
103
66
  closed: true,
104
67
  properties: ['has_custom_normals', 'total_vert_sel', 'total_edge_sel', 'total_face_sel'],
@@ -1,34 +1,4 @@
1
- /**
2
- * BLENDER'S MATERIAL 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_material.py` at the
7
- * engine's pin — its `classes` tuple is the panel order, `bl_label` the
8
- * title, each `layout.prop(mat, "x")` an entry. Blender's UI layer is never
9
- * run, ported or recorded.
10
- *
11
- * THE SLOTS LIST IS FIRST, and it is `MATERIAL_UL_matslots` over
12
- * `ob.material_slots` (`:76`, `EEVEE_MATERIAL_PT_context_material`, whose
13
- * `bl_label` is empty because in Blender it is the tab's header block rather
14
- * than a panel). The context door hands it to this tab as its own path, with
15
- * the ACTIVE slot named separately (`buttons_context_path_material`:
16
- * `BKE_object_material_get(ob, ob->actcol)`), so the list marks it.
17
- *
18
- * SURFACE IS A NODE TREE, AND THIS IS NOT THE NODE VIEW. Blender's Surface
19
- * panel (`:164`) calls `panel_node_draw`, which walks the material output
20
- * node's `Surface` input back to the shader that feeds it and draws THAT
21
- * node's inputs by name. Showing those values as a flat list needs the node
22
- * tree walked, which is I5's read-only node view (§Inspection parity, I5) —
23
- * and a half version of it here would be a second, worse implementation of
24
- * the same walk. What stands in this panel instead is what the MATERIAL
25
- * itself holds: `use_nodes` and the `node_tree` pointer, which drills into
26
- * the generic view, plus the settings Blender's own EEVEE panels read off the
27
- * material datablock directly.
28
- *
29
- * It stands when: `buttons_context_path_material` — the object's data holds a
30
- * `materials` collection (the tab stands even when the active slot is empty).
31
- */
1
+ /** Blender’s Material Properties tab follows scripts/startup/bl_ui/properties_material.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. */
32
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
33
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
34
4
 
@@ -44,8 +14,7 @@ const CURATED: readonly BlenderCuratedPanel[] = [
44
14
  properties: ['name', 'material', 'link', 'slot_index'],
45
15
  },
46
16
  {
47
- // EEVEE_MATERIAL_PT_surface, :164 — see the header for why this is the
48
- // material's own two node properties rather than the shader's inputs.
17
+ // EEVEE_MATERIAL_PT_surface, :164. Read the material’s node properties; shader inputs belong to its node tree.
49
18
  title: 'Surface',
50
19
  from: 'Material',
51
20
  properties: ['use_nodes', 'node_tree', 'surface_render_method', 'use_backface_culling'],
@@ -1,32 +1,4 @@
1
- /**
2
- * BLENDER'S MODIFIERS TAB, as a Properties section (WORK.md §Blender in the
3
- * tab is Blender, "Inspection parity", I2): the STACK, each modifier its own
4
- * panel with its own settings, with the generic view beneath under "All
5
- * properties".
6
- *
7
- * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_data_modifier.py` at
8
- * the engine's pin, and reading it is what decided the shape here. That file
9
- * is barely a panel list at all: `DATA_PT_modifiers.draw` is `Add Modifier`
10
- * plus `layout.template_modifiers()`, and the per-modifier panels are built in
11
- * C from each type's own RNA (`MOD_*.cc`'s `panel_draw`). There is no Python
12
- * table of sixty modifier types to transcribe — and transcribing one would be
13
- * a COPY of Blender's layout, which the ruling forbids, and would go stale the
14
- * first time a modifier gained a property.
15
- *
16
- * So the stack is derived instead: each member of `ob.modifiers` is a panel
17
- * headed by the four toggles every modifier has (`ModifierData`'s own
18
- * `show_viewport` / `show_render` / `show_in_editmode` / `show_on_cage`,
19
- * which is the header row `template_modifiers` draws), followed by every
20
- * property THIS MODIFIER'S OWN struct declares — `andDeclared`, which reads
21
- * `BlenderRnaRow.group`, RNA's own answer to "which struct declares this".
22
- * For a Subdivision modifier that is exactly `SubsurfModifier`'s levels,
23
- * render_levels, quality, uv_smooth and the rest, in RNA's order.
24
- *
25
- * The ACTIVE modifier (`buttons_context_path_modifier`, which the door reports
26
- * as `activeModifier`) is marked, the way Blender outlines the active panel.
27
- *
28
- * It stands when: the object's type is in `buttons_context.cc`'s modifier set.
29
- */
1
+ /** Blender’s Modifiers Properties tab follows scripts/startup/bl_ui/properties_data_modifier.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
 
@@ -37,7 +9,7 @@ const CURATED: readonly BlenderCuratedPanel[] = [
37
9
  collection: true,
38
10
  active: 'activeModifier',
39
11
  // `ModifierData`'s own, in the order `template_modifiers` heads a panel
40
- // with them; everything after these is the type's own (see the header).
12
+ // with them; everything after these is the type's own.
41
13
  properties: [
42
14
  'name',
43
15
  'type',
@@ -1,34 +1,4 @@
1
- /**
2
- * BLENDER'S OBJECT TAB, as a Properties section (WORK.md §Blender in the tab
3
- * is Blender, "Inspection parity", I2). The body is Blender's own panels, in
4
- * Blender's own order, over the RNA door; the generic view of every property
5
- * the object has stays beneath them under "All properties".
6
- *
7
- * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_object.py`, read at
8
- * the engine's pin. Its `classes` tuple at the foot of the file is the panel
9
- * ORDER, each class's `bl_label` the title, each `layout.prop(ob, "x")` an
10
- * entry below in the order the draw function lists it, and `bl_options =
11
- * {'DEFAULT_CLOSED'}` a `closed: true`. Blender's UI layer is never run,
12
- * ported or recorded — the Python is read as a statement of WHICH properties
13
- * this tab shows, and our widgets draw them from the door's rows.
14
- *
15
- * WHAT THE TRANSCRIPTION DELIBERATELY DROPS:
16
- * - type-only `poll`/`if` branches. `show_all_edges` is mesh-only in Python; here
17
- * it is simply not on a Camera's RNA, so it does not draw. Same outcome,
18
- * from the datablock rather than from a copy of Blender's conditions.
19
- * Rotation mode is a value on every object, so its draw branch is retained.
20
- * - `OBJECT_PT_context_object` (`bl_label = ""`) — the breadcrumb row, which
21
- * is the host's identity row here.
22
- * - the four panels whose datablock is NOT the object: Motion Paths reads
23
- * `ob.animation_visualization` and `ob.motion_path`, Light/Shadow Linking
24
- * their collections. The context door names the object, so those are
25
- * reached by drilling their POINTER row in the generic view beneath — a
26
- * curated panel addresses one of the TAB's own datablocks, and inventing a
27
- * second addressing scheme to reach a sub-struct is the kind of machinery
28
- * the generic view exists to make unnecessary.
29
- *
30
- * It stands when: `buttons_context_path_object` — there is an active object.
31
- */
1
+ /** Blender’s Object Properties tab follows scripts/startup/bl_ui/properties_object.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. */
32
2
 
33
3
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
34
4
  import type { BlenderCuratedPanel } from './blender-properties-view';
@@ -196,19 +166,7 @@ export const order = 70;
196
166
  * object group (Object … Material), then Texture. The presentation draws a
197
167
  * separator wherever this changes. */
198
168
  export const railGroup = 'object';
199
- /** THE TAB THE RAIL OPENS ON. Blender's Properties editor stores its context
200
- * per screen in the startup file, and at factory settings the Layout
201
- * workspace's is OBJECT:
202
- *
203
- * bpy.data.screens['Layout'].areas[PROPERTIES].spaces[0].context -> 'OBJECT'
204
- *
205
- * (measured against Blender 5.2.0 LTS at `--factory-startup`, walk 5 parity
206
- * row 3; the other screens read OBJECT, MODIFIER, WORLD, DATA, TOOL, OUTPUT
207
- * and RENDER, so a per-workspace default is the next fidelity step and this
208
- * one declaration is the Layout/Model answer). Without it the rail opened on
209
- * Render — the first tab in `ED_buttons_tabs_list`'s order — and a person
210
- * selecting the cube got Sampling and Light Paths where Blender shows
211
- * Transform. */
169
+ /** The Model Properties rail opens on Object, matching the factory Layout workspace’s context. */
212
170
  export const railDefault = true;
213
171
  export const match = blenderPropertiesTabMatch(TAB);
214
172
  export default blenderPropertiesTabSection(TAB);
@@ -1,30 +1,4 @@
1
- /**
2
- * BLENDER'S OUTPUT 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_output.py` at the
7
- * engine's pin — `classes` (`:728-748`) is the panel order, `bl_label` the
8
- * title, each `layout.prop(rd, "x")` an entry in the order the draw function
9
- * lists it. Blender's UI layer is never run, ported or recorded.
10
- *
11
- * THE TAB'S DATABLOCK IS `scene.render`, and three panels reach past it: Frame
12
- * Range is the SCENE's own `frame_start`/`frame_end`/`frame_step` (`:151`),
13
- * Output's Color Management is `rd.image_settings` (`:371`), and Encoding is
14
- * `rd.ffmpeg` (`:499`). The context door names each beside the render
15
- * settings as one of this tab's paths and the rows say which they come from
16
- * ({@link BlenderCuratedProperty}).
17
- *
18
- * WHAT THE TRANSCRIPTION DELIBERATELY DROPS:
19
- * - the preset menus (`RENDER_PT_format_presets` `:15`,
20
- * `RENDER_MT_framerate_presets` `:28`, `RENDER_PT_ffmpeg_presets` `:22`)
21
- * and `template_image_settings`/`template_image_views` — operator and
22
- * template widgets. Inspection parity, not editing parity.
23
- * - `RENDER_PT_output_pixel_density` (`:396`), whose body is a preset menu
24
- * plus two properties behind it; the two are named here.
25
- *
26
- * It stands when: `buttons_context_path_scene` — the same path as Render.
27
- */
1
+ /** Blender’s Output Properties tab follows scripts/startup/bl_ui/properties_output.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. */
28
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
29
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
30
4
 
@@ -1,48 +1,4 @@
1
- /**
2
- * BLENDER'S PARTICLES 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_particle.py` at the
7
- * engine's pin — `classes` (`:2248-2302`) is the panel order, `bl_label` the
8
- * title, each `layout.prop(part, "x")` an entry. Blender's UI layer is never
9
- * run, ported or recorded.
10
- *
11
- * NEARLY EVERY PANEL DRAWS `psys.settings`, NOT THE SYSTEM. `part` in these
12
- * draw functions is `particle_get_settings(context)` (`:40-46`), which is the
13
- * active system's ParticleSettings — a datablock of its own. The system
14
- * itself carries only a handful (`seed`, `parent`, `child_seed`, the
15
- * vertex-group names, `use_hair_dynamics`). The context door names both, plus
16
- * the hair-dynamics cloth settings, as this tab's paths, and each row says
17
- * which it comes from.
18
- *
19
- * THE TYPE POLLS ARE READ, and this tab is why `when` exists. Blender gates
20
- * these panels on values rather than on structure: `settings.type == 'HAIR'`
21
- * (Hair Dynamics `:362`, Hair Shape `:2205`), `settings.physics_type` in
22
- * NEWTON/FLUID or == BOIDS (`:1161`, `:1195`, `:1222`, `:934`),
23
- * `settings.render_type` (`:1449`, `:1507`, `:1536`), `settings.child_type`
24
- * (`:1743`, `:1778`), `settings.is_fluid` (`:272`). Every one of those
25
- * properties is on the same struct under every mode, so RNA cannot answer the
26
- * poll and the condition is read from the ENGINE's live value instead — a copy
27
- * of Blender's test, never of its branching.
28
- *
29
- * WHAT THE TRANSCRIPTION DELIBERATELY DROPS:
30
- * - `PARTICLE_PT_context_particles` (`:156`, `bl_label = ""`) — the systems
31
- * list plus the settings ID template. The list itself is the first panel
32
- * here, as a collection with the active system marked; the `template_ID`
33
- * row is an editing widget.
34
- * - the Boids panels (`:931`-`:1045`) and the fluid-spring panels
35
- * (`:835`-`:928`): both read sub-structs of the settings (`part.boids`,
36
- * `part.fluid`), which stand as POINTER rows in the Physics panel and
37
- * drill. Naming a fourth and fifth datablock on this tab to reach a mode
38
- * almost nothing uses is the machinery the generic view exists to avoid.
39
- * - `PARTICLE_PT_boidbrain` (`:1254`) — a rules list with per-rule operators.
40
- * - `template_curve_mapping` (the clump and roughness curves) — a curve
41
- * widget this view does not draw.
42
- * - `PARTICLE_PT_custom_props` (`:2243`), IDProperties rather than RNA.
43
- *
44
- * It stands when: `buttons_context_path_particle` — `ob->type == OB_MESH`.
45
- */
1
+ /** Blender’s Particles Properties tab follows scripts/startup/bl_ui/properties_particle.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
 
@@ -193,7 +149,7 @@ const CURATED: readonly BlenderCuratedPanel[] = [
193
149
  },
194
150
  {
195
151
  // PARTICLE_PT_physics, :730. `boids` and `fluid` are the two mode
196
- // sub-structs whose own panels are one drill away (see the header).
152
+ // sub-structs whose own panels are one drill away.
197
153
  title: 'Physics',
198
154
  closed: true,
199
155
  from: 'Settings',
@@ -1,42 +1,4 @@
1
- /**
2
- * BLENDER'S PHYSICS 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 SEVEN FILES, not one: `properties_physics_common.py`
7
- * carries the tab's own header block, and `properties_physics_rigidbody.py`,
8
- * `_rigidbody_constraint.py`, `_cloth.py`, `_softbody.py`, `_field.py`
9
- * (Force Fields AND Collision), `_fluid.py` and `_dynamicpaint.py` each carry
10
- * one simulation. Each panel below cites its file and line. Blender's UI
11
- * layer is never run, ported or recorded.
12
- *
13
- * THE "ENABLE BUTTONS" ROW IS NOT DRAWN, AND WHAT IT SAYS STILL IS.
14
- * `PHYSICS_PT_add` (`properties_physics_common.py:55`) is a grid of
15
- * `object.modifier_add` / `rigidbody.object_add` OPERATORS — editing, not
16
- * inspection (ARCHITECTURE-CORE §Blender north star), and this view draws
17
- * properties. What that row COMMUNICATES is which simulations the object has,
18
- * and that is exactly which panels stand here: the context door resolves a
19
- * path per simulation the way `physics_add`/`physics_add_special` test for
20
- * one — the data for `obj.rigid_body`, `obj.rigid_body_constraint`,
21
- * `obj.soft_body`, `obj.collision` and `obj.field`, and a MODIFIER of the
22
- * right type for cloth, fluid and dynamic paint (Blender's `context.cloth` is
23
- * the ClothModifier, and the panels read its `settings` structs). An absent
24
- * simulation is an absent path and its panels do not draw.
25
- *
26
- * FLUID AND DYNAMIC PAINT STOP AT THEIR TYPE. Both are a modifier whose whole
27
- * panel set is per-`fluid_type` / per-`ui_type` — domain, flow and effector
28
- * are three different property surfaces, and dynamic paint's canvas splits
29
- * again per surface. What stands here is the modifier's type row and the
30
- * pointer to the settings struct the type selects, which drills into the
31
- * generic view; transcribing three or four alternative panel sets each is the
32
- * machinery the generic view exists to make unnecessary. Stated, not
33
- * silently partial.
34
- *
35
- * It stands when: `buttons_context_path_object` — Physics shares Object's
36
- * path, so the engine grants it whenever it grants Object. It is
37
- * context-gated rather than standing because the Properties editor follows
38
- * the ACTIVE OBJECT, not the row that was clicked.
39
- */
1
+
40
2
  import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
41
3
  import type { BlenderCuratedPanel } from './blender-properties-view';
42
4
 
@@ -578,8 +540,7 @@ const CURATED: readonly BlenderCuratedPanel[] = [
578
540
  ],
579
541
  },
580
542
  {
581
- // properties_physics_fluid.py — see the header for why this stops at the
582
- // type and the settings pointer.
543
+ // properties_physics_fluid.py: show the type and settings pointers; each pointer drills into its properties.
583
544
  title: 'Fluid',
584
545
  from: 'Fluid',
585
546
  properties: ['fluid_type', 'domain_settings', 'flow_settings', 'effector_settings'],