Browse Source

add converter for armature

Jason0214 7 years ago
parent
commit
2b9b84ee18

+ 2 - 2
io_scene_godot/__init__.py

@@ -57,7 +57,7 @@ class ExportGodot(bpy.types.Operator, ExportHelper):
             ("EMPTY", "Empty", ""),
             ("CAMERA", "Camera", ""),
             ("LAMP", "Lamp", ""),
-            # ("ARMATURE", "Armature", ""),
+            ("ARMATURE", "Armature", ""),
             ("MESH", "Mesh", ""),
             # ("CURVE", "Curve", ""),
         ),
@@ -65,7 +65,7 @@ class ExportGodot(bpy.types.Operator, ExportHelper):
             "EMPTY",
             "CAMERA",
             "LAMP",
-            # "ARMATURE",
+            "ARMATURE",
             "MESH",
             # "CURVE"
         },

+ 2 - 0
io_scene_godot/converters/__init__.py

@@ -18,10 +18,12 @@ are stored in individual files.
 from .simple_nodes import *  # pylint: disable=wildcard-import
 from .mesh import export_mesh_node
 from .physics import export_physics_properties
+from .armature import export_armature_node
 
 
 BLENDER_TYPE_TO_EXPORTER = {
     "MESH": export_mesh_node,
+    "ARMATURE": export_armature_node,
     "CAMERA": export_camera_node,
     "LAMP": export_lamp_node,
     "EMPTY": export_empty_node

+ 94 - 0
io_scene_godot/converters/armature.py

@@ -0,0 +1,94 @@
+"""Export a armature node"""
+import mathutils
+from ..structures import NodeTemplate
+
+
+def get_armature_data(node):
+    """Get the armature modifier of a blender object
+    if does not have one, return None"""
+    for modifier in node.modifiers:
+        if modifier.type.lower() == 'armature':
+            return modifier.object.data
+    return None
+
+
+class Bone:
+    """A Bone has almost same attributes as Godot bones"""
+
+    # must be ordered, Godot scene exporter force the first
+    # attribute of bone be 'name'
+    attributes = (
+        "name",
+        "parent",
+        "rest",
+        "pose",
+        "enabled",
+        "bound_children",
+    )
+
+    def __init__(self, bone_id, name, parent_id):
+        # name needs wrapped by double quotes
+        self.id = bone_id
+        self.name = '"{}"'.format(name)
+
+        # attributes
+        self.parent = parent_id
+        self.rest = mathutils.Matrix()
+        self.pose = mathutils.Matrix()
+        self.enabled = True
+        self.bound_children = None
+
+    def attr_to_key(self, attr_name):
+        """Add bone id to bone attribute"""
+        assert attr_name in Bone.attributes
+
+        return "bones/{}/{}".format(self.id, attr_name).lower()
+
+
+def export_bone(pose_bone, self_id, parent_id):
+    """Convert a Blender bone to a escn bone"""
+    bone_name = pose_bone.name
+
+    rest_bone = pose_bone.bone
+    if pose_bone.parent is None:
+        rest_mat = rest_bone.matrix_local
+    else:
+        rest_mat = (rest_bone.parent.matrix_local.inverted_safe() *
+                    rest_bone.matrix_local)
+    pose_mat = pose_bone.matrix_basis
+
+    bone = Bone(self_id, bone_name, parent_id)
+    bone.rest = rest_mat
+    bone.pose = pose_mat
+    return bone
+
+
+def attach_bones_to_skeleton(skeleton_node, bone_list):
+    """Convert Bone list to attributes of skeleton node"""
+    for bone in bone_list:
+        for attr in Bone.attributes:
+            if not getattr(bone, attr) is None:
+                skeleton_node[bone.attr_to_key(attr)] = getattr(bone, attr)
+
+
+def export_armature_node(escn_file, export_settings, node, parent_gd_node):
+    """Export an armature node"""
+    if "ARMATURE" not in export_settings['object_types']:
+        return parent_gd_node
+
+    skeleton_node = NodeTemplate(node.name, "Skeleton", parent_gd_node)
+    skeleton_node['transform'] = node.matrix_local
+
+    bone_list = list()
+    for index, pose_bone in enumerate(node.pose.bones):
+        if pose_bone.parent is None:
+            parent_id = -1
+        else:
+            parent_id = node.pose.bones.find(pose_bone.parent.name)
+        bone_list.append(export_bone(pose_bone, index, parent_id))
+
+    attach_bones_to_skeleton(skeleton_node, bone_list)
+
+    escn_file.add_node(skeleton_node)
+
+    return skeleton_node

+ 58 - 25
io_scene_godot/converters/mesh.py

@@ -4,8 +4,11 @@ import bmesh
 import mathutils
 
 from .material import export_material
-from ..structures import Array, NodeTemplate, InternalResource
+from ..structures import Array, NodeTemplate, InternalResource, NodePath
 from . import physics
+from . import armature
+
+MAX_BONE_PER_VERTEX = 4
 
 
 # ------------------------------- The Mesh -----------------------------------
@@ -28,15 +31,31 @@ def export_mesh_node(escn_file, export_settings, node, parent_gd_node):
         return parent_gd_node
 
     else:
-        armature = None
-        if node.parent is not None and node.parent.type == "ARMATURE":
-            armature = node.parent
-
-        mesh_id = export_mesh(escn_file, export_settings, node, armature)
+        armature_data = armature.get_armature_data(node)
+
+        skeleton_node = None
+        if ("ARMATURE" in export_settings['object_types'] and
+                armature_data is not None):
+            # trace up the godot scene tree to find the binded skeleton node
+            gd_node_ptr = parent_gd_node
+            while (gd_node_ptr is not None and
+                   gd_node_ptr.get_type() != "Skeleton"):
+                gd_node_ptr = gd_node_ptr.get_parent()
+            skeleton_node = gd_node_ptr
+
+        mesh_id = export_mesh(
+            escn_file,
+            export_settings,
+            node,
+            armature_data
+        )
 
         mesh_node = NodeTemplate(node.name, "MeshInstance", parent_gd_node)
         mesh_node['mesh'] = "SubResource({})".format(mesh_id)
         mesh_node['visible'] = not node.hide
+        if skeleton_node is not None:
+            mesh_node['skeleton'] = NodePath(
+                skeleton_node.get_path(), mesh_node.get_path())
         if not physics.has_physics(node) or not physics.is_physics_root(node):
             mesh_node['transform'] = node.matrix_local
         else:
@@ -46,7 +65,7 @@ def export_mesh_node(escn_file, export_settings, node, parent_gd_node):
         return mesh_node
 
 
-def export_mesh(escn_file, export_settings, node, armature):
+def export_mesh(escn_file, export_settings, node, armature_data):
     """Saves a mesh into the escn file """
     # Check if it exists so we don't bother to export it twice
     mesh = node.data
@@ -61,7 +80,7 @@ def export_mesh(escn_file, export_settings, node, armature):
         escn_file,
         export_settings,
         node,
-        armature)
+        armature_data)
 
     for surface in surfaces:
         mesh_resource[surface.name_str] = surface
