There's a newer script than the one in the original package. May or may not be the absolute latest, but posted for posterity:
Code: Select all
#!BPY
"""
Name: 'MDL (.mdl)'
Blender: 243
Group: 'Export'
Tooltip: 'Export to QuakeI file format (.mdl)'
"""
__author__ = "Andrew Apted"
__url__ = ("http://openarena.ws")
__version__ = "0.62 2011-03-29"
__bpydoc__ = """\
This script exports a QuakeI file (MDL).
"""
import math
import struct
import Blender
import BPyMesh
#q_shared
MAX_QPATH = 64
MDL_IDENT = "IDPO"
MDL_VERSION = 6
MDL_MAX_SKINS = 64
MDL_MAX_VERTICES = 1024
MDL_MAX_TRIANGLES = 2048
MDL_MAX_FRAMES = 256
MDL_XYZ_SCALE = (1.0 / 64.0)
MDL_BLENDER_SCALE = (1.0 / 1.0)
# strips file type extension
def StripExtension(name):
if name.find('.') != -1:
name = name[:name.find('.')]
return name
#q_math
def VectorLength(v):
return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
def RadiusFromBounds(mins, maxs):
corner = [0, 0, 0]
a = 0
b = 0
for i in range(0, 3):
a = abs(mins[i])
b = abs(maxs[i])
if a > b:
corner[i] = a
else:
corner[i] = b
return VectorLength(corner)
# our own logger class. it works just the same as a normal logger except
# all info messages get show.
class Logger:
def __init__(self, name):
self.has_warnings = False
self.has_errors = False
self.has_critical = False
self.message = ""
self.name = name
self.start = 0
if name not in ( text.getName() for text in Blender.Text.Get() ):
self.outtext = Blender.Text.New(name)
else:
self.outtext = Blender.Text.Get(name)
self.outtext.clear()
self.outtext.write("\n___START___\n\n")
def log(self, type, msg, *args):
self.message = type.ljust(10) + ":" + msg % args
self.outtext.write(self.message+'\n')
print self.message
def info(self, msg, *args):
self.log("info", msg, *args)
def warning(self, msg, *args):
self.log("warning", msg, *args)
self.has_warnings = True
def error(self, msg, *args):
self.log("error", msg, *args)
self.has_errors = True
def critical(self, msg, *args):
self.log("critical", msg, *args)
self.has_errors = True
class BlenderGui:
def __init__(self, log):
text = ["A log has been written to a blender text window.",
"Change this window type to a text window.",
"You will be able to select the file %s." % log.name ]
text+=["Parsed in %i seconds"%(Blender.sys.time() - log.start)]
if log.has_critical:
text += ["There were critical errors!!!!"]
elif log.has_errors:
text += ["There were errors!"]
elif log.has_warnings:
text += ["There were warnings"]
text.reverse()
self.msg = text
Blender.Draw.Register(self.gui, self.event, self.button_event)
def gui(self,):
quitbutton = Blender.Draw.Button("Exit", 1, 0, 0, 100, 20, "Close Window")
y = 35
for line in self.msg:
Blender.BGL.glRasterPos2i(10,y)
Blender.Draw.Text(line)
y+=15
def event(self,evt, val):
if evt == Blender.Draw.ESCKEY:
Blender.Draw.Exit()
return
def button_event(self,evt):
if evt == 1:
Blender.Draw.Exit()
return
log = Logger("mdl_export_log")
#------------------------------------------------------------------------#
class q1ColorCache:
def __init__(self, gamma, fullbright):
self.gamma = gamma
self.fullbright = fullbright
self.cache = {}
self.MakeGammaTab()
if fullbright:
self.color_range = range(0,256)
else:
self.color_range = range(0,224)
self.palette = (
( 0, 0, 0), ( 15, 15, 15), ( 31, 31, 31), ( 47, 47, 47),
( 63, 63, 63), ( 75, 75, 75), ( 91, 91, 91), (107, 107, 107),
(123, 123, 123), (139, 139, 139), (155, 155, 155), (171, 171, 171),
(187, 187, 187), (203, 203, 203), (219, 219, 219), (235, 235, 235),
( 15, 11, 7), ( 23, 15, 11), ( 31, 23, 11), ( 39, 27, 15),
( 47, 35, 19), ( 55, 43, 23), ( 63, 47, 23), ( 75, 55, 27),
( 83, 59, 27), ( 91, 67, 31), ( 99, 75, 31), (107, 83, 31),
(115, 87, 31), (123, 95, 35), (131, 103, 35), (143, 111, 35),
( 11, 11, 15), ( 19, 19, 27), ( 27, 27, 39), ( 39, 39, 51),
( 47, 47, 63), ( 55, 55, 75), ( 63, 63, 87), ( 71, 71, 103),
( 79, 79, 115), ( 91, 91, 127), ( 99, 99, 139), (107, 107, 151),
(115, 115, 163), (123, 123, 175), (131, 131, 187), (139, 139, 203),
( 0, 0, 0), ( 7, 7, 0), ( 11, 11, 0), ( 19, 19, 0),
( 27, 27, 0), ( 35, 35, 0), ( 43, 43, 7), ( 47, 47, 7),
( 55, 55, 7), ( 63, 63, 7), ( 71, 71, 7), ( 75, 75, 11),
( 83, 83, 11), ( 91, 91, 11), ( 99, 99, 11), (107, 107, 15),
( 7, 0, 0), ( 15, 0, 0), ( 23, 0, 0), ( 31, 0, 0),
( 39, 0, 0), ( 47, 0, 0), ( 55, 0, 0), ( 63, 0, 0),
( 71, 0, 0), ( 79, 0, 0), ( 87, 0, 0), ( 95, 0, 0),
(103, 0, 0), (111, 0, 0), (119, 0, 0), (127, 0, 0),
( 19, 19, 0), ( 27, 27, 0), ( 35, 35, 0), ( 47, 43, 0),
( 55, 47, 0), ( 67, 55, 0), ( 75, 59, 7), ( 87, 67, 7),
( 95, 71, 7), (107, 75, 11), (119, 83, 15), (131, 87, 19),
(139, 91, 19), (151, 95, 27), (163, 99, 31), (175, 103, 35),
( 35, 19, 7), ( 47, 23, 11), ( 59, 31, 15), ( 75, 35, 19),
( 87, 43, 23), ( 99, 47, 31), (115, 55, 35), (127, 59, 43),
(143, 67, 51), (159, 79, 51), (175, 99, 47), (191, 119, 47),
(207, 143, 43), (223, 171, 39), (239, 203, 31), (255, 243, 27),
( 11, 7, 0), ( 27, 19, 0), ( 43, 35, 15), ( 55, 43, 19),
( 71, 51, 27), ( 83, 55, 35), ( 99, 63, 43), (111, 71, 51),
(127, 83, 63), (139, 95, 71), (155, 107, 83), (167, 123, 95),
(183, 135, 107), (195, 147, 123), (211, 163, 139), (227, 179, 151),
(171, 139, 163), (159, 127, 151), (147, 115, 135), (139, 103, 123),
(127, 91, 111), (119, 83, 99), (107, 75, 87), ( 95, 63, 75),
( 87, 55, 67), ( 75, 47, 55), ( 67, 39, 47), ( 55, 31, 35),
( 43, 23, 27), ( 35, 19, 19), ( 23, 11, 11), ( 15, 7, 7),
(187, 115, 159), (175, 107, 143), (163, 95, 131), (151, 87, 119),
(139, 79, 107), (127, 75, 95), (115, 67, 83), (107, 59, 75),
( 95, 51, 63), ( 83, 43, 55), ( 71, 35, 43), ( 59, 31, 35),
( 47, 23, 27), ( 35, 19, 19), ( 23, 11, 11), ( 15, 7, 7),
(219, 195, 187), (203, 179, 167), (191, 163, 155), (175, 151, 139),
(163, 135, 123), (151, 123, 111), (135, 111, 95), (123, 99, 83),
(107, 87, 71), ( 95, 75, 59), ( 83, 63, 51), ( 67, 51, 39),
( 55, 43, 31), ( 39, 31, 23), ( 27, 19, 15), ( 15, 11, 7),
(111, 131, 123), (103, 123, 111), ( 95, 115, 103), ( 87, 107, 95),
( 79, 99, 87), ( 71, 91, 79), ( 63, 83, 71), ( 55, 75, 63),
( 47, 67, 55), ( 43, 59, 47), ( 35, 51, 39), ( 31, 43, 31),
( 23, 35, 23), ( 15, 27, 19), ( 11, 19, 11), ( 7, 11, 7),
(255, 243, 27), (239, 223, 23), (219, 203, 19), (203, 183, 15),
(187, 167, 15), (171, 151, 11), (155, 131, 7), (139, 115, 7),
(123, 99, 7), (107, 83, 0), ( 91, 71, 0), ( 75, 55, 0),
( 59, 43, 0), ( 43, 31, 0), ( 27, 15, 0), ( 11, 7, 0),
( 0, 0, 255), ( 11, 11, 239), ( 19, 19, 223), ( 27, 27, 207),
( 35, 35, 191), ( 43, 43, 175), ( 47, 47, 159), ( 47, 47, 143),
( 47, 47, 127), ( 47, 47, 111), ( 47, 47, 95), ( 43, 43, 79),
( 35, 35, 63), ( 27, 27, 47), ( 19, 19, 31), ( 11, 11, 15),
( 43, 0, 0), ( 59, 0, 0), ( 75, 7, 0), ( 95, 7, 0),
(111, 15, 0), (127, 23, 7), (147, 31, 7), (163, 39, 11),
(183, 51, 15), (195, 75, 27), (207, 99, 43), (219, 127, 59),
(227, 151, 79), (231, 171, 95), (239, 191, 119), (247, 211, 139),
(167, 123, 59), (183, 155, 55), (199, 195, 55), (231, 227, 87),
(127, 191, 255), (171, 231, 255), (215, 255, 255), (103, 0, 0),
(139, 0, 0), (179, 0, 0), (215, 0, 0), (255, 0, 0),
(255, 243, 147), (255, 247, 199), (255, 255, 255), (159, 91, 83))
#
def MakeGammaTab(self):
self.gamma_map = {}
for r in range(0,256):
k = r / 255.0
k = k ** (1.0 / self.gamma)
self.gamma_map[r] = int(k * 255.0)
#
def LookupColor(self, r, g, b):
# AJA: find the closest color
# (this might be better done in HSV space, giving more weight
# to the HUE and less weight to the VALUE).
r = self.gamma_map[r]
g = self.gamma_map[g]
b = self.gamma_map[b]
#
best = 0
best_dist = 99999999
#
for i in self.color_range:
dr = self.palette[i][0] - r
dg = self.palette[i][1] - g
db = self.palette[i][2] - b
dist = dr*dr + dg*dg + db*db
if dist == 0: # exact match
return i
if dist < best_dist:
best = i
best_dist = dist
return best
def MapColor(self, r, g, b):
rgb = r*65536 + g*256 + b
if self.cache.has_key(rgb):
return self.cache[rgb]
#
# don't let the cache grow without limit
if len(self.cache) > 8000:
self.cache.clear()
pixel = self.LookupColor(r, g, b)
self.cache[rgb] = pixel
return pixel
class q1Normalizer:
def __init__(self):
self.group_range = range(0,11)
self.x_group = (
(1.0000, 0.0000, 0.0000), (52,52,52,52,143,143,143,143),
(0.9554, 0.2952, 0.0000), (51,51,55,55,141,141,145,145),
(0.9511, 0.1625, 0.2629), (53,63,57,70,142,148,146,151),
(0.8642, 0.4429, 0.2389), (46,61,56,69,19,147,123,150),
(0.8507, 0.5257, 0.0000), (41,41,54,54,18,18,116,116),
(0.8507, 0.0000, 0.5257), (60,67,60,67,144,155,144,155),
(0.8090, 0.3090, 0.5000), (48,62,58,68,16,149,124,152),
(0.7166, 0.6817, 0.1476), (42,43,111,100,20,25,118,117),
(0.6882, 0.5878, 0.4253), (47,76,140,101,21,156,125,161),
(0.6817, 0.1476, 0.7166), (49,65,59,66,15,153,126,154),
(0.5878, 0.4253, 0.6882), (50,75,139,102,17,157,128,160) )
#
self.y_group = (
(0.0000, 1.0000, 0.0000), (32,32,104,104,32,32,104,104),
(0.0000, 0.9554, 0.2952), (33,30,107,103,33,30,107,103),
(0.2629, 0.9511, 0.1625), (36,39,109,105,34,31,122,115),
(0.2389, 0.8642, 0.4429), (35,38,108,97,23,29,121,113),
(0.5257, 0.8507, 0.0000), (44,44,112,112,27,27,119,119),
(0.0000, 0.8507, 0.5257), (6,28,106,90,6,28,106,90),
(0.5000, 0.8090, 0.3090), (37,40,110,98,22,26,120,114),
(0.1476, 0.7166, 0.6817), (8,71,136,92,7,77,130,91),
(0.4253, 0.6882, 0.5878), (45,73,138,99,24,158,131,159),
(0.7166, 0.6817, 0.1476), (42,43,111,100,20,25,118,117),
(0.6882, 0.5878, 0.4253), (47,76,140,101,21,156,125,161) )
#
self.z_group = (
(0.0000, 0.0000, 1.0000), (5,84,5,84,5,84,5,84),
(0.2952, 0.0000, 0.9554), (12,85,12,85,2,82,2,82),
(0.1625, 0.2629, 0.9511), (14,86,134,96,4,83,132,89),
(0.4429, 0.2389, 0.8642), (13,74,133,95,1,81,127,87),
(0.5257, 0.0000, 0.8507), (11,64,11,64,0,80,0,80),
(0.0000, 0.5257, 0.8507), (9,79,137,93,9,79,137,93),
(0.3090, 0.5000, 0.8090), (10,72,135,94,3,78,129,88),
(0.6817, 0.1476, 0.7166), (49,65,59,66,15,153,126,154),
(0.5878, 0.4253, 0.6882), (50,75,139,102,17,157,128,160),
(0.1476, 0.7166, 0.6817), (8,71,136,92,7,77,130,91),
(0.4253, 0.6882, 0.5878), (45,73,138,99,24,158,131,159) )
#
def MapNormal(self, x, y, z):
# AJA: I use the following shortcuts to speed up normal lookup:
#
# Firstly, a preliminary match only uses the first quadrant
# (where all coords are >= 0). Then we use the appropriate
# normal index for the actual quadrant. We can do this because
# the 162 MDL/MD2 normals are not arbitrary but are mirrored in
# every quadrant. The eight numbers in the lists above are the
# indices for each quadrant (+++ ++- +-+ +-- -++ -+- --+ ---).
#
# Secondly we use the axis with the greatest magnitude (of the
# incoming normal) to search an axis-specific group, which means
# we only need to check about 1/3rd of the normals.
#
fx = abs(x)
fy = abs(y)
fz = abs(z)
#
group = self.x_group
if (fy > fx) and (fy > fz):
group = self.y_group
elif (fz > fx) and (fz > fy):
group = self.z_group
#
best = 0
best_dot = -1
#
for i in self.group_range:
dot = group[i*2][0] * fx + group[i*2][1] * fy + group[i*2][2] * fz
#
if dot > best_dot:
best = i
best_dot = dot
#
quadrant = 0
if x < 0:
quadrant += 4
if y < 0:
quadrant += 2
if z < 0:
quadrant += 1
#
return group[best*2+1][quadrant]
col_cache = q1ColorCache(1.0, False)
normalizer = q1Normalizer()
#------------------------------------------------------------------------#
class mdlFrame:
__slots__ = 'name', 'vlist', 'mins', 'maxs'
def __init__(self, name, vlist):
self.name = name
self.vlist = vlist
# compute bbox of this frame
mins = [ +9e9, +9e9, +9e9 ]
maxs = [ -9e9, -9e9, -9e9 ]
for i in range(0, 3):
for V in vlist:
if V[i] < mins[i]: mins[i] = V[i]
if V[i] > maxs[i]: maxs[i] = V[i]
self.mins = mins
self.maxs = maxs
class mdlObject:
def __init__(self):
self.skins = []
self.verts = []
self.tris = []
self.frames = []
#
self.sync_type = 0
self.flags = 0
#
self.skin_w = 32 # temp
self.skin_h = 32
self.avg_tri_size = 20.0
#
self.scale = [1.0] * 3 # temp stuff
self.origin = [0.0] * 3
self.bound_radius = 100.0
self.eye = [0.5, 0.5, 0.5]
#
self.mins = [ +9e9, +9e9, +9e9 ]
self.maxs = [ -9e9, -9e9, -9e9 ]
def AddSkin(self, image):
size = image.getSize()
if len(self.skins) == 0:
self.skin_w = size[0]
self.skin_h = size[1]
## FIXME: elif size[0] != self.skin_w ERROR
self.skins.append(image)
def AddVertex(self, u, v): # returns new vertex index
result = len(self.verts)
# invert vertically
v = 1 - v
# map onto the skin
if u < 0: u = 0
if u > 1: u = 1
if v < 0: v = 0
if v > 1: v = 1
u = int(u * 0.999 * self.skin_w + 0.99)
v = int(v * 0.999 * self.skin_h + 0.99)
on_seam = 0 ## FIXME
self.verts.append( (on_seam, u, v) )
return result
def AddTriangle(self, vlist):
on_front = 0
self.tris.append( (on_front, vlist[0], vlist[2], vlist[1]) )
def AddFrame(self, vlist):
name = "frame_%d" % len(self.frames)
f = mdlFrame(name, vlist)
# update bbox of whole model
for i in range(0, 3):
if f.mins[i] < self.mins[i]: self.mins[i] = f.mins[i]
if f.maxs[i] > self.maxs[i]: self.maxs[i] = f.maxs[i]
self.frames.append(f)
def CalcScaleOrigin(self):
for i in range(0, 3):
size = self.maxs[i] - self.mins[i]
self.scale[i] = size / 253.0
self.origin[i] = self.mins[i] - 1.0 * self.scale[i]
log.info("MDL Scale: (%1.3f, %1.3f, %1.3f)",
self.scale[0], self.scale[1], self.scale[2]);
log.info("MDL Origin: (%1.3f, %1.3f, %1.3f)",
self.origin[0], self.origin[1], self.origin[2]);
def MapCoord(self, x, y, z):
x = int((x - self.origin[0]) / self.scale[0])
y = int((y - self.origin[1]) / self.scale[1])
z = int((z - self.origin[2]) / self.scale[2])
return (x, y, z)
def Write(self, file):
self.CalcScaleOrigin()
#
# Header
#
file.write(struct.pack("<4si", MDL_IDENT, MDL_VERSION))
#
file.write(struct.pack("<3f3f4f",
self.scale[0], self.scale[1], self.scale[2],
self.origin[0], self.origin[1], self.origin[2],
self.bound_radius,
self.eye[0], self.eye[1], self.eye[2]))
#
file.write(struct.pack("<3i",
1, ##!!!!!!!!! FIXME
self.skin_w,
self.skin_h))
#
file.write(struct.pack("<5if",
len(self.verts),
len(self.tris),
len(self.frames),
self.sync_type,
self.flags,
self.avg_tri_size))
#
# Skins
#
if len(self.skins) == 0:
self.WriteDummySkin(file)
else:
for S in self.skins:
self.WriteSkin(file, S)
#
# Vertices (ST coords)
self.WriteVertices(file)
#
# Triangles
self.WriteTriangles(file)
#
# Frames
self.WriteFrames(file)
def WriteSkin(self, file, image):
file.write(struct.pack("<i", 0)) # type = SINGLE
x_range = range(0, self.skin_w)
y_range = reversed(range(0, self.skin_h))
for y in y_range:
for x in x_range:
rgb = image.getPixelI(x, y)
pixel = col_cache.MapColor(rgb[0], rgb[1], rgb[2])
file.write(struct.pack("B", pixel))
def WriteDummySkin(self, file):
file.write(struct.pack("<i", 0)) # type = SINGLE
x_range = range(0, 16)
y_range = range(0, 32)
for y in y_range:
for x in x_range:
pix = 3 + 5 * ((x + int(y/2)) % 2)
file.write(struct.pack("BB", pix, pix))
def WriteVertices(self, file):
for st in self.verts:
file.write(
struct.pack("<3i", st[0], st[1], st[2]))
def WriteTriangles(self, file):
for t in self.tris:
file.write(
struct.pack("<4i", t[0], t[1], t[2], t[3]))
def WriteFrames(self, file):
for f in self.frames:
file.write(struct.pack("<i", 0)) # type = SINGLE
#
mins = self.MapCoord(f.mins[0], f.mins[1], f.mins[2])
maxs = self.MapCoord(f.maxs[0], f.maxs[1], f.maxs[2])
#
file.write(struct.pack("<4B4B",
mins[0], mins[1], mins[2], 0,
maxs[0], maxs[1], maxs[2], 0))
#
file.write(struct.pack("16s", f.name))
#
# all vertices for this frame
for V in f.vlist:
xyz = self.MapCoord(V[0], V[1], V[2])
file.write(struct.pack("<4B",
xyz[0], xyz[1], xyz[2], V[3]))
#------------------------------------------------------------------------#
def Export(fileName):
#log starts here
log.start = Blender.sys.time()
log.info("Quake MDL Export script v0.62")
if (fileName.find('.mdl', -4) <= 0):
fileName += '.mdl'
log.info("Exporting to: %s", fileName)
# create MDL header object (contains everything else)
mdl = mdlObject()
# get the scene
scene = Blender.Scene.GetCurrent()
scene.makeCurrent()
# find the mesh to process
meshOBJ = None
for obj in Blender.Object.GetSelected():
# check if it's a mesh object
if obj.getType() == "Mesh":
meshOBJ = obj
break
if not meshOBJ:
print "Error: Must select a mesh to output as MDL"
Blender.Draw.PupMenu("Selected Object must be a mesh to output as MDL%t|OK")
return
scene.makeCurrent()
total_frames = Blender.Get("curframe")
Blender.Set("curframe", 1)
Blender.Window.Redraw()
# get the object (not just name) and the Mesh, not NMesh
mesh = meshOBJ.getData(False, True)
matrix = meshOBJ.getMatrix('worldspace')
log.info("Materials: %s", mesh.materials)
#if not mesh.materials:
## surf.shaders[0].name = pathName + meshOBJ.name
#else:
## surf.shaders[0].name = pathName + mesh.materials[0].name
# find skin texture
mesh_image = mesh.faces[0].image
if mesh_image == "":
mesh_image = None
if mesh_image:
mdl.AddSkin(mesh_image)
# Process the Mesh....
# because MDL doesnt suppoort faceUVs like blender, we need to duplicate
# any vertex that has multiple uv coords
SeenVerts = {} # maps vertex id + UV coords to the new vertex id
OldToNew = {} # maps old vertex id to a list of new ids after duplicating to account for UV
# process each face in the mesh
for face in mesh.faces:
# this makes a list for each tri in this face. a quad will be [[0,1,2],[0,2,3]]
tris_in_this_face = []
for vi in range(1, len(face)-1):
tris_in_this_face.append([0, vi, vi + 1])
# loop across each tri in the face, then each vertex in the tri
for cur_tri in tris_in_this_face:
tri_verts = []
for vi in cur_tri:
# get the old vertex index and uv coords
old_vert = face.v[vi].index
if mesh.faceUV == True:
uv = tuple(face.uv[vi])
elif mesh.vertexUV:
uv = (face.v[vi].uvco[0], face.v[vi].uvco[1])
else:
uv = (0.0, 0.0) # handle case with no tex coords
if SeenVerts.has_key((old_vert, uv)):
new_vert = SeenVerts[(old_vert, uv)]
else:
# haven't seen this vertex/uv combo before
new_vert = mdl.AddVertex(uv[0], uv[1])
SeenVerts[(old_vert, uv)] = new_vert
# now because we have created a new index,
# we need a way to link it to the index that
# blender returns for NMVert.index
if not OldToNew.has_key(old_vert):
OldToNew[old_vert] = []
OldToNew[old_vert].append(new_vert)
tri_verts.append(new_vert)
mdl.AddTriangle(tri_verts)
# we're done with faces and uv coords
# now vertices are stored as frames :-
# all vertices for frame 1, all vertices for frame 2...., all vertices for frame n
# so we need to iterate across blender's frames, and copy out each vertex
for frame_idx in range(0, total_frames):
# print "Doing frame ", frame_idx, total_frames
Blender.Set("curframe", frame_idx + 1)
Blender.Window.Redraw()
m = BPyMesh.getMeshFromObject(meshOBJ)
m.transform(matrix)
frame_verts = [ (0,0,0,0) ] * len(mdl.verts)
no_face_verts = 0
for V in m.verts:
try:
vlist = OldToNew[V.index]
except:
no_face_verts += 1
continue
norm = normalizer.MapNormal(V.no[0], V.no[1], V.no[2])
vert_info = ( V.co[0], V.co[1], V.co[2], norm )
# apply the position to all the duplicated vertices
for new_vert in vlist:
frame_verts[new_vert] = vert_info
mdl.AddFrame(frame_verts)
if frame_idx == 1 and no_face_verts > 0:
log.warning("Found %d vertices not part of a face",
no_face_verts)
# export!
file = open(fileName, "wb")
mdl.Write(file)
file.close()
log.info("Finished.")
def FileSelectorCallback(fileName):
Export(fileName)
BlenderGui(log)
Blender.Window.FileSelector(FileSelectorCallback, "Export Quake1 MDL", Blender.sys.makename(ext='.mdl'))
#--- editor settings ------------
# vi:ts=4:sw=4:noexpandtab