@volter/editor-blender 0.1.150 → 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,49 +1,10 @@
1
- /**
2
- * THE NODE EDITOR — Blender's Shader Editor, READ-ONLY, over the active
3
- * material's RNA node tree (WORK.md §Blender in the tab is Blender,
4
- * "Inspection parity", I5; ARCHITECTURE-CORE §Blender north star).
5
- *
6
- * OUR PANEL, BLENDER'S DRAWING. Blender's own UI layer — `bl_ui`, the node
7
- * editor's C++ drawing code — is never run, ported as a UI system, or
8
- * recorded here. It is READ as the specification of what to draw, and every
9
- * number it specifies lives in `./blender-node-geometry.ts` with its file and
10
- * line beside it. This module is an ordinary React component over that data.
11
- *
12
- * INSPECTION PARITY, NOT EDITING PARITY. Nothing here writes. A gesture that
13
- * WOULD edit — dragging a node, dragging a link, typing into a socket's value
14
- * — is refused BY NAME in the frame's status line rather than quietly doing
15
- * nothing, which is the ruling's own "never a silent degrade". The only state
16
- * this view owns is INSPECTION state: which node is looked at, and where the
17
- * view is panned. Selecting a node here does not write `Node.select` in the
18
- * engine (that would be a mutation the document saves); the ENGINE's own
19
- * selection is still drawn, in Blender's own selected outline.
20
- *
21
- * WHY SVG AND NOT CANVAS 2D. The tree is a few dozen rounded rects, circles
22
- * and cubics, and SVG gives three things a 2D context would have to
23
- * reimplement: the browser's own `C` path segment for `node_link_bezier`'s
24
- * cubic, real text layout and measurement for the header labels, and hit
25
- * testing for free on every node and socket. The measured tree this unit was
26
- * built against is 2 nodes and 37 sockets; a pathological 200-node tree is
27
- * ~1,200 elements, which is inside what a browser lays out in one frame. The
28
- * moment a tree is measured slow, the answer is a canvas with the same
29
- * geometry module behind it — which is why the geometry lives in its own
30
- * module and this file holds no constants.
31
- *
32
- * WHY IT IS THE DRAWER AND NOT A SECOND DOCUMENT. Blender's Shading workspace
33
- * puts the Shader Editor in the AREA BELOW the 3D viewport, and this host's
34
- * bottom group (`volter:bottom-center`) is that area: the Model workspace hides
35
- * it for exactly this reason, recorded in `workspace-regions.ts` — "Blender's
36
- * modeling workspace has no timeline strip". The Shading workspace shows it
37
- * and puts this in it.
38
- */
1
+
39
2
 
40
3
  import type { BlenderNode, BlenderNodeTree } from '@volter/blender-engine/browser/rna';
41
- import { registerViewVerbs } from '@volter/sdk/views';
42
4
  import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
43
5
  import {
44
6
  blenderNodeTree,
45
7
  blenderRnaVersion,
46
- NODE_VIEW_VERBS,
47
8
  subscribeBlenderRna,
48
9
  } from '../host/blender-runtime-host';
49
10
  import type { NodeViewTransform } from '../src/node-view-state';
@@ -107,26 +68,7 @@ export const point = 'workspace.document';
107
68
  * the editor-type menu): "Shader Editor" for a `ShaderNodeTree`. */
108
69
  export const title = 'Shader Editor';
109
70
 
