@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,350 +0,0 @@
1
- /**
2
- * THE SOURCE of `blender.palette.json` — Blender's own default theme, read
3
- * from Blender's own theme TABLE rather than eyedropped from a screenshot
4
- * (WORK.md §Blender in the tab is Blender, "Inspection parity", I2 decision 7;
5
- * ARCHITECTURE-CORE §Blender north star, "The reference is Blender's SOURCE as
6
- * well as its frames").
7
- *
8
- * Run `node packages/editor-blender/contributions/blender-palette.source.mjs` to
9
- * rewrite the JSON; `--check` reports what would change and exits non-zero if
10
- * anything would. It reads
11
- * `release/datafiles/userdef/userdef_default_theme.c` from a Blender checkout
12
- * at the engine's pin (`$BLENDER_SRC`, default `~/volter/blender-src`; the
13
- * checkout recipe is in WORK.md §Blender in the tab is Blender, "Inspection
14
- * parity"). That file is itself generated — `tools/utils/blender_theme_as_c.py`
15
- * writes it from the shipped theme — so it is the closest thing Blender has to
16
- * a machine-readable palette.
17
- *
18
- * ## Why this exists
19
- *
20
- * Every value in `blender.palette.json` used to be eyedropped off a reference
21
- * frame, and I1 measured the drift that produces: `surface.panel` carried
22
- * `#2f2f2f` where `.space_properties.back` is `#303030`, and `content.primary`
23
- * `#e5e5e5` against `.text`'s `#e6e6e6`. One level each — invisible, and
24
- * exactly the kind of error an eyedropper makes (a screenshot is a COMPOSITE:
25
- * a panel fill sits under a hairline outline at 6.7% white, and a PNG's own
26
- * colour management can move a level besides). The source has no such
27
- * ambiguity.
28
- *
29
- * ## What is NOT derived, and why each one
30
- *
31
- * A key is derived only when its meaning is EXACTLY one theme member. The
32
- * rest are stated as literals with their reason, because a false citation is
33
- * worse than an honest measurement:
34
- *
35
- * - a COMPOSITE Blender draws by blending two members (the viewport's axis
36
- * lines) — the source value is an input to what you see, not what you see;
37
- * - a member Blender declares with an ALPHA it composites over a surface the
38
- * palette does not carry (a region header, a popover back);
39
- * - a semantic colour Blender's theme has no member for at all (our
40
- * danger/warning/success set, which is the editor's own vocabulary).
41
- *
42
- * Nothing here invents a colour: every literal below names where it came from.
43
- */
44
- import { readFileSync, writeFileSync } from 'node:fs';
45
- import { homedir } from 'node:os';
46
-
47
- // ------------------------------------------------- Blender's theme table
48
-
49
- const CHECKOUT = process.env['BLENDER_SRC'] ?? `${homedir()}/volter/blender-src`;
50
- const THEME = 'release/datafiles/userdef/userdef_default_theme.c';
51
-
52
- /**
53
- * `userdef_default_theme.c` as `{ 'tui.panel_header': { hex, alpha, line } }`.
54
- *
55
- * The file is machine-generated and utterly regular — one `.member = {` per
56
- * nesting level, one `.member = RGBA(0x…)` per colour — so this is a brace
57
- * walk rather than a C parser. A member the walk does not recognise is
58
- * skipped; a member the TABLE below names and the walk did not find is a hard
59
- * error, which is what keeps this honest when Blender renames one.
60
- */
61
- function readTheme() {
62
- let text;
63
- try {
64
- text = readFileSync(`${CHECKOUT}/${THEME}`, 'utf8');
65
- } catch {
66
- throw new Error(
67
- `${THEME} is not in the Blender checkout at ${CHECKOUT}. Set BLENDER_SRC, or add the ` +
68
- 'directory to the sparse checkout (`git sparse-checkout add release/datafiles/userdef`) ' +
69
- 'and re-run the LFS-off checkout line in WORK.md §Blender in the tab is Blender.',
70
- );
71
- }
72
- const members = {};
73
- const stack = [];
74
- text.split('\n').forEach((line, index) => {
75
- const open = /^\s*\.(\w+)\s*=\s*\{\s*$/.exec(line);
76
- if (open !== null) {
77
- stack.push(open[1]);
78
- return;
79
- }
80
- if (/^\s*\},?\s*$/.test(line)) {
81
- stack.pop();
82
- return;
83
- }
84
- const value = /^\s*\.(\w+)\s*=\s*RGBA?\(0x([0-9a-fA-F]+)\)/.exec(line);
85
- if (value === null) return;
86
- const digits = value[2].padStart(8, '0');
87
- members[[...stack, value[1]].join('.')] = {
88
- hex: `#${digits.slice(0, 6).toLowerCase()}`,
89
- alpha: Number.parseInt(digits.slice(6, 8), 16) / 255,
90
- line: index + 1,
91
- };
92
- });
93
- return members;
94
- }
95
-
96
- // ------------------------------------------------------------ the mapping
97
-
98
- /** A theme member, taken OPAQUE: Blender's `back` members carry an alpha byte
99
- * that the region fill does not use (`.space_properties.back` is `0x30303000`
100
- * and the properties editor is plainly not transparent), and a palette
101
- * surface is one colour. */
102
- const rgb = (member) => ({ member });
103
- /** A theme member taken WITH its alpha — for the members whose alpha is the
104
- * point (`.tui.widget_emboss`, `.tui.panel_outline`). */
105
- const rgba = (member) => ({ member, alpha: true });
106
- /** A value the theme has no single member for. `why` is mandatory. */
107
- const held = (value, why) => ({ value, why });
108
-
109
- /**
110
- * PALETTE KEY → THEME MEMBER. Dotted on both sides; the left is a path into
111
- * `theme.color`, the right a path into `U_theme_default`.
112
- */
113
- const MAPPING = {
114
- 'surface.shell': rgb('tui.editor_border'),
115
- 'surface.panel': rgb('space_properties.back'),
116
- 'surface.chrome': held(
117
- '#1c1c1c',
118
- 'a region HEADER is `.header` at alpha 0xb3 (`space_properties.header`) composited over the ' +
119
- 'window, and this palette carries ONE opaque colour for every header in the editor. ' +
120
- 'Measured on the reference frames.',
121
- ),
122
- 'surface.raised': rgb('tui.wcol_regular.inner'),
123
- 'surface.inset': rgb('tui.wcol_text.inner'),
124
- 'surface.overlay': held(
125
- 'rgba(48,48,48,0.94)',
126
- 'a popover in the source is `.tui.wcol_menu_back.inner` #181818 opaque; ours floats over the ' +
127
- 'editor and the frames measure it as the properties back at 94%. Our own compositing, not ' +
128
- "Blender's.",
129
- ),
130
-
131
- 'boundary.default': rgb('tui.wcol_regular.outline'),
132
- 'boundary.strong': rgb('tui.wcol_regular.outline'),
133
- 'boundary.area': rgb('tui.editor_border'),
134
- 'boundary.indent': held(
135
- '#656565',
136
- "the Outliner's indent guides, which `outliner_draw.cc` draws as a shade of the row rather " +
137
- 'than from a member of its own. Measured on `outliner.png`.',
138
- ),
139
- 'boundary.divider': held(
140
- '#2e2e2e',
141
- 'the rule inside one region, which Blender draws as a shade of the region back rather than ' +
142
- 'from a member of its own. Measured on the reference frames.',
143
- ),
144
-
145
- 'content.primary': rgb('tui.wcol_regular.text'),
146
- 'content.muted': rgb('space_outliner.text'),
147
- 'content.dim': held(
148
- '#969696',
149
- 'Blender dims text by drawing the same `.text` at reduced alpha (`widget_alpha_factor`), not ' +
150
- 'from a second member. #969696 is `.tui.wcol_regular.text` #e6e6e6 at 0.5 over ' +
151
- '`.space_properties.back` #303030.',
152
- ),
153
- 'content.onAccent': rgb('tui.wcol_regular.text_sel'),
154
- 'content.menu': rgb('tui.wcol_menu_item.text'),
155
- 'content.status': rgb('space_statusbar.header_text'),
156
- 'content.placeholder': held(
157
- '#5e5e5e',
158
- "Blender's fields draw no placeholder text at all; this is the editor's own affordance, set " +
159
- 'between `.tui.wcol_text.inner` and `.text`.',
160
- ),
161
- 'content.active': rgb('space_view3d.active'),
162
- 'content.selected': rgb('space_outliner.selected_object'),
163
- 'content.onBright.primary': held(
164
- '#1d1d1d',
165
- '`.tui.wcol_regular.item` #1d1d1d — the ink Blender puts ON a filled widget — read opaque.',
166
- ),
167
- 'content.onBright.muted': held('rgba(29,29,29,0.82)', 'the same ink, our own two dim steps.'),
168
- 'content.onBright.dim': held('rgba(29,29,29,0.7)', 'the same ink, our own two dim steps.'),
169
-
170
- 'accent.default': rgb('tui.wcol_regular.inner_sel'),
171
- 'accent.muted': rgb('tui.wcol_regular.inner_sel'),
172
-
173
- 'widget.regular': rgb('tui.wcol_regular.inner'),
174
- 'widget.menu': rgb('tui.wcol_menu.inner'),
175
- 'widget.field': rgb('tui.wcol_text.inner'),
176
- 'widget.emboss': rgba('tui.widget_emboss'),
177
-
178
- 'viewport.background': rgb('space_view3d.back'),
179
- 'viewport.grid': rgb('space_view3d.grid_major'),
180
- 'viewport.axisX': held(
181
- '#cb293f',
182
- "the grid's axis line is a BLEND of `.space_view3d.grid_major` and `.tui.xaxis` #ff3352 " +
183
- '(the overlay engine blends them per line; `draw/engines/overlay` is outside the sparse ' +
184
- 'checkout). The source value is an input to what is drawn, not what is drawn — measured.',
185
- ),
186
- 'viewport.axisY': held(
187
- '#69aa15',
188
- 'the same blend over `.tui.yaxis` #8bdc00 — measured, see axisX.',
189
- ),
190
- // There is no `viewport.axisZ`: the grid plane draws two axis lines. The
191
- // gizmos carry all three (`gizmo.*`).
192
- 'viewport.selection': rgb('space_view3d.select'),
193
- 'viewport.active': rgb('space_view3d.active'),
194
- // The box-select rectangle is not themed in Blender at all: `wm_gesture_draw_rect`
195
- // (`source/blender/windowmanager/intern/wm_gesture.cc`) states its colours as literals, so
196
- // each is held with that citation. Its dash (8 framebuffer px at `udash_factor` 0.5) is the
197
- // style's `stage.marqueeDash`.
198
- 'viewport.marqueeLine': held(
199
- '#666666',
200
- "`wm_gesture_draw_rect`'s dashed outline, `immUniform4f(\"color\", 0.4f, 0.4f, 0.4f, 1.0f)` " +
201
- '— the first half of each dash period. A literal in `wm_gesture.cc`, not a theme member.',
202
- ),
203
- 'viewport.marqueeGap': held(
204
- '#ffffff',
205
- 'the same outline\'s `immUniform4f("color2", 1.0f, 1.0f, 1.0f, 1.0f)` — the second half of ' +
206
- 'each period (`colors_len` 2, so the gap is drawn rather than discarded).',
207
- ),
208
- 'viewport.marqueeFill': held(
209
- 'rgba(255,255,255,0.05)',
210
- "`wm_gesture_draw_rect`'s fill, `immUniformColor4f(1.0f, 1.0f, 1.0f, 0.05f)` under " +
211
- '`GPU_BLEND_ALPHA`. Not the accent: `.tui.wcol_regular.inner_sel` is opaque, and as the ' +
212
- 'fill it drew the band as a solid blue square.',
213
- ),
214
-
215
- // The transform gizmo draws the theme's axis colours as they are
216
- // (`transform_gizmo_3d.cc`, `gizmo_get_axis_color`), and so does the navigation gizmo, mixed
217
- // toward the viewport by each ball's depth as it draws (`view3d_gizmo_navigate_type.cc`; the
218
- // stage computes the mix, so the measured (245,54,81) of `modeling-object-none.png` is `.tui.xaxis`
219
- // at that ball's depth, not a colour of its own).
220
- 'gizmo.x': rgb('tui.xaxis'),
221
- 'gizmo.y': rgb('tui.yaxis'),
222
- 'gizmo.z': rgb('tui.zaxis'),
223
-
224
- 'region.outliner': rgb('space_outliner.back'),
225
- 'region.properties': rgb('space_properties.back'),
226
-
227
- // The icon groups, `interface_icons.cc:126-132`'s `DEF_ICON_<GROUP>` macros
228
- // through `interface/resources.cc:1059-1078` to these members. The two new
229
- // names are I2's: `scene` for the Render/Output/View Layer/Scene tabs and
230
- // `collection` for the Collection tab.
231
- 'category.object': rgb('tui.icon_object'),
232
- 'category.modifier': rgb('tui.icon_modifier'),
233
- 'category.material': rgb('tui.icon_shading'),
234
- 'category.data': rgb('tui.icon_object_data'),
235
- 'category.scene': rgb('tui.icon_scene'),
236
- 'category.collection': rgb('tui.icon_collection'),
237
- 'category.tool': held(
238
- '#cbcbcb',
239
- 'Blender declares no icon group for the Tool tab — `ICON_TOOL_SETTINGS` is a plain ' +
240
- '`DEF_ICON`, drawn in `.text`. Measured on the reference frames, and a level below ' +
241
- '`.tui.icon_scene` #cccccc, which is what it sits beside.',
242
- ),
243
- 'category.select': held(
244
- '#ffaf2a',
245
- "Blender's Select Box marquee, baked into the tool icon's geometry (`ops.generic.select_box`), " +
246
- 'not a theme member. Measured on `gizmo-select-box.png`: (255,175,42).',
247
- ),
248
- 'category.operator': held(
249
- '#95dab2',
250
- "Blender's edit-mode tool column tints its creating operators green; no theme member carries " +
251
- 'it. Measured on `modeling-edit-none.png`.',
252
- ),
253
-
254
- // The editor's OWN vocabulary. Blender's theme has no danger/warning/success
255
- // group: a Blender error is a report banner, not a themed colour, so these
256
- // are ours and are stated as ours.
257
- 'semantic.danger': held('#e0524a', "the editor's own; Blender's theme has no error colour."),
258
- 'semantic.dangerMuted': held('rgba(224,82,74,0.28)', 'the same, at our own two alphas.'),
259
- 'semantic.dangerFaint': held('rgba(224,82,74,0.12)', 'the same, at our own two alphas.'),
260
- 'semantic.warning': held('#e5b83c', "the editor's own."),
261
- 'semantic.warningMuted': held('rgba(229,184,60,0.28)', "the editor's own."),
262
- 'semantic.success': held('#6fbf5f', "the editor's own."),
263
- 'semantic.successMuted': held('rgba(111,191,95,0.28)', "the editor's own."),
264
- 'semantic.dynamic': held('#e5b83c', "the editor's own."),
265
- 'semantic.dynamicMuted': held('rgba(229,184,60,0.18)', "the editor's own."),
266
- 'semantic.instance': held(
267
- '#ffa028',
268
- "`.space_view3d.active` #ffa028 — the editor's instance badge reuses Blender's active ink.",
269
- ),
270
-
271
- 'neutralOverlay.hover': held(
272
- 'rgba(255,255,255,0.08)',
273
- "Blender lightens a widget by SHADE (`widget_state`'s `+15` on the inner colour), not by " +
274
- 'an overlay; ours is an overlay because our widgets are CSS. The alpha matches that shade.',
275
- ),
276
- 'neutralOverlay.active': held('rgba(255,255,255,0.14)', 'the same shade rule, one step up.'),
277
- scrim: held('rgba(0,0,0,0.6)', 'a modal scrim is ours — Blender dims nothing behind a popup.'),
278
- };
279
-
280
- /** Groups the emitted document keeps verbatim: not colours. */
281
- const TYPOGRAPHY = {
282
- sans: "'Inter', 'DejaVu Sans', system-ui, sans-serif",
283
- mono: "'DejaVu Sans Mono', Menlo, monospace",
284
- };
285
-
286
- // ------------------------------------------------------------------- emit
287
-
288
- function set(target, path, value) {
289
- const parts = path.split('.');
290
- let node = target;
291
- for (const part of parts.slice(0, -1)) node = node[part] ??= {};
292
- node[parts.at(-1)] = value;
293
- }
294
-
295
- function get(source, path) {
296
- return path.split('.').reduce((node, part) => node?.[part], source);
297
- }
298
-
299
- function cssOf(entry, withAlpha) {
300
- if (!withAlpha || entry.alpha === 1) return entry.hex;
301
- const [r, g, b] = [1, 3, 5].map((i) => Number.parseInt(entry.hex.slice(i, i + 2), 16));
302
- return `rgba(${r},${g},${b},${Math.round(entry.alpha * 1000) / 1000})`;
303
- }
304
-
305
- const theme = readTheme();
306
- const color = {};
307
- const citations = {};
308
- for (const [key, rule] of Object.entries(MAPPING)) {
309
- if (rule.member === undefined) {
310
- set(color, key, rule.value);
311
- citations[key] = `held — ${rule.why}`;
312
- continue;
313
- }
314
- const entry = theme[rule.member];
315
- if (entry === undefined)
316
- throw new Error(
317
- `${THEME} has no member \`${rule.member}\`, which \`${key}\` cites. Blender renamed or ` +
318
- 'removed it; re-read the theme and update the mapping rather than dropping the key.',
319
- );
320
- set(color, key, cssOf(entry, rule.alpha === true));
321
- citations[key] = `${rule.member} (${THEME}:${entry.line})`;
322
- }
323
-
324
- const target = new URL('./blender.palette.json', import.meta.url);
325
- const before = JSON.parse(readFileSync(target, 'utf8'));
326
- const document = {
327
- schemaVersion: 3,
328
- name: 'Blender',
329
- theme: { id: 'blender', color, typography: TYPOGRAPHY },
330
- };
331
-
332
- const changes = [];
333
- for (const key of Object.keys(MAPPING)) {
334
- const was = get(before.theme.color, key);
335
- const now = get(color, key);
336
- if (was !== now)
337
- changes.push(` ${key.padEnd(28)} ${String(was)} -> ${now} [${citations[key]}]`);
338
- }
339
- for (const line of changes) console.log(line);
340
- console.log(
341
- `blender.palette.json — ${Object.keys(MAPPING).length} keys, ` +
342
- `${Object.values(MAPPING).filter((rule) => rule.member !== undefined).length} derived from ` +
343
- `${THEME}, ${changes.length} changed`,
344
- );
345
-
346
- if (process.argv.includes('--check')) {
347
- if (changes.length > 0) process.exit(1);
348
- } else {
349
- writeFileSync(target, `${JSON.stringify(document, null, 2)}\n`);
350
- }