@volter/editor-blender 0.1.8 → 0.1.10

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 (39) hide show
  1. package/contributions/blender-header-menus.tsx +4 -9
  2. package/contributions/blender-icons.source.mjs +74 -6
  3. package/contributions/blender-outliner-authoring.ts +10 -21
  4. package/contributions/blender-palette.source.mjs +16 -5
  5. package/contributions/blender-properties-view.tsx +28 -9
  6. package/contributions/blender-runtime-skin.ts +6 -0
  7. package/contributions/blender-runtime.document.tsx +219 -23
  8. package/contributions/blender.command.ts +5 -1
  9. package/contributions/blender.environment.ts +32 -0
  10. package/contributions/blender.icons.json +22 -2
  11. package/contributions/blender.icons.traced.json +28 -0
  12. package/contributions/blender.keymap.ts +33 -0
  13. package/contributions/blender.palette.json +6 -0
  14. package/contributions/blender.status.tsx +2 -6
  15. package/contributions/blender.style.ts +30 -89
  16. package/contributions/model.layout.ts +7 -7
  17. package/contributions/models.finder.ts +16 -0
  18. package/contributions/sculpt.layout.ts +3 -0
  19. package/contributions/shading.layout.ts +3 -0
  20. package/contributions/studiolights/world/city.exr +0 -0
  21. package/contributions/studiolights/world/courtyard.exr +0 -0
  22. package/contributions/studiolights/world/forest.exr +0 -0
  23. package/contributions/studiolights/world/interior.exr +0 -0
  24. package/contributions/studiolights/world/license.txt +15 -0
  25. package/contributions/studiolights/world/night.exr +0 -0
  26. package/contributions/studiolights/world/studio.exr +0 -0
  27. package/contributions/studiolights/world/sunrise.exr +0 -0
  28. package/contributions/studiolights/world/sunset.exr +0 -0
  29. package/contributions/uv-editing.layout.ts +3 -0
  30. package/dist-node/serving.mjs +624 -0
  31. package/host/blender-runtime-host.ts +11 -1
  32. package/node/blender-mcp.ts +2 -2
  33. package/package.json +20 -10
  34. package/serving/blender-routes.ts +427 -0
  35. package/serving/blender-wasm-artifact.ts +449 -0
  36. package/serving/index.ts +10 -0
  37. package/src/layouts.tsx +4 -5
  38. package/src/presentation.ts +111 -0
  39. package/src/regions.ts +59 -0
@@ -175,97 +175,38 @@ export const style: StyleContribution = {
175
175
  // applied TWICE; see `theme.ts`'s `areaEmbossValue`.
176
176
  areaEmboss: 8.2,
177
177
  },
