Sun position: Fix T80379 - Custom startup breaks the add-on
[blender-addons.git] / io_scene_fbx / parse_fbx.py
blob634c4d93f777f41f1d1d587e002da300c2e59096
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 # Script copyright (C) 2006-2012, assimp team
22 # Script copyright (C) 2013 Blender Foundation
24 __all__ = (
25 "parse",
26 "data_types",
27 "parse_version",
28 "FBXElem",
31 from struct import unpack
32 import array
33 import zlib
35 from . import data_types
37 # at the end of each nested block, there is a NUL record to indicate
38 # that the sub-scope exists (i.e. to distinguish between P: and P : {})
39 _BLOCK_SENTINEL_LENGTH = ...
40 _BLOCK_SENTINEL_DATA = ...
41 read_fbx_elem_uint = ...
42 _IS_BIG_ENDIAN = (__import__("sys").byteorder != 'little')
43 _HEAD_MAGIC = b'Kaydara FBX Binary\x20\x20\x00\x1a\x00'
44 from collections import namedtuple
45 FBXElem = namedtuple("FBXElem", ("id", "props", "props_type", "elems"))
46 del namedtuple
49 def read_uint(read):
50 return unpack(b'<I', read(4))[0]
53 def read_uint64(read):
54 return unpack(b'<Q', read(8))[0]
57 def read_ubyte(read):
58 return unpack(b'B', read(1))[0]
61 def read_string_ubyte(read):
62 size = read_ubyte(read)
63 data = read(size)
64 return data
67 def unpack_array(read, array_type, array_stride, array_byteswap):
68 length = read_uint(read)
69 encoding = read_uint(read)
70 comp_len = read_uint(read)
72 data = read(comp_len)
74 if encoding == 0:
75 pass
76 elif encoding == 1:
77 data = zlib.decompress(data)
79 assert(length * array_stride == len(data))
81 data_array = array.array(array_type, data)
82 if array_byteswap and _IS_BIG_ENDIAN:
83 data_array.byteswap()
84 return data_array
87 read_data_dict = {
88 b'Y'[0]: lambda read: unpack(b'<h', read(2))[0], # 16 bit int
89 b'C'[0]: lambda read: unpack(b'?', read(1))[0], # 1 bit bool (yes/no)
90 b'I'[0]: lambda read: unpack(b'<i', read(4))[0], # 32 bit int
91 b'F'[0]: lambda read: unpack(b'<f', read(4))[0], # 32 bit float
92 b'D'[0]: lambda read: unpack(b'<d', read(8))[0], # 64 bit float
93 b'L'[0]: lambda read: unpack(b'<q', read(8))[0], # 64 bit int
94 b'R'[0]: lambda read: read(read_uint(read)), # binary data
95 b'S'[0]: lambda read: read(read_uint(read)), # string data
96 b'f'[0]: lambda read: unpack_array(read, data_types.ARRAY_FLOAT32, 4, False), # array (float)
97 b'i'[0]: lambda read: unpack_array(read, data_types.ARRAY_INT32, 4, True), # array (int)
98 b'd'[0]: lambda read: unpack_array(read, data_types.ARRAY_FLOAT64, 8, False), # array (double)
99 b'l'[0]: lambda read: unpack_array(read, data_types.ARRAY_INT64, 8, True), # array (long)
100 b'b'[0]: lambda read: unpack_array(read, data_types.ARRAY_BOOL, 1, False), # array (bool)
101 b'c'[0]: lambda read: unpack_array(read, data_types.ARRAY_BYTE, 1, False), # array (ubyte)
105 # FBX 7500 (aka FBX2016) introduces incompatible changes at binary level:
106 # * The NULL block marking end of nested stuff switches from 13 bytes long to 25 bytes long.
107 # * The FBX element metadata (end_offset, prop_count and prop_length) switch from uint32 to uint64.
108 def init_version(fbx_version):
109 global _BLOCK_SENTINEL_LENGTH, _BLOCK_SENTINEL_DATA, read_fbx_elem_uint
111 _BLOCK_SENTINEL_LENGTH = ...
112 _BLOCK_SENTINEL_DATA = ...
113 read_fbx_elem_uint = ...
115 if fbx_version < 7500:
116 _BLOCK_SENTINEL_LENGTH = 13
117 read_fbx_elem_uint = read_uint
118 else:
119 _BLOCK_SENTINEL_LENGTH = 25
120 read_fbx_elem_uint = read_uint64
121 _BLOCK_SENTINEL_DATA = (b'\0' * _BLOCK_SENTINEL_LENGTH)
124 def read_elem(read, tell, use_namedtuple):
125 # [0] the offset at which this block ends
126 # [1] the number of properties in the scope
127 # [2] the length of the property list
128 end_offset = read_fbx_elem_uint(read)
129 if end_offset == 0:
130 return None
132 prop_count = read_fbx_elem_uint(read)
133 prop_length = read_fbx_elem_uint(read)
135 elem_id = read_string_ubyte(read) # elem name of the scope/key
136 elem_props_type = bytearray(prop_count) # elem property types
137 elem_props_data = [None] * prop_count # elem properties (if any)
138 elem_subtree = [] # elem children (if any)
140 for i in range(prop_count):
141 data_type = read(1)[0]
142 elem_props_data[i] = read_data_dict[data_type](read)
143 elem_props_type[i] = data_type
145 if tell() < end_offset:
146 while tell() < (end_offset - _BLOCK_SENTINEL_LENGTH):
147 elem_subtree.append(read_elem(read, tell, use_namedtuple))
149 if read(_BLOCK_SENTINEL_LENGTH) != _BLOCK_SENTINEL_DATA:
150 raise IOError("failed to read nested block sentinel, "
151 "expected all bytes to be 0")
153 if tell() != end_offset:
154 raise IOError("scope length not reached, something is wrong")
156 args = (elem_id, elem_props_data, elem_props_type, elem_subtree)
157 return FBXElem(*args) if use_namedtuple else args
160 def parse_version(fn):
162 Return the FBX version,
163 if the file isn't a binary FBX return zero.
165 with open(fn, 'rb') as f:
166 read = f.read
168 if read(len(_HEAD_MAGIC)) != _HEAD_MAGIC:
169 return 0
171 return read_uint(read)
174 def parse(fn, use_namedtuple=True):
175 root_elems = []
177 with open(fn, 'rb') as f:
178 read = f.read
179 tell = f.tell
181 if read(len(_HEAD_MAGIC)) != _HEAD_MAGIC:
182 raise IOError("Invalid header")
184 fbx_version = read_uint(read)
185 init_version(fbx_version)
187 while True:
188 elem = read_elem(read, tell, use_namedtuple)
189 if elem is None:
190 break
191 root_elems.append(elem)
193 args = (b'', [], bytearray(0), root_elems)
194 return FBXElem(*args) if use_namedtuple else args, fbx_version