#!BPY """ Name: 'MilkShape3D (.ms3d)...' Blender: 245 Group: 'Import' Tooltip: 'Import from MilkShape3D file format (.ms3d)' """ # # Author: Markus Ilmola # Email: markus.ilmola@pp.inet.fi # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # import needed stuff import os.path import math from math import * import struct import Blender from Blender import Mathutils from Blender.Mathutils import * # trims a string by removing ending 0 and everything after it def uku(s): try: return s[:s.index('\0')] except: return s # Converts ms3d euler angles to a rotation matrix def RM(a): sy = sin(a[2]) cy = cos(a[2]) sp = sin(a[1]) cp = cos(a[1]) sr = sin(a[0]) cr = cos(a[0]) 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]) # Converts ms3d euler angles to a quaternion def RQ(a): angle = a[2] * 0.5; sy = sin(angle); cy = cos(angle); angle = a[1] * 0.5; sp = sin(angle); cp = cos(angle); angle = a[0] * 0.5; sr = sin(angle); cr = cos(angle); 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) # takes a texture filename and tries to load it def loadImage(path, filename): image = None try: image = Blender.Image.Load(os.path.abspath(filename)) except IOError: print "Warning: Failed to load image: " + filename + ". Trying short path instead...\n" try: image = Blender.Image.Load(os.path.dirname(path) + "/" + os.path.basename(filename)) except IOError: print "Warning: Failed to load image: " + os.path.basename(filename) + "!\n" return image # imports a ms3d file to the current scene def import_ms3d(path): # get scene scn = Blender.Scene.GetCurrent() if scn == None: return "No scene to import to!" # open the file try: file = open(path, 'rb') except IOError: return "Failed to open the file!" # get the file size file.seek(0, os.SEEK_END); fileSize = file.tell(); file.seek(0, os.SEEK_SET); # read id to check if the file is a MilkShape3D file id = file.read(10) if id!="MS3D000000": return "The file is not a MS3D file!" # read version version = struct.unpack("i", file.read(4))[0] if version!=4: return "The file has invalid version!" # Create the mesh scn.objects.selected = [] mesh = Blender.Mesh.New("MilkShape3D Mesh") meshOb = scn.objects.new(mesh) # read the number of vertices numVertices = struct.unpack("H", file.read(2))[0] # read vertices coords = [] boneIds = [] for i in xrange(numVertices): # skip flags file.read(1) # read coords coords.append(struct.unpack("fff", file.read(3*4))) # read bone ids boneIds.append(struct.unpack("b", file.read(1))[0]) # skip refcount file.read(1) # add the vertices to the mesh mesh.verts.extend(coords) # read number of triangles numTriangles = struct.unpack("H", file.read(2))[0] # read triangles faces = [] uvs = [] for i in xrange(numTriangles): # skip flags file.read(2) # read indices (faces) faces.append(struct.unpack("HHH", file.read(3*2))) # read normals normals = struct.unpack("fffffffff", file.read(3*3*4)) # read texture coordinates s = struct.unpack("fff", file.read(3*4)) t = struct.unpack("fff", file.read(3*4)) # store texture coordinates uvs.append([[s[0], 1-t[0]], [s[1], 1-t[1]], [s[2], 1-t[2]]]) if faces[-1][2] == 0: # Cant have zero at the third index faces[-1] = faces[-1][1], faces[-1][2], faces[-1][0] uvs[-1] = uvs[-1][1], uvs[-1][2], uvs[-1][0] # skip smooth group file.read(1) # skip group file.read(1) # add the faces to the mesh mesh.faces.extend(faces) # set texture coordinates for i in xrange(numTriangles): mesh.faces[i].uv = [Vector(uvs[i][0]), Vector(uvs[i][1]), Vector(uvs[i][2])] # read number of groups numGroups = struct.unpack("H", file.read(2))[0] # read groups for i in xrange(numGroups): # skip flags file.read(1) # skip name file.read(32) # read the number of triangles in the group numGroupTriangles = struct.unpack("H", file.read(2))[0] # read the group triangles if numGroupTriangles > 0: triangleIndices = struct.unpack(str(numGroupTriangles) + "H", file.read(2*numGroupTriangles)); # read material material = struct.unpack("b", file.read(1))[0] if material>=0: for j in xrange(numGroupTriangles): mesh.faces[triangleIndices[j]].mat = material # read the number of materials numMaterials = struct.unpack("H", file.read(2))[0] # read materials for i in xrange(numMaterials): # read name name = uku(file.read(32)) # create the material mat = Blender.Material.New(name) mesh.materials += [mat] # read ambient color ambient = struct.unpack("ffff", file.read(4*4))[0:3] mat.setAmb((ambient[0]+ambient[1]+ambient[2])/3) # read diffuse color diffuse = struct.unpack("ffff", file.read(4*4))[0:3] mat.setRGBCol(diffuse) # read specular color specular = struct.unpack("ffff", file.read(4*4))[0:3] mat.setSpecCol(specular) # read emissive color emissive = struct.unpack("ffff", file.read(4*4))[0:3] mat.setEmit((emissive[0]+emissive[1]+emissive[2])/3) # read shininess shininess = struct.unpack("f", file.read(4))[0] # read transparency transparency = struct.unpack("f", file.read(4))[0] mat.setAlpha(transparency) if transparency < 1: mat.mode |= Blender.Material.Modes.ZTRANSP # read mode mode = struct.unpack("B", file.read(1))[0] # read texturemap texturemap = uku(file.read(128)) if len(texturemap)>0: colorTexture = Blender.Texture.New(name + "_texture") colorTexture.setType('Image') colorTexture.setImage(loadImage(path, texturemap)) mat.setTexture(0, colorTexture, Blender.Texture.TexCo.UV, Blender.Texture.MapTo.COL) # read alphamap alphamap = uku(file.read(128)) if len(alphamap)>0: alphaTexture = Blender.Texture.New(name + "_alpha") alphaTexture.setType('Image') alphaTexture.setImage(loadImage(path, alphamap)) mat.setTexture(1, alphaTexture, Blender.Texture.TexCo.UV, Blender.Texture.MapTo.ALPHA) # read animation fps = struct.unpack("f", file.read(4))[0] time = struct.unpack("f", file.read(4))[0] frames = struct.unpack("i", file.read(4))[0] # read the number of joints numJoints = struct.unpack("H", file.read(2))[0] # create the armature armature = 0 armOb = 0 if numJoints > 0: armOb = Blender.Object.New('Armature', "MilkShape3D Skeleton") armature = Blender.Armature.New("MilkShape3D Skeleton") armature.drawType = Blender.Armature.STICK armOb.link(armature) scn.objects.link(armOb) armOb.makeParentDeform([meshOb]) armature.makeEditable() # read joints joints = [] rotKeys = {} posKeys = {} for i in xrange(numJoints): # skip flags file.read(1) # read name name = uku(file.read(32)) joints.append(name) # create the bone bone = Blender.Armature.Editbone() armature.bones[name] = bone # read parent parent = uku(file.read(32)) if len(parent)>0: bone.parent = armature.bones[parent] # read orientation rot = struct.unpack("fff", file.read(3*4)) # read position pos = struct.unpack("fff", file.read(3*4)) # set head if bone.hasParent(): bone.head = Vector(pos) * bone.parent.matrix + bone.parent.head tempM = RM(rot) * bone.parent.matrix tempM.transpose; bone.matrix = tempM else: bone.head = Vector(pos) bone.matrix = RM(rot) # set tail bvec = bone.tail - bone.head bvec.normalize() bone.tail = bone.head + 0.01 * bvec # Create vertex group for this bone mesh.addVertGroup(name) vgroup = [] for index, v in enumerate(boneIds): if v==i: vgroup.append(index) mesh.assignVertsToGroup(name, vgroup, 1.0, 1) # read the number of rotation keys numKeyFramesRot = struct.unpack("H", file.read(2))[0] # read the number of postions keys numKeyFramesPos = struct.unpack("H", file.read(2))[0] # read rotation keys rotKeys[name] = [] for j in xrange(numKeyFramesRot): # read time time = fps * struct.unpack("f", file.read(4))[0] # read data rotKeys[name].append([time, struct.unpack("fff", file.read(3*4))]) # read position keys posKeys[name] = [] for j in xrange(numKeyFramesPos): # read time time = fps * struct.unpack("f", file.read(4))[0] # read data posKeys[name].append([time, struct.unpack("fff", file.read(3*4))]) # create action and pose action = 0 pose = 0 if armature!=0: armature.update() pose = armOb.getPose() action = armOb.getAction() if not action: action = Blender.Armature.NLA.NewAction() action.setActive(armOb) # create animation key frames for name, pbone in pose.bones.items(): # create position keys for key in posKeys[name]: pbone.loc = Vector(key[1]) pbone.insertKey(armOb, int(key[0]+0.5), Blender.Object.Pose.LOC, True) # create rotation keys for key in rotKeys[name]: pbone.quat = RQ(key[1]) pbone.insertKey(armOb, int(key[0]+0.5), Blender.Object.Pose.ROT, True) # The old format ends here. If there is more data then the file is newer version # check to see if there are any comments if file.tell()0: print "Group comment: " + file.read(size) # Material comments numComments = struct.unpack("i", file.read(4))[0] for i in range(numComments): file.read(4) # index size = struct.unpack("i", file.read(4))[0] # comment size if size>0: print "Material comment: " + file.read(size) # Joint comments numComments = struct.unpack("i", file.read(4))[0] for i in range(numComments): file.read(4) # index size = struct.unpack("i", file.read(4))[0] # comment size if size>0: print "Joint comment: " + file.read(size) # Model comments numComments = struct.unpack("i", file.read(4))[0] for i in range(numComments): file.read(4) # index size = struct.unpack("i", file.read(4))[0] # comment size if size>0: print "Model comment: " + file.read(size) # Unknown version give a warning else: print "Warning: Unknown version!" # check to see if there is any extra vertex data if file.tell()=0 or ids[1]>=0 or ids[2]>=0: mesh.assignVertsToGroup(joints[boneIds[i]], [i], 0.01*weights[0], 1) if ids[0]>=0: mesh.assignVertsToGroup(joints[ids[0]], [i], 0.01*weights[1], 1) if ids[1]>=0: mesh.assignVertsToGroup(joints[ids[1]], [i], 0.01*weights[2], 1) if ids[2]>=0: mesh.assignVertsToGroup(joints[ids[2]], [i], 0.01*(100-(weights[0]+weights[1]+weights[2])), 1) elif subVersion==1: # read extra data for each vertex for i in xrange(numVertices): # bone ids ids = struct.unpack("bbb", file.read(3)) # weights weights = struct.unpack("BBB", file.read(3)) # add extra vertices with weights to deform groups if ids[0]>=0 or ids[1]>=0 or ids[2]>=0: mesh.assignVertsToGroup(joints[boneIds[i]], [i], 0.01*weights[0], 1) if ids[0]>=0: mesh.assignVertsToGroup(joints[ids[0]], [i], 0.01*weights[1], 1) if ids[1]>=0: mesh.assignVertsToGroup(joints[ids[1]], [i], 0.01*weights[2], 1) if ids[2]>=0: mesh.assignVertsToGroup(joints[ids[2]], [i], 0.01*(100-(weights[0]+weights[1]+weights[2])), 1) # non supported subversion give a warning else: print "Warning: Unknown subversion!" # rest of the extra data in the file is not imported/used # refresh the view Blender.Redraw() # close the file file.close() # succes return empty error string return "" # load the model def fileCallback(filename): error = import_ms3d(filename) if error!="": Blender.Draw.PupMenu("An error occured during import: " + error + "|Not all data might have been imported succesfully.", 2) Blender.Window.FileSelector(fileCallback, 'Import')