Adding Call of Duty IO alias BlenderCoD to contrib.
[blender-addons-contrib.git] / io_scene_cod / import_xmodel.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 """
22 Blender-CoD: Blender Add-On for Call of Duty modding
23 Version: alpha 3
24
25 Copyright (c) 2011 CoDEmanX, Flybynyt -- blender-cod@online.de
26
27 http://code.google.com/p/blender-cod/
28
29 NOTES
30 - Code is in early state of development and work in progress!
31 - Importing rigs from XMODEL_EXPORT v6 works, but the code is really messy.
32
33 TODO
34 - Implement full xmodel import
35
36 """
37
38 import os
39 import bpy
40 from mathutils import *
41 import math
42 #from mathutils.geometry import tesselate_polygon
43 #from io_utils import load_image, unpack_list, unpack_face_list
44
45 def round_matrix_3x3(mat, precision=6):
46     return Matrix(((round(mat[0][0],precision), round(mat[0][1],precision), round(mat[0][2],precision)),
47                 (round(mat[1][0],precision), round(mat[1][1],precision), round(mat[1][2],precision)),
48                 (round(mat[2][0],precision), round(mat[2][1],precision), round(mat[2][2],precision))))
49
50 def load(self, context, filepath=""):
51
52     test_0 = []
53     test_1 = []
54     test_2 = []
55     test_3 = []
56
57     state = 0
58
59     # placeholders
60     vec0 = Vector((0.0, 0.0, 0.0))
61     mat0 = Matrix(((0.0, 0.0, 0.0),(0.0, 0.0, 0.0),(0.0, 0.0, 0.0)))
62
63     numbones = 0
64     numbones_i = 0
65     bone_i = 0
66     bone_table = []
67     numverts = 0
68     vert_i = 0
69     vert_table = [] # allocate table? [0]*numverts
70     face_i = 0
71     face_tmp = []
72     face_table = []
73     bones_influencing_num = 0
74     bones_influencing_i = 0
75     numfaces = 0
76
77     print("\nImporting %s" % filepath)
78
79     try:
80         file = open(filepath, "r")
81     except IOError:
82         return "Could not open file for reading:\n%s" % filepath
83
84     for line in file:
85         line = line.strip()
86         line_split = line.split()
87
88         # Skip empty and comment lines
89         if not line or line[0] == "/":
90             continue
91
92         elif state == 0 and line_split[0] == "MODEL":
93             state = 1
94
95         elif state == 1 and line_split[0] == "VERSION":
96             if line_split[1] != "6":
97                 error_string = "Unsupported version: %s" % line_split[1]
98                 print("\n%s" % error_string)
99                 return error_string
100             state = 2
101
102         elif state == 2 and line_split[0] == "NUMBONES":
103             numbones = int(line_split[1])
104             state = 3
105
106         elif state == 3 and line_split[0] == "BONE":
107             if numbones_i != int(line_split[1]):
108                 error_string = "Unexpected bone number: %s (expected %i)" % (line_split[1], numbones_i)
109                 print("\n%s" % error_string)
110                 return error_string
111             bone_table.append((line_split[3][1:-1], int(line_split[2]), vec0, mat0))
112             test_0.append(line_split[3][1:-1])
113             test_1.append(int(line_split[2]))
114             if numbones_i >= numbones-1:
115                 state = 4
116             else:
117                 numbones_i += 1
118
119         elif state == 4 and line_split[0] == "BONE":
120             bone_num = int(line_split[1])
121             if bone_i != bone_num:
122                 error_string = "Unexpected bone number: %s (expected %i)" % (line_split[1], bone_i)
123                 print("\n%s" % error_string)
124                 return error_string
125             state = 5
126
127         elif state == 5 and line_split[0] == "OFFSET":
128             # remove commas - line_split[#][:-1] would also work, but isn't as save
129             line_split = line.replace(",", "").split()
130
131             # should we check for len(line_split) to ensure we got enough elements?
132             # Note: we can't assign a new vector to tuple object, we need to change each value
133
134             bone_table[bone_i][2].xyz = Vector((float(line_split[1]), float(line_split[2]), float(line_split[3])))
135             #print("\nPROBLEMATIC: %s" % bone_table[bone_i][2])
136             #NO ERROR HERE, but for some reason the whole table will contain the same vectors
137             #bone_table[bone_i][2][0] = float(line_split[1])
138             #bone_table[bone_i][2][1] = float(line_split[2])
139             #bone_table[bone_i][2][2] = float(line_split[3])
140             test_2.append(Vector((float(line_split[1]),float(line_split[2]),float(line_split[3]))))
141
142             state = 6
143
144         elif state == 6 and line_split[0] == "SCALE":
145             # always 1.000000?! no processing so far...
146             state = 7
147
148         elif state == 7 and line_split[0] == "X":
149             line_split = line.replace(",", "").split()
150             bone_table[bone_i][3][0] = Vector((float(line_split[1]), float(line_split[2]), float(line_split[3])))
151
152             """ Use something like this:
153             bone.align_roll(targetmatrix[2])
154             roll = roll%360 #nicer to have it 0-359.99...
155             """
156             m_col = []
157             m_col.append((float(line_split[1]), float(line_split[2]), float(line_split[3])))
158             
159             state = 8
160
161         elif state == 8 and line_split[0] == "Y":
162             line_split = line.replace(",", "").split()
163             bone_table[bone_i][3][1] = Vector((float(line_split[1]), float(line_split[2]), float(line_split[3])))
164             
165             m_col.append((float(line_split[1]), float(line_split[2]), float(line_split[3])))
166
167             state = 9
168
169         elif state == 9 and line_split[0] == "Z":
170             line_split = line.replace(",", "").split()
171             vec_roll = Vector((float(line_split[1]), float(line_split[2]), float(line_split[3])))
172             ##bone_table[bone_i][3][2] = vec_roll
173             #print("bone_table: %s" % bone_table[bone_i][3][2])
174             
175             m_col.append((float(line_split[1]), float(line_split[2]), float(line_split[3])))
176
177             #test_3.append(Vector(vec_roll))
178             
179             test_3.append(m_col)
180             #print("test_3: %s\n\n" % test_3[:])
181
182             if bone_i >= numbones-1:
183                 state = 10
184             else:
185                 #print("\n---> Increasing bone: %3i" % bone_i)
186                 #print("\t" + str(bone_table[bone_i][3]))
187                 #print("\t" + str(bone_table[bone_i][0]))
188                 bone_i += 1
189                 state = 4
190
191         elif state == 10 and line_split[0] == "NUMVERTS":
192             numverts = int(line_split[1])
193             state = 11
194
195         elif state == 11 and line_split[0] == "VERT":
196             vert_num = int(line_split[1])
197             if vert_i != vert_num:
198                 error_string = "Unexpected vertex number: %s (expected %i)" % (line_split[1], vert_i)
199                 print("\n%s" % error_string)
200                 return error_string
201             vert_i += 1
202             state = 12
203
204         elif state == 12 and line_split[0] == "OFFSET":
205             line_split = line.replace(",", "").split()
206             vert_table.append(Vector((float(line_split[1]), float(line_split[2]), float(line_split[3]))))
207             state = 13
208
209         elif state == 13 and line_split[0] == "BONES":
210             # TODO: process
211             bones_influencing_num = int(line_split[1])
212             state= 14
213
214         elif state == 14 and line_split[0] == "BONE":
215             # TODO: add bones to vert_table
216             if bones_influencing_i >= bones_influencing_num-1:
217                 if vert_i >= numverts:
218                     state = 15
219                 else:
220                     state = 11
221             else:
222                 bones_influencing_i += 1
223                 #state = 14
224
225         elif state == 15 and line_split[0] == "NUMFACES":
226             numfaces = int(line_split[1])
227             state = 16
228             
229         elif state == 16: #and line_split[0] == "TRI":
230             #face_i += 1
231             face_tmp = []
232             state = 17
233             
234         elif (state == 17 or state == 21 or state == 25) and line_split[0] == "VERT":
235             #print("face_tmp length: %i" % len(face_tmp))
236             face_tmp.append(int(line_split[1]))
237             state += 1
238         
239         elif (state == 18 or state == 22 or state == 26) and line_split[0] == "NORMAL":
240             state += 1
241             
242         elif (state == 19 or state == 23 or state == 27) and line_split[0] == "COLOR":
243             state += 1
244             
245         elif (state == 20 or state == 24 or state == 28) and line_split[0] == "UV":
246             state += 1
247         
248         elif state == 29:
249
250             #print("Adding face: %s\n%i faces so far (of %i)\n" % (str(face_tmp), face_i, numfaces))
251             face_table.append(face_tmp)
252             if (face_i >= numfaces - 1):
253                 state = 30
254             else:
255                 face_i += 1
256                 face_tmp = []
257                 state = 17
258                 
259         elif state > 15 and state < 30 and line_split[0] == "NUMOBJECTS":
260             print("Bad numfaces, terminated loop\n")
261             state = 30
262             
263         elif state == 30:
264             print("Adding mesh!")
265             me = bpy.data.meshes.new("pymesh")
266             me.from_pydata(vert_table, [], face_table)
267             me.update()
268             ob = bpy.data.objects.new("Py-Mesh", me)
269             bpy.context.scene.objects.link(ob)
270             
271             state = 31
272
273         else: #elif state == 16:
274             #UNDONE
275             print("eh? state is %i line: %s" % (state, line))
276             pass
277
278         #print("\nCurrent state=" + str(state) + "\nLine:" + line)
279
280     #print("\n" + str(list(bone_table)) + "\n\n" + str(list(vert_table)))
281
282     #createRig(context, "Armature", Vector((0,0,0)), bone_table)
283
284     name = "Armature"
285     origin = Vector((0,0,0))
286     boneTable = bone_table
287
288     # If no context object, an object was deleted and mode is 'OBJECT' for sure
289     if context.object: #and context.mode is not 'OBJECT':
290
291         # Change mode, 'cause modes like POSE will lead to incorrect context poll
292         bpy.ops.object.mode_set(mode='OBJECT')
293
294     # Create armature and object
295     bpy.ops.object.add(
296         type='ARMATURE', 
297         enter_editmode=True,
298         location=origin)
299     ob = bpy.context.object
300     ob.show_x_ray = True
301     ob.name = name
302     amt = ob.data
303     amt.name = name + "Amt"
304     #amt.show_axes = True
305
306     # Create bones
307     bpy.ops.object.mode_set(mode='EDIT')
308     #for (bname, pname, vector, matrix) in boneTable:
309     #i = 0
310     for (t0, t1, t2, t3) in zip(test_0, test_1, test_2, test_3):
311
312         t3 = Matrix(t3)
313
314         bone = amt.edit_bones.new(t0)
315         if t1 != -1:
316             parent = amt.edit_bones[t1]
317             bone.parent = parent
318             bone.head = parent.tail
319
320             bone.align_roll((parent.matrix.to_3x3()*t3)[2])
321             #local_mat = parent.matrix.to_3x3() * t3()
322             #bone.align_roll(local_mat[2])
323             from math import degrees
324             print("t3[2]: %s\nroll: %f\n---------" % (t3.col[2], degrees(bone.roll)))
325             #bone.roll = math.radians(180 - math.degrees(bone.roll))
326             #print("###\nalign_roll: %s\nroll: %.2f\ntest_3:%s" % (t3, math.degrees(bone.roll), list(test_3)))
327             bone.use_connect = True
328         else:
329             bone.head = (0,0,0)
330             rot = Matrix.Translation((0,0,0))   # identity matrix
331             bone.align_roll(t3[2])
332             bone.use_connect = False
333         bone.tail = t2
334
335     file.close()
336
337 """
338 def createRig(context, name, origin, boneTable):
339
340     # If no context object, an object was deleted and mode is 'OBJECT' for sure
341     if context.object: #and context.mode is not 'OBJECT':
342
343         # Change mode, 'cause modes like POSE will lead to incorrect context poll
344         bpy.ops.object.mode_set(mode='OBJECT')
345
346     # Create armature and object
347     bpy.ops.object.add(
348         type='ARMATURE', 
349         enter_editmode=True,
350         location=origin)
351     ob = bpy.context.object
352     ob.show_x_ray = True
353     ob.name = name
354     amt = ob.data
355     amt.name = name + "Amt"
356     #amt.show_axes = True
357
358     # Create bones
359     bpy.ops.object.mode_set(mode='EDIT')
360     #for (bname, pname, vector, matrix) in boneTable:
361     #i = 0
362     for i in range(len(test_0)):
363         t0 = test_0[i]
364         t1 = test_1[i]
365         t2 = test_2[i]
366         t3 = test_3[i]
367
368         bone = amt.edit_bones.new(t0)
369         if t1 != -1:
370             parent = amt.edit_bones[t1]
371             bone.parent = parent
372             bone.head = parent.tail
373             bone.use_connect = True
374             bone.align_roll(t3)
375             #print("align_roll: %s\nroll: %.2f" % (t3, math.degrees(bone.roll)))
376             #(trans, rot, scale) = parent.matrix.decompose()
377         else:
378             bone.head = (0,0,0)
379             rot = Matrix.Translation((0,0,0))   # identity matrix
380             bone.use_connect = False
381         #bone.tail = Vector(vector) * rot + bone.head
382         bone.tail = t2
383         #bone.tail = boneTable[i][2] #passing boneTable as parameter seems to break it :(
384         #i += 1
385
386     #outfile.write("\n%s" % str(boneTable))
387
388     bpy.ops.object.mode_set(mode='OBJECT')
389     return ob
390 """