[Bf-extensions-cvs] [4f1fd99] master: FBX export: Huge refactor of 'Model' part of the code, and fix exporting instances when org objects are not selected.
Bastien Montagne
noreply at git.blender.org
Fri May 9 15:18:10 CEST 2014
Commit: 4f1fd99befe8c99305da475c69cc614540002d3c
Author: Bastien Montagne
Date: Fri May 9 13:19:27 2014 +0200
https://developer.blender.org/rBA4f1fd99befe8c99305da475c69cc614540002d3c
FBX export: Huge refactor of 'Model' part of the code, and fix exporting instances when org objects are not selected.
'Model' data (which can be Blender Object, DupliObject or Bone/PoseBone) are now
wrapped in an ObjectWrapper class that present the same API in all cases.
Hopefully this will make that part of the code easier to follow (and maintain/troublefix).
Also made var names a bit more consistent.
Note all this makes export slightly slower in worst cases (many actions to check against objects),
about 15% it seems. But tha 'export all actions' code need some more attention, maybe it's
possible to enhance its performances?
===================================================================
M io_scene_fbx/export_fbx_bin.py
===================================================================
diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py
index 272a283..7bc11eb 100644
--- a/io_scene_fbx/export_fbx_bin.py
+++ b/io_scene_fbx/export_fbx_bin.py
@@ -280,8 +280,7 @@ def get_blender_dupli_key(dup):
def get_blender_bone_key(armature, bone):
"""Return bone's keys (Model and NodeAttribute)."""
- key = "|".join((get_blenderID_key(armature), get_blenderID_key(bone)))
- return key, key + "_Data"
+ return "|".join((get_blenderID_key((armature, bone)), "Data"))
def get_blender_armature_bindpose_key(armature, mesh):
@@ -1011,113 +1010,294 @@ def fbx_template_def_animcurve(scene, settings, override_defaults=None, nbr_user
##### FBX objects generators. #####
-def dupli_list_create(obj, scene, settings='PREVIEW'):
- # Sigh, why raise exception here? :/
- try:
- obj.dupli_list_create(scene, settings)
- except:
- pass
-
-def gen_dupli_key(dup):
- return (dup.id_data,) + tuple(dup.persistent_id)
-
-
-def has_valid_parent(scene_data, obj):
- if isinstance(obj, PoseBone):
- obj = obj.bone
- return obj.parent and obj.parent in scene_data.objects
-
-
-def use_bake_space_transform(scene_data, obj):
- # NOTE: Only applies to object types supporting this!!! Currently, only meshes...
- # Also, do not apply it to children objects.
- # TODO: Check whether this can work for bones too...
- return (scene_data.settings.bake_space_transform and isinstance(obj, Object) and
- obj.type in BLENDER_OBJECT_TYPES_MESHLIKE and not has_valid_parent(scene_data, obj))
-
-
-def fbx_object_matrix(scene_data, obj, armature=None, local_space=False, global_space=False):
- """
- Generate object transform matrix (*always* in matching *FBX* space!).
- If local_space is True, returned matrix is *always* in local space.
- Else if global_space is True, returned matrix is always in world space.
- If both local_space and global_space are False, returned matrix is in parent space if parent is valid,
- else in world space.
- Note local_space has precedence over global_space.
- If obj is a bone, and global_space is True, armature must be provided (it's the bone's armature object!).
- obj can also be a DupliObject.
- Applies specific rotation to bones, lamps and cameras (conversion Blender -> FBX).
- """
- is_posebone = isinstance(obj, PoseBone)
- is_bone = is_posebone or isinstance(obj, Bone)
- is_dupli = isinstance(obj, DupliObject)
- # 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 (is_bone or is_dupli or has_valid_parent(scene_data, obj)))
-
- if is_bone:
- bo = obj
- matrix = (bo.matrix if is_posebone else bo.matrix_local) * MAT_CONVERT_BONE
+# FBX Model-like data (i.e. Blender objects, dupliobjects and bones) are wrapped in ObjectWrapper.
+# This allows us to have a (nearly) same code FBX-wise for all those types.
+# The wrapper tries to stay as small as possible, by mostly using callbacks (property(get...))
+# to actual Blender data it contains.
+# Note it caches its instances, so that you may call several times ObjectWrapper(your_object)
+# with a minimal cost (just re-computing the key).
+
+class MetaObjectWrapper(type):
+ def __call__(cls, bdata, armature=None):
+ if bdata is None:
+ return None
+ dup_mat = None
+ if isinstance(bdata, Object):
+ key = get_blenderID_key(bdata)
+ elif isinstance(bdata, DupliObject):
+ key = "|".join((get_blenderID_key((bdata.id_data, bdata.object)), cls._get_dup_num_id(bdata)))
+ dup_mat = bdata.matrix.copy()
+ else: # isinstance(bdata, (Bone, PoseBone)):
+ if isinstance(bdata, PoseBone):
+ bdata = armature.data.bones[bdata.name]
+ key = get_blenderID_key((armature, bdata))
+
+ cache = getattr(cls, "_cache", None)
+ if cache is None:
+ cache = cls._cache = {}
+ if key in cache:
+ instance = cache[key]
+ # 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).
+ instance._dupli_matrix = dup_mat
+ return instance
+
+ instance = cls.__new__(cls, bdata, armature)
+ instance.__init__(bdata, armature)
+ instance.key = key
+ instance._dupli_matrix = dup_mat
+ cache[key] = instance
+ return instance
+
+
+class ObjectWrapper(metaclass=MetaObjectWrapper):
+ """
+ This class provides a same common interface for all (FBX-wise) object-like elements:
+ * Blender Object
+ * Blender Bone and PoseBone
+ * Blender DupliObject
+ Note since a same Blender object might be 'mapped' to several FBX models (esp. with duplis),
+ we need to use a key to identify each.
+ """
+ __slots__ = ('name', 'key', 'bdata', '_tag', '_ref', '_dupli_matrix')
+
+ @classmethod
+ def cache_clear(cls):
+ if hasattr(cls, "_cache"):
+ del cls._cache
+
+ @staticmethod
+ def _get_dup_num_id(bdata):
+ return ".".join(str(i) for i in bdata.persistent_id if i != 2147483647)
+
+ def __init__(self, bdata, armature=None):
+ """
+ bdata might be an Object, DupliObject, Bone or PoseBone.
+ If Bone or PoseBone, armature Object must be provided.
+ """
+ if isinstance(bdata, Object):
+ self._tag = 'OB'
+ self.name = get_blenderID_name(bdata)
+ self.bdata = bdata
+ self._ref = None
+ elif isinstance(bdata, DupliObject):
+ self._tag = 'DP'
+ self.name = "|".join((get_blenderID_name((bdata.id_data, bdata.object)),
+ "Dupli", self._get_dup_num_id(bdata)))
+ self.bdata = bdata.object
+ self._ref = bdata.id_data
+ else: # isinstance(bdata, (Bone, PoseBone)):
+ if isinstance(bdata, PoseBone):
+ bdata = armature.data.bones[bdata.name]
+ self._tag = 'BO'
+ self.name = get_blenderID_name((armature, bdata))
+ self.bdata = bdata
+ self._ref = armature
+
+ def __eq__(self, other):
+ return isinstance(other, self.__class__) and self.key == other.key
+
+ def __hash__(self):
+ return hash(self.key)
+
+ #### Common to all _tag values.
+ def get_fbx_uuid(self):
+ return get_fbxuid_from_key(self.key)
+ fbx_uuid = property(get_fbx_uuid)
+
+ def get_parent(self):
+ if self._tag == 'OB':
+ return ObjectWrapper(self.bdata.parent)
+ elif self._tag == 'DP':
+ return ObjectWrapper(self.bdata.parent or self._ref)
+ else: # self._tag == 'BO'
+ return ObjectWrapper(self.bdata.parent, self._ref) or ObjectWrapper(self._ref)
+ parent = property(get_parent)
+
+ 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() * 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() 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):
+ if self._tag == 'OB':
+ return self.bdata.matrix_world.copy()
+ elif self._tag == 'DP':
+ return self._dupli_matrix
+ else: # 'BO', current pose
+ return self._ref.matrix_world * self._ref.pose.bones[self.bdata.name].matrix
+ 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() 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)
- # Bones are in armature (object) space currently, either bring them to global space or real
- # local space (relative to parent bone).
+ def get_matrix_rest_global(self):
+ if self._tag == 'BO':
+ return self._ref.matrix_world * self.bdata.matrix_local
+ else:
+ return self.matrix_global
+ matrix_rest_global = property(get_matrix_rest_global)
+
+ #### Transform and helpers
+ def has_valid_parent(self, objects):
+ par = self.parent
+ if par in objects:
+ if self._tag == 'OB':
+ par_type = self.bdata.parent_type
+ if par_type in {'OBJECT', 'BONE'}:
+ return True
+ else:
+ print("Sorry, “{}” parenting type is not supported".format(par_type))
+ return False
+ return True
+ return False
+
+ def use_bake_space_transform(self, scene_data):
+ # NOTE: Only applies to object types supporting this!!! Currently, only meshes...
+ # Also, do not apply it to children objects.
+ # TODO: Check whether this can work for bones too...
+ return (scene_data.settings.bake_space_transform and self._tag == 'OB' and
+ self.bdata.type in BLENDER_OBJECT_TYPES_MESHLIKE and not self.has_valid_parent(scene_data.objects))
+
+ def fbx_object_matrix(self, scene_data, rest=False, local_space=False, global_space=False):
+ """
+ Generate object transform matrix (*always* in matching *FBX* space!).
+ If local_space is True, returned matrix is *always* in local space.
+
@@ Diff output truncated at 10240 characters. @@
More information about the Bf-extensions-cvs
mailing list