110
- // THE VIEW'S PRODUCT DOOR (WORK.md §The core is Code-OSS U8, ruling 1). This
111
- // view's verbs are published ONCE, here, where the view itself is contributed:
112
- // under the Code-OSS frame each becomes a `volter.blender-node-view.<verb>`
113
- // command the bridge dispatches into the view, and standalone `cyclotron edit` —
114
- // which has no command service — reaches the SAME table through the session's
115
- // `blender-node-view` verb. One table, two doors, which is why the remaining
116
- // read-only editors (UV Editing, Animation, Texture Paint) add no session verb
117
- // of their own: they register here instead.
118
- // THIS LINE RUNS ONCE PER EVALUATION, and a contribution is evaluated more
119
- // than once per session by design — the tool loader imports it as
120
- // `?t=<version>`, so a save re-evaluates it against a registry that already
121
- // holds the view. `registerViewVerbs` recognises the re-evaluation and the
122
- // newer table wins; measured live 2026-09-19, before it did, the first reload
123
- // threw and the loader reported "editor app failed to start", taking the whole
124
- // editor down with the view.
125
- registerViewVerbs(NODE_VIEW_VERBS);
126
-
127
- /** The N-panel's width. `bl_ui` sizes a sidebar in `UI_UNIT_X`; the node
128
- * editor's sidebar opens at the region default, measured in Blender 5.2 at
129
- * ~16 units. */
71
+ /** The node sidebar opens at about 16 UI_UNIT_X units in Blender 5.2. */
130
72
  const SIDEBAR_WIDTH = 16 * 20;
131
73
 
132
74
  /** One unit of tree space per CSS px is Blender's zoom 1 — the node editor
@@ -137,23 +79,13 @@ const ZOOM_MAX = 4;
137
79
  type View = NodeViewTransform;
138
80
 
139
81
  export default function BlenderNodeEditor() {
140
- // THE TREE'S FRESHNESS IS THE TREE'S, NOT THE PRESENTED FRAME'S (ruling 3,
141
- // 2026-09-19). This used to read the properties model's version, which moves
142
- // on a presented frame — and collapsing a socket panel writes
143
- // `Node.panel_states[n].is_collapsed`, which presents NOTHING, so the view
144
- // kept drawing the old tree until the document was reopened (measured by the
145
- // socket-panel step that shipped the panels). `blenderRnaVersion` is the RNA
146
- // door's own, bumped by the write, and a frame bumps it too.
82
+ // Use the RNA door’s version: every write and presented frame bumps it, including panel-state writes that change no rendered geometry.
147
83
  const version = useSyncExternalStore(subscribeBlenderRna, blenderRnaVersion, blenderRnaVersion);
148
84
  const [tree, setTree] = useState<BlenderNodeTree | null>(null);
149
85
  const [error, setError] = useState<string | null>(null);
150
86
  const canvas = useRef<HTMLDivElement | null>(null);
151
87
  const framed = useRef(false);
152
- // THE VIEW'S STATE IS THE STORE'S (`../src/node-view-state.ts`), not this
153
- // component's, because the session verb `blender-node-view` has to reach it:
154
- // `editor.document.*` is scoped to the active CENTER document and a drawer
155
- // utility is not one, so a `useState` here would be a surface nothing in the
156
- // product could read or drive.
88
+ // Share view state with the blender-node-view command through node-view-state.ts.
157
89
  useSyncExternalStore(subscribeNodeView, nodeViewVersion, nodeViewVersion);
158
90
  const { transform: view, looked, refusal, size } = nodeViewState();
159
91
  const setView = useCallback((next: View | ((current: View) => View)): void => {
@@ -272,7 +204,7 @@ export default function BlenderNodeEditor() {
272
204
  // `VIEW2D_OT_pan`, bound to `MIDDLEMOUSE` in the default keymap).
273
205
  if (event.button !== 1) return;
274
206
  event.preventDefault();
275
- (event.target as Element).setPointerCapture?.(event.pointerId);
207
+ (event.target as Element).setPointerCapture(event.pointerId);
276
208
  panning.current = { x: event.clientX, y: event.clientY, cx: view.cx, cy: view.cy };
277
209
  };
278
210
  const onPointerMove = (event: React.PointerEvent<SVGSVGElement>) => {
@@ -310,11 +242,7 @@ export default function BlenderNodeEditor() {
310
242
  >
311
243
  <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0, minHeight: 0 }}>
312
244
  <NodeEditorHeader tree={tree} zoom={view.zoom} onViewAll={viewAll} onRefuse={refuse} />
313
- {/* THE CANVAS BOX. Its height comes from FLEX, never from its own
314
- content: a `height: 100%` SVG inside an auto-height parent makes
315
- the parent's height a function of the SVG's, which is a function
316
- of the parent's — measured live 2026-09-19, that loop settled at
317
- ~30 px and drew the whole tree in a sliver at 31 % zoom. */}
245
+ {/* Flex supplies the canvas height; a percentage-height SVG inside an auto-height parent would size the parent from its own content. */}
318
246
  <div ref={canvas} style={{ position: 'relative', flex: 1, minHeight: 0 }}>
