Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastien Montagne <montagne29@wanadoo.fr>2015-07-18 19:42:47 +0300
committerBastien Montagne <montagne29@wanadoo.fr>2015-07-18 19:42:47 +0300
commit039f3646b5a2a3a76fd43df1a2cf68bc0242f1c7 (patch)
tree2bd17e05ca7830f0da27bad1e13ecee4657b8c03
parent8e62ba483ef5e56667f1601b9f7c08a58a5ba986 (diff)
FBX Export: WIP attempt to have 'skip_export' objects.
This would allo us e.g. to not export armature object as a root for bone chains, or to have a real 'only deform' export option, in each case by ignoring some nodes and 'reparenting' their children to first exported parent. Idea is simple, but as always when touching to FBX transform (and even worse, FBX bones binding), it turns into nightmare (a bit like the 'apply transform' issue). So stuffing this here for now, in case someone (or myself) feels like wasting another day or two on this mess - with no guarantee of success...
-rw-r--r--io_scene_fbx/__init__.py17
-rw-r--r--io_scene_fbx/export_fbx_bin.py64
-rw-r--r--io_scene_fbx/fbx_utils.py86
-rw-r--r--io_scene_fbx/import_fbx.py13
4 files changed, 121 insertions, 59 deletions
diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py
index 119e8edc..2a90471b 100644
--- a/io_scene_fbx/__init__.py
+++ b/io_scene_fbx/__init__.py
@@ -336,6 +336,17 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
description="Export custom properties",
default=False,
)
+
+ use_armature_as_root = BoolProperty(
+ name="Export Armature As Root",
+ description="Export armature object itself as an 'empty' FBX object, parent of all bones",
+ default=True,
+ )
+ use_armature_deform_only = BoolProperty(
+ name="Only Deform Bones",
+ description="Only write deforming bones (and non-deforming ones when they have deforming children)",
+ default=False,
+ )
add_leaf_bones = BoolProperty(
name="Add Leaf Bones",
description="Append a final bone to the end of each chain to specify last bone length "
@@ -364,11 +375,6 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
),
default='X',
)
- use_armature_deform_only = BoolProperty(
- name="Only Deform Bones",
- description="Only write deforming bones (and non-deforming ones when they have deforming children)",
- default=False,
- )
# Anim - 7.4
bake_anim = BoolProperty(
name="Baked Animation",
@@ -506,6 +512,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
#~ sub.enabled = self.mesh_smooth_type in {'OFF'}
sub.prop(self, "use_tspace")
elif self.ui_tab == 'ARMATURE':
+ layout.prop(self, "use_armature_as_root")
layout.prop(self, "use_armature_deform_only")
layout.prop(self, "add_leaf_bones")
layout.prop(self, "primary_bone_axis")
diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py
index cfb57689..35416831 100644
--- a/io_scene_fbx/export_fbx_bin.py
+++ b/io_scene_fbx/export_fbx_bin.py
@@ -714,7 +714,8 @@ def fbx_data_bindpose_element(root, me_obj, me, scene_data, arm_obj=None, mat_wo
elem_data_single_string(fbx_pose, b"Type", b"BindPose")
elem_data_single_int32(fbx_pose, b"Version", FBX_POSE_BIND_VERSION)
- elem_data_single_int32(fbx_pose, b"NbPoseNodes", 1 + (1 if (arm_obj != me_obj) else 0) + len(bones))
+ elem_data_single_int32(fbx_pose, b"NbPoseNodes",
+ 1 + (1 if (arm_obj != me_obj and mat_world_arm) else 0) + len(bones))
# First node is mesh/object.
mat_world_obj = me_obj.fbx_object_matrix(scene_data, global_space=True)
@@ -722,7 +723,7 @@ def fbx_data_bindpose_element(root, me_obj, me, scene_data, arm_obj=None, mat_wo
elem_data_single_int64(fbx_posenode, b"Node", me_obj.fbx_uuid)
elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix4_to_array(mat_world_obj))
# Second node is armature object itself.
- if arm_obj != me_obj:
+ if arm_obj != me_obj and mat_world_arm:
fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
elem_data_single_int64(fbx_posenode, b"Node", arm_obj.fbx_uuid)
elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix4_to_array(mat_world_arm))
@@ -1400,10 +1401,10 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
* 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...
+ Note armature itself has no data, it is a mere "Null" Model (when exported)...
"""
- 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)
+ mat_world_arm = arm_obj.fbx_object_matrix(scene_data, global_space=True) if not arm_obj.skip_export else None
+ bones = tuple(bo_obj for bo_obj in arm_obj.bones if bo_obj in scene_data.objects and not bo_obj.skip_export)
bone_radius_scale = 33.0
@@ -1489,7 +1490,8 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
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))
+ if mat_world_arm:
+ elem_data_single_float64_array(fbx_clstr, b"TransformAssociateModel", matrix4_to_array(mat_world_arm))
def fbx_data_leaf_bone_elements(root, scene_data):
@@ -1753,8 +1755,11 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
Also supports "parent to bone" (simple parent to Model/LimbNode).
arm_parents is a set of tuples (armature, object) for all successful armature bindings.
"""
- # We need some data for our armature 'object' too!!!
- data_empties[arm_obj] = get_blender_empty_key(arm_obj.bdata)
+ if (settings.use_armature_as_root):
+ # We need some data for our armature 'object' too!!!
+ data_empties[arm_obj] = get_blender_empty_key(arm_obj.bdata)
+ else:
+ arm_obj.skip_export = True
arm_data = arm_obj.bdata.data
bones = OrderedDict()
@@ -1812,11 +1817,14 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
def fbx_generate_leaf_bones(settings, data_bones):
- # find which bons have no children
+ # find which bones 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
+ if bo.skip_export:
+ continue
+ bo_par = bo.fbx_parent
+ if bo_par and bo_par.is_bone:
+ child_count[bo_par] += 1
bone_radius_scale = settings.global_scale * 33.0
@@ -2317,7 +2325,8 @@ def fbx_data_from_scene(scene, settings):
templates[b"Camera"] = fbx_template_def_camera(scene, settings, nbr_users=len(data_cameras))
if data_bones:
- templates[b"Bone"] = fbx_template_def_bone(scene, settings, nbr_users=len(data_bones))
+ nbr_users = sum(1 for db in data_bones if not db.skip_export)
+ templates[b"Bone"] = fbx_template_def_bone(scene, settings, nbr_users=nbr_users)
if data_meshes:
nbr = len(data_meshes)
@@ -2326,7 +2335,8 @@ def fbx_data_from_scene(scene, settings):
templates[b"Geometry"] = fbx_template_def_geometry(scene, settings, nbr_users=nbr)
if objects:
- templates[b"Model"] = fbx_template_def_model(scene, settings, nbr_users=len(objects))
+ nbr_users = sum(1 for ob in objects if not ob.skip_export)
+ templates[b"Model"] = fbx_template_def_model(scene, settings, nbr_users=nbr_users)
if arm_parents:
# Number of Pose|BindPose elements should be the same as number of meshes-parented-to-armatures
@@ -2389,22 +2399,25 @@ def fbx_data_from_scene(scene, settings):
for ob_obj in objects:
# Bones are handled later.
if not ob_obj.is_bone:
- par_obj = ob_obj.parent
+ par_obj = ob_obj.fbx_parent
# Meshes parented to armature are handled separately, yet we want the 'no parent' connection (0).
- if par_obj and ob_obj.has_valid_parent(objects) and (par_obj, ob_obj) not in arm_parents:
+ if par_obj and ob_obj.has_valid_fbx_parent(objects) and (par_obj, ob_obj) not in arm_parents:
connections.append((b"OO", ob_obj.fbx_uuid, par_obj.fbx_uuid, None))
else:
connections.append((b"OO", ob_obj.fbx_uuid, 0, None))
# Armature & Bone chains.
for bo_obj in data_bones.keys():
- par_obj = bo_obj.parent
- if par_obj not in objects:
- continue
- connections.append((b"OO", bo_obj.fbx_uuid, par_obj.fbx_uuid, None))
+ par_obj = bo_obj.fbx_parent
+ if par_obj is None:
+ connections.append((b"OO", bo_obj.fbx_uuid, 0, None))
+ elif par_obj in objects:
+ connections.append((b"OO", bo_obj.fbx_uuid, par_obj.fbx_uuid, None))
# Object data.
for ob_obj in objects:
+ if ob_obj.skip_export:
+ continue
if ob_obj.is_bone:
bo_data_key = data_bones[ob_obj]
connections.append((b"OO", get_fbx_uuid_from_key(bo_data_key), ob_obj.fbx_uuid, None))
@@ -2729,7 +2742,7 @@ def fbx_objects_elements(root, scene_data):
perfmon.step("FBX export fetch objects (%d)..." % len(scene_data.objects))
for ob_obj in scene_data.objects:
- if ob_obj.is_dupli:
+ if ob_obj.is_dupli or ob_obj.skip_export:
continue
fbx_data_object_elements(objects, ob_obj, scene_data)
ob_obj.dupli_list_create(scene_data.scene, 'RENDER')
@@ -2802,6 +2815,7 @@ def fbx_takes_elements(root, scene_data):
# ##### "Main" functions. #####
# This func can be called with just the filepath
+# Warning! Arg order is not guaranteed, only use keyargs!
def save_single(operator, scene, filepath="",
global_matrix=Matrix(),
apply_unit_scale=False,
@@ -2811,7 +2825,11 @@ def save_single(operator, scene, filepath="",
object_types=None,
use_mesh_modifiers=True,
mesh_smooth_type='FACE',
+ use_armature_as_root=True,
use_armature_deform_only=False,
+ add_leaf_bones=False,
+ primary_bone_axis='Y',
+ secondary_bone_axis='X',
bake_anim=True,
bake_anim_use_all_bones=True,
bake_anim_use_nla_strips=True,
@@ -2819,9 +2837,6 @@ def save_single(operator, scene, filepath="",
bake_anim_step=1.0,
bake_anim_simplify_factor=1.0,
bake_anim_force_startend_keying=True,
- add_leaf_bones=False,
- primary_bone_axis='Y',
- secondary_bone_axis='X',
use_metadata=True,
path_mode='AUTO',
use_mesh_edges=True,
@@ -2881,7 +2896,8 @@ def save_single(operator, scene, filepath="",
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, add_leaf_bones, bone_correction_matrix, bone_correction_matrix_inv,
+ use_armature_as_root, use_armature_deform_only, add_leaf_bones,
+ bone_correction_matrix, bone_correction_matrix_inv,
bake_anim, bake_anim_use_all_bones, bake_anim_use_nla_strips, bake_anim_use_all_actions,
bake_anim_step, bake_anim_simplify_factor, bake_anim_force_startend_keying,
False, media_settings, use_custom_props,
diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py
index c4ae3383..cd5f08f8 100644
--- a/io_scene_fbx/fbx_utils.py
+++ b/io_scene_fbx/fbx_utils.py
@@ -886,7 +886,7 @@ class MetaObjectWrapper(type):
if instance is not None:
# Duplis hack: since duplis are not persistent in Blender (we have to re-create them to get updated
# info like matrix...), we *always* need to reset that matrix when calling ObjectWrapper() (all
- # other data is supposed valid during whole cache live, so we can skip resetting it).
+ # other data is supposed valid during whole cache life, so we can skip resetting it).
instance._dupli_matrix = dup_mat
return instance
@@ -908,7 +908,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
we need to use a key to identify each.
"""
__slots__ = (
- 'name', 'key', 'bdata', 'parented_to_armature',
+ 'name', 'key', 'bdata', 'parented_to_armature', 'skip_export', 'is_root',
'_tag', '_ref', '_dupli_matrix'
)
@@ -945,6 +945,8 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
self.bdata = bdata
self._ref = armature
self.parented_to_armature = False
+ self.skip_export = False
+ self.is_root = False
def __eq__(self, other):
return isinstance(other, self.__class__) and self.key == other.key
@@ -980,16 +982,29 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
return ObjectWrapper(self.bdata.parent, self._ref) or ObjectWrapper(self._ref)
parent = property(get_parent)
+ @property
+ def fbx_parent(self):
+ par = self.parent
+ while par and par.skip_export:
+ par = par.parent
+ return par
+
def get_matrix_local(self):
- if self._tag == 'OB':
- return self.bdata.matrix_local.copy()
- elif self._tag == 'DP':
- return self._ref.matrix_world.inverted_safe() * self._dupli_matrix
- else: # 'BO', current pose
- # PoseBone.matrix is in armature space, bring in back in real local one!
- par = self.bdata.parent
- par_mat_inv = self._ref.pose.bones[par.name].matrix.inverted_safe() if par else Matrix()
- return par_mat_inv * self._ref.pose.bones[self.bdata.name].matrix
+ par = self.parent
+ if par and par.skip_export:
+ par = self.fbx_parent
+ par_mat_inv = par.matrix_global.inverted_safe() if par else Matrix()
+ return par_mat_inv * self.matrix_global
+ else:
+ if self._tag == 'OB':
+ return self.bdata.matrix_local.copy()
+ elif self._tag == 'DP':
+ return self._ref.matrix_world.inverted_safe() * self._dupli_matrix
+ else: # 'BO', current pose
+ # PoseBone.matrix is in armature space, bring in back in real local one!
+ par = self.bdata.parent
+ par_mat_inv = self._ref.pose.bones[par.name].matrix.inverted_safe() if par else Matrix()
+ return par_mat_inv * self._ref.pose.bones[self.bdata.name].matrix
matrix_local = property(get_matrix_local)
def get_matrix_global(self):
@@ -1002,13 +1017,19 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
matrix_global = property(get_matrix_global)
def get_matrix_rest_local(self):
- if self._tag == 'BO':
- # Bone.matrix_local is in armature space, bring in back in real local one!
- par = self.bdata.parent
- par_mat_inv = par.matrix_local.inverted_safe() if par else Matrix()
- return par_mat_inv * self.bdata.matrix_local
+ par = self.parent
+ if par and par.skip_export:
+ par = self.fbx_parent
+ par_mat_inv = par.matrix_rest_global.inverted_safe() if par else Matrix()
+ return par_mat_inv * self.matrix_rest_global
else:
- return self.matrix_local.copy()
+ if self._tag == 'BO':
+ # Bone.matrix_local is in armature space, bring in back in real local one!
+ par = self.bdata.parent
+ par_mat_inv = par.matrix_local.inverted_safe() if par else Matrix()
+ return par_mat_inv * self.bdata.matrix_local
+ else:
+ return self.matrix_local
matrix_rest_local = property(get_matrix_rest_local)
def get_matrix_rest_global(self):
@@ -1019,8 +1040,8 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
matrix_rest_global = property(get_matrix_rest_global)
# #### Transform and helpers
- def has_valid_parent(self, objects):
- par = self.parent
+ def has_valid_fbx_parent(self, objects):
+ par = self.fbx_parent
if par in objects:
if self._tag == 'OB':
par_type = self.bdata.parent_type
@@ -1035,7 +1056,9 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
def use_bake_space_transform(self, scene_data):
# NOTE: Only applies to object types supporting this!!! Currently, only meshes and the like...
# TODO: Check whether this can work for bones too...
- return (scene_data.settings.bake_space_transform and self._tag in {'OB', 'DP'} and
+ return (scene_data.settings.bake_space_transform and
+ self._tag in {'OB', 'DP'} and
+ not self.skip_export and
self.bdata.type in BLENDER_OBJECT_TYPES_MESHLIKE | {'EMPTY'})
def fbx_object_matrix(self, scene_data, rest=False, local_space=False, global_space=False):
@@ -1052,7 +1075,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
# Objects which are not bones and do not have any parent are *always* in global space
# (unless local_space is True!).
is_global = (not local_space and
- (global_space or not (self._tag in {'DP', 'BO'} or self.has_valid_parent(scene_data.objects))))
+ (global_space or not (self._tag == 'DP' or self.has_valid_fbx_parent(scene_data.objects))))
# Objects (meshes!) parented to armature are not parented to anything in FBX, hence we need them
# in global space, which is their 'virtual' local space...
@@ -1060,7 +1083,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
# Since we have to apply corrections to some types of object, we always need local Blender space here...
matrix = self.matrix_rest_local if rest else self.matrix_local
- parent = self.parent
+ parent = self.fbx_parent
# Bones, lamps and cameras need to be rotated (in local space!).
if self._tag == 'BO':
@@ -1075,8 +1098,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
elif self.bdata.type == 'CAMERA':
matrix = matrix * MAT_CONVERT_CAMERA
- if self._tag in {'DP', 'OB'} and parent:
- if parent._tag == 'BO':
+ if self._tag in {'DP', 'OB'} and parent and parent.is_bone:
# In bone parent case, we get transformation in **bone tip** space (sigh).
# Have to bring it back into bone root, which is FBX expected value.
matrix = Matrix.Translation((0, (parent.bdata.tail - parent.bdata.head).length, 0)) * matrix
@@ -1086,7 +1108,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
if is_global:
# Move matrix to global Blender space.
matrix = (parent.matrix_rest_global if rest else parent.matrix_global) * matrix
- elif parent.use_bake_space_transform(scene_data):
+ elif parent and parent.use_bake_space_transform(scene_data):
# Blender's and FBX's local space of parent may differ if we use bake_space_transform...
# Apply parent's *Blender* local space...
matrix = (parent.matrix_rest_local if rest else parent.matrix_local) * matrix
@@ -1101,6 +1123,17 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
if is_global:
# In any case, pre-multiply the global matrix to get it in FBX global space!
matrix = scene_data.settings.global_matrix * matrix
+ elif 0: #parent:
+ par = parent
+ while par and par.skip_export:
+ print(matrix)
+ # Since this parent will not be exported, we have to apply its transform to its children...
+ par_mat = par.fbx_object_matrix(scene_data, rest=rest)
+ print(par_mat)
+ matrix = par_mat * matrix
+ print(matrix)
+ print("\n\n")
+ par = par.parent
return matrix
@@ -1202,7 +1235,8 @@ FBXExportSettings = namedtuple("FBXExportSettings", (
"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", "add_leaf_bones", "bone_correction_matrix", "bone_correction_matrix_inv",
+ "use_armature_as_root", "use_armature_deform_only", "add_leaf_bones",
+ "bone_correction_matrix", "bone_correction_matrix_inv",
"bake_anim", "bake_anim_use_all_bones", "bake_anim_use_nla_strips", "bake_anim_use_all_actions",
"bake_anim_step", "bake_anim_simplify_factor", "bake_anim_force_startend_keying",
"use_metadata", "media_settings", "use_custom_props",
diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py
index e667693d..252e532d 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -1664,7 +1664,7 @@ class FbxImportHelperNode:
child.armature = armature
child.find_armature_bones(armature)
- def find_armatures(self):
+ def find_armatures(self, settings):
needs_armature = False
for child in self.children:
if child.is_bone:
@@ -1681,6 +1681,11 @@ class FbxImportHelperNode:
armature.fbx_name = "Armature"
armature.is_armature = True
+ # if our fake armature is a non-parented object, we need to set a transform
+ # that will result in NOP once applied all corrections...
+ if self.is_root:
+ armature.matrix = settings.global_matrix_inv
+
for child in self.children:
if child.is_bone:
child.parent = armature
@@ -1692,7 +1697,7 @@ class FbxImportHelperNode:
for child in self.children:
if child.is_armature or child.is_bone:
continue
- child.find_armatures()
+ child.find_armatures(settings)
def find_bone_children(self):
has_bone_children = False
@@ -2525,7 +2530,7 @@ def load(operator, context, filepath="",
assert(fbx_props[0] is not None)
transform_data = blen_read_object_transform_preprocess(fbx_props, fbx_obj, Matrix(), use_prepost_rot)
- is_bone = fbx_obj.props[2] in {b'LimbNode'} # Note: 'Root' "bones" are handled as (armature) objects.
+ is_bone = fbx_obj.props[2] == b'LimbNode' # Note: 'Root' "bones" are handled as (armature) objects.
fbx_helper_nodes[a_uuid] = FbxImportHelperNode(fbx_obj, bl_data, transform_data, is_bone)
# add parent-child relations and add blender data to the node
@@ -2552,7 +2557,7 @@ def load(operator, context, filepath="",
child.parent = parent
# find armatures (either an empty below a bone or a new node inserted at the bone
- root_helper.find_armatures()
+ root_helper.find_armatures(settings)
# mark nodes that have bone children
root_helper.find_bone_children()