178
- viewport: {
179
- // BLENDER'S GIZMO IS A CONSTANT SCREEN SIZE (owner, 2026-09-21: "is
180
- // gizmo perhaps the wrong size?"). `U.gizmo_size` is 75
181
- // (`DNA_userdef_types.h:1095`; Preferences > Viewport > Gizmos > Size,
182
- // `rna_userdef.cc:5415-5419`, range 10..200), and it is px PER GIZMO
183
- // UNIT: `wm_gizmo.cc:450-474` sets `scale_final = scale_basis *
184
- // UI_SCALE_FAC * U.gizmo_size * ED_view3d_pixel_size_no_ui_scale(...)`,
185
- // whose last term is world units per DEVICE px against a
186
- // `UI_SCALE_FAC` of device px per UI px — so one gizmo unit is 75 CSS
187
- // px whatever the display scale (read back from the engine's own pin
188
- // on this box: `ui_scale=2.0 pixel_size=2.0 gizmo_size=75`).
189
- gizmoSize: 75,
190
- // BLENDER'S SHELF OPENS ON SELECT BOX, so a selected object carries no
191
- // transform gizmo until one of the four transform tools is armed
192
- // (`space_toolsystem_toolbar.py`: `_defs_view3d_generic.select_box` is
193
- // the first entry of the Object Mode `_tools_default` tuple).
194
- // Photographed at the engine's pin rather than remembered:
195
- // `gizmo-select-box.png` has Select Box lit in the shelf, the default
196
- // cube selected and outlined orange, and nothing at its origin but the
197
- // 3D cursor and the object dot; `gizmo-move.png` has Move lit in the
198
- // same frame. Ours opened on the COMBINED gizmo — Unity's shape, three
199
- // tools' handles at once, on something merely selected.
200
- shelfTool: 'select',
201
- // BLENDER'S WORLD IS Z-UP, and the stage presents it through the exact
202
- // signed permutation `(x, y, z) → (x, z, −y)` the presented root
203
- // carries (`blender-runtime-view.ts`'s constructor). So three's Y is
204
- // Blender's Z and three's Z is Blender's −Y, and the transform gizmo's
205
- // colours, the side each arm is drawn on, the plane squares and the
206
- // navigation gizmo's six labels all say so: the arm that points up is
207
- // Z and it is blue, Y is green and points away.
208
- upAxis: 'z',
209
- // BLENDER'S SELECT BOX IS A TOUCH TEST — its object-mode box select
210
- // reads the object-id buffer under the rectangle, so any drawn part of
211
- // an object inside it selects that object. The editor's own answer is
212
- // containment, for its own measured reason; see the field.
213
- boxSelect: 'touch',
214
- },
178
+ },
179
+ stage: {
180
+ // BLENDER'S GIZMO IS A CONSTANT SCREEN SIZE (owner, 2026-09-21: "is
181
+ // gizmo perhaps the wrong size?"). `U.gizmo_size` is 75
182
+ // (`DNA_userdef_types.h:1095`; Preferences > Viewport > Gizmos > Size,
183
+ // `rna_userdef.cc:5415-5419`, range 10..200), and it is px PER GIZMO
184
+ // UNIT: `wm_gizmo.cc:450-474` sets `scale_final = scale_basis *
185
+ // UI_SCALE_FAC * U.gizmo_size * ED_view3d_pixel_size_no_ui_scale(...)`,
186
+ // whose last term is world units per DEVICE px against a
187
+ // `UI_SCALE_FAC` of device px per UI px — so one gizmo unit is 75 CSS
188
+ // px whatever the display scale (read back from the engine's own pin
189
+ // on this box: `ui_scale=2.0 pixel_size=2.0 gizmo_size=75`).
190
+ gizmoSize: 75,
191
+ // A resting handle at 0.6 opacity, a highlighted one opaque in its own colour
192
+ // (`transform_gizmo_3d.cc`, `gizmo_get_axis_color`).
193
+ gizmoOpacity: 0.6,
194
+ // BLENDER'S OUTLINE IS A HARD LINE, and hidden parts are not drawn: four device pixels
195
+ // of orange around the cube in `modeling-object-selected.png` (at 2x), no halo.
196
+ outlineStyle: 'crisp',
197
+ outlineWidth: 4,
198
+ outlineHidden: false,
199
+ // BLENDER'S FLOOR LINES, in device pixels, measured across one scanline of
200
+ // `modeling-object-none.png` at device resolution: the 1 m line is 4 px at half rise and
201
+ // plateaus at 83, the 10 m line is 6 px and plateaus at 101 — so the major line is wider
202
+ // (2.25 against 1.5) and carried (102 − 63) / (84 − 63) past the minor's colour from the
203
+ // background they share. Stated here, the look's, since the editor's own floor is a
204
+ // single hairline level.
205
+ gridLineWidth: 1.5,
206
+ gridMajorWidth: 2.25,
207
+ gridMajorContrast: 39 / 21,
215
208
  },
216
209
  },
217
210
  icons,
