[Bf-extensions-cvs] [54f80d0] fbx_io_development: FBX import: First step toward Armature support.

Bastien Montagne noreply at git.blender.org
Sat May 3 16:30:34 CEST 2014


Commit: 54f80d067b38346b078c59f00595a3fe386d8030
Author: Bastien Montagne
Date:   Fri May 2 22:41:16 2014 +0200
https://developer.blender.org/rBA54f80d067b38346b078c59f00595a3fe386d8030

FBX import: First step toward Armature support.

Imports armatures (with bones), does not yet re-create binding to meshes.

===================================================================

M	io_scene_fbx/import_fbx.py

===================================================================

diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py
index 1ed46ee..7fa0660 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -42,6 +42,29 @@ def tuple_deg_to_rad(eul):
             eul[2] / 57.295779513)
 
 
+def array_to_matrix4(arr):
+    """Convert a single 16-len tuple into a valid 4D Blender matrix"""
+    from mathutils import Matrix
+
+    # Blender matrix is row major, fbx is col major so transpose on read
+    return Matrix(tuple(zip(*[iter(arr)]*4))).transposed()
+
+
+def similar_values(v1, v2, e=1e-6):
+    """Return True if v1 and v2 are nearly the same."""
+    if v1 == v2:
+        return True
+    return ((abs(v1 - v2) / max(abs(v1), abs(v2))) <= e)
+
+
+def similar_vectors(vec1, vec2, e=1e-6):
+    """Return True if vec1 and vec2 are nearly the same."""
+    for v1, v2 in zip(vec1, vec2):
+        if not similar_values(v1, v2, e):
+            return False
+    return True
+
+
 def elem_find_first(elem, id_search, default=None):
     for fbx_item in elem.elems:
         if fbx_item.id == id_search:
@@ -89,6 +112,13 @@ def elem_split_name_class(elem):
     return elem_name, elem_class
 
 
+def elem_name_ensure_class(elem, clss=...):
+    elem_name, elem_class = elem_split_name_class(elem)
+    if clss is not ...:
+        assert(elem_class == clss)
+    return elem_name.decode('utf-8')
+
+
 def elem_split_name_class_nodeattr(elem):
     assert(elem.props_type[-2] == data_types.STRING)
     elem_name, elem_class = elem.props[-2].split(b'\x00\x01')
@@ -215,30 +245,12 @@ def elem_props_get_enum(elem, elem_prop_id, default=None):
 # ------
 # Object
 
-def blen_read_object(fbx_tmpl, fbx_obj, object_data):
-    elem_name, elem_class = elem_split_name_class(fbx_obj)
-    elem_name_utf8 = elem_name.decode('utf-8')
+def blen_read_object_transform(fbx_props, fbx_obj, rot_alt_mat):
+    # This is quite involved, 'fbxRNode.cpp' from openscenegraph used as a reference
 
     const_vector_zero_3d = 0.0, 0.0, 0.0
     const_vector_one_3d = 1.0, 1.0, 1.0
 
-    # Object data must be created already
-    obj = bpy.data.objects.new(name=elem_name_utf8, object_data=object_data)
-
-    fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
-                 elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
-    assert(fbx_props[0] is not None)
-
-    # ----
-    # Misc Attributes
-
-    obj.color[0:3] = elem_props_get_color_rgb(fbx_props, b'Color', (0.8, 0.8, 0.8))
-
-    # ----
-    # Transformation
-
-    # This is quite involved, 'fbxRNode.cpp' from openscenegraph used as a reference
-
     loc = elem_props_get_vector_3d(fbx_props, b'Lcl Translation', const_vector_zero_3d)
     rot = elem_props_get_vector_3d(fbx_props, b'Lcl Rotation', const_vector_zero_3d)
     sca = elem_props_get_vector_3d(fbx_props, b'Lcl Scaling', const_vector_one_3d)
@@ -268,20 +280,11 @@ def blen_read_object(fbx_tmpl, fbx_obj, object_data):
         rot_ord = 'XYZ'
 
     from mathutils import Matrix, Euler
-    from math import pi
 
     # translation
     lcl_translation = Matrix.Translation(loc)
 
     # rotation
-    if obj.type == 'CAMERA':
-        rot_alt_mat = Matrix.Rotation(pi / -2.0, 4, 'Y')
-    elif obj.type == 'LAMP':
-        rot_alt_mat = Matrix.Rotation(pi / -2.0, 4, 'X')
-    else:
-        rot_alt_mat = Matrix()
-
-    # rotation
     lcl_rot = Euler(tuple_deg_to_rad(rot), rot_ord).to_matrix().to_4x4() * rot_alt_mat
     pre_rot = Euler(tuple_deg_to_rad(pre_rot), rot_ord).to_matrix().to_4x4()
     pst_rot = Euler(tuple_deg_to_rad(pst_rot), rot_ord).to_matrix().to_4x4()
@@ -295,7 +298,7 @@ def blen_read_object(fbx_tmpl, fbx_obj, object_data):
     lcl_scale = Matrix()
     lcl_scale[0][0], lcl_scale[1][1], lcl_scale[2][2] = sca
 
-    obj.matrix_basis = (
+    return (
         lcl_translation *
         rot_ofs *
         rot_piv *
@@ -307,11 +310,114 @@ def blen_read_object(fbx_tmpl, fbx_obj, object_data):
         sca_piv *
         lcl_scale *
         sca_piv.inverted()
-        )
+    )
+
+
+def blen_read_object(fbx_tmpl, fbx_obj, object_data):
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj)
+
+    # Object data must be created already
+    obj = bpy.data.objects.new(name=elem_name_utf8, object_data=object_data)
+
+    fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
+                 elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
+    assert(fbx_props[0] is not None)
+
+    # ----
+    # Misc Attributes
+
+    obj.color[0:3] = elem_props_get_color_rgb(fbx_props, b'Color', (0.8, 0.8, 0.8))
+
+    # ----
+    # Transformation
+
+    from mathutils import Matrix
+    from math import pi
+
+    # rotation corrections
+    if obj.type == 'CAMERA':
+        rot_alt_mat = Matrix.Rotation(pi / -2.0, 4, 'Y')
+    elif obj.type == 'LAMP':
+        rot_alt_mat = Matrix.Rotation(pi / -2.0, 4, 'X')
+    else:
+        rot_alt_mat = Matrix()
+
+    obj.matrix_basis = blen_read_object_transform(fbx_props, fbx_obj, rot_alt_mat)
 
     return obj
 
 
+# --------
+# Armature
+
+def blen_read_armatures_add_bone(bl_obj, bl_arm, bones, b_uuid):
+    from mathutils import Matrix, Euler, Vector
+
+    b_item, bsize, p_uuid, clusters = bones[b_uuid]
+    fbx_bdata, bl_bdata = b_item
+    if bl_bdata is not None:
+        return  # Might have already been created...
+
+    p_ebo = None
+    if p_uuid is not None:
+        # Recurse over parents!
+        blen_read_armatures_add_bone(bl_obj, bl_arm, bones, p_uuid)
+        p_ebo = bones[p_uuid][0][1]
+
+    # Remember we assume (for now) that one bone only has one cluster, this will have to be checked ultimately.
+    fbx_cdata = clusters[0][0]
+
+    # We assume matrices in cluster are rest pose of bones (they are in Global space!).
+    # TransformLink is matrix of bone, in global space.
+    # TransformAssociateModel is matrix of armature, in global space (at bind time).
+    # We seek for matrix of bone in armature space...
+    elm = elem_find_first(fbx_cdata, b'TransformLink', default=None)
+    bmat_glob = array_to_matrix4(elm.props[0]) if elm is not None else Matrix()
+    elm = elem_find_first(fbx_cdata, b'TransformAssociateModel', default=None)
+    omat_glob = array_to_matrix4(elm.props[0]) if elm is not None else Matrix()
+    bmat_arm = omat_glob.inverted() * bmat_glob
+    bone_name_utf8 = elem_name_ensure_class(fbx_bdata, b'Model')
+
+    b_item[1] = ebo = bl_arm.edit_bones.new(name=bone_name_utf8)
+    ebo.head[:] = bmat_arm.to_translation()
+    if p_ebo is not None:
+        ebo.parent = p_ebo
+        if similar_vectors(p_ebo.tail, ebo.head):
+            ebo.use_connect = True
+
+    # So that our bone gets its final length, but still Y-aligned in armature space.
+    ebo.tail = ebo.head + Vector((0.0, 1.0, 0.0)) * bsize
+    # And finally, rotate it to its final "rest pose".
+    ebo.transform(bmat_arm.to_3x3())
+
+
+def blen_read_armatures(fbx_tmpl, armatures, scene):
+    for a_item, bones in armatures:
+        fbx_adata, bl_adata = a_item
+
+        # ----
+        # Armature data.
+        elem_name_utf8 = elem_name_ensure_class(fbx_adata, b'Model')
+        bl_arm = bpy.data.armatures.new(name=elem_name_utf8)
+
+        # Need to create the object right now, since we can only add bones in Edit mode... :/
+        assert(a_item[1] is None)
+        bl_adata = a_item[1] = blen_read_object(fbx_tmpl, fbx_adata, bl_arm)
+
+        # Instantiate in scene.
+        obj_base = scene.objects.link(bl_adata)
+        obj_base.select = True
+
+        # Switch to Edit mode.
+        scene.objects.active = bl_adata
+        bpy.ops.object.mode_set(mode='EDIT')
+
+        for b_uuid in bones:
+            blen_read_armatures_add_bone(bl_adata, bl_arm, bones, b_uuid)
+
+        bpy.ops.object.mode_set(mode='OBJECT')
+
+
 # ----
 # Mesh
 
@@ -611,9 +717,7 @@ def blen_read_geom_layer_normal(fbx_obj, mesh):
 
 def blen_read_geom(fbx_tmpl, fbx_obj):
     # TODO, use 'fbx_tmpl'
-    elem_name, elem_class = elem_split_name_class(fbx_obj)
-    assert(elem_class == b'Geometry')
-    elem_name_utf8 = elem_name.decode('utf-8')
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'Geometry')
 
     fbx_verts = elem_prop_first(elem_find_first(fbx_obj, b'Vertices'))
     fbx_polys = elem_prop_first(elem_find_first(fbx_obj, b'PolygonVertexIndex'))
@@ -701,11 +805,8 @@ def blen_read_geom(fbx_tmpl, fbx_obj):
 # --------
 # Material
 
-def blen_read_material(fbx_tmpl, fbx_obj,
-                       cycles_material_wrap_map, use_cycles):
-    elem_name, elem_class = elem_split_name_class(fbx_obj)
-    assert(elem_class == b'Material')
-    elem_name_utf8 = elem_name.decode('utf-8')
+def blen_read_material(fbx_tmpl, fbx_obj, cycles_material_wrap_map, use_cycles):
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'Material')
 
     ma = bpy.data.materials.new(name=elem_name_utf8)
 
@@ -761,9 +862,7 @@ def blen_read_texture(fbx_tmpl, fbx_obj, basedir, image_cache,
     import os
     from bpy_extras import image_utils
 
-    elem_name, elem_class = elem_split_name_class(fbx_obj)
-    assert(elem_class == b'Texture')
-    elem_name_utf8 = elem_name.decode('utf-8')
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'NodeAttribute')
 
     filepath = elem_find_first_string(fbx_obj, b'FileName')
     if os.sep == '/':
@@ -793,9 +892,7 @@ def blen_read_camera(fbx_tmpl, fbx_obj, global_scale):
     # meters to inches
     M2I = 0.0393700787
 
-    elem_name, elem_class = elem_split_name_class_nodeattr(fbx_obj)
-    assert(elem_class == b'Camera')
-    elem_name_utf8 = elem_name.decode('utf-8')
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'NodeAttribute')
 
     fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
                  elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
@@ -820,9 +917,7 @@ def blen_read_camera(fbx_tmpl, fbx_obj, global_scale):
 
 def blen_read_light(fbx_tmpl, fbx_obj, global_scale):
     import math
-    elem_name, elem_class = elem_split_name_class_nodeattr(fbx_obj)
-    assert(elem_class == b'Light')
-    elem_name_utf8 = elem_name.decode('utf-8')
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'NodeAttribute')
 
     fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
                  ele

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list