@threepipe/plugin-blend-importer 0.0.9 → 0.3.0
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/CHANGELOG.md +51 -2
- package/README.md +25 -1
- package/dist/BlendLoadPlugin.d.ts +63 -3
- package/dist/BlendLoadPlugin.d.ts.map +1 -1
- package/dist/decompress.d.ts +10 -0
- package/dist/decompress.d.ts.map +1 -0
- package/dist/dependencies.txt +31 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1351 -89
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1349 -87
- package/dist/index.mjs.map +1 -1
- package/dist/loader/camera.d.ts +1 -1
- package/dist/loader/ctx.d.ts +9 -1
- package/dist/loader/ctx.d.ts.map +1 -1
- package/dist/loader/geometry.d.ts +2 -2
- package/dist/loader/geometry.d.ts.map +1 -1
- package/dist/loader/index.d.ts +1 -1
- package/dist/loader/light.d.ts +1 -1
- package/dist/loader/material.d.ts +4 -1
- package/dist/loader/material.d.ts.map +1 -1
- package/dist/loader/mesh.d.ts +1 -1
- package/dist/loader/mesh.d.ts.map +1 -1
- package/docs/assets/highlight.css +49 -0
- package/docs/assets/icons.js +1 -1
- package/docs/assets/icons.svg +1 -1
- package/docs/assets/main.js +2 -2
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/assets/style.css +3 -3
- package/docs/classes/BlendLoadPlugin.html +6 -3
- package/docs/index.html +1 -1
- package/docs/interfaces/BlendFile.html +18 -0
- package/docs/interfaces/BlendLoadOptions.html +12 -0
- package/package.json +4 -3
- package/src/BlendLoadPlugin.ts +204 -13
- package/src/decompress.ts +59 -0
- package/src/index.ts +1 -0
- package/src/js-blend/main.js +4 -1
- package/src/js-blend/parser/parser.js +192 -52
- package/src/loader/ctx.ts +9 -0
- package/src/loader/geometry.ts +270 -13
- package/src/loader/material.ts +324 -8
- package/src/loader/mesh.ts +34 -8
- package/tsconfig.json +42 -0
- package/docs/.nojekyll +0 -1
|
@@ -64,6 +64,9 @@ function worker_code () {
|
|
|
64
64
|
working_blend_file = null;
|
|
65
65
|
finished_objects = [];
|
|
66
66
|
current_SDNA_template = null;
|
|
67
|
+
// ERROR is module-global; without this reset a failed parse (e.g. an unsupported
|
|
68
|
+
// big-endian file) would be reported for the next, successful parse.
|
|
69
|
+
ERROR = null;
|
|
67
70
|
|
|
68
71
|
|
|
69
72
|
// set data
|
|
@@ -122,6 +125,16 @@ function worker_code () {
|
|
|
122
125
|
return pointerLow;
|
|
123
126
|
}
|
|
124
127
|
},
|
|
128
|
+
|
|
129
|
+
// Alignment-safe TypedArray read over the file buffer. Consumers (e.g. loader/geometry.ts)
|
|
130
|
+
// that reach into `__blender_file__.AB` at a datablock's `__data_address__` MUST use this
|
|
131
|
+
// instead of `new Ctor(AB, byteOffset, n)` — Blender packs blocks with no padding, so on
|
|
132
|
+
// v1 (17-byte header) and some v0 files the payload lands at an offset that isn't a multiple
|
|
133
|
+
// of the element size, where a direct view throws RangeError. All supported files are
|
|
134
|
+
// little-endian (big-endian is rejected at header parse), so a raw byte copy is correct.
|
|
135
|
+
readTypedArray: function (Ctor, byteOffset, length) {
|
|
136
|
+
return alignedTypedArray(Ctor, this.AB, byteOffset, length);
|
|
137
|
+
},
|
|
125
138
|
};
|
|
126
139
|
|
|
127
140
|
self.onmessage = parseFile;
|
|
@@ -132,11 +145,25 @@ function worker_code () {
|
|
|
132
145
|
This allows the underlying binary data to be changed.
|
|
133
146
|
*/
|
|
134
147
|
|
|
148
|
+
// Construct a TypedArray view over the file buffer, copying into a fresh aligned buffer when
|
|
149
|
+
// the byte offset isn't a multiple of the element size. Blender packs blocks with no padding,
|
|
150
|
+
// so on v1 (17-byte header) and some v0 files the payload lands at unaligned offsets where a
|
|
151
|
+
// direct `new Float32Array(buf, oddOffset, n)` would throw. All supported files are
|
|
152
|
+
// little-endian (big-endian is rejected at header parse), so a raw byte copy is correct.
|
|
153
|
+
function alignedTypedArray (Ctor, buffer, byteOffset, length) {
|
|
154
|
+
if ((byteOffset % Ctor.BYTES_PER_ELEMENT) === 0) {
|
|
155
|
+
return new Ctor(buffer, byteOffset, length);
|
|
156
|
+
}
|
|
157
|
+
const out = new Ctor(length);
|
|
158
|
+
new Uint8Array(out.buffer).set(new Uint8Array(buffer, byteOffset, length * Ctor.BYTES_PER_ELEMENT));
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
135
162
|
function float64Prop (offset, Blender_Array_Length, length) {
|
|
136
163
|
return {
|
|
137
164
|
get: function () {
|
|
138
165
|
return (Blender_Array_Length > 1) ?
|
|
139
|
-
|
|
166
|
+
alignedTypedArray(Float64Array, this.__blender_file__.AB, this.__data_address__ + offset, length) :
|
|
140
167
|
this.__blender_file__.dv.getFloat64(this.__data_address__ + offset, this.__blender_file__.template.endianess);
|
|
141
168
|
},
|
|
142
169
|
set: function (float) {
|
|
@@ -153,7 +180,7 @@ function worker_code () {
|
|
|
153
180
|
return {
|
|
154
181
|
get: function () {
|
|
155
182
|
return (Blender_Array_Length > 1) ?
|
|
156
|
-
|
|
183
|
+
alignedTypedArray(Float32Array, this.__blender_file__.AB, this.__data_address__ + offset, length) :
|
|
157
184
|
this.__blender_file__.dv.getFloat32(this.__data_address__ + offset, this.__blender_file__.template.endianess);
|
|
158
185
|
},
|
|
159
186
|
set: function (float) {
|
|
@@ -170,7 +197,7 @@ function worker_code () {
|
|
|
170
197
|
return {
|
|
171
198
|
get: function () {
|
|
172
199
|
return (Blender_Array_Length > 1) ?
|
|
173
|
-
|
|
200
|
+
alignedTypedArray(Int32Array, this.__blender_file__.AB, this.__data_address__ + offset, length) :
|
|
174
201
|
this.__blender_file__.dv.getInt32(this.__data_address__ + offset, this.__blender_file__.template.endianess);
|
|
175
202
|
},
|
|
176
203
|
set: function (float) {
|
|
@@ -187,7 +214,7 @@ function worker_code () {
|
|
|
187
214
|
return {
|
|
188
215
|
get: function () {
|
|
189
216
|
return (Blender_Array_Length > 1) ?
|
|
190
|
-
|
|
217
|
+
alignedTypedArray(Uint32Array, this.__blender_file__.AB, this.__data_address__ + offset, length) :
|
|
191
218
|
this.__blender_file__.dv.getUint32(this.__data_address__ + offset, this.__blender_file__.template.endianess);
|
|
192
219
|
},
|
|
193
220
|
set: function (float) {
|
|
@@ -204,7 +231,7 @@ function worker_code () {
|
|
|
204
231
|
return {
|
|
205
232
|
get: function () {
|
|
206
233
|
return (Blender_Array_Length > 1) ?
|
|
207
|
-
|
|
234
|
+
alignedTypedArray(Int16Array, this.__blender_file__.AB, this.__data_address__ + offset, length) :
|
|
208
235
|
this.__blender_file__.dv.getInt16(this.__data_address__ + offset, this.__blender_file__.template.endianess);
|
|
209
236
|
},
|
|
210
237
|
set: function (float) {
|
|
@@ -221,7 +248,7 @@ function worker_code () {
|
|
|
221
248
|
return {
|
|
222
249
|
get: function () {
|
|
223
250
|
return (Blender_Array_Length > 1) ?
|
|
224
|
-
|
|
251
|
+
alignedTypedArray(Uint16Array, this.__blender_file__.AB, this.__data_address__ + offset, length) :
|
|
225
252
|
this.__blender_file__.dv.getUint16(this.__data_address__ + offset, this.__blender_file__.template.endianess);
|
|
226
253
|
},
|
|
227
254
|
set: function (float) {
|
|
@@ -274,22 +301,38 @@ function worker_code () {
|
|
|
274
301
|
function pointerProp2 (offset) {
|
|
275
302
|
return {
|
|
276
303
|
get: function () {
|
|
277
|
-
|
|
278
|
-
|
|
304
|
+
const bf = this.__blender_file__;
|
|
305
|
+
let pointer = bf.getPointer(this.__data_address__ + offset, bf);
|
|
306
|
+
const link = bf.memory_lookup[pointer];
|
|
279
307
|
|
|
280
308
|
const results = [];
|
|
281
309
|
|
|
282
310
|
if (link) {
|
|
283
311
|
const address = link.__data_address__;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
312
|
+
const ps = (bf.template && bf.template.pointer_size) || 8;
|
|
313
|
+
// The pointed-to block is a malloc'd array of `block_length / pointer_size` pointers
|
|
314
|
+
// (e.g. `Material **mat` is `totcol` pointers). Read the WHOLE array and keep null
|
|
315
|
+
// (unresolved) entries as null — Blender leaves empty/object-linked material slots as
|
|
316
|
+
// null pointers in the middle of the array, so breaking at the first null silently
|
|
317
|
+
// drops every slot after it (lost materials, invisible faces).
|
|
318
|
+
const count = (typeof link.__byte_length__ === 'number' && link.__byte_length__ > 0)
|
|
319
|
+
? Math.floor(link.__byte_length__ / ps) : 0;
|
|
320
|
+
if (count > 0) {
|
|
321
|
+
for (let j = 0; j < count; j++) {
|
|
322
|
+
pointer = bf.getPointer(address + j * ps, bf);
|
|
323
|
+
results.push(bf.memory_lookup[pointer] || null);
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
// Fallback (no known block length): read until the first null, as before.
|
|
327
|
+
let j = 0;
|
|
328
|
+
while (true) {
|
|
329
|
+
pointer = bf.getPointer(address + j * ps, bf);
|
|
330
|
+
const obj = bf.memory_lookup[pointer];
|
|
331
|
+
if (!obj) break;
|
|
332
|
+
results.push(obj);
|
|
333
|
+
j++;
|
|
334
|
+
}
|
|
291
335
|
}
|
|
292
|
-
|
|
293
336
|
}
|
|
294
337
|
|
|
295
338
|
return results;
|
|
@@ -327,6 +370,35 @@ function worker_code () {
|
|
|
327
370
|
};
|
|
328
371
|
}
|
|
329
372
|
|
|
373
|
+
// Signed 1-byte field (int8_t). Used widely in Blender 5.0 DNA (enum domains, flags, etc).
|
|
374
|
+
function int8Prop (offset, Blender_Array_Length, length) {
|
|
375
|
+
return {
|
|
376
|
+
get: function () {
|
|
377
|
+
return (Blender_Array_Length > 1) ?
|
|
378
|
+
alignedTypedArray(Int8Array, this.__blender_file__.AB, this.__data_address__ + offset, length) :
|
|
379
|
+
this.__blender_file__.dv.getInt8(this.__data_address__ + offset);
|
|
380
|
+
},
|
|
381
|
+
set: function (v) { if (Blender_Array_Length <= 1) this.__blender_file__.dv.setInt8(this.__data_address__ + offset, v); },
|
|
382
|
+
enumerable: true, configurable: true,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 8-byte integer field (int64_t / uint64_t). Returned as a JS Number — .blend counts/sizes are
|
|
387
|
+
// well under 2^53. Used by Blender 5.0 DNA (AttributeArray.size, LargeBHead lengths, etc).
|
|
388
|
+
function int64Prop (offset, Blender_Array_Length, length, unsigned) {
|
|
389
|
+
return {
|
|
390
|
+
get: function () {
|
|
391
|
+
if (Blender_Array_Length > 1) {
|
|
392
|
+
return alignedTypedArray(unsigned ? BigUint64Array : BigInt64Array, this.__blender_file__.AB, this.__data_address__ + offset, length);
|
|
393
|
+
}
|
|
394
|
+
const dv = this.__blender_file__.dv, le = this.__blender_file__.template.endianess;
|
|
395
|
+
return Number(unsigned ? dv.getBigUint64(this.__data_address__ + offset, le) : dv.getBigInt64(this.__data_address__ + offset, le));
|
|
396
|
+
},
|
|
397
|
+
set: function () {},
|
|
398
|
+
enumerable: true, configurable: true,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
330
402
|
function compileProp (obj, name, type, offset, array_size, IS_POINTER, pointer_size, length) {
|
|
331
403
|
|
|
332
404
|
if (!IS_POINTER) {
|
|
@@ -338,18 +410,34 @@ function worker_code () {
|
|
|
338
410
|
Object.defineProperty(obj, name, floatProp(offset, array_size, length >> 2));
|
|
339
411
|
break;
|
|
340
412
|
case 'int':
|
|
413
|
+
case 'int32_t':
|
|
341
414
|
Object.defineProperty(obj, name, intProp(offset, array_size, length >> 2));
|
|
342
415
|
break;
|
|
416
|
+
case 'uint32_t':
|
|
417
|
+
Object.defineProperty(obj, name, uIntProp(offset, array_size, length >> 2));
|
|
418
|
+
break;
|
|
343
419
|
case 'short':
|
|
420
|
+
case 'int16_t':
|
|
344
421
|
Object.defineProperty(obj, name, shortProp(offset, array_size, length >> 1));
|
|
345
422
|
break;
|
|
346
423
|
case 'ushort':
|
|
424
|
+
case 'uint16_t':
|
|
347
425
|
Object.defineProperty(obj, name, uShortProp(offset, array_size, length >> 1));
|
|
348
426
|
break;
|
|
427
|
+
case 'int8_t':
|
|
428
|
+
Object.defineProperty(obj, name, int8Prop(offset, array_size, length));
|
|
429
|
+
break;
|
|
349
430
|
case 'char':
|
|
350
431
|
case 'uchar':
|
|
432
|
+
case 'uint8_t':
|
|
351
433
|
Object.defineProperty(obj, name, charProp(offset, array_size, length));
|
|
352
434
|
break;
|
|
435
|
+
case 'int64_t':
|
|
436
|
+
Object.defineProperty(obj, name, int64Prop(offset, array_size, length >> 3, false));
|
|
437
|
+
break;
|
|
438
|
+
case 'uint64_t':
|
|
439
|
+
Object.defineProperty(obj, name, int64Prop(offset, array_size, length >> 3, true));
|
|
440
|
+
break;
|
|
353
441
|
default:
|
|
354
442
|
// compile list to
|
|
355
443
|
obj[name] = {};
|
|
@@ -530,6 +618,11 @@ function worker_code () {
|
|
|
530
618
|
BLENDER_FILE.addObject(this);
|
|
531
619
|
|
|
532
620
|
this.__blender_file__ = BLENDER_FILE;
|
|
621
|
+
// Byte length of this block's payload (data_block_length is the end offset). Lets
|
|
622
|
+
// consumers derive element counts for raw DATA blocks (e.g. poly_offset_indices) whose
|
|
623
|
+
// logical length isn't available elsewhere — needed for Blender 5.0 where Mesh.tot* are
|
|
624
|
+
// runtime (post-geometry-nodes) counts, not the stored array sizes.
|
|
625
|
+
this.__byte_length__ = data_block_length - _data_offset;
|
|
533
626
|
|
|
534
627
|
const struct = this.__list__;
|
|
535
628
|
let j = 0,
|
|
@@ -611,31 +704,76 @@ function worker_code () {
|
|
|
611
704
|
struct_names = [];
|
|
612
705
|
offset = 0;
|
|
613
706
|
|
|
614
|
-
//
|
|
707
|
+
// Compressed (gzip/zstd) blend files are decompressed upstream in BlendLoadPlugin.loadAsync.
|
|
615
708
|
// Make sure we have a .blend __blender_file__. All blend files have the first 12bytes
|
|
616
709
|
// set with BLENDER-v### in Utf-8
|
|
617
710
|
const magic = toString(_data, offset, 7)
|
|
618
711
|
if (magic !== 'BLENDER') return ERROR = 'File supplied is not a .blend compatible Blender file.';
|
|
619
712
|
|
|
620
|
-
//
|
|
713
|
+
// Decode the file header. Two formats exist
|
|
714
|
+
// (.repos/blender/source/blender/blenloader_core/intern/blo_core_blend_header.cc):
|
|
715
|
+
// v0 (12 bytes): 'BLENDER' + ('_'|'-') + ('v'|'V') + 3-digit version
|
|
716
|
+
// '_' = 4-byte pointers (BHead4), '-' = 8-byte pointers (SmallBHead8)
|
|
717
|
+
// v1 (17 bytes, Blender 5.0+): 'BLENDER' + '17' + '-' + '01' + 'v' + 4-digit version
|
|
718
|
+
// always 8-byte pointers, little-endian, LargeBHead8 blocks.
|
|
719
|
+
// BIG_ENDIAN is misleadingly named — it is `true` for little-endian files (the value is
|
|
720
|
+
// passed straight to DataView getters as the `littleEndian` arg). Keep that convention.
|
|
721
|
+
let version, large_bhead = false
|
|
722
|
+
const fmtByte = toString(_data, 7, 8)
|
|
723
|
+
if (fmtByte === '_' || fmtByte === '-') {
|
|
724
|
+
pointer_size = fmtByte === '_' ? 4 : 8
|
|
725
|
+
BIG_ENDIAN = toString(_data, 8, 9) !== 'V'
|
|
726
|
+
version = toString(_data, 9, 12)
|
|
727
|
+
offset = 12
|
|
728
|
+
} else {
|
|
729
|
+
// New-style 17-byte header. Bytes 7-8 = header size ('17'), 9 = '-',
|
|
730
|
+
// 10-11 = file-format version ('01'), 12 = 'v', 13-16 = blender version.
|
|
731
|
+
const headerSize = parseInt(toString(_data, 7, 9), 10)
|
|
732
|
+
if (headerSize !== 17) return ERROR = 'Unsupported new-style blend header size: ' + headerSize;
|
|
733
|
+
pointer_size = 8
|
|
734
|
+
BIG_ENDIAN = true // v1 is always little-endian
|
|
735
|
+
large_bhead = true
|
|
736
|
+
version = toString(_data, 13, 17)
|
|
737
|
+
offset = headerSize
|
|
738
|
+
}
|
|
621
739
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
740
|
+
if (!BIG_ENDIAN) {
|
|
741
|
+
// Big-endian .blend files (ancient PowerPC/SGI era, e.g. Blender < 2.0) need every field
|
|
742
|
+
// byte-swapped, which we don't do. Bail clearly instead of producing garbage.
|
|
743
|
+
return ERROR = 'Big-endian .blend files are not supported';
|
|
744
|
+
}
|
|
626
745
|
|
|
746
|
+
// Per-format block-header layout. BHead variants
|
|
747
|
+
// (.repos/blender/source/blender/blenloader_core/BLO_core_bhead.hh):
|
|
748
|
+
// BHead4 (ptr=4): code@0 len@4(i32) old@8(u32) SDNAnr@12 nr@16 — 20 bytes
|
|
749
|
+
// SmallBHead8 (v0): code@0 len@4(i32) old@8(u64) SDNAnr@16 nr@20 — 24 bytes
|
|
750
|
+
// LargeBHead8 (v1): code@0 SDNAnr@4 old@8(u64) len@16(i64) nr@24(i64) — 32 bytes
|
|
751
|
+
// For v0 these reduce to the original parametrised offsets, so v0 behaviour is unchanged.
|
|
752
|
+
const BLOCK_HDR = large_bhead ? 32 : (16 + pointer_size)
|
|
753
|
+
const SDNA_OFF = large_bhead ? 4 : (8 + pointer_size)
|
|
754
|
+
const LEN_OFF = large_bhead ? 16 : 4
|
|
755
|
+
const NR_OFF = large_bhead ? 24 : (12 + pointer_size)
|
|
756
|
+
const LEN_I64 = large_bhead
|
|
757
|
+
const NR_I64 = large_bhead
|
|
758
|
+
// Blender packs blocks with no inter-block padding (verified empirically + writefile.cc),
|
|
759
|
+
// so block offsets advance by exactly BLOCK_HDR + len. The v1 17-byte header (and some v0
|
|
760
|
+
// files) leave block payloads at offsets that aren't multiples of 4/8 — those are handled
|
|
761
|
+
// at the TypedArray getter level (alignedTypedArray) rather than by realigning the buffer.
|
|
762
|
+
const readLen = (o) => LEN_I64 ? Number(data.getBigInt64(o + LEN_OFF, true)) : data.getInt32(o + LEN_OFF, true)
|
|
763
|
+
const readSdna = (o) => data.getInt32(o + SDNA_OFF, BIG_ENDIAN)
|
|
764
|
+
const readNr = (o) => NR_I64 ? Number(data.getBigInt64(o + NR_OFF, true)) : data.getInt32(o + NR_OFF, BIG_ENDIAN)
|
|
627
765
|
|
|
628
766
|
// create new master template if none exist for current blender version;
|
|
629
|
-
|
|
630
|
-
|
|
767
|
+
// Key on format + version so a v0 "050" and a v1 "0500" can never share a template.
|
|
768
|
+
const templateKey = (large_bhead ? 'v1:' : 'v0:') + version
|
|
769
|
+
if (!templates[templateKey]) {
|
|
770
|
+
templates[templateKey] = new MASTER_SDNA_SCHEMA(version);
|
|
631
771
|
}
|
|
632
772
|
|
|
633
|
-
current_SDNA_template = templates[
|
|
773
|
+
current_SDNA_template = templates[templateKey];
|
|
634
774
|
|
|
635
775
|
FILE.template = current_SDNA_template;
|
|
636
776
|
|
|
637
|
-
offset += 3;
|
|
638
|
-
|
|
639
777
|
// Set SDNA structs if template hasn't been set.
|
|
640
778
|
// Todo: Move the following block into the MASTER_SDNA_SCHEMA object.
|
|
641
779
|
//* Like so:*/ current_SDNA_template.set(AB);
|
|
@@ -647,11 +785,20 @@ function worker_code () {
|
|
|
647
785
|
offset2 = offset;
|
|
648
786
|
|
|
649
787
|
while (true) {
|
|
650
|
-
|
|
788
|
+
// Bounds guard: the SDNA-search loop walks block headers looking for DNA1.
|
|
789
|
+
// If a previous block's block_length is wrong (happens on some Blender 4.x
|
|
790
|
+
// geometry-nodes files), offset2 lands past EOF and the getInt32 below throws
|
|
791
|
+
// RangeError synchronously, killing the whole load. Match the main block
|
|
792
|
+
// loop's guard pattern (line ~810).
|
|
793
|
+
if (offset2 + BLOCK_HDR > data.byteLength) {
|
|
794
|
+
ERROR = 'Unexpected end of file while searching for DNA1';
|
|
795
|
+
break
|
|
796
|
+
}
|
|
797
|
+
sdna_index = readSdna(offset2);
|
|
651
798
|
// eslint-disable-next-line no-control-regex
|
|
652
799
|
code = toString(_data, offset2, offset2 + 4).replace(/\u0000/g, '');
|
|
653
|
-
block_length =
|
|
654
|
-
offset2 +=
|
|
800
|
+
block_length = readLen(offset2);
|
|
801
|
+
offset2 += BLOCK_HDR;
|
|
655
802
|
if (code === 'DNA1') {
|
|
656
803
|
// DNA found; This is the core of the __blender_file__ and contains all the structure for the various data types used in Blender.
|
|
657
804
|
count = 0;
|
|
@@ -794,6 +941,9 @@ function worker_code () {
|
|
|
794
941
|
// debugger
|
|
795
942
|
// offset = (4 - (offset % 4)) + offset;
|
|
796
943
|
// }
|
|
944
|
+
// Defensive: skip any stray NUL padding before a block header. Blender writes none
|
|
945
|
+
// between blocks (verified), so for correctly-decoded files this is a no-op; it only
|
|
946
|
+
// matters as a safety net against a mis-decoded length. Block codes are always A-Z.
|
|
797
947
|
for (let j = 0; j < 8; j++) {
|
|
798
948
|
if(data.getInt8(offset) === 0) {
|
|
799
949
|
offset++
|
|
@@ -801,41 +951,31 @@ function worker_code () {
|
|
|
801
951
|
}
|
|
802
952
|
break
|
|
803
953
|
}
|
|
804
|
-
if(data.getInt8(offset) === 0) {
|
|
805
|
-
debugger
|
|
806
|
-
}
|
|
807
954
|
|
|
808
955
|
data_offset = offset;
|
|
809
956
|
|
|
810
|
-
if (offset +
|
|
957
|
+
if (offset + BLOCK_HDR > data.byteLength) {
|
|
811
958
|
ERROR = 'Unexpected end of file while parsing';
|
|
812
959
|
break
|
|
813
960
|
}
|
|
814
961
|
|
|
815
|
-
sdna_index =
|
|
816
|
-
|
|
817
|
-
const code_str = toString(_data, offset, offset + 4) // data.getUint32(offset, BIG_ENDIAN);
|
|
962
|
+
sdna_index = readSdna(offset);
|
|
963
|
+
const code_str = toString(_data, offset, offset + 4)
|
|
818
964
|
|
|
819
|
-
offset2 = offset +
|
|
820
|
-
// console.log(code_str, code_str.length, data.getInt8(offset))
|
|
965
|
+
offset2 = offset + BLOCK_HDR;
|
|
821
966
|
|
|
822
|
-
const blockLength =
|
|
823
|
-
if (blockLength < 0 || offset + blockLength +
|
|
967
|
+
const blockLength = readLen(offset);
|
|
968
|
+
if (blockLength < 0 || offset + blockLength + BLOCK_HDR > data.byteLength) {
|
|
824
969
|
ERROR = 'Invalid block length detected';
|
|
825
970
|
break
|
|
826
971
|
}
|
|
827
|
-
// if(blockLength === 1) {
|
|
828
|
-
// debugger
|
|
829
|
-
// }
|
|
830
|
-
|
|
831
|
-
// last_offset = offset
|
|
832
972
|
|
|
833
|
-
offset += blockLength +
|
|
973
|
+
offset += blockLength + BLOCK_HDR;
|
|
834
974
|
|
|
835
975
|
if (code_str === 'DNA1') {} // skip - already processed at this point
|
|
836
976
|
else if (code_str === 'ENDB') break; // end of __blender_file__ found
|
|
837
977
|
else if (code_str === 'TEST') { // snapshot
|
|
838
|
-
const data_start = data_offset +
|
|
978
|
+
const data_start = data_offset + BLOCK_HDR;
|
|
839
979
|
const width = data.getInt32(data_start, BIG_ENDIAN);
|
|
840
980
|
const height = data.getInt32(data_start + 4, BIG_ENDIAN);
|
|
841
981
|
if ((width * height > 0)) {
|
|
@@ -844,7 +984,7 @@ function worker_code () {
|
|
|
844
984
|
ERROR = 'Invalid TEST block length detected';
|
|
845
985
|
break;
|
|
846
986
|
}
|
|
847
|
-
const image_data =
|
|
987
|
+
const image_data = alignedTypedArray(Uint32Array, _data, data_start + 8, data_len >> 2);
|
|
848
988
|
const image = {
|
|
849
989
|
width: width,
|
|
850
990
|
height: height,
|
|
@@ -855,14 +995,14 @@ function worker_code () {
|
|
|
855
995
|
}
|
|
856
996
|
else {
|
|
857
997
|
// Create a Blender object using a constructor template from current_SDNA_template
|
|
858
|
-
const data_start = data_offset +
|
|
998
|
+
const data_start = data_offset + BLOCK_HDR;
|
|
859
999
|
|
|
860
1000
|
// Get a SDNA constructor by name;
|
|
861
1001
|
const constructor = current_SDNA_template.getSDNAStructureConstructor(current_SDNA_template.SDNA_NAMES[sdna_index]);
|
|
862
1002
|
|
|
863
|
-
const size =
|
|
1003
|
+
const size = readLen(data_offset);
|
|
864
1004
|
|
|
865
|
-
count =
|
|
1005
|
+
count = readNr(data_offset);
|
|
866
1006
|
|
|
867
1007
|
if (count > 0 && constructor) {
|
|
868
1008
|
let obj = new constructor();
|
package/src/loader/ctx.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
PerspectiveCamera,
|
|
12
12
|
PointLight,
|
|
13
13
|
SpotLight,
|
|
14
|
+
Texture,
|
|
14
15
|
} from 'threepipe'
|
|
15
16
|
|
|
16
17
|
export interface Ctx {
|
|
@@ -26,4 +27,12 @@ export interface Ctx {
|
|
|
26
27
|
BufferAttribute: typeof BufferAttribute
|
|
27
28
|
MeshPhysicalMaterial: typeof MeshPhysicalMaterial
|
|
28
29
|
MeshBasicMaterial: typeof MeshBasicMaterial
|
|
30
|
+
/**
|
|
31
|
+
* Load an external (file-path) image referenced by a Blender Image datablock, resolving it
|
|
32
|
+
* relative to the `.blend`'s directory through threepipe's asset pipeline (cache + LoadingManager).
|
|
33
|
+
* Returns a Texture whose image fills in asynchronously (the load is awaited by the loader before
|
|
34
|
+
* the scene is returned). Returns `null` when unavailable (e.g. Node without a DOM). Provided by
|
|
35
|
+
* `BlendLoadPlugin`; absent when the loader is driven outside that plugin.
|
|
36
|
+
*/
|
|
37
|
+
loadExternalTexture?: (blenderPath: string, srgb: boolean) => Texture | null
|
|
29
38
|
}
|