summaryrefslogtreecommitdiff
path: root/collada_superbmd_export.py
blob: e574dc44fb60da3ad5e77ad4a5fbedd3e869f80b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import bpy, math
from . import blender_funcs

# just a DAE exporter.
# it was supposed to only rotate the meshes around the X axis by -90 degrees
# but above that it now checks for armature and vertex group stuff
# only exports the armature object selected    

# write_bmd_bdl_collada function
# function to write custom collada file for
# SuperBMD conversion
def write_bmd_bdl_collada(context, filepath, triangulate):
    
  # this thing is always needed for stuff
  scene = bpy.context.scene
  
  # if nothing is selected end the exporter
  if (scene.objects.active == None or scene.objects.active.type != "ARMATURE"):
    if (scene.objects.active == None):
      blender_funcs.disp_msg("No Armature selected. Select one and try again.")
    else:
      blender_funcs.disp_msg("No Armature selected. Currently selecting: \"%s\""
                             % (scene.objects.active.name))
    return {"FINISHED"}
  
  # get armature object
  armature = scene.objects.active  
  print("\nArmature found: \"%s\"" % (armature.name))
  # select it
  blender_funcs.select_obj(armature, False, "OBJECT")
  
  # check if the armature contains only mesh objects inside
  # unhide all objects
  for child in armature.children:
    child.hide = False
    if (child.type != "MESH"):
      blender_funcs.disp_msg("\"%s\": contains non-mesh object (%s)." % (armature.name, child.name))
      return {"FINISHED"}
  
  # check if there is a single "root level" bone
  # SuperBMD/SMG needs a sigle root bone and the rest must be added as a child to that bone
  bone_with_no_parent_found = False
  for bone in armature.data.bones:
    # the root bone has been found
    if (bone.parent == None):
      if (bone_with_no_parent_found == False):
        bone_with_no_parent_found = True
      else: # second root bone?
        blender_funcs.disp_msg("\"%s\": has 2 or more root level bones (max is 1)."
                               % (armature.name, child.name))
        return {"FINISHED"}
  
  # check if all meshes have an armature modifier that is
  # assigned to the armature object and it is binded to
  # vertex groups if none of those are just create them,
  # I don't think it is bad to automate that
  for mesh in armature.children:
    # check if all meshes have an armature modifier
    has_armature_modifier = False
    has_multiple_armature_modifiers = False
    armature_modifier = None
    
    # iterate over the mesh modifiers
    i = 0
    while (i < len(mesh.modifiers)):
      # check the existance of an armature modifier
      if (mesh.modifiers[i].type == "ARMATURE"):
        if (has_armature_modifier == False):
          has_armature_modifier = True
          armature_modifier = mesh.modifiers[i] # save current armature modifier          
        else: # 2 armature modifiers? (remove duplicates)
          has_multiple_armature_modifiers = True
          mesh.modifiers.remove(mesh.modifiers[i])
          i -= 1
      i += 1
    
    # notify the user about duplicate armature modifiers
    if (has_multiple_armature_modifiers == True):
      blender_funcs.disp_msg("\"%s\": has two or armature modifiers. Removing duplicates..." % (mesh.name))
        
    # check if there is actually an armature modifier
    if (has_armature_modifier == False):
      blender_funcs.disp_msg("\"%s\": has no armature modifier. Adding one..." % (mesh.name))
      armature_modifier = mesh.modifiers.new(name = armature.name, type = "ARMATURE")
      armature_modifier.object = armature
      armature_modifier.use_vertex_groups = True
    else: # ensure bind is to a vertex group and that the bind is to the actual parent armature
      if (armature_modifier.use_vertex_groups == False):
        blender_funcs.disp_msg("\"%s\": armature modifier wasn't binded to vertex groups" % (mesh.name))
        armature_modifier.use_vertex_groups = True
      if (armature_modifier.object != armature):
        blender_funcs.disp_msg("\"%s\": armature modifier was binded to another armature object" % (mesh.name))
        armature_modifier.object = armature;
  
  # check if all the vertex groups in each mesh correspond to the name of a skeleton bone
  bone_name_list = []
  for bone in armature.data.bones:
    bone_name_list.append(bone.name)  
  # vertex group check
  for mesh in armature.children:
    for v_group in mesh.vertex_groups:
      if (v_group.name not in bone_name_list):
        blender_funcs.disp_msg(("\"%s\": contains non-valid vertex group \"%s\"."  % (mesh.name, v_group.name))
                               + " Unable to continue.")
        return {"FINISHED"}
  
  # vertex weight check
  for mesh in armature.children:
    for vertex in mesh.data.vertices:
      # check if the vertex is assigned to at least a single vertex group
      if (len(vertex.groups) == 0):
        blender_funcs.disp_msg(("\"%s\": contains unweighted vertices." % (mesh.name))
                               + " Unable to continue.")
        return {"FINISHED"}
      # check if vertex weights are normalized
      vertex_weight = 0
      for group in vertex.groups:
        vertex_weight += group.weight
      # calling round because there seems to be floating point issues here
      if (round(vertex_weight, 5) > 1):
        blender_funcs.disp_msg(("\"%s\": contains non normalized weight in vertices." % (mesh.name))
                               + " Unable to continue.")
        return {"FINISHED"}
  
  # get the object names
  obj_name = armature.name
  child_names = []
  for child in armature.children:
    child_names.append(child.name)
  
  # change to object view and make a copy of the armature object (with its children)
  blender_funcs.select_obj(armature, True, "OBJECT")
  bpy.ops.object.duplicate(linked = True)
  bpy.ops.object.make_single_user(type = "SELECTED_OBJECTS", object = True, obdata = True)
  old_armature = armature
  armature = scene.objects.active
  blender_funcs.select_obj(armature, False, "OBJECT")
  
  # apply the transformations to the armature
  armature.rotation_euler[0] = math.radians(-90)
  armature.scale = 100 * armature.scale
  bpy.ops.object.transform_apply(location = True, rotation = True, scale = True)
   
  # apply the transformations to the meshes inside the armature
  for i in range(0, len(armature.children)):
    blender_funcs.select_obj(armature.children[i], False, "OBJECT")
    bpy.ops.object.transform_apply(location = True, rotation = True, scale = True)
  blender_funcs.select_obj(armature, False, "OBJECT")
  
  # handle the object names so that they match the original model names
  armature.name = obj_name
  armature.data.name = obj_name
  for i in range(0, len(armature.children)):
    armature.children[i].name = child_names[i] 
    armature.children[i].data.name = child_names[i] 
  
  # delete all the rest_mat/bind_mat custom property matrices
  # they seem to alter exported models in a weird way (I would expect it is a Assimp thing)
  for bone in armature.data.bones:
    if ("bind_mat" in bone):
      bone.pop("bind_mat")
    if ("rest_mat" in bone):
      bone.pop("rest_mat")
  
  # export the object
  bpy.ops.wm.collada_export(filepath = filepath, use_blender_profile = False,
                            selected = True, include_children = True,
                            triangulate = triangulate)
    
  # delete the duplicate object
  blender_funcs.select_obj(armature, True, "OBJECT")
  bpy.ops.object.delete(use_global = False)
  
  # re-assign the original object names
  armature = old_armature
  armature.name = obj_name
  armature.data.name = obj_name
  for i in range(0, len(armature.children)):
    armature.children[i].name = child_names[i] 
    armature.children[i].data.name = child_names[i] 
  
  # done!
  blender_funcs.select_obj(armature, False, "OBJECT")
  blender_funcs.disp_msg("Armature \"%s\" exported!" % (armature.name))
  return {"FINISHED"}