319
247
  <svg
320
248
  role="presentation"
@@ -383,8 +311,6 @@ interface LaidLink {
383
311
  readonly key: string;
384
312
  readonly from: readonly [number, number];
385
313
  readonly to: readonly [number, number];
386
- readonly fromColor: string;
387
- readonly toColor: string;
388
314
  readonly muted: boolean;
389
315
  readonly valid: boolean;
390
316
  }
@@ -419,7 +345,6 @@ function layoutTree(tree: BlenderNodeTree | null): {
419
345
  const placed = new Map<string, LaidOutNode>();
420
346
  const entries = tree.nodes.map((node): LaidEntry => {
421
347
  const flat = {
422
- name: node.name,
423
348
  location: node.location,
424
349
  width: node.width,
425
350
  collapsed: node.collapsed,
@@ -469,9 +394,8 @@ function layoutTree(tree: BlenderNodeTree | null): {
469
394
  return { node, laid, panels: [], fallback: null, approximate: false };
470
395
  });
471
396
  const links: LaidLink[] = tree.links.flatMap((link, index) => {
472
- const from = placed.get(link.fromNode);
473
- const to = placed.get(link.toNode);
474
- if (!from || !to) return [];
397
+ const from = placed.get(link.fromNode)!;
398
+ const to = placed.get(link.toNode)!;
475
399
  const fromSocket = from.sockets.find((s) => s.output && s.identifier === link.fromSocket);
476
400
  const toSocket = to.sockets.find((s) => !s.output && s.identifier === link.toSocket);
477
401
  if (!fromSocket || !toSocket) return [];
@@ -480,8 +404,6 @@ function layoutTree(tree: BlenderNodeTree | null): {
480
404
  key: `${link.fromNode}.${link.fromSocket}→${link.toNode}.${link.toSocket}#${index}`,
481
405
  from: [fromSocket.x, fromSocket.y] as const,
482
406
  to: [toSocket.x, toSocket.y] as const,
483
- fromColor: socketColor(fromSocket.type),
484
- toColor: socketColor(toSocket.type),
485
407
  muted: link.muted,
486
408
  valid: link.valid,
487
409
  },
@@ -837,11 +759,7 @@ function PanelHeader({
837
759
  );
838
760
  }
839
761
 
840
- /** Blender's socket mark: a 10×10 box (radius `NODE_SOCKSIZE`) in the socket
841
- * TYPE's colour with a 1 px `TH_WIRE` outline, shaped by `display_shape`
842
- * (`node_draw.cc:1824-1857`; the shapes themselves are SDFs in
843
- * `gpu_shader_2D_node_socket*.glsl`, which is outside the sparse checkout —
844
- * the silhouettes below are drawn from the enum's own names). */
762
+ /** Socket marks use Blender's type colours, TH_WIRE outlines and display_shape silhouettes. */
845
763
  function Socket({
846
764
  socket,
847
765
  node,
@@ -925,12 +843,8 @@ function Socket({
925
843
  * `:529`), which is why an output's name is right-aligned against the node's
926
844
  * edge and an input's widget fills the row.
927
845
  *
928
- * WHY THE WIDGET IS DRAWN HERE AND NOT BORROWED FROM THE PROPERTIES RAIL.
929
- * This surface is a ZOOMING tree-space canvas: a DOM control would need a
930
- * `foreignObject` per socket and would not scale with the view transform, so
931
- * it would stop being the same drawing at any zoom but 1. The Properties
932
- * rail's widgets are reused where they belong — the N-panel, which is
933
- * ordinary unzoomed DOM.
846
+ * SVG widgets use the node canvas's tree-space transform. The N-panel uses
847
+ * ordinary unzoomed DOM controls.
934
848
  */
935
849
  function SocketRow({
936
850
  socket,
@@ -965,6 +879,7 @@ function SocketRow({
965
879
  }
966
880
 
967
881
  const isColour = Array.isArray(socket.value) && socket.value.length >= 3;
882
+ const fraction = sliderFraction(socket);
968
883
  return (
969
884
  <g
970
885
  onPointerDown={(event) => {
@@ -989,15 +904,8 @@ function SocketRow({
989
904
  fill="#545454"
990
905
  transform="scale(1 -1)"
991
906
  />
992
- {/* THE SLIDER'S FILL. A scalar socket with a bounded SOFT range is drawn
993
- by Blender as `UI_BTYPE_NUM_SLIDER`, whose back is filled to the
994
- value's proportion of that range (`widget_numslider`,
995
- `interface_widgets.cc`) — which is why Roughness at 0.5 reads as
996
- half-filled at a glance and ours read as a text box (sighted read,
997
- 2026-09-20). No range means no fill, which is also what Blender does
998
- for an unbounded number. The fill is clipped to the field's own
999
- rounded rect so a full-value slider does not square off its corners. */}
1000
- {sliderFraction(socket) !== null && (
907
+ {/* A bounded scalar socket fills its rounded field to its fraction of the soft range (widget_numslider). Unbounded values have no fill. */}
908
+ {fraction !== null && (
1001
909
  <>
1002
910
  <clipPath id={`sl-${socket.identifier}`}>
1003
911
  <rect
@@ -1011,7 +919,7 @@ function SocketRow({
1011
919
  <rect
1012
920
  x={left}
1013
921
  y={centre - NODE_DY / 2 + 1}
1014
- width={(right - left) * (sliderFraction(socket) ?? 0)}
922
+ width={(right - left) * fraction}
1015
923
  height={NODE_DY - 2}
1016
924
  fill={NODE_THEME.sliderFill}
1017
925
  clipPath={`url(#sl-${socket.identifier})`}
@@ -1037,7 +945,7 @@ function SocketRow({
1037
945
  width={30}
1038
946
  height={NODE_DY - 6}
1039
947
  rx={2}
1040
- fill={rgbFloatsToHex(socket.value as readonly number[])}
948
+ fill={rgbFloatsToHex(socket.value as unknown as readonly [number, number, number, ...number[]])}
1041
949
  transform="scale(1 -1)"
1042
950
  />
1043
951
  ) : (
@@ -1059,15 +967,14 @@ function SocketRow({
1059
967
  }
1060
968
 
1061
969
  /**
1062
- * How far along its soft range a scalar socket sits, or `null` when it is not
1063
- * a slider at all. Blender's own test is the property's soft range being
1064
- * bounded (`ui_but_is_slider` → `UI_BTYPE_NUM_SLIDER`); the engine reports the
1065
- * pair only for a bounded scalar, so the presence of the pair IS the test.
970
+ * A scalar factor socket's fraction of its soft range, or null without a
971
+ * numeric value and range. The engine reports only finite, increasing soft
972
+ * ranges for factor scalars.
1066
973
  */
1067
974
  function sliderFraction(socket: LaidOutSocket): number | null {
1068
975
  const { softMin, softMax, value } = socket;
1069
976
  if (softMin === undefined || softMax === undefined) return null;
1070
- if (typeof value !== 'number' || softMax <= softMin) return null;
977
+ if (typeof value !== 'number') return null;
1071
978
  return Math.min(1, Math.max(0, (value - softMin) / (softMax - softMin)));
1072
979
  }
1073
980
 
@@ -1104,10 +1011,8 @@ function Noodle({ link }: { readonly link: LaidLink }) {
1104
1011
  );
1105
1012
  }
1106
1013
 
1107
- /** A frame: its own colour at `TH_NODE_FRAME`'s alpha, radius `BASIS_RAD` with
1108
- * no 0.5 padding (`frame_node_draw_background`, `node_draw.cc:3749-3772`),
1109
- * and a centred label `0.5 * margin_top + 0.35 * label_size` below the top
1110
- * (`node_draw.cc:3474-3484, 3699-3703`). */
1014
+ /** A frame with its own colour at TH_NODE_FRAME's alpha, BASIS_RAD corners and
1015
+ * a centred 20-pixel label 25 tree units below the top. */
1111
1016
  function FrameNode({ node, laid }: { readonly node: BlenderNode; readonly laid: LaidOutNode }) {
1112
1017
  const height = Math.max(laid.yTop - laid.yBottom, FRAME_MARGIN * 2);
1113
1018
  return (
@@ -1140,21 +1045,7 @@ function FrameNode({ node, laid }: { readonly node: BlenderNode; readonly laid:
1140
1045
 
1141
1046
  /* -------------------------------------------------------------------------- */
1142
1047
 
1143
- /**
1144
- * THE HEADER, per `bl_ui/space_node.py:41-260` — read as a specification and
1145
- * drawn READ-ONLY, which is where it diverges from Blender by design: a
1146
- * control that would WRITE is drawn as what it reads and refuses the click by
1147
- * name. What the shader path's header carries, in order: the tree-type well
1148
- * (`template_header`, `:55`), the shader-type selector (`snode.shader_type`,
1149
- * `:62`), the material slot popover and the material ID (`:79-93`), the pin
1150
- * (`:222-224`), the breadcrumb parent (`:228-230`), snapping
1151
- * (`tool_settings.use_snap_node`, `:249-252`) and the overlays popover
1152
- * (`:254-260`).
1153
- *
1154
- * There is deliberately NO "Use Nodes" checkbox: measured in the source, 5.2
1155
- * draws it only for Line Style (`:119`) and Texture (`:133`) trees, never for
1156
- * a material's.
1157
- */
1048
+ /** Read-only tree/material labels, zoom and View All; Use Nodes reports its write refusal. */
1158
1049
  function NodeEditorHeader({
1159
1050
  tree,
1160
1051
  zoom,
@@ -1213,20 +1104,7 @@ function NodeEditorHeader({
1213
1104
  );
1214
1105
  }
1215
1106
 
1216
- /**
1217
- * THE STATUS LINE — where a refusal lands, and where anything this view
1218
- * cannot draw is named rather than silently skipped.
1219
- *
1220
- * THE PANEL GAP IS CLOSED, and what is left of it is stated here rather than
1221
- * hidden. A node's socket-panel MEMBERSHIP is traced from Blender's own
1222
- * `declare()` bodies (`blender.node-panels.json`) and its COLLAPSED STATE is
1223
- * read live from `Node.panel_states`, so a Principled BSDF draws the eight
1224
- * collapsed panel rows Blender draws. Two things still fall back, and both
1225
- * say so by name: a node whose LIVE socket list disagrees with the traced
1226
- * declaration (the ruling's own condition), and a node the trace does not
1227
- * cover at all — a node GROUP, whose panels are its tree interface's runtime
1228
- * data rather than a declaration.
1229
- */
1107
+ /** The status line names refusals and node-layout limitations. Socket panels use traced declarations and live panel_states; mismatched socket lists and runtime node groups draw flat with a named warning. */
1230
1108
  function StatusLine({
1231
1109
  entries,
1232
1110
  error,
@@ -1272,20 +1150,7 @@ function StatusLine({
1272
1150
  );
1273
1151
  }
1274
1152
 
1275
- /**
1276
- * THE N-PANEL'S NODE TAB — `NODE_PT_active_node_generic`
1277
- * (`bl_ui/space_node.py:800-841`), in its own order: `name` (`:819`), `label`
1278
- * (`:820`), the Color heading with `use_custom_color` and `color`
1279
- * (`:822-834`), then `show_options` (`:837`) and `mute` (`:838`). Blender
1280
- * 5.2 has no separate `NODE_PT_active_node_color`; the colour controls are
1281
- * that panel's own sub-column.
1282
- *
1283
- * `NODE_PT_active_node_properties` (`:844-857`) is ONE call —
1284
- * `layout.template_node_inputs(node)` — and that template is C: it draws the
1285
- * same per-socket widgets the node body draws. The node's own inputs are
1286
- * listed here with their values for exactly that reason, which is the same
1287
- * fact through the same door.
1288
- */
1153
+ /** Read-only looked-at node name, label, colour, mute and visible socket values. */
1289
1154
  function NodePanel({
1290
1155
  node,
1291
1156
  onRefuse,
@@ -1301,15 +1166,9 @@ function NodePanel({
1301
1166
  flex: '0 0 auto',
1302
1167
  overflow: 'auto',
1303
1168
  // THE SIDEBAR IS PART OF THE NODE EDITOR'S AREA, so it wears the
1304
- // space's own `TH_BACK` like the header and the canvas do. It used to
1305
- // paint `--volter-color-surface-panel`, which is the DOCK's grey
1306
- // (#303030 under the Blender look): Blender fills every region of a
1307
- // node editor area with `TH_BACK` and draws panels ON it
1308
- // (`ED_region_panels`), so the grey was this view wearing its
1309
- // container's colour. MEASURED 2026-09-19 (the frame walk, beat 5):
1310
- // at (600,500) — inside the Shader Editor's pane, which a 320px
1311
- // sidebar had squeezed the canvas out of — the read was #303030 where
1312
- // the beat named #1a1a1a. The frame's `volter.nodeEditor.background`
1169
+ // space's own `TH_BACK` like the header and the canvas do, not the
1170
+ // DOCK's grey: Blender fills every region of a node editor area with
1171
+ // `TH_BACK` and draws panels ON it (`ED_region_panels`). The frame's `volter.nodeEditor.background`
1313
1172
  // theme colour carries the same traced value for the workbench's own
1314
1173
  // painting; this is the same number, from the same trace, for the
1315
1174
  // pixels we paint ourselves.
@@ -1322,8 +1181,6 @@ function NodePanel({
1322
1181
  >
1323
1182
  <div style={{ opacity: 0.6, marginBottom: 'var(--volter-space-2)' }}>Node</div>
1324
1183
  {!node ? (
1325
- // Blender's panel `poll` is `context.active_node is not None`
1326
- // (`space_node.py:806-808`): with no active node the tab draws nothing.
1327
1184
  <div style={{ opacity: 0.5 }}>No node looked at.</div>
1328
1185
  ) : (
1329
1186
  <>
@@ -1391,12 +1248,3 @@ function Row({
1391
1248
  </div>
1392
1249
  );
1393
1250
  }
1394
-
1395
- // THERE IS DELIBERATELY NO `available` GATE. A `workspace.utility` may gate
1396
- // its tab on session state, and the Network utility does because a game may or
1397
- // may not expose a networking adapter. This tab's condition is that the
1398
- // BLENDER BUILD is loaded, and a contribution of this package cannot be
1399
- // registered unless it is — the same reason `model.layout.ts` gates on the
1400
- // project declaring a `model` document and nothing else. With no material the
1401
- // view draws what Blender draws with no tree: the bare background, and the
1402
- // header saying so.