@@ -72,12 +91,21 @@ def export_mesh(escn_file, export_settings, node, armature):
     return mesh_id
 
 
-def make_arrays(escn_file, export_settings, node, armature):
+def make_arrays(escn_file, export_settings, node, armature_data):
     """Generates arrays of positions, normals etc"""
+    if armature_data is not None:
+        original_pose_position = armature_data.pose_position
+        armature_data.pose_position = 'REST'
+        bpy.context.scene.update()
+
     mesh = node.to_mesh(bpy.context.scene,
                         export_settings['use_mesh_modifiers'],
                         "RENDER")
 
+    if armature_data is not None:
+        armature_data.pose_position = original_pose_position
+        bpy.context.scene.update()
+
     # Prepare the mesh for export
     tri_mesh = bmesh.new()
     tri_mesh.from_mesh(mesh)
@@ -106,9 +134,11 @@ def make_arrays(escn_file, export_settings, node, armature):
         has_tangents
     )
 
+    has_bone = True if armature_data is not None else False
+
     for surface_id, surface in enumerate(surfaces):
         surface.id = surface_id
-        surface.armature = armature
+        surface.has_bone = has_bone
 
     bpy.data.meshes.remove(mesh)
 
@@ -166,6 +196,10 @@ def generate_surfaces(escn_file, export_settings, mesh, has_tangents):
                 new_vert.tangent = fix_vertex(loop.tangent)
                 new_vert.bitangent = fix_vertex(loop.bitangent)
 
