blender/release/scripts/ms3d_import_ascii.py

480 lines
13 KiB
Python
Raw Normal View History

#!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')