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
227
|
import bpy, os
from lxml import etree
from . import collada_funcs
from . import blender_funcs
from . import math_funcs
# Comments (español) (reescribir):
# Bind Pose o Pose de Vínculo (aplica a huesos): es la posición (transformación) que tienen los huesos de un modelo en 3D cuando asignas los huesos a las mallas de dicho modelo. Cualquier otra posición deforma las mallas del modelo. A las matrices que llevan esta información se les denomina matriz de vínculo o bind matrix. En los archivos collada las matrices de vínculo (inversas) de los huesos nombrados en el elemento <Name_array> pueden obtenerse del elemento <float_array> (que viene después de <Name_array>) en el elemento <skin> de dicho archivo. Hay que invertir las matrices en esta sección para obtener las que se van a palicar a los huesos. Estas transformaciones son con respecto al sistema del objeto armadura (no son locales!).
# Bind Shape Matrix o Matriz Forma de Vínculo (aplica a mallas): es la matriz de transformación que se le aplica a las mallas del modelo en 3D para que dichas mallas esten en la posición correcta para ser usadas por los huesos de dicho modelo. De otra forma, las mallas tienen una posición (transformación) original y estas matrices la modifican para entonces tener los vertices de dicha malla en la posición deseada para ser movidos por los huesos del modelo. Esta posición original creo que no tiene que ver con el proceso de vínculo (huesos con la malla) o con la pose de descanso, creo que es algo separado a ambos.
# Rest Pose o Pose de Descanso (aplica a huesos): o pose de descanso, es la posición (transformación) asignada a los huesos luego de enlazar (vincular) los huesos a la malla del modelo en 3D. Por su descripción, puede ser igual o diferente a las transformaciones de hueso en la pose de vínculo. Es la transformación que se usa como base para las animaciones de esqueleto (no se usan para describir la posición de los huesos con las mallas!!!). Estas transformaciones son con respecto al origen del hueso padre (no son locales!).
# Voy a suponer que las mallas con su matriz de forma de vinculo estan ya en la configuración para la pose de vinculo con los huesos (¿será por eso que se llama bind shape matrix?)
# lo lógico es entonces:
# - Cargar las mallas con el importador de collada de blender (Mallas con su BSM)
# - Poner los huesos en su posición de vínculo con la información en las mallas (Huesos en su pose para empezar a deformar las mallas).
# - Crear una pose de descanso para el esqueleto en el modo pose de blender (Huesos en su pose para animar las mallas)
# - Almacenar la informacion de las poses de vínculo y descanso en las propieades de los huesos del esqueleto de blender
# import_collada_superbmd() function
# read superbmd/blenxy collada file
# used to import the collada file into Blender
def import_collada_superbmd(context, filepath):
# scene variable is always needed for something
scene = bpy.context.scene
# make a temporal collada file with the "unit" and "up_axis" elements modified
# and import that file (elements inside the assets element)
# also fix the dumb Z:\ paths that happen when using wine
xml = collada_funcs.check_collada(filepath)
if (xml == None):
blender_funcs.disp_msg("Collada file isn't well formatted")
return {'FINISHED'}
# get asset's unit element
root = xml.getroot()
# texture files path
if (os.name == "posix" and root.find("library_images") != None):
for image in root.find("library_images"):
tmp = image.find("init_from").text
while (tmp[0] != '\\'):
tmp = tmp[1:]
image.find("init_from").text = tmp.replace("\\", "/")
# save the new collada file and load it into blender
xml.write(filepath + ".xml", pretty_print = True)
bpy.ops.wm.collada_import(filepath = filepath + ".xml")
# remove the temporal file
os.remove(filepath + ".xml")
# reconstruct the skeleton for bind pose
# get the armature object and start doing math shidge
armature = None
for obj in scene.objects:
if (obj.select == True and obj.type == "ARMATURE"):
armature = obj
break
# ^ the collada importer selects the armature and its children
# last children is scene's active object
# apply transform and select only the object in edit mode
blender_funcs.select_obj(armature, False, "EDIT")
# Showtime (bind pose, meshes are already in their correct position)
# delete all bones in the armature
bpy.ops.armature.select_all(action = "SELECT")
bpy.ops.armature.delete()
# get the bones bind matrices
# traverse through the <Name_array> and
# <float_array> elements with the bone bind pose data
bind_matrices = {}
for controller in root.find("library_controllers").findall("controller"):
bone_names = controller[0][1][0].text.split()
bone_matrices = controller[0][2][0].text
for i in range(0, len(bone_names)):
mat = collada_funcs.get_text_mat4x4(bone_matrices, 16 * i).inverted()
dict_entry = {bone_names[i] : mat}
bind_matrices.update(dict_entry)
# get the bones rest matrices and in parallel reconstruct the armature
# traverse through the <node> elements in <visual_scene>
rest_matrices = {}
# search for the node named "skeleton_root" inside the <visual_scene>
# element which is inside the <library_visual_scenes> element
jnt_root_node = root.find("library_visual_scenes").find("visual_scene")
for node in jnt_root_node:
if (node.attrib["name"] == "skeleton_root"):
jnt_root_node = node.find("node")
break
# get the bone rest pose information
jnts_same_level = [jnt_root_node]
is_first_jnt = True
while (jnts_same_level != []):
# get the joints for the next iteration
next_jnts = []
for jnt in jnts_same_level:
for elem in jnt.getchildren():
if (elem.tag == "node"):
next_jnts.append(elem)
# get the current joint's matrix information
for jnt in jnts_same_level:
jnt_name = jnt.attrib["name"]
# create the bone
bpy.ops.armature.bone_primitive_add(name = jnt_name)
armature.data.edit_bones[jnt_name].length = 10
mat = collada_funcs.get_text_mat4x4(jnt.find("matrix").text, 0)
# check if this is the root joint of the skeleton
if (is_first_jnt == False):
parent_name = jnt.getparent().attrib["name"]
# parent the bone
armature.data.edit_bones[jnt_name].parent = armature.data.edit_bones[parent_name]
# check if the current bone matrix is not zero filled
if (mat.determinant() == 0):
mat = math_funcs.get_id_mat4x4()
# add the respective matrix to the dictionary and assign it to the bone
rest_matrices.update({jnt_name : mat})
jnts_same_level = next_jnts
is_first_jnt = False
# apply the bind matrices
for i in range(0, len(armature.data.edit_bones)):
bone = armature.data.edit_bones[i]
if (bone.name in bind_matrices):
bone.matrix = bind_matrices[bone.name]
else:
if (bone.parent != None):
bone.matrix = bone.parent.matrix * rest_matrices[bone.name]
else:
bone.matrix = rest_matrices[bone.name]
# get to pose mode and deform the meshes
blender_funcs.select_obj(armature, False, "POSE")
# set current pose to be rest pose and rest pose to be rest pose
for bone in armature.pose.bones:
if (bone.parent != None):
bone.matrix = bone.parent.matrix * rest_matrices[bone.name]
else:
bone.matrix = rest_matrices[bone.name]
# apply visual transform to all meshes
for child in armature.children:
blender_funcs.select_obj(child, False, "OBJECT")
bpy.ops.object.convert(target = "MESH")
# apply pose to rest pose
blender_funcs.select_obj(armature, False, "POSE")
bpy.ops.pose.armature_apply()
# reassign the armature modifiers to all meshes
for child in armature.children:
blender_funcs.select_obj(child, False, "OBJECT")
bpy.ops.object.modifier_add(type = "ARMATURE")
child.modifiers["Armature"].object = armature
child.modifiers["Armature"].use_vertex_groups = True
# scale down the model and apply transformations
armature.scale = (0.01, 0.01, 0.01)
blender_funcs.transf_apply_recurse(scene, armature, True, True, True)
# change the rotation mode of the pose bones
for bone in armature.pose.bones:
bone.rotation_mode = "XYZ"
# remove doubles
for mesh in armature.children:
if (mesh.type == "MESH"):
blender_funcs.select_obj(mesh, False, "EDIT")
bpy.ops.mesh.select_all(action = "SELECT")
bpy.ops.mesh.remove_doubles()
bpy.ops.mesh.select_all(action = "DESELECT")
blender_funcs.select_obj(mesh, False, "OBJECT")
# re-select the armature object
blender_funcs.select_obj(armature, False, "OBJECT")
# done!
blender_funcs.disp_msg("SuperBMD Collada file imported!")
return {'FINISHED'}
# Stuff down is for the menu appending
# of the importer to work plus some setting stuff
# comes from a Blender importer template
# ExportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator
class import_superbmd_collada(Operator, ExportHelper):
"""Import a Collada file from SuperBMD (SuperBMD only)"""
bl_idname = "import_scene.superbmd_collada"
bl_label = "Import SuperBMD Collada (.DAE)"
# ExportHelper mixin class uses this
filename_ext = ".dae"
filter_glob = StringProperty(default = "*.dae", options = {'HIDDEN'}, maxlen = 255)
bruh = BoolProperty(name = "Bruh?",
description = "LMAOOOOO I made you read",
default = False)
# execute function
def execute(self, context):
return import_collada_superbmd(context, self.filepath)
# Only needed if you want to add into a dynamic menu
def menu_import_superbmd_collada(self, context):
self.layout.operator(import_superbmd_collada.bl_idname, text = "SuperBMD Collada (.dae)")
bpy.utils.register_class(import_superbmd_collada)
bpy.types.INFO_MT_file_import.append(menu_import_superbmd_collada)
# test call
bpy.ops.import_scene.superbmd_collada("INVOKE_DEFAULT")
|