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