Skip to content

Commit

Permalink
Merge pull request godotengine#227 from Jason0214/fix_vertex_weight
Browse files Browse the repository at this point in the history
Refactor armature exporting
  • Loading branch information
Jason0214 committed Jun 30, 2019
2 parents 556fd11 + 6eefaa3 commit e998175
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 94 deletions.
169 changes: 84 additions & 85 deletions io_scene_godot/converters/armature.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
"""Export a armature node"""
import collections
import mathutils
from ..structures import NodeTemplate, NodePath, Array


def export_bone_attachment(escn_file, node, parent_gd_node):
def export_bone_attachment(escn_file, export_settings,
bl_object, parent_gd_node):
"""Export a blender object with parent_bone to a BoneAttachment"""
bone_attachment = NodeTemplate(node.parent_bone + 'BoneAttachment',
bone_attachment = NodeTemplate(bl_object.parent_bone + 'BoneAttachment',
'BoneAttachment', parent_gd_node)

# node.parent_bone is exactly the bone name
# in the parent armature node
bone_attachment['bone_name'] = "\"{}\"".format(
parent_gd_node.find_bone_name(node.parent_bone)
parent_gd_node.find_bone_name(bl_object.parent_bone)
)

# parent_gd_node is the SkeletonNode with the parent bone
bone_id = parent_gd_node.find_bone_id(node.parent_bone)
bone_id = parent_gd_node.find_bone_id(bl_object.parent_bone)

# append node to its parent bone's bound_children list
parent_gd_node["bones/{}/{}".format(bone_id, 'bound_children')].append(
Expand All @@ -30,9 +30,8 @@ def export_bone_attachment(escn_file, node, parent_gd_node):
class Bone:
"""A Bone has almost same attributes as Godot bones"""

def __init__(self, bone_name, parent_name):
# id assigned when add to skeleton
self.id = None
def __init__(self, bone_id, bone_name, parent_name):
self.id = bone_id
self.name = bone_name
self.parent_name = parent_name
self.rest = mathutils.Matrix()
Expand All @@ -53,122 +52,122 @@ def should_export(export_settings, armature_obj, rest_bone):
return False


def export_bone(pose_bone, bl_bones_to_export):
def export_bone(pose_bone, bones_mapping):
"""Convert a Blender bone to a escn bone"""
assert pose_bone.name in bones_mapping
bone_id = bones_mapping[pose_bone.name]
bone_name = pose_bone.name
parent_bone_name = ""

rest_bone = pose_bone.bone

ps_bone_ptr = pose_bone.parent
while ps_bone_ptr is not None and ps_bone_ptr not in bl_bones_to_export:
ps_bone_ptr = ps_bone_ptr.parent
if ps_bone_ptr is not None:
rest_mat = (ps_bone_ptr.bone.matrix_local.inverted_safe() @
ps_bone_iter = pose_bone.parent
while (ps_bone_iter is not None and
ps_bone_iter.name not in bones_mapping):
ps_bone_iter = ps_bone_iter.parent
if ps_bone_iter is not None:
rest_mat = (ps_bone_iter.bone.matrix_local.inverted_safe() @
rest_bone.matrix_local)
parent_bone_name = ps_bone_ptr.name
parent_bone_name = ps_bone_iter.name
else:
rest_mat = rest_bone.matrix_local
parent_bone_name = ""

pose_mat = pose_bone.matrix_basis

bone = Bone(bone_name, parent_bone_name)
bone = Bone(bone_id, bone_name, parent_bone_name)
bone.rest = rest_mat
bone.pose = pose_mat
return bone


def generate_bones_mapping(export_settings, armature_obj):
"""Return a dict mapping blender bone name to godot bone id"""
bone_id = 0
bones_mapping = dict()
for pose_bone in armature_obj.pose.bones:
if should_export(export_settings, armature_obj, pose_bone.bone):
bones_mapping[pose_bone.name] = bone_id
bone_id += 1

return bones_mapping


class SkeletonNode(NodeTemplate):
"""tscn node with type Skeleton"""
BoneInfo = collections.namedtuple(
"boneInfo",
('id', 'name'),
)

def __init__(self, name, parent):
super().__init__(name, "Skeleton", parent)
self['bones_in_world_transform'] = True

# Mapping from blender bone name to godot bone id and name
self.bones = dict()
#
# Usually we should not keep information in the godot node
# instance, but Skeleton is a special case, we need to reference
# some bone information when dealing with Animation
self._bones_mapping = dict()

def set_bones_mapping(self, bones_mapping):
"""set blender bone name to godot bone id map"""
self._bones_mapping = bones_mapping

def find_bone_id(self, bl_bone_name):
""""Given blender bone name return the bone id in Skeleton node"""
if bl_bone_name not in self.bones:
return -1
return self.bones[bl_bone_name].id
return self._bones_mapping.get(bl_bone_name, -1)

def find_bone_name(self, bl_bone_name):
""""Given blender bone name return the bone name in Skeleton node"""
if bl_bone_name not in self.bones:
return ""
return self.bones[bl_bone_name].name
bone_id = self.find_bone_id(bl_bone_name)
bone_name_key = 'bones/%d/name' % bone_id
return self.get(bone_name_key, "").strip('"')

def find_bone_rest(self, bl_bone_name):
"""Given a blender bone name , return its rest matrix"""
gd_bone_id = self.find_bone_id(bl_bone_name)
if gd_bone_id == -1 or gd_bone_id >= len(self.bones):
return mathutils.Matrix.Identity(4)
bone_rest_key = 'bones/{}/rest'.format(gd_bone_id)
return self[bone_rest_key]

def add_bones(self, bone_list):
"""Add a list of bone to skeleton node"""
# need first add all bones into name_to_id_map,
# otherwise the parent bone finding would be incorrect
bone_name_set = set()
for bone in bone_list:
bone.id = len(self.bones)
bl_bone_name = bone.name

# filter illegal char from blender bone name
bone.name = bone.name.replace(":", "").replace("/", "")
# solve possible name conflict
iterations = 1
gd_bone_name = bone.name
while gd_bone_name in bone_name_set:
gd_bone_name = bone.name + str(iterations).zfill(3)
iterations += 1
bone.name = gd_bone_name

bone_name_set.add(bone.name)
self.bones[bl_bone_name] = SkeletonNode.BoneInfo(
bone.id, bone.name
)

for bone in bone_list:
bone_prefix = 'bones/{}'.format(bone.id)

# bone name must be the first property
self[bone_prefix + '/name'] = '"{}"'.format(bone.name)
self[bone_prefix + '/parent'] = self.find_bone_id(bone.parent_name)
self[bone_prefix + '/rest'] = bone.rest
self[bone_prefix + '/pose'] = bone.pose
self[bone_prefix + '/enabled'] = True
self[bone_prefix + '/bound_children'] = Array(
prefix='[', suffix=']')


def export_armature_node(escn_file, export_settings, node, parent_gd_node):
"""Export an armature node"""
skeleton_node = SkeletonNode(node.name, parent_gd_node)
skeleton_node['transform'] = node.matrix_local

# according to configures, generate a set of blender bones
# need to be exported
bl_bones_to_export = list()
for pose_bone in node.pose.bones:
if should_export(export_settings, node, pose_bone.bone):
bl_bones_to_export.append(pose_bone)
bone_id = self.find_bone_id(bl_bone_name)
bone_rest_key = 'bones/%d/rest' % bone_id
return self.get(bone_rest_key, mathutils.Matrix.Identity(4))


def export_armature_node(escn_file, export_settings,
armature_obj, parent_gd_node):
"""Export an armature object"""
skeleton_node = SkeletonNode(armature_obj.name, parent_gd_node)
skeleton_node['transform'] = armature_obj.matrix_local

bones_mapping = generate_bones_mapping(export_settings, armature_obj)
skeleton_node.set_bones_mapping(bones_mapping)

gd_bone_list = list()
for pose_bone in bl_bones_to_export:
if pose_bone in bl_bones_to_export:
gd_bone = export_bone(pose_bone, bl_bones_to_export)
for pose_bone in armature_obj.pose.bones:
if pose_bone.name in bones_mapping:
gd_bone = export_bone(pose_bone, bones_mapping)
gd_bone_list.append(gd_bone)

skeleton_node.add_bones(gd_bone_list)
bone_name_set = set()
for gd_bone in sorted(gd_bone_list, key=lambda x: x.id):
# filter illegal char from blender bone name
gd_bone.name = gd_bone.name.replace(":", "").replace("/", "")

# solve possible name conflict
iterations = 1
name_tmp = gd_bone.name
while name_tmp in bone_name_set:
name_tmp = gd_bone.name + str(iterations).zfill(3)
iterations += 1
gd_bone.name = name_tmp

bone_name_set.add(gd_bone.name)

bone_prefix = 'bones/{}'.format(gd_bone.id)
# bone name must be the first property
skeleton_node[bone_prefix + '/name'] = '"{}"'.format(gd_bone.name)
skeleton_node[bone_prefix + '/parent'] = \
bones_mapping.get(gd_bone.parent_name, -1)
skeleton_node[bone_prefix + '/rest'] = gd_bone.rest
skeleton_node[bone_prefix + '/pose'] = gd_bone.pose
skeleton_node[bone_prefix + '/enabled'] = True
skeleton_node[bone_prefix + '/bound_children'] = \
Array(prefix='[', suffix=']')

escn_file.add_node(skeleton_node)

Expand Down
17 changes: 8 additions & 9 deletions io_scene_godot/converters/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,13 @@ def init_mesh_bones_data(self, armature_obj, export_settings):
and bone id"""
if armature_obj is None:
return
export_bones = []
for bone in armature_obj.data.bones:
if armature.should_export(export_settings, armature_obj, bone):
export_bones.append(bone)
for bone_id, bone in enumerate(export_bones):
if armature.should_export(export_settings, armature_obj, bone):
group = self.object.vertex_groups.get(bone.name)
if group is not None:
self.vgroup_to_bone_mapping[group.index] = bone_id

bones_mapping = armature.generate_bones_mapping(export_settings,
armature_obj)
for bl_bone_name, gd_bone_id in bones_mapping.items():
group = self.object.vertex_groups.get(bl_bone_name)
if group is not None:
self.vgroup_to_bone_mapping[group.index] = gd_bone_id

def export_mesh(self, escn_file, export_settings):
"""Saves a mesh into the escn file"""
Expand Down Expand Up @@ -544,6 +542,7 @@ def _get_bone_arrays(self):
totalw = 0.0
for index, weight in enumerate(weights):
if index >= MAX_BONE_PER_VERTEX:
logging.warning("Vertex weights more than maximal")
break
totalw += weight[1]

Expand Down
1 change: 1 addition & 0 deletions io_scene_godot/export_godot.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def export_object(self, obj, parent_gd_node):
is_bone_attachment = True
parent_gd_node = converters.BONE_ATTACHMENT_EXPORTER(
self.escn_file,
self.config,
obj,
parent_gd_node
)
Expand Down

0 comments on commit e998175

Please sign in to comment.