Newer
Older
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Script copyright (C) Campbell Barton, Bastien Montagne
import array
import datetime
import math
import os
import time
import collections
from collections import namedtuple, OrderedDict
from collections.abc import Iterable
import itertools
from itertools import zip_longest, chain
import bpy
import bpy_extras
from bpy.types import Object, Bone, PoseBone
from mathutils import Vector, Matrix
from . import encode_bin, data_types
# "Constants"
FBX_VERSION = 7400
FBX_HEADER_VERSION = 1003
FBX_SCENEINFO_VERSION = 100
FBX_TEMPLATES_VERSION = 100
FBX_MODELS_VERSION = 232
FBX_GEOMETRY_VERSION = 124
# Revert back normals to 101 (simple 3D values) for now, 102 (4D + weights) seems not well supported by most apps
# currently, apart from some AD products.
FBX_GEOMETRY_NORMAL_VERSION = 101
FBX_GEOMETRY_BINORMAL_VERSION = 101
FBX_GEOMETRY_TANGENT_VERSION = 101
FBX_GEOMETRY_SMOOTHING_VERSION = 102
FBX_GEOMETRY_VCOLOR_VERSION = 101
FBX_GEOMETRY_UV_VERSION = 101
FBX_GEOMETRY_MATERIAL_VERSION = 101
FBX_GEOMETRY_LAYER_VERSION = 100
FBX_POSE_BIND_VERSION = 100
FBX_DEFORMER_SKIN_VERSION = 101
FBX_DEFORMER_CLUSTER_VERSION = 100
FBX_MATERIAL_VERSION = 102
FBX_TEXTURE_VERSION = 202
FBX_NAME_CLASS_SEP = b"\x00\x01"
FBX_KTIME = 46186158000 # This is the number of "ktimes" in one second (yep, precision over the nanosecond...)
MAT_CONVERT_LAMP = Matrix.Rotation(math.pi / 2.0, 4, 'X') # Blender is -Z, FBX is -Y.
MAT_CONVERT_CAMERA = Matrix.Rotation(math.pi / 2.0, 4, 'Y') # Blender is -Z, FBX is +X.
#MAT_CONVERT_BONE = Matrix.Rotation(math.pi / -2.0, 4, 'X') # Blender is +Y, FBX is +Z.
MAT_CONVERT_BONE = Matrix()
Bastien Montagne
committed
BLENDER_OTHER_OBJECT_TYPES = {'CURVE', 'SURFACE', 'FONT', 'META'}
BLENDER_OBJECT_TYPES_MESHLIKE = {'MESH'} | BLENDER_OTHER_OBJECT_TYPES
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# Lamps.
FBX_LIGHT_TYPES = {
'POINT': 0, # Point.
'SUN': 1, # Directional.
'SPOT': 2, # Spot.
'HEMI': 1, # Directional.
'AREA': 3, # Area.
}
FBX_LIGHT_DECAY_TYPES = {
'CONSTANT': 0, # None.
'INVERSE_LINEAR': 1, # Linear.
'INVERSE_SQUARE': 2, # Quadratic.
'CUSTOM_CURVE': 2, # Quadratic.
'LINEAR_QUADRATIC_WEIGHTED': 2, # Quadratic.
}
##### Misc utilities #####
# Note: this could be in a utility (math.units e.g.)...
UNITS = {
"meter": 1.0, # Ref unit!
"kilometer": 0.001,
"millimeter": 1000.0,
"foot": 1.0 / 0.3048,
"inch": 1.0 / 0.0254,
"turn": 1.0, # Ref unit!
"degree": 360.0,
"radian": math.pi * 2.0,
"second": 1.0, # Ref unit!
"ktime": FBX_KTIME,
}
def units_convert(val, u_from, u_to):
"""Convert value."""
conv = UNITS[u_to] / UNITS[u_from]
return val * conv
def units_convert_iter(it, u_from, u_to):
"""Convert value."""
conv = UNITS[u_to] / UNITS[u_from]
return (v * conv for v in it)
def matrix_to_array(mat):
"""Concatenate matrix's columns into a single, flat tuple"""
# blender matrix is row major, fbx is col major so transpose on write
return tuple(f for v in mat.transposed() for f in v)
Bastien Montagne
committed
def similar_values(v1, v2, e=1e-6):
"""Return True if v1 and v2 are nearly the same."""
Bastien Montagne
committed
if v1 == v2:
return True
return ((abs(v1 - v2) / max(abs(v1), abs(v2))) <= e)
Bastien Montagne
committed
RIGHT_HAND_AXES = {
# Up, Front -> FBX values (tuples of (axis, sign), Up, Front, Coord).
# Note: Since we always stay in right-handed system, third coord sign is always positive!
('X', 'Y'): ((0, 1), (1, -1), (2, 1)),
('X', '-Y'): ((0, 1), (1, 1), (2, 1)),
('X', 'Z'): ((0, 1), (2, -1), (1, 1)),
('X', '-Z'): ((0, 1), (2, 1), (1, 1)),
('-X', 'Y'): ((0, -1), (1, -1), (2, 1)),
('-X', '-Y'): ((0, -1), (1, 1), (2, 1)),
('-X', 'Z'): ((0, -1), (2, -1), (1, 1)),
('-X', '-Z'): ((0, -1), (2, 1), (1, 1)),
('Y', 'X'): ((1, 1), (0, -1), (2, 1)),
('Y', '-X'): ((1, 1), (0, 1), (2, 1)),
('Y', 'Z'): ((1, 1), (2, -1), (0, 1)),
('Y', '-Z'): ((1, 1), (2, 1), (0, 1)),
('-Y', 'X'): ((1, -1), (0, -1), (2, 1)),
('-Y', '-X'): ((1, -1), (0, 1), (2, 1)),
('-Y', 'Z'): ((1, -1), (2, -1), (0, 1)),
('-Y', '-Z'): ((1, -1), (2, 1), (0, 1)),
('Z', 'X'): ((2, 1), (0, -1), (1, 1)),
('Z', '-X'): ((2, 1), (0, 1), (1, 1)),
('Z', 'Y'): ((2, 1), (1, -1), (0, 1)), # Blender system!
('Z', '-Y'): ((2, 1), (1, 1), (0, 1)),
('-Z', 'X'): ((2, -1), (0, -1), (1, 1)),
('-Z', '-X'): ((2, -1), (0, 1), (1, 1)),
('-Z', 'Y'): ((2, -1), (1, -1), (0, 1)),
('-Z', '-Y'): ((2, -1), (1, 1), (0, 1)),
Bastien Montagne
committed
FBX_FRAMERATES = (
(-1.0, 14), # Custom framerate.
(120.0, 1),
(100.0, 2),
(60.0, 3),
(50.0, 4),
(48.0, 5),
(30.0, 6), # BW NTSC.
(30.0 / 1.001, 9), # Color NTSC.
(25.0, 10),
(24.0, 11),
(24.0 / 1.001, 13),
(96.0, 15),
(72.0, 16),
(60.0 / 1.001, 17),
)
##### UIDs code. #####
# ID class (mere int).
class UID(int):
pass
# UIDs storage.
_keys_to_uids = {}
_uids_to_keys = {}
def _key_to_uid(uids, key):
# TODO: Check this is robust enough for our needs!
# Note: We assume we have already checked the related key wasn't yet in _keys_to_uids!
# As int64 is signed in FBX, we keep uids below 2**63...
if isinstance(key, int) and 0 <= key < 2**63:
# We can use value directly as id!
uid = key
else:
uid = hash(key)
if uid < 0:
uid = -uid
if uid >= 2**63:
uid //= 2
Bastien Montagne
committed
# Try to make our uid shorter!
if uid > int(1e9):
t_uid = uid % int(1e9)
if t_uid not in uids:
uid = t_uid
# Make sure our uid *is* unique.
if uid in uids:
inc = 1 if uid < 2**62 else -1
while uid in uids:
uid += inc
if 0 > uid >= 2**63:
# Note that this is more that unlikely, but does not harm anyway...
raise ValueError("Unable to generate an UID for key {}".format(key))
return UID(uid)
def get_fbxuid_from_key(key):
"""
Return an UID for given key, which is assumed hasable.
"""
uid = _keys_to_uids.get(key, None)
if uid is None:
uid = _key_to_uid(_uids_to_keys, key)
_keys_to_uids[key] = uid
_uids_to_keys[uid] = key
return uid
def get_key_from_fbxuid(uid):
"""
Return the key which generated this uid.
"""
assert(uid.__class__ == UID)
return _uids_to_keys.get(uid, None)
# Blender-specific key generators
def get_blenderID_key(bid):
if isinstance(bid, Iterable):
return "|".join("B" + e.rna_type.name + "#" + e.name for e in bid)
return "B" + bid.rna_type.name + "#" + bid.name
def get_blenderID_name(bid):
if isinstance(bid, Iterable):
return "|".join(e.name for e in bid)
else:
return bid.name
def get_blender_empty_key(obj):
"""Return bone's keys (Model and NodeAttribute)."""
return "|".join((get_blenderID_key(obj), "Empty"))
def get_blender_bone_key(armature, bone):
"""Return bone's keys (Model and NodeAttribute)."""
key = "|".join((get_blenderID_key(armature), get_blenderID_key(bone)))
return key, key + "_Data"
def get_blender_armature_bindpose_key(armature, mesh):
"""Return armature's bindpose key."""
return "|".join((get_blenderID_key(armature), get_blenderID_key(mesh), "BindPose"))
def get_blender_armature_skin_key(armature, mesh):
"""Return armature's skin key."""
return "|".join((get_blenderID_key(armature), get_blenderID_key(mesh), "DeformerSkin"))
def get_blender_bone_cluster_key(armature, mesh, bone):
"""Return bone's cluster key."""
return "|".join((get_blenderID_key(armature), get_blenderID_key(mesh),
get_blenderID_key(bone), "SubDeformerCluster"))
def get_blender_anim_id_base(scene, ref_id):
if ref_id is not None:
return get_blenderID_key(scene) + "|" + get_blenderID_key(ref_id)
Bastien Montagne
committed
else:
return get_blenderID_key(scene)
def get_blender_anim_stack_key(scene, ref_id):
"""Return single anim stack key."""
return get_blender_anim_id_base(scene, ref_id) + "|AnimStack"
def get_blender_anim_layer_key(scene, ref_id):
"""Return ID's anim layer key."""
return get_blender_anim_id_base(scene, ref_id) + "|AnimLayer"
def get_blender_anim_curve_node_key(scene, ref_id, ID, fbx_prop_name):
"""Return (stack/layer, ID, fbxprop) curve node key."""
return "|".join((get_blender_anim_id_base(scene, ref_id), get_blenderID_key(ID), fbx_prop_name, "AnimCurveNode"))
def get_blender_anim_curve_key(scene, ref_id, ID, fbx_prop_name, fbx_prop_item_name):
"""Return (stack/layer, ID, fbxprop, item) curve key."""
return "|".join((get_blender_anim_id_base(scene, ref_id), get_blenderID_key(ID), fbx_prop_name,
fbx_prop_item_name, "AnimCurve"))
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
##### Element generators. #####
# Note: elem may be None, in this case the element is not added to any parent.
def elem_empty(elem, name):
sub_elem = encode_bin.FBXElem(name)
if elem is not None:
elem.elems.append(sub_elem)
return sub_elem
def elem_properties(elem):
return elem_empty(elem, b"Properties70")
def _elem_data_single(elem, name, value, func_name):
sub_elem = elem_empty(elem, name)
getattr(sub_elem, func_name)(value)
return sub_elem
def _elem_data_vec(elem, name, value, func_name):
sub_elem = elem_empty(elem, name)
func = getattr(sub_elem, func_name)
for v in value:
func(v)
return sub_elem
def elem_data_single_bool(elem, name, value):
return _elem_data_single(elem, name, value, "add_bool")
def elem_data_single_int16(elem, name, value):
return _elem_data_single(elem, name, value, "add_int16")
def elem_data_single_int32(elem, name, value):
return _elem_data_single(elem, name, value, "add_int32")
def elem_data_single_int64(elem, name, value):
return _elem_data_single(elem, name, value, "add_int64")
def elem_data_single_float32(elem, name, value):
return _elem_data_single(elem, name, value, "add_float32")
def elem_data_single_float64(elem, name, value):
return _elem_data_single(elem, name, value, "add_float64")
def elem_data_single_bytes(elem, name, value):
return _elem_data_single(elem, name, value, "add_bytes")
def elem_data_single_string(elem, name, value):
return _elem_data_single(elem, name, value, "add_string")
def elem_data_single_string_unicode(elem, name, value):
return _elem_data_single(elem, name, value, "add_string_unicode")
def elem_data_single_bool_array(elem, name, value):
return _elem_data_single(elem, name, value, "add_bool_array")
def elem_data_single_int32_array(elem, name, value):
return _elem_data_single(elem, name, value, "add_int32_array")
def elem_data_single_int64_array(elem, name, value):
return _elem_data_single(elem, name, value, "add_int64_array")
def elem_data_single_float32_array(elem, name, value):
return _elem_data_single(elem, name, value, "add_float32_array")
def elem_data_single_float64_array(elem, name, value):
return _elem_data_single(elem, name, value, "add_float64_array")
def elem_data_single_byte_array(elem, name, value):
return _elem_data_single(elem, name, value, "add_byte_array")
def elem_data_vec_float64(elem, name, value):
return _elem_data_vec(elem, name, value, "add_float64")
##### Generators for standard FBXProperties70 properties. #####
# Properties definitions, format: (b"type_1", b"label(???)", "name_set_value_1", "name_set_value_2", ...)
# XXX Looks like there can be various variations of formats here... Will have to be checked ultimately!
# Also, those "custom" types like 'FieldOfView' or 'Lcl Translation' are pure nonsense,
# these are just Vector3D ultimately... *sigh* (again).
FBX_PROPERTIES_DEFINITIONS = {
# Generic types.
"p_bool": (b"bool", b"", "add_int32"), # Yes, int32 for a bool (and they do have a core bool type)!!!
"p_integer": (b"int", b"Integer", "add_int32"),
"p_ulonglong": (b"ULongLong", b"", "add_int64"),
Bastien Montagne
committed
"p_double": (b"double", b"Number", "add_float64"), # Non-animatable?
"p_number": (b"Number", b"", "add_float64"), # Animatable-only?
"p_enum": (b"enum", b"", "add_int32"),
Bastien Montagne
committed
"p_vector_3d": (b"Vector3D", b"Vector", "add_float64", "add_float64", "add_float64"), # Non-animatable?
"p_vector": (b"Vector", b"", "add_float64", "add_float64", "add_float64"), # Animatable-only?
"p_color_rgb": (b"ColorRGB", b"Color", "add_float64", "add_float64", "add_float64"), # Non-animatable?
"p_color": (b"Color", b"", "add_float64", "add_float64", "add_float64"), # Animatable-only?
"p_string": (b"KString", b"", "add_string_unicode"),
"p_string_url": (b"KString", b"Url", "add_string_unicode"),
"p_timestamp": (b"KTime", b"Time", "add_int64"),
"p_datetime": (b"DateTime", b"", "add_string_unicode"),
# Special types.
"p_object": (b"object", b""), # XXX Check this! No value for this prop??? Would really like to know how it works!
"p_compound": (b"Compound", b""),
# Specific types (sic).
## Objects (Models).
"p_lcl_translation": (b"Lcl Translation", b"", "add_float64", "add_float64", "add_float64"),
"p_lcl_rotation": (b"Lcl Rotation", b"", "add_float64", "add_float64", "add_float64"),
"p_lcl_scaling": (b"Lcl Scaling", b"", "add_float64", "add_float64", "add_float64"),
"p_visibility": (b"Visibility", b"", "add_float64"),
"p_visibility_inheritance": (b"Visibility Inheritance", b"", "add_int32"),
## Cameras!!!
"p_roll": (b"Roll", b"", "add_float64"),
"p_opticalcenterx": (b"OpticalCenterX", b"", "add_float64"),
"p_opticalcentery": (b"OpticalCenterY", b"", "add_float64"),
"p_fov": (b"FieldOfView", b"", "add_float64"),
"p_fov_x": (b"FieldOfViewX", b"", "add_float64"),
"p_fov_y": (b"FieldOfViewY", b"", "add_float64"),
def _elem_props_set(elem, ptype, name, value, flags):
p = elem_data_single_string(elem, b"P", name)
p.add_string(flags)
if len(ptype) == 3:
getattr(p, ptype[2])(value)
elif len(ptype) > 3:
# We assume value is iterable, else it's a bug!
for callback, val in zip(ptype[2:], value):
getattr(p, callback)(val)
def _elem_props_flags(animatable, custom):
if animatable and custom:
return b"AU"
elif animatable:
return b"A"
elif custom:
return b"U"
return b""
def elem_props_set(elem, ptype, name, value=None, animatable=False, custom=False):
ptype = FBX_PROPERTIES_DEFINITIONS[ptype]
_elem_props_set(elem, ptype, name, value, _elem_props_flags(animatable, custom))
def elem_props_compound(elem, cmpd_name, custom=False):
def _setter(ptype, name, value, animatable=False, custom=False):
name = cmpd_name + b"|" + name
elem_props_set(elem, ptype, name, value, animatable=animatable, custom=custom)
elem_props_set(elem, "p_compound", cmpd_name, custom=custom)
Bastien Montagne
committed
def elem_props_template_init(templates, template_type):
"""
Init a writing template of given type, for *one* element's properties.
"""
ret = None
if template_type in templates:
tmpl = templates[template_type]
written = tmpl.written[0]
props = tmpl.properties
ret = OrderedDict((name, [val, ptype, anim, written]) for name, (val, ptype, anim) in props.items())
return ret or OrderedDict()
def elem_props_template_set(template, elem, ptype_name, name, value, animatable=False):
"""
Only add a prop if the same value is not already defined in given template.
Note it is important to not give iterators as value, here!
"""
ptype = FBX_PROPERTIES_DEFINITIONS[ptype_name]
Bastien Montagne
committed
if len(ptype) > 3:
value = tuple(value)
tmpl_val, tmpl_ptype, tmpl_animatable, tmpl_written = template.get(name, (None, None, False, False))
# Note animatable flag from template takes precedence over given one, if applicable.
if tmpl_ptype is not None:
Bastien Montagne
committed
if (tmpl_written and
((len(ptype) == 3 and (tmpl_val, tmpl_ptype) == (value, ptype_name)) or
(len(ptype) > 3 and (tuple(tmpl_val), tmpl_ptype) == (value, ptype_name)))):
return # Already in template and same value.
_elem_props_set(elem, ptype, name, value, _elem_props_flags(tmpl_animatable, False))
Bastien Montagne
committed
template[name][3] = True
else:
_elem_props_set(elem, ptype, name, value, _elem_props_flags(animatable, False))
Bastien Montagne
committed
def elem_props_template_finalize(template, elem):
"""
Finalize one element's template/props.
Issue is, some templates might be "needed" by different types (e.g. NodeAttribute is for lights, cameras, etc.),
but values for only *one* subtype can be written as template. So we have to be sure we write those for ths other
subtypes in each and every elements, if they are not overriden by that element.
Yes, hairy, FBX that is to say. When they could easily support several subtypes per template... :(
"""
for name, (value, ptype_name, animatable, written) in template.items():
if written:
continue
ptype = FBX_PROPERTIES_DEFINITIONS[ptype_name]
_elem_props_set(elem, ptype, name, value, _elem_props_flags(animatable, False))
##### Generators for connection elements. #####
def elem_connection(elem, c_type, uid_src, uid_dst, prop_dst=None):
e = elem_data_single_string(elem, b"C", c_type)
e.add_int64(uid_src)
e.add_int64(uid_dst)
if prop_dst is not None:
e.add_string(prop_dst)
##### Templates #####
# TODO: check all those "default" values, they should match Blender's default as much as possible, I guess?
Bastien Montagne
committed
FBXTemplate = namedtuple("FBXTemplate", ("type_name", "prop_type_name", "properties", "nbr_users", "written"))
def fbx_templates_generate(root, fbx_templates):
# We may have to gather different templates in the same node (e.g. NodeAttribute template gathers properties
# for Lights, Cameras, LibNodes, etc.).
ref_templates = {(tmpl.type_name, tmpl.prop_type_name): tmpl for tmpl in fbx_templates.values()}
Bastien Montagne
committed
templates = OrderedDict()
Bastien Montagne
committed
for type_name, prop_type_name, properties, nbr_users, _written in fbx_templates.values():
if type_name not in templates:
Bastien Montagne
committed
templates[type_name] = [OrderedDict(((prop_type_name, (properties, nbr_users)),)), nbr_users]
Bastien Montagne
committed
templates[type_name][0][prop_type_name] = (properties, nbr_users)
templates[type_name][1] += nbr_users
for type_name, (subprops, nbr_users) in templates.items():
template = elem_data_single_string(root, b"ObjectType", type_name)
elem_data_single_int32(template, b"Count", nbr_users)
Bastien Montagne
committed
if len(subprops) == 1:
prop_type_name, (properties, _nbr_sub_type_users) = next(iter(subprops.items()))
subprops = (prop_type_name, properties)
ref_templates[(type_name, prop_type_name)].written[0] = True
else:
# Ack! Even though this could/should work, looks like it is not supported. So we have to chose one. :|
max_users = max_props = -1
written_prop_type_name = None
for prop_type_name, (properties, nbr_sub_type_users) in subprops.items():
if nbr_sub_type_users > max_users or (nbr_sub_type_users == max_users and len(properties) > max_props):
max_users = nbr_sub_type_users
max_props = len(properties)
written_prop_type_name = prop_type_name
subprops = (written_prop_type_name, properties)
ref_templates[(type_name, written_prop_type_name)].written[0] = True
prop_type_name, properties = subprops
if prop_type_name and properties:
elem = elem_data_single_string(template, b"PropertyTemplate", prop_type_name)
props = elem_properties(elem)
for name, (value, ptype, animatable) in properties.items():
elem_props_set(props, ptype, name, value, animatable=animatable)
def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr_users=0):
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"GlobalSettings", b"", props, nbr_users, [False])
def fbx_template_def_model(scene, settings, override_defaults=None, nbr_users=0):
gscale = settings.global_scale
# Name, Value, Type, Animatable
(b"QuaternionInterpolate", (0, "p_enum", False)), # 0 = no quat interpolation.
(b"RotationOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"RotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"ScalingOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"ScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"TranslationActive", (False, "p_bool", False)),
(b"TranslationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"TranslationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"TranslationMinX", (False, "p_bool", False)),
(b"TranslationMinY", (False, "p_bool", False)),
(b"TranslationMinZ", (False, "p_bool", False)),
(b"TranslationMaxX", (False, "p_bool", False)),
(b"TranslationMaxY", (False, "p_bool", False)),
(b"TranslationMaxZ", (False, "p_bool", False)),
(b"RotationOrder", (0, "p_enum", False)), # we always use 'XYZ' order.
(b"RotationSpaceForLimitOnly", (False, "p_bool", False)),
Bastien Montagne
committed
(b"RotationStiffnessX", (0.0, "p_double", False)),
(b"RotationStiffnessY", (0.0, "p_double", False)),
(b"RotationStiffnessZ", (0.0, "p_double", False)),
(b"AxisLen", (10.0, "p_double", False)),
(b"PreRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"PostRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"RotationActive", (False, "p_bool", False)),
(b"RotationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"RotationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"RotationMinX", (False, "p_bool", False)),
(b"RotationMinY", (False, "p_bool", False)),
(b"RotationMinZ", (False, "p_bool", False)),
(b"RotationMaxX", (False, "p_bool", False)),
(b"RotationMaxY", (False, "p_bool", False)),
(b"RotationMaxZ", (False, "p_bool", False)),
(b"InheritType", (1, "p_enum", False)), # RSrs
(b"ScalingActive", (False, "p_bool", False)),
(b"ScalingMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"ScalingMax", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
(b"ScalingMinX", (False, "p_bool", False)),
(b"ScalingMinY", (False, "p_bool", False)),
(b"ScalingMinZ", (False, "p_bool", False)),
(b"ScalingMaxX", (False, "p_bool", False)),
(b"ScalingMaxY", (False, "p_bool", False)),
(b"ScalingMaxZ", (False, "p_bool", False)),
(b"GeometricTranslation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"GeometricRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"GeometricScaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
Bastien Montagne
committed
(b"MinDampRangeX", (0.0, "p_double", False)),
(b"MinDampRangeY", (0.0, "p_double", False)),
(b"MinDampRangeZ", (0.0, "p_double", False)),
(b"MaxDampRangeX", (0.0, "p_double", False)),
(b"MaxDampRangeY", (0.0, "p_double", False)),
(b"MaxDampRangeZ", (0.0, "p_double", False)),
(b"MinDampStrengthX", (0.0, "p_double", False)),
(b"MinDampStrengthY", (0.0, "p_double", False)),
(b"MinDampStrengthZ", (0.0, "p_double", False)),
(b"MaxDampStrengthX", (0.0, "p_double", False)),
(b"MaxDampStrengthY", (0.0, "p_double", False)),
(b"MaxDampStrengthZ", (0.0, "p_double", False)),
(b"PreferedAngleX", (0.0, "p_double", False)),
(b"PreferedAngleY", (0.0, "p_double", False)),
(b"PreferedAngleZ", (0.0, "p_double", False)),
(b"LookAtProperty", (None, "p_object", False)),
(b"UpVectorProperty", (None, "p_object", False)),
(b"Show", (True, "p_bool", False)),
(b"NegativePercentShapeSupport", (True, "p_bool", False)),
(b"DefaultAttributeIndex", (0, "p_integer", False)),
(b"Freeze", (False, "p_bool", False)),
(b"LODBox", (False, "p_bool", False)),
(b"Lcl Translation", ((0.0, 0.0, 0.0), "p_lcl_translation", True)),
(b"Lcl Rotation", ((0.0, 0.0, 0.0), "p_lcl_rotation", True)),
(b"Lcl Scaling", ((1.0, 1.0, 1.0), "p_lcl_scaling", True)),
(b"Visibility", (1.0, "p_visibility", True)),
(b"Visibility Inheritance", (1, "p_visibility_inheritance", False)),
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"Model", b"FbxNode", props, nbr_users, [False])
def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0):
props = OrderedDict((
(b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
(b"Size", (100.0, "p_double", False)),
(b"Look", (1, "p_enum", False)), # Cross (0 is None, i.e. invisible?).
))
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"NodeAttribute", b"FbxNull", props, nbr_users, [False])
def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0):
gscale = settings.global_scale
props = OrderedDict((
(b"LightType", (0, "p_enum", False)), # Point light.
(b"CastLight", (True, "p_bool", False)),
(b"Color", ((1.0, 1.0, 1.0), "p_color", True)),
(b"Intensity", (100.0, "p_number", True)), # Times 100 compared to Blender values...
(b"DecayType", (2, "p_enum", False)), # Quadratic.
Bastien Montagne
committed
(b"DecayStart", (30.0 * gscale, "p_double", False)),
(b"CastShadows", (True, "p_bool", False)),
(b"ShadowColor", ((0.0, 0.0, 0.0), "p_color", True)),
(b"AreaLightShape", (0, "p_enum", False)), # Rectangle.
))
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"NodeAttribute", b"FbxLight", props, nbr_users, [False])
def fbx_template_def_camera(scene, settings, override_defaults=None, nbr_users=0):
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
r = scene.render
props = OrderedDict((
(b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
(b"Position", ((0.0, 0.0, -50.0), "p_vector", True)),
(b"UpVector", ((0.0, 1.0, 0.0), "p_vector", True)),
(b"InterestPosition", ((0.0, 0.0, 0.0), "p_vector", True)),
(b"Roll", (0.0, "p_roll", True)),
(b"OpticalCenterX", (0.0, "p_opticalcenterx", True)),
(b"OpticalCenterY", (0.0, "p_opticalcentery", True)),
(b"BackgroundColor", ((0.8, 0.8, 0.8), "p_color", True)),
(b"TurnTable", (0.0, "p_number", True)),
(b"DisplayTurnTableIcon", (False, "p_bool", False)),
(b"UseMotionBlur", (False, "p_bool", False)),
(b"UseRealTimeMotionBlur", (True, "p_bool", False)),
(b"Motion Blur Intensity", (1.0, "p_number", True)),
(b"AspectRatioMode", (2, "p_enum", False)), # Fixed ratio, height and width in pixels.
(b"AspectWidth", (float(r.resolution_x), "p_double", False)),
(b"AspectHeight", (float(r.resolution_y), "p_double", False)),
(b"PixelAspectRatio", (float(r.pixel_aspect_x / r.pixel_aspect_y), "p_double", False)),
(b"FilmOffsetX", (0.0, "p_number", True)),
(b"FilmOffsetY", (0.0, "p_number", True)),
(b"FilmWidth", (1.2598425196850394, "p_double", False)),
(b"FilmHeight", (0.7086614173228346, "p_double", False)),
(b"FilmAspectRatio", (1.777777777777778, "p_double", False)),
(b"FilmSqueezeRatio", (1.0, "p_double", False)),
(b"FilmFormatIndex", (0, "p_enum", False)), # Assuming this is ApertureFormat, 0 = custom.
(b"PreScale", (1.0, "p_number", True)),
(b"FilmTranslateX", (0.0, "p_number", True)),
(b"FilmTranslateY", (0.0, "p_number", True)),
(b"FilmRollPivotX", (0.0, "p_number", True)),
(b"FilmRollPivotY", (0.0, "p_number", True)),
(b"FilmRollValue", (0.0, "p_number", True)),
(b"FilmRollOrder", (0, "p_enum", False)), # 0 = rotate first (default).
(b"ApertureMode", (3, "p_enum", False)), # 3 = focal length.
(b"GateFit", (0, "p_enum", False)), # 0 = no resolution gate fit.
(b"FieldOfView", (49.13434207760448, "p_fov", True)),
(b"FieldOfViewX", (49.13434207760448, "p_fov_x", True)),
(b"FieldOfViewY", (28.841546110078532, "p_fov_y", True)),
(b"FocalLength", (35.0, "p_number", True)),
(b"CameraFormat", (0, "p_enum", False)), # Custom camera format.
(b"UseFrameColor", (False, "p_bool", False)),
(b"FrameColor", ((0.3, 0.3, 0.3), "p_color_rgb", False)),
(b"ShowName", (True, "p_bool", False)),
(b"ShowInfoOnMoving", (True, "p_bool", False)),
(b"ShowGrid", (True, "p_bool", False)),
(b"ShowOpticalCenter", (False, "p_bool", False)),
(b"ShowAzimut", (True, "p_bool", False)),
(b"ShowTimeCode", (True, "p_bool", False)),
(b"ShowAudio", (False, "p_bool", False)),
(b"AudioColor", ((0.0, 1.0, 0.0), "p_vector_3d", False)), # Yep, vector3d, not corlorgb… :cry:
(b"NearPlane", (1.0, "p_double", False)),
(b"FarPlane", (100.0, "p_double", False)),
(b"AutoComputeClipPanes", (False, "p_bool", False)),
(b"ViewCameraToLookAt", (True, "p_bool", False)),
(b"ViewFrustumNearFarPlane", (False, "p_bool", False)),
(b"ViewFrustumBackPlaneMode", (2, "p_enum", False)), # 2 = show back plane if texture added.
(b"BackPlaneDistance", (100.0, "p_number", True)),
(b"BackPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera.
(b"ViewFrustumFrontPlaneMode", (2, "p_enum", False)), # 2 = show front plane if texture added.
(b"FrontPlaneDistance", (1.0, "p_number", True)),
(b"FrontPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera.
(b"LockMode", (False, "p_bool", False)),
(b"LockInterestNavigation", (False, "p_bool", False)),
(b"BackPlateFitImage", (False, "p_bool", False)),
(b"BackPlateCrop", (False, "p_bool", False)),
(b"BackPlateCenter", (True, "p_bool", False)),
(b"BackPlateKeepRatio", (True, "p_bool", False)),
(b"BackgroundAlphaTreshold", (0.5, "p_double", False)),
(b"ShowBackplate", (True, "p_bool", False)),
(b"BackPlaneOffsetX", (0.0, "p_number", True)),
(b"BackPlaneOffsetY", (0.0, "p_number", True)),
(b"BackPlaneRotation", (0.0, "p_number", True)),
(b"BackPlaneScaleX", (1.0, "p_number", True)),
(b"BackPlaneScaleY", (1.0, "p_number", True)),
(b"Background Texture", (None, "p_object", False)),
(b"FrontPlateFitImage", (True, "p_bool", False)),
(b"FrontPlateCrop", (False, "p_bool", False)),
(b"FrontPlateCenter", (True, "p_bool", False)),
(b"FrontPlateKeepRatio", (True, "p_bool", False)),
(b"Foreground Opacity", (1.0, "p_double", False)),
(b"ShowFrontplate", (True, "p_bool", False)),
(b"FrontPlaneOffsetX", (0.0, "p_number", True)),
(b"FrontPlaneOffsetY", (0.0, "p_number", True)),
(b"FrontPlaneRotation", (0.0, "p_number", True)),
(b"FrontPlaneScaleX", (1.0, "p_number", True)),
(b"FrontPlaneScaleY", (1.0, "p_number", True)),
(b"Foreground Texture", (None, "p_object", False)),
(b"DisplaySafeArea", (True, "p_bool", False)),
(b"DisplaySafeAreaOnRender", (False, "p_bool", False)),
(b"SafeAreaDisplayStyle", (1, "p_enum", False)), # 1 = rounded corners.
(b"SafeAreaAspectRatio", (1.777777777777778, "p_double", False)),
(b"Use2DMagnifierZoom", (False, "p_bool", False)),
(b"2D Magnifier Zoom", (100.0, "p_number", True)),
(b"2D Magnifier X", (50.0, "p_number", True)),
(b"2D Magnifier Y", (50.0, "p_number", True)),
(b"CameraProjectionType", (0, "p_enum", False)), # 0 = perspective, 1 = orthogonal.
(b"OrthoZoom", (1.0, "p_double", False)),
(b"UseRealTimeDOFAndAA", (False, "p_bool", False)),
(b"UseDepthOfField", (False, "p_bool", False)),
(b"FocusSource", (1, "p_enum", False)), # 0 = camera interest, 1 = distance from camera interest.
(b"FocusAngle", (3.5, "p_double", False)), # ???
(b"FocusDistance", (10.0, "p_double", False)),
(b"UseAntialiasing", (False, "p_bool", False)),
(b"AntialiasingIntensity", (0.77777, "p_double", False)),
(b"AntialiasingMethod", (0, "p_enum", False)), # 0 = oversampling, 1 = hardware.
(b"UseAccumulationBuffer", (False, "p_bool", False)),
(b"FrameSamplingCount", (7, "p_integer", False)),
(b"FrameSamplingType", (1, "p_enum", False)), # 0 = uniform, 1 = stochastic.
))
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"NodeAttribute", b"FbxCamera", props, nbr_users, [False])
def fbx_template_def_bone(scene, settings, override_defaults=None, nbr_users=0):
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"NodeAttribute", b"LimbNode", props, nbr_users, [False])
def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users=0):
props = OrderedDict((
(b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
(b"BBoxMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"BBoxMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"Primary Visibility", (True, "p_bool", False)),
(b"Casts Shadows", (True, "p_bool", False)),
(b"Receive Shadows", (True, "p_bool", False)),
Bastien Montagne
committed
))
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"Geometry", b"FbxMesh", props, nbr_users, [False])
def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users=0):
# WIP...
(b"ShadingModel", ("Phong", "p_string", False)),
(b"MultiLayer", (False, "p_bool", False)),
Bastien Montagne
committed
(b"EmissiveColor", ((0.8, 0.8, 0.8), "p_color", True)), # Same as diffuse.
(b"EmissiveFactor", (0.0, "p_number", True)),
Bastien Montagne
committed
(b"AmbientColor", ((0.0, 0.0, 0.0), "p_color", True)),
(b"AmbientFactor", (1.0, "p_number", True)),
Bastien Montagne
committed
(b"DiffuseColor", ((0.8, 0.8, 0.8), "p_color", True)),
(b"DiffuseFactor", (0.8, "p_number", True)),
Bastien Montagne
committed
(b"TransparentColor", ((0.8, 0.8, 0.8), "p_color", True)), # Same as diffuse.
(b"TransparencyFactor", (0.0, "p_number", True)),
(b"Opacity", (1.0, "p_number", True)),
(b"NormalMap", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"Bump", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
Bastien Montagne
committed
(b"BumpFactor", (1.0, "p_double", False)),
(b"DisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
Bastien Montagne
committed
(b"DisplacementFactor", (0.0, "p_double", False)),
(b"VectorDisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
(b"VectorDisplacementFactor", (0.0, "p_double", False)),
Bastien Montagne
committed
(b"SpecularColor", ((1.0, 1.0, 1.0), "p_color", True)),
(b"SpecularFactor", (0.5 / 2.0, "p_number", True)),
# Not sure about the name, importer uses this (but ShininessExponent for tex prop name!)
# And in fbx exported by sdk, you have one in template, the other in actual material!!! :/
# For now, using both.
(b"Shininess", ((50.0 - 1.0) / 5.10, "p_number", True)),
(b"ShininessExponent", ((50.0 - 1.0) / 5.10, "p_number", True)),
Bastien Montagne
committed
(b"ReflectionColor", ((1.0, 1.0, 1.0), "p_color", True)),
(b"ReflectionFactor", (0.0, "p_number", True)),
))
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"Material", b"FbxSurfacePhong", props, nbr_users, [False])
def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_users=0):
# WIP...
# XXX Not sure about all names!
props = OrderedDict((
(b"TextureTypeUse", (0, "p_enum", False)), # Standard.
(b"AlphaSource", (2, "p_enum", False)), # Black (i.e. texture's alpha), XXX name guessed!.
Bastien Montagne
committed
(b"Texture alpha", (1.0, "p_double", False)),
(b"PremultiplyAlpha", (False, "p_bool", False)),
(b"CurrentTextureBlendMode", (0, "p_enum", False)), # Translucent, assuming this means "Alpha over"!
(b"CurrentMappingType", (1, "p_enum", False)), # Planar.
(b"WrapModeU", (0, "p_enum", False)), # Repeat.
(b"WrapModeV", (0, "p_enum", False)), # Repeat.
(b"UVSwap", (False, "p_bool", False)),
(b"Translation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"Rotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"Scaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
(b"TextureRotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
(b"TextureScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
# Not sure about those two... At least, UseMaterial should always be ON imho.
(b"UseMaterial", (True, "p_bool", False)),
(b"UseMipMap", (False, "p_bool", False)),
))
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"Texture", b"FbxFileTexture", props, nbr_users, [False])
def fbx_template_def_video(scene, settings, override_defaults=None, nbr_users=0):
# WIP...
(b"Width", (0, "p_integer", False)),
(b"Height", (0, "p_integer", False)),
(b"Path", ("", "p_string_url", False)),
(b"AccessMode", (0, "p_enum", False)), # Disk (0=Disk, 1=Mem, 2=DiskAsync).
(b"StartFrame", (0, "p_integer", False)),
(b"StopFrame", (0, "p_integer", False)),
(b"Offset", (0, "p_timestamp", False)),
Bastien Montagne
committed
(b"PlaySpeed", (1.0, "p_double", False)),
(b"FreeRunning", (False, "p_bool", False)),
(b"Loop", (False, "p_bool", False)),
(b"InterlaceMode", (0, "p_enum", False)), # None, i.e. progressive.
(b"ImageSequence", (False, "p_bool", False)),
(b"ImageSequenceOffset", (0, "p_integer", False)),
Bastien Montagne
committed
(b"FrameRate", (scene.render.fps / scene.render.fps_base, "p_double", False)),
(b"LastFrame", (0, "p_integer", False)),
))
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"Video", b"FbxVideo", props, nbr_users, [False])
def fbx_template_def_pose(scene, settings, override_defaults=None, nbr_users=0):
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"Pose", b"", props, nbr_users, [False])
def fbx_template_def_deformer(scene, settings, override_defaults=None, nbr_users=0):
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"Deformer", b"", props, nbr_users, [False])
def fbx_template_def_animstack(scene, settings, override_defaults=None, nbr_users=0):
props = OrderedDict((
(b"Description", ("", "p_string", False)),
(b"LocalStart", (0, "p_timestamp", False)),
(b"LocalStop", (0, "p_timestamp", False)),
(b"ReferenceStart", (0, "p_timestamp", False)),
(b"ReferenceStop", (0, "p_timestamp", False)),
))
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"AnimationStack", b"FbxAnimStack", props, nbr_users, [False])
def fbx_template_def_animlayer(scene, settings, override_defaults=None, nbr_users=0):
props = OrderedDict((
(b"Weight", (100.0, "p_number", True)),
(b"Mute", (False, "p_bool", False)),
(b"Solo", (False, "p_bool", False)),
(b"Lock", (False, "p_bool", False)),
Bastien Montagne
committed
(b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
(b"BlendMode", (0, "p_enum", False)),
(b"RotationAccumulationMode", (0, "p_enum", False)),
(b"ScaleAccumulationMode", (0, "p_enum", False)),
(b"BlendModeBypass", (0, "p_ulonglong", False)),
))
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"AnimationLayer", b"FbxAnimLayer", props, nbr_users, [False])
def fbx_template_def_animcurvenode(scene, settings, override_defaults=None, nbr_users=0):
props = OrderedDict((
(b"d", (None, "p_compound", False)),
))
if override_defaults is not None:
props.update(override_defaults)
Bastien Montagne
committed
return FBXTemplate(b"AnimationCurveNode", b"FbxAnimCurveNode", props, nbr_users, [False])
def fbx_template_def_animcurve(scene, settings, override_defaults=None, nbr_users=0):
props = OrderedDict()