2 # ##### BEGIN GPL LICENSE BLOCK #####
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # ##### END GPL LICENSE BLOCK #####
22 # Script copyright (C) 2006-2012, assimp team
23 # Script copyright (C) 2013 Blender Foundation
31 This script will write a JSON file for each FBX argument given.
37 The JSON data is formatted into a list of nested lists of 4 items:
39 ``[id, [data, ...], "data_types", [subtree, ...]]``
41 Where each list may be empty, and the items in
42 the subtree are formatted the same way.
44 data_types is a string, aligned with data that spesifies a type
47 The types are as follows:
57 * 'f': - FLOAT32_ARRAY
59 * 'd': - FLOAT64_ARRAY
64 Note that key:value pairs aren't used since the id's are not
69 # ----------------------------------------------------------------------------
72 from struct
import unpack
76 # at the end of each nested block, there is a NUL record to indicate
77 # that the sub-scope exists (i.e. to distinguish between P: and P : {})
78 _BLOCK_SENTINEL_LENGTH
= ...
79 _BLOCK_SENTINEL_DATA
= ...
80 read_fbx_elem_uint
= ...
81 _IS_BIG_ENDIAN
= (__import__("sys").byteorder
!= 'little')
82 _HEAD_MAGIC
= b
'Kaydara FBX Binary\x20\x20\x00\x1a\x00'
83 from collections
import namedtuple
84 FBXElem
= namedtuple("FBXElem", ("id", "props", "props_type", "elems"))
89 return unpack(b
'<I', read(4))[0]
92 def read_uint64(read
):
93 return unpack(b
'<Q', read(8))[0]
97 return unpack(b
'B', read(1))[0]
100 def read_string_ubyte(read
):
101 size
= read_ubyte(read
)
106 def unpack_array(read
, array_type
, array_stride
, array_byteswap
):
107 length
= read_uint(read
)
108 encoding
= read_uint(read
)
109 comp_len
= read_uint(read
)
111 data
= read(comp_len
)
116 data
= zlib
.decompress(data
)
118 assert(length
* array_stride
== len(data
))
120 data_array
= array
.array(array_type
, data
)
121 if array_byteswap
and _IS_BIG_ENDIAN
:
122 data_array
.byteswap()
127 b
'Y'[0]: lambda read
: unpack(b
'<h', read(2))[0], # 16 bit int
128 b
'C'[0]: lambda read
: unpack(b
'?', read(1))[0], # 1 bit bool (yes/no)
129 b
'I'[0]: lambda read
: unpack(b
'<i', read(4))[0], # 32 bit int
130 b
'F'[0]: lambda read
: unpack(b
'<f', read(4))[0], # 32 bit float
131 b
'D'[0]: lambda read
: unpack(b
'<d', read(8))[0], # 64 bit float
132 b
'L'[0]: lambda read
: unpack(b
'<q', read(8))[0], # 64 bit int
133 b
'R'[0]: lambda read
: read(read_uint(read
)), # binary data
134 b
'S'[0]: lambda read
: read(read_uint(read
)), # string data
135 b
'f'[0]: lambda read
: unpack_array(read
, 'f', 4, False), # array (float)
136 b
'i'[0]: lambda read
: unpack_array(read
, 'i', 4, True), # array (int)
137 b
'd'[0]: lambda read
: unpack_array(read
, 'd', 8, False), # array (double)
138 b
'l'[0]: lambda read
: unpack_array(read
, 'q', 8, True), # array (long)
139 b
'b'[0]: lambda read
: unpack_array(read
, 'b', 1, False), # array (bool)
140 b
'c'[0]: lambda read
: unpack_array(read
, 'B', 1, False), # array (ubyte)
144 # FBX 7500 (aka FBX2016) introduces incompatible changes at binary level:
145 # * The NULL block marking end of nested stuff switches from 13 bytes long to 25 bytes long.
146 # * The FBX element metadata (end_offset, prop_count and prop_length) switch from uint32 to uint64.
147 def init_version(fbx_version
):
148 global _BLOCK_SENTINEL_LENGTH
, _BLOCK_SENTINEL_DATA
, read_fbx_elem_uint
150 assert(_BLOCK_SENTINEL_LENGTH
== ...)
151 assert(_BLOCK_SENTINEL_DATA
== ...)
153 if fbx_version
< 7500:
154 _BLOCK_SENTINEL_LENGTH
= 13
155 read_fbx_elem_uint
= read_uint
157 _BLOCK_SENTINEL_LENGTH
= 25
158 read_fbx_elem_uint
= read_uint64
159 _BLOCK_SENTINEL_DATA
= (b
'\0' * _BLOCK_SENTINEL_LENGTH
)
162 def read_elem(read
, tell
, use_namedtuple
):
163 # [0] the offset at which this block ends
164 # [1] the number of properties in the scope
165 # [2] the length of the property list
166 end_offset
= read_fbx_elem_uint(read
)
170 prop_count
= read_fbx_elem_uint(read
)
171 prop_length
= read_fbx_elem_uint(read
)
173 elem_id
= read_string_ubyte(read
) # elem name of the scope/key
174 elem_props_type
= bytearray(prop_count
) # elem property types
175 elem_props_data
= [None] * prop_count
# elem properties (if any)
176 elem_subtree
= [] # elem children (if any)
178 for i
in range(prop_count
):
179 data_type
= read(1)[0]
180 elem_props_data
[i
] = read_data_dict
[data_type
](read
)
181 elem_props_type
[i
] = data_type
183 if tell() < end_offset
:
184 while tell() < (end_offset
- _BLOCK_SENTINEL_LENGTH
):
185 elem_subtree
.append(read_elem(read
, tell
, use_namedtuple
))
187 if read(_BLOCK_SENTINEL_LENGTH
) != _BLOCK_SENTINEL_DATA
:
188 raise IOError("failed to read nested block sentinel, "
189 "expected all bytes to be 0")
191 if tell() != end_offset
:
192 raise IOError("scope length not reached, something is wrong")
194 args
= (elem_id
, elem_props_data
, elem_props_type
, elem_subtree
)
195 return FBXElem(*args
) if use_namedtuple
else args
198 def parse_version(fn
):
200 Return the FBX version,
201 if the file isn't a binary FBX return zero.
203 with
open(fn
, 'rb') as f
:
206 if read(len(_HEAD_MAGIC
)) != _HEAD_MAGIC
:
209 return read_uint(read
)
212 def parse(fn
, use_namedtuple
=True):
215 with
open(fn
, 'rb') as f
:
219 if read(len(_HEAD_MAGIC
)) != _HEAD_MAGIC
:
220 raise IOError("Invalid header")
222 fbx_version
= read_uint(read
)
223 init_version(fbx_version
)
226 elem
= read_elem(read
, tell
, use_namedtuple
)
229 root_elems
.append(elem
)
231 args
= (b
'', [], bytearray(0), root_elems
)
232 return FBXElem(*args
) if use_namedtuple
else args
, fbx_version
235 # ----------------------------------------------------------------------------
239 data_types
= type(array
)("data_types")
240 data_types
.__dict
__.update(
250 FLOAT32_ARRAY
= b
'f'[0],
251 INT32_ARRAY
= b
'i'[0],
252 FLOAT64_ARRAY
= b
'd'[0],
253 INT64_ARRAY
= b
'l'[0],
254 BOOL_ARRAY
= b
'b'[0],
255 BYTE_ARRAY
= b
'c'[0],
259 parse_bin
= type(array
)("parse_bin")
260 parse_bin
.__dict
__.update(
266 # ----------------------------------------------------------------------------
268 # from pyfbx import parse_bin, data_types
273 def fbx2json_property_as_string(prop
, prop_type
):
274 if prop_type
== data_types
.STRING
:
275 prop_str
= prop
.decode('utf-8')
276 prop_str
= prop_str
.replace('\x00\x01', '::')
277 return json
.dumps(prop_str
)
279 prop_py_type
= type(prop
)
280 if prop_py_type
== bytes
:
281 return json
.dumps(repr(prop
)[2:-1])
282 elif prop_py_type
== bool:
283 return json
.dumps(prop
)
284 elif prop_py_type
== array
.array
:
285 return repr(list(prop
))
290 def fbx2json_properties_as_string(fbx_elem
):
291 return ", ".join(fbx2json_property_as_string(*prop_item
)
292 for prop_item
in zip(fbx_elem
.props
,
293 fbx_elem
.props_type
))
296 def fbx2json_recurse(fw
, fbx_elem
, ident
, is_last
):
297 fbx_elem_id
= fbx_elem
.id.decode('utf-8')
298 fw('%s["%s", ' % (ident
, fbx_elem_id
))
299 fw('[%s], ' % fbx2json_properties_as_string(fbx_elem
))
300 fw('"%s", ' % (fbx_elem
.props_type
.decode('ascii')))
305 ident_sub
= ident
+ " "
306 for fbx_elem_sub
in fbx_elem
.elems
:
307 fbx2json_recurse(fw
, fbx_elem_sub
, ident_sub
,
308 fbx_elem_sub
is fbx_elem
.elems
[-1])
311 fw(']%s' % ('' if is_last
else ',\n'))
317 fn_json
= "%s.json" % os
.path
.splitext(fn
)[0]
318 print("Writing: %r " % fn_json
, end
="")
319 fbx_root_elem
, fbx_version
= parse(fn
, use_namedtuple
=True)
320 print("(Version %d) ..." % fbx_version
)
322 with
open(fn_json
, 'w', encoding
="ascii", errors
='xmlcharrefreplace') as f
:
326 for fbx_elem_sub
in fbx_root_elem
.elems
:
327 fbx2json_recurse(f
.write
, fbx_elem_sub
, ident_sub
,
328 fbx_elem_sub
is fbx_root_elem
.elems
[-1])
332 # ----------------------------------------------------------------------------
338 if "--help" in sys
.argv
:
342 for arg
in sys
.argv
[1:]:
346 print("Failed to convert %r, error:" % arg
)
349 traceback
.print_exc()
352 if __name__
== "__main__":