218
- regions: {
219
- workspaceTabs: 'shown',
220
- // Blender has no document tab strip over its areas (the host's own note
221
- // in `workspace-presets.ts` says so; the bundle contradicted it).
222
- header: 'shown',
223
- shelf: 'shown',
224
- inspector: 'properties',
225
- // Measured on `modeling.png`: Blender's top-right corner carries two quiet
226
- // wells (Scene, ViewLayer) and nothing brighter than the 216 its labels
227
- // ink at. Ours drew a yellow FPS/ms readout, an orange sparkline and a lit
228
- // audio meter there — the brightest, highest-chroma thing in the frame and
229
- // the first place the eye lands. The numbers keep their door (the Profiler
230
- // utility, which the readout's own tooltip names).
231
- telemetry: 'hidden',
232
- // Measured on `outliner.png`: Scene Collection, Collection, Camera, Cube
233
- // and Light each carry their NAME and nothing after it — no type text
234
- // anywhere in the frame. The type is the GLYPH, which is why the glyph is
235
- // coloured by category in the first place; a row that also spelled the
236
- // type would say it twice. Ours printed `Hero Box ·HeroBox` at
237
- // `content.dim` in a 10px face, one `space-2` past the name.
238
- hierarchyTypeSuffix: 'hidden',
239
- // Measured across all three Outliner frames — `outliner.png` (edit mode),
240
- // `modeling-object-none.png` (object mode, nothing selected) and
241
- // `modeling-object-selected.png` (object mode, everything selected):
242
- // Blender draws NO rule, dotted or solid, under any name in that panel, in
243
- // any selection state. Ours drew an orange dotted rule under every
244
- // component-instance name — six of this scaffold's eleven rows — which is a
245
- // mark with no counterpart in the frame at all. The instance fact keeps its
246
- // doors (the label's tooltip, Go to Callsite / Open Component Source, the
247
- // inspector's component sections); only the paint yields.
248
- hierarchyInstanceRule: 'hidden',
249
- // Measured on `outliner.png`: every object row carries an EYE (CSS ink
250
- // 257.5-270.5, 32 px in from the area's right edge) and a render CAMERA
251
- // (277.5-290.5, 12 px in) on a 20 px column pitch, and no lock glyph
252
- // anywhere — Blender's Disable Selection column is off by default. Ours
253
- // drew a lock 29-41 px in and the eye 6-18 px in, so the LOCK was sitting
254
- // in Blender's eye column and the eye outboard of its camera column. The
255
- // lock goes, and the render column is reserved blank so the eye lands on
256
- // Blender's own; `workspace-regions.ts` carries why it stays blank.
257
- hierarchyRestrictions: 'viewport+render',
258
- // Measured at native 2x on `outliner.png` AND on the Properties area of
259
- // `modeling-edit-none.png` — device x relative to each area's own left
260
- // edge, and the two agree to half a pixel: every Blender area begins with
261
- // an EDITOR-TYPE WELL at x 17..80, i.e. 8.5 CSS in and 32 CSS wide, 20
262
- // tall, its glyph and a chevron inside a 1 px `#3c3c3c` outline over a
263
- // `#272727` inner. It is there whether the area holds one editor or a
264
- // dozen, and its dropdown is how an area changes editor. Ours began with
265
- // a 203 px strip of WORDS, which is what squeezed the Outliner's search
266
- // to 64 px against Blender's 115, truncated the third tab to `Libr`, and
267
- // left the Properties area — a lone panel declaring no header controls —
268
- // with no header band at all.
269
- },
270
211
  palette,
271
212
  };
@@ -7,20 +7,20 @@
7
7
  */
8
8
 
9
9
  import type { WorkspaceLayoutContribution } from '@volter/editor-sdk/looks';
10
+ import { BLENDER_REGIONS } from '../src/regions';
10
11
 
11
12
  export const point = 'workspace.layout';
12
13
  export const layout: WorkspaceLayoutContribution = {
13
14
  id: 'model',
14
15
  title: 'Model',
15
16
  description: 'Blender-shaped modeling: a dominant document, outliner above properties.',
16
- // No `tabs` here ON PURPOSE. The host's default is already `shown`, so
17
- // stating it changed nothing for a bare host — but the WORKSPACE layer wins
18
- // over the style bundle (`workspace-regions.ts`), so restating the default
19
- // silently shadowed `blender.style.ts`'s `tabs: 'hidden'` and the Model
20
- // document still painted a 25px tab row (measured against Blender 5.2, which
21
- // has none). Leave a region unstated unless this workspace genuinely needs
22
- // it different from the host default.
17
+ // No `tabs` here: the workspace layer sits above the person's own choice
18
+ // (`workspace-regions.ts`), so a stated region shadows it. Leave a region
19
+ // unstated unless this workspace genuinely needs it different from the host
20
+ // default.
21
+ // Blender's areas beneath this workspace's own (`../src/regions.ts`).
23
22
  regions: {
23
+ ...BLENDER_REGIONS,
24
24
  header: 'shown',
25
25
  shelf: 'shown',
26
26
  inspector: 'properties',
@@ -16,6 +16,12 @@
16
16
  *
17
17
  * documents: { find: [{ finder: 'modelsFromBlendFiles', include: ['src/models/**\/*.blend'] }] }
18
18
  *
19
+ * THE SESSION'S OWN MODEL. A project that holds no `.blend` of its own still
20
+ * needs ONE model — it is what `blender-start` presents into and what a first
21
+ * bpy call models in, saved to the session's default `models/model.blend` — so
22
+ * when the selection finds no file, the finder lists the standing
23
+ * `blender:runtime` entry instead. Never beside a real model.
24
+ *
19
25
  * This module is a plain object the HOST registers; it mounts no UI. It reads
20
26
  * the project only through `input.files` — the PATHS matching those globs.
21
27
  * Deliberately not `input.sources`: a `.blend` is binary, there is nothing in
@@ -54,6 +60,16 @@ export const finder = {
54
60
  source: { path },
55
61
  });
56
62
  }
