@volter/editor-blender 0.1.150 → 0.1.152
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -9
- package/README.md +5 -5
- package/contributions/blender-animation-editors.tsx +17 -69
- package/contributions/blender-display-settings.ts +36 -0
- package/contributions/blender-export-gltf.ts +228 -0
- package/contributions/blender-export.verbs.ts +39 -0
- package/contributions/blender-gltf-export.verbs.ts +312 -0
- package/contributions/blender-header-menus.tsx +37 -133
- package/contributions/blender-icon-trace.mjs +8 -98
- package/contributions/blender-icons.source.mjs +96 -860
- package/contributions/blender-mixer-pose.ts +369 -0
- package/contributions/blender-model-opening.tsx +26 -56
- package/contributions/blender-node-editor.document.tsx +30 -181
- package/contributions/blender-node-geometry.ts +44 -230
- package/contributions/blender-node-panels.source.mjs +7 -121
- package/contributions/blender-outliner-authoring.ts +61 -415
- package/contributions/blender-outliner-model.ts +15 -112
- package/contributions/blender-play-area.tsx +99 -0
- package/contributions/blender-playback-movie.ts +409 -0
- package/contributions/blender-pose.ts +18 -368
- package/contributions/blender-properties-context.ts +1 -1
- package/contributions/blender-properties-model.ts +59 -91
- package/contributions/blender-properties-tab.tsx +3 -50
- package/contributions/blender-properties-view.tsx +39 -181
- package/contributions/blender-remedy.ts +14 -0
- package/contributions/blender-runtime-skin.ts +103 -99
- package/contributions/blender-runtime.document.tsx +170 -618
- package/contributions/blender-scene-mixer.ts +51 -0
- package/contributions/blender-screenshot.service.ts +28 -0
- package/contributions/blender-timeline-geometry.ts +16 -136
- package/contributions/blender-timeline.document.tsx +145 -333
- package/contributions/blender-uv-editor.document.tsx +48 -112
- package/contributions/blender-uv-geometry.ts +14 -179
- package/contributions/blender-version.status.tsx +45 -31
- package/contributions/blender.environment.ts +2 -2
- package/contributions/blender.icons.json +0 -9
- package/contributions/blender.keymap.ts +9 -51
- package/contributions/blender.node-panels.json +4 -13
- package/contributions/blender.page.ts +64 -0
- package/contributions/blender.status.tsx +8 -150
- package/contributions/blender.style.ts +25 -152
- package/contributions/blender.verbs.ts +52 -0
- package/contributions/model.layout.ts +2 -34
- package/contributions/models.finder.ts +3 -30
- package/contributions/properties-bone-constraints.inspector.tsx +1 -16
- package/contributions/properties-bone.inspector.tsx +2 -31
- package/contributions/properties-collection.inspector.tsx +1 -34
- package/contributions/properties-constraints.inspector.tsx +1 -21
- package/contributions/properties-data.inspector.tsx +2 -39
- package/contributions/properties-material.inspector.tsx +2 -33
- package/contributions/properties-modifiers.inspector.tsx +2 -30
- package/contributions/properties-object.inspector.tsx +2 -44
- package/contributions/properties-output.inspector.tsx +1 -27
- package/contributions/properties-particles.inspector.tsx +2 -46
- package/contributions/properties-physics.inspector.tsx +2 -41
- package/contributions/properties-render.inspector.tsx +1 -45
- package/contributions/properties-scene.inspector.tsx +1 -35
- package/contributions/properties-texture.inspector.tsx +1 -49
- package/contributions/properties-view-layer.inspector.tsx +1 -29
- package/contributions/properties-world.inspector.tsx +2 -39
- package/contributions/shading.layout.ts +3 -78
- package/contributions/uv-editing.layout.ts +3 -70
- package/dist-node/serving.mjs +13 -32
- package/host/blender-runtime-host.ts +795 -412
- package/host/scene-sound.ts +143 -0
- package/node/blender-mcp.ts +52 -95
- package/package.json +1221 -46
- package/producticons/blender-icons.otf +0 -0
- package/producticons/blender-product-icon-theme.json +129 -0
- package/serving/blender-routes.ts +9 -48
- package/serving/blender-wasm-artifact.ts +29 -166
- package/serving/index.ts +1 -1
- package/src/api.ts +9 -0
- package/src/layouts.tsx +1 -17
- package/src/looks.ts +1 -12
- package/src/model-document-saving.ts +25 -0
- package/src/node-view-state.ts +3 -25
- package/src/play-mode.ts +13 -42
- package/src/play-tool.ts +44 -0
- package/src/presentation.ts +6 -18
- package/src/regions.ts +3 -43
- package/src/start-document.ts +15 -0
- package/src/timeline-view-state.ts +4 -39
- package/src/uv-view-state.ts +4 -20
- package/themes/blender.json +299 -0
- package/view/blender-view-routes.ts +1 -1
- package/contributions/blender-game-panel.tsx +0 -809
- package/contributions/blender-palette.source.mjs +0 -350
- package/contributions/blender-play-skin.ts +0 -508
- package/contributions/blender-screenshot.menu.ts +0 -32
- package/contributions/blender.command.ts +0 -109
- package/contributions/blender.palette.json +0 -102
- package/src/play-live.ts +0 -27
|
@@ -1,49 +1,11 @@
|
|
|
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';
|
|
5
|
+
import { StateSurface } from '@volter/sdk/widgets';
|
|
43
6
|
import {
|
|
44
7
|
blenderNodeTree,
|
|
45
8
|
blenderRnaVersion,
|
|
46
|
-
NODE_VIEW_VERBS,
|
|
47
9
|
subscribeBlenderRna,
|
|
48
10
|
} from '../host/blender-runtime-host';
|
|
49
11
|
import type { NodeViewTransform } from '../src/node-view-state';
|
|
@@ -107,26 +69,7 @@ export const point = 'workspace.document';
|
|
|
107
69
|
* the editor-type menu): "Shader Editor" for a `ShaderNodeTree`. */
|
|
108
70
|
export const title = 'Shader Editor';
|
|
109
71
|
|
|
110
|
-
|
|
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. */
|
|
72
|
+
/** The node sidebar opens at about 16 UI_UNIT_X units in Blender 5.2. */
|
|
130
73
|
const SIDEBAR_WIDTH = 16 * 20;
|
|
131
74
|
|
|
132
75
|
/** One unit of tree space per CSS px is Blender's zoom 1 — the node editor
|
|
@@ -137,23 +80,13 @@ const ZOOM_MAX = 4;
|
|
|
137
80
|
type View = NodeViewTransform;
|
|
138
81
|
|
|
139
82
|
export default function BlenderNodeEditor() {
|
|
140
|
-
//
|
|
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.
|
|
83
|
+
// Use the RNA door’s version: every write and presented frame bumps it, including panel-state writes that change no rendered geometry.
|
|
147
84
|
const version = useSyncExternalStore(subscribeBlenderRna, blenderRnaVersion, blenderRnaVersion);
|
|
148
85
|
const [tree, setTree] = useState<BlenderNodeTree | null>(null);
|
|
149
86
|
const [error, setError] = useState<string | null>(null);
|
|
150
87
|
const canvas = useRef<HTMLDivElement | null>(null);
|
|
151
88
|
const framed = useRef(false);
|
|
152
|
-
//
|
|
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.
|
|
89
|
+
// Share view state with the blender-node-view command through node-view-state.ts.
|
|
157
90
|
useSyncExternalStore(subscribeNodeView, nodeViewVersion, nodeViewVersion);
|
|
158
91
|
const { transform: view, looked, refusal, size } = nodeViewState();
|
|
159
92
|
const setView = useCallback((next: View | ((current: View) => View)): void => {
|
|
@@ -272,7 +205,7 @@ export default function BlenderNodeEditor() {
|
|
|
272
205
|
// `VIEW2D_OT_pan`, bound to `MIDDLEMOUSE` in the default keymap).
|
|
273
206
|
if (event.button !== 1) return;
|
|
274
207
|
event.preventDefault();
|
|
275
|
-
(event.target as Element).setPointerCapture
|
|
208
|
+
(event.target as Element).setPointerCapture(event.pointerId);
|
|
276
209
|
panning.current = { x: event.clientX, y: event.clientY, cx: view.cx, cy: view.cy };
|
|
277
210
|
};
|
|
278
211
|
const onPointerMove = (event: React.PointerEvent<SVGSVGElement>) => {
|
|
@@ -310,11 +243,7 @@ export default function BlenderNodeEditor() {
|
|
|
310
243
|
>
|
|
311
244
|
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0, minHeight: 0 }}>
|
|
312
245
|
<NodeEditorHeader tree={tree} zoom={view.zoom} onViewAll={viewAll} onRefuse={refuse} />
|
|
313
|
-
{/*
|
|
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. */}
|
|
246
|
+
{/* Flex supplies the canvas height; a percentage-height SVG inside an auto-height parent would size the parent from its own content. */}
|
|
318
247
|
<div ref={canvas} style={{ position: 'relative', flex: 1, minHeight: 0 }}>
|
|
319
248
|
<svg
|
|
320
249
|
role="presentation"
|
|
@@ -383,8 +312,6 @@ interface LaidLink {
|
|
|
383
312
|
readonly key: string;
|
|
384
313
|
readonly from: readonly [number, number];
|
|
385
314
|
readonly to: readonly [number, number];
|
|
386
|
-
readonly fromColor: string;
|
|
387
|
-
readonly toColor: string;
|
|
388
315
|
readonly muted: boolean;
|
|
389
316
|
readonly valid: boolean;
|
|
390
317
|
}
|
|
@@ -419,7 +346,6 @@ function layoutTree(tree: BlenderNodeTree | null): {
|
|
|
419
346
|
const placed = new Map<string, LaidOutNode>();
|
|
420
347
|
const entries = tree.nodes.map((node): LaidEntry => {
|
|
421
348
|
const flat = {
|
|
422
|
-
name: node.name,
|
|
423
349
|
location: node.location,
|
|
424
350
|
width: node.width,
|
|
425
351
|
collapsed: node.collapsed,
|
|
@@ -469,9 +395,8 @@ function layoutTree(tree: BlenderNodeTree | null): {
|
|
|
469
395
|
return { node, laid, panels: [], fallback: null, approximate: false };
|
|
470
396
|
});
|
|
471
397
|
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 [];
|
|
398
|
+
const from = placed.get(link.fromNode)!;
|
|
399
|
+
const to = placed.get(link.toNode)!;
|
|
475
400
|
const fromSocket = from.sockets.find((s) => s.output && s.identifier === link.fromSocket);
|
|
476
401
|
const toSocket = to.sockets.find((s) => !s.output && s.identifier === link.toSocket);
|
|
477
402
|
if (!fromSocket || !toSocket) return [];
|
|
@@ -480,8 +405,6 @@ function layoutTree(tree: BlenderNodeTree | null): {
|
|
|
480
405
|
key: `${link.fromNode}.${link.fromSocket}→${link.toNode}.${link.toSocket}#${index}`,
|
|
481
406
|
from: [fromSocket.x, fromSocket.y] as const,
|
|
482
407
|
to: [toSocket.x, toSocket.y] as const,
|
|
483
|
-
fromColor: socketColor(fromSocket.type),
|
|
484
|
-
toColor: socketColor(toSocket.type),
|
|
485
408
|
muted: link.muted,
|
|
486
409
|
valid: link.valid,
|
|
487
410
|
},
|
|
@@ -837,11 +760,7 @@ function PanelHeader({
|
|
|
837
760
|
);
|
|
838
761
|
}
|
|
839
762
|
|
|
840
|
-
/** Blender's
|
|
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). */
|
|
763
|
+
/** Socket marks use Blender's type colours, TH_WIRE outlines and display_shape silhouettes. */
|
|
845
764
|
function Socket({
|
|
846
765
|
socket,
|
|
847
766
|
node,
|
|
@@ -925,12 +844,8 @@ function Socket({
|
|
|
925
844
|
* `:529`), which is why an output's name is right-aligned against the node's
|
|
926
845
|
* edge and an input's widget fills the row.
|
|
927
846
|
*
|
|
928
|
-
*
|
|
929
|
-
*
|
|
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.
|
|
847
|
+
* SVG widgets use the node canvas's tree-space transform. The N-panel uses
|
|
848
|
+
* ordinary unzoomed DOM controls.
|
|
934
849
|
*/
|
|
935
850
|
function SocketRow({
|
|
936
851
|
socket,
|
|
@@ -965,6 +880,7 @@ function SocketRow({
|
|
|
965
880
|
}
|
|
966
881
|
|
|
967
882
|
const isColour = Array.isArray(socket.value) && socket.value.length >= 3;
|
|
883
|
+
const fraction = sliderFraction(socket);
|
|
968
884
|
return (
|
|
969
885
|
<g
|
|
970
886
|
onPointerDown={(event) => {
|
|
@@ -989,15 +905,8 @@ function SocketRow({
|
|
|
989
905
|
fill="#545454"
|
|
990
906
|
transform="scale(1 -1)"
|
|
991
907
|
/>
|
|
992
|
-
{/*
|
|
993
|
-
|
|
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 && (
|
|
908
|
+
{/* A bounded scalar socket fills its rounded field to its fraction of the soft range (widget_numslider). Unbounded values have no fill. */}
|
|
909
|
+
{fraction !== null && (
|
|
1001
910
|
<>
|
|
1002
911
|
<clipPath id={`sl-${socket.identifier}`}>
|
|
1003
912
|
<rect
|
|
@@ -1011,7 +920,7 @@ function SocketRow({
|
|
|
1011
920
|
<rect
|
|
1012
921
|
x={left}
|
|
1013
922
|
y={centre - NODE_DY / 2 + 1}
|
|
1014
|
-
width={(right - left) *
|
|
923
|
+
width={(right - left) * fraction}
|
|
1015
924
|
height={NODE_DY - 2}
|
|
1016
925
|
fill={NODE_THEME.sliderFill}
|
|
1017
926
|
clipPath={`url(#sl-${socket.identifier})`}
|
|
@@ -1037,7 +946,7 @@ function SocketRow({
|
|
|
1037
946
|
width={30}
|
|
1038
947
|
height={NODE_DY - 6}
|
|
1039
948
|
rx={2}
|
|
1040
|
-
fill={rgbFloatsToHex(socket.value as readonly number[])}
|
|
949
|
+
fill={rgbFloatsToHex(socket.value as unknown as readonly [number, number, number, ...number[]])}
|
|
1041
950
|
transform="scale(1 -1)"
|
|
1042
951
|
/>
|
|
1043
952
|
) : (
|
|
@@ -1059,15 +968,14 @@ function SocketRow({
|
|
|
1059
968
|
}
|
|
1060
969
|
|
|
1061
970
|
/**
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
* pair only for a bounded scalar, so the presence of the pair IS the test.
|
|
971
|
+
* A scalar factor socket's fraction of its soft range, or null without a
|
|
972
|
+
* numeric value and range. The engine reports only finite, increasing soft
|
|
973
|
+
* ranges for factor scalars.
|
|
1066
974
|
*/
|
|
1067
975
|
function sliderFraction(socket: LaidOutSocket): number | null {
|
|
1068
976
|
const { softMin, softMax, value } = socket;
|
|
1069
977
|
if (softMin === undefined || softMax === undefined) return null;
|
|
1070
|
-
if (typeof value !== 'number'
|
|
978
|
+
if (typeof value !== 'number') return null;
|
|
1071
979
|
return Math.min(1, Math.max(0, (value - softMin) / (softMax - softMin)));
|
|
1072
980
|
}
|
|
1073
981
|
|
|
@@ -1104,10 +1012,8 @@ function Noodle({ link }: { readonly link: LaidLink }) {
|
|
|
1104
1012
|
);
|
|
1105
1013
|
}
|
|
1106
1014
|
|
|
1107
|
-
/** A frame
|
|
1108
|
-
*
|
|
1109
|
-
* and a centred label `0.5 * margin_top + 0.35 * label_size` below the top
|
|
1110
|
-
* (`node_draw.cc:3474-3484, 3699-3703`). */
|
|
1015
|
+
/** A frame with its own colour at TH_NODE_FRAME's alpha, BASIS_RAD corners and
|
|
1016
|
+
* a centred 20-pixel label 25 tree units below the top. */
|
|
1111
1017
|
function FrameNode({ node, laid }: { readonly node: BlenderNode; readonly laid: LaidOutNode }) {
|
|
1112
1018
|
const height = Math.max(laid.yTop - laid.yBottom, FRAME_MARGIN * 2);
|
|
1113
1019
|
return (
|
|
@@ -1140,21 +1046,7 @@ function FrameNode({ node, laid }: { readonly node: BlenderNode; readonly laid:
|
|
|
1140
1046
|
|
|
1141
1047
|
/* -------------------------------------------------------------------------- */
|
|
1142
1048
|
|
|
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
|
-
*/
|
|
1049
|
+
/** Read-only tree/material labels, zoom and View All; Use Nodes reports its write refusal. */
|
|
1158
1050
|
function NodeEditorHeader({
|
|
1159
1051
|
tree,
|
|
1160
1052
|
zoom,
|
|
@@ -1213,20 +1105,7 @@ function NodeEditorHeader({
|
|
|
1213
1105
|
);
|
|
1214
1106
|
}
|
|
1215
1107
|
|
|
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
|
-
*/
|
|
1108
|
+
/** 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
1109
|
function StatusLine({
|
|
1231
1110
|
entries,
|
|
1232
1111
|
error,
|
|
@@ -1272,20 +1151,7 @@ function StatusLine({
|
|
|
1272
1151
|
);
|
|
1273
1152
|
}
|
|
1274
1153
|
|
|
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
|
-
*/
|
|
1154
|
+
/** Read-only looked-at node name, label, colour, mute and visible socket values. */
|
|
1289
1155
|
function NodePanel({
|
|
1290
1156
|
node,
|
|
1291
1157
|
onRefuse,
|
|
@@ -1301,15 +1167,9 @@ function NodePanel({
|
|
|
1301
1167
|
flex: '0 0 auto',
|
|
1302
1168
|
overflow: 'auto',
|
|
1303
1169
|
// 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
|
|
1305
|
-
//
|
|
1306
|
-
//
|
|
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`
|
|
1170
|
+
// space's own `TH_BACK` like the header and the canvas do, not the
|
|
1171
|
+
// DOCK's grey: Blender fills every region of a node editor area with
|
|
1172
|
+
// `TH_BACK` and draws panels ON it (`ED_region_panels`). The frame's `volter.nodeEditor.background`
|
|
1313
1173
|
// theme colour carries the same traced value for the workbench's own
|
|
1314
1174
|
// painting; this is the same number, from the same trace, for the
|
|
1315
1175
|
// pixels we paint ourselves.
|
|
@@ -1322,9 +1182,7 @@ function NodePanel({
|
|
|
1322
1182
|
>
|
|
1323
1183
|
<div style={{ opacity: 0.6, marginBottom: 'var(--volter-space-2)' }}>Node</div>
|
|
1324
1184
|
{!node ? (
|
|
1325
|
-
|
|
1326
|
-
// (`space_node.py:806-808`): with no active node the tab draws nothing.
|
|
1327
|
-
<div style={{ opacity: 0.5 }}>No node looked at.</div>
|
|
1185
|
+
<StateSurface inline title="No node looked at." />
|
|
1328
1186
|
) : (
|
|
1329
1187
|
<>
|
|
1330
1188
|
<Row label="Name" value={node.name} onRefuse={onRefuse} />
|
|
@@ -1391,12 +1249,3 @@ function Row({
|
|
|
1391
1249
|
</div>
|
|
1392
1250
|
);
|
|
1393
1251
|
}
|
|
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.
|