forked from bartvdbraak/blender
480 lines
13 KiB
Python
480 lines
13 KiB
Python
|
#!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')
|