[#8540] Import scripts for MilkShape3D file formats
authorCampbell Barton <ideasman42@gmail.com>
Tue, 23 Sep 2008 00:49:44 +0000 (00:49 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Tue, 23 Sep 2008 00:49:44 +0000 (00:49 +0000)
from Markus Ilmola (glome)

note, UV's will be incorrect in some faces because of eekadoodle problem.

release/scripts/ms3d_import_ascii.py [new file with mode: 0644]

diff --git a/release/scripts/ms3d_import_ascii.py b/release/scripts/ms3d_import_ascii.py
new file mode 100644 (file)
index 0000000..d8c22a1
--- /dev/null
@@ -0,0 +1,479 @@
+#!BPY
+""" 
+Name: 'MilkShape3D ASCII (.txt)...'
+Blender: 245
+Group: 'Import'
+Tooltip: 'Import from a MilkShape3D ASCII file format (.txt)'
+"""
+# 
+# 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 re
+import math
+from math import *
+import Blender
+from Blender import Mathutils
+from Blender.Mathutils import *
+
+
+
+# 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
+
+
+
+# returns the next non-empty, non-comment line from the file
+def getNextLine(file):
+       ready = False
+       while ready==False:
+               line = file.readline()
+               if len(line)==0:
+                       print "Warning: End of file reached."
+                       return line
+               ready = True
+               line = line.strip()
+               if len(line)==0 or line.isspace():
+                       ready = False
+               if len(line)>=2 and line[0]=='/' and line[1]=='/':
+                       ready = False
+       return line     
+
+
+
+# imports a MilkShape3D ascii file to the current scene
+def import_ms3d_ascii(path):
+       # limits
+       MAX_NUMMESHES = 1000
+       MAX_NUMVERTS = 100000
+       MAX_NUMNORMALS = 100000
+       MAX_NUMTRIS = 100000
+       MAX_NUMMATS = 16
+       MAX_NUMBONES = 100
+       MAX_NUMPOSKEYS = 1000
+       MAX_NUMROTKEYS = 1000
+
+       # get scene
+       scn = Blender.Scene.GetCurrent()
+       if scn==None:
+               return "No scene to import to!"
+
+       # open the file
+       try:
+               file = open(path, 'r')
+       except IOError:
+               return "Failed to open the file!"
+
+       # Read frame info
+       try:
+               lines = getNextLine(file).split()
+               if len(lines) != 2 or lines[0] != "Frames:":
+                       raise ValueError
+               lines = getNextLine(file).split()
+               if len(lines) != 2 or lines[0] != "Frame:":
+                       raise ValueError
+       except ValueError:
+               return "Frame information is invalid!"
+
+       # Create the mesh
+       meshOb = Blender.Object.New('Mesh', "MilkShape3D Object")
+       mesh = Blender.Mesh.New("MilkShape3D Mesh")
+       meshOb.link(mesh)
+       scn.objects.link(meshOb)
+
+       # read the number of meshes
+       try:
+               lines = getNextLine(file).split()
+               if len(lines)!=2 or lines[0]!="Meshes:":
+                       raise ValueError
+               numMeshes = int(lines[1])
+               if numMeshes < 0 or numMeshes > MAX_NUMMESHES:
+                       raise ValueError
+       except ValueError:
+               return "Number of meshes is invalid!"
+
+       # read meshes
+       vertBase = 0
+       faceBase = 0
+       boneIds = []
+       for i in range(numMeshes):
+               # read name, flags and material
+               try:
+                       lines = re.findall(r'\".*\"|[^ ]+', getNextLine(file))
+                       if len(lines)!=3:
+                               raise ValueError
+                       material = int(lines[2])
+               except ValueError:
+                       return "Name, flags or material in mesh " + str(i+1) + " are invalid!"
+       
+               # read the number of vertices
+               try:
+                       numVerts = int(getNextLine(file))
+                       if numVerts < 0 or numVerts > MAX_NUMVERTS:
+                               raise ValueError
+               except ValueError:
+                       return "Number of vertices in mesh " + str(i+1) + " is invalid!"
+
+               # read vertices
+               coords = []
+               uvs = []
+               for j in xrange(numVerts):
+                       try:
+                               lines = getNextLine(file).split()
+                               if len(lines)!=7:
+                                       raise ValueError
+                               coords.append([float(lines[1]), float(lines[2]), float(lines[3])])
+                               uvs.append([float(lines[4]), 1-float(lines[5])])
+                               boneIds.append(int(lines[6]))
+                       except ValueError:
+                               return "Vertex " + str(j+1) + " in mesh " + str(i+1) + " is invalid!"
+               mesh.verts.extend(coords)
+
+               # read number of normals
+               try:
+                       numNormals = int(getNextLine(file))
+                       if numNormals < 0 or numNormals > MAX_NUMNORMALS:
+                               raise ValueError
+               except ValueError:
+                       return "Number of normals in mesh " + str(i+1) + " is invalid!"
+
+               # read normals
+               normals = []
+               for j in xrange(numNormals):
+                       try:
+                               lines = getNextLine(file).split()
+                               if len(lines)!=3:
+                                       raise ValueError
+                               normals.append([float(lines[0]), float(lines[1]), float(lines[2])])
+                       except ValueError:
+                               return "Normal " + str(j+1) + " in mesh " + str(i+1) + " is invalid!"
+
+               # read the number of triangles
+               try:
+                       numTris = int(getNextLine(file))
+                       if numTris < 0 or numTris > MAX_NUMTRIS:
+                               raise ValueError
+               except ValueError:
+                       return "Number of triangles in mesh " + str(i+1) + " is invalid!"
+
+               # read triangles
+               faces = []
+               for j in xrange(numTris):
+                       # read the triangle
+                       try:
+                               lines = getNextLine(file).split()
+                               if len(lines)!=8:
+                                       raise ValueError
+                               v1 = int(lines[1])
+                               v2 = int(lines[2])
+                               v3 = int(lines[3])
+                               faces.append([v1+vertBase, v2+vertBase, v3+vertBase])
+                       except ValueError:
+                               return "Triangle " + str(j+1) + " in mesh " + str(i+1) + " is invalid!"
+               mesh.faces.extend(faces)
+
+               # set texture coordinates and material
+               for j in xrange(faceBase, len(mesh.faces)):
+                       face = mesh.faces[j]
+                       face.uv = [Vector(uvs[face.verts[0].index-vertBase]), Vector(uvs[face.verts[1].index-vertBase]), Vector(uvs[face.verts[2].index-vertBase])]
+                       if material>=0:
+                               face.mat = material
+
+               # increase vertex and face base
+               vertBase = len(mesh.verts)
+               faceBase = len(mesh.faces)
+
+       # read the number of materials
+       try:
+               lines = getNextLine(file).split()
+               if len(lines)!=2 or lines[0]!="Materials:":
+                       raise ValueError
+               numMats = int(lines[1])
+               if numMats < 0 or numMats > MAX_NUMMATS:
+                       raise ValueError
+       except ValueError:
+               return "Number of materials is invalid!"
+
+       # read the materials
+       for i in range(numMats):
+               # read name
+               name = getNextLine(file)[1:-1]
+
+               # create the material
+               mat = Blender.Material.New(name)
+               mesh.materials += [mat]
+
+               # read ambient color
+               try:
+                       lines = getNextLine(file).split()
+                       if len(lines)!=4:
+                               raise ValueError
+                       amb = (float(lines[0])+float(lines[1])+float(lines[2]))/3
+                       mat.setAmb(amb)
+               except ValueError:
+                       return "Ambient color in material " + str(i+1) + " is invalid!"
+
+               # read diffuse color
+               try:
+                       lines = getNextLine(file).split()
+                       if len(lines)!=4:
+                               raise ValueError
+                       mat.setRGBCol([float(lines[0]), float(lines[1]), float(lines[2])])
+               except ValueError:
+                       return "Diffuse color in material " + str(i+1) + " is invalid!"
+
+               # read specular color
+               try:
+                       lines = getNextLine(file).split()
+                       if len(lines)!=4:
+                               raise ValueError
+                       mat.setSpecCol([float(lines[0]), float(lines[1]), float(lines[2])])
+               except ValueError:
+                       return "Specular color in material " + str(i+1) + " is invalid!"
+
+               # read emissive color
+               try:
+                       lines = getNextLine(file).split()
+                       if len(lines)!=4:
+                               raise ValueError
+                       emit = (float(lines[0])+float(lines[1])+float(lines[2]))/3
+                       mat.setEmit(emit)
+               except ValueError:
+                       return "Emissive color in material " + str(i+1) + " is invalid!"
+
+               # read shininess
+               try:
+                       shi = float(getNextLine(file))
+                       #mat.setHardness(int(shi))
+               except ValueError:
+                       return "Shininess in material " + str(i+1) + " is invalid!"
+
+               # read transparency
+               try:
+                       alpha = float(getNextLine(file))
+                       mat.setAlpha(alpha)
+                       if alpha < 1:
+                                mat.mode |= Blender.Material.Modes.ZTRANSP
+               except ValueError:
+                       return "Transparency in material " + str(i+1) + " is invalid!"
+
+               # read texturemap
+               texturemap = getNextLine(file)[1:-1]
+               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 = getNextLine(file)[1:-1]
+               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 the number of bones
+       try:
+               lines = getNextLine(file).split()
+               if len(lines)!=2 or lines[0]!="Bones:":
+                       raise ValueError
+               numBones = int(lines[1])
+               if numBones < 0 or numBones > MAX_NUMBONES:
+                       raise ValueError
+       except:
+               return "Number of bones is invalid!"
+
+       # create the armature
+       armature = None
+       armOb = None
+       if numBones > 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 bones
+       posKeys = {}
+       rotKeys = {}
+       for i in range(numBones):
+               # read name
+               name = getNextLine(file)[1:-1]
+
+               # create the bone
+               bone = Blender.Armature.Editbone()
+               armature.bones[name] = bone
+
+               # read parent
+               parent = getNextLine(file)[1:-1]
+               if len(parent)>0:
+                       bone.parent = armature.bones[parent]
+
+               # read position and rotation
+               try:
+                       lines = getNextLine(file).split()
+                       if len(lines) != 7:
+                               raise ValueError
+                       pos = [float(lines[1]), float(lines[2]), float(lines[3])]
+                       rot = [float(lines[4]), float(lines[5]), float(lines[6])]
+               except ValueError:
+                       return "Invalid position or orientation in a bone!"
+
+               # set position and orientation
+               if bone.hasParent():
+                       bone.head =  Vector(pos) * bone.parent.matrix + bone.parent.head
+                       bone.tail = bone.head + Vector([1,0,0])
+                       tempM = RM(rot) * bone.parent.matrix
+                       tempM.transpose;
+                       bone.matrix = tempM
+               else:
+                       bone.head = Vector(pos)
+                       bone.tail = bone.head + Vector([1,0,0])
+                       bone.matrix = RM(rot)
+
+               # 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 position key frames
+               try:
+                       numPosKeys = int(getNextLine(file))
+                       if numPosKeys < 0 or numPosKeys > MAX_NUMPOSKEYS:
+                               raise ValueError
+               except ValueError:
+                       return "Invalid number of position key frames!"
+
+               # read position key frames
+               posKeys[name] = []
+               for j in range(numPosKeys):
+                       # read time and position
+                       try:
+                               lines = getNextLine(file).split()
+                               if len(lines) != 4:
+                                       raise ValueError
+                               time = float(lines[0])
+                               pos = [float(lines[1]), float(lines[2]), float(lines[3])]
+                               posKeys[name].append([time, pos])
+                       except ValueError:
+                               return "Invalid position key frame!"
+                       
+               # read the number of rotation key frames
+               try:
+                       numRotKeys = int(getNextLine(file))
+                       if numRotKeys < 0 or numRotKeys > MAX_NUMROTKEYS:
+                               raise ValueError
+               except ValueError:
+                       return "Invalid number of rotation key frames!"
+
+               # read rotation key frames
+               rotKeys[name] = []
+               for j in range(numRotKeys):
+                       # read time and rotation
+                       try:
+                               lines = getNextLine(file).split()
+                               if len(lines) != 4:
+                                       raise ValueError
+                               time = float(lines[0])
+                               rot = [float(lines[1]), float(lines[2]), float(lines[3])]
+                               rotKeys[name].append([time, rot])
+                       except ValueError:
+                               return "Invalid rotation key frame!"
+
+       # create action and pose
+       action = None
+       pose = None
+       if armature != None:
+               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)
+
+       # set the imported object to be the selected one
+       scn.objects.selected = []
+       meshOb.sel= 1
+       Blender.Redraw()
+
+       # The import was a succes!
+       return ""
+
+
+# load the model
+def fileCallback(filename):
+       error = import_ms3d_ascii(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')