@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,50 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* THE GENERIC PROPERTIES VIEW — every `bl_rna` property of the active
|
|
3
|
-
* datablock, drawn in OUR panels (WORK.md §Blender in the tab is Blender,
|
|
4
|
-
* "Inspection parity", I1; ARCHITECTURE-CORE §Blender north star, "Inspection
|
|
5
|
-
* parity, not editing parity"). Blender's UI layer is never run, ported or
|
|
6
|
-
* recorded: what arrives is `session.py`'s RNA rows, and what is drawn here is
|
|
7
|
-
* this file's own widgets.
|
|
8
|
-
*
|
|
9
|
-
* ## The geometry is Blender's SOURCE, not an eyedropper
|
|
10
|
-
*
|
|
11
|
-
* Every number below cites the file and constant it came from, read at the
|
|
12
|
-
* engine's own pin (Blender 5.2.0, `fbe6228777e7`, `~/volter/blender-src`).
|
|
13
|
-
* That is the rule as of 2026-09-19: a theme value or a metric traces to
|
|
14
|
-
* `userdef_default_theme.c` / `interface_*.cc`, and a frame is what confirms
|
|
15
|
-
* the result rather than what supplies the number.
|
|
16
|
-
*
|
|
17
|
-
* UI_UNIT_Y 20 `UI_interface_c.hh:2346` (`U.widget_unit`, 1x)
|
|
18
|
-
* row gap 2 `interface_style.cc:110` (`buttonspacey`)
|
|
19
|
-
* label column 40% `interface_layout.cc:79` (UI_ITEM_PROP_SEP_DIVIDE)
|
|
20
|
-
* label alignment right `interface_layout.cc:3394` (LayoutAlign::Right)
|
|
21
|
-
* panel header 25 `interface_intern.hh:152` (PNL_HEADER = 1.25 UI_UNIT_Y)
|
|
22
|
-
* panel label x 20 `interface_panel.cc:1054` (UI_UNIT_X * 1.0)
|
|
23
|
-
* sub-panel indent 14 `interface_panel.cc:1058` (+0.7 UI_UNIT_X)
|
|
24
|
-
* panel padding 8 `interface_style.cc:111` (`panelspace`)
|
|
25
|
-
* panel radius 4 `interface_panel.cc:1103` (panel_roundness 0.4 x unit x 0.5)
|
|
26
|
-
* widget radius 0.2 x h `interface_widgets.cc` widget_radius_from_rcti,
|
|
27
|
-
* `userdef_default_theme.c:100` wcol_num.roundness
|
|
28
|
-
* text margin 8 `interface_intern.hh:1503` (UI_TEXT_MARGIN_X 0.4 x unit)
|
|
29
|
-
* font 11 `UI_interface_c.hh:425` (UI_DEFAULT_TEXT_POINTS)
|
|
30
|
-
* read-only alpha 0.5 `interface_widgets.cc` widget_alpha_factor /
|
|
31
|
-
* widget_color_disabled — EVERY widget colour at
|
|
32
|
-
* half alpha, which is why a read-only row here is
|
|
33
|
-
* DIMMED rather than a disabled control.
|
|
34
|
-
*
|
|
35
|
-
* Colours, from `release/datafiles/userdef/userdef_default_theme.c`:
|
|
36
|
-
* panel header/back #3d3d3d :281,:282 sub-panel back #0000001f :283
|
|
37
|
-
* panel outline #ffffff11 :284 panel title #e6e6e6 :285
|
|
38
|
-
* widget inner #545454 :95 widget outline #3d3d3d :93
|
|
39
|
-
* widget text #e6e6e6 :98 number slider #4772b3 :97
|
|
40
|
-
* checkbox mark #ffffff :77 (wcol_option.item)
|
|
41
|
-
*
|
|
42
|
-
* The values are literal rather than `var(--volter-…)` because the palette is
|
|
43
|
-
* the FRAME's grading (U8/U9) and eyedropped: `blender.palette.json` carries
|
|
44
|
-
* `surface.panel` #2f2f2f where the source says `.space_properties.back` is
|
|
45
|
-
* #303030, and `content.primary` #e5e5e5 against `.text` #e6e6e6. Where a
|
|
46
|
-
* token and the source agree, the token is used.
|
|
47
|
-
*/
|
|
1
|
+
|
|
48
2
|
|
|
49
3
|
import type {
|
|
50
4
|
BlenderRnaCollectionValue,
|
|
@@ -54,12 +8,15 @@ import type {
|
|
|
54
8
|
BlenderRnaView,
|
|
55
9
|
} from '@volter/blender-engine/browser/rna';
|
|
56
10
|
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
11
|
+
import { Progress } from '@volter/sdk/progress';
|
|
57
12
|
import { faLock, faLockOpen } from '@fortawesome/free-solid-svg-icons';
|
|
58
|
-
import { ColorPicker, EditorIcon, hexToRgb, parseAlpha, toHex } from '@volter/sdk/widgets';
|
|
13
|
+
import { ColorPicker, EditorIcon, hexToRgb, parseAlpha, StateSurface, toHex } from '@volter/sdk/widgets';
|
|
59
14
|
import {
|
|
60
15
|
type BlenderSubject,
|
|
61
16
|
blenderPropertiesState,
|
|
62
17
|
blenderPropertiesVersion,
|
|
18
|
+
blenderRnaUnavailable,
|
|
19
|
+
subscribeBlenderRnaAvailability,
|
|
63
20
|
blenderRnaViewFor,
|
|
64
21
|
showBlenderSubject,
|
|
65
22
|
subscribeBlenderProperties,
|
|
@@ -84,9 +41,6 @@ const SUB_PANEL_BACK = 'rgba(0,0,0,0.12)';
|
|
|
84
41
|
const WIDGET_INNER = '#545454';
|
|
85
42
|
const WIDGET_OUTLINE = '#3d3d3d';
|
|
86
43
|
const WIDGET_ITEM = '#4772b3';
|
|
87
|
-
/** `wcol_state.error` (`userdef_default_theme.c:234`) — the ink Blender puts
|
|
88
|
-
* on a widget whose state is an error. The door's refusals are drawn in it. */
|
|
89
|
-
const ERROR_INK = '#991616';
|
|
90
44
|
/** The collapse triangle's optical size and the gap after it. Blender draws
|
|
91
45
|
* the triangle as an icon inside `panel_label_offset`'s UI_UNIT_X lead-in
|
|
92
46
|
* (`interface_panel.cc:1049-1060`) rather than as text; 8 is that mark's
|
|
@@ -98,25 +52,7 @@ const TRIANGLE_GAP = 6;
|
|
|
98
52
|
* is 0.2 for every widget this view draws. */
|
|
99
53
|
const widgetRadius = (height: number) => Math.round(height * 0.2);
|
|
100
54
|
|
|
101
|
-
/**
|
|
102
|
-
* WHAT BLENDER DRAWS NOWHERE, WE DRAW NOWHERE — RNA's own `PROP_HIDDEN`
|
|
103
|
-
* (`BlenderRnaRow.hidden`, `rna_rna.cc:795-799`). The door reports the whole
|
|
104
|
-
* surface and this is the one place it is narrowed, so "hidden" stays
|
|
105
|
-
* Blender's answer rather than a list of ours.
|
|
106
|
-
*
|
|
107
|
-
* AND THE SECOND RULE, ruled on after I2 measured it: UNDRAWN STATE
|
|
108
|
-
* (`BlenderRnaRow.undrawn`). The `PROP_HIDDEN` flag above does NOT cover a
|
|
109
|
-
* modifier's panel-open booleans (`open_advanced_panel` and its siblings) —
|
|
110
|
-
* there is not one `PROP_HIDDEN` in the whole of `rna_modifier.cc`, because
|
|
111
|
-
* `rna_def_modifier_panel_open_prop` (`rna_modifier.cc:2694-2705`) sets
|
|
112
|
-
* `PROP_NO_DEG_UPDATE` and an sdna bit and no flag. What RNA does say is that
|
|
113
|
-
* they carry no `RNA_def_property_ui_text` at all, so
|
|
114
|
-
* `rna_define.cc:1311-1312`'s definition-time defaults still stand: `name`
|
|
115
|
-
* equal to the identifier, `description` empty. A property Blender never gave
|
|
116
|
-
* UI text to is a property Blender never meant to draw, and the generic view
|
|
117
|
-
* omits it here for exactly the same reason it omits a hidden one. The
|
|
118
|
-
* curated lists need no change: `bl_ui` names none of them.
|
|
119
|
-
*/
|
|
55
|
+
/** Omit RNA rows marked hidden or undrawn. Undrawn properties have no UI text; curated lists do not name them either. */
|
|
120
56
|
const shown = (rows: readonly BlenderRnaRow[]): readonly BlenderRnaRow[] =>
|
|
121
57
|
rows.filter((row) => row.hidden !== true && row.undrawn !== true);
|
|
122
58
|
|
|
@@ -130,6 +66,14 @@ export function useBlenderProperties() {
|
|
|
130
66
|
|
|
131
67
|
// ---- Blender's panel -------------------------------------------------------
|
|
132
68
|
|
|
69
|
+
/** A datablock still being read: the Properties view's wait, or, when Blender holds no model to
|
|
70
|
+
* read it from, that failure said in its place. */
|
|
71
|
+
function Reading({ path }: { readonly path: string }) {
|
|
72
|
+
const unavailable = useSyncExternalStore(subscribeBlenderRnaAvailability, () => blenderRnaUnavailable(path), () => null);
|
|
73
|
+
if (unavailable !== null) return <StateSurface inline tone="error" title={unavailable} />;
|
|
74
|
+
return <Progress view="properties" title={"Reading " + path} />;
|
|
75
|
+
}
|
|
76
|
+
|
|
133
77
|
function Panel({
|
|
134
78
|
title,
|
|
135
79
|
sub = false,
|
|
@@ -235,12 +179,7 @@ function Row({
|
|
|
235
179
|
}) {
|
|
236
180
|
return (
|
|
237
181
|
<div
|
|
238
|
-
//
|
|
239
|
-
// view (`editor-document-probe.ts` scope `'rail'`), and before these
|
|
240
|
-
// three attributes a reading of it could recover a label only as the
|
|
241
|
-
// row's text and could not say which RNA property that label belonged
|
|
242
|
-
// to or whether the field was writable. They change nothing about what
|
|
243
|
-
// is drawn.
|
|
182
|
+
// Publish the RNA property identity and writability for the rail’s document probe.
|
|
244
183
|
data-rna-field={identifier ?? ''}
|
|
245
184
|
data-field-label={label}
|
|
246
185
|
data-field-readonly={readonly ? 'true' : undefined}
|
|
@@ -446,7 +385,7 @@ function colourBytes(values: readonly number[], gamma: boolean): [number, number
|
|
|
446
385
|
return [byte(0), byte(1), byte(2)];
|
|
447
386
|
}
|
|
448
387
|
|
|
449
|
-
function colourOf(values: readonly number[], gamma
|
|
388
|
+
function colourOf(values: readonly number[], gamma: boolean): string {
|
|
450
389
|
const [r, g, b] = colourBytes(values, gamma);
|
|
451
390
|
return `rgb(${r}, ${g}, ${b})`;
|
|
452
391
|
}
|
|
@@ -523,11 +462,10 @@ function StringWidget({ row, write }: WidgetProps) {
|
|
|
523
462
|
);
|
|
524
463
|
}
|
|
525
464
|
|
|
526
|
-
/** A COLOR subtype is a swatch, not three numbers — `PropertyRNA.subtype` is
|
|
527
|
-
* exactly what says so, and it is why the door carries `subtype` at all. */
|
|
528
465
|
/** BLENDER'S COLOUR FIELD: the whole-width swatch, and a click opens a picker under it
|
|
529
466
|
* (`ui_block_colorpicker`); the colour is written when the pick ends, one step, as Blender's
|
|
530
|
-
* popup pushes one undo when it closes.
|
|
467
|
+
* popup pushes one undo when it closes. `PropertyRNA.subtype` selects this swatch
|
|
468
|
+
* presentation for COLOR values instead of separate numeric fields. */
|
|
531
469
|
function ColourWidget({ row, write }: WidgetProps) {
|
|
532
470
|
const values = (row.value as readonly number[] | null) ?? [];
|
|
533
471
|
const gamma = row.subtype === 'COLOR_GAMMA';
|
|
@@ -566,23 +504,7 @@ function ColourWidget({ row, write }: WidgetProps) {
|
|
|
566
504
|
);
|
|
567
505
|
}
|
|
568
506
|
|
|
569
|
-
/**
|
|
570
|
-
* `RNA_property_array_item_char` (X/Y/Z/W) and numbered past four.
|
|
571
|
-
*
|
|
572
|
-
* A BOOLEAN ARRAY IS AN ARRAY, and that is why this function dispatches on
|
|
573
|
-
* the element type rather than being numeric-only. `Object.lock_location` is
|
|
574
|
-
* three booleans, and Blender draws all three: `properties_object.py:49`
|
|
575
|
-
* (`OBJECT_PT_transform`) is `row.prop(ob, "lock_location", text="",
|
|
576
|
-
* emboss=False, icon='DECORATE_UNLOCKED')` — a whole-array `prop()` beside
|
|
577
|
-
* the location vector, which Blender's layout engine expands into one lock
|
|
578
|
-
* toggle per component, stacked alongside the three number fields (the same
|
|
579
|
-
* shape at `:59`/`:74` for `lock_rotation` and `:83` for `lock_scale`).
|
|
580
|
-
* Measured 2026-09-21 in the rail before this: `lock_location` fell to the
|
|
581
|
-
* single-checkbox arm of {@link widgetFor}, so ONE unchecked box stood for
|
|
582
|
-
* three values — and its write was `setattr(ob, "lock_location", true)`, a
|
|
583
|
-
* scalar into a three-element array, which is a refusal rather than the lock
|
|
584
|
-
* the person asked for. Each toggle here writes its own index (the door's
|
|
585
|
-
* `index` argument, `session.py::rna_set`'s `getattr(target, id)[index]`). */
|
|
507
|
+
/** Draw array elements as separate rows using RNA component labels. Boolean arrays write an individual index through rna_set, just as numeric arrays do. */
|
|
586
508
|
function ArrayRows({ row, path, label, title, write, locks }: WidgetProps & {
|
|
587
509
|
label: string; title: string;
|
|
588
510
|
locks?: readonly { row: BlenderRnaRow; index?: number }[] | undefined;
|
|
@@ -793,27 +715,7 @@ function CollectionRow({
|
|
|
793
715
|
|
|
794
716
|
// ---- the curated body ------------------------------------------------------
|
|
795
717
|
|
|
796
|
-
|
|
797
|
-
* ONE OF BLENDER'S OWN PANELS, as data.
|
|
798
|
-
*
|
|
799
|
-
* A tab module declares a list of these and the view draws them ABOVE the
|
|
800
|
-
* generic RNA view, which stays beneath under "All properties". The list is a
|
|
801
|
-
* transcription of the matching `scripts/startup/bl_ui/properties_*.py`:
|
|
802
|
-
* `bl_label` is the title, each `layout.prop(ob, "x")` is an entry of
|
|
803
|
-
* {@link properties} IN BLENDER'S ORDER, and a `bl_parent_id` panel is a
|
|
804
|
-
* {@link sub}. Blender's UI layer is never run, ported or recorded
|
|
805
|
-
* (ARCHITECTURE-CORE §Blender north star) — the Python file is read as the
|
|
806
|
-
* SPECIFICATION of which properties this tab shows and in what order, and
|
|
807
|
-
* what draws them is this file's own widgets over the RNA door's rows.
|
|
808
|
-
*
|
|
809
|
-
* WHAT IS DELIBERATELY NOT TRANSCRIBED: the `poll`/`if` conditions Blender's
|
|
810
|
-
* draw functions carry. A `properties_object.py` panel hides
|
|
811
|
-
* `show_all_edges` for a non-mesh by branching in Python; here the property is
|
|
812
|
-
* simply NOT ON the datablock RNA answered with, so it does not render and
|
|
813
|
-
* the panel header says how many of Blender's it found. That is the same
|
|
814
|
-
* outcome by a mechanism that cannot go stale — a transcribed condition is a
|
|
815
|
-
* copy of Blender's logic, which is the one thing the ruling forbids.
|
|
816
|
-
*/
|
|
718
|
+
|
|
817
719
|
/**
|
|
818
720
|
* ONE ENTRY OF A PANEL'S PROPERTY LIST.
|
|
819
721
|
*
|
|
@@ -924,19 +826,7 @@ export interface BlenderCuratedPanel {
|
|
|
924
826
|
readonly active?: keyof BlenderRnaContext;
|
|
925
827
|
}
|
|
926
828
|
|
|
927
|
-
/**
|
|
928
|
-
* WHICH MEMBER OF THIS COLLECTION THE CONTEXT CALLS ACTIVE — by address when
|
|
929
|
-
* RNA gives the member one, and by INDEX when it does not.
|
|
930
|
-
*
|
|
931
|
-
* The index half is not a convenience, it is the only answer for two of the
|
|
932
|
-
* three lists that need one, and the live walk is what said so (2026-09-19):
|
|
933
|
-
* a `VertexGroup` has no `path_from_id()` at all — I1 recorded that — so
|
|
934
|
-
* `activeVertexGroup` carries only its index, and `activeMaterial` carries the
|
|
935
|
-
* MATERIAL's address while the list being drawn is of material SLOTS, so its
|
|
936
|
-
* path never matches a row. Both marked nothing. `rna_context` reports
|
|
937
|
-
* `index` for exactly those two (`session.py::rna_context`), which is what
|
|
938
|
-
* Blender's own `template_list` highlights by.
|
|
939
|
-
*/
|
|
829
|
+
/** Resolve active collection members by RNA address or index. Vertex groups have no path_from_id and material context names a material rather than its slot, so both use the context’s index. */
|
|
940
830
|
function activeMember(panel: BlenderCuratedPanel): { path: string | null; index: number | null } {
|
|
941
831
|
if (panel.active === undefined) return { path: null, index: null };
|
|
942
832
|
const handle = blenderPropertiesState().context?.[panel.active];
|
|
@@ -963,21 +853,19 @@ function CuratedCollectionPanel({
|
|
|
963
853
|
if (view === undefined)
|
|
964
854
|
return (
|
|
965
855
|
<Panel title={panel.title} closed={panel.closed === true}>
|
|
966
|
-
<
|
|
856
|
+
<Reading path={path} />
|
|
967
857
|
</Panel>
|
|
968
858
|
);
|
|
969
859
|
if (view.kind !== 'collection')
|
|
970
860
|
return (
|
|
971
861
|
<Panel title={panel.title} closed={panel.closed === true} note={path}>
|
|
972
|
-
<
|
|
973
|
-
{path} is not a collection — this panel is declared `collection: true`.
|
|
974
|
-
</div>
|
|
862
|
+
<StateSurface inline tone="error" title={<>{path} is not a collection — this panel is declared <code>collection: true</code>.</>} />
|
|
975
863
|
</Panel>
|
|
976
864
|
);
|
|
977
865
|
return (
|
|
978
866
|
<Panel title={panel.title} closed={panel.closed === true} note={path} count={view.count}>
|
|
979
867
|
{view.items.length === 0 ? (
|
|
980
|
-
<
|
|
868
|
+
<StateSurface inline title="None" />
|
|
981
869
|
) : null}
|
|
982
870
|
{view.items.map((item, index) => (
|
|
983
871
|
<Panel
|
|
@@ -988,9 +876,7 @@ function CuratedCollectionPanel({
|
|
|
988
876
|
sub
|
|
989
877
|
>
|
|
990
878
|
{item.path === null ? (
|
|
991
|
-
<
|
|
992
|
-
{item.type} — RNA gives this member no address.
|
|
993
|
-
</div>
|
|
879
|
+
<StateSurface inline title={<>{item.type} — RNA gives this member no address.</>} />
|
|
994
880
|
) : (
|
|
995
881
|
<CuratedRows
|
|
996
882
|
path={item.path}
|
|
@@ -1010,20 +896,18 @@ function CuratedCollectionPanel({
|
|
|
1010
896
|
function CuratedRows({
|
|
1011
897
|
path,
|
|
1012
898
|
properties,
|
|
1013
|
-
andDeclared
|
|
899
|
+
andDeclared,
|
|
1014
900
|
onOpen,
|
|
1015
901
|
}: {
|
|
1016
902
|
readonly path: string;
|
|
1017
903
|
readonly properties: readonly BlenderCuratedProperty[];
|
|
1018
|
-
readonly andDeclared
|
|
904
|
+
readonly andDeclared: boolean;
|
|
1019
905
|
readonly onOpen: (path: string) => void;
|
|
1020
906
|
}) {
|
|
1021
907
|
useBlenderProperties();
|
|
1022
908
|
const view = blenderRnaViewFor(path);
|
|
1023
909
|
if (view === undefined)
|
|
1024
|
-
return
|
|
1025
|
-
<div style={{ color: INK, opacity: READONLY_ALPHA, fontSize: FONT }}>Reading {path}…</div>
|
|
1026
|
-
);
|
|
910
|
+
return <Reading path={path} />;
|
|
1027
911
|
const rows = view.kind === 'struct' ? view.groups.flatMap((group) => group.rows) : [];
|
|
1028
912
|
const byIdentifier = new Map(rows.map((row) => [row.identifier, row]));
|
|
1029
913
|
// A collection member is addressed by its own path, so a qualified entry is
|
|
@@ -1142,9 +1026,7 @@ function CuratedPanel({
|
|
|
1142
1026
|
if (view === undefined)
|
|
1143
1027
|
return (
|
|
1144
1028
|
<Panel title={panel.title} closed={panel.closed === true}>
|
|
1145
|
-
<
|
|
1146
|
-
Reading {entry.path}…
|
|
1147
|
-
</div>
|
|
1029
|
+
<Reading path={entry.path} />
|
|
1148
1030
|
</Panel>
|
|
1149
1031
|
);
|
|
1150
1032
|
if (found.length === 0 && sub.length === 0) return null;
|
|
@@ -1170,12 +1052,7 @@ function CuratedPanel({
|
|
|
1170
1052
|
<PropertyRow key={item.identifier} row={item.row} path={item.path} onOpen={onOpen}
|
|
1171
1053
|
label={item.label} locks={item.locks} />
|
|
1172
1054
|
))}
|
|
1173
|
-
{/*
|
|
1174
|
-
same `bl_label` under different engines and expects the poll to pick
|
|
1175
|
-
one — Render's "Sampling" (EEVEE `:675`, Workbench `:1004`) and
|
|
1176
|
-
"Film" (`:757`, `:1025`), View Layer's "Data" (`:111`, `:137`). A
|
|
1177
|
-
title key made those React duplicates, which the live walk found by
|
|
1178
|
-
name ("Encountered two children with the same key"). */}
|
|
1055
|
+
{/* Key by position: engine-specific panels can share a title. */}
|
|
1179
1056
|
{sub.map((child, index) => (
|
|
1180
1057
|
<CuratedPanel key={`${index}:${child.title}`} panel={child} paths={paths} onOpen={onOpen} />
|
|
1181
1058
|
))}
|
|
@@ -1195,25 +1072,17 @@ function DatablockView({
|
|
|
1195
1072
|
useBlenderProperties();
|
|
1196
1073
|
const view: BlenderRnaView | undefined = blenderRnaViewFor(path);
|
|
1197
1074
|
if (view === undefined)
|
|
1198
|
-
return
|
|
1199
|
-
<div style={{ color: INK, opacity: READONLY_ALPHA, fontSize: FONT, padding: PANEL_PAD }}>
|
|
1200
|
-
Reading {path}…
|
|
1201
|
-
</div>
|
|
1202
|
-
);
|
|
1075
|
+
return <Reading path={path} />;
|
|
1203
1076
|
if (view.kind === 'collection')
|
|
1204
1077
|
return (
|
|
1205
1078
|
<>
|
|
1206
1079
|
{view.items.length === 0 ? (
|
|
1207
|
-
<
|
|
1208
|
-
{view.count === 0 ? 'None' : `${view.count} items`}
|
|
1209
|
-
</div>
|
|
1080
|
+
<StateSurface inline title={view.count === 0 ? 'None' : <>{view.count} items</>} />
|
|
1210
1081
|
) : null}
|
|
1211
1082
|
{view.items.map((item) => (
|
|
1212
1083
|
<Panel key={item.path ?? item.name ?? item.type} title={item.name ?? item.type} sub>
|
|
1213
1084
|
{item.path === null ? (
|
|
1214
|
-
<
|
|
1215
|
-
{item.type} — RNA gives this member no address.
|
|
1216
|
-
</div>
|
|
1085
|
+
<StateSurface inline title={<>{item.type} — RNA gives this member no address.</>} />
|
|
1217
1086
|
) : (
|
|
1218
1087
|
<DatablockView path={item.path} onOpen={onOpen} />
|
|
1219
1088
|
)}
|
|
@@ -1240,27 +1109,18 @@ function DatablockView({
|
|
|
1240
1109
|
|
|
1241
1110
|
// ---- the section body ------------------------------------------------------
|
|
1242
1111
|
|
|
1243
|
-
/**
|
|
1244
|
-
* ONE PROPERTIES TAB'S BODY. `tabId` names the tab this section IS; the
|
|
1245
|
-
* context door says which datablocks that tab shows and in what order
|
|
1246
|
-
* (`session.py::rna_context`, mirrored from `buttons_context.cc`).
|
|
1247
|
-
*
|
|
1248
|
-
* A pointer row DRILLS: clicking it replaces the shown datablock with the
|
|
1249
|
-
* target, and the breadcrumb walks back. That is the "pointer → the target's
|
|
1250
|
-
* name as a link that changes the active datablock" half of I1, and it is what
|
|
1251
|
-
* makes `object.data.materials[0].node_tree` reachable with no per-tab code.
|
|
1252
|
-
*/
|
|
1112
|
+
/** One Properties tab body over the datablocks and ordering returned by rna_context. Pointer rows drill into a target datablock and breadcrumbs return to the parent. */
|
|
1253
1113
|
export function BlenderPropertiesSection({
|
|
1254
1114
|
tabId,
|
|
1255
1115
|
subject,
|
|
1256
|
-
curated
|
|
1116
|
+
curated,
|
|
1257
1117
|
}: {
|
|
1258
1118
|
readonly tabId: string;
|
|
1259
1119
|
readonly subject: BlenderSubject;
|
|
1260
1120
|
/** Blender's own panels for this tab, in Blender's order
|
|
1261
1121
|
* ({@link BlenderCuratedPanel}). Empty — the tab has not been curated yet —
|
|
1262
1122
|
* and the generic view is the whole body, uncollapsed. */
|
|
1263
|
-
readonly curated
|
|
1123
|
+
readonly curated: readonly BlenderCuratedPanel[];
|
|
1264
1124
|
}) {
|
|
1265
1125
|
useBlenderProperties();
|
|
1266
1126
|
useEffect(() => showBlenderSubject(subject), [subject]);
|
|
@@ -1276,13 +1136,11 @@ export function BlenderPropertiesSection({
|
|
|
1276
1136
|
|
|
1277
1137
|
if (state.error !== null)
|
|
1278
1138
|
return (
|
|
1279
|
-
<
|
|
1139
|
+
<StateSurface inline tone="error" title="Blender did not answer for these properties" detail={state.error} />
|
|
1280
1140
|
);
|
|
1281
1141
|
if (tab === null)
|
|
1282
|
-
return (
|
|
1283
|
-
<
|
|
1284
|
-
{state.loading ? 'Reading the engine…' : 'Blender shows no properties here.'}
|
|
1285
|
-
</div>
|
|
1142
|
+
return state.loading ? <Progress view="properties" title="Reading the engine" /> : (
|
|
1143
|
+
<StateSurface inline title="Blender shows no properties here." />
|
|
1286
1144
|
);
|
|
1287
1145
|
|
|
1288
1146
|
const open = (path: string) => setDrill((held) => [...held, path]);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHAT TO DO ABOUT WHAT PLAYS ONLY IN BLENDER, said after a warning that names it, by the Timeline
|
|
3
|
+
* (`blender-runtime-skin.ts`). Pure: it reads no Blender host.
|
|
4
|
+
*/
|
|
5
|
+
export function remedy(unsupported: readonly string[]): string {
|
|
6
|
+
const out: string[] = [];
|
|
7
|
+
if (unsupported.some((thing) => thing.includes(' constraint ') || thing.includes('drivers')))
|
|
8
|
+
out.push('Bake a constraint or driver into the action (Pose ▸ Animation ▸ Bake Action, Visual Keying), or aim with a Damped Track.');
|
|
9
|
+
if (unsupported.some((thing) => thing.includes('curves on ')))
|
|
10
|
+
out.push("Key the motion on a bone (the root bone) instead of the armature object, or move the object from the game.");
|
|
11
|
+
if (unsupported.some((thing) => thing.includes('interpolation') || thing.includes(' modifier')))
|
|
12
|
+
out.push('Use Bezier, Linear or Constant keys and the Cycles modifier only, or bake the curves (Key ▸ Bake Keyframes).');
|
|
13
|
+
return out.length ? ` ${out.join(' ')}` : '';
|
|
14
|
+
}
|