/* eslint-disable camelcase, no-unused-vars, no-empty, no-constant-condition */ const DNA1 = 826363460; const ENDB = 1111772741; /* Note: Blender coordinates treat the Z axis as the vertical an Y as depth. */ // web worker not functional in this version let USE_WEBWORKER = false; let worker = null; // FR = new FileReader(), const return_object = { loadBlendFromArrayBuffer: function (array_buffer) { return_object.ready = false; if (USE_WEBWORKER) { worker.postMessage(array_buffer, array_buffer); } else { worker.onmessage({ data: array_buffer, }); } }, // loadBlendFromBlob: function (blob) { // FR.onload = function () { // return_object.loadBlendFromArrayBuffer(this.result); // }; // FR.readAsArrayBuffer(blob); // }, ready: true, onParseReady: function () {}, }; function worker_code () { 'use strict'; let data = null, _data = null, BIG_ENDIAN = false, pointer_size = 0, struct_names = [], offset = 0, working_blend_file = null, current_SDNA_template = null, templates = {}, finished_objects = [], FILE = null, ERROR = null, AB = null; const self = this; function parseFile (msg) { if (typeof msg.data == 'object') { // reset global variables AB = null; data = null; BIG_ENDIAN = false; pointer_size = 0; struct_names = []; offset = 0; working_blend_file = null; finished_objects = []; current_SDNA_template = null; // set data _data = msg.data; AB = _data.slice(); data = new DataView(_data); FILE = new BLENDER_FILE(AB); // start parsing readFile(); // export parsed data self.postMessage(FILE, ERROR); } } /* Export object for a parsed __blender_file__. */ var BLENDER_FILE = function (AB) { this.AB = AB; // this.double = new Float64Array(AB); this.byte = new Uint8Array(AB); this.dv = new DataView(AB); this.objects = {}; this.memory_lookup = {}, this.object_array = []; this.template = null; }; BLENDER_FILE.prototype = { addObject: function (obj) { this.object_array.push(obj); if (!this.objects[obj.blender_name]) this.objects[obj.blender_name] = []; this.objects[obj.blender_name].push(obj); }, getPointer: function (offset) { const pointerLow = this.dv.getUint32(offset, this.template.endianess); if (this.template.pointer_size > 4) { const pointerHigh = this.dv.getUint32(offset + 4, this.template.endianess); if (this.template.endianess) { return (pointerLow) + 'l|h' + pointerHigh; } else { return (pointerHigh) + 'h|l' + pointerLow; } } else { return pointerLow; } }, }; self.onmessage = parseFile; // this.onmessage = parseFile; /* These functions map offsets in the blender __blender_file__ to basic types (byte,short,int,float) through TypedArrays; This allows the underlying binary data to be changed. */ function float64Prop (offset, Blender_Array_Length, length) { return { get: function () { return (Blender_Array_Length > 1) ? new Float64Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : this.__blender_file__.dv.getFloat64(this.__data_address__ + offset, this.__blender_file__.template.endianess); }, set: function (float) { if (Blender_Array_Length > 1) {} else { this.__blender_file__.dv.setFloat64(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); } }, }; } function floatProp (offset, Blender_Array_Length, length) { return { get: function () { return (Blender_Array_Length > 1) ? new Float32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : this.__blender_file__.dv.getFloat32(this.__data_address__ + offset, this.__blender_file__.template.endianess); }, set: function (float) { if (Blender_Array_Length > 1) {} else { this.__blender_file__.dv.setFloat32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); } }, }; } function intProp (offset, Blender_Array_Length, length) { return { get: function () { return (Blender_Array_Length > 1) ? new Int32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : this.__blender_file__.dv.getInt32(this.__data_address__ + offset, this.__blender_file__.template.endianess); }, set: function (float) { if (Blender_Array_Length > 1) {} else { this.__blender_file__.dv.setInt32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); } }, }; } function uIntProp (offset, Blender_Array_Length, length) { return { get: function () { return (Blender_Array_Length > 1) ? new Uint32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : this.__blender_file__.dv.getUint32(this.__data_address__ + offset, this.__blender_file__.template.endianess); }, set: function (float) { if (Blender_Array_Length > 1) {} else { this.__blender_file__.dv.setUint32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); } }, }; } function shortProp (offset, Blender_Array_Length, length) { return { get: function () { return (Blender_Array_Length > 1) ? new Int16Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : this.__blender_file__.dv.getInt16(this.__data_address__ + offset, this.__blender_file__.template.endianess); }, set: function (float) { if (Blender_Array_Length > 1) {} else { this.__blender_file__.dv.setInt16(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); } }, }; } const uShortProp = (offset, Blender_Array_Length, length) => { return { get: function () { return (Blender_Array_Length > 1) ? new Uint16Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : this.__blender_file__.dv.getUint16(this.__data_address__ + offset, this.__blender_file__.template.endianess); }, set: function (float) { if (Blender_Array_Length > 1) { } else { this.__blender_file__.dv.setUint16(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); } }, }; }; function charProp (offset, Blender_Array_Length, length) { return { get: function () { if (Blender_Array_Length > 1) { let start = this.__data_address__ + offset; let end = start; let buffer_guard = 0; while (this.__blender_file__.byte[end] != 0 && buffer_guard++ < length) end++; return toString(this.__blender_file__.AB, start, end); } return this.__blender_file__.byte[(this.__data_address__ + offset)]; }, set: function (byte) { if (Blender_Array_Length > 1) { const string = byte + ''; let i = 0; const l = string.length; while (i < length) { if (i < l) { this.__blender_file__.byte[(this.__data_address__ + offset + i)] = string.charCodeAt(i) | 0; } else { this.__blender_file__.byte[(this.__data_address__ + offset + i)] = 0; } i++; } } else { this.__blender_file__.byte[(this.__data_address__ + offset)] = byte | 0; } }, }; } function pointerProp2 (offset) { return { get: function () { let pointer = this.__blender_file__.getPointer(this.__data_address__ + offset, this.__blender_file__); const link = this.__blender_file__.memory_lookup[pointer]; const results = []; if (link) { const address = link.__data_address__; let j = 0; while (true) { pointer = this.__blender_file__.getPointer(address + j * 8, this.__blender_file__); let obj = this.__blender_file__.memory_lookup[pointer]; if (!obj) break; results.push(obj); j++; } } return results; }, set: function () {}, }; } function pointerProp (offset, Blender_Array_Length, length) { return { get: function () { if (Blender_Array_Length > 1) { let array = []; let j = 0; let off = offset; while (j < Blender_Array_Length) { let pointer = this.__blender_file__.getPointer(this.__data_address__ + off, this.__blender_file__); array.push(this.__blender_file__.memory_lookup[pointer]); off += length; j++; } return array; } else { let pointer = this.__blender_file__.getPointer(this.__data_address__ + offset, this.__blender_file__); return this.__blender_file__.memory_lookup[pointer]; } }, set: function () {}, }; } function compileProp (obj, name, type, offset, array_size, IS_POINTER, pointer_size, length) { if (!IS_POINTER) { switch (type) { case 'double': Object.defineProperty(obj, name, float64Prop(offset, array_size, length >> 3)); break; case 'float': Object.defineProperty(obj, name, floatProp(offset, array_size, length >> 2)); break; case 'int': Object.defineProperty(obj, name, intProp(offset, array_size, length >> 2)); break; case 'short': case 'ushort': Object.defineProperty(obj, name, shortProp(offset, array_size, length >> 1)); break; case 'char': case 'uchar': Object.defineProperty(obj, name, charProp(offset, array_size, length)); break; default: // compile list to obj[name] = {}; obj.__list__.push(name, type, length, offset, array_size, IS_POINTER); } obj._length += length; offset += length; } else { Object.defineProperty(obj, name, pointerProp(offset, array_size, pointer_size)); offset += pointer_size * array_size; } return offset; } // Store final DNA structs const MASTER_SDNA_SCHEMA = function (version) { this.version = version; this.SDNA_SET = false; this.byte_size = 0; this.struct_index = 0; this.structs = {}; this.SDNA = {}; this.endianess = false; }; MASTER_SDNA_SCHEMA.prototype = { getSDNAStructureConstructor: function (name, struct) { if (struct) { const blen_struct = Function('function ' + name + '(){}; return ' + name)(); blen_struct.prototype = new BLENDER_STRUCTURE(); blen_struct.prototype.blender_name = name; blen_struct.prototype.__pointers = []; blen_struct.prototype.__list__ = []; let offset = 0; // Create properties of struct for (let i = 0; i < struct.length; i += 3) { let _name = struct[i]; const n = _name, type = struct[i + 1]; let length = struct[i + 2], array_length = 0, match = null, Blender_Array_Length = 1, Suparray_match = 1, PointerToArray = false, Pointer_Match = 0; const DNA = this.SDNA[name] = { constructor: blen_struct, }; let original_name = _name; // mini type parser if ((match = _name.match(/(\*?)(\*?)(\w+)(\[(\w*)\])?(\[(\w*)\])?/))) { // base name _name = match[3]; // pointer type if (match[1]) { Pointer_Match = 10; blen_struct.prototype.__pointers.push(_name); } if (match[2]) { PointerToArray = true; } // arrays if (match[4]) { if (match[6]) { Suparray_match = parseInt(match[5]); Blender_Array_Length = parseInt(match[7]); } else { Blender_Array_Length = parseInt(match[5]); } } array_length = Blender_Array_Length * length; length = array_length * Suparray_match; } DNA[n] = { type: type, length: length, isArray: (Blender_Array_Length > 0), }; if (PointerToArray) { Object.defineProperty(blen_struct.prototype, _name, pointerProp2(offset)); offset += pointer_size; } else if (Suparray_match > 1) { const array_names = new Array(Suparray_match); // construct sub_array object that will return the correct structs for (let j = 0; j < Suparray_match; j++) { let array_name_ = `__${_name}[${j}]__`; array_names[j] = array_name_; offset = compileProp(blen_struct.prototype, array_name_, type, offset, Blender_Array_Length, Pointer_Match, pointer_size, array_length); } Object.defineProperty(blen_struct.prototype, _name, { get: (function (array_names) { return function () { const array = []; for (let i = 0; i < array_names.length; i++) { array.push(this[array_names[i]]); } return array; }; })(array_names), }); } else { offset = compileProp(blen_struct.prototype, _name, type, offset, Blender_Array_Length, Pointer_Match, pointer_size, length); } } return this.SDNA[name].constructor; } else { if (!this.SDNA[name]) { return null; } return this.SDNA[name].constructor; } }, }; var BLENDER_STRUCTURE = function () { this.__blender_file__ = null; this.__list__ = null; this.__super_array_list__ = null; this.blender_name = ''; this.__pointers = null; this.address = null; this.length = 0; this.__data_address__ = 0; this.blender_name = ''; this._length = 0; }; /* Returns a pre-constructed BLENDER_STRUCTURE or creates a new BLENDER_STRUCTURE to match the DNA struct type */ const pointer_function = (pointer) => () => { return FILE.memory_lookup[pointer]; }; function getPointer (offset) { const pointerLow = data.getUint32(offset, BIG_ENDIAN); if (pointer_size > 4) { const pointerHigh = data.getUint32(offset + 4, BIG_ENDIAN); if (BIG_ENDIAN) { return (pointerLow) + '' + pointerHigh; } else { return (pointerHigh) + '' + pointerLow; } } else { return pointerLow; } } BLENDER_STRUCTURE.prototype = { setData: function (pointer, _data_offset, data_block_length, BLENDER_FILE) { if (this.__list__ === null) return this; BLENDER_FILE.addObject(this); this.__blender_file__ = BLENDER_FILE; const struct = this.__list__; let j = 0, i = 0, obj, name = '', type, length, Blender_Array_Length, Pointer_Match, offset, constructor; this.__data_address__ = _data_offset; if (struct === null) return this; for (i = 0; i < struct.length; i += 6) { obj = null; name = struct[i]; type = struct[i + 1]; Blender_Array_Length = struct[i + 4]; Pointer_Match = struct[i + 5]; offset = this.__data_address__ + struct[i + 3]; if (Blender_Array_Length > 1) { this[name] = []; j = 0; while (j < Blender_Array_Length) { if (current_SDNA_template.getSDNAStructureConstructor(type)) { constructor = current_SDNA_template.getSDNAStructureConstructor(type); this[name].push((new constructor()).setData(0, offset, offset + length / Blender_Array_Length, BLENDER_FILE)); } else this[name].push(null); offset += length / Blender_Array_Length; j++; } } else { if (current_SDNA_template.getSDNAStructureConstructor(type)) { constructor = current_SDNA_template.getSDNAStructureConstructor(type); this[name] = (new constructor()).setData(0, offset, length + offset, BLENDER_FILE); } else this[name] = null; } } // break connection to configuration list this.__list__ = null; return this; }, get aname () { if (this.id) return this.id.name.slice(2); else return undefined; }, }; function toString (buffer, _in, _out) { return String.fromCharCode.apply(String, new Uint8Array(buffer, _in, _out - _in)); } // Begin parsing blender __blender_file__ function readFile () { let count = 0; let offset2 = 0; const root = 0; const i = 0; let data_offset = 0; let sdna_index = 0; let code = ''; let block_length = 0; let curr_count = 0; let curr_count2 = 0; FILE.memory_lookup = {}; struct_names = []; offset = 0; // Make sure we have a .blend __blender_file__. All blend files have the first 12bytes // set with BLENDER-v### in Utf-8 if (toString(_data, offset, 7) !== 'BLENDER') return ERROR = 'File supplied is not a .blend compatible Blender file.'; // otherwise get templete from save version. offset += 7; pointer_size = ((toString(_data, offset++, offset)) == '_') ? 4 : 8; BIG_ENDIAN = toString(_data, offset++, offset) !== 'V'; const version = toString(_data, offset, offset + 3); // create new master template if none exist for current blender version; if (!templates[version]) { templates[version] = new MASTER_SDNA_SCHEMA(version); } current_SDNA_template = templates[version]; FILE.template = current_SDNA_template; offset += 3; // Set SDNA structs if template hasn't been set. // Todo: Move the following block into the MASTER_SDNA_SCHEMA object. //* Like so:*/ current_SDNA_template.set(AB); if (!current_SDNA_template.SDNA_SET) { current_SDNA_template.endianess = BIG_ENDIAN; current_SDNA_template.pointer_size = pointer_size; // find DNA1 data block offset2 = offset; while (true) { sdna_index = data.getInt32(offset2 + pointer_size + 8, BIG_ENDIAN); // eslint-disable-next-line no-control-regex code = toString(_data, offset2, offset2 + 4).replace(/\u0000/g, ''); block_length = data.getInt32(offset2 + 4, true); offset2 += 16 + (pointer_size); if (code === 'DNA1') { // DNA found; This is the core of the __blender_file__ and contains all the structure for the various data types used in Blender. count = 0; let types = [], fields = [], names = [], lengths = [], name = '', curr_name = ''; // skip SDNA and NAME identifiers offset2 += 8; // Number of structs. count = data.getInt32(offset2, true); offset2 += 4; curr_count = 0; // Build up list of names for structs while (curr_count < count) { curr_name = ''; while (data.getInt8(offset2) !== 0) { curr_name += toString(_data, offset2, offset2 + 1); offset2++; } names.push(curr_name); offset2++; curr_count++; } // Adjust for 4byte alignment if ((offset2 % 4) > 0) offset2 = (4 - (offset2 % 4)) + offset2; offset2 += 4; // Number of struct types count = data.getInt32(offset2, true); offset2 += 4; curr_count = 0; // Build up list of types while (curr_count < count) { curr_name = ''; while (data.getInt8(offset2) !== 0) { curr_name += toString(_data, offset2, offset2 + 1); offset2++; } types.push(curr_name); offset2++; curr_count++; } // Adjust for 4byte alignment if ((offset2 % 4) > 0) offset2 = (4 - (offset2 % 4)) + offset2; offset2 += 4; curr_count = 0; // Build up list of byte lengths for types while (curr_count < count) { lengths.push(data.getInt16(offset2, BIG_ENDIAN)); offset2 += 2; curr_count++; } // Adjust for 4byte alignment if ((offset2 % 4) > 0) offset2 = (4 - (offset2 % 4)) + offset2; offset2 += 4; // Number of structures const structure_count = data.getInt32(offset2, BIG_ENDIAN); offset2 += 4; curr_count = 0; // Create constructor objects from list of SDNA structs while (curr_count < structure_count) { const struct_name = types[data.getInt16(offset2, BIG_ENDIAN)]; offset2 += 2; const obj = []; count = data.getInt16(offset2, BIG_ENDIAN); offset2 += 2; curr_count2 = 0; struct_names.push(struct_name); // Fill an array with name, type, and length for each SDNA struct property while (curr_count2 < count) { obj.push(names[data.getInt16(offset2 + 2, BIG_ENDIAN)], types[data.getInt16(offset2, BIG_ENDIAN)], lengths[data.getInt16(offset2, BIG_ENDIAN)]); offset2 += 4; curr_count2++; } // Create a SDNA constructor by passing [type,name,lenth] array as second argument current_SDNA_template.getSDNAStructureConstructor(struct_name, obj); curr_count++; } current_SDNA_template.SDNA_SET = true; current_SDNA_template.SDNA_NAMES = struct_names; break; } offset2 += block_length; } } // parse the rest of the data, starting back at the top. // TODO: turn into "on-demand" parsing. while (true) { if ((offset % 4) > 0) { offset = (4 - (offset % 4)) + offset; } data_offset = offset; sdna_index = data.getInt32(offset + pointer_size + 8, BIG_ENDIAN); let code_uint = data.getUint32(offset, BIG_ENDIAN); offset2 = offset + 16 + (pointer_size); offset += data.getInt32(offset + 4, true) + 16 + (pointer_size); if (code_uint === DNA1); // skip - already processed at this point else if (code_uint === ENDB) break; // end of __blender_file__ found else { // Create a Blender object using a constructor template from current_SDNA_template const data_start = data_offset + pointer_size + 16; // Get a SDNA constructor by name; const constructor = current_SDNA_template.getSDNAStructureConstructor(current_SDNA_template.SDNA_NAMES[sdna_index]); const size = data.getInt32(data_offset + 4, BIG_ENDIAN); count = data.getInt32(data_offset + 12 + pointer_size, BIG_ENDIAN); if (count > 0) { let obj = new constructor(); const length = constructor.prototype._length; const address = FILE.getPointer(data_offset + 8); obj.address = address + ''; obj.setData(address, data_start, data_start + size, FILE); if (count > 1) { let array = []; array.push(obj); for (let u = 1; u < count; u++) { obj = new constructor(); obj.setData(address, data_start + length * u, data_start + (length * u) + length, FILE); array.push(obj); } FILE.memory_lookup[address] = array; } else { FILE.memory_lookup[address] = obj; } } } } } } worker = new worker_code(); worker.postMessage = function (message) { return_object.onParseReady(message); }; export default return_object;