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 #####
21 # Script copyright (C) 2006-2012, assimp team
22 # Script copyright (C) 2013 Blender Foundation
31 from struct
import unpack
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"))
50 return unpack(b
'<I', read(4))[0]
53 def read_uint64(read
):
54 return unpack(b
'<Q', read(8))[0]
58 return unpack(b
'B', read(1))[0]
61 def read_string_ubyte(read
):
62 size
= read_ubyte(read
)
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
)
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
:
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
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
)
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
:
168 if read(len(_HEAD_MAGIC
)) != _HEAD_MAGIC
:
171 return read_uint(read
)
174 def parse(fn
, use_namedtuple
=True):
177 with
open(fn
, 'rb') as f
:
181 if read(len(_HEAD_MAGIC
)) != _HEAD_MAGIC
:
182 raise IOError("Invalid header")
184 fbx_version
= read_uint(read
)
185 init_version(fbx_version
)
188 elem
= read_elem(read
, tell
, use_namedtuple
)
191 root_elems
.append(elem
)
193 args
= (b
'', [], bytearray(0), root_elems
)
194 return FBXElem(*args
) if use_namedtuple
else args
, fbx_version