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:
-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.py11
4 files changed, 120 insertions, 58 deletions
diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py
index 9ccc5b7f..7e77ec69 100644
--- a/io_scene_fbx/__init__.py
+++ b/io_scene_fbx/__init__.py
@@ -342,6 +342,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 "
@@ -370,11 +381,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",
@@ -513,6 +519,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 d3aeee76..7247caea 100644
--- a/io_scene_fbx/export_fbx_bin.py
+++ b/io_scene_fbx/export_fbx_bin.py
@@ -719,7 +719,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)
@@ -727,7 +728,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))
@@ -1405,10 +1406,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
@@ -1494,7 +1495,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):
@@ -1758,8 +1760,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()
@@ -1817,11 +1822,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
@@ -2345,7 +2353,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({me_key for me_key, _me, _free in data_meshes.values()})
@@ -2354,7 +2363,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
@@ -2417,22 +2427,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))
@@ -2767,7 +2780,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')
@@ -2840,6 +2853,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,
@@ -2849,7 +2863,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,
@@ -2857,9 +2875,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,
@@ -2919,7 +2934,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 f0501792..35a1b6f9 100644
--- a/io_scene_fbx/fbx_utils.py
+++ b/io_scene_fbx/fbx_utils.py
@@ -880,7 +880,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
@@ -902,7 +902,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'
)
@@ -939,6 +939,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
@@ -974,16 +976,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):
@@ -996,13 +1011,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):
@@ -1013,8 +1034,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
@@ -1029,7 +1050,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):
@@ -1046,7 +1069,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...
@@ -1054,7 +1077,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':
@@ -1069,8 +1092,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
@@ -1080,7 +1102,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
@@ -1095,6 +1117,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
@@ -1192,7 +1225,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 b8429071..244bc746 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -1661,7 +1661,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:
@@ -1678,6 +1678,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
@@ -1689,7 +1694,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
@@ -2563,7 +2568,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()