@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.
- 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 +3 -7
- package/contributions/blender-node-editor.document.tsx +28 -180
- 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 +100 -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 +15 -90
- package/contributions/blender-properties-tab.tsx +3 -50
- package/contributions/blender-properties-view.tsx +15 -147
- package/contributions/blender-remedy.ts +14 -0
- package/contributions/blender-runtime-skin.ts +103 -99
- package/contributions/blender-runtime.document.tsx +217 -339
- 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 +142 -332
- package/contributions/blender-uv-editor.document.tsx +48 -112
- package/contributions/blender-uv-geometry.ts +14 -179
- package/contributions/blender-version.status.tsx +43 -23
- 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 +63 -0
- package/contributions/blender.status.tsx +9 -145
- 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 +12 -32
- 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 -30
- package/host/blender-runtime-host.ts +541 -372
- package/host/scene-sound.ts +143 -0
- package/node/blender-mcp.ts +49 -93
- package/package.json +1209 -46
- package/producticons/blender-icons.otf +0 -0
- package/producticons/blender-product-icon-theme.json +129 -0
- package/serving/blender-routes.ts +9 -43
- 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/model-play-extension.ts +173 -0
- package/src/node-view-state.ts +3 -25
- package/src/play-mode.ts +64 -38
- 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,31 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* BLENDER'S NODE EDITOR, AS NUMBERS — the geometry and the colours the node
|
|
3
|
-
* view draws with, each one READ from Blender 5.2.0's own source at the
|
|
4
|
-
* engine's pin (`fbe6228777e7`) and cited beside it.
|
|
5
|
-
*
|
|
6
|
-
* WORK.md §Blender in the tab is Blender, "Inspection parity", I5;
|
|
7
|
-
* ARCHITECTURE-CORE §Blender north star, "Inspection parity, not editing
|
|
8
|
-
* parity" and "The reference is Blender's SOURCE as well as its frames".
|
|
9
|
-
* Blender's own drawing code is never run, ported as a UI system, or
|
|
10
|
-
* recorded — it is READ as the specification of what to draw, which is what
|
|
11
|
-
* every citation below is doing.
|
|
12
|
-
*
|
|
13
|
-
* THE SCALE. Every constant here is stated at `UI_SCALE_FAC = 1` and
|
|
14
|
-
* `U.pixelsize = 1`, which makes `U.widget_unit = int(round(18 * 1)) + 2 * 1`
|
|
15
|
-
* = **20** (`windowmanager/intern/wm_window.cc:779`). The node editor draws
|
|
16
|
-
* WITHOUT DPI — "Nodes draw without DPI - the view zoom is flexible", the
|
|
17
|
-
* comment above the macro block (`space_node/node_intern.hh:332`) — so a node's
|
|
18
|
-
* tree-space size is these numbers exactly and the VIEW's zoom is the only
|
|
19
|
-
* scale between them and the screen.
|
|
20
|
-
*
|
|
21
|
-
* WHY THE LAYOUT IS COMPUTED HERE AND NOT READ. `Node.dimensions` is RNA's
|
|
22
|
-
* answer for a node's drawn size, and it is `bNode.runtime->draw_bounds`,
|
|
23
|
-
* which only the node editor's own draw pass fills in. Measured 2026-09-19 on
|
|
24
|
-
* the engine in the tab: every node of a freshly built Principled material
|
|
25
|
-
* answers `dimensions == [0, 0]`, because this Blender has no node editor
|
|
26
|
-
* region and never will. So the height is derived the way
|
|
27
|
-
* `node_update_basis` derives it, from facts RNA does answer.
|
|
28
|
-
*/
|
|
1
|
+
|
|
29
2
|
|
|
30
3
|
/* -------------------------------------------------------------------------- */
|
|
31
4
|
/* Geometry — `space_node/node_intern.hh:332-344` */
|
|
@@ -65,10 +38,6 @@ export const NODE_ITEM_SPACING_Y = 0.1 * WIDGET_UNIT;
|
|
|
65
38
|
/** `#define NODE_HEADER_ICON_SIZE (0.8f * U.widget_unit)` (`node_draw.cc:2143`). */
|
|
66
39
|
export const NODE_HEADER_ICON_SIZE = 0.8 * WIDGET_UNIT;
|
|
67
40
|
|
|
68
|
-
/** `#define NODE_MULTI_INPUT_LINK_GAP (0.25f * U.widget_unit)`
|
|
69
|
-
* (`node_intern.hh:341`) — how far apart a multi-input socket fans its links. */
|
|
70
|
-
export const NODE_MULTI_INPUT_LINK_GAP = 0.25 * WIDGET_UNIT;
|
|
71
|
-
|
|
72
41
|
/** The 0.5 every filled rect is grown by "to prevent transparent gaps with the
|
|
73
42
|
* outline" (`node_draw.cc:2920`), and the radius that goes with it. */
|
|
74
43
|
export const NODE_FILL_PADDING = 0.5;
|
|
@@ -77,20 +46,10 @@ export const NODE_FILL_RADIUS = BASIS_RAD + NODE_FILL_PADDING;
|
|
|
77
46
|
* (`node_draw.cc:3184-3211`). */
|
|
78
47
|
export const NODE_OUTLINE_RADIUS = BASIS_RAD + PIXEL_SIZE;
|
|
79
48
|
|
|
80
|
-
/** `NODE_GRID_UNIT` (`editors/include/ED_node_c.hh:31-32`) — the dot grid's
|
|
81
|
-
* finest step, and the snapping unit. */
|
|
82
|
-
export const NODE_GRID_STEP = 20;
|
|
83
|
-
|
|
84
49
|
/** `LINK_WIDTH` (`space_node/drawnode.cc:1959`) — a noodle's stroke at 100 %,
|
|
85
50
|
* clamped never to go below this as the view zooms out
|
|
86
51
|
* (`drawnode.cc:2299-2302`); a FIELD link draws at 0.7 of it. */
|
|
87
52
|
export const LINK_WIDTH = 2.5;
|
|
88
|
-
export const LINK_FIELD_WIDTH_FACTOR = 0.7;
|
|
89
|
-
|
|
90
|
-
/** `NODE_LINK_RESOL` (`node_intern.hh:343`) — how many segments Blender's own
|
|
91
|
-
* CPU evaluation of a noodle uses. SVG draws the cubic itself, so this is
|
|
92
|
-
* here as the record of what the reference samples, not as a step count. */
|
|
93
|
-
export const NODE_LINK_RESOL = 12;
|
|
94
53
|
|
|
95
54
|
/** `NODE_TREE_SCALE_SMALL` (`node_draw.cc:1596`) — below `1/aspect > 0.2`,
|
|
96
55
|
* `draw_node_details` is false and Blender draws NO sockets, no reroute
|
|
@@ -113,7 +72,6 @@ export const REROUTE_RADIUS = NODE_SOCKSIZE;
|
|
|
113
72
|
* margin)`, and the baseline sits `0.5 * margin_top + 0.35 * label_size`
|
|
114
73
|
* below the top (`node_draw.cc:3474-3484`). */
|
|
115
74
|
export const FRAME_MARGIN = 1.5 * WIDGET_UNIT;
|
|
116
|
-
export const FRAME_LABEL_SIZE_DEFAULT = 20;
|
|
117
75
|
|
|
118
76
|
/** The label's point size — `UI_DEFAULT_TEXT_POINTS` (`UI_interface_c.hh:425`),
|
|
119
77
|
* which `style->widget` takes (`interface_style.cc:91`). A node's name is an
|
|
@@ -128,30 +86,12 @@ export const UI_TEXT_POINTS = 11;
|
|
|
128
86
|
/** Every value is the DEFAULT THEME's, by member and line. A member absent
|
|
129
87
|
* from the initializer takes the struct's zero and is called out. */
|
|
130
88
|
export const NODE_THEME = {
|
|
131
|
-
/**
|
|
132
|
-
* (`widget_numslider`, `interface_widgets.cc`, which draws the item rect to
|
|
133
|
-
* the value's proportion before the text).
|
|
134
|
-
*
|
|
135
|
-
* SAMPLED FROM THE FRAME, not recalled: `rgb(70,113,178)` at the Roughness
|
|
136
|
-
* and Alpha fills on Blender 5.2.0 LTS's own shader editor at 2x, against
|
|
137
|
-
* `rgb(83,83,83)` in the same row's unfilled remainder. It is BLUE — the
|
|
138
|
-
* playhead's family — and the first pass here guessed a grey a step lighter
|
|
139
|
-
* than the field, drew it, and produced a slider nobody could see. The
|
|
140
|
-
* frame was on screen the whole time. */
|
|
89
|
+
/** wcol_numslider.item fills the value’s fraction of the slider; source colour is rgb(70,113,178). */
|
|
141
90
|
sliderFill: '#4671b2',
|
|
142
91
|
/** `.back` `0x1a1a1a00` (`:653`). The alpha is 0x00 and IRRELEVANT:
|
|
143
92
|
* `draw_background_color` clears with the RGB and a hard 1.0
|
|
144
93
|
* (`node_draw.cc:4713-4718`), so the canvas is opaque #1a1a1a. */
|
|
145
94
|
background: '#1a1a1a',
|
|
146
|
-
/** `.grid` `0x30303000` (`:661`) — alpha ZERO. Blender's dot grid is
|
|
147
|
-
* therefore INVISIBLE under the shipped theme, and drawing one here would
|
|
148
|
-
* be a mark the reference does not make. `.grid_levels` is 3 (`:671`) and
|
|
149
|
-
* the draw is `view2d_dot_grid_draw(&v2d, TH_GRID, NODE_GRID_STEP_SIZE,
|
|
150
|
-
* grid_levels)` (`node_draw.cc:4755-4756`) — a DOT grid, not
|
|
151
|
-
* `UI_view2d_multi_grid_draw`, which the node editor does not call. */
|
|
152
|
-
grid: '#303030',
|
|
153
|
-
gridAlpha: 0,
|
|
154
|
-
gridLevels: 3,
|
|
155
95
|
/** `.syntaxl` `0x303030ff` (`:673`), which `resources.cc:659-660` returns
|
|
156
96
|
* for `TH_NODE`. EVERY node's BODY is this colour whatever its class
|
|
157
97
|
* (`node_draw.cc:3149`); only the header carries the class hue. */
|
|
@@ -171,11 +111,6 @@ export const NODE_THEME = {
|
|
|
171
111
|
/** `.wire` `0x1a1a1aff` (`:662`) → `TH_WIRE`: a noodle's OUTLINE
|
|
172
112
|
* (`drawnode.cc:2309`) and a socket's outline (`node_draw.cc:1502-1505`). */
|
|
173
113
|
wire: '#1a1a1a',
|
|
174
|
-
/** `.edge_select` `0xffffffb3` (`:665`), whose Python name IS `wire_select`
|
|
175
|
-
* (`rna_userdef.cc:3604-3605`) — a SELECTED noodle blends toward this by
|
|
176
|
-
* its own alpha (`drawnode.cc:2338-2352`). */
|
|
177
|
-
wireSelect: '#ffffff',
|
|
178
|
-
wireSelectAlpha: 0xb3 / 255,
|
|
179
114
|
/** `.noodle_curving` `4` (`:670`) → `TH_NODE_CURVING`
|
|
180
115
|
* (`resources.cc:713-714`). NOTE the divergence worth recording: RNA's
|
|
181
116
|
* property default is 5 (`rna_userdef.cc:3658-3663`), but the SHIPPED THEME
|
|
@@ -293,7 +228,6 @@ export interface LaidOutSocket {
|
|
|
293
228
|
readonly shape: string;
|
|
294
229
|
readonly output: boolean;
|
|
295
230
|
readonly linked: boolean;
|
|
296
|
-
readonly multiInput: boolean;
|
|
297
231
|
readonly hideValue: boolean;
|
|
298
232
|
readonly value: number | string | boolean | readonly number[] | null;
|
|
299
233
|
/** See {@link LayoutSocketInput.softMin} — carried through so the inline
|
|
@@ -314,7 +248,6 @@ export interface LaidOutSocket {
|
|
|
314
248
|
}
|
|
315
249
|
|
|
316
250
|
export interface LaidOutNode {
|
|
317
|
-
readonly name: string;
|
|
318
251
|
readonly x: number;
|
|
319
252
|
readonly yTop: number;
|
|
320
253
|
readonly yBottom: number;
|
|
@@ -333,7 +266,6 @@ interface LayoutSocketInput {
|
|
|
333
266
|
readonly hide: boolean;
|
|
334
267
|
readonly hideValue: boolean;
|
|
335
268
|
readonly linked: boolean;
|
|
336
|
-
readonly multiInput: boolean;
|
|
337
269
|
readonly value: number | string | boolean | readonly number[] | null;
|
|
338
270
|
/** The `default_value` property's SOFT range, present only when the socket
|
|
339
271
|
* is a bounded scalar — which is exactly Blender's own test for drawing a
|
|
@@ -343,8 +275,7 @@ interface LayoutSocketInput {
|
|
|
343
275
|
}
|
|
344
276
|
|
|
345
277
|
interface LayoutNodeInput {
|
|
346
|
-
readonly
|
|
347
|
-
readonly location: readonly number[];
|
|
278
|
+
readonly location: readonly [number, number];
|
|
348
279
|
readonly width: number;
|
|
349
280
|
readonly collapsed: boolean;
|
|
350
281
|
readonly inputs: readonly LayoutSocketInput[];
|
|
@@ -357,43 +288,10 @@ export function socketDraws(socket: LayoutSocketInput): boolean {
|
|
|
357
288
|
return socket.enabled && !socket.hide;
|
|
358
289
|
}
|
|
359
290
|
|
|
360
|
-
/**
|
|
361
|
-
* ONE NODE, LAID OUT — `node_update_basis` (`node_draw.cc:1288-1324`) run
|
|
362
|
-
* over the facts RNA answers.
|
|
363
|
-
*
|
|
364
|
-
* ```
|
|
365
|
-
* loc = node.location node_draw.cc:1295
|
|
366
|
-
* dy = loc.y node_draw.cc:1297
|
|
367
|
-
* dy -= NODE_DY node_draw.cc:1300 // the header, exactly 20
|
|
368
|
-
* …outputs, then inputs…
|
|
369
|
-
* xmin = loc.x ; xmax = loc.x + NODE_WIDTH(node) node_draw.cc:1309-1310
|
|
370
|
-
* ymax = loc.y ; ymin = min(dy, loc.y - 2 * NODE_DY) node_draw.cc:1311-1312
|
|
371
|
-
* ```
|
|
372
|
-
* — so a node is never shorter than two header-heights, 40.
|
|
373
|
-
*
|
|
374
|
-
* WHICH OF BLENDER'S TWO BODY PATHS THIS IS, and the one divergence it
|
|
375
|
-
* carries. `node_update_basis` branches on `is_node_panels_supported()` =
|
|
376
|
-
* `decl->use_custom_socket_order` (`node_draw.cc:343-346, 1302-1307`):
|
|
377
|
-
*
|
|
378
|
-
* - the DECLARATION path (`node_update_basis_from_declaration`, `:1086-1218`)
|
|
379
|
-
* spaces items through one margin table in units of `NODE_ITEM_SPACING_Y`
|
|
380
|
-
* (`:886-1000`): 2× before the first socket, 1× between two sockets, 2×
|
|
381
|
-
* after the last;
|
|
382
|
-
* - the LEGACY path (`node_update_basis_from_socket_lists`, `:1227-1283`)
|
|
383
|
-
* uses `NODE_DYS / 2` = 5 at the top, `NODE_ITEM_SPACING_Y` between
|
|
384
|
-
* sockets, `NODE_DY / 4` = 5 between the outputs and the inputs, and
|
|
385
|
-
* `NODE_DYS / 2` = 5 at the bottom.
|
|
386
|
-
*
|
|
387
|
-
* RNA EXPOSES NO `use_custom_socket_order`, so this cannot be read from the
|
|
388
|
-
* engine. The declaration path is the one Blender 5.2 lays nearly every
|
|
389
|
-
* built-in node out with, so it is the one implemented, and the cost is
|
|
390
|
-
* STATED rather than hidden: a node Blender lays out the legacy way sits
|
|
391
|
-
* 1 px lower at its first socket, 3 px lower at its first input, and ends
|
|
392
|
-
* 1 px further down — at most 5 px over a whole node.
|
|
393
|
-
*/
|
|
291
|
+
/** Default node geometry uses declaration socket spacing: location anchors the top-left, the header is NODE_DY high, and the body is at least two headers tall. Traced custom-order declarations use layoutNodeWithPanels. */
|
|
394
292
|
export function layoutNode(node: LayoutNodeInput): LaidOutNode {
|
|
395
|
-
const x = node.location[0]
|
|
396
|
-
const top = node.location[1]
|
|
293
|
+
const x = node.location[0];
|
|
294
|
+
const top = node.location[1];
|
|
397
295
|
const width = node.width;
|
|
398
296
|
const outputs = node.outputs.filter(socketDraws);
|
|
399
297
|
const inputs = node.inputs.filter(socketDraws);
|
|
@@ -413,7 +311,6 @@ export function layoutNode(node: LayoutNodeInput): LaidOutNode {
|
|
|
413
311
|
});
|
|
414
312
|
};
|
|
415
313
|
return {
|
|
416
|
-
name: node.name,
|
|
417
314
|
x,
|
|
418
315
|
yTop,
|
|
419
316
|
yBottom,
|
|
@@ -444,9 +341,7 @@ export function layoutNode(node: LayoutNodeInput): LaidOutNode {
|
|
|
444
341
|
});
|
|
445
342
|
if (outputs.length > 0 && inputs.length > 0) dy -= NODE_ITEM_SPACING_Y;
|
|
446
343
|
inputs.forEach((socket, index) => {
|
|
447
|
-
if (index > 0
|
|
448
|
-
if (index > 0) dy -= NODE_ITEM_SPACING_Y;
|
|
449
|
-
}
|
|
344
|
+
if (index > 0) dy -= NODE_ITEM_SPACING_Y;
|
|
450
345
|
row(socket, false);
|
|
451
346
|
});
|
|
452
347
|
if (outputs.length > 0 || inputs.length > 0) {
|
|
@@ -456,7 +351,6 @@ export function layoutNode(node: LayoutNodeInput): LaidOutNode {
|
|
|
456
351
|
}
|
|
457
352
|
|
|
458
353
|
return {
|
|
459
|
-
name: node.name,
|
|
460
354
|
x,
|
|
461
355
|
yTop: top,
|
|
462
356
|
// `ymin = min_ff(dy, loc.y - 2 * NODE_DY)` — node_draw.cc:1311.
|
|
@@ -482,7 +376,6 @@ function socketAt(
|
|
|
482
376
|
shape: socket.shape,
|
|
483
377
|
output,
|
|
484
378
|
linked: socket.linked,
|
|
485
|
-
multiInput: socket.multiInput,
|
|
486
379
|
hideValue: socket.hideValue,
|
|
487
380
|
value: socket.value,
|
|
488
381
|
...(socket.softMin === undefined ? {} : { softMin: socket.softMin }),
|
|
@@ -513,14 +406,8 @@ function socketAt(
|
|
|
513
406
|
export function linkHandles(
|
|
514
407
|
from: readonly [number, number],
|
|
515
408
|
to: readonly [number, number],
|
|
516
|
-
curving: number = NODE_THEME.noodleCurving,
|
|
517
409
|
): [[number, number], [number, number]] {
|
|
518
|
-
|
|
519
|
-
return [
|
|
520
|
-
[from[0] + (to[0] - from[0]) / 3, from[1] + (to[1] - from[1]) / 3],
|
|
521
|
-
[from[0] + ((to[0] - from[0]) * 2) / 3, from[1] + ((to[1] - from[1]) * 2) / 3],
|
|
522
|
-
];
|
|
523
|
-
}
|
|
410
|
+
const curving = NODE_THEME.noodleCurving;
|
|
524
411
|
const distX = Math.abs(to[0] - from[0]);
|
|
525
412
|
const distY = Math.abs(to[1] - from[1]);
|
|
526
413
|
// `slope` divides by dist_x; Blender's own code does the same and relies on
|
|
@@ -562,18 +449,18 @@ export function nodeHeaderColor(node: {
|
|
|
562
449
|
* (`node_draw.cc:3145-3149`). */
|
|
563
450
|
export function nodeBodyColor(node: {
|
|
564
451
|
readonly useCustomColor: boolean;
|
|
565
|
-
readonly color: readonly number
|
|
452
|
+
readonly color: readonly [number, number, number];
|
|
566
453
|
}): string {
|
|
567
454
|
if (!node.useCustomColor) return NODE_THEME.body;
|
|
568
455
|
return rgbFloatsToHex(node.color);
|
|
569
456
|
}
|
|
570
457
|
|
|
571
|
-
export function rgbFloatsToHex(channels: readonly number[]): string {
|
|
458
|
+
export function rgbFloatsToHex(channels: readonly [number, number, number, ...number[]]): string {
|
|
572
459
|
const byte = (value: number): string =>
|
|
573
460
|
Math.round(Math.min(1, Math.max(0, value)) * 255)
|
|
574
461
|
.toString(16)
|
|
575
462
|
.padStart(2, '0');
|
|
576
|
-
return `#${byte(channels[0]
|
|
463
|
+
return `#${byte(channels[0])}${byte(channels[1])}${byte(channels[2])}`;
|
|
577
464
|
}
|
|
578
465
|
|
|
579
466
|
/**
|
|
@@ -609,22 +496,9 @@ export function socketColor(type: string): string {
|
|
|
609
496
|
/* SOCKET PANELS — `node_update_basis_from_declaration`'s other half */
|
|
610
497
|
/* -------------------------------------------------------------------------- */
|
|
611
498
|
|
|
612
|
-
/**
|
|
613
|
-
* A PANEL HEADER'S ROW, in the layout: `NODE_DYS` tall, its centre exactly
|
|
614
|
-
* half that below where the row starts — `locy -= h/2; center = locy;
|
|
615
|
-
* locy -= h/2` (`node_draw.cc:1189-1195`). It is HALF a socket row, which is
|
|
616
|
-
* the whole reason eight collapsed panels cost 160 px where their thirty
|
|
617
|
-
* sockets cost 660.
|
|
618
|
-
*/
|
|
499
|
+
/** A panel-header row is NODE_DYS tall, half a socket row (node_draw.cc:1189-1195). */
|
|
619
500
|
export const PANEL_HEADER_HEIGHT = NODE_DYS;
|
|
620
501
|
|
|
621
|
-
/**
|
|
622
|
-
* The header's DRAWN band is twice its laid-out height — `center ± NODE_DYS`
|
|
623
|
-
* (`node_draw.cc:2005-2008`) — so two consecutive headers' bands abut exactly
|
|
624
|
-
* at the 20-unit pitch they sit at.
|
|
625
|
-
*/
|
|
626
|
-
export const PANEL_HEADER_BAND = 2 * NODE_DYS;
|
|
627
|
-
|
|
628
502
|
/** `header_but_margin = NODE_MARGIN_X / 3` — where the triangle starts, and
|
|
629
503
|
* the inset the whole header button takes (`node_draw.cc:2031-2038`). */
|
|
630
504
|
export const PANEL_HEADER_MARGIN_X = NODE_MARGIN_X / 3;
|
|
@@ -635,34 +509,7 @@ export const PANEL_HEADER_BUT_PADDING = NODE_MARGIN_X / 4;
|
|
|
635
509
|
* (`node_draw.cc:2059-2078`). */
|
|
636
510
|
export const UI_UNIT_X = WIDGET_UNIT;
|
|
637
511
|
|
|
638
|
-
/**
|
|
639
|
-
* BLENDER'S PANEL TRIANGLE, as geometry rather than as a lookalike.
|
|
640
|
-
*
|
|
641
|
-
* `node_draw_panels` asks for `ICON_RIGHTARROW` when the panel is collapsed
|
|
642
|
-
* and `ICON_DOWNARROW_HLT` when it is open, at `but_size = U.widget_unit *
|
|
643
|
-
* 0.8` = {@link NODE_HEADER_ICON_SIZE} (`node_draw.cc:2035-2046`). Both marks
|
|
644
|
-
* are single stroked chevrons, and these numbers are read straight off their
|
|
645
|
-
* own path data in the checkout:
|
|
646
|
-
*
|
|
647
|
-
* - `release/datafiles/icons_svg/rightarrow.svg` —
|
|
648
|
-
* `l3.64649 3.64648 -3.64649 3.64648` with a `.50005` round cap, under
|
|
649
|
-
* `matrix(100 0 0 100 -40899.645 -60100.058)`: a chevron reaching **4**
|
|
650
|
-
* units along its axis and **±3.646** across, stroked **1** unit wide, on
|
|
651
|
-
* Blender's **8**-unit small-icon cell.
|
|
652
|
-
* - `downarrow_hlt.svg` — `l4 4 4 -4`, the same chevron turned a quarter.
|
|
653
|
-
*
|
|
654
|
-
* DELIBERATELY NOT ADDED TO `blender.icons.traced.json`, and the reason is a
|
|
655
|
-
* measured defect in that trace rather than a preference:
|
|
656
|
-
* `traceBlenderIcon`'s one scaling rule is `16 / viewBox.width`, which is
|
|
657
|
-
* right only for the 1600-wide artboards that carry a `matrix(100 …)`.
|
|
658
|
-
* These two files are 700x1100 and 1100x700, and **13 glyphs already in that
|
|
659
|
-
* set** come from non-1600 artboards and are scaled wrong —
|
|
660
|
-
* `blender-mod-particle-instance` traces to a box at **[273, -60]**, off the
|
|
661
|
-
* 16-unit grid entirely, and `rightarrow` would have traced to a 10.8x20.3
|
|
662
|
-
* mark. Correcting that trace moves twelve shipped marks and belongs to
|
|
663
|
-
* whoever owns the icon set; stating it here, with the numbers, is this
|
|
664
|
-
* unit's honest half.
|
|
665
|
-
*/
|
|
512
|
+
/** Panel toggles draw the source rightarrow and downarrow_hlt chevrons in Blender’s 8-unit small-icon cell, scaled to NODE_HEADER_ICON_SIZE. */
|
|
666
513
|
export const PANEL_TRIANGLE = {
|
|
667
514
|
/** Blender's small-icon cell — the space the numbers below are on. */
|
|
668
515
|
cell: 8,
|
|
@@ -686,21 +533,17 @@ export const PANEL_TRIANGLE = {
|
|
|
686
533
|
export const PANEL_SUB_BACK = '#000000';
|
|
687
534
|
export const PANEL_SUB_BACK_ALPHA = (0x1f / 255) * 1.5;
|
|
688
535
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
536
|
+
interface NodeSocketDecl {
|
|
537
|
+
readonly name: string;
|
|
538
|
+
readonly identifier?: string;
|
|
539
|
+
readonly panelToggle?: boolean;
|
|
540
|
+
readonly alignWithPrevious?: boolean;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** One item of the traced C++ declaration in blender.node-panels.json: inputs, outputs, panels, layouts and separators expand into the flat layout items. */
|
|
695
544
|
export type NodeDeclItem =
|
|
696
|
-
| {
|
|
697
|
-
|
|
698
|
-
readonly name: string;
|
|
699
|
-
readonly identifier?: string;
|
|
700
|
-
readonly panelToggle?: boolean;
|
|
701
|
-
readonly conditional?: boolean;
|
|
702
|
-
readonly alignWithPrevious?: boolean;
|
|
703
|
-
}
|
|
545
|
+
| ({ readonly kind: 'input' } & NodeSocketDecl)
|
|
546
|
+
| ({ readonly kind: 'output' } & NodeSocketDecl)
|
|
704
547
|
| {
|
|
705
548
|
readonly kind: 'panel';
|
|
706
549
|
readonly name: string;
|
|
@@ -712,7 +555,6 @@ export type NodeDeclItem =
|
|
|
712
555
|
|
|
713
556
|
export interface NodeDeclaration {
|
|
714
557
|
readonly customSocketOrder: boolean;
|
|
715
|
-
readonly conditional?: boolean;
|
|
716
558
|
readonly items: readonly NodeDeclItem[];
|
|
717
559
|
}
|
|
718
560
|
|
|
@@ -722,23 +564,7 @@ export interface NodeDeclaration {
|
|
|
722
564
|
const declIdentifier = (item: { readonly name: string; readonly identifier?: string }): string =>
|
|
723
565
|
item.identifier ?? item.name;
|
|
724
566
|
|
|
725
|
-
/**
|
|
726
|
-
* DOES THE TRACED DECLARATION DESCRIBE THIS LIVE NODE?
|
|
727
|
-
*
|
|
728
|
-
* The ruling's own condition — "when the live socket list disagrees with the
|
|
729
|
-
* traced declaration the view draws flat with the frame warning it draws
|
|
730
|
-
* today". The check is STRICT and ordered: every socket the declaration
|
|
731
|
-
* names, in declaration order, against `node.inputs`/`node.outputs` by
|
|
732
|
-
* IDENTIFIER.
|
|
733
|
-
*
|
|
734
|
-
* Strict is the right strength because an UNAVAILABLE socket is still in the
|
|
735
|
-
* live list: `.available(false)` sets `SOCK_UNAVAIL`, which RNA reports as
|
|
736
|
-
* `enabled == False`, not as an absent socket. So a declaration that matches
|
|
737
|
-
* the engine matches it exactly, and anything less is the engine having moved
|
|
738
|
-
* out from under the trace — a different Blender, a `declare()` that took the
|
|
739
|
-
* other arm of an `if`, a node group whose panels are runtime data. Each of
|
|
740
|
-
* those must fall back, and be SEEN to.
|
|
741
|
-
*/
|
|
567
|
+
/** Match each traced socket identifier against the live inputs and outputs in order. Unavailable sockets remain in the live list. A mismatch or runtime group declaration uses the named flat-layout fallback. */
|
|
742
568
|
export function declarationAgrees(
|
|
743
569
|
declaration: NodeDeclaration,
|
|
744
570
|
inputs: readonly { readonly identifier: string }[],
|
|
@@ -747,7 +573,7 @@ export function declarationAgrees(
|
|
|
747
573
|
if (!declaration.customSocketOrder) {
|
|
748
574
|
return {
|
|
749
575
|
ok: false,
|
|
750
|
-
why: 'its declaration does not set use_custom_socket_order, so Blender
|
|
576
|
+
why: 'its declaration does not set use_custom_socket_order, so Blender uses the default socket order',
|
|
751
577
|
};
|
|
752
578
|
}
|
|
753
579
|
const declaredIn: string[] = [];
|
|
@@ -769,8 +595,8 @@ export function declarationAgrees(
|
|
|
769
595
|
return `the declaration names ${declared.length} ${side}${declared.length === 1 ? '' : 's'} and the engine reports ${live.length}`;
|
|
770
596
|
}
|
|
771
597
|
for (let index = 0; index < declared.length; index++) {
|
|
772
|
-
if (declared[index] !== live[index]
|
|
773
|
-
return `${side} ${index} is "${live[index]
|
|
598
|
+
if (declared[index] !== live[index]!.identifier) {
|
|
599
|
+
return `${side} ${index} is "${live[index]!.identifier}" in the engine and "${declared[index]}" in the declaration`;
|
|
774
600
|
}
|
|
775
601
|
}
|
|
776
602
|
return null;
|
|
@@ -850,12 +676,10 @@ const MARGIN_BETWEEN: Partial<Record<FlatType, Partial<Record<FlatType, number>>
|
|
|
850
676
|
PanelContentEnd: { Socket: 1, Separator: 1, Layout: 1, PanelHeader: 3, PanelContentEnd: 0 },
|
|
851
677
|
};
|
|
852
678
|
|
|
853
|
-
|
|
854
|
-
readonly type:
|
|
855
|
-
readonly
|
|
856
|
-
readonly
|
|
857
|
-
readonly panel?: PanelState | undefined;
|
|
858
|
-
}
|
|
679
|
+
type FlatItem =
|
|
680
|
+
| { readonly type: 'Socket'; readonly input?: LayoutSocketInput | undefined; readonly output?: LayoutSocketInput | undefined }
|
|
681
|
+
| { readonly type: 'Layout' | 'Separator' }
|
|
682
|
+
| { readonly type: 'PanelHeader' | 'PanelContentBegin' | 'PanelContentEnd'; readonly panel: PanelState };
|
|
859
683
|
|
|
860
684
|
interface PanelState {
|
|
861
685
|
readonly name: string;
|
|
@@ -900,14 +724,13 @@ function flatten(
|
|
|
900
724
|
}
|
|
901
725
|
// `add_flat_items_for_socket`: an ALIGNED socket joins the previous
|
|
902
726
|
// item's row instead of opening one (`node_draw.cc:703-717`).
|
|
903
|
-
|
|
904
|
-
|
|
727
|
+
if (item.alignWithPrevious === true && previousVisible) {
|
|
728
|
+
const last = out[out.length - 1] as Extract<FlatItem, { type: 'Socket' }>;
|
|
905
729
|
out[out.length - 1] = {
|
|
906
730
|
type: 'Socket',
|
|
907
731
|
input: item.kind === 'input' ? socket : last.input,
|
|
908
732
|
output: item.kind === 'output' ? socket : last.output,
|
|
909
733
|
};
|
|
910
|
-
previousVisible = true;
|
|
911
734
|
continue;
|
|
912
735
|
}
|
|
913
736
|
out.push({
|
|
@@ -929,12 +752,6 @@ function flatten(
|
|
|
929
752
|
if (showOptions) out.push({ type: 'Layout' });
|
|
930
753
|
continue;
|
|
931
754
|
}
|
|
932
|
-
// A DISCRIMINANT THAT IS ITSELF A UNION (`kind: 'input' | 'output'`) does
|
|
933
|
-
// not narrow away in the NEGATIVE branch of `a === 'input' || a ===
|
|
934
|
-
// 'output'`, so without this guard `item` still carries that member here
|
|
935
|
-
// and `item.items` does not typecheck. It is a compiler fact, not a real
|
|
936
|
-
// branch.
|
|
937
|
-
if (item.kind !== 'panel') continue;
|
|
938
755
|
// A PANEL. `panel_input_decl()` (`node_declaration.cc:528-542`): the
|
|
939
756
|
// panel's FIRST item, when it is a `panel_toggle` boolean input, is drawn
|
|
940
757
|
// in the header and takes no row of its own.
|
|
@@ -1015,8 +832,8 @@ export interface LaidOutPanelledNode extends LaidOutNode {
|
|
|
1015
832
|
* with.
|
|
1016
833
|
*/
|
|
1017
834
|
export function layoutNodeWithPanels(node: PanelLayoutInput): LaidOutPanelledNode {
|
|
1018
|
-
const x = node.location[0]
|
|
1019
|
-
const top = node.location[1]
|
|
835
|
+
const x = node.location[0];
|
|
836
|
+
const top = node.location[1];
|
|
1020
837
|
const width = node.width;
|
|
1021
838
|
const sockets = new Map<string, LayoutSocketInput>();
|
|
1022
839
|
for (const socket of node.inputs) sockets.set(`input:${socket.identifier}`, socket);
|
|
@@ -1043,13 +860,12 @@ export function layoutNodeWithPanels(node: PanelLayoutInput): LaidOutPanelledNod
|
|
|
1043
860
|
dy -= NODE_DYS; // `get_margin_empty()` — node_draw.cc:833-836
|
|
1044
861
|
}
|
|
1045
862
|
for (let index = 0; index < items.length; index++) {
|
|
1046
|
-
const item = items[index]
|
|
1047
|
-
if (!item) continue;
|
|
863
|
+
const item = items[index]!;
|
|
1048
864
|
const previous = items[index - 1];
|
|
1049
865
|
const margin =
|
|
1050
866
|
index === 0
|
|
1051
867
|
? (MARGIN_FROM_TOP[item.type] ?? 0)
|
|
1052
|
-
: (MARGIN_BETWEEN[previous
|
|
868
|
+
: (MARGIN_BETWEEN[previous!.type]?.[item.type] ?? 0);
|
|
1053
869
|
dy -= margin * NODE_ITEM_SPACING_Y;
|
|
1054
870
|
|
|
1055
871
|
if (item.type === 'Socket') {
|
|
@@ -1066,7 +882,7 @@ export function layoutNodeWithPanels(node: PanelLayoutInput): LaidOutPanelledNod
|
|
|
1066
882
|
dy -= NODE_DY;
|
|
1067
883
|
continue;
|
|
1068
884
|
}
|
|
1069
|
-
if (item.type === 'PanelHeader'
|
|
885
|
+
if (item.type === 'PanelHeader') {
|
|
1070
886
|
dy -= PANEL_HEADER_HEIGHT / 2;
|
|
1071
887
|
item.panel.centerY = dy;
|
|
1072
888
|
dy -= PANEL_HEADER_HEIGHT / 2;
|
|
@@ -1075,8 +891,8 @@ export function layoutNodeWithPanels(node: PanelLayoutInput): LaidOutPanelledNod
|
|
|
1075
891
|
}
|
|
1076
892
|
continue;
|
|
1077
893
|
}
|
|
1078
|
-
if (item.type === 'PanelContentBegin'
|
|
1079
|
-
if (item.type === 'PanelContentEnd'
|
|
894
|
+
if (item.type === 'PanelContentBegin') item.panel.contentTop = dy;
|
|
895
|
+
if (item.type === 'PanelContentEnd') item.panel.contentBottom = dy;
|
|
1080
896
|
}
|
|
1081
897
|
|
|
1082
898
|
const last = items[items.length - 1];
|
|
@@ -1097,16 +913,14 @@ export function layoutNodeWithPanels(node: PanelLayoutInput): LaidOutPanelledNod
|
|
|
1097
913
|
}
|
|
1098
914
|
}
|
|
1099
915
|
|
|
1100
|
-
// `tag_final_panel` (`node_draw.cc:1063-1082`):
|
|
1101
|
-
// the innermost panel whose content runs to the node's very bottom.
|
|
916
|
+
// `tag_final_panel` (`node_draw.cc:1063-1082`): mark each closing panel at the node's bottom.
|
|
1102
917
|
for (let index = items.length - 1; index >= 0; index--) {
|
|
1103
|
-
const item = items[index]
|
|
1104
|
-
if (
|
|
1105
|
-
|
|
918
|
+
const item = items[index]!;
|
|
919
|
+
if (item.type !== 'PanelContentEnd') break;
|
|
920
|
+
item.panel.fillsNodeEnd = true;
|
|
1106
921
|
}
|
|
1107
922
|
|
|
1108
923
|
return {
|
|
1109
|
-
name: node.name,
|
|
1110
924
|
x,
|
|
1111
925
|
yTop: top,
|
|
1112
926
|
yBottom: Math.min(dy, top - 2 * NODE_DY),
|