+            for vertex_group in mesh.vertices[loop.vertex_index].groups:
+                new_vert.bones.append(vertex_group.group)
+                new_vert.weights.append(vertex_group.weight)
+
             # Merge similar vertices
             tup = new_vert.get_tup()
             if tup not in surface.vertex_map:
@@ -189,7 +223,7 @@ class Surface:
         self.indices = []
         self.id = None
         self.material = None
-        self.armature = None
+        self.has_bone = False
 
     def calc_tangent_dp(self, vert):
         """Calculates the dot product of the tangent. I think this has
@@ -279,37 +313,36 @@ class Surface:
 
     def _get_bone_arrays(self):
         """Returns the most influential bones and their weights"""
-        if self.armature is None:
+        if not self.has_bone:
             return [
                 Array("null, ; No Bones", "", ""),
                 Array("null, ; No Weights", "", "")
             ]
 
-        # Skin Weights!
-        float_values = Array("FloatArray(")
-        float_valuesw = Array("FloatArray(")
+        # Skin Weights
+        bone_idx_array = Array("IntArray(")
+        bone_ws_array = Array("FloatArray(")
         for vert in self.vertices:
-            # skin_weights_total += len(v.weights)
             weights = []
-            for i in len(vert.bones):
-                weights += (vert.bones[i], vert.weights[i])
+            for i in range(len(vert.bones)):
+                weights.append((vert.bones[i], vert.weights[i]))
 
-            weights = sorted(weights, key=lambda x: -x[1])
+            weights = sorted(weights, key=lambda x: x[1], reverse=True)
             totalw = 0.0
             for weight in weights:
                 totalw += weight[1]
             if totalw == 0.0:
                 totalw = 0.000000001
 
-            for i in range(4):
+            for i in range(MAX_BONE_PER_VERTEX):
                 if i < len(weights):
-                    float_values.append(weights[i][0])
-                    float_valuesw.append(weights[i][1]/totalw)
+                    bone_idx_array.append(weights[i][0])
+                    bone_ws_array.append(weights[i][1]/totalw)
                 else:
-                    float_values.append(0)
-                    float_valuesw.append(0.0)
+                    bone_idx_array.append(0)
+                    bone_ws_array.append(0.0)
 
-        return float_values, float_valuesw
+        return bone_idx_array, bone_ws_array
 
     @property
     def name_str(self):

+ 15 - 0
io_scene_godot/structures.py

@@ -263,3 +263,18 @@ class Array(list):
             self.seperator.join([str(v) for v in self]),
             self.suffix
         )
+
+
+class NodePath:
+    """Nodes in scene refers to other nodes or nodes' attribute,
+    for example MeshInstane refers Skeleton. """
+    def __init__(self, referee_path, referrer_path, referrer_attr=''):
+        self.ref_path = os.path.relpath(referee_path, referrer_path)
+        self.attr_name = referrer_attr
+
+    def to_string(self):
+        """Serialize a node path"""
+        return 'NodePath("{}:{}")'.format(
+            self.ref_path,
+            self.attr_name
+        )