@volter/editor-blender 0.1.8 → 0.1.9
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/contributions/blender-palette.source.mjs +15 -5
- package/contributions/blender-properties-view.tsx +28 -9
- package/contributions/blender-runtime.document.tsx +6 -1
- package/contributions/blender.command.ts +5 -1
- package/contributions/blender.environment.ts +32 -0
- package/contributions/blender.palette.json +8 -0
- package/contributions/blender.status.tsx +2 -6
- package/contributions/blender.style.ts +30 -89
- package/contributions/material-preview.view.ts +35 -0
- package/contributions/model.layout.ts +7 -7
- package/contributions/models.finder.ts +16 -0
- package/contributions/sculpt.layout.ts +3 -0
- package/contributions/shading.layout.ts +3 -0
- package/contributions/studiolights/world/city.exr +0 -0
- package/contributions/studiolights/world/courtyard.exr +0 -0
- package/contributions/studiolights/world/forest.exr +0 -0
- package/contributions/studiolights/world/interior.exr +0 -0
- package/contributions/studiolights/world/license.txt +15 -0
- package/contributions/studiolights/world/night.exr +0 -0
- package/contributions/studiolights/world/studio.exr +0 -0
- package/contributions/studiolights/world/sunrise.exr +0 -0
- package/contributions/studiolights/world/sunset.exr +0 -0
- package/contributions/uv-editing.layout.ts +3 -0
- package/dist-node/serving.mjs +624 -0
- package/host/blender-runtime-host.ts +11 -1
- package/node/blender-mcp.ts +2 -2
- package/package.json +21 -10
- package/serving/blender-routes.ts +427 -0
- package/serving/blender-wasm-artifact.ts +449 -0
- package/serving/index.ts +10 -0
- package/src/layouts.tsx +4 -5
- package/src/presentation.ts +32 -0
- package/src/regions.ts +59 -0
|
@@ -187,14 +187,24 @@ const MAPPING = {
|
|
|
187
187
|
'#69aa15',
|
|
188
188
|
'the same blend over `.tui.yaxis` #8bdc00 — measured, see axisX.',
|
|
189
189
|
),
|
|
190
|
-
// There is no `viewport.axisZ`:
|
|
191
|
-
//
|
|
192
|
-
// draws two axis lines. `.tui.zaxis` #2890ff is the member a third would
|
|
193
|
-
// cite when one exists — a palette key no reader reads is a name with no
|
|
194
|
-
// caller.
|
|
190
|
+
// There is no `viewport.axisZ`: the grid plane draws two axis lines. The
|
|
191
|
+
// gizmos carry all three (`gizmo.*`).
|
|
195
192
|
'viewport.selection': rgb('space_view3d.select'),
|
|
196
193
|
'viewport.active': rgb('space_view3d.active'),
|
|
197
194
|
|
|
195
|
+
// The transform gizmo draws the theme's axis colours as they are
|
|
196
|
+
// (`transform_gizmo_3d.cc`, `gizmo_get_axis_color`).
|
|
197
|
+
'gizmo.x': rgb('tui.xaxis'),
|
|
198
|
+
'gizmo.y': rgb('tui.yaxis'),
|
|
199
|
+
'gizmo.z': rgb('tui.zaxis'),
|
|
200
|
+
'gizmo.navigationX': held(
|
|
201
|
+
'#f53651',
|
|
202
|
+
"the navigation gizmo's X ball as Blender draws it, read off `modeling-object-none.png` " +
|
|
203
|
+
'(245,54,81): drawn from `.tui.xaxis` but not equal to it — measured.',
|
|
204
|
+
),
|
|
205
|
+
'gizmo.navigationY': held('#6fa41b', 'the same ball for Y (111,164,27) — measured, see navigationX.'),
|
|
206
|
+
'gizmo.navigationZ': held('#2e83e3', 'the same ball for Z (46,131,227) — measured, see navigationX.'),
|
|
207
|
+
|
|
198
208
|
'region.outliner': rgb('space_outliner.back'),
|
|
199
209
|
'region.properties': rgb('space_properties.back'),
|
|
200
210
|
|
|
@@ -383,9 +383,27 @@ function Numeric({
|
|
|
383
383
|
);
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
/**
|
|
387
|
-
*
|
|
388
|
-
|
|
386
|
+
/** An array component's letter, as `RNA_property_array_item_char` gives it:
|
|
387
|
+
* W/X/Y/Z for a quaternion or axis-angle (the angle first), X/Y/Z/W for the
|
|
388
|
+
* vector subtypes, R/G/B/A for a colour, and none otherwise, where the
|
|
389
|
+
* component is numbered. */
|
|
390
|
+
const VECTOR_SUBTYPES = new Set([
|
|
391
|
+
'TRANSLATION',
|
|
392
|
+
'DIRECTION',
|
|
393
|
+
'XYZ',
|
|
394
|
+
'XYZ_LENGTH',
|
|
395
|
+
'EULER',
|
|
396
|
+
'VELOCITY',
|
|
397
|
+
'ACCELERATION',
|
|
398
|
+
'COORDS',
|
|
399
|
+
]);
|
|
400
|
+
function arrayItemChar(subtype: string, index: number): string | undefined {
|
|
401
|
+
if (subtype === 'QUATERNION' || subtype === 'AXISANGLE') return 'WXYZ'[index];
|
|
402
|
+
if (index >= 4) return undefined;
|
|
403
|
+
if (VECTOR_SUBTYPES.has(subtype)) return 'XYZW'[index];
|
|
404
|
+
if (subtype === 'COLOR' || subtype === 'COLOR_GAMMA') return 'RGBA'[index];
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
389
407
|
|
|
390
408
|
/** A field's WHOLE name, for the control's `aria-label` — the property's UI
|
|
391
409
|
* name (its RNA identifier when it has none), plus the array component's
|
|
@@ -393,7 +411,7 @@ const ARRAY_LETTERS = ['X', 'Y', 'Z', 'W'];
|
|
|
393
411
|
* component after the first. */
|
|
394
412
|
function fieldName(row: BlenderRnaRow, index?: number): string {
|
|
395
413
|
const name = row.name || row.identifier;
|
|
396
|
-
return index === undefined ? name : `${name} ${
|
|
414
|
+
return index === undefined ? name : `${name} ${arrayItemChar(row.subtype, index) ?? index}`;
|
|
397
415
|
}
|
|
398
416
|
|
|
399
417
|
function colourOf(values: readonly number[]): string {
|
|
@@ -499,8 +517,8 @@ function ArrayRows({ row, label, title, write }: WidgetProps & { label: string;
|
|
|
499
517
|
<>
|
|
500
518
|
{values.map((value, index) => (
|
|
501
519
|
<Row
|
|
502
|
-
key={`${row.identifier}-${
|
|
503
|
-
label={componentLabel(label, index,
|
|
520
|
+
key={`${row.identifier}-${index}`}
|
|
521
|
+
label={componentLabel(label, index, row.subtype)}
|
|
504
522
|
title={title}
|
|
505
523
|
readonly={row.readonly}
|
|
506
524
|
identifier={`${row.identifier}[${index}]`}
|
|
@@ -526,9 +544,10 @@ function ArrayRows({ row, label, title, write }: WidgetProps & { label: string;
|
|
|
526
544
|
);
|
|
527
545
|
}
|
|
528
546
|
|
|
529
|
-
function componentLabel(label: string, index: number,
|
|
530
|
-
|
|
531
|
-
return
|
|
547
|
+
function componentLabel(label: string, index: number, subtype: string): string {
|
|
548
|
+
const letter = arrayItemChar(subtype, index);
|
|
549
|
+
if (letter === undefined) return index === 0 ? label : `${label} ${index}`;
|
|
550
|
+
return `${index === 0 ? `${label} ` : ''}${letter}`;
|
|
532
551
|
}
|
|
533
552
|
|
|
534
553
|
function widgetFor(props: WidgetProps): React.ReactNode {
|
|
@@ -20,10 +20,12 @@
|
|
|
20
20
|
*
|
|
21
21
|
* WITHOUT AN ENTRY it is still the document `blender-start` presents into: a
|
|
22
22
|
* project that lists no `.blend` of its own gets one Model document at the
|
|
23
|
-
* standing `blender:runtime` address (`
|
|
23
|
+
* standing `blender:runtime` address (`models.finder.ts` lists it), and
|
|
24
24
|
* the session opens its default `models/model.blend`.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
// How the `model` stage this document builds behaves (its starting presentation).
|
|
28
|
+
import '../src/presentation';
|
|
27
29
|
import { blenderModelView } from '@volter/blender-engine/browser/three/blender-runtime-view';
|
|
28
30
|
import type { ToolContributionProps, ToolDocumentToolbar } from '@volter/editor-sdk/contributions';
|
|
29
31
|
import { editorHost } from '@volter/editor-sdk/host';
|
|
@@ -236,6 +238,9 @@ function BlenderModelViewport({
|
|
|
236
238
|
// `--factory-startup`), about a third of the size a bare fit gives, which
|
|
237
239
|
// is the number `openingFit` was written for and nothing was passing.
|
|
238
240
|
openingFit={3}
|
|
241
|
+
// Every entry this document opens is a `model` stage, the standing `blender:runtime`
|
|
242
|
+
// address included (its id carries no `model:` prefix).
|
|
243
|
+
stageKind={documentKind}
|
|
239
244
|
// SOLID SHADING IS BLENDER'S, and Blender's Solid mode has NO world
|
|
240
245
|
// light: `light_ambient` is (0,0,0), there is no IBL, and the model is
|
|
241
246
|
// lit by four studio lights stated in VIEW space. So this document
|
|
@@ -60,7 +60,11 @@ export const commands: CommandContribution['commands'] = {
|
|
|
60
60
|
'blender-screenshot-view': verb('always', 60_000),
|
|
61
61
|
'blender-read-file': verb('none', 60_000),
|
|
62
62
|
'blender-write-file': verb('none', 60_000),
|
|
63
|
-
|
|
63
|
+
// Listing waits on the worker like a read: after an execute the MCP mirror lists
|
|
64
|
+
// while the page is still presenting the frame and refreshing its RNA and
|
|
65
|
+
// outliner reads, which on a large document outlasts the generic 5s default and
|
|
66
|
+
// turned a successful execute into a timeout.
|
|
67
|
+
'blender-list-files': verb('none', 60_000),
|
|
64
68
|
// THE RNA DOOR (WORK.md §Blender in the tab is Blender, "Inspection
|
|
65
69
|
// parity", I1). Two READS — a datablock's `bl_rna.properties` and the
|
|
66
70
|
// Properties context — and one WRITE. The reads present nothing, so they
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLENDER'S WORLD STUDIO LIGHTS: the eight HDRIs Blender ships for Material Preview
|
|
3
|
+
* (`datafiles/studiolights/world`, copied from Blender 5.2), registered as environment images
|
|
4
|
+
* (`@volter/editor-sdk/kit/environment-images`) so a view can light by one and draw it behind
|
|
5
|
+
* the scene. Material Preview opens on Forest. All CC0, by Greg Zaal (Poly Haven); the terms
|
|
6
|
+
* and each original are in `studiolights/world/license.txt`.
|
|
7
|
+
*/
|
|
8
|
+
import type { EnvironmentImageSet } from '@volter/editor-sdk/kit/environment-images';
|
|
9
|
+
import city from './studiolights/world/city.exr?url';
|
|
10
|
+
import courtyard from './studiolights/world/courtyard.exr?url';
|
|
11
|
+
import forest from './studiolights/world/forest.exr?url';
|
|
12
|
+
import interior from './studiolights/world/interior.exr?url';
|
|
13
|
+
import night from './studiolights/world/night.exr?url';
|
|
14
|
+
import studio from './studiolights/world/studio.exr?url';
|
|
15
|
+
import sunrise from './studiolights/world/sunrise.exr?url';
|
|
16
|
+
import sunset from './studiolights/world/sunset.exr?url';
|
|
17
|
+
|
|
18
|
+
export const point = 'workspace.environment';
|
|
19
|
+
export const environment: EnvironmentImageSet = {
|
|
20
|
+
id: 'blender-studiolights-world',
|
|
21
|
+
source: "Blender's world studio lights, CC0 (Poly Haven); studiolights/world/license.txt",
|
|
22
|
+
images: [
|
|
23
|
+
{ id: 'blender:forest', title: 'Forest', url: forest, format: 'exr' },
|
|
24
|
+
{ id: 'blender:city', title: 'City', url: city, format: 'exr' },
|
|
25
|
+
{ id: 'blender:courtyard', title: 'Courtyard', url: courtyard, format: 'exr' },
|
|
26
|
+
{ id: 'blender:interior', title: 'Interior', url: interior, format: 'exr' },
|
|
27
|
+
{ id: 'blender:night', title: 'Night', url: night, format: 'exr' },
|
|
28
|
+
{ id: 'blender:studio', title: 'Studio', url: studio, format: 'exr' },
|
|
29
|
+
{ id: 'blender:sunrise', title: 'Sunrise', url: sunrise, format: 'exr' },
|
|
30
|
+
{ id: 'blender:sunset', title: 'Sunset', url: sunset, format: 'exr' },
|
|
31
|
+
],
|
|
32
|
+
};
|
|
@@ -53,6 +53,14 @@
|
|
|
53
53
|
"selection": "#ed5700",
|
|
54
54
|
"active": "#ffa028"
|
|
55
55
|
},
|
|
56
|
+
"gizmo": {
|
|
57
|
+
"x": "#ff3352",
|
|
58
|
+
"y": "#8bdc00",
|
|
59
|
+
"z": "#2890ff",
|
|
60
|
+
"navigationX": "#f53651",
|
|
61
|
+
"navigationY": "#6fa41b",
|
|
62
|
+
"navigationZ": "#2e83e3"
|
|
63
|
+
},
|
|
56
64
|
"region": {
|
|
57
65
|
"outliner": "#282828",
|
|
58
66
|
"properties": "#303030"
|
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
* Blender's bottom row opens with three tiny mouse glyphs, each with one
|
|
5
5
|
* button lit and the action it performs beside it — `Select`, `Rotate View`,
|
|
6
6
|
* `Options` in the reference (`modeling.png`). It is the one piece of chrome
|
|
7
|
-
* that tells a newcomer what the three buttons DO without a menu
|
|
8
|
-
* gap a human build session measured here was exactly that question ("how do
|
|
9
|
-
* I move the camera", six times — `viewport-controls-hint.ts`).
|
|
7
|
+
* that tells a newcomer what the three buttons DO without a menu.
|
|
10
8
|
*
|
|
11
9
|
* THEY ARE HOVER-DRIVEN THERE, AND ALWAYS-ON HERE. Measured across the whole
|
|
12
10
|
* reference set: `modeling.png` is the ONE frame with any ink left of centre
|
|
@@ -33,9 +31,7 @@
|
|
|
33
31
|
* `editor-viewport.ts:682-686` sets `orbitControls.mouseButtons` to
|
|
34
32
|
* `{ LEFT: -1, MIDDLE: PAN, RIGHT: ROTATE }`, and the left button is left
|
|
35
33
|
* free for the selection raycast (`editor-viewport.ts:3782`,
|
|
36
|
-
* `if (e.button === 0 && !e.altKey)`).
|
|
37
|
-
* same thing (`ViewportControlsHint.tsx:22`: "Orbit: right-drag · Pan:
|
|
38
|
-
* middle-drag").
|
|
34
|
+
* `if (e.button === 0 && !e.altKey)`).
|
|
39
35
|
*
|
|
40
36
|
* That contract is a constant of the host, not of any document, so the table
|
|
41
37
|
* below is a constant too — with the citations above as its provenance. When
|
|
@@ -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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLENDER'S MATERIAL PREVIEW: the scene lit by a world studio light alone, Forest at strength 1
|
|
3
|
+
* and rotation 0, fixed in the world, drawn over the viewport's own colour, in AgX. Read from
|
|
4
|
+
* Blender 5.2's factory View3DShading: `studio_light` Default (forest.exr),
|
|
5
|
+
* `studiolight_intensity` 1, `studiolight_rotate_z` 0, `studiolight_background_alpha` 0,
|
|
6
|
+
* `use_studiolight_view_rotation` (World Space Lighting) on, `use_scene_lights` off.
|
|
7
|
+
* Measured against Blender 5.2's own EEVEE render of the default cube under the same world
|
|
8
|
+
* with its lamp removed.
|
|
9
|
+
* A named view (`@volter/editor-sdk/kit/viewport-presentation` `ViewPreset`); the images are
|
|
10
|
+
* `blender.environment.ts`'s.
|
|
11
|
+
*/
|
|
12
|
+
import type { ViewPreset } from '@volter/editor-sdk/kit/viewport-presentation';
|
|
13
|
+
|
|
14
|
+
export const point = 'workspace.view';
|
|
15
|
+
export const view: ViewPreset = {
|
|
16
|
+
id: 'blender-material-preview',
|
|
17
|
+
title: 'Material Preview',
|
|
18
|
+
layer: {
|
|
19
|
+
all: {
|
|
20
|
+
lighting: {
|
|
21
|
+
source: 'preview',
|
|
22
|
+
// Blender's viewport never changes its shading on its own: a lamp in the scene does not
|
|
23
|
+
// take Material Preview over.
|
|
24
|
+
auto: null,
|
|
25
|
+
preview: {
|
|
26
|
+
sceneLights: false,
|
|
27
|
+
sun: { enabled: false },
|
|
28
|
+
environment: { enabled: true, image: 'blender:forest', energy: 1, rotation: 0 },
|
|
29
|
+
},
|
|
30
|
+
tone: { mapper: 'agx', exposure: 1 },
|
|
31
|
+
},
|
|
32
|
+
backdrop: { source: 'fill' },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -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
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
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',
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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',
|