Import script: Neverwinter Nights text models

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()

That looks like a usefull script! I just got Neverwinter Nights, but I don’t have it here at work with me.