# Stuff down is for the menu appending
# of the exporter to work plus some setting stuff
# comes from a Blender importer template
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator

class export_superbmd_collada(Operator, ExportHelper):
  """Export a Collada file for SuperBMD (SuperBMD only)"""
  bl_idname = "export_scene.superbmd_collada"
  bl_label = "Export SuperBMD Collada (.DAE)"
  filename_ext = ".dae"
  filter_glob = StringProperty(default = "*.dae", options = {'HIDDEN'}, maxlen = 255)
    
  triangulate = BoolProperty(name = "Triangulate meshes",
                             description = "Triangulate meshes inside the armature",
                             default = False)
  def execute(self, context):
    return write_bmd_bdl_collada(context, self.filepath, self.triangulate)

# Only needed if you want to add into a dynamic menu
def menu_export_superbmd_collada(self, context):
  self.layout.operator(export_superbmd_collada.bl_idname, text="SuperBMD Collada (.dae)")

# register func
@bpy.app.handlers.persistent
def register(dummy):
  try:
    bpy.utils.register_class(export_superbmd_collada)
    bpy.types.INFO_MT_file_export.append(menu_export_superbmd_collada)
  except:
    return
    
# unregister func
def unregister():
  try:
    bpy.utils.unregister_class(export_superbmd_collada)
    bpy.types.INFO_MT_file_export.remove(menu_export_superbmd_collada)
  except:
    return