[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