UNPKG

@threepipe/plugin-blend-importer

Version:
1 lines 84.9 kB
{"version":3,"file":"index.mjs","sources":["../src/js-blend/parser/parser.js","../src/js-blend/main.js","../src/loader/camera.ts","../src/loader/light.ts","../src/loader/geometry.ts","../src/loader/material.ts","../src/loader/mesh.ts","../src/loader/index.ts","../src/BlendLoadPlugin.ts"],"sourcesContent":["/* eslint-disable camelcase, no-unused-vars, no-empty, no-constant-condition */\nconst DNA1 = 826363460;\nconst ENDB = 1111772741;\n\n/* Note: Blender coordinates treat the Z axis as the vertical an Y as depth. */\n\n// web worker not functional in this version\nlet USE_WEBWORKER = false;\n\nlet worker = null;\n\n// FR = new FileReader(),\n\nconst return_object = {\n loadBlendFromArrayBuffer: function (array_buffer) {\n return_object.ready = false;\n if (USE_WEBWORKER) {\n worker.postMessage(array_buffer, array_buffer);\n } else {\n worker.onmessage({\n data: array_buffer,\n });\n }\n },\n // loadBlendFromBlob: function (blob) {\n // FR.onload = function () {\n // return_object.loadBlendFromArrayBuffer(this.result);\n // };\n // FR.readAsArrayBuffer(blob);\n // },\n ready: true,\n onParseReady: function () {},\n};\n\nfunction worker_code () {\n 'use strict';\n\n let data = null,\n _data = null,\n BIG_ENDIAN = false,\n pointer_size = 0,\n struct_names = [],\n offset = 0,\n working_blend_file = null,\n current_SDNA_template = null,\n templates = {},\n finished_objects = [],\n FILE = null,\n ERROR = null,\n AB = null;\n\n const self = this;\n\n function parseFile (msg) {\n\n if (typeof msg.data == 'object') {\n // reset global variables\n AB = null;\n data = null;\n BIG_ENDIAN = false;\n pointer_size = 0;\n struct_names = [];\n offset = 0;\n working_blend_file = null;\n finished_objects = [];\n current_SDNA_template = null;\n\n\n // set data\n _data = msg.data;\n\n AB = _data.slice();\n\n data = new DataView(_data);\n\n\n FILE = new BLENDER_FILE(AB);\n\n // start parsing\n readFile();\n\n // export parsed data\n self.postMessage(FILE, ERROR);\n }\n }\n\n /*\n Export object for a parsed __blender_file__.\n */\n\n const BLENDER_FILE = function (AB) {\n this.AB = AB;\n // this.double = new Float64Array(AB);\n this.byte = new Uint8Array(AB);\n\n this.dv = new DataView(AB);\n\n this.objects = {};\n this.memory_lookup = {},\n this.object_array = [];\n\n this.template = null;\n };\n\n BLENDER_FILE.prototype = {\n addObject: function (obj) {\n this.object_array.push(obj);\n if (!this.objects[obj.blender_name]) this.objects[obj.blender_name] = [];\n this.objects[obj.blender_name].push(obj);\n },\n\n getPointer: function (offset) {\n const pointerLow = this.dv.getUint32(offset, this.template.endianess);\n if (this.template.pointer_size > 4) {\n const pointerHigh = this.dv.getUint32(offset + 4, this.template.endianess);\n if (this.template.endianess) {\n return (pointerLow) + 'l|h' + pointerHigh;\n } else {\n return (pointerHigh) + 'h|l' + pointerLow;\n }\n } else {\n return pointerLow;\n }\n },\n };\n\n self.onmessage = parseFile;\n // this.onmessage = parseFile;\n\n /*\n These functions map offsets in the blender __blender_file__ to basic types (byte,short,int,float) through TypedArrays;\n This allows the underlying binary data to be changed.\n */\n\n function float64Prop (offset, Blender_Array_Length, length) {\n return {\n get: function () {\n return (Blender_Array_Length > 1) ?\n new Float64Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :\n this.__blender_file__.dv.getFloat64(this.__data_address__ + offset, this.__blender_file__.template.endianess);\n },\n set: function (float) {\n if (Blender_Array_Length > 1) {} else {\n this.__blender_file__.dv.setFloat64(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);\n }\n },\n enumerable: true,\n configurable: true,\n };\n }\n\n function floatProp (offset, Blender_Array_Length, length) {\n return {\n get: function () {\n return (Blender_Array_Length > 1) ?\n new Float32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :\n this.__blender_file__.dv.getFloat32(this.__data_address__ + offset, this.__blender_file__.template.endianess);\n },\n set: function (float) {\n if (Blender_Array_Length > 1) {} else {\n this.__blender_file__.dv.setFloat32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);\n }\n },\n enumerable: true,\n configurable: true,\n };\n }\n\n function intProp (offset, Blender_Array_Length, length) {\n return {\n get: function () {\n return (Blender_Array_Length > 1) ?\n new Int32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :\n this.__blender_file__.dv.getInt32(this.__data_address__ + offset, this.__blender_file__.template.endianess);\n },\n set: function (float) {\n if (Blender_Array_Length > 1) {} else {\n this.__blender_file__.dv.setInt32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);\n }\n },\n enumerable: true,\n configurable: true,\n };\n }\n\n function uIntProp (offset, Blender_Array_Length, length) {\n return {\n get: function () {\n return (Blender_Array_Length > 1) ?\n new Uint32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :\n this.__blender_file__.dv.getUint32(this.__data_address__ + offset, this.__blender_file__.template.endianess);\n },\n set: function (float) {\n if (Blender_Array_Length > 1) {} else {\n this.__blender_file__.dv.setUint32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);\n }\n },\n enumerable: true,\n configurable: true,\n };\n }\n\n function shortProp (offset, Blender_Array_Length, length) {\n return {\n get: function () {\n return (Blender_Array_Length > 1) ?\n new Int16Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :\n this.__blender_file__.dv.getInt16(this.__data_address__ + offset, this.__blender_file__.template.endianess);\n },\n set: function (float) {\n if (Blender_Array_Length > 1) {} else {\n this.__blender_file__.dv.setInt16(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);\n }\n },\n enumerable: true,\n configurable: true,\n };\n }\n\n const uShortProp = (offset, Blender_Array_Length, length) => {\n return {\n get: function () {\n return (Blender_Array_Length > 1) ?\n new Uint16Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :\n this.__blender_file__.dv.getUint16(this.__data_address__ + offset, this.__blender_file__.template.endianess);\n },\n set: function (float) {\n if (Blender_Array_Length > 1) {\n } else {\n this.__blender_file__.dv.setUint16(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);\n }\n },\n enumerable: true,\n configurable: true,\n };\n };\n\n function charProp (offset, Blender_Array_Length, length) {\n return {\n get: function () {\n if (Blender_Array_Length > 1) {\n let start = this.__data_address__ + offset;\n let end = start;\n let buffer_guard = 0;\n\n while (this.__blender_file__.byte[end] != 0 && buffer_guard++ < length) end++;\n\n return toString(this.__blender_file__.AB, start, end);\n }\n return this.__blender_file__.byte[(this.__data_address__ + offset)];\n },\n set: function (byte) {\n if (Blender_Array_Length > 1) {\n const string = byte + '';\n let i = 0;\n const l = string.length;\n while (i < length) {\n if (i < l) {\n this.__blender_file__.byte[(this.__data_address__ + offset + i)] = string.charCodeAt(i) | 0;\n } else {\n this.__blender_file__.byte[(this.__data_address__ + offset + i)] = 0;\n }\n i++;\n }\n } else {\n this.__blender_file__.byte[(this.__data_address__ + offset)] = byte | 0;\n }\n },\n enumerable: true,\n configurable: true,\n };\n }\n\n function pointerProp2 (offset) {\n return {\n get: function () {\n let pointer = this.__blender_file__.getPointer(this.__data_address__ + offset, this.__blender_file__);\n const link = this.__blender_file__.memory_lookup[pointer];\n\n const results = [];\n\n if (link) {\n const address = link.__data_address__;\n let j = 0;\n while (true) {\n pointer = this.__blender_file__.getPointer(address + j * 8, this.__blender_file__);\n let obj = this.__blender_file__.memory_lookup[pointer];\n if (!obj) break;\n results.push(obj);\n j++;\n }\n\n }\n\n return results;\n },\n set: function () {},\n enumerable: true,\n configurable: true,\n };\n }\n\n function pointerProp (offset, Blender_Array_Length, length) {\n return {\n get: function () {\n if (Blender_Array_Length > 1) {\n let array = [];\n let j = 0;\n let off = offset;\n while (j < Blender_Array_Length) {\n let pointer = this.__blender_file__.getPointer(this.__data_address__ + off, this.__blender_file__);\n\n array.push(this.__blender_file__.memory_lookup[pointer]);\n off += length;\n j++;\n }\n\n return array;\n } else {\n let pointer = this.__blender_file__.getPointer(this.__data_address__ + offset, this.__blender_file__);\n return this.__blender_file__.memory_lookup[pointer];\n }\n },\n set: function () {},\n enumerable: true,\n configurable: true,\n };\n }\n\n function compileProp (obj, name, type, offset, array_size, IS_POINTER, pointer_size, length) {\n\n if (!IS_POINTER) {\n switch (type) {\n case 'double':\n Object.defineProperty(obj, name, float64Prop(offset, array_size, length >> 3));\n break;\n case 'float':\n Object.defineProperty(obj, name, floatProp(offset, array_size, length >> 2));\n break;\n case 'int':\n Object.defineProperty(obj, name, intProp(offset, array_size, length >> 2));\n break;\n case 'short':\n Object.defineProperty(obj, name, shortProp(offset, array_size, length >> 1));\n break;\n case 'ushort':\n Object.defineProperty(obj, name, uShortProp(offset, array_size, length >> 1));\n break;\n case 'char':\n case 'uchar':\n Object.defineProperty(obj, name, charProp(offset, array_size, length));\n break;\n default:\n // compile list to\n obj[name] = {};\n obj.__list__.push(name, type, length, offset, array_size, IS_POINTER);\n }\n obj._length += length;\n offset += length;\n } else {\n Object.defineProperty(obj, name, pointerProp(offset, array_size, pointer_size));\n obj._length += pointer_size * array_size;\n offset += pointer_size * array_size;\n }\n\n return offset;\n }\n\n // Store final DNA structs\n const MASTER_SDNA_SCHEMA = function (version) {\n this.version = version;\n this.SDNA_SET = false;\n this.byte_size = 0;\n this.struct_index = 0;\n this.structs = {};\n this.SDNA = {};\n this.endianess = false;\n };\n\n MASTER_SDNA_SCHEMA.prototype = {\n getSDNAStructureConstructor: function (name, struct) {\n if (struct) {\n const jsName = name === 'void' ? 'void_' : name\n const blen_struct = Function('function ' + jsName + '(){}; return ' + jsName)();\n\n // if(name === 'CustomDataLayer') debugger\n\n blen_struct.prototype = new BLENDER_STRUCTURE();\n blen_struct.prototype.blender_name = name;\n blen_struct.prototype.__pointers = [];\n blen_struct.prototype.__list__ = [];\n\n const DNA = this.SDNA[name] = {\n constructor: blen_struct,\n };\n let offset = 0;\n // Create properties of struct\n for (let i = 0; i < struct.length; i += 3) {\n let _name = struct[i];\n const n = _name,\n type = struct[i + 1];\n let length = struct[i + 2],\n array_length = 0,\n match = null,\n Blender_Array_Length = 1,\n Suparray_match = 1,\n PointerToArray = false,\n Pointer_Match = 0;\n\n\n let original_name = _name;\n\n // mini type parser\n if ((match = _name.match(/(\\*?)(\\*?)(\\w+)(\\[(\\w*)\\])?(\\[(\\w*)\\])?/))) {\n\n // base name\n _name = match[3];\n\n // pointer type\n if (match[1]) {\n Pointer_Match = 10;\n blen_struct.prototype.__pointers.push(_name);\n }\n\n if (match[2]) {\n PointerToArray = true;\n }\n\n // arrays\n if (match[4]) {\n if (match[6]) {\n Suparray_match = parseInt(match[5]);\n Blender_Array_Length = parseInt(match[7]);\n } else {\n Blender_Array_Length = parseInt(match[5]);\n }\n }\n array_length = Blender_Array_Length * length;\n length = array_length * Suparray_match;\n }\n\n DNA[n] = {\n type: type,\n length: length,\n isArray: (Blender_Array_Length > 0),\n };\n\n if (PointerToArray) {\n Object.defineProperty(blen_struct.prototype, _name, pointerProp2(offset));\n offset += pointer_size;\n } else if (Suparray_match > 1) {\n const array_names = new Array(Suparray_match);\n\n // construct sub_array object that will return the correct structs\n for (let j = 0; j < Suparray_match; j++) {\n let array_name_ = `__${_name}[${j}]__`;\n array_names[j] = array_name_;\n\n offset = compileProp(blen_struct.prototype, array_name_, type, offset, Blender_Array_Length, Pointer_Match, pointer_size, array_length);\n }\n\n Object.defineProperty(blen_struct.prototype, _name, {\n get: (function (array_names) {\n return function () {\n const array = [];\n for (let i = 0; i < array_names.length; i++) {\n array.push(this[array_names[i]]);\n }\n return array;\n };\n })(array_names),\n enumerable: true,\n configurable: true,\n });\n } else {\n offset = compileProp(blen_struct.prototype, _name, type, offset, Blender_Array_Length, Pointer_Match, pointer_size, length);\n }\n }\n\n return this.SDNA[name].constructor;\n\n } else {\n if (!this.SDNA[name]) {\n return null;\n }\n return this.SDNA[name].constructor;\n }\n },\n };\n\n const BLENDER_STRUCTURE = function () {\n this.__blender_file__ = null;\n this.__list__ = null;\n this.__super_array_list__ = null;\n this.blender_name = '';\n this.__pointers = null;\n this.address = null;\n this.length = 0;\n this.__data_address__ = 0;\n this.blender_name = '';\n this._length = 0;\n };\n\n\n /*\n Returns a pre-constructed BLENDER_STRUCTURE or creates a new BLENDER_STRUCTURE to match the DNA struct type\n */\n const pointer_function = (pointer) => () => {\n return FILE.memory_lookup[pointer];\n };\n\n function getPointer (offset) {\n const pointerLow = data.getUint32(offset, BIG_ENDIAN);\n if (pointer_size > 4) {\n const pointerHigh = data.getUint32(offset + 4, BIG_ENDIAN);\n\n if (BIG_ENDIAN) {\n return (pointerLow) + '' + pointerHigh;\n } else {\n return (pointerHigh) + '' + pointerLow;\n }\n } else {\n return pointerLow;\n }\n }\n\n BLENDER_STRUCTURE.prototype = {\n setData: function (pointer, _data_offset, data_block_length, BLENDER_FILE) {\n if (this.__list__ === null) return this;\n BLENDER_FILE.addObject(this);\n\n this.__blender_file__ = BLENDER_FILE;\n\n const struct = this.__list__;\n let j = 0,\n i = 0,\n obj, name = '',\n type, length, Blender_Array_Length, Pointer_Match, offset, constructor;\n\n this.__data_address__ = _data_offset;\n\n // if(this.blender_name === 'CustomDataLayer') debugger\n\n if (struct === null) return this;\n\n for (i = 0; i < struct.length; i += 6) {\n obj = null;\n name = struct[i];\n type = struct[i + 1];\n Blender_Array_Length = struct[i + 4];\n Pointer_Match = struct[i + 5];\n offset = this.__data_address__ + struct[i + 3];\n\n if (Blender_Array_Length > 1) {\n this[name] = [];\n j = 0;\n while (j < Blender_Array_Length) {\n if (current_SDNA_template.getSDNAStructureConstructor(type)) {\n constructor = current_SDNA_template.getSDNAStructureConstructor(type);\n this[name].push((new constructor()).setData(0, offset, offset + length / Blender_Array_Length, BLENDER_FILE));\n } else this[name].push(null);\n offset += length / Blender_Array_Length;\n j++;\n }\n } else {\n if (current_SDNA_template.getSDNAStructureConstructor(type)) {\n constructor = current_SDNA_template.getSDNAStructureConstructor(type);\n this[name] = (new constructor()).setData(0, offset, length + offset, BLENDER_FILE);\n } else this[name] = null;\n }\n }\n // break connection to configuration list\n this.__list__ = null;\n return this;\n },\n\n get aname () {\n if (this.id) return this.id.name.slice(2);\n else return undefined;\n },\n };\n\n function toString (buffer, _in, _out) {\n return String.fromCharCode.apply(String, new Uint8Array(buffer, _in, _out - _in));\n }\n function seekCheck (buffer, _in, str) {\n const length = str.length;\n for (let i = 0; i < length; i++) {\n if (buffer.getUint8(_in + i) !== str.charCodeAt(i)) {\n return false;\n }\n }\n return true;\n }\n\n // Begin parsing blender __blender_file__\n\n function readFile () {\n let count = 0;\n let offset2 = 0;\n const root = 0;\n const i = 0;\n let data_offset = 0;\n let sdna_index = 0;\n let code = '';\n let block_length = 0;\n let curr_count = 0;\n let curr_count2 = 0;\n\n FILE.memory_lookup = {};\n struct_names = [];\n offset = 0;\n\n // todo gzip, zstd(28 b5 2f fd, https://projects.blender.org/archive/blender-file/issues/93858)\n // Make sure we have a .blend __blender_file__. All blend files have the first 12bytes\n // set with BLENDER-v### in Utf-8\n const magic = toString(_data, offset, 7)\n if (magic !== 'BLENDER') return ERROR = 'File supplied is not a .blend compatible Blender file.';\n\n // otherwise get templete from save version.\n\n offset += 7;\n pointer_size = ((toString(_data, offset++, offset)) == '_') ? 4 : 8;\n BIG_ENDIAN = toString(_data, offset++, offset) !== 'V';\n const version = toString(_data, offset, offset + 3);\n\n\n // create new master template if none exist for current blender version;\n if (!templates[version]) {\n templates[version] = new MASTER_SDNA_SCHEMA(version);\n }\n\n current_SDNA_template = templates[version];\n\n FILE.template = current_SDNA_template;\n\n offset += 3;\n\n // Set SDNA structs if template hasn't been set.\n // Todo: Move the following block into the MASTER_SDNA_SCHEMA object.\n //* Like so:*/ current_SDNA_template.set(AB);\n\n if (!current_SDNA_template.SDNA_SET) {\n current_SDNA_template.endianess = BIG_ENDIAN;\n current_SDNA_template.pointer_size = pointer_size;\n // find DNA1 data block\n offset2 = offset;\n\n while (true) {\n sdna_index = data.getInt32(offset2 + pointer_size + 8, BIG_ENDIAN);\n // eslint-disable-next-line no-control-regex\n code = toString(_data, offset2, offset2 + 4).replace(/\\u0000/g, '');\n block_length = data.getInt32(offset2 + 4, true);\n offset2 += 16 + (pointer_size);\n if (code === 'DNA1') {\n // DNA found; This is the core of the __blender_file__ and contains all the structure for the various data types used in Blender.\n count = 0;\n let types = [],\n fields = [],\n names = [],\n lengths = [],\n name = '',\n curr_name = '';\n\n // skip SDNA and NAME identifiers\n offset2 += 8;\n\n // Number of structs.\n count = data.getInt32(offset2, true);\n offset2 += 4;\n\n curr_count = 0;\n\n // Build up list of names for structs\n while (curr_count < count) {\n curr_name = '';\n while (data.getInt8(offset2) !== 0) {\n curr_name += toString(_data, offset2, offset2 + 1);\n offset2++;\n }\n names.push(curr_name);\n offset2++;\n curr_count++;\n }\n\n // Adjust for alignment TYPE\n for (let j = 0; j < 8; j++) {\n if(seekCheck(data, offset2, 'TYPE')) break\n offset2++;\n }\n if(!seekCheck(data, offset2, 'TYPE')) {\n ERROR = 'Unexpected alignment error in SDNA parsing';\n break\n }\n\n offset2 += 4;\n\n // Number of struct types\n count = data.getUint32(offset2, true);\n offset2 += 4;\n curr_count = 0;\n\n // Build up list of types\n while (curr_count < count) {\n curr_name = '';\n while (data.getInt8(offset2) !== 0) {\n curr_name += toString(_data, offset2, offset2 + 1);\n offset2++;\n }\n types.push(curr_name);\n offset2++;\n curr_count++;\n }\n\n // Adjust for alignment\n for (let j = 0; j < 8; j++) {\n if(seekCheck(data, offset2, 'TLEN')) break\n offset2++;\n }\n if(!seekCheck(data, offset2, 'TLEN')) {\n ERROR = 'Unexpected alignment error in SDNA parsing';\n break\n }\n offset2 += 4;\n curr_count = 0;\n\n // Build up list of byte lengths for types\n while (curr_count < count) {\n lengths.push(data.getInt16(offset2, BIG_ENDIAN));\n offset2 += 2;\n curr_count++;\n }\n\n // Adjust for alignment\n for (let j = 0; j < 8; j++) {\n if(seekCheck(data, offset2, 'STRC')) break\n offset2++;\n }\n if(!seekCheck(data, offset2, 'STRC')) {\n ERROR = 'Unexpected alignment error in SDNA parsing';\n break\n }\n offset2 += 4;\n\n // Number of structures\n const structure_count = data.getInt32(offset2, BIG_ENDIAN);\n offset2 += 4;\n curr_count = 0;\n\n // Create constructor objects from list of SDNA structs\n while (curr_count < structure_count) {\n const struct_name = types[data.getInt16(offset2, BIG_ENDIAN)];\n offset2 += 2;\n const obj = [];\n count = data.getInt16(offset2, BIG_ENDIAN);\n offset2 += 2;\n curr_count2 = 0;\n struct_names.push(struct_name);\n\n // Fill an array with name, type, and length for each SDNA struct property\n while (curr_count2 < count) {\n\n // const n = names[data.getInt16(offset2 + 2, BIG_ENDIAN)]\n // if(obj.find((o, i)=>i%3===0 && o===n)){\n // debugger\n // }\n obj.push(names[data.getInt16(offset2 + 2, BIG_ENDIAN)], types[data.getInt16(offset2, BIG_ENDIAN)], lengths[data.getInt16(offset2, BIG_ENDIAN)]);\n offset2 += 4;\n curr_count2++;\n }\n\n // Create a SDNA constructor by passing [type,name,lenth] array as second argument\n current_SDNA_template.getSDNAStructureConstructor(struct_name, obj);\n curr_count++;\n }\n current_SDNA_template.SDNA_SET = true;\n current_SDNA_template.SDNA_NAMES = struct_names;\n break;\n }\n offset2 += block_length;\n }\n }\n\n // parse the rest of the data, starting back at the top.\n\n // TODO: turn into \"on-demand\" parsing.\n\n // https://github.com/fschutt/mystery-of-the-blend-backup\n while (true) {\n // todo fix cleanup\n // this is not the case, see blend-load-test.blend and Ruins_Assets.blend\n // if ((offset % 4) > 0) {\n // console.log('fix', offset, offset % 4)\n // debugger\n // offset = (4 - (offset % 4)) + offset;\n // }\n for (let j = 0; j < 8; j++) {\n if(data.getInt8(offset) === 0) {\n offset++\n continue\n }\n break\n }\n if(data.getInt8(offset) === 0) {\n debugger\n }\n\n data_offset = offset;\n\n if (offset + pointer_size + 12 >= data.byteLength) {\n ERROR = 'Unexpected end of file while parsing';\n break\n }\n\n sdna_index = data.getInt32(offset + pointer_size + 8, BIG_ENDIAN);\n // let code_uint = data.getUint32(offset, BIG_ENDIAN);\n const code_str = toString(_data, offset, offset + 4) // data.getUint32(offset, BIG_ENDIAN);\n\n offset2 = offset + 16 + (pointer_size);\n // console.log(code_str, code_str.length, data.getInt8(offset))\n\n const blockLength = data.getInt32(offset + 4, true);\n if (blockLength < 0 || offset + blockLength + 16 + pointer_size > data.byteLength) {\n ERROR = 'Invalid block length detected';\n break\n }\n // if(blockLength === 1) {\n // debugger\n // }\n\n // last_offset = offset\n\n offset += blockLength + 16 + (pointer_size);\n\n if (code_str === 'DNA1') {} // skip - already processed at this point\n else if (code_str === 'ENDB') break; // end of __blender_file__ found\n else if (code_str === 'TEST') { // snapshot\n const data_start = data_offset + pointer_size + 16;\n const width = data.getInt32(data_start, BIG_ENDIAN);\n const height = data.getInt32(data_start + 4, BIG_ENDIAN);\n if ((width * height > 0)) {\n const data_len = width * height * 4; // RGBA\n if(blockLength < data_len + 8) {\n ERROR = 'Invalid TEST block length detected';\n break;\n }\n const image_data = new Uint32Array(_data, data_start + 8, data_len >> 2);\n const image = {\n width: width,\n height: height,\n data: image_data,\n };\n FILE.thumbnail = image\n }\n }\n else {\n // Create a Blender object using a constructor template from current_SDNA_template\n const data_start = data_offset + pointer_size + 16;\n\n // Get a SDNA constructor by name;\n const constructor = current_SDNA_template.getSDNAStructureConstructor(current_SDNA_template.SDNA_NAMES[sdna_index]);\n\n const size = data.getInt32(data_offset + 4, BIG_ENDIAN);\n\n count = data.getInt32(data_offset + 12 + pointer_size, BIG_ENDIAN);\n\n if (count > 0 && constructor) {\n let obj = new constructor();\n\n const length = constructor.prototype._length;\n\n\n const address = FILE.getPointer(data_offset + 8);\n\n obj.address = address + '';\n\n obj.setData(address, data_start, data_start + size, FILE);\n\n if (count > 1) {\n let array = [];\n array.push(obj);\n for (let u = 1; u < count; u++) {\n obj = new constructor();\n obj.setData(address, data_start + length * u, data_start + (length * u) + length, FILE);\n array.push(obj);\n }\n FILE.memory_lookup[address] = array;\n } else {\n FILE.memory_lookup[address] = obj;\n }\n }\n }\n }\n }\n}\n\nworker = new worker_code();\n\nworker.postMessage = function (message, err) {\n return_object.onParseReady(message, err);\n};\n\nexport default return_object;\n\n// window.prettyPrintHex = function (view, offset = 0, length = view.byteLength - offset) {\n// const bytes = Array.from({ length }, (_, i) => view.getUint8(offset + i));\n// console.log(bytes.map((b, i) => (i % 16 === 0 ? '\\n' : '') + b.toString(16).padStart(2, '0')).join(' '));\n// console.log(bytes.map((b, i) => (i % 16 === 0 ? '\\n' : '') + String.fromCharCode(b)).join(''));\n// }\n","/* eslint-disable camelcase */\n/**\n * JS.Blend\n * Original Repo: https://github.com/acweathersby/js.blend\n * Slightly modified for three.js and js updates, minor refactor.\n * Object-creation part re-written for latest three.js and typescript\n * MIT License\n * Copyright (c) 2020 Anthony C, Weathersby\n * @license\n */\nimport parser from './parser/parser.js';\n\nexport async function parseBlend (buffer, name = '') {\n return new Promise((res, rej) => {\n parser.onParseReady = (file, error) => {\n // todo throw error if no objects?\n if (error) console.error(error)\n res(file)\n }\n parser.loadBlendFromArrayBuffer(buffer, name)\n })\n}\n","import {Object3D} from 'threepipe'\nimport {Ctx} from './ctx'\n\nexport function createCamera(object: any, ctx: Ctx): Object3D | undefined {\n const cdata = object.data\n // console.log(bakeGetters(object))\n if (!cdata) return undefined\n\n // Blender camera types: 0 = Perspective, 1 = Orthographic\n const type = cdata.type\n let camera: Object3D\n if (type === 0) {\n // Perspective\n // Convert lens (mm) to field of view (fov) in degrees (vfov)\n const sensorWidth = cdata.sensor_x || 36\n const sensorHeight = cdata.sensor_y || 24\n const lens = cdata.lens || 50\n const fov = 2 * Math.atan(sensorHeight / (2 * lens)) * (180 / Math.PI)\n\n camera = new ctx.PerspectiveCamera(\n fov,\n sensorWidth / sensorHeight,\n cdata.clipsta || 0.1,\n cdata.clipend || 1000\n )\n if ('shiftx' in cdata) camera.userData.shiftX = cdata.shiftx // todo?\n if ('shiftx' in cdata) camera.userData.shiftX = cdata.shiftx // todo?\n if ('shifty' in cdata) camera.userData.shiftY = cdata.shifty\n } else if (type === 1) {\n // Orthographic\n const scale = cdata.ortho_scale || 1\n const aspect = (cdata.sensor_x || 36) / (cdata.sensor_y || 24)\n const left = -scale * aspect / 2\n const right = scale * aspect / 2\n const top = scale / 2\n const bottom = -scale / 2\n camera = new ctx.OrthographicCamera(\n left, right, top, bottom,\n cdata.clipsta || 0.1,\n cdata.clipend || 1000\n )\n if ('shiftx' in cdata) camera.userData.shiftX = cdata.shiftx\n if ('shifty' in cdata) camera.userData.shiftY = cdata.shifty\n } else {\n // Unknown camera type\n camera = new ctx.PerspectiveCamera()\n console.warn('Unsupported camera type', type)\n }\n\n // const obj = new Object3D()\n // camera.rotation.x = -Math.PI / 2\n // obj.add(camera)\n\n return camera\n}\n","import {Light} from 'threepipe'\nimport {Ctx} from './ctx'\n\nconst blenderLightTypes = {\n point: 0,\n sun: 1,\n spot: 0,\n hemi: 0,\n area: 0,\n}\n\nexport function createLight(lamp: any, ctx: Ctx) {\n const ldata = lamp.data\n\n const position = [lamp.loc[0], lamp.loc[2], -lamp.loc[1]]\n\n const color = ldata.r * 255 << 16 | ldata.g * 255 << 8 | ldata.b * 255 << 0\n const intensity = ldata.energy\n const distance = 0\n\n let light: Light | undefined = undefined\n\n switch (ldata.type) {\n case blenderLightTypes.point:\n light = new ctx.PointLight(color, intensity, distance)\n light!.position.fromArray(position, 0)\n light!.castShadow = true\n break\n case blenderLightTypes.sun:\n light = new ctx.PointLight(color, intensity, distance)\n light!.position.fromArray(position, 0)\n light!.castShadow = true\n if (light!.shadow) {\n light!.shadow.mapSize.width = 1024\n light!.shadow.mapSize.height = 1024\n // @ts-expect-error ??/\n light!.shadow.camera.near = 0.01\n // @ts-expect-error ??/\n light!.shadow.camera.far = 500\n }\n break\n default:\n console.warn('Unsupported light type', ldata.type)\n }\n\n return light\n}\n","import {Ctx} from './ctx'\n\nfunction getLayer(layers: any, i: number) {\n if (!Array.isArray(layers)) return layers\n return layers[i]\n}\n// https://projects.blender.org/blender/blender/pulls/108015/files\n// https://projects.blender.org/blender/blender/commit/1b63a290c68636211b16c5e212a699e6b63031b9\n// https://developer.blender.org/docs/release_notes/4.0/python_api/#breaking-changes\n// https://developer.blender.org/docs/release_notes/4.0/#blend-files\n// https://projects.blender.org/blender/blender/commit/7966cd16d6dc4e66d01f7bd68a090107c1a7978c\n// https://projects.blender.org/blender/blender/issues/95967\n// https://projects.blender.org/blender/blender/pulls/106638\n\n// https://github.com/blender/blender/blob/55e2fd2929b7577e0785c128c8f8069efd990c07/source/blender/blenkernel/intern/mesh.cc#L413\nexport function createBufferGeometry(meshData: any, ctx: Ctx) {\n\n if (meshData.mpoly) return createBufferGeometryOld(meshData, ctx)\n const geometry = new ctx.BufferGeometry()\n geometry.name = meshData.aname || ''\n\n // console.log(bakeGetters(meshData))\n\n // https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L19\n\n let vertices\n let verticesData\n let indices\n let indicesData\n\n // Extract vertex positions from vdata layers\n if (meshData.vdata && meshData.vdata.layers && meshData.vdata.totlayer > 0) {\n for (let i = 0; i < meshData.vdata.totlayer; i++) {\n const layer = getLayer(meshData.vdata.layers, i)\n // if (layer.type === 0) {\n // https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L1575\n const data = layer.data || []\n // if (layer.name === 'position') { // type = 48 (custom vec3)\n if (data.length === meshData.totvert) {\n if (vertices && (layer.name !== 'position' || vertices.name === 'position')) {\n // console.warn('BlendLoader - multiple vertices, ignoring', layer)\n continue\n }\n vertices = layer\n verticesData = data\n } else {\n // console.log('unknown vdata', layer)\n }\n }\n }\n\n // Blender loops are indexes in three.js/blender. Called dots in gltf exporter\n\n // // Extract indexes from loop data layers\n if (meshData.ldata && meshData.ldata.layers) {\n for (let i = 0; i < meshData.ldata.totlayer; i++) {\n const layer = getLayer(meshData.ldata.layers, i)\n // if (layer.type === 0) {\n const data = layer.data || []\n // if (layer.name === '.corner_vert') { // type = 11\n if (data.length === meshData.totloop) { // type = 11\n if (indices && (layer.name !== '.corner_vert' || indices.name === '.corner_vert')) {\n // console.warn('BlendLoader - multiple indices, ignoring', layer)\n continue\n }\n indices = layer\n indicesData = data\n } else {\n // console.log('unknown ldata', layer)\n }\n }\n }\n // if (meshData.pdata && meshData.pdata.layers) {\n // for (let i = 0; i < meshData.pdata.totlayer; i++) {\n // const layer = getLayer(meshData.pdata.layers, i)\n // // if (layer.type === 0) {\n // // if (layer.name === '.corner_vert') { // type = 11\n // // } else {\n // console.log('unknown pdata', layer)\n // // }\n // }\n // }\n // if (meshData.fdata && meshData.fdata.layers) {\n // for (let i = 0; i < meshData.fdata.totlayer; i++) {\n // const layer = getLayer(meshData.fdata.layers, i)\n // // if (layer.type === 0) {\n // // if (layer.name === '.corner_vert') { // type = 11\n // // } else {\n // console.log('unknown fdata', layer)\n // // }\n // }\n // }\n // if (meshData.edata && meshData.edata.layers) {\n // for (let i = 0; i < meshData.edata.totlayer; i++) {\n // const layer = getLayer(meshData.edata.layers, i)\n // // if (layer.type === 0) {\n // // if (layer.name === '.corner_vert') { // type = 11\n // // } else {\n // console.log('unknown edata', layer)\n // // }\n // }\n // }\n\n let faceIndices: number[] = []\n if (meshData.poly_offset_indices && meshData.totpoly > 0) {\n faceIndices = [...new Int32Array(meshData.poly_offset_indices.__blender_file__.AB, meshData.poly_offset_indices.__data_address__, meshData.totpoly + 1)]\n }\n // if (faceSize !== 3 && faceSize !== 4 && !faceIndices.length) {\n // console.error('not supported polygons with ', faceSize, 'vertices')\n // return geometry\n // }\n\n if (verticesData && verticesData.length > 0) {\n const positions = new Float32Array(verticesData.length * 3)\n for (let j = 0; j < verticesData.length; j++) {\n const {x, y, z, co} = verticesData[j] || {}\n if (x !== undefined) {\n positions[j * 3] = x\n positions[j * 3 + 1] = z\n positions[j * 3 + 2] = -y\n } else if (co !== undefined) {\n positions[j * 3] = co[0]\n positions[j * 3 + 1] = co[2]\n positions[j * 3 + 2] = -co[1]\n } else {\n // console.log(bakeGetters(meshData))\n // const t = [...new Int32Array(vertex.__blender_file__.AB, vertex.__data_address__, 8)]\n // debugger\n console.error('BlendLoader - unknown vertex', verticesData[j])\n break\n }\n }\n geometry.setAttribute('position', new ctx.BufferAttribute(positions, 3))\n }\n\n if (indicesData && indicesData.length > 0 && verticesData?.length) {\n const faceSize = meshData.totloop / meshData.totpoly\n if (faceIndices.length > 0) {\n // Use face offset indices for variable-sized faces\n let totalTriangles = 0\n for (let i = 0; i < faceIndices.length - 1; i++) {\n const faceVertCount = faceIndices[i + 1] - faceIndices[i]\n totalTriangles += Math.max(0, faceVertCount - 2)\n }\n\n const indexes = new Uint32Array(totalTriangles * 3)\n let t = 0\n\n for (let i = 0; i < faceIndices.length - 1; i++) {\n const faceStart = faceIndices[i]\n const faceEnd = faceIndices[i + 1]\n const faceVertCount = faceEnd - faceStart\n\n if (faceVertCount >= 3) {\n // todo better Triangulate the face using fan triangulation\n const firstVert = indicesData[faceStart].i\n for (let k = 1; k < faceVertCount - 1; k++) {\n indexes[t++] = firstVert\n indexes[t++] = indicesData[faceStart + k].i\n indexes[t++] = indicesData[faceStart + k + 1].i\n }\n } else {\n // debugger\n }\n }\n\n // console.log(indexes)\n geometry.setIndex(new ctx.BufferAttribute(indexes, 1))\n } else if (faceSize === 3 || faceSize === 4) {\n // Fall back to uniform face size approach\n const isQuad = faceSize === 4\n const faceCount = indices.length / faceSize\n const indexes = new Uint32Array(faceCount * 3 * (isQuad ? 2 : 1))\n\n if (faceSize !== 3 && faceSize !== 4) return geometry\n for (let j = 0, t = 0; j < indices.length; j += faceSize) {\n const a = indices[j].i\n const b = indices[j + 1].i\n const c = indices[j + 2].i\n indexes[t++] = a\n indexes[t++] = b\n indexes[t++] = c\n\n if (isQuad) {\n const d = indices[j + 3].i\n indexes[t++] = a\n indexes[t++] = c\n indexes[t++] = d\n }\n }\n geometry.setIndex(new ctx.BufferAttribute(indexes, 1))\n }\n } else if (faceIndices) {\n console.error('BlendLoader - no indices data found, but face indices are present', faceIndices)\n }\n\n // console.log(geometry.attributes.position)\n // console.log(geometry.index)\n\n // compute stuff not present\n if (geometry.attributes.position && !geometry.attributes.normal)\n geometry.computeVertexNormals()\n\n // if (meshData.loc) { // maybe this is the bbox center?\n // geometry.translate(meshData.loc[0], meshData.loc[2], -meshData.loc[1])\n // }\n // if (meshData.size) { // maybe this is the bbox size?\n // geometry.scale(meshData.size[0], meshData.size[2], -meshData.size[1])\n // }\n\n return geometry\n}\n// // https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L19\n// // https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L1575\n// // todo __set_morph_locs_attribute\n// // todo __set_morph_tangent_attribute\n// // todo pdata(points), edata(edge), fdata(face? legacy?)\n// // meshData.eData = domain edge - https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L189\n// // todo normals, tangents\n\nexport function createBufferGeometryOld(mesh: any, ctx: Ctx) {\n const\n faces = Array.isArray(mesh.mpoly) ? mesh.mpoly as any[] : [mesh.mpoly],\n loops = mesh.mloop,\n uv = mesh.mloopuv,\n vertices = mesh.mvert\n\n const geometry = new ctx.BufferGeometry()\n\n if (!faces) return geometry\n\n const size = faces.reduce((acc, face) => acc + Math.floor(face.totloop * 3.0 /