Merged 15170:15635 from trunk (no conflicts or even merges)
[blender.git] / release / scripts / ms3d_import.py
1 #!BPY
2 """ 
3 Name: 'MilkShape3D (.ms3d)...'
4 Blender: 245
5 Group: 'Import'
6 Tooltip: 'Import from MilkShape3D file format (.ms3d)'
7 """
8
9 # Author: Markus Ilmola
10 # Email: markus.ilmola@pp.inet.fi
11 #
12 # This program is free software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; either version 2
15 # of the License, or (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25 #
26
27 # import needed stuff
28 import os.path
29 import math
30 from math import *
31 import struct
32 import Blender
33 from Blender import Mathutils
34 from Blender.Mathutils import *
35
36
37 # trims a string by removing ending 0 and everything after it
38 def uku(s):
39         try:
40                 return s[:s.index('\0')]
41         except:
42                 return s
43
44
45 # Converts ms3d euler angles to a rotation matrix
46 def RM(a):
47         sy = sin(a[2])
48         cy = cos(a[2])
49         sp = sin(a[1])
50         cp = cos(a[1])
51         sr = sin(a[0])
52         cr = cos(a[0])
53         return Matrix([cp*cy, cp*sy, -sp], [sr*sp*cy+cr*-sy, sr*sp*sy+cr*cy, sr*cp],[cr*sp*cy+-sr*-sy, cr*sp*sy+-sr*cy, cr*cp])
54
55
56 # Converts ms3d euler angles to a quaternion
57 def RQ(a):
58         angle = a[2] * 0.5;
59         sy = sin(angle);
60         cy = cos(angle);
61         angle = a[1] * 0.5;
62         sp = sin(angle);
63         cp = cos(angle);
64         angle = a[0] * 0.5;
65         sr = sin(angle);
66         cr = cos(angle);
67         return Quaternion(cr*cp*cy+sr*sp*sy, sr*cp*cy-cr*sp*sy, cr*sp*cy+sr*cp*sy, cr*cp*sy-sr*sp*cy)
68
69
70 # takes a texture filename and tries to load it
71 def loadImage(path, filename):
72         image = None
73         try:
74                 image = Blender.Image.Load(os.path.abspath(filename))
75         except IOError:
76                 print "Warning: Failed to load image: " + filename + ". Trying short path instead...\n"
77                 try:
78                         image = Blender.Image.Load(os.path.dirname(path) + "/" + os.path.basename(filename))
79                 except IOError:
80                         print "Warning: Failed to load image: " + os.path.basename(filename) + "!\n"
81         return image
82
83
84 # imports a ms3d file to the current scene
85 def import_ms3d(path):
86         # get scene
87         scn = Blender.Scene.GetCurrent()
88         if scn == None:
89                 return "No scene to import to!"
90
91         # open the file
92         try:
93                 file = open(path, 'rb')
94         except IOError:
95                 return "Failed to open the file!"
96
97         # get the file size
98         file.seek(0, os.SEEK_END);
99         fileSize = file.tell();
100         file.seek(0, os.SEEK_SET);
101
102         # read id to check if the file is a MilkShape3D file
103         id = file.read(10)
104         if id!="MS3D000000":
105                 return "The file is not a MS3D file!"
106
107         # read version
108         version = struct.unpack("i", file.read(4))[0]
109         if version!=4:
110                 return "The file has invalid version!"
111
112         # Create the mesh
113         scn.objects.selected = []
114         mesh = Blender.Mesh.New("MilkShape3D Mesh")
115         meshOb = scn.objects.new(mesh)
116
117         # read the number of vertices
118         numVertices = struct.unpack("H", file.read(2))[0]
119
120         # read vertices
121         coords = []
122         boneIds = []
123         for i in xrange(numVertices):
124                 # skip flags
125                 file.read(1)
126
127                 # read coords
128                 coords.append(struct.unpack("fff", file.read(3*4)))
129
130                 # read bone ids 
131                 boneIds.append(struct.unpack("b", file.read(1))[0])
132
133                 # skip refcount         
134                 file.read(1)
135
136         # add the vertices to the mesh
137         mesh.verts.extend(coords)
138
139         # read number of triangles
140         numTriangles = struct.unpack("H", file.read(2))[0]
141         
142         # read triangles
143         faces = []
144         uvs = []
145         for i in xrange(numTriangles):
146                 # skip flags
147                 file.read(2)
148
149                 # read indices (faces)
150                 faces.append(struct.unpack("HHH", file.read(3*2)))
151
152                 # read normals
153                 normals = struct.unpack("fffffffff", file.read(3*3*4))
154
155                 # read texture coordinates
156                 s = struct.unpack("fff", file.read(3*4))
157                 t = struct.unpack("fff", file.read(3*4))
158
159                 # store texture coordinates
160                 uvs.append([[s[0], 1-t[0]], [s[1], 1-t[1]], [s[2], 1-t[2]]])
161                 
162                 if faces[-1][2] == 0: # Cant have zero at the third index
163                         faces[-1] = faces[-1][1], faces[-1][2], faces[-1][0]
164                         uvs[-1] = uvs[-1][1], uvs[-1][2], uvs[-1][0]
165                 
166                 # skip smooth group
167                 file.read(1)
168
169                 # skip group
170                 file.read(1)
171
172         # add the faces to the mesh
173         mesh.faces.extend(faces)
174
175         # set texture coordinates
176         for i in xrange(numTriangles):
177                 mesh.faces[i].uv = [Vector(uvs[i][0]), Vector(uvs[i][1]), Vector(uvs[i][2])]
178
179         # read number of groups
180         numGroups = struct.unpack("H", file.read(2))[0]
181
182         # read groups
183         for i in xrange(numGroups):
184                 # skip flags
185                 file.read(1)
186
187                 # skip name
188                 file.read(32)
189
190                 # read the number of triangles in the group
191                 numGroupTriangles = struct.unpack("H", file.read(2))[0]
192
193                 # read the group triangles
194                 if numGroupTriangles > 0:
195                         triangleIndices = struct.unpack(str(numGroupTriangles) + "H", file.read(2*numGroupTriangles));
196
197                 # read material
198                 material = struct.unpack("b", file.read(1))[0]
199                 if material>=0:
200                         for j in xrange(numGroupTriangles):
201                                 mesh.faces[triangleIndices[j]].mat = material
202
203         # read the number of materials
204         numMaterials = struct.unpack("H", file.read(2))[0]
205
206         # read materials
207         for i in xrange(numMaterials):
208                 # read name
209                 name = uku(file.read(32))
210
211                 # create the material
212                 mat = Blender.Material.New(name)
213                 mesh.materials += [mat]
214
215                 # read ambient color
216                 ambient = struct.unpack("ffff", file.read(4*4))[0:3]
217                 mat.setAmb((ambient[0]+ambient[1]+ambient[2])/3)
218
219                 # read diffuse color
220                 diffuse = struct.unpack("ffff", file.read(4*4))[0:3]
221                 mat.setRGBCol(diffuse)
222
223                 # read specular color
224                 specular = struct.unpack("ffff", file.read(4*4))[0:3]
225                 mat.setSpecCol(specular)
226
227                 # read emissive color
228                 emissive = struct.unpack("ffff", file.read(4*4))[0:3]
229                 mat.setEmit((emissive[0]+emissive[1]+emissive[2])/3)
230
231                 # read shininess
232                 shininess = struct.unpack("f", file.read(4))[0]
233
234                 # read transparency             
235                 transparency = struct.unpack("f", file.read(4))[0]
236                 mat.setAlpha(transparency)
237                 if transparency < 1:
238                          mat.mode |= Blender.Material.Modes.ZTRANSP
239
240                 # read mode
241                 mode = struct.unpack("B", file.read(1))[0]
242
243                 # read texturemap
244                 texturemap = uku(file.read(128))
245                 if len(texturemap)>0:
246                         colorTexture = Blender.Texture.New(name + "_texture")
247                         colorTexture.setType('Image')
248                         colorTexture.setImage(loadImage(path, texturemap))
249                         mat.setTexture(0, colorTexture, Blender.Texture.TexCo.UV, Blender.Texture.MapTo.COL)
250
251                 # read alphamap
252                 alphamap = uku(file.read(128))
253                 if len(alphamap)>0:
254                         alphaTexture = Blender.Texture.New(name + "_alpha")
255                         alphaTexture.setType('Image') 
256                         alphaTexture.setImage(loadImage(path, alphamap))
257                         mat.setTexture(1, alphaTexture, Blender.Texture.TexCo.UV, Blender.Texture.MapTo.ALPHA)          
258
259         # read animation
260         fps = struct.unpack("f", file.read(4))[0]
261         time = struct.unpack("f", file.read(4))[0]
262         frames = struct.unpack("i", file.read(4))[0]
263
264         # read the number of joints
265         numJoints = struct.unpack("H", file.read(2))[0]
266
267         # create the armature
268         armature = 0
269         armOb = 0
270         if numJoints > 0:
271                 armOb = Blender.Object.New('Armature', "MilkShape3D Skeleton")
272                 armature = Blender.Armature.New("MilkShape3D Skeleton")
273                 armature.drawType = Blender.Armature.STICK
274                 armOb.link(armature)
275                 scn.objects.link(armOb)
276                 armOb.makeParentDeform([meshOb])
277                 armature.makeEditable()
278
279         # read joints
280         joints = []
281         rotKeys = {}
282         posKeys = {}
283         for i in xrange(numJoints):
284                 # skip flags
285                 file.read(1)
286
287                 # read name
288                 name = uku(file.read(32))
289                 joints.append(name)
290
291                 # create the bone
292                 bone = Blender.Armature.Editbone()
293                 armature.bones[name] = bone
294
295                 # read parent
296                 parent = uku(file.read(32))
297                 if len(parent)>0:
298                         bone.parent = armature.bones[parent]
299
300                 # read orientation
301                 rot = struct.unpack("fff", file.read(3*4))
302
303                 # read position
304                 pos = struct.unpack("fff", file.read(3*4))
305                 
306                 # set head
307                 if bone.hasParent():
308                         bone.head =  Vector(pos) * bone.parent.matrix + bone.parent.head
309                         tempM = RM(rot) * bone.parent.matrix
310                         tempM.transpose;
311                         bone.matrix = tempM
312                 else:
313                         bone.head = Vector(pos)
314                         bone.matrix = RM(rot)
315
316                 # set tail
317                 bvec = bone.tail - bone.head
318                 bvec.normalize()
319                 bone.tail = bone.head + 0.01 * bvec
320
321                 # Create vertex group for this bone
322                 mesh.addVertGroup(name)
323                 vgroup = []
324                 for index, v in enumerate(boneIds):
325                         if v==i:
326                                 vgroup.append(index)
327                 mesh.assignVertsToGroup(name, vgroup, 1.0, 1)
328         
329                 # read the number of rotation keys
330                 numKeyFramesRot = struct.unpack("H", file.read(2))[0]
331                         
332                 # read the number of postions keys
333                 numKeyFramesPos = struct.unpack("H", file.read(2))[0]
334
335                 # read rotation keys
336                 rotKeys[name] = []              
337                 for j in xrange(numKeyFramesRot):
338                         # read time
339                         time = fps * struct.unpack("f", file.read(4))[0]
340                         # read data
341                         rotKeys[name].append([time, struct.unpack("fff", file.read(3*4))])
342
343                 # read position keys
344                 posKeys[name] = []
345                 for j in xrange(numKeyFramesPos):
346                         # read time
347                         time = fps * struct.unpack("f", file.read(4))[0]
348                         # read data
349                         posKeys[name].append([time, struct.unpack("fff", file.read(3*4))])
350         
351         # create action and pose
352         action = 0
353         pose = 0
354         if armature!=0:
355                 armature.update()
356                 pose = armOb.getPose()
357                 action = armOb.getAction()
358                 if not action:
359                         action = Blender.Armature.NLA.NewAction()
360                         action.setActive(armOb)
361
362         # create animation key frames
363         for name, pbone in pose.bones.items():
364                 # create position keys
365                 for key in posKeys[name]:
366                         pbone.loc = Vector(key[1])
367                         pbone.insertKey(armOb, int(key[0]+0.5), Blender.Object.Pose.LOC, True)
368
369                 # create rotation keys
370                 for key in rotKeys[name]:
371                         pbone.quat = RQ(key[1])
372                         pbone.insertKey(armOb, int(key[0]+0.5), Blender.Object.Pose.ROT, True)
373
374         # The old format ends here. If there is more data then the file is newer version
375
376         # check to see if there are any comments
377         if file.tell()<fileSize:
378
379                 # read sub version
380                 subVersion = struct.unpack("i", file.read(4))[0]
381
382                 # Is the sub version a supported one
383                 if subVersion==1:
384
385                         # Group comments
386                         numComments = struct.unpack("i", file.read(4))[0]
387                         for i in range(numComments):
388                                 file.read(4) # index
389                                 size = struct.unpack("i", file.read(4))[0] # comment size
390                                 if size>0:
391                                         print "Group comment: " + file.read(size)
392
393                         # Material comments
394                         numComments = struct.unpack("i", file.read(4))[0]
395                         for i in range(numComments):
396                                 file.read(4) # index
397                                 size = struct.unpack("i", file.read(4))[0] # comment size
398                                 if size>0:
399                                         print "Material comment: " + file.read(size)
400
401                         # Joint comments
402                         numComments = struct.unpack("i", file.read(4))[0]
403                         for i in range(numComments):
404                                 file.read(4) # index
405                                 size = struct.unpack("i", file.read(4))[0] # comment size
406                                 if size>0:
407                                         print "Joint comment: " + file.read(size)
408
409                         # Model comments
410                         numComments = struct.unpack("i", file.read(4))[0]
411                         for i in range(numComments):
412                                 file.read(4) # index
413                                 size = struct.unpack("i", file.read(4))[0] # comment size
414                                 if size>0:
415                                         print "Model comment: " + file.read(size)
416
417                 # Unknown version give a warning
418                 else:
419                         print "Warning: Unknown version!"
420
421                 
422         # check to see if there is any extra vertex data
423         if file.tell()<fileSize:
424                 
425                 # read the subversion
426                 subVersion = struct.unpack("i", file.read(4))[0]
427
428                 # is the version supported
429                 if subVersion==2:
430                         # read the extra data for each vertex
431                         for i in xrange(numVertices):
432                                 # bone ids
433                                 ids = struct.unpack("bbb", file.read(3))
434                                 # weights
435                                 weights = struct.unpack("BBB", file.read(3))
436                                 # extra
437                                 extra = struct.unpack("I", file.read(4))
438                                 # add extra vertices with weights to deform groups
439                                 if ids[0]>=0 or ids[1]>=0 or ids[2]>=0:
440                                         mesh.assignVertsToGroup(joints[boneIds[i]], [i], 0.01*weights[0], 1)
441                                         if ids[0]>=0:
442                                                 mesh.assignVertsToGroup(joints[ids[0]], [i], 0.01*weights[1], 1)
443                                         if ids[1]>=0:
444                                                 mesh.assignVertsToGroup(joints[ids[1]], [i], 0.01*weights[2], 1)
445                                         if ids[2]>=0:
446                                                 mesh.assignVertsToGroup(joints[ids[2]], [i], 0.01*(100-(weights[0]+weights[1]+weights[2])), 1)  
447                                         
448                 elif subVersion==1:
449                         # read extra data for each vertex
450                         for i in xrange(numVertices):
451                                 # bone ids
452                                 ids = struct.unpack("bbb", file.read(3))
453                                 # weights
454                                 weights = struct.unpack("BBB", file.read(3))
455                                 # add extra vertices with weights to deform groups
456                                 if ids[0]>=0 or ids[1]>=0 or ids[2]>=0:
457                                         mesh.assignVertsToGroup(joints[boneIds[i]], [i], 0.01*weights[0], 1)
458                                         if ids[0]>=0:
459                                                 mesh.assignVertsToGroup(joints[ids[0]], [i], 0.01*weights[1], 1)
460                                         if ids[1]>=0:
461                                                 mesh.assignVertsToGroup(joints[ids[1]], [i], 0.01*weights[2], 1)
462                                         if ids[2]>=0:
463                                                 mesh.assignVertsToGroup(joints[ids[2]], [i], 0.01*(100-(weights[0]+weights[1]+weights[2])), 1)  
464
465                 # non supported subversion give a warning
466                 else:
467                         print "Warning: Unknown subversion!"
468         
469         # rest of the extra data in the file is not imported/used
470         
471         # refresh the view
472         Blender.Redraw()
473         
474         # close the file
475         file.close()
476
477         # succes return empty error string
478         return ""
479
480
481 # load the model
482 def fileCallback(filename):
483         error = import_ms3d(filename)
484         if error!="":
485                 Blender.Draw.PupMenu("An error occured during import: " + error + "|Not all data might have been imported succesfully.", 2)
486
487 Blender.Window.FileSelector(fileCallback, 'Import')