@volter/blender-engine 0.1.132 → 0.1.134
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/browser/protocol.ts +1 -7
- package/browser/rna.ts +0 -73
- package/browser/runtime.ts +3 -12
- package/browser/session.py +68 -36
- package/browser/three/blender-runtime-armature.ts +2 -0
- package/browser/three/blender-runtime-geometry.ts +1 -1
- package/browser/three/blender-runtime-skeleton.ts +204 -0
- package/browser/three/blender-runtime-view.ts +134 -8
- package/browser/worker.ts +1 -5
- package/package.json +1 -1
- package/wasm/BUNDLE.json +11 -11
- package/wasm/blender_browser.js +1 -1
- package/wasm/blender_browser.wasm.br +0 -0
|
@@ -8,6 +8,7 @@ import { z } from 'zod';
|
|
|
8
8
|
import { onPresenterChange, presenterChanged } from './blender-presenter-change';
|
|
9
9
|
import { bytesFromBase64 } from './blender-base64';
|
|
10
10
|
import { ArmatureOverlay, armatureSchema } from './blender-runtime-armature';
|
|
11
|
+
import { type FrameSkin, RuntimeSkeletons, skinGeometry } from './blender-runtime-skeleton';
|
|
11
12
|
import { UNKNOWN_GEOMETRY, UNKNOWN_IMAGE } from './blender-runtime-frame';
|
|
12
13
|
import {
|
|
13
14
|
drawArraysFromColumns,
|
|
@@ -129,6 +130,38 @@ const attributeSchema = z.discriminatedUnion('type', [
|
|
|
129
130
|
z.object({ ...attributeFields, type: z.literal('BOOLEAN'), data: z.array(z.boolean()) }).strict(),
|
|
130
131
|
z.object({ ...attributeFields, type: z.literal('STRING'), data: z.array(z.string()) }).strict(),
|
|
131
132
|
]);
|
|
133
|
+
/** The door's skin block (`bpy_web_export.cc` `write_skin`): per EXPORTED vertex, four deform
|
|
134
|
+
* groups and their weights, for the armature that deforms the mesh. */
|
|
135
|
+
const frameSkinSchema = z
|
|
136
|
+
.object({
|
|
137
|
+
armature: z.string(),
|
|
138
|
+
groups: z.array(z.string()),
|
|
139
|
+
columns: z.object({ joints: z.instanceof(Uint16Array), weights: z.instanceof(Float32Array) }).strict(),
|
|
140
|
+
})
|
|
141
|
+
.strict();
|
|
142
|
+
/** The same skin per DRAWN vertex, as a copy of a presented frame carries it (`fullFrame`). */
|
|
143
|
+
const drawnSkinSchema = z
|
|
144
|
+
.object({
|
|
145
|
+
armature: z.string(),
|
|
146
|
+
groups: z.array(z.string()),
|
|
147
|
+
index: z.instanceof(Uint16Array),
|
|
148
|
+
weight: z.instanceof(Float32Array),
|
|
149
|
+
})
|
|
150
|
+
.strict();
|
|
151
|
+
/** An exported mesh's geometry with its skin laid over its drawn vertices (`skinGeometry`). */
|
|
152
|
+
function withFrameSkin(geometry: THREE.BufferGeometry, skin: FrameSkin | undefined): THREE.BufferGeometry {
|
|
153
|
+
if (skin) skinGeometry(geometry, skin);
|
|
154
|
+
return geometry;
|
|
155
|
+
}
|
|
156
|
+
/** A drawn mesh's geometry with the per-drawn-vertex skin a copy carried (`fullFrame`). */
|
|
157
|
+
function withDrawnSkin(geometry: THREE.BufferGeometry,
|
|
158
|
+
skin: { armature: string; groups: string[]; index: Uint16Array; weight: Float32Array } | undefined): THREE.BufferGeometry {
|
|
159
|
+
if (!skin) return geometry;
|
|
160
|
+
geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(skin.index, 4));
|
|
161
|
+
geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(skin.weight, 4));
|
|
162
|
+
geometry.userData['blenderSkin'] = { armature: skin.armature, groups: [...skin.groups] };
|
|
163
|
+
return geometry;
|
|
164
|
+
}
|
|
132
165
|
const drawArraysSchema = z
|
|
133
166
|
.object({
|
|
134
167
|
positions: z.instanceof(Float32Array),
|
|
@@ -147,6 +180,9 @@ const drawArraysSchema = z
|
|
|
147
180
|
})
|
|
148
181
|
.strict(),
|
|
149
182
|
),
|
|
183
|
+
/** The draw's vertex map (`DrawArrays.sourceVertex`), kept by a copy that plays (skins bind over it). */
|
|
184
|
+
sourceVertex: z.instanceof(Uint32Array).optional(),
|
|
185
|
+
skin: drawnSkinSchema.optional(),
|
|
150
186
|
hash: z.string(),
|
|
151
187
|
})
|
|
152
188
|
.strict();
|
|
@@ -222,6 +258,7 @@ const columnAttributeSchema = z
|
|
|
222
258
|
.strict();
|
|
223
259
|
const exportedMeshSchema = z
|
|
224
260
|
.object({
|
|
261
|
+
skin: frameSkinSchema.optional(),
|
|
225
262
|
columns: columnsSchema,
|
|
226
263
|
attributes: z.array(columnAttributeSchema),
|
|
227
264
|
activeUv: z.string().nullable(),
|
|
@@ -565,6 +602,11 @@ export const frameSchema = z
|
|
|
565
602
|
* `show_in_front`. `blender-runtime-armature.ts` is the drawing.
|
|
566
603
|
*/
|
|
567
604
|
armatures: z.record(z.string(), armatureSchema).default({}),
|
|
605
|
+
/** Every action and its revision (`bpy_web_export.cc`): a clip is baked again exactly when its
|
|
606
|
+
* action's revision moves. */
|
|
607
|
+
actions: z.record(z.string(), z.number().int().nonnegative()).default({}),
|
|
608
|
+
/** The scene's frame range and rate (`session.py`), beside `frame`. */
|
|
609
|
+
clock: z.object({ start: z.number().int(), end: z.number().int(), fps: z.number().finite().positive() }).strict().optional(),
|
|
568
610
|
/** The active object's active vertex group, per vertex
|
|
569
611
|
* (`blender-runtime-weights.ts`); null when nothing is painted. */
|
|
570
612
|
weights: weightsSchema.nullable().default(null),
|
|
@@ -661,8 +703,24 @@ export interface PhotographRecord {
|
|
|
661
703
|
render: { width: number; height: number; fov: number; orthographic: boolean };
|
|
662
704
|
}
|
|
663
705
|
|
|
706
|
+
/** A skinned mesh as the plain mesh it is again (its geometry kept), when its skin went away. */
|
|
707
|
+
function plainMesh(skinned: THREE.Mesh): THREE.Mesh {
|
|
708
|
+
const plain = new THREE.Mesh(skinned.geometry, skinned.material);
|
|
709
|
+
plain.name = skinned.name;
|
|
710
|
+
plain.matrixAutoUpdate = skinned.matrixAutoUpdate;
|
|
711
|
+
plain.matrix.copy(skinned.matrix);
|
|
712
|
+
plain.matrix.decompose(plain.position, plain.quaternion, plain.scale);
|
|
713
|
+
plain.userData = skinned.userData;
|
|
714
|
+
return plain;
|
|
715
|
+
}
|
|
716
|
+
|
|
664
717
|
export class BlenderRuntimeView {
|
|
665
718
|
readonly root = new THREE.Group();
|
|
719
|
+
/** Every armature's bones as the frame poses them, and the skins bound to them
|
|
720
|
+
* (`blender-runtime-skeleton.ts`). A reader plays clips on `skeletons.rig(name).bones`. */
|
|
721
|
+
readonly skeletons = new RuntimeSkeletons();
|
|
722
|
+
/** The geometry and rig each skinned mesh was bound with, so it is bound again only when either changes. */
|
|
723
|
+
private skinBinds = new WeakMap<THREE.Object3D, { geometry: THREE.BufferGeometry; rig: object }>();
|
|
666
724
|
private readonly instances = new BlenderRuntimeInstances(this.root);
|
|
667
725
|
private readonly materialRanges = new BlenderMaterialRanges();
|
|
668
726
|
private readonly transparentInstances = new BlenderTransparentInstances(this.root);
|
|
@@ -1059,6 +1117,35 @@ export class BlenderRuntimeView {
|
|
|
1059
1117
|
* needs it — its rows come from the engine by name, and every one that our
|
|
1060
1118
|
* viewport can select has to find its way back to a three object through
|
|
1061
1119
|
* the frame's own table, never through `object.name`. */
|
|
1120
|
+
/**
|
|
1121
|
+
* WHAT THE LAST FRAME SAYS ABOUT ANIMATION, for a reader that plays clips on `skeletons`: the
|
|
1122
|
+
* scene's current frame and clock, every action's revision, each armature's assigned action, the
|
|
1123
|
+
* active object, and which armature a mesh object's skin is bound to. Read off the frame the view
|
|
1124
|
+
* holds, never the engine.
|
|
1125
|
+
*/
|
|
1126
|
+
animationFacts(): {
|
|
1127
|
+
frame: number;
|
|
1128
|
+
clock: { start: number; end: number; fps: number } | null;
|
|
1129
|
+
active: string | null;
|
|
1130
|
+
actions: Readonly<Record<string, number>>;
|
|
1131
|
+
armatures: Readonly<Record<string, { action: string | null }>>;
|
|
1132
|
+
skinArmature(object: string): string | null;
|
|
1133
|
+
} {
|
|
1134
|
+
const frame = this.frame;
|
|
1135
|
+
return {
|
|
1136
|
+
frame: frame?.frame ?? 0,
|
|
1137
|
+
clock: frame?.clock ?? null,
|
|
1138
|
+
active: frame?.active ?? null,
|
|
1139
|
+
actions: frame?.actions ?? {},
|
|
1140
|
+
armatures: Object.fromEntries(Object.entries(frame?.armatures ?? {}).map(([name, armature]) => [name, { action: armature.action ?? null }])),
|
|
1141
|
+
skinArmature: (name) => {
|
|
1142
|
+
const object = this.objectForBlenderName(name) as THREE.Mesh | null;
|
|
1143
|
+
const skin = object?.geometry?.userData['blenderSkin'] as { armature: string } | undefined;
|
|
1144
|
+
return skin?.armature ?? null;
|
|
1145
|
+
},
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1062
1149
|
objectForBlenderName(name: string): THREE.Object3D | null {
|
|
1063
1150
|
const entry = this.frame?.objects.find((o) => o.name === name);
|
|
1064
1151
|
return entry === undefined ? null : (this.objects.get(entry.id) ?? null);
|
|
@@ -1669,9 +1756,11 @@ export class BlenderRuntimeView {
|
|
|
1669
1756
|
const data = exportedMeshSchema.parse(part.piece);
|
|
1670
1757
|
if (staged.meshes.has(part.mesh)) throw new Error('Duplicate Blender mesh piece');
|
|
1671
1758
|
const signature = `revision:${data.revision}`;
|
|
1672
|
-
|
|
1759
|
+
const geometry = geometryFromDrawArrays(drawArraysFromColumns({
|
|
1673
1760
|
...data.columns, attributes: data.attributes as never, activeUv: data.activeUv, renderUv: data.renderUv,
|
|
1674
|
-
}, signature))
|
|
1761
|
+
}, signature));
|
|
1762
|
+
if (data.skin) skinGeometry(geometry, data.skin);
|
|
1763
|
+
staged.meshes.set(part.mesh, { signature, geometry });
|
|
1675
1764
|
} else if (part.image !== undefined) {
|
|
1676
1765
|
if (staged.images.has(part.image)) throw new Error('Duplicate Blender image piece');
|
|
1677
1766
|
staged.images.set(part.image, frameImageSchema.parse(part.piece));
|
|
@@ -1789,7 +1878,7 @@ export class BlenderRuntimeView {
|
|
|
1789
1878
|
prepared.set(id, {
|
|
1790
1879
|
signature,
|
|
1791
1880
|
geometry: exported
|
|
1792
|
-
? geometryFromDrawArrays(
|
|
1881
|
+
? withFrameSkin(geometryFromDrawArrays(
|
|
1793
1882
|
drawArraysFromColumns(
|
|
1794
1883
|
{
|
|
1795
1884
|
...data.columns,
|
|
@@ -1799,9 +1888,9 @@ export class BlenderRuntimeView {
|
|
|
1799
1888
|
},
|
|
1800
1889
|
signature,
|
|
1801
1890
|
),
|
|
1802
|
-
)
|
|
1891
|
+
), data.skin)
|
|
1803
1892
|
: drawn
|
|
1804
|
-
? geometryFromDrawArrays(data)
|
|
1893
|
+
? withDrawnSkin(geometryFromDrawArrays(data), data.skin)
|
|
1805
1894
|
: drawRuntimeGeometry(data),
|
|
1806
1895
|
});
|
|
1807
1896
|
}
|
|
@@ -2178,6 +2267,26 @@ export class BlenderRuntimeView {
|
|
|
2178
2267
|
this.lights.delete(id);
|
|
2179
2268
|
}
|
|
2180
2269
|
this.root.updateMatrixWorld(true);
|
|
2270
|
+
// THE SKINS, once the graph stands (a bind reads world matrices): every armature's bones as
|
|
2271
|
+
// Blender posed them, and every mesh whose exported geometry carries a skin bound to them. A
|
|
2272
|
+
// mesh is bound again only when its geometry or its rig was rebuilt; one whose geometry lost
|
|
2273
|
+
// its skin draws as the plain mesh it is.
|
|
2274
|
+
const named = new Map(next.objects.map((obj) => [obj.name, this.objects.get(obj.id) ?? null]));
|
|
2275
|
+
this.skeletons.apply(next.armatures, (name) => named.get(name) ?? null);
|
|
2276
|
+
for (const obj of next.objects) {
|
|
2277
|
+
if (obj.mesh === null || obj.volume) continue;
|
|
2278
|
+
const object = this.objects.get(obj.id) as THREE.Mesh | undefined;
|
|
2279
|
+
if (!object?.isMesh) continue;
|
|
2280
|
+
const skin = object.geometry.userData['blenderSkin'] as { armature: string } | undefined;
|
|
2281
|
+
const rig = skin ? this.skeletons.rig(skin.armature) : null;
|
|
2282
|
+
const held = this.skinBinds.get(object);
|
|
2283
|
+
if (rig && held?.geometry === object.geometry && held.rig === rig) continue;
|
|
2284
|
+
const bound = rig ? this.skeletons.bind(object) : null;
|
|
2285
|
+
const replacement = bound ?? ((object as THREE.SkinnedMesh).isSkinnedMesh ? plainMesh(object) : null);
|
|
2286
|
+
if (replacement && replacement !== object) this.replacePresentedObject(object, replacement);
|
|
2287
|
+
if (bound && rig) this.skinBinds.set(bound, { geometry: bound.geometry, rig });
|
|
2288
|
+
}
|
|
2289
|
+
this.root.updateMatrixWorld(true);
|
|
2181
2290
|
// Keep the frame's identity and object table, not its geometry payloads:
|
|
2182
2291
|
// a presented mesh is already resident as its BufferGeometry, and the
|
|
2183
2292
|
// nested number arrays it arrived as cost ~15x their JSON (the blower's
|
|
@@ -2248,13 +2357,14 @@ export class BlenderRuntimeView {
|
|
|
2248
2357
|
/** Everything this view holds, as ONE self-contained frame another view can be built from:
|
|
2249
2358
|
* every mesh's resident geometry and every resident image, with no reference to what a
|
|
2250
2359
|
* receiver already holds. */
|
|
2251
|
-
private fullFrame(): Frame {
|
|
2360
|
+
private fullFrame(options: { sourceVertices?: boolean } = {}): Frame {
|
|
2252
2361
|
const source = this.frame;
|
|
2253
2362
|
if (!source) throw new Error('Blender capture requires a presented frame');
|
|
2254
2363
|
// Inspection overlays are not render content. In particular weight-paint
|
|
2255
2364
|
// overlays reference source vertices, which the evaluated draw no longer
|
|
2256
2365
|
// needs and a render snapshot deliberately does not copy.
|
|
2257
|
-
|
|
2366
|
+
// A copy that plays keeps the armatures its skins bind to; a render snapshot draws no bones.
|
|
2367
|
+
const frame: Frame = structuredClone({ ...source, images: {}, armatures: options.sourceVertices ? source.armatures : {}, weights: null });
|
|
2258
2368
|
for (const [id, { signature, geometry }] of this.meshes) {
|
|
2259
2369
|
const attribute = (name: string): Float32Array<ArrayBuffer> | null => {
|
|
2260
2370
|
const value = geometry.getAttribute(name);
|
|
@@ -2290,6 +2400,20 @@ export class BlenderRuntimeView {
|
|
|
2290
2400
|
count: group.count,
|
|
2291
2401
|
materialIndex: group.materialIndex ?? 0,
|
|
2292
2402
|
})),
|
|
2403
|
+
// A COPY THAT PLAYS keeps the draw's vertex map: Play binds skins over it (a Blender
|
|
2404
|
+
// vertex's weights expand onto its drawn vertices through it), where a render snapshot
|
|
2405
|
+
// needs none.
|
|
2406
|
+
...(options.sourceVertices && geometry.getAttribute('blenderVertex')
|
|
2407
|
+
? { sourceVertex: new Uint32Array((geometry.getAttribute('blenderVertex') as THREE.BufferAttribute).array) }
|
|
2408
|
+
: {}),
|
|
2409
|
+
// A COPY THAT PLAYS keeps its skin too, per drawn vertex, so its own skeletons bind it.
|
|
2410
|
+
...(options.sourceVertices && geometry.userData['blenderSkin'] && geometry.getAttribute('skinIndex')
|
|
2411
|
+
? { skin: {
|
|
2412
|
+
...(geometry.userData['blenderSkin'] as { armature: string; groups: string[] }),
|
|
2413
|
+
index: new Uint16Array((geometry.getAttribute('skinIndex') as THREE.BufferAttribute).array),
|
|
2414
|
+
weight: new Float32Array((geometry.getAttribute('skinWeight') as THREE.BufferAttribute).array),
|
|
2415
|
+
} }
|
|
2416
|
+
: {}),
|
|
2293
2417
|
};
|
|
2294
2418
|
}
|
|
2295
2419
|
for (const [id, { signature }] of this.volumes)
|
|
@@ -2368,7 +2492,7 @@ export class BlenderRuntimeView {
|
|
|
2368
2492
|
*/
|
|
2369
2493
|
detach(): { readonly view: BlenderRuntimeView; dispose(): void } {
|
|
2370
2494
|
const detached = new BlenderRuntimeView();
|
|
2371
|
-
if (this.frame) detached.applyFrame(this.fullFrame());
|
|
2495
|
+
if (this.frame) detached.applyFrame(this.fullFrame({ sourceVertices: true }));
|
|
2372
2496
|
let disposed = false;
|
|
2373
2497
|
return {
|
|
2374
2498
|
view: detached,
|
|
@@ -2499,6 +2623,8 @@ export class BlenderRuntimeView {
|
|
|
2499
2623
|
}
|
|
2500
2624
|
|
|
2501
2625
|
private clear() {
|
|
2626
|
+
this.skeletons.dispose();
|
|
2627
|
+
this.skinBinds = new WeakMap();
|
|
2502
2628
|
this.worldVisibility.dispose();
|
|
2503
2629
|
this.objectInfoMaterials.clear();
|
|
2504
2630
|
this.materialRanges.clear();
|
package/browser/worker.ts
CHANGED
|
@@ -796,16 +796,12 @@ async function handle(request: WorkerRequest): Promise<unknown> {
|
|
|
796
796
|
// reason is a measured defect: I4's Collection tab simply did not stand
|
|
797
797
|
// because `collection` crossed `runtime.ts` and `session.py` and never
|
|
798
798
|
// this switch, with no error anywhere.
|
|
799
|
-
case 'rig':
|
|
800
|
-
return pullWork('rig', () => ask({
|
|
801
|
-
op: 'rig',
|
|
802
|
-
...(request.object === undefined ? {} : { object: request.object }),
|
|
803
|
-
}));
|
|
804
799
|
case 'action-clip':
|
|
805
800
|
return ask({
|
|
806
801
|
op: 'action-clip',
|
|
807
802
|
...(request.object === undefined ? {} : { object: request.object }),
|
|
808
803
|
...(request.bake === undefined ? {} : { bake: request.bake }),
|
|
804
|
+
...(request.action === undefined ? {} : { action: request.action }),
|
|
809
805
|
});
|
|
810
806
|
case 'outliner-set':
|
|
811
807
|
return ask({
|
package/package.json
CHANGED
package/wasm/BUNDLE.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"blender": "5.2.0 LTS",
|
|
3
|
-
"source": "
|
|
3
|
+
"source": "49cb2710c66715ba61fbf65cc1cc818b1f49d73a",
|
|
4
4
|
"upstream": "fbe6228777e7d9afefcd61a413844e790ae75db7",
|
|
5
5
|
"sourceRepository": "https://github.com/volter-ai/blender",
|
|
6
|
-
"sourceRecipe": "https://github.com/volter-ai/blender/blob/
|
|
6
|
+
"sourceRecipe": "https://github.com/volter-ai/blender/blob/49cb2710c66715ba61fbf65cc1cc818b1f49d73a/build_files/emscripten/RELEASE.md",
|
|
7
7
|
"factoryEngine": "BLENDER_EEVEE",
|
|
8
8
|
"files": {
|
|
9
9
|
"blender_browser.js": "raw",
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
},
|
|
13
13
|
"rawFiles": {
|
|
14
14
|
"blender_browser.js": {
|
|
15
|
-
"bytes":
|
|
16
|
-
"sha256": "
|
|
15
|
+
"bytes": 408218,
|
|
16
|
+
"sha256": "bcdbcb3ff18110c095b5170d70a0d0f0b33687f86c14c8bc10c979d613ed11c7"
|
|
17
17
|
},
|
|
18
18
|
"blender_browser.wasm": {
|
|
19
|
-
"bytes":
|
|
20
|
-
"sha256": "
|
|
19
|
+
"bytes": 86366810,
|
|
20
|
+
"sha256": "56752d31e837d314eb489ffb031915e003ec9fade944657690ae097ed7c1e947"
|
|
21
21
|
},
|
|
22
22
|
"blender_browser.data": {
|
|
23
23
|
"bytes": 52699590,
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
},
|
|
27
27
|
"shippedFiles": {
|
|
28
28
|
"blender_browser.js": {
|
|
29
|
-
"bytes":
|
|
30
|
-
"sha256": "
|
|
29
|
+
"bytes": 408218,
|
|
30
|
+
"sha256": "bcdbcb3ff18110c095b5170d70a0d0f0b33687f86c14c8bc10c979d613ed11c7"
|
|
31
31
|
},
|
|
32
32
|
"blender_browser.wasm.br": {
|
|
33
|
-
"bytes":
|
|
34
|
-
"sha256": "
|
|
33
|
+
"bytes": 18248590,
|
|
34
|
+
"sha256": "54cb053de1d77030ba19345eaca38d16d1971bbb50f3de6624709f8b2f97f4e9"
|
|
35
35
|
},
|
|
36
36
|
"blender_browser.data.br": {
|
|
37
37
|
"bytes": 8056485,
|
|
@@ -45,6 +45,6 @@
|
|
|
45
45
|
"note": "Emscripten 6.0.5, Release -O3 objects and -O2 link, retained public build recipe and dependency licenses. Scoped render-evaluated export preserves render-only geometry and render-specific modifier results without replacing the viewport export state. Prior geometry identity and memory fixes remain. Full-scene Three versus Cycles rendering parity remains incomplete.",
|
|
46
46
|
"callTrampoline": "CPython 3.13.13 is compiled with PY_CALL_TRAMPOLINE for Blender CPython slot arity casts. This applies to CPython dispatch, not arbitrary indirect calls.",
|
|
47
47
|
"engineIds": "The session preserves the compiled native CYCLES engine. VOLTER_THREE selects the explicit raster presenter; BLENDER_EEVEE and BLENDER_WORKBENCH remain presentation engines.",
|
|
48
|
-
"exportDoor": "The source-owned bpy_web_export.cc exports Principled physical inputs, normal images, eight named UV layers and evaluated corner normals, and takes graph_materials/graph_images/graph_generated (a graph-drawn shader mix reduced to the one Principled BSDF it reaches, graph images at the door revisions with every UDIM tile, orco for deformed meshes whose graph reads Generated coordinates). A frame can defer its changed meshes and pictures (defer) for the caller to pull one at a time (export_mesh, export_image), each call freeing the previous one's columns and a picture it decoded; an undo step keeps the revisions when Blender kept the depsgraph (session_undo); Blender's CPU subdivision descriptor is released once a mesh is read; the memory door reports malloc in-use and free. Datablock revisions are keyed by session_uid. writefile.cc writes one copy of the document per save in the browser build. CPU evaluators adopt stencil tables without cloning; browser subdivision descriptors are released immediately after CPU tessellation/deformation. Native evaluated corner triangles accompany corner normals. Consumed local conversion stencils are released before later UV channels are constructed. Caller-thread file-read, evaluated-object and subdivision checkpoints provide backpressure for resumable startup. Shared geometry revisions advance once per frame. Existing synchronous export remains available. Optional instance_sources exports stable evaluated collection and particle source meshes, including excluded library meshes and converted curves. Frame-local instance_geometry tokens verify Python placement metadata against the exported geometry and are consumed before presentation. Geometry resource identity preserves implicit input storage only for unmodified geometry and separates evaluated modifier/deformation outputs. Object and mesh resource keys have independent namespaces. Scoped begin_render_export/end_render_export reads the render engine depsgraph and isolates viewport revision, undo, deferred resource and image watcher state. Geometry resource identity preserves implicit input storage only for unmodified geometry and separates evaluated modifier/deformation outputs. Object and mesh resource keys have independent namespaces.",
|
|
48
|
+
"exportDoor": "The source-owned bpy_web_export.cc exports Principled physical inputs, normal images, eight named UV layers and evaluated corner normals, and takes graph_materials/graph_images/graph_generated (a graph-drawn shader mix reduced to the one Principled BSDF it reaches, graph images at the door revisions with every UDIM tile, orco for deformed meshes whose graph reads Generated coordinates). A frame can defer its changed meshes and pictures (defer) for the caller to pull one at a time (export_mesh, export_image), each call freeing the previous one's columns and a picture it decoded; an undo step keeps the revisions when Blender kept the depsgraph (session_undo); Blender's CPU subdivision descriptor is released once a mesh is read; the memory door reports malloc in-use and free. Datablock revisions are keyed by session_uid. writefile.cc writes one copy of the document per save in the browser build. CPU evaluators adopt stencil tables without cloning; browser subdivision descriptors are released immediately after CPU tessellation/deformation. Native evaluated corner triangles accompany corner normals. Consumed local conversion stencils are released before later UV channels are constructed. Caller-thread file-read, evaluated-object and subdivision checkpoints provide backpressure for resumable startup. Shared geometry revisions advance once per frame. Existing synchronous export remains available. Optional instance_sources exports stable evaluated collection and particle source meshes, including excluded library meshes and converted curves. Frame-local instance_geometry tokens verify Python placement metadata against the exported geometry and are consumed before presentation. Geometry resource identity preserves implicit input storage only for unmodified geometry and separates evaluated modifier/deformation outputs. Object and mesh resource keys have independent namespaces. Scoped begin_render_export/end_render_export reads the render engine depsgraph and isolates viewport revision, undo, deferred resource and image watcher state. Geometry resource identity preserves implicit input storage only for unmodified geometry and separates evaluated modifier/deformation outputs. Object and mesh resource keys have independent namespaces. A frame ships each armature-deformed mesh's skin with its geometry (write_skin: four joints and weights per drawn vertex by the armature's deform bones, keyed by skin_digest so it is re-sent only when the binding changes) and each armature's assigned action (actions).",
|
|
49
49
|
"preloadedPayload": "releasePreloadedFileData releases the JS preload buffer after session ready. The measured working payload exclusions are retained."
|
|
50
50
|
}
|