63
+ if (entries.length === 0) {
64
+ entries.push({
65
+ id: 'blender:runtime',
66
+ label: 'Model',
67
+ kind: 'model',
68
+ region: null,
69
+ authorable: true,
70
+ reach: { kind: 'root-mount' as const },
71
+ });
72
+ }
57
73
  return { entries, notes };
58
74
  },
59
75
  } as const;
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { WorkspaceLayoutContribution } from '@volter/editor-sdk/looks';
9
+ import { BLENDER_REGIONS } from '../src/regions';
9
10
 
10
11
  export const point = 'workspace.layout';
11
12
  export const layout: WorkspaceLayoutContribution = {
@@ -16,7 +17,9 @@ export const layout: WorkspaceLayoutContribution = {
16
17
  requires: { documentKind: 'model' },
17
18
  // `tabs` unstated — see `model.layout.ts`: the workspace layer wins over the
18
19
  // style bundle, so restating the host default shadows it.
20
+ // Blender's areas beneath this workspace's own (`../src/regions.ts`).
19
21
  regions: {
22
+ ...BLENDER_REGIONS,
20
23
  header: 'shown',
21
24
  shelf: 'shown',
22
25
  inspector: 'properties',
@@ -53,6 +53,7 @@
53
53
  */
54
54
 
55
55
  import type { WorkspaceLayoutContribution } from '@volter/editor-sdk/looks';
56
+ import { BLENDER_REGIONS } from '../src/regions';
56
57
 
57
58
  export const point = 'workspace.layout';
58
59
  export const layout: WorkspaceLayoutContribution = {
@@ -64,7 +65,9 @@ export const layout: WorkspaceLayoutContribution = {
64
65
  // layer wins over the style bundle, so restating the host default would
65
66
  // silently shadow `blender.style.ts`'s `tabs: 'hidden'` and paint a tab row
66
67
  // Blender does not have.
68
+ // Blender's areas beneath this workspace's own (`../src/regions.ts`).
67
69
  regions: {
70
+ ...BLENDER_REGIONS,
68
71
  header: 'shown',
69
72
  shelf: 'shown',
70
73
  inspector: 'properties',
@@ -0,0 +1,15 @@
1
+ All HDRIs are licensed as CC0.
2
+
3
+ These were created by Greg Zaal (Poly Haven https://polyhaven.com).
4
+ Originals used for each HDRI:
5
+ - City: https://polyhaven.com/a/portland_landing_pad
6
+ - Courtyard: https://polyhaven.com/a/courtyard
7
+ - Forest: https://polyhaven.com/a/ninomaru_teien
8
+ - Interior: https://polyhaven.com/a/hotel_room
9
+ - Night: Probably https://polyhaven.com/a/moonless_golf
10
+ - Studio: Probably https://polyhaven.com/a/studio_small_01
11
+ - Sunrise: https://polyhaven.com/a/spruit_sunrise
12
+ - Sunset: https://polyhaven.com/a/venice_sunset
13
+
14
+ 1K resolution of each was taken, and compressed with oiiotool:
15
+ oiiotool input.exr --ch R,G,B -d float --compression dwab:300 --clamp:min=0.0:max=32000.0 -o output.exr
@@ -46,6 +46,7 @@
46
46
  */
47
47
 
48
48
  import type { WorkspaceLayoutContribution } from '@volter/editor-sdk/looks';
49
+ import { BLENDER_REGIONS } from '../src/regions';
49
50
 
50
51
  export const point = 'workspace.layout';
51
52
  export const layout: WorkspaceLayoutContribution = {
@@ -57,7 +58,9 @@ export const layout: WorkspaceLayoutContribution = {
57
58
  // layer wins over the style bundle, so restating the host default would
58
59
  // silently shadow `blender.style.ts`'s `tabs: 'hidden'` and paint a tab row
59
60
  // Blender does not have.
61
+ // Blender's areas beneath this workspace's own (`../src/regions.ts`).
60
62
  regions: {
63
+ ...BLENDER_REGIONS,
61
64
  header: 'shown',
62
65
  shelf: 'shown',
63
66
  inspector: 'properties',