diff options
author | Bastien Montagne <montagne29@wanadoo.fr> | 2014-10-08 17:33:56 +0400 |
---|---|---|
committer | Bastien Montagne <montagne29@wanadoo.fr> | 2014-10-08 17:42:44 +0400 |
commit | 4a07e805addbe606d80223811d6d29c68579b603 (patch) | |
tree | 7a73c681cd56512a6c35f69adc7bebc190cd5d84 /io_scene_fbx/export_fbx_bin.py | |
parent | 355f86ca0881785af1b4362b3e7fb6a8ffdbd6f2 (diff) |
Merged experimental code in 'stable' addon (mainly Jens Restemeier's summer work).
Will remove experimental one soon. Keeping old 6.1 exporter active for now, though.
Notes:
* Not 100% happy with how complex code is now... Rather sure though refactor would
take a huge amount of time, not worth it for now.
* New code handles things like armature & animation import much much better than previous one,
so definitively worth it.
* New code also fixes some real bugs (like Blender-side of T42135).
* Did a few cosmetic changes on the run, nothing serious.
This should make FBX work complete, aside from a few nice TODOs that I'll tackle in comming weeks,
in addition to ongoing bugbusting.
Many thanks to Jens for his work on armature mess! :D
Diffstat (limited to 'io_scene_fbx/export_fbx_bin.py')
-rw-r--r-- | io_scene_fbx/export_fbx_bin.py | 134 |
1 files changed, 130 insertions, 4 deletions
diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index ff223fa8..a5cfdd25 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -1371,6 +1371,8 @@ def fbx_data_armature_elements(root, arm_obj, scene_data): 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) + bone_radius_scale = scene_data.settings.global_scale * 33.0 + # Bones "data". for bo_obj in bones: bo = bo_obj.bdata @@ -1382,13 +1384,18 @@ def fbx_data_armature_elements(root, arm_obj, scene_data): 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", (bo.tail_local - bo.head_local).length) + elem_props_template_set(tmpl, props, "p_double", b"Size", bo.head_radius * bone_radius_scale) elem_props_template_finalize(tmpl, props) # Custom properties. if scene_data.settings.use_custom_props: fbx_data_element_custom_properties(props, bo) + # 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) @@ -1450,6 +1457,56 @@ def fbx_data_armature_elements(root, arm_obj, scene_data): elem_data_single_float64_array(fbx_clstr, b"TransformAssociateModel", matrix4_to_array(mat_world_arm)) +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) + + def fbx_data_object_elements(root, ob_obj, scene_data): """ Write the Object (Model) data blocks. @@ -1709,6 +1766,37 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes, objects.update(bones) +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): """ Generate animation data (a single AnimStack) from objects, for a given frame range. @@ -2050,6 +2138,11 @@ def fbx_data_from_scene(scene, settings): fbx_skeleton_from_armature(scene, settings, ob_obj, objects, data_meshes, data_bones, data_deformers_skin, arm_parents) + # Generate leaf bones + data_leaf_bones = None + if settings.add_leaf_bones: + data_leaf_bones = fbx_generate_leaf_bones(settings, data_bones) + # Some world settings are embedded in FBX materials... if scene.world: data_world = OrderedDict(((scene.world, get_blenderID_key(scene.world)),)) @@ -2125,7 +2218,7 @@ def fbx_data_from_scene(scene, settings): None, None, None, settings, scene, objects, None, 0.0, 0.0, data_empties, data_lamps, data_cameras, data_meshes, None, - data_bones, data_deformers_skin, data_deformers_shape, + data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape, data_world, data_materials, data_textures, data_videos, ) animations, frame_start, frame_end = fbx_animations(tmp_scdata) @@ -2248,6 +2341,11 @@ def fbx_data_from_scene(scene, settings): mesh_key, _me, _free = data_meshes[ob_obj] connections.append((b"OO", get_fbx_uuid_from_key(mesh_key), ob_obj.fbx_uuid, None)) + # Leaf Bones + for (_node_name, par_uuid, node_uuid, attr_uuid, _matrix, _hide, _size) in data_leaf_bones: + connections.append((b"OO", node_uuid, parent_uuid, None)) + connections.append((b"OO", attr_uuid, node_uuid, None)) + # 'Shape' deformers (shape keys, only for meshes currently)... for me_key, shapes_key, shapes in data_deformers_shape.values(): # shape -> geometry @@ -2332,7 +2430,7 @@ def fbx_data_from_scene(scene, settings): templates, templates_users, connections, settings, scene, objects, animations, frame_start, frame_end, data_empties, data_lamps, data_cameras, data_meshes, mesh_mat_indices, - data_bones, data_deformers_skin, data_deformers_shape, + data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape, data_world, data_materials, data_textures, data_videos, ) @@ -2546,6 +2644,9 @@ def fbx_objects_elements(root, scene_data): continue fbx_data_armature_elements(objects, ob_obj, scene_data) + if scene_data.data_leaf_bones: + fbx_data_leaf_bone_elements(objects, scene_data) + for mat in scene_data.data_materials: fbx_data_material_elements(objects, mat, scene_data) @@ -2608,6 +2709,9 @@ def save_single(operator, scene, filepath="", bake_anim_use_all_actions=True, bake_anim_step=1.0, bake_anim_simplify_factor=1.0, + add_leaf_bones=False, + primary_bone_axis='Y', + secondary_bone_axis='X', use_metadata=True, path_mode='AUTO', use_mesh_edges=True, @@ -2627,6 +2731,11 @@ def save_single(operator, scene, filepath="", if 'OTHER' in object_types: object_types |= BLENDER_OTHER_OBJECT_TYPES + # Scale/unit mess. FBX can store the 'reference' unit of a file in its UnitScaleFactor property + # (1.0 meaning centimeter, afaik). We use that to reflect user's default unit as set in Blender with scale_length. + # However, we always get values in BU (i.e. meters), so we have to reverse-apply that scale in global matrix... + if scene.unit_settings.system != 'NONE': + global_matrix = global_matrix * Matrix.Scale(1.0 / scene.unit_settings.scale_length, 4) global_scale = global_matrix.median_scale global_matrix_inv = global_matrix.inverted() # For transforming mesh normals. @@ -2636,6 +2745,19 @@ def save_single(operator, scene, filepath="", if embed_textures and path_mode != 'COPY': embed_textures = False + # Calcuate bone correction matrix + bone_correction_matrix = None # Default is None = no change + bone_correction_matrix_inv = None + if (primary_bone_axis, secondary_bone_axis) != ('Y', 'X'): + from bpy_extras.io_utils import axis_conversion + bone_correction_matrix = axis_conversion(from_forward=secondary_bone_axis, + from_up=primary_bone_axis, + to_forward='X', + to_up='Y', + ).to_4x4() + bone_correction_matrix_inv = bone_correction_matrix.inverted() + + media_settings = FBXExportSettingsMedia( path_mode, os.path.dirname(bpy.data.filepath), # base_src @@ -2650,7 +2772,8 @@ def save_single(operator, scene, filepath="", operator.report, (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, + mesh_smooth_type, use_mesh_edges, use_tspace, + use_armature_deform_only, add_leaf_bones, bone_correction_matrix, bone_correction_matrix_inv, bake_anim, bake_anim_use_nla_strips, bake_anim_use_all_actions, bake_anim_step, bake_anim_simplify_factor, False, media_settings, use_custom_props, ) @@ -2730,6 +2853,9 @@ def defaults_unity3d(): "bake_anim_step": 1.0, "bake_anim_use_nla_strips": True, "bake_anim_use_all_actions": True, + "add_leaf_bones": False, # Avoid memory/performance cost for something only useful for modelling + "primary_bone_axis": 'Y', # Doesn't really matter for Unity, so leave unchanged + "secondary_bone_axis": 'X', "path_mode": 'AUTO', "embed_textures": False, |