Newer
Older
Bastien Montagne
committed
me.loops.foreach_get("bitangent", t_ln)
lay_nor = elem_data_single_int32(geom, b"LayerElementBinormal", idx)
elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_BINORMAL_VERSION)
elem_data_single_string_unicode(lay_nor, b"Name", name)
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
Bastien Montagne
committed
elem_data_single_float64_array(lay_nor, b"Binormals",
chain(*nors_transformed_gen(t_ln, geom_mat_no)))
Bastien Montagne
committed
# Binormal weights, no idea what it is.
# elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
# Loop tangents.
# NOTE: this is not supported by importer currently.
me.loops.foreach_get("tangent", t_ln)
lay_nor = elem_data_single_int32(geom, b"LayerElementTangent", idx)
elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_TANGENT_VERSION)
elem_data_single_string_unicode(lay_nor, b"Name", name)
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
Bastien Montagne
committed
elem_data_single_float64_array(lay_nor, b"Tangents",
chain(*nors_transformed_gen(t_ln, geom_mat_no)))
Bastien Montagne
committed
# Tangent weights, no idea what it is.
# elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)
del t_ln
# del t_lnw
me.free_tangents()
me.free_normals_split()
Bastien Montagne
committed
# Write VertexColor Layers.
vcolnumber = len(me.vertex_colors)
if vcolnumber:
def _coltuples_gen(raw_cols):
return zip(*(iter(raw_cols),) * 3 + (_infinite_gen(1.0),)) # We need a fake alpha...
t_lc = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 3
for colindex, collayer in enumerate(me.vertex_colors):
collayer.data.foreach_get("color", t_lc)
lay_vcol = elem_data_single_int32(geom, b"LayerElementColor", colindex)
elem_data_single_int32(lay_vcol, b"Version", FBX_GEOMETRY_VCOLOR_VERSION)
elem_data_single_string_unicode(lay_vcol, b"Name", collayer.name)
elem_data_single_string(lay_vcol, b"MappingInformationType", b"ByPolygonVertex")
elem_data_single_string(lay_vcol, b"ReferenceInformationType", b"IndexToDirect")
col2idx = tuple(set(_coltuples_gen(t_lc)))
elem_data_single_float64_array(lay_vcol, b"Colors", chain(*col2idx)) # Flatten again...
col2idx = {col: idx for idx, col in enumerate(col2idx)}
elem_data_single_int32_array(lay_vcol, b"ColorIndex", (col2idx[c] for c in _coltuples_gen(t_lc)))
del col2idx
del t_lc
del _coltuples_gen
# Write UV layers.
# Note: LayerElementTexture is deprecated since FBX 2011 - luckily!
# Textures are now only related to materials, in FBX!
uvnumber = len(me.uv_layers)
if uvnumber:
def _uvtuples_gen(raw_uvs):
return zip(*(iter(raw_uvs),) * 2)
t_luv = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 2
for uvindex, uvlayer in enumerate(me.uv_layers):
uvlayer.data.foreach_get("uv", t_luv)
lay_uv = elem_data_single_int32(geom, b"LayerElementUV", uvindex)
elem_data_single_int32(lay_uv, b"Version", FBX_GEOMETRY_UV_VERSION)
elem_data_single_string_unicode(lay_uv, b"Name", uvlayer.name)
elem_data_single_string(lay_uv, b"MappingInformationType", b"ByPolygonVertex")
elem_data_single_string(lay_uv, b"ReferenceInformationType", b"IndexToDirect")
uv2idx = tuple(set(_uvtuples_gen(t_luv)))
elem_data_single_float64_array(lay_uv, b"UV", chain(*uv2idx)) # Flatten again...
uv2idx = {uv: idx for idx, uv in enumerate(uv2idx)}
elem_data_single_int32_array(lay_uv, b"UVIndex", (uv2idx[uv] for uv in _uvtuples_gen(t_luv)))
del uv2idx
del t_luv
del _uvtuples_gen
# Face's materials.
me_fbxmats_idx = scene_data.mesh_mat_indices.get(me)
if me_fbxmats_idx is not None:
me_blmats = me.materials
if me_fbxmats_idx and me_blmats:
lay_mat = elem_data_single_int32(geom, b"LayerElementMaterial", 0)
elem_data_single_int32(lay_mat, b"Version", FBX_GEOMETRY_MATERIAL_VERSION)
elem_data_single_string(lay_mat, b"Name", b"")
nbr_mats = len(me_fbxmats_idx)
if nbr_mats > 1:
t_pm = array.array(data_types.ARRAY_INT32, (0,)) * len(me.polygons)
me.polygons.foreach_get("material_index", t_pm)
# We have to validate mat indices, and map them to FBX indices.
# Note a mat might not be in me_fbxmats_idx (e.g. node mats are ignored).
blmats_to_fbxmats_idxs = [me_fbxmats_idx[m] for m in me_blmats if m in me_fbxmats_idx]
mat_idx_limit = len(blmats_to_fbxmats_idxs)
def_mat = blmats_to_fbxmats_idxs[0]
_gen = (blmats_to_fbxmats_idxs[m] if m < mat_idx_limit else def_mat for m in t_pm)
t_pm = array.array(data_types.ARRAY_INT32, _gen)
elem_data_single_string(lay_mat, b"MappingInformationType", b"ByPolygon")
# XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one
# value per polygon...
# But looks like FBX expects it to be IndexToDirect here (maybe because materials are already
# indices??? *sigh*).
elem_data_single_string(lay_mat, b"ReferenceInformationType", b"IndexToDirect")
elem_data_single_int32_array(lay_mat, b"Materials", t_pm)
del t_pm
else:
elem_data_single_string(lay_mat, b"MappingInformationType", b"AllSame")
elem_data_single_string(lay_mat, b"ReferenceInformationType", b"IndexToDirect")
elem_data_single_int32_array(lay_mat, b"Materials", [0])
# And the "layer TOC"...
layer = elem_data_single_int32(geom, b"Layer", 0)
elem_data_single_int32(layer, b"Version", FBX_GEOMETRY_LAYER_VERSION)
Bastien Montagne
committed
if write_normals:
lay_nor = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_nor, b"Type", b"LayerElementNormal")
elem_data_single_int32(lay_nor, b"TypedIndex", 0)
if tspacenumber:
lay_binor = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_binor, b"Type", b"LayerElementBinormal")
elem_data_single_int32(lay_binor, b"TypedIndex", 0)
lay_tan = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_tan, b"Type", b"LayerElementTangent")
elem_data_single_int32(lay_tan, b"TypedIndex", 0)
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
if smooth_type in {'FACE', 'EDGE'}:
lay_smooth = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_smooth, b"Type", b"LayerElementSmoothing")
elem_data_single_int32(lay_smooth, b"TypedIndex", 0)
if vcolnumber:
lay_vcol = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_vcol, b"Type", b"LayerElementColor")
elem_data_single_int32(lay_vcol, b"TypedIndex", 0)
if uvnumber:
lay_uv = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_uv, b"Type", b"LayerElementUV")
elem_data_single_int32(lay_uv, b"TypedIndex", 0)
if me_fbxmats_idx is not None:
lay_mat = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_mat, b"Type", b"LayerElementMaterial")
elem_data_single_int32(lay_mat, b"TypedIndex", 0)
# Add other uv and/or vcol layers...
for vcolidx, uvidx, tspaceidx in zip_longest(range(1, vcolnumber), range(1, uvnumber), range(1, tspacenumber),
fillvalue=0):
layer = elem_data_single_int32(geom, b"Layer", max(vcolidx, uvidx))
elem_data_single_int32(layer, b"Version", FBX_GEOMETRY_LAYER_VERSION)
if vcolidx:
lay_vcol = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_vcol, b"Type", b"LayerElementColor")
elem_data_single_int32(lay_vcol, b"TypedIndex", vcolidx)
if uvidx:
lay_uv = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_uv, b"Type", b"LayerElementUV")
elem_data_single_int32(lay_uv, b"TypedIndex", uvidx)
if tspaceidx:
lay_binor = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_binor, b"Type", b"LayerElementBinormal")
elem_data_single_int32(lay_binor, b"TypedIndex", tspaceidx)
lay_tan = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_tan, b"Type", b"LayerElementTangent")
elem_data_single_int32(lay_tan, b"TypedIndex", tspaceidx)
# Shape keys...
fbx_data_mesh_shapes_elements(root, me_obj, me, scene_data, tmpl, props)
elem_props_template_finalize(tmpl, props)
done_meshes.add(me_key)
Bastien Montagne
committed
def check_skip_material(mat):
"""Simple helper to check whether we actually support exporting that material or not"""
return mat.type not in {'SURFACE'} or mat.use_nodes
def fbx_data_material_elements(root, mat, scene_data):
"""
Write the Material data block.
"""
ambient_color = (0.0, 0.0, 0.0)
if scene_data.data_world:
ambient_color = next(iter(scene_data.data_world.keys())).ambient_color
mat_key, _objs = scene_data.data_materials[mat]
Bastien Montagne
committed
skip_mat = check_skip_material(mat)
mat_type = b"Phong"
Bastien Montagne
committed
if not skip_mat and mat.specular_shader not in {'COOKTORR', 'PHONG', 'BLINN'}:
mat_type = b"Lambert"
fbx_mat = elem_data_single_int64(root, b"Material", get_fbx_uuid_from_key(mat_key))
fbx_mat.add_string(fbx_name_class(mat.name.encode(), b"Material"))
fbx_mat.add_string(b"")
elem_data_single_int32(fbx_mat, b"Version", FBX_MATERIAL_VERSION)
# those are not yet properties, it seems...
elem_data_single_string(fbx_mat, b"ShadingModel", mat_type)
elem_data_single_int32(fbx_mat, b"MultiLayer", 0) # Should be bool...
Bastien Montagne
committed
tmpl = elem_props_template_init(scene_data.templates, b"Material")
props = elem_properties(fbx_mat)
Bastien Montagne
committed
Bastien Montagne
committed
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
if not skip_mat:
elem_props_template_set(tmpl, props, "p_string", b"ShadingModel", mat_type.decode())
elem_props_template_set(tmpl, props, "p_color", b"EmissiveColor", mat.diffuse_color)
elem_props_template_set(tmpl, props, "p_number", b"EmissiveFactor", mat.emit)
elem_props_template_set(tmpl, props, "p_color", b"AmbientColor", ambient_color)
elem_props_template_set(tmpl, props, "p_number", b"AmbientFactor", mat.ambient)
elem_props_template_set(tmpl, props, "p_color", b"DiffuseColor", mat.diffuse_color)
elem_props_template_set(tmpl, props, "p_number", b"DiffuseFactor", mat.diffuse_intensity)
elem_props_template_set(tmpl, props, "p_color", b"TransparentColor",
mat.diffuse_color if mat.use_transparency else (1.0, 1.0, 1.0))
elem_props_template_set(tmpl, props, "p_number", b"TransparencyFactor",
1.0 - mat.alpha if mat.use_transparency else 0.0)
elem_props_template_set(tmpl, props, "p_number", b"Opacity", mat.alpha if mat.use_transparency else 1.0)
elem_props_template_set(tmpl, props, "p_vector_3d", b"NormalMap", (0.0, 0.0, 0.0))
# Not sure about those...
"""
b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"),
b"BumpFactor": (1.0, "p_double"),
b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"),
b"DisplacementFactor": (0.0, "p_double"),
"""
if mat_type == b"Phong":
elem_props_template_set(tmpl, props, "p_color", b"SpecularColor", mat.specular_color)
elem_props_template_set(tmpl, props, "p_number", b"SpecularFactor", mat.specular_intensity / 2.0)
# See Material template about those two!
elem_props_template_set(tmpl, props, "p_number", b"Shininess", (mat.specular_hardness - 1.0) / 5.10)
elem_props_template_set(tmpl, props, "p_number", b"ShininessExponent", (mat.specular_hardness - 1.0) / 5.10)
elem_props_template_set(tmpl, props, "p_color", b"ReflectionColor", mat.mirror_color)
elem_props_template_set(tmpl, props, "p_number", b"ReflectionFactor",
mat.raytrace_mirror.reflect_factor if mat.raytrace_mirror.use else 0.0)
Bastien Montagne
committed
elem_props_template_finalize(tmpl, props)
if scene_data.settings.use_custom_props:
fbx_data_element_custom_properties(props, mat)
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
def _gen_vid_path(img, scene_data):
msetts = scene_data.settings.media_settings
fname_rel = bpy_extras.io_utils.path_reference(img.filepath, msetts.base_src, msetts.base_dst, msetts.path_mode,
msetts.subdir, msetts.copy_set, img.library)
fname_abs = os.path.normpath(os.path.abspath(os.path.join(msetts.base_dst, fname_rel)))
return fname_abs, fname_rel
def fbx_data_texture_file_elements(root, tex, scene_data):
"""
Write the (file) Texture data block.
"""
# XXX All this is very fuzzy to me currently...
# Textures do not seem to use properties as much as they could.
# For now assuming most logical and simple stuff.
tex_key, _mats = scene_data.data_textures[tex]
img = tex.texture.image
fname_abs, fname_rel = _gen_vid_path(img, scene_data)
fbx_tex = elem_data_single_int64(root, b"Texture", get_fbx_uuid_from_key(tex_key))
fbx_tex.add_string(fbx_name_class(tex.name.encode(), b"Texture"))
fbx_tex.add_string(b"")
elem_data_single_string(fbx_tex, b"Type", b"TextureVideoClip")
elem_data_single_int32(fbx_tex, b"Version", FBX_TEXTURE_VERSION)
elem_data_single_string(fbx_tex, b"TextureName", fbx_name_class(tex.name.encode(), b"Texture"))
elem_data_single_string(fbx_tex, b"Media", fbx_name_class(img.name.encode(), b"Video"))
elem_data_single_string_unicode(fbx_tex, b"FileName", fname_abs)
elem_data_single_string_unicode(fbx_tex, b"RelativeFilename", fname_rel)
alpha_source = 0 # None
if img.use_alpha:
if tex.texture.use_calculate_alpha:
alpha_source = 1 # RGBIntensity as alpha.
else:
alpha_source = 2 # Black, i.e. alpha channel.
# BlendMode not useful for now, only affects layered textures afaics.
Bastien Montagne
committed
mapping = 0 # UV.
uvset = None
if tex.texture_coords in {'ORCO'}: # XXX Others?
if tex.mapping in {'FLAT'}:
mapping = 1 # Planar
elif tex.mapping in {'CUBE'}:
mapping = 4 # Box
elif tex.mapping in {'TUBE'}:
mapping = 3 # Cylindrical
elif tex.mapping in {'SPHERE'}:
mapping = 2 # Spherical
elif tex.texture_coords in {'UV'}:
Bastien Montagne
committed
mapping = 0 # UV
# Yuck, UVs are linked by mere names it seems... :/
uvset = tex.uv_layer
wrap_mode = 1 # Clamp
if tex.texture.extension in {'REPEAT'}:
wrap_mode = 0 # Repeat
Bastien Montagne
committed
tmpl = elem_props_template_init(scene_data.templates, b"TextureFile")
props = elem_properties(fbx_tex)
elem_props_template_set(tmpl, props, "p_enum", b"AlphaSource", alpha_source)
elem_props_template_set(tmpl, props, "p_bool", b"PremultiplyAlpha",
img.alpha_mode in {'STRAIGHT'}) # Or is it PREMUL?
elem_props_template_set(tmpl, props, "p_enum", b"CurrentMappingType", mapping)
Bastien Montagne
committed
if uvset is not None:
elem_props_template_set(tmpl, props, "p_string", b"UVSet", uvset)
elem_props_template_set(tmpl, props, "p_enum", b"WrapModeU", wrap_mode)
elem_props_template_set(tmpl, props, "p_enum", b"WrapModeV", wrap_mode)
elem_props_template_set(tmpl, props, "p_vector_3d", b"Translation", tex.offset)
elem_props_template_set(tmpl, props, "p_vector_3d", b"Scaling", tex.scale)
Bastien Montagne
committed
# UseMaterial should always be ON imho.
elem_props_template_set(tmpl, props, "p_bool", b"UseMaterial", True)
elem_props_template_set(tmpl, props, "p_bool", b"UseMipMap", tex.texture.use_mipmap)
Bastien Montagne
committed
elem_props_template_finalize(tmpl, props)
# Custom properties.
if scene_data.settings.use_custom_props:
fbx_data_element_custom_properties(props, tex.texture)
def fbx_data_video_elements(root, vid, scene_data):
"""
Write the actual image data block.
"""
vid_key, _texs = scene_data.data_videos[vid]
fname_abs, fname_rel = _gen_vid_path(vid, scene_data)
fbx_vid = elem_data_single_int64(root, b"Video", get_fbx_uuid_from_key(vid_key))
fbx_vid.add_string(fbx_name_class(vid.name.encode(), b"Video"))
fbx_vid.add_string(b"Clip")
elem_data_single_string(fbx_vid, b"Type", b"Clip")
# XXX No Version???
tmpl = elem_props_template_init(scene_data.templates, b"Video")
props = elem_properties(fbx_vid)
elem_props_template_set(tmpl, props, "p_string_url", b"Path", fname_abs)
elem_props_template_finalize(tmpl, props)
elem_data_single_int32(fbx_vid, b"UseMipMap", 0)
elem_data_single_string_unicode(fbx_vid, b"Filename", fname_abs)
elem_data_single_string_unicode(fbx_vid, b"RelativeFilename", fname_rel)
if scene_data.settings.media_settings.embed_textures:
if vid.packed_file is not None:
elem_data_single_bytes(fbx_vid, b"Content", vid.packed_file.data)
else:
filepath = bpy.path.abspath(vid.filepath)
try:
with open(filepath, 'br') as f:
elem_data_single_bytes(fbx_vid, b"Content", f.read())
except Exception as e:
print("WARNING: embedding file {} failed ({})".format(filepath, e))
elem_data_single_bytes(fbx_vid, b"Content", b"")
elem_data_single_bytes(fbx_vid, b"Content", b"")
Bastien Montagne
committed
def fbx_data_armature_elements(root, arm_obj, scene_data):
"""
Write:
* Bones "data" (NodeAttribute::LimbNode, contains pretty much nothing!).
* Deformers (i.e. Skin), bind between an armature and a mesh.
** SubDeformers (i.e. Cluster), one per bone/vgroup pair.
* BindPose.
Note armature itself has no data, it is a mere "Null" Model...
"""
Bastien Montagne
committed
mat_world_arm = arm_obj.fbx_object_matrix(scene_data, global_space=True)
bones = tuple(bo_obj for bo_obj in arm_obj.bones if bo_obj in scene_data.objects)
Bastien Montagne
committed
bone_radius_scale = scene_data.settings.global_scale * 33.0
for bo_obj in bones:
Bastien Montagne
committed
bo = bo_obj.bdata
bo_data_key = scene_data.data_bones[bo_obj]
fbx_bo = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(bo_data_key))
fbx_bo.add_string(fbx_name_class(bo.name.encode(), b"NodeAttribute"))
fbx_bo.add_string(b"LimbNode")
elem_data_single_string(fbx_bo, b"TypeFlags", b"Skeleton")
Bastien Montagne
committed
tmpl = elem_props_template_init(scene_data.templates, b"Bone")
props = elem_properties(fbx_bo)
Bastien Montagne
committed
elem_props_template_set(tmpl, props, "p_double", b"Size", bo.head_radius * bone_radius_scale)
Bastien Montagne
committed
elem_props_template_finalize(tmpl, props)
# Custom properties.
if scene_data.settings.use_custom_props:
fbx_data_element_custom_properties(props, bo)
Bastien Montagne
committed
# Store Blender bone length - XXX Not much useful actually :/
# (LimbLength can't be used because it is a scale factor 0-1 for the parent-child distance:
# http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/cpp_ref/class_fbx_skeleton.html#a9bbe2a70f4ed82cd162620259e649f0f )
# elem_props_set(props, "p_double", "BlenderBoneLength".encode(), (bo.tail_local - bo.head_local).length, custom=True)
# Skin deformers and BindPoses.
# Note: we might also use Deformers for our "parent to vertex" stuff???
deformer = scene_data.data_deformers_skin.get(arm_obj, None)
if deformer is not None:
Bastien Montagne
committed
for me, (skin_key, ob_obj, clusters) in deformer.items():
mat_world_obj, mat_world_bones = fbx_data_bindpose_element(root, ob_obj, me, scene_data, arm_obj, bones)
fbx_skin = elem_data_single_int64(root, b"Deformer", get_fbx_uuid_from_key(skin_key))
Bastien Montagne
committed
fbx_skin.add_string(fbx_name_class(arm_obj.name.encode(), b"Deformer"))
fbx_skin.add_string(b"Skin")
elem_data_single_int32(fbx_skin, b"Version", FBX_DEFORMER_SKIN_VERSION)
elem_data_single_float64(fbx_skin, b"Link_DeformAcuracy", 50.0) # Only vague idea what it is...
Bastien Montagne
committed
# Pre-process vertex weights (also to check vertices assigned ot more than four bones).
Bastien Montagne
committed
ob = ob_obj.bdata
bo_vg_idx = {bo_obj.bdata.name: ob.vertex_groups[bo_obj.bdata.name].index
for bo_obj in clusters.keys() if bo_obj.bdata.name in ob.vertex_groups}
Bastien Montagne
committed
valid_idxs = set(bo_vg_idx.values())
Bastien Montagne
committed
vgroups = {vg.index: OrderedDict() for vg in ob.vertex_groups}
Bastien Montagne
committed
verts_vgroups = (sorted(((vg.group, vg.weight) for vg in v.groups if vg.weight and vg.group in valid_idxs),
key=lambda e: e[1], reverse=True)
for v in me.vertices)
for idx, vgs in enumerate(verts_vgroups):
for vg_idx, w in vgs:
vgroups[vg_idx][idx] = w
Bastien Montagne
committed
for bo_obj, clstr_key in clusters.items():
bo = bo_obj.bdata
# Find which vertices are affected by this bone/vgroup pair, and matching weights.
Bastien Montagne
committed
# Note we still write a cluster for bones not affecting the mesh, to get 'rest pose' data
# (the TransformBlah matrices).
vg_idx = bo_vg_idx.get(bo.name, None)
indices, weights = ((), ()) if vg_idx is None or not vgroups[vg_idx] else zip(*vgroups[vg_idx].items())
# Create the cluster.
fbx_clstr = elem_data_single_int64(root, b"Deformer", get_fbx_uuid_from_key(clstr_key))
fbx_clstr.add_string(fbx_name_class(bo.name.encode(), b"SubDeformer"))
fbx_clstr.add_string(b"Cluster")
elem_data_single_int32(fbx_clstr, b"Version", FBX_DEFORMER_CLUSTER_VERSION)
# No idea what that user data might be...
fbx_userdata = elem_data_single_string(fbx_clstr, b"UserData", b"")
fbx_userdata.add_string(b"")
Bastien Montagne
committed
elem_data_single_int32_array(fbx_clstr, b"Indexes", indices)
elem_data_single_float64_array(fbx_clstr, b"Weights", weights)
# Transform, TransformLink and TransformAssociateModel matrices...
# They seem to be doublons of BindPose ones??? Have armature (associatemodel) in addition, though.
# WARNING! Even though official FBX API presents Transform in global space,
# **it is stored in bone space in FBX data!** See:
# http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return-
# by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/
elem_data_single_float64_array(fbx_clstr, b"Transform",
matrix4_to_array(mat_world_bones[bo_obj].inverted_safe() * mat_world_obj))
elem_data_single_float64_array(fbx_clstr, b"TransformLink", matrix4_to_array(mat_world_bones[bo_obj]))
elem_data_single_float64_array(fbx_clstr, b"TransformAssociateModel", matrix4_to_array(mat_world_arm))
Bastien Montagne
committed
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
def fbx_data_leaf_bone_elements(root, scene_data):
# Write a dummy leaf bone that is used by applications to show the length of the last bone in a chain
for (node_name, _par_uuid, node_uuid, attr_uuid, matrix, hide, size) in scene_data.data_leaf_bones:
# Bone 'data'...
fbx_bo = elem_data_single_int64(root, b"NodeAttribute", attr_uuid)
fbx_bo.add_string(fbx_name_class(node_name.encode(), b"NodeAttribute"))
fbx_bo.add_string(b"LimbNode")
elem_data_single_string(fbx_bo, b"TypeFlags", b"Skeleton")
tmpl = elem_props_template_init(scene_data.templates, b"Bone")
props = elem_properties(fbx_bo)
elem_props_template_set(tmpl, props, "p_double", b"Size", size)
elem_props_template_finalize(tmpl, props)
# And bone object.
model = elem_data_single_int64(root, b"Model", node_uuid)
model.add_string(fbx_name_class(node_name.encode(), b"Model"))
model.add_string(b"LimbNode")
elem_data_single_int32(model, b"Version", FBX_MODELS_VERSION)
# Object transform info.
loc, rot, scale = matrix.decompose()
rot = rot.to_euler('XYZ')
rot = tuple(convert_rad_to_deg_iter(rot))
tmpl = elem_props_template_init(scene_data.templates, b"Model")
# For now add only loc/rot/scale...
props = elem_properties(model)
elem_props_template_set(tmpl, props, "p_lcl_translation", b"Lcl Translation", loc)
elem_props_template_set(tmpl, props, "p_lcl_rotation", b"Lcl Rotation", rot)
elem_props_template_set(tmpl, props, "p_lcl_scaling", b"Lcl Scaling", scale)
elem_props_template_set(tmpl, props, "p_visibility", b"Visibility", float(not hide))
# Absolutely no idea what this is, but seems mandatory for validity of the file, and defaults to
# invalid -1 value...
elem_props_template_set(tmpl, props, "p_integer", b"DefaultAttributeIndex", 0)
elem_props_template_set(tmpl, props, "p_enum", b"InheritType", 1) # RSrs
# Those settings would obviously need to be edited in a complete version of the exporter, may depends on
# object type, etc.
elem_data_single_int32(model, b"MultiLayer", 0)
elem_data_single_int32(model, b"MultiTake", 0)
elem_data_single_bool(model, b"Shading", True)
elem_data_single_string(model, b"Culling", b"CullingOff")
elem_props_template_finalize(tmpl, props)
Bastien Montagne
committed
def fbx_data_object_elements(root, ob_obj, scene_data):
"""
Write the Object (Model) data blocks.
Bastien Montagne
committed
Note this "Model" can also be bone or dupli!
"""
obj_type = b"Null" # default, sort of empty...
Bastien Montagne
committed
if ob_obj.is_bone:
obj_type = b"LimbNode"
Bastien Montagne
committed
elif (ob_obj.type == 'ARMATURE'):
obj_type = b"Root"
Bastien Montagne
committed
elif (ob_obj.type in BLENDER_OBJECT_TYPES_MESHLIKE):
Bastien Montagne
committed
elif (ob_obj.type == 'LAMP'):
Bastien Montagne
committed
elif (ob_obj.type == 'CAMERA'):
Bastien Montagne
committed
model = elem_data_single_int64(root, b"Model", ob_obj.fbx_uuid)
model.add_string(fbx_name_class(ob_obj.name.encode(), b"Model"))
model.add_string(obj_type)
elem_data_single_int32(model, b"Version", FBX_MODELS_VERSION)
# Object transform info.
Bastien Montagne
committed
loc, rot, scale, matrix, matrix_rot = ob_obj.fbx_object_tx(scene_data)
Bastien Montagne
committed
tmpl = elem_props_template_init(scene_data.templates, b"Model")
# For now add only loc/rot/scale...
props = elem_properties(model)
elem_props_template_set(tmpl, props, "p_lcl_translation", b"Lcl Translation", loc)
elem_props_template_set(tmpl, props, "p_lcl_rotation", b"Lcl Rotation", rot)
elem_props_template_set(tmpl, props, "p_lcl_scaling", b"Lcl Scaling", scale)
Bastien Montagne
committed
elem_props_template_set(tmpl, props, "p_visibility", b"Visibility", float(not ob_obj.hide))
Bastien Montagne
committed
# Absolutely no idea what this is, but seems mandatory for validity of the file, and defaults to
# invalid -1 value...
elem_props_template_set(tmpl, props, "p_integer", b"DefaultAttributeIndex", 0)
elem_props_template_set(tmpl, props, "p_enum", b"InheritType", 1) # RSrs
if scene_data.settings.use_custom_props:
Bastien Montagne
committed
fbx_data_element_custom_properties(props, ob_obj.bdata)
# Those settings would obviously need to be edited in a complete version of the exporter, may depends on
# object type, etc.
elem_data_single_int32(model, b"MultiLayer", 0)
elem_data_single_int32(model, b"MultiTake", 0)
elem_data_single_bool(model, b"Shading", True)
elem_data_single_string(model, b"Culling", b"CullingOff")
Bastien Montagne
committed
if obj_type == b"Camera":
# Why, oh why are FBX cameras such a mess???
# And WHY add camera data HERE??? Not even sure this is needed...
render = scene_data.scene.render
width = render.resolution_x * 1.0
height = render.resolution_y * 1.0
elem_props_template_set(tmpl, props, "p_enum", b"ResolutionMode", 0) # Don't know what it means
Bastien Montagne
committed
elem_props_template_set(tmpl, props, "p_double", b"AspectW", width)
elem_props_template_set(tmpl, props, "p_double", b"AspectH", height)
elem_props_template_set(tmpl, props, "p_bool", b"ViewFrustum", True)
elem_props_template_set(tmpl, props, "p_enum", b"BackgroundMode", 0) # Don't know what it means
elem_props_template_set(tmpl, props, "p_bool", b"ForegroundTransparent", True)
Bastien Montagne
committed
elem_props_template_finalize(tmpl, props)
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
return (int(v) for v in convert_sec_to_ktime_iter((f / fps for f, _v in keys)))
Bastien Montagne
committed
# Animation stacks.
for astack_key, alayers, alayer_key, name, f_start, f_end in animations:
astack = elem_data_single_int64(root, b"AnimationStack", get_fbx_uuid_from_key(astack_key))
Bastien Montagne
committed
astack.add_string(fbx_name_class(name, b"AnimStack"))
astack.add_string(b"")
astack_tmpl = elem_props_template_init(scene_data.templates, b"AnimationStack")
astack_props = elem_properties(astack)
r = scene_data.scene.render
fps = r.fps / r.fps_base
start = int(convert_sec_to_ktime(f_start / fps))
end = int(convert_sec_to_ktime(f_end / fps))
Bastien Montagne
committed
elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"LocalStart", start)
elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"LocalStop", end)
elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"ReferenceStart", start)
elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"ReferenceStop", end)
elem_props_template_finalize(astack_tmpl, astack_props)
# For now, only one layer for all animations.
alayer = elem_data_single_int64(root, b"AnimationLayer", get_fbx_uuid_from_key(alayer_key))
Bastien Montagne
committed
alayer.add_string(fbx_name_class(name, b"AnimLayer"))
alayer.add_string(b"")
Bastien Montagne
committed
for ob_obj, (alayer_key, acurvenodes) in alayers.items():
Bastien Montagne
committed
# Animation layer.
# alayer = elem_data_single_int64(root, b"AnimationLayer", get_fbx_uuid_from_key(alayer_key))
Bastien Montagne
committed
# alayer.add_string(fbx_name_class(ob_obj.name.encode(), b"AnimLayer"))
Bastien Montagne
committed
# alayer.add_string(b"")
Bastien Montagne
committed
for fbx_prop, (acurvenode_key, acurves, acurvenode_name) in acurvenodes.items():
# Animation curve node.
acurvenode = elem_data_single_int64(root, b"AnimationCurveNode", get_fbx_uuid_from_key(acurvenode_key))
Bastien Montagne
committed
acurvenode.add_string(fbx_name_class(acurvenode_name.encode(), b"AnimCurveNode"))
acurvenode.add_string(b"")
acn_tmpl = elem_props_template_init(scene_data.templates, b"AnimationCurveNode")
acn_props = elem_properties(acurvenode)
for fbx_item, (acurve_key, def_value, keys, _acurve_valid) in acurves.items():
Bastien Montagne
committed
elem_props_template_set(acn_tmpl, acn_props, "p_number", fbx_item.encode(),
def_value, animatable=True)
Bastien Montagne
committed
# Only create Animation curve if needed!
if keys:
acurve = elem_data_single_int64(root, b"AnimationCurve", get_fbx_uuid_from_key(acurve_key))
Bastien Montagne
committed
acurve.add_string(fbx_name_class(b"", b"AnimCurve"))
acurve.add_string(b"")
# key attributes...
nbr_keys = len(keys)
# flags...
keyattr_flags = (
Bastien Montagne
committed
1 << 2 | # interpolation mode, 1 = constant, 2 = linear, 3 = cubic.
Bastien Montagne
committed
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", def_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", (nbr_keys,))
elem_props_template_finalize(acn_tmpl, acn_props)
Bastien Montagne
committed
# ##### Top-level FBX data container. #####
def fbx_mat_properties_from_texture(tex):
"""
Returns a set of FBX metarial properties that are affected by the given texture.
Quite obviously, this is a fuzzy and far-from-perfect mapping! Amounts of influence are completely lost, e.g.
Note tex is actually expected to be a texture slot.
"""
# Mapping Blender -> FBX (blend_use_name, blend_fact_name, fbx_name).
blend_to_fbx = (
# Lambert & Phong...
("diffuse", "diffuse", b"DiffuseFactor"),
("color_diffuse", "diffuse_color", b"DiffuseColor"),
("alpha", "alpha", b"TransparencyFactor"),
("diffuse", "diffuse", b"TransparentColor"), # Uses diffuse color in Blender!
("emit", "emit", b"EmissiveFactor"),
("diffuse", "diffuse", b"EmissiveColor"), # Uses diffuse color in Blender!
("ambient", "ambient", b"AmbientFactor"),
# ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore...
Bastien Montagne
committed
("normal", "normal", b"NormalMap"),
# Note: unsure about those... :/
# ("", "", b"Bump"),
# ("", "", b"BumpFactor"),
# ("", "", b"DisplacementColor"),
# ("", "", b"DisplacementFactor"),
# Phong only.
("specular", "specular", b"SpecularFactor"),
("color_spec", "specular_color", b"SpecularColor"),
# See Material template about those two!
("hardness", "hardness", b"Shininess"),
("hardness", "hardness", b"ShininessExponent"),
("mirror", "mirror", b"ReflectionColor"),
("raymir", "raymir", b"ReflectionFactor"),
)
tex_fbx_props = set()
for use_map_name, name_factor, fbx_prop_name in blend_to_fbx:
Bastien Montagne
committed
# Always export enabled textures, even if they have a null influence...
if getattr(tex, "use_map_" + use_map_name):
tex_fbx_props.add(fbx_prop_name)
return tex_fbx_props
Bastien Montagne
committed
def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
data_bones, data_deformers_skin, arm_parents):
"""
Create skeleton from armature/bones (NodeAttribute/LimbNode and Model/LimbNode), and for each deformed mesh,
create Pose/BindPose(with sub PoseNode) and Deformer/Skin(with Deformer/SubDeformer/Cluster).
Also supports "parent to bone" (simple parent to Model/LimbNode).
arm_parents is a set of tuples (armature, object) for all successful armature bindings.
"""
Bastien Montagne
committed
arm_data = arm_obj.bdata.data
bones = OrderedDict()
Bastien Montagne
committed
for bo in arm_obj.bones:
if settings.use_armature_deform_only:
if bo.bdata.use_deform:
bones[bo] = True
bo_par = bo.parent
while bo_par.is_bone:
bones[bo_par] = True
bo_par = bo_par.parent
elif bo not in bones: # Do not override if already set in the loop above!
bones[bo] = False
else:
bones[bo] = True
bones = OrderedDict((bo, None) for bo, use in bones.items() if use)
Bastien Montagne
committed
if not bones:
return
data_bones.update((bo, get_blender_bone_key(arm_obj.bdata, bo.bdata)) for bo in bones)
Bastien Montagne
committed
for ob_obj in objects:
Bastien Montagne
committed
if not ob_obj.is_deformed_by_armature(arm_obj):
continue
# Always handled by an Armature modifier...
found = False
for mod in ob_obj.bdata.modifiers:
if mod.type not in {'ARMATURE'}:
continue
# We only support vertex groups binding method, not bone envelopes one!
if mod.object == arm_obj.bdata and mod.use_vertex_groups:
found = True
break
if not found:
continue
Bastien Montagne
committed
# Now we have a mesh using this armature.
# Note: bindpose have no relations at all (no connections), so no need for any preprocess for them.
# Create skin & clusters relations (note skins are connected to geometry, *not* model!).
_key, me, _free = data_meshes[ob_obj]
Bastien Montagne
committed
clusters = OrderedDict((bo, get_blender_bone_cluster_key(arm_obj.bdata, me, bo.bdata)) for bo in bones)
data_deformers_skin.setdefault(arm_obj, OrderedDict())[me] = (get_blender_armature_skin_key(arm_obj.bdata, me),
ob_obj, clusters)
# We don't want a regular parent relationship for those in FBX...
Bastien Montagne
committed
arm_parents.add((arm_obj, ob_obj))
# Needed to handle matrices/spaces (since we do not parent them to 'armature' in FBX :/ ).
ob_obj.parented_to_armature = True
Bastien Montagne
committed
objects.update(bones)
Bastien Montagne
committed
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
def fbx_generate_leaf_bones(settings, data_bones):
# find which bons have no children
child_count = {bo: 0 for bo, _bo_key in data_bones.items()}
for bo, _bo_key in data_bones.items():
if bo.parent and bo.parent.is_bone:
child_count[bo.parent] += 1
bone_radius_scale = settings.global_scale * 33.0
# generate bone data
leaf_parents = [bo for bo, count in child_count.items() if count == 0]
leaf_bones = []
for parent in leaf_parents:
node_name = parent.name + "_end"
parent_uuid = parent.fbx_uuid
node_uuid = get_fbx_uuid_from_key(node_name + "_node")
attr_uuid = get_fbx_uuid_from_key(node_name + "_nodeattr")
hide = parent.hide
size = parent.bdata.head_radius * bone_radius_scale
bone_length = (parent.bdata.tail_local - parent.bdata.head_local).length
matrix = Matrix.Translation((0, bone_length, 0))
if settings.bone_correction_matrix_inv:
matrix = settings.bone_correction_matrix_inv * matrix
if settings.bone_correction_matrix:
matrix = matrix * settings.bone_correction_matrix
leaf_bones.append((node_name, parent_uuid, node_uuid, attr_uuid, matrix, hide, size))
return leaf_bones
def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=None, force_keep=False):
Bastien Montagne
committed
Generate animation data (a single AnimStack) from objects, for a given frame range.
bake_step = scene_data.settings.bake_anim_step
scene = scene_data.scene
meshes = scene_data.data_meshes
if objects is not None:
Bastien Montagne
committed
# Add bones and duplis!
Bastien Montagne
committed
for ob_obj in tuple(objects):
if not ob_obj.is_object:
Bastien Montagne
committed
continue
Bastien Montagne
committed
if ob_obj.type == 'ARMATURE':
objects |= {bo_obj for bo_obj in ob_obj.bones if bo_obj in scene_data.objects}
Bastien Montagne
committed
ob_obj.dupli_list_create(scene, 'RENDER')
for dp_obj in ob_obj.dupli_list:
if dp_obj in scene_data.objects:
objects.add(dp_obj)
ob_obj.dupli_list_clear()
Bastien Montagne
committed
objects = scene_data.objects
back_currframe = scene.frame_current
animdata_ob = OrderedDict((ob_obj, (AnimationCurveNodeWrapper(ob_obj.key, 'LCL_TRANSLATION', (0.0, 0.0, 0.0)),
AnimationCurveNodeWrapper(ob_obj.key, 'LCL_ROTATION', (0.0, 0.0, 0.0)),
AnimationCurveNodeWrapper(ob_obj.key, 'LCL_SCALING', (1.0, 1.0, 1.0))))
for ob_obj in objects)
animdata_shapes = OrderedDict()
for me, (me_key, _shapes_key, shapes) in scene_data.data_deformers_shape.items():
# Ignore absolute shape keys for now!
if not me.shape_keys.use_relative:
continue
for shape, (channel_key, geom_key, _shape_verts_co, _shape_verts_idx) in shapes.items():
acnode = AnimationCurveNodeWrapper(channel_key, 'SHAPE_KEY', (0.0,))
# Sooooo happy to have to twist again like a mad snake... Yes, we need to write those curves twice. :/
acnode.add_group(me_key, shape.name, shape.name, (shape.name,))
animdata_shapes[channel_key] = (acnode, me, shape)
Bastien Montagne
committed
p_rots = {}
Bastien Montagne
committed
currframe = f_start
Bastien Montagne
committed
while currframe <= f_end:
real_currframe = currframe - f_start if start_zero else currframe
scene.frame_set(int(currframe), currframe - int(currframe))
Bastien Montagne
committed
for ob_obj in animdata_ob:
Bastien Montagne
committed
ob_obj.dupli_list_create(scene, 'RENDER')
for ob_obj, (anim_loc, anim_rot, anim_scale) in animdata_ob.items():
Bastien Montagne
committed
# We compute baked loc/rot/scale for all objects (rot being euler-compat with previous value!).
Bastien Montagne
committed
p_rot = p_rots.get(ob_obj, None)
loc, rot, scale, _m, _mr = ob_obj.fbx_object_tx(scene_data, rot_euler_compat=p_rot)
p_rots[ob_obj] = rot
anim_loc.add_keyframe(real_currframe, loc)
anim_rot.add_keyframe(real_currframe, tuple(convert_rad_to_deg_iter(rot)))
anim_scale.add_keyframe(real_currframe, scale)
Bastien Montagne
committed
for ob_obj in objects:
ob_obj.dupli_list_clear()
for anim_shape, me, shape in animdata_shapes.values():
anim_shape.add_keyframe(real_currframe, (shape.value * 100.0,))
currframe += bake_step
scene.frame_set(back_currframe, 0.0)
Bastien Montagne
committed
animations = OrderedDict()
simplify_fac = scene_data.settings.bake_anim_simplify_factor
# And now, produce final data (usable by FBX export code)
# Objects-like loc/rot/scale...
for ob_obj, anims in animdata_ob.items():
for anim in anims:
anim.simplify(simplify_fac, bake_step, force_keep)
Bastien Montagne
committed
if not anim:
continue
for obj_key, group_key, group, fbx_group, fbx_gname in anim.get_final_data(scene, ref_id, force_keep):
anim_data = animations.get(obj_key)
if anim_data is None:
anim_data = animations[obj_key] = ("dummy_unused_key", OrderedDict())
anim_data[1][fbx_group] = (group_key, group, fbx_gname)
# And meshes' shape keys.
for channel_key, (anim_shape, me, shape) in animdata_shapes.items():
Bastien Montagne
committed
final_keys = OrderedDict()
anim_shape.simplify(simplify_fac, bake_step, force_keep)
if not anim_shape:
continue
for elem_key, group_key, group, fbx_group, fbx_gname in anim_shape.get_final_data(scene, ref_id, force_keep):
anim_data = animations.get(elem_key)
if anim_data is None:
anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict())
anim_data[1][fbx_group] = (group_key, group, fbx_gname)
Bastien Montagne
committed
astack_key = get_blender_anim_stack_key(scene, ref_id)
alayer_key = get_blender_anim_layer_key(scene, ref_id)
name = (get_blenderID_name(ref_id) if ref_id else scene.name).encode()
Bastien Montagne
committed
if start_zero:
f_end -= f_start
f_start = 0.0
Bastien Montagne
committed
return (astack_key, animations, alayer_key, name, f_start, f_end) if animations else None
def fbx_animations(scene_data):
Bastien Montagne
committed
"""
Generate global animation data from objects.
"""
scene = scene_data.scene
animations = []
frame_start = 1e100
frame_end = -1e100
Bastien Montagne
committed
def add_anim(animations, anim):
nonlocal frame_start, frame_end
if anim is not None:
animations.append(anim)
f_start, f_end = anim[4:6]
if f_start < frame_start:
frame_start = f_start
if f_end > frame_end:
frame_end = f_end
Bastien Montagne
committed
# Per-NLA strip animstacks.
if scene_data.settings.bake_anim_use_nla_strips:
strips = []
Bastien Montagne
committed
for ob_obj in scene_data.objects:
Bastien Montagne
committed
# NLA tracks only for objects, not bones!
Bastien Montagne
committed
if not ob_obj.is_object:
Bastien Montagne
committed
continue
Bastien Montagne
committed
ob = ob_obj.bdata # Back to real Blender Object.
if not ob.animation_data:
continue
for track in ob.animation_data.nla_tracks:
Bastien Montagne
committed
if track.mute:
continue
for strip in track.strips:
if strip.mute:
continue
strips.append(strip)
strip.mute = True
for strip in strips:
strip.mute = False
add_anim(animations, fbx_animations_do(scene_data, strip, strip.frame_start, strip.frame_end, True))
Bastien Montagne
committed
strip.mute = True
for strip in strips:
strip.mute = False
# All actions.
if scene_data.settings.bake_anim_use_all_actions:
def validate_actions(act, path_resolve):
for fc in act.fcurves:
data_path = fc.data_path
if fc.array_index:
data_path = data_path + "[%d]" % fc.array_index
try:
path_resolve(data_path)
except ValueError:
return False # Invalid.
return True # Valid.
Bastien Montagne
committed
def restore_object(ob_to, ob_from):
# Restore org state of object (ugh :/ ).
props = (
'location', 'rotation_quaternion', 'rotation_axis_angle', 'rotation_euler', 'rotation_mode', 'scale',
'delta_location', 'delta_rotation_euler', 'delta_rotation_quaternion', 'delta_scale',
'lock_location', 'lock_rotation', 'lock_rotation_w', 'lock_rotations_4d', 'lock_scale',
'tag', 'layers', 'select', 'track_axis', 'up_axis', 'active_material', 'active_material_index',
'matrix_parent_inverse', 'empty_draw_type', 'empty_draw_size', 'empty_image_offset', 'pass_index',
'color', 'hide', 'hide_select', 'hide_render', 'use_slow_parent', 'slow_parent_offset',
'use_extra_recalc_object', 'use_extra_recalc_data', 'dupli_type', 'use_dupli_frames_speed',
'use_dupli_vertices_rotation', 'use_dupli_faces_scale', 'dupli_faces_scale', 'dupli_group',
'dupli_frames_start', 'dupli_frames_end', 'dupli_frames_on', 'dupli_frames_off',
'draw_type', 'show_bounds', 'draw_bounds_type', 'show_name', 'show_axis', 'show_texture_space',
'show_wire', 'show_all_edges', 'show_transparent', 'show_x_ray',
'show_only_shape_key', 'use_shape_key_edit_mode', 'active_shape_key_index',
)
for p in props:
Bastien Montagne
committed
setattr(ob_to, p, getattr(ob_from, p))
Bastien Montagne
committed
for ob_obj in scene_data.objects:
# Actions only for objects, not bones!
Bastien Montagne
committed
if not ob_obj.is_object:
continue
Bastien Montagne
committed
ob = ob_obj.bdata # Back to real Blender Object.
# We can't play with animdata and actions and get back to org state easily.
# So we have to add a temp copy of the object to the scene, animate it, and remove it... :/
Bastien Montagne
committed
ob_copy = ob.copy()
# Great, have to handle bones as well if needed...
pbones_matrices = [pbo.matrix_basis.copy() for pbo in ob.pose.bones] if ob.type == 'ARMATURE' else ...
Bastien Montagne
committed
if ob.animation_data:
org_act = ob.animation_data.action
else:
org_act = ...