I’ve written a little script that runs in Blender 2.25. It’s far from done, I want to support animations, materials, particles, and exporting, but here it is for now.
Input data are models and textures from the Neverwinter Nights Model Viewer.
It imports triangle meshes and converts other objects to Empties (which is probably the right type for dummies), and correctly preserves hierarchies, textures, and UV coordinates. Note: it doesn’t work right unless started in the directory the models and textures are in. Inheritance from other models does not work correctly; the Jaguar keeps the talons from the Dire Cat, but the Jaguar should not have any.
The next big hurdle will probably be converting those animations into Ipos. Rotation code is disabled because it didn’t do the intended type of rotation; I have no clue when it comes to this 3D matrix math.
from Blender import Object, NMesh, Window, Scene, Image
from math import *
filename='Dire_Cat.mdl'
mdlnodetoblendobj={'dummy': 'Empty',
'trimesh': 'Mesh'}
def texture(name):
name=name+'.tga'
tex=Image.Get(name)
if not tex:
tex=Image.Load(name)
return tex
def rotate(m1, x, y, z, amount):
s=sin(amount)
c=cos(amount)
t=1-c
m2=[[t*x*x+c, t*x*y-s*z, t*x*z+s*y, 0],
[t*x*y+s*z, t*y*y+c, t*y*z-s*x, 0],
[t*x*z-s*y, t*y*z+s*x, t*z*z+c, 0],
[0, 0, 0, 1]]
result=[[],[],[],[]]
for i in range(4):
for j in range(4):
result[i].append(0)
for k in range(4):
result[i][j]=result[i][j]+m1[i][k]*m2[k][j]
m1=result
def node(file, type, name):
try:
type=mdlnodetoblendobj[type]
except KeyError:
type='Empty'
obj=Object.Get(name)
if obj:
objisnew=0
else:
obj=Object.New(type)
obj.name=name
objisnew=1
if type=='Empty':
# Enable drawing of the name
obj.drawMode=obj.drawMode|8
if type=='Mesh':
mesh=obj.data
vertices=[]
faces=[]
uvcoords=[]
line=file.readline().split()
while line[0]!='endnode':
if line[0]=='parent' and line[1]!='NULL':
parent=Object.Get(line[1])
parent.makeParent([obj])
#elif line[0]=='wirecolor':
# [r,g,b]=map(float, line[1:4])
# wc=NMesh.Col(r*255, g*255, b*255, 255)
elif line[0]=='bitmap':
tex=texture(line[1])
elif line[0]=='position':
pos=map(float, line[1:4])
#obj.matrix[3][0:3]=pos[0:3]
obj.LocX=pos[0]
obj.LocY=pos[1]
obj.LocZ=pos[2]
# FIXME how should this be applied?
#elif line[0]=='orientation':
# rot=map(float, line[1:5])
# loc=obj.matrix[3][0:3]
# obj.matrix[3][0:3]=[1,1,1]
# rotate(obj.matrix, rot[0], rot[1], rot[2], rot[3])
# obj.matrix[3][0:3]=loc
elif line[0]=='verts':
for i in range(int(line[1])):
pos=map(float, file.readline().split())
vertices.append(pos)
elif line[0]=='faces':
for i in range(int(line[1])):
vi=map(int, file.readline().split())
faces.append(vi)
elif line[0]=='tverts':
for i in range(int(line[1])):
tverts=map(float, file.readline().split())
# Blender expects a tuple, and does only 2D texture coordinates.
uvcoords.append((tverts[0], tverts[1]))
line=file.readline().split()
if type=='Mesh':
mesh.faces=[]
mesh.verts=[]
for pos in vertices:
mesh.verts.append(NMesh.Vert(pos[0], pos[1], pos[2]))
for fv in faces:
face=NMesh.Face()
for i in range(3):
face.v.append(mesh.verts[fv[i]])
face.uv.append(uvcoords[fv[i+4]])
face.image=tex
mesh.faces.append(face)
mesh.update()
if objisnew:
Scene.getCurrent().link(obj)
def loadnwnmdl(filename):
file=open(filename)
inmodelgeom=0
line=file.readline().strip()
while line!='':
if len(line)==0:
continue
line=line.split()
if line[0]=='beginmodelgeom':
inmodelgeom=1
elif line[0]=='setsupermodel':
if line[2]!='NULL':
loadnwnmdl(line[2]+'.mdl')
elif inmodelgeom and line[0]=='node':
node(file, line[1], line[2])
elif line[0]=='endmodelgeom':
inmodelgeom=0
line=file.readline().strip()
file.close()
del file
loadnwnmdl(filename)
Window.RedrawAll()