FBX: support exporting baked animation.
Note this code is highly theorical, I could not get to test it really (other apps I have access to also fail at importing FBX anim generated from collada by FBXConverter :( ). For now, only basic (loc/rot/scale) of objects is implemented. Will wait for other testers' feedback before going any further.
This commit is contained in:
parent
22052a47dc
commit
a6f8095463
|
@ -62,6 +62,7 @@ FBX_DEFORMER_SKIN_VERSION = 101
|
|||
FBX_DEFORMER_CLUSTER_VERSION = 100
|
||||
FBX_MATERIAL_VERSION = 102
|
||||
FBX_TEXTURE_VERSION = 202
|
||||
FBX_ANIM_KEY_VERSION = 4008
|
||||
|
||||
FBX_NAME_CLASS_SEP = b"\x00\x01"
|
||||
|
||||
|
@ -241,6 +242,26 @@ def get_blender_bone_cluster_key(armature, mesh, bone):
|
|||
get_blenderID_key(bone), "SubDeformerCluster"))
|
||||
|
||||
|
||||
def get_blender_anim_stack_key(scene):
|
||||
"""Return single anim stack key."""
|
||||
return "|".join((get_blenderID_key(scene), "AnimStack"))
|
||||
|
||||
|
||||
def get_blender_anim_layer_key(ID):
|
||||
"""Return ID's anim layer key."""
|
||||
return "|".join((get_blenderID_key(ID), "AnimLayer"))
|
||||
|
||||
|
||||
def get_blender_anim_curve_node_key(ID, fbx_prop_name):
|
||||
"""Return (ID, fbxprop) curve node key."""
|
||||
return "|".join((get_blenderID_key(ID), fbx_prop_name, "AnimCurveNode"))
|
||||
|
||||
|
||||
def get_blender_anim_curve_key(ID, fbx_prop_name, fbx_prop_item_name):
|
||||
"""Return (ID, fbxprop, item) curve key."""
|
||||
return "|".join((get_blenderID_key(ID), fbx_prop_name, fbx_prop_item_name, "AnimCurve"))
|
||||
|
||||
|
||||
##### Element generators. #####
|
||||
|
||||
# Note: elem may be None, in this case the element is not added to any parent.
|
||||
|
@ -341,6 +362,7 @@ def elem_data_vec_float64(elem, name, value):
|
|||
FBX_PROPERTIES_DEFINITIONS = {
|
||||
"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"),
|
||||
"p_enum": (b"enum", b"", "add_int32"),
|
||||
"p_number": (b"double", b"Number", "add_float64"),
|
||||
"p_visibility": (b"Visibility", b"", "add_float64"),
|
||||
|
@ -691,6 +713,52 @@ def fbx_template_def_deformer(scene, settings, override_defaults=None, nbr_users
|
|||
return FBXTemplate(b"Deformer", b"", props, nbr_users)
|
||||
|
||||
|
||||
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)
|
||||
return FBXTemplate(b"AnimationStack", b"FbxAnimStack", props, nbr_users)
|
||||
|
||||
|
||||
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)),
|
||||
(b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", True)),
|
||||
(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)
|
||||
return FBXTemplate(b"AnimationLayer", b"FbxAnimLayer", props, nbr_users)
|
||||
|
||||
|
||||
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)
|
||||
return FBXTemplate(b"AnimationCurveNode", b"FbxAnimCurveNode", props, nbr_users)
|
||||
|
||||
|
||||
def fbx_template_def_animcurve(scene, settings, override_defaults=None, nbr_users=0):
|
||||
props = OrderedDict()
|
||||
if override_defaults is not None:
|
||||
props.update(override_defaults)
|
||||
return FBXTemplate(b"AnimationCurve", b"", props, nbr_users)
|
||||
|
||||
|
||||
##### FBX objects generators. #####
|
||||
def has_valid_parent(scene_data, obj):
|
||||
return obj.parent and obj.parent in scene_data.objects
|
||||
|
@ -704,7 +772,7 @@ def use_bake_space_transform(scene_data, obj):
|
|||
obj.type in {'MESH'} and not has_valid_parent(scene_data, obj))
|
||||
|
||||
|
||||
def object_matrix(scene_data, obj, armature=None, local_space=False, global_space=False):
|
||||
def fbx_object_matrix(scene_data, obj, armature=None, local_space=False, global_space=False):
|
||||
"""
|
||||
Generate object transform matrix (*always* in matching *FBX* space!).
|
||||
If local_space is True, returned matrix is *always* in local space.
|
||||
|
@ -750,7 +818,7 @@ def object_matrix(scene_data, obj, armature=None, local_space=False, global_spac
|
|||
# Apply parent's *Blender* local space...
|
||||
matrix = obj.parent.matrix_local * matrix
|
||||
# ...and move it back into parent's *FBX* local space.
|
||||
par_mat = object_matrix(scene_data, obj.parent, local_space=True)
|
||||
par_mat = fbx_object_matrix(scene_data, obj.parent, local_space=True)
|
||||
matrix = par_mat.inverted() * matrix
|
||||
|
||||
if use_bake_space_transform(scene_data, obj):
|
||||
|
@ -764,11 +832,11 @@ def object_matrix(scene_data, obj, armature=None, local_space=False, global_spac
|
|||
return matrix
|
||||
|
||||
|
||||
def object_tx(scene_data, obj):
|
||||
def fbx_object_tx(scene_data, obj):
|
||||
"""
|
||||
Generate object transform data (always in local space when possible).
|
||||
"""
|
||||
matrix = object_matrix(scene_data, obj)
|
||||
matrix = fbx_object_matrix(scene_data, obj)
|
||||
loc, rot, scale = matrix.decompose()
|
||||
matrix_rot = rot.to_matrix()
|
||||
rot = rot.to_euler() # quat -> euler, we always use 'XYZ' order.
|
||||
|
@ -848,7 +916,7 @@ def fbx_data_camera_elements(root, cam_obj, scene_data):
|
|||
|
||||
# Real data now, good old camera!
|
||||
# Object transform info.
|
||||
loc, rot, scale, matrix, matrix_rot = object_tx(scene_data, cam_obj)
|
||||
loc, rot, scale, matrix, matrix_rot = fbx_object_tx(scene_data, cam_obj)
|
||||
up = matrix_rot * Vector((0.0, 1.0, 0.0))
|
||||
to = matrix_rot * Vector((0.0, 0.0, -1.0))
|
||||
# Render settings.
|
||||
|
@ -1479,14 +1547,14 @@ def fbx_data_armature_elements(root, armature, scene_data):
|
|||
elem_data_single_int32(fbx_pose, b"NbPoseNodes", 1 + len(armature.data.bones))
|
||||
|
||||
# First node is mesh/object.
|
||||
mat_world_obj = object_matrix(scene_data, obj, global_space=True)
|
||||
mat_world_obj = fbx_object_matrix(scene_data, obj, global_space=True)
|
||||
fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
|
||||
elem_data_single_int64(fbx_posenode, b"Node", get_fbxuid_from_key(scene_data.objects[obj]))
|
||||
elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix_to_array(mat_world_obj))
|
||||
# And all bones of armature!
|
||||
mat_world_bones = {}
|
||||
for bo in armature.data.bones:
|
||||
bomat = object_matrix(scene_data, bo, armature, global_space=True)
|
||||
bomat = fbx_object_matrix(scene_data, bo, armature, global_space=True)
|
||||
mat_world_bones[bo] = bomat
|
||||
fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
|
||||
elem_data_single_int64(fbx_posenode, b"Node", get_fbxuid_from_key(scene_data.objects[bo]))
|
||||
|
@ -1557,7 +1625,7 @@ def fbx_data_object_elements(root, obj, scene_data):
|
|||
elem_data_single_int32(model, b"Version", FBX_MODELS_VERSION)
|
||||
|
||||
# Object transform info.
|
||||
loc, rot, scale, matrix, matrix_rot = object_tx(scene_data, obj)
|
||||
loc, rot, scale, matrix, matrix_rot = fbx_object_tx(scene_data, obj)
|
||||
rot = tuple(units_convert_iter(rot, "radian", "degree"))
|
||||
|
||||
tmpl = scene_data.templates[b"Model"]
|
||||
|
@ -1594,6 +1662,71 @@ def fbx_data_object_elements(root, obj, scene_data):
|
|||
elem_props_template_set(tmpl, props, "p_bool", b"ForegroundTransparent", True)
|
||||
|
||||
|
||||
def fbx_data_animation_elements(root, scene_data):
|
||||
"""
|
||||
Write animation data.
|
||||
"""
|
||||
animations = scene_data.animations
|
||||
if not animations:
|
||||
return
|
||||
scene = scene_data.scene
|
||||
|
||||
fps = scene.render.fps / scene.render.fps_base
|
||||
def keys_to_ktimes(keys):
|
||||
return (int(v) for v in units_convert_iter((f / fps for f, _v in keys), "second", "ktime"))
|
||||
|
||||
astack_key, alayers = animations
|
||||
acn_tmpl = scene_data.templates[b"AnimationCurveNode"]
|
||||
|
||||
# Animation stack.
|
||||
astack = elem_data_single_int64(root, b"AnimationStack", get_fbxuid_from_key(astack_key))
|
||||
astack.add_string(fbx_name_class(scene.name.encode(), b"AnimStack"))
|
||||
astack.add_string(b"")
|
||||
|
||||
for obj, (alayer_key, acurvenodes) in alayers.items():
|
||||
# Animation layer.
|
||||
alayer = elem_data_single_int64(root, b"AnimationLayer", get_fbxuid_from_key(alayer_key))
|
||||
alayer.add_string(fbx_name_class(obj.name.encode(), b"AnimLayer"))
|
||||
alayer.add_string(b"")
|
||||
|
||||
for fbx_prop, (acurvenode_key, acurves) in acurvenodes.items():
|
||||
# Animation curve node.
|
||||
acurvenode = elem_data_single_int64(root, b"AnimationCurveNode", get_fbxuid_from_key(acurvenode_key))
|
||||
acurvenode.add_string(fbx_name_class(fbx_prop.encode(), b"AnimCurveNode"))
|
||||
acurvenode.add_string(b"")
|
||||
|
||||
acn_props = elem_properties(acurvenode)
|
||||
|
||||
for fbx_item, (acurve_key, default_value, keys) in acurves.items():
|
||||
elem_props_template_set(acn_tmpl, acn_props, "p_number", fbx_item.encode(), default_value)
|
||||
|
||||
# Only create Animation curve if needed!
|
||||
if keys:
|
||||
acurve = elem_data_single_int64(root, b"AnimationCurve", get_fbxuid_from_key(acurve_key))
|
||||
acurve.add_string(fbx_name_class(b"", b"AnimCurve"))
|
||||
acurve.add_string(b"")
|
||||
|
||||
# key attributes...
|
||||
# flags...
|
||||
keyattr_flags = (1 << 3 | # interpolation mode, 1 = constant, 2 = linear, 3 = cubic.
|
||||
1 << 8 | # tangent mode, 8 = auto, 9 = TCB, 10 = user, 11 = generic break,
|
||||
1 << 13 | # tangent mode, 12 = generic clamp, 13 = generic time independent,
|
||||
1 << 14 | # tangent mode, 13 + 14 = generic clamp progressive.
|
||||
0,
|
||||
)
|
||||
# Maybe values controlling TCB & co???
|
||||
keyattr_datafloat = (0.0, 0.0, 9.419963346924634e-30, 0.0)
|
||||
|
||||
# And now, the *real* data!
|
||||
elem_data_single_float64(acurve, b"Default", default_value)
|
||||
elem_data_single_int32(acurve, b"KeyVer", FBX_ANIM_KEY_VERSION)
|
||||
elem_data_single_int64_array(acurve, b"KeyTime", keys_to_ktimes(keys))
|
||||
elem_data_single_float32_array(acurve, b"KeyValueFloat", (v for _f, v in keys))
|
||||
elem_data_single_int32_array(acurve, b"KeyAttrFlags", keyattr_flags)
|
||||
elem_data_single_float32_array(acurve, b"KeyAttrDataFloat", keyattr_datafloat)
|
||||
elem_data_single_int32_array(acurve, b"KeyAttrRefCount", (len(keys),))
|
||||
|
||||
|
||||
##### Top-level FBX data container. #####
|
||||
|
||||
# Helper container gathering some data we need multiple times:
|
||||
|
@ -1603,7 +1736,7 @@ def fbx_data_object_elements(root, obj, scene_data):
|
|||
# * takes.
|
||||
FBXData = namedtuple("FBXData", (
|
||||
"templates", "templates_users", "connections",
|
||||
"settings", "scene", "objects",
|
||||
"settings", "scene", "objects", "animations",
|
||||
"data_lamps", "data_cameras", "data_meshes", "mesh_mat_indices",
|
||||
"data_bones", "data_deformers",
|
||||
"data_world", "data_materials", "data_textures", "data_videos",
|
||||
|
@ -1707,6 +1840,117 @@ def fbx_skeleton_from_armature(scene, settings, armature, objects, data_bones, d
|
|||
arm_parents.add((armature, obj))
|
||||
|
||||
|
||||
def fbx_animations_simplify(scene_data, animdata):
|
||||
"""
|
||||
Simplifies FCurves!
|
||||
"""
|
||||
fac = scene_data.settings.bake_anim_simplify_factor
|
||||
step = scene_data.settings.bake_anim_step
|
||||
# So that, with default factor and step values (1), we get:
|
||||
max_frame_diff = step * fac * 10 # max step of 10 frames.
|
||||
value_diff_fac = fac / 1000 # min value evolution: 0.1% of whole range.
|
||||
|
||||
for obj, keys in animdata.items():
|
||||
if not keys:
|
||||
continue
|
||||
extremums = [(min(values), max(values)) for values in zip(*(k[1] for k in keys))]
|
||||
min_diffs = [max((mx - mn) * value_diff_fac, 0.000001) for mx, mn in extremums]
|
||||
p_currframe, p_key, p_key_write = keys[0]
|
||||
p_keyed = [(p_currframe - max_frame_diff, val) for val in p_key]
|
||||
for currframe, key, key_write in keys:
|
||||
#if obj.name == "Cube":
|
||||
#print(currframe, key, key_write)
|
||||
for idx, (val, p_val) in enumerate(zip(key, p_key)):
|
||||
p_keyedframe, p_keyedval = p_keyed[idx]
|
||||
if val == p_val:
|
||||
# Never write keyframe when value is exactly the same as prev one!
|
||||
continue
|
||||
if abs(val - p_val) >= min_diffs[idx]:
|
||||
# If enough difference from previous sampled value, key this value *and* the previous one!
|
||||
key_write[idx] = True
|
||||
p_key_write[idx] = True
|
||||
p_keyed[idx] = (currframe, val)
|
||||
elif (abs(val - p_keyedval) > min_diffs[idx]) or (currframe - p_keyedframe >= max_frame_diff):
|
||||
# Else, if enough difference from previous keyed value (or max gap between keys is reached),
|
||||
# key this value only!
|
||||
key_write[idx] = True
|
||||
p_keyed[idx] = (currframe, val)
|
||||
p_currframe, p_key, p_key_write = currframe, key, key_write
|
||||
|
||||
|
||||
def fbx_animations_objects(scene_data):
|
||||
"""
|
||||
Generate animation data from objects.
|
||||
"""
|
||||
objects = scene_data.objects
|
||||
bake_step = scene_data.settings.bake_anim_step
|
||||
scene = scene_data.scene
|
||||
|
||||
# FBX mapping info: Property affected, and name of the "sub" property (to distinguish e.g. vector's channels).
|
||||
fbx_names = (
|
||||
("Lcl Translation", "d|X"), ("Lcl Translation", "d|Y"), ("Lcl Translation", "d|Z"),
|
||||
("Lcl Rotation", "d|X"), ("Lcl Rotation", "d|Y"), ("Lcl Rotation", "d|Z"),
|
||||
("Lcl Scaling", "d|X"), ("Lcl Scaling", "d|Y"), ("Lcl Scaling", "d|Z"),
|
||||
)
|
||||
|
||||
back_currframe = scene.frame_current
|
||||
animdata = {obj: [] for obj in objects.keys()}
|
||||
|
||||
currframe = scene.frame_start
|
||||
while currframe < scene.frame_end:
|
||||
scene.frame_set(int(currframe), currframe - int(currframe))
|
||||
for obj in objects.keys():
|
||||
if isinstance(obj, Bone):
|
||||
continue # TODO!
|
||||
# We compute baked loc/rot/scale for all objects.
|
||||
loc, rot, scale, _m, _mr = fbx_object_tx(scene_data, obj)
|
||||
tx = tuple(loc) + tuple(units_convert_iter(rot, "radian", "degree")) + tuple(scale)
|
||||
animdata[obj].append((currframe, tx, [False] * len(tx)))
|
||||
currframe += bake_step
|
||||
|
||||
scene.frame_set(back_currframe, 0.0)
|
||||
|
||||
fbx_animations_simplify(scene_data, animdata)
|
||||
|
||||
animations = {}
|
||||
|
||||
# And now, produce final data (usable by FBX export code)...
|
||||
for obj, keys in animdata.items():
|
||||
if not keys:
|
||||
continue
|
||||
curves = [[] for k in keys[0][1]]
|
||||
for currframe, key, key_write in keys:
|
||||
#if obj.name == "Cube":
|
||||
#print(currframe, key, key_write)
|
||||
for idx, (val, wrt) in enumerate(zip(key, key_write)):
|
||||
if wrt:
|
||||
curves[idx].append((currframe, val))
|
||||
|
||||
loc, rot, scale, _m, _mr = fbx_object_tx(scene_data, obj)
|
||||
tx = tuple(loc) + tuple(units_convert_iter(rot, "radian", "degree")) + tuple(scale)
|
||||
# If animation for a channel, (True, keyframes), else (False, current value).
|
||||
final_keys = {}
|
||||
for idx, c in enumerate(curves):
|
||||
fbx_group, fbx_item = fbx_names[idx]
|
||||
fbx_item_key = get_blender_anim_curve_key(obj, fbx_group, fbx_item)
|
||||
if fbx_group not in final_keys:
|
||||
final_keys[fbx_group] = (get_blender_anim_curve_node_key(obj, fbx_group), {})
|
||||
final_keys[fbx_group][1][fbx_item] = (fbx_item_key, tx[idx], c if len(c) > 1 else [])
|
||||
# And now, remove anim groups (i.e. groups of curves affecting a single FBX property) with no curve at all!
|
||||
del_groups = []
|
||||
for grp, (_k, data) in final_keys.items():
|
||||
if True in (bool(d[2]) for d in data.values()):
|
||||
continue
|
||||
del_groups.append(grp)
|
||||
for grp in del_groups:
|
||||
del final_keys[grp]
|
||||
|
||||
if final_keys:
|
||||
animations[obj] = (get_blender_anim_layer_key(obj), final_keys)
|
||||
|
||||
return (get_blender_anim_stack_key(scene), animations) if animations else None
|
||||
|
||||
|
||||
def fbx_data_from_scene(scene, settings):
|
||||
"""
|
||||
Do some pre-processing over scene's data...
|
||||
|
@ -1792,6 +2036,17 @@ def fbx_data_from_scene(scene, settings):
|
|||
else:
|
||||
data_videos[img] = (get_blenderID_key(img), [tex])
|
||||
|
||||
# Animation...
|
||||
# From objects only for a start.
|
||||
tmp_scdata = FBXData( # Kind of hack, we need a temp scene_data for object's space handling to bake animations...
|
||||
None, None, None,
|
||||
settings, scene, objects, None,
|
||||
data_lamps, data_cameras, data_meshes, None,
|
||||
data_bones, data_deformers,
|
||||
data_world, data_materials, data_textures, data_videos,
|
||||
)
|
||||
animations = fbx_animations_objects(tmp_scdata)
|
||||
|
||||
##### Creation of templates...
|
||||
|
||||
templates = OrderedDict()
|
||||
|
@ -1836,6 +2091,20 @@ def fbx_data_from_scene(scene, settings):
|
|||
if data_videos:
|
||||
templates[b"Video"] = fbx_template_def_video(scene, settings, nbr_users=len(data_videos))
|
||||
|
||||
if animations:
|
||||
# One stack!
|
||||
templates[b"AnimationStack"] = fbx_template_def_animstack(scene, settings, nbr_users=1)
|
||||
# One layer per animated object.
|
||||
templates[b"AnimationLayer"] = fbx_template_def_animlayer(scene, settings, nbr_users=len(animations[1]))
|
||||
# As much curve node as animated properties.
|
||||
nbr = sum(len(al) for _kal, al in animations[1].values())
|
||||
templates[b"AnimationCurveNode"] = fbx_template_def_animcurvenode(scene, settings, nbr_users=nbr)
|
||||
# And the number of curves themselves...
|
||||
nbr = sum(1 if ac else 0 for _kal, al in animations[1].values()
|
||||
for _kacn, acn in al.values()
|
||||
for _kac, _dv, ac in acn.values())
|
||||
templates[b"AnimationCurve"] = fbx_template_def_animcurve(scene, settings, nbr_users=nbr)
|
||||
|
||||
templates_users = sum(tmpl.nbr_users for tmpl in templates.values())
|
||||
|
||||
##### Creation of connections...
|
||||
|
@ -1927,11 +2196,31 @@ def fbx_data_from_scene(scene, settings):
|
|||
tex_key, _texs = data_textures[tex]
|
||||
connections.append((b"OO", get_fbxuid_from_key(vid_key), get_fbxuid_from_key(tex_key), None))
|
||||
|
||||
#Animations
|
||||
if animations:
|
||||
# Animstack itself is linked nowhere!
|
||||
astack_id = get_fbxuid_from_key(animations[0])
|
||||
for obj, (alayer_key, acurvenodes) in animations[1].items():
|
||||
obj_id = get_fbxuid_from_key(objects[obj])
|
||||
# Animlayer -> animstack.
|
||||
alayer_id = get_fbxuid_from_key(alayer_key)
|
||||
connections.append((b"OO", alayer_id, astack_id, None))
|
||||
for fbx_prop, (acurvenode_key, acurves) in acurvenodes.items():
|
||||
# Animcurvenode -> animalayer.
|
||||
acurvenode_id = get_fbxuid_from_key(acurvenode_key)
|
||||
connections.append((b"OO", acurvenode_id, alayer_id, None))
|
||||
# Animcurvenode -> object property.
|
||||
connections.append((b"OP", alayer_id, obj_id, fbx_prop.encode()))
|
||||
for fbx_item, (acurve_key, dafault_value, acurve) in acurves.items():
|
||||
if acurve:
|
||||
# Animcurve -> Animcurvenode.
|
||||
connections.append((b"OP", get_fbxuid_from_key(acurve_key), acurvenode_id, fbx_item.encode()))
|
||||
|
||||
##### And pack all this!
|
||||
|
||||
return FBXData(
|
||||
templates, templates_users, connections,
|
||||
settings, scene, objects,
|
||||
settings, scene, objects, animations,
|
||||
data_lamps, data_cameras, data_meshes, mesh_mat_indices,
|
||||
data_bones, data_deformers,
|
||||
data_world, data_materials, data_textures, data_videos,
|
||||
|
@ -2115,6 +2404,8 @@ def fbx_objects_elements(root, scene_data):
|
|||
for vid in scene_data.data_videos.keys():
|
||||
fbx_data_video_elements(objects, vid, scene_data)
|
||||
|
||||
fbx_data_animation_elements(objects, scene_data)
|
||||
|
||||
|
||||
def fbx_connections_elements(root, scene_data):
|
||||
"""
|
||||
|
@ -2130,9 +2421,26 @@ def fbx_takes_elements(root, scene_data):
|
|||
"""
|
||||
Animations. Have yet to check how this work...
|
||||
"""
|
||||
# XXX Are takes needed at all in new anim system?
|
||||
takes = elem_empty(root, b"Takes")
|
||||
elem_data_single_string(takes, b"Current", b"")
|
||||
|
||||
animations = scene_data.animations
|
||||
if animations is None:
|
||||
return
|
||||
scene = scene_data.scene
|
||||
take_name = scene.name.encode()
|
||||
fps = scene.render.fps / scene.render.fps_base
|
||||
scene_start_ktime = int(units_convert(scene.frame_start / fps, "second", "ktime"))
|
||||
scene_end_ktime = int(units_convert(scene.frame_end / fps, "second", "ktime"))
|
||||
|
||||
take = elem_data_single_string(takes, b"Take", take_name)
|
||||
elem_data_single_string(take, b"FileName", take_name + b".tak")
|
||||
take_loc_time = elem_data_single_int64(take, b"LocalTime", scene_start_ktime)
|
||||
take_loc_time.add_int64(scene_end_ktime)
|
||||
take_ref_time = elem_data_single_int64(take, b"ReferenceTime", scene_start_ktime)
|
||||
take_ref_time.add_int64(scene_end_ktime)
|
||||
|
||||
|
||||
##### "Main" functions. #####
|
||||
FBXSettingsMedia = namedtuple("FBXSettingsMedia", (
|
||||
|
@ -2144,7 +2452,7 @@ FBXSettings = namedtuple("FBXSettings", (
|
|||
"bake_space_transform", "global_matrix_inv", "global_matrix_inv_transposed",
|
||||
"context_objects", "object_types", "use_mesh_modifiers",
|
||||
"mesh_smooth_type", "use_mesh_edges", "use_tspace", "use_armature_deform_only",
|
||||
"use_anim", "use_anim_optimize", "anim_optimize_precision", "use_anim_action_all", "use_default_take",
|
||||
"bake_anim", "bake_anim_step", "bake_anim_simplify_factor",
|
||||
"use_metadata", "media_settings", "use_custom_properties",
|
||||
))
|
||||
|
||||
|
@ -2158,16 +2466,13 @@ def save_single(operator, scene, filepath="",
|
|||
object_types=None,
|
||||
use_mesh_modifiers=True,
|
||||
mesh_smooth_type='FACE',
|
||||
use_armature_deform_only=False,
|
||||
use_anim=True,
|
||||
use_anim_optimize=True,
|
||||
anim_optimize_precision=6,
|
||||
use_anim_action_all=False,
|
||||
bake_anim=True,
|
||||
bake_anim_step=1.0,
|
||||
bake_anim_simplify_factor=1.0,
|
||||
use_metadata=True,
|
||||
path_mode='AUTO',
|
||||
use_mesh_edges=True,
|
||||
use_tspace=True,
|
||||
use_default_take=True,
|
||||
embed_textures=False,
|
||||
use_custom_properties=False,
|
||||
bake_space_transform=False,
|
||||
|
@ -2200,9 +2505,9 @@ def save_single(operator, scene, filepath="",
|
|||
(axis_up, axis_forward), global_matrix, global_scale,
|
||||
bake_space_transform, global_matrix_inv, global_matrix_inv_transposed,
|
||||
context_objects, object_types, use_mesh_modifiers,
|
||||
mesh_smooth_type, use_mesh_edges, use_tspace, use_armature_deform_only,
|
||||
use_anim, use_anim_optimize, anim_optimize_precision, use_anim_action_all, use_default_take,
|
||||
use_metadata, media_settings, use_custom_properties,
|
||||
mesh_smooth_type, use_mesh_edges, use_tspace, False,
|
||||
bake_anim, bake_anim_step, bake_anim_simplify_factor,
|
||||
False, media_settings, use_custom_properties,
|
||||
)
|
||||
|
||||
import bpy_extras.io_utils
|
||||
|
@ -2252,12 +2557,11 @@ def defaults_unity3d():
|
|||
"use_selection": False,
|
||||
"object_types": {'ARMATURE', 'EMPTY', 'MESH'},
|
||||
"use_mesh_modifiers": True,
|
||||
"use_armature_deform_only": True,
|
||||
"use_anim": True,
|
||||
"use_anim_optimize": False,
|
||||
"use_anim_action_all": True,
|
||||
#"use_armature_deform_only": True,
|
||||
"bake_anim": True,
|
||||
#"use_anim_optimize": False,
|
||||
#"use_anim_action_all": True,
|
||||
"batch_mode": 'OFF',
|
||||
"use_default_take": True,
|
||||
# Should really be True, but it can cause problems if a model is already in a scene or prefab
|
||||
# with the old transforms.
|
||||
"bake_space_transform": False,
|
||||
|
|
Loading…
Reference in New Issue