[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