7a6d165641
git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/trunk/supertuxkart@1118 178a84e3-b1eb-0310-8ba1-8eac791a3b58
947 lines
29 KiB
Python
947 lines
29 KiB
Python
#!BPY
|
|
|
|
""" Registration info for Blender menus:
|
|
Name: 'Super Tux Kart Track'
|
|
Blender: 233
|
|
Group: 'Export'
|
|
Submenu: 'All meshes and empties ...' all
|
|
Submenu: 'Only selected ...' sel
|
|
Submenu: 'Configure' config
|
|
Tip: 'Create Super Tux Kart racing tracks.'
|
|
"""
|
|
|
|
# --------------------------------------------------------------------------
|
|
# This script turns the AC3D exporter script into a TuxKart track exporter.
|
|
# --------------------------------------------------------------------------
|
|
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
# Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
|
|
# Copyright (C) 2006: Eduardo Hernández, coz.eduardo.hernandez _at_ gmail.com
|
|
#
|
|
# 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.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
# --------------------------------------------------------------------------
|
|
|
|
VERSION = '0.2'
|
|
|
|
HELPTEXT = HELPTEXT_INTRO = [
|
|
'This script can export racing tracks with all needed data for TuxKart.',
|
|
'',
|
|
'TuxKart is an open source 3D kart racing game by Steve and Oliver Baker.',
|
|
'It is written in portable C++ using OpenGL and Steve\'s PLib 3D gaming library.',
|
|
'',
|
|
'Links:',
|
|
'TuxKart: tuxkart.sf.net PLib: plib.sf.net OpenGL: opengl.org',
|
|
'',
|
|
'The game\'s site contains a text describing how to add new tracks.',
|
|
'Please read it to understand better how to use this script.',
|
|
]
|
|
|
|
HELPTEXT_DRV = [
|
|
'To export the drive path file you need to create two mesh objects,',
|
|
'which are the left and right borders of the track. They must be only',
|
|
'linked 2D line segments (aka a curve). The left border mesh object must',
|
|
'be called "DRV_LEFT" and the right border mesh object is to be',
|
|
'called "DRV_RIGHT".',
|
|
'',
|
|
'Hints to create the drive line in the 3D View window (use TOP VIEW):',
|
|
'',
|
|
'- select the track mesh (the actual path);',
|
|
'- enter edit mode, and select all the vertex of one of the borders;',
|
|
'- use the P-KEY to separate that border from the main mesh;',
|
|
'- select the other border and separate it;',
|
|
'- change the name of the mesh objects to DRV_LEFT and DRV_RIGHT;',
|
|
'',
|
|
'To write the vertices in the correct order, the script starts with the',
|
|
'closest one to (0,0,0), looks for the edge that goes the most "up" ('
|
|
'increasing y coordinate) then follows the line segments till the last one.'
|
|
]
|
|
|
|
HELPTEXT_LOC = [
|
|
'To put items on the track add Empties (SPACEBAR->ADD->EMPTY)',
|
|
'where you want them to appear and give them a proper name.',
|
|
'Examples:',
|
|
'(x,y,z are coordinates and h,p,r are rotation angles)'
|
|
'',
|
|
'- sign.ac is exported as .loc line: "sign.ac",x,y,z,h,p,r',
|
|
'- sign.ac{Z}: "sign.ac",x,y,{},h,p,r',
|
|
'- sign.ac{ZPR}: "sign.ac",x,y,{},h,{},{}',
|
|
'- RHERRING exported as: RHERRING,x,y,z',
|
|
'',
|
|
'So (did you read the text about this at TuxKart\'s site?):',
|
|
'- to have z,p or r calculated by the game, append them inside {};',
|
|
'- names without an extension (.something) are considered hardcoded',
|
|
' game objects, like GHERRING (default if name is left as Empty);',
|
|
'- item examples: zipper.ac, RHERRING, GHERRING, YHERRING;',
|
|
'- don\'t worry about the .001 etc. extensions appended by Blender.',
|
|
'',
|
|
'You can assign a song file for your track in the Config screen.'
|
|
]
|
|
|
|
HELPTEXT_AC = [
|
|
'The script uses the AC3D exporter.',
|
|
'',
|
|
'AC3D is a shareware 3D modeler with a simple text format (.ac).',
|
|
'',
|
|
'There are a few options you can configure:',
|
|
'- skip data: set it if you don\'t want mesh names (ME:, not OB: field)',
|
|
' to be exported as strings for AC\'s "data" tags (19 chars max);',
|
|
'- rgb mirror color can be exported as ambient and/or emissive if needed,',
|
|
' since Blender handles these differently;',
|
|
'- default mat: a default (white) material is added if some mesh was',
|
|
' left without mats -- it\'s better to always add your own materials...',
|
|
'',
|
|
'To export mesh groups, parent each set to an Empty that has the suffix "GR_".'
|
|
]
|
|
|
|
import Blender
|
|
from Blender import sys as bsys
|
|
from Blender import Mathutils
|
|
from math import sqrt
|
|
|
|
# Globals / config options:
|
|
ARG = __script__['arg'] # user selected argument
|
|
HELPME = CONFIG = 0 # secondary screens
|
|
TK_ALL = 0
|
|
TK_DRV = 1
|
|
TK_LOC = 0
|
|
TK_TRK = 0
|
|
TK_PATH = bsys.dirname(Blender.Get('filename')) + bsys.sep
|
|
TK_TUNE = ''
|
|
AC_SKIP_DATA = 0
|
|
AC_MIRCOL_AS_AMB = 0
|
|
AC_MIRCOL_AS_EMIS = 0
|
|
AC_ADD_DEFAULT_MAT = 1
|
|
|
|
# Looking for a saved key in Blender.Registry dict:
|
|
rd = Blender.Registry.GetKey('TuxKart')
|
|
if rd:
|
|
TK_ALL = rd['TK_ALL']
|
|
TK_DRV = rd['TK_DRV']
|
|
TK_LOC = rd['TK_LOC']
|
|
TK_TRK = rd['TK_TRK']
|
|
TK_PATH = rd['TK_PATH']
|
|
TK_TUNE = rd['TK_TUNE']
|
|
AC_SKIP_DATA = rd['AC_SKIP_DATA']
|
|
AC_MIRCOL_AS_AMB = rd['AC_MIRCOL_AS_AMB']
|
|
AC_MIRCOL_AS_EMIS = rd['AC_MIRCOL_AS_EMIS']
|
|
AC_ADD_DEFAULT_MAT = rd['AC_ADD_DEFAULT_MAT']
|
|
|
|
if "DRV_LEFT" and "DRV_RIGHT" in Blender.Object.Get():
|
|
TK_DRV = 1 # found both DRV meshes
|
|
|
|
def update_RegistryInfo():
|
|
d = {}
|
|
d['TK_ALL'] = TK_ALL
|
|
d['TK_DRV'] = TK_DRV
|
|
d['TK_LOC'] = TK_LOC
|
|
d['TK_TRK'] = TK_TRK
|
|
d['TK_PATH'] = TK_PATH
|
|
d['TK_TUNE'] = TK_TUNE
|
|
d['AC_SKIP_DATA'] = AC_SKIP_DATA
|
|
d['AC_MIRCOL_AS_AMB'] = AC_MIRCOL_AS_AMB
|
|
d['AC_MIRCOL_AS_EMIS'] = AC_MIRCOL_AS_EMIS
|
|
d['AC_ADD_DEFAULT_MAT'] = AC_ADD_DEFAULT_MAT
|
|
Blender.Registry.SetKey('TuxKart', d)
|
|
|
|
# The default material to be used when necessary (see AC_ADD_DEFAULT_MAT)
|
|
DEFAULT_MAT = \
|
|
'MATERIAL "DefaultWhite" rgb 1 1 1 amb 1 1 1 emis 0 0 0 \
|
|
spec 0.5 0.5 0.5 shi 64 trans 0'
|
|
|
|
# This transformation aligns Blender and AC3D coordinate systems:
|
|
acmatrix = Mathutils.Matrix([1,0,0,0], [0,0,-1,0], [0,1,0,0], [0,0,0,1])
|
|
|
|
def Round(f):
|
|
r = round(f,6) # precision set to 10e-06
|
|
if r == int(r):
|
|
return str(int(r))
|
|
else:
|
|
return str(r)
|
|
|
|
def transform_verts(verts, m):
|
|
vecs = []
|
|
for v in verts:
|
|
vec = Mathutils.Vector([v[0],v[1],v[2], 1])
|
|
vecs.append(vec * m)
|
|
return vecs
|
|
|
|
# ---
|
|
|
|
def WriteLocation(objs, filename):
|
|
"Write the .loc file"
|
|
filename = Blender.sys.splitext(filename)[0]
|
|
trackname = Blender.sys.basename(filename)+".ac"
|
|
filename += '.loc'
|
|
locfile = file(filename, "w")
|
|
|
|
print "Writing %s file..." % filename
|
|
|
|
locfile.write("# Created by tuxkart.py v%s Blender Python script.\n#\n" \
|
|
% VERSION)
|
|
locfile.write('"%s",0,0,0,0,0,0\n' % trackname)
|
|
|
|
rad2deg = 180/3.1415926535 #from radians to degrees
|
|
for item in objs:
|
|
x,y,z = map(str, map(Round, item.loc))
|
|
rx,ry,rz = map(lambda x: rad2deg*x, item.rot)
|
|
h,p,r = map(str, map(Round, [rz,rx,ry]))
|
|
|
|
name = item.name.split(".")
|
|
hardcoded = 1 # name has extension (like .ac) ?
|
|
if len(name) == 1:
|
|
name = name[0]
|
|
else:
|
|
if name[-1].isdigit():
|
|
name = name[:-1]
|
|
if name == 'Empty': name = 'GHERRING'
|
|
if len(name) == 1:
|
|
name = name[0]
|
|
else:
|
|
name = ".".join(name)
|
|
hardcoded = 0 # has extension, isn't harcoded
|
|
if name.find("{") > 0:
|
|
name, encoded = name.split("{", 1)
|
|
encoded = encoded.split("}")[0].lower()
|
|
if encoded.find("z") >= 0: z = '{}'
|
|
if encoded.find("p") >= 0: p = '{}'
|
|
if encoded.find("r") >= 0: r = '{}'
|
|
|
|
locline = '%s,%s,%s' % (x,y,z)
|
|
if not hardcoded:
|
|
name = '"'+name+'"'
|
|
locline += ',%s,%s,%s' % (h,p,r)
|
|
locfile.write('%s,%s\n' % (name, locline))
|
|
|
|
if TK_TUNE:
|
|
locfile.write('MUSIC "%s"\n' % TK_TUNE)
|
|
locfile.close()
|
|
|
|
|
|
def WriteLeftDriveLine(obj, filename):
|
|
"Write the .drvl file"
|
|
mesh = obj.getData()
|
|
mesh.transform(obj.getMatrix())
|
|
for f in mesh.faces:
|
|
if len(f.v) != 2:
|
|
print "Can't export Drive Line .drvl file: invalid mesh."
|
|
print "The mesh must be made of linked line segments only."
|
|
return
|
|
|
|
filename = Blender.sys.splitext(filename)[0]
|
|
filename += '.drvl'
|
|
drvfile = file(filename, "w")
|
|
print "Writing drive line file: %s" % filename
|
|
verts = mesh.verts
|
|
|
|
shortest_distance = 100000
|
|
for v in verts:
|
|
#find vertex closest to 0,0
|
|
distance = sqrt(v[0]**2 + v[1]**2)
|
|
if distance < shortest_distance:
|
|
shortest_distance = distance
|
|
first_vertex = v
|
|
|
|
#Find the two vertex that are linked to the first vertex
|
|
e_v = []
|
|
edges = mesh.edges
|
|
|
|
for e in edges:
|
|
if first_vertex == e.v1:
|
|
e_v.append(e.v2)
|
|
elif first_vertex == e.v2:
|
|
e_v.append(e.v1)
|
|
|
|
second_vertex = first_vertex
|
|
#Find the vertex which has the higher Y value
|
|
for v in e_v:
|
|
if v[1] > second_vertex[1]:
|
|
second_vertex = v
|
|
|
|
if second_vertex == first_vertex:
|
|
print "Can't export Drive Line .drvl file: invalid mesh."
|
|
print "The mesh only has one point."
|
|
return
|
|
|
|
sorted_vertex = [first_vertex, second_vertex]
|
|
|
|
prev_vertex = first_vertex
|
|
curr_vertex = second_vertex
|
|
|
|
count = 2
|
|
#Find the edge that contains the second vertex but does not has first vertex
|
|
while count != len(verts):
|
|
for e in edges:
|
|
if prev_vertex != e.v1 and prev_vertex != e.v2:
|
|
#Find which vertex is the next_vertex
|
|
if curr_vertex == e.v1:
|
|
prev_vertex = curr_vertex
|
|
curr_vertex = e.v2
|
|
sorted_vertex.append(curr_vertex)
|
|
count = count + 1
|
|
break
|
|
elif curr_vertex == e.v2:
|
|
prev_vertex = curr_vertex
|
|
curr_vertex = e.v1
|
|
sorted_vertex.append(curr_vertex)
|
|
count = count + 1
|
|
break
|
|
|
|
for v in sorted_vertex:
|
|
drvfile.write('%f,%f,%f\n' % (v[0], v[1], v[2]))
|
|
|
|
drvfile.close()
|
|
|
|
def WriteRightDriveLine(obj, filename):
|
|
"Write the .drvl file"
|
|
mesh = obj.getData()
|
|
mesh.transform(obj.getMatrix())
|
|
for f in mesh.faces:
|
|
if len(f.v) != 2:
|
|
print "Can't export Drive Line .drvr file: invalid mesh."
|
|
print "The mesh must be made of linked line segments only."
|
|
return
|
|
|
|
filename = Blender.sys.splitext(filename)[0]
|
|
filename += '.drvr'
|
|
drvfile = file(filename, "w")
|
|
print "Writing drive line file: %s" % filename
|
|
verts = mesh.verts
|
|
|
|
shortest_distance = 100000
|
|
for v in verts:
|
|
#find vertex closest to 0,0
|
|
distance = sqrt(v[0]**2 + v[1]**2)
|
|
if distance < shortest_distance:
|
|
shortest_distance = distance
|
|
first_vertex = v
|
|
|
|
#Find the two vertex that are linked to the first vertex
|
|
e_v = []
|
|
edges = mesh.edges
|
|
|
|
for e in edges:
|
|
if first_vertex == e.v1:
|
|
e_v.append(e.v2)
|
|
elif first_vertex == e.v2:
|
|
e_v.append(e.v1)
|
|
|
|
second_vertex = first_vertex
|
|
#Find the vertex which has the higher Y value
|
|
for v in e_v:
|
|
if v[1] > second_vertex[1]:
|
|
second_vertex = v
|
|
|
|
if second_vertex == first_vertex:
|
|
print "Can't export Drive Line .drvr file: invalid mesh."
|
|
print "The mesh only has one point."
|
|
return
|
|
|
|
sorted_vertex = [first_vertex, second_vertex]
|
|
|
|
prev_vertex = first_vertex
|
|
curr_vertex = second_vertex
|
|
|
|
count = 2
|
|
#Find the edge that contains the second vertex but does not has first vertex
|
|
while count != len(verts):
|
|
for e in edges:
|
|
if prev_vertex != e.v1 and prev_vertex != e.v2:
|
|
#Find which vertex is the next_vertex
|
|
if curr_vertex == e.v1:
|
|
prev_vertex = curr_vertex
|
|
curr_vertex = e.v2
|
|
sorted_vertex.append(curr_vertex)
|
|
count = count + 1
|
|
break
|
|
elif curr_vertex == e.v2:
|
|
prev_vertex = curr_vertex
|
|
curr_vertex = e.v1
|
|
sorted_vertex.append(curr_vertex)
|
|
count = count + 1
|
|
break
|
|
|
|
for v in sorted_vertex:
|
|
drvfile.write('%f,%f,%f\n' % (v[0], v[1], v[2]))
|
|
|
|
drvfile.close()
|
|
|
|
|
|
class Track: # the ac3d exporter part
|
|
|
|
def __init__(self, bl_objlist, filename):
|
|
|
|
from Blender.sys import time
|
|
TIME = time()
|
|
global ARG, AC_SKIP_DATA, AC_ADD_DEFAULT_MAT, DEFAULT_MAT
|
|
|
|
print 'Exporting track model(s) to AC3D format...'
|
|
|
|
header = 'AC3Db'
|
|
self.buf = ''
|
|
self.mbuf = ''
|
|
world_kids = 0
|
|
self.mlist = []
|
|
kids_dict = {}
|
|
objlist = []
|
|
bl_objlist2 = []
|
|
|
|
if filename.find('.ac', -3) <= 0: filename += '.ac'
|
|
|
|
try:
|
|
file = open(filename, 'w')
|
|
except IOError, (errno, strerror):
|
|
errmsg = "IOError #%s" % errno
|
|
errmsg = errmsg + "%t|" + strerror
|
|
Blender.Draw.PupMenu(errmsg)
|
|
return None
|
|
|
|
file.write(header+'\n')
|
|
|
|
for obj in bl_objlist:
|
|
if obj.getType() != 'Mesh' and obj.getType() != 'Empty':
|
|
continue
|
|
else: kids_dict[obj.name] = 0
|
|
if obj.getParent() == None:
|
|
objlist.append(obj)
|
|
else:
|
|
bl_objlist2.append(obj)
|
|
|
|
bl_objlist = bl_objlist2[:]
|
|
world_kids = len(objlist)
|
|
|
|
while bl_objlist2:
|
|
for obj in bl_objlist:
|
|
obj2 = obj
|
|
dad = obj.getParent()
|
|
kids_dict[dad.name] += 1
|
|
while dad.name not in objlist:
|
|
obj2 = dad
|
|
dad = dad.getParent()
|
|
kids_dict[dad.name] += 1
|
|
objlist.insert(objlist.index(dad.name)+1, obj2.name)
|
|
bl_objlist2.remove(obj2)
|
|
|
|
meshlist = [o for o in objlist if o.getType() == 'Mesh']
|
|
|
|
self.MATERIALS(meshlist)
|
|
if not self.mbuf or AC_ADD_DEFAULT_MAT:
|
|
self.mbuf = DEFAULT_MAT + '\n' + self.mbuf
|
|
file.write(self.mbuf)
|
|
|
|
file.write('OBJECT world\nkids %s\n' % world_kids)
|
|
|
|
for obj in objlist:
|
|
self.obj = obj
|
|
|
|
if obj.getType == 'Empty':
|
|
file.write('OBJECT group\n')
|
|
emptyname = obj.name # skip the GR_ prefix
|
|
if len(emptyname) > 3: emptyname = emptyname[3:]
|
|
file.write(emptyname+'\n')
|
|
#self.rot(obj.rot)
|
|
#self.loc(obj.loc)
|
|
else:
|
|
mesh = self.mesh = obj.getData()
|
|
file.write('OBJECT poly\nname "%s"\n' % obj.name)
|
|
if not AC_SKIP_DATA:
|
|
file.write('data %s\n%s\n' % (len(mesh.name), mesh.name))
|
|
texline = self.texture(mesh.faces)
|
|
if texline: file.write(texline)
|
|
self.numvert(mesh.verts, obj.getMatrix(), file)
|
|
self.numsurf(mesh.faces, mesh.hasFaceUV(), file)
|
|
|
|
file.write('kids %s\n' % kids_dict[obj.name])
|
|
|
|
file.close()
|
|
print "Done. Saved to %s\n" % filename
|
|
|
|
def MATERIALS(self, meshlist):
|
|
for meobj in meshlist:
|
|
me = meobj.getData()
|
|
mat = me.materials
|
|
mbuf = []
|
|
mlist = self.mlist
|
|
for m in xrange(len(mat)):
|
|
name = mat[m].name
|
|
try:
|
|
mlist.index(name)
|
|
except ValueError:
|
|
mlist.append(name)
|
|
M = Blender.Material.Get(name)
|
|
material = 'MATERIAL "%s"' % name
|
|
mirCol = "%s %s %s" % (Round(M.mirCol[0]), Round(M.mirCol[1]),
|
|
Round(M.mirCol[2]))
|
|
rgb = "rgb %s %s %s" % (Round(M.R), Round(M.G), Round(M.B))
|
|
amb = "amb %s %s %s" % (Round(M.amb), Round(M.amb), Round(M.amb))
|
|
if AC_MIRCOL_AS_AMB:
|
|
amb = "amb %s" % mirCol
|
|
emis = "emis 0 0 0"
|
|
if AC_MIRCOL_AS_EMIS:
|
|
emis = "emis %s" % mirCol
|
|
spec = "spec %s %s %s" % (Round(M.specCol[0]),
|
|
Round(M.specCol[1]), Round(M.specCol[2]))
|
|
shi = "shi 72"
|
|
trans = "trans %s" % (Round(1 - M.alpha))
|
|
mbuf.append("%s %s %s %s %s %s %s\n" \
|
|
% (material, rgb, amb, emis, spec, shi, trans))
|
|
self.mlist = mlist
|
|
self.mbuf += "".join(mbuf)
|
|
|
|
def texture(self, faces):
|
|
tex = []
|
|
for f in faces:
|
|
if f.image and f.image.name not in tex:
|
|
tex.append(f.image.name)
|
|
if tex:
|
|
if len(tex) > 1:
|
|
print "\nAC3Db format supports only one texture per object."
|
|
print "Object %s -- using only the first one: %s\n" % \
|
|
(self.obj.name, tex[0])
|
|
image = Blender.Image.Get(tex[0])
|
|
buf = 'texture "%s"\n' % image.filename
|
|
xrep = image.xrep
|
|
yrep = image.yrep
|
|
buf += 'texrep %s %s\n' % (xrep, yrep)
|
|
return buf
|
|
|
|
def rot(self, matrix):
|
|
rot = ''
|
|
not_I = 0
|
|
for i in [0, 1, 2]:
|
|
r = map(Round, matrix[i])
|
|
not_I += (r[0] != '0.0')+(r[1] != '0.0')+(r[2] != '0.0')
|
|
not_I -= (r[i] == '1.0')
|
|
for j in [0, 1, 2]:
|
|
rot = "%s %s" % (rot, r[j])
|
|
if not_I:
|
|
rot = rot.strip()
|
|
buf = 'rot %s\n' % rot
|
|
self.buf = self.buf + buf
|
|
|
|
def loc(self, loc):
|
|
loc = map(Round, loc)
|
|
if loc[0] or loc[1] or loc[2]:
|
|
buf = 'loc %s %s %s\n' % (loc[0], loc[1], loc[2])
|
|
self.buf = self.buf + buf
|
|
|
|
def numvert(self, verts, matrix, file):
|
|
file.write("numvert %s\n" % len(verts))
|
|
m = matrix * acmatrix
|
|
verts = transform_verts(verts, m)
|
|
for v in verts:
|
|
v0, v1, v2 = Round(v[0]), Round(v[1]), Round(v[2])
|
|
file.write("%s %s %s\n" % (v0, v1, v2))
|
|
|
|
def numsurf(self, faces, hasFaceUV, file):
|
|
|
|
global AC_ADD_DEFAULT_MAT
|
|
|
|
file.write("numsurf %s\n" % len(faces))
|
|
|
|
mlist = self.mlist
|
|
indexerror = 0
|
|
omlist = {}
|
|
objmats = self.mesh.materials
|
|
for i in range(len(objmats)):
|
|
objmats[i] = objmats[i].name
|
|
for f in faces:
|
|
m_idx = f.materialIndex
|
|
try:
|
|
m_idx = mlist.index(objmats[m_idx])
|
|
except IndexError:
|
|
if not indexerror:
|
|
print "\nNotice: object " + self.obj.name + \
|
|
" has at least one material *index* assigned\n" \
|
|
"\tbut not defined (not linked to an existing material).\n" \
|
|
"\tThis can make some faces be exported with a wrong color.\n" \
|
|
"\tYou can fix the problem in the Edit Buttons Window (F9).\n"
|
|
indexerror = 1
|
|
m_idx = 0
|
|
refs = len(f)
|
|
flaglow = (refs == 2) << 1
|
|
two_side = f.mode & Blender.NMesh.FaceModes['TWOSIDE']
|
|
two_side = (two_side > 0) << 1
|
|
flaghigh = f.smooth | two_side
|
|
surfstr = "SURF 0x%d%d\n" % (flaghigh, flaglow)
|
|
if AC_ADD_DEFAULT_MAT and objmats: m_idx += 1
|
|
matstr = "mat %s\n" % m_idx
|
|
refstr = "refs %s\n" % refs
|
|
u, v, vi = 0, 0, 0
|
|
fvstr = ''
|
|
for vert in f.v:
|
|
fvstr = fvstr + str(self.mesh.verts.index(vert))
|
|
if hasFaceUV:
|
|
u = f.uv[vi][0]
|
|
v = f.uv[vi][1]
|
|
vi += 1
|
|
fvstr = fvstr + " %s %s\n" % (u, v)
|
|
file.write(surfstr + matstr + refstr + fvstr)
|
|
|
|
# End of Class Track
|
|
|
|
from Blender import Draw, BGL
|
|
from Blender.Window import FileSelector
|
|
|
|
# Special Buttons
|
|
BUTPATH = Draw.Create(TK_PATH)
|
|
BUTTUNE = Draw.Create(TK_TUNE)
|
|
BUTTRK = Draw.Create(TK_TRK)
|
|
BUTDRV = Draw.Create(TK_DRV)
|
|
BUTALL = Draw.Create(TK_ALL)
|
|
BUTLOC = Draw.Create(TK_LOC)
|
|
HTRK = Draw.Create(0)
|
|
HDRV = Draw.Create(0)
|
|
HLOC = Draw.Create(0)
|
|
HINTRO = Draw.Create(0)
|
|
|
|
HTOG = HTOG_INTRO = 0
|
|
HTOG_TRK = 1
|
|
HTOG_LOC = 2
|
|
HTOG_DRV = 3
|
|
|
|
# Button Events
|
|
EVT_CONFIG = 1
|
|
EVT_AC_DMAT = 2
|
|
EVT_AC_SKIP = 3
|
|
EVT_AC_MIR = 4
|
|
EVT_AC_EMIS = 5
|
|
EVT_AC_ALL = 6
|
|
EVT_AC_SEL = 7
|
|
EVT_HELP = 10
|
|
EVT_HELP_DRV = 11
|
|
EVT_HELP_LOC = 12
|
|
EVT_HELP_TRK = 13
|
|
EVT_HELP_INTRO = 14
|
|
EVT_ALL = 20
|
|
EVT_TRK = 21
|
|
EVT_DRV = 22
|
|
EVT_LOC = 23
|
|
EVT_PATH = 24
|
|
EVT_BROWSE = 25
|
|
EVT_TUNE = 26
|
|
EVT_TUNEBROWSE = 27
|
|
EVT_EXPORT = 30
|
|
EVT_EXIT = 31
|
|
|
|
def gui(): # gui drawing callback
|
|
global AC_SKIP_DATA, AC_MIRCOL_AS_AMB, AC_MIRCOL_AS_EMIS, AC_ADD_DEFAULT_MAT
|
|
global HELPME, CONFIG, HELPTEXT, HTRK, HDRV, HLOC, HINTRO
|
|
global HTOG, HTOG_INTRO, HTOG_TRK, HTOG_LOC, HTOG_DRV
|
|
global BUTPATH, BUTTRK, BUTDRV, BUTALL, BUTLOC, BUTTUNE
|
|
|
|
if HELPME: # help screen
|
|
BGL.glClearColor(0.4,0.5,0.8,1)
|
|
BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
|
|
BGL.glColor3f(1,1,1)
|
|
|
|
x = 55
|
|
y = 50+20*len(HELPTEXT)
|
|
for line in HELPTEXT:
|
|
y -= 20
|
|
BGL.glRasterPos2i(x, y)
|
|
Draw.Text(line)
|
|
w = 35
|
|
h = 20
|
|
y = 10
|
|
HINTRO = Draw.Toggle("intro", EVT_HELP_INTRO, x, y, w, h,(HTOG==HTOG_INTRO),
|
|
"Intro Help screen.")
|
|
HLOC = Draw.Toggle("loc", EVT_HELP_LOC, x+w, y, w, h, (HTOG==HTOG_LOC),
|
|
"Help for location (.loc) file.")
|
|
HTRK = Draw.Toggle("track", EVT_HELP_TRK, x+2*w, y, w, h, (HTOG==HTOG_TRK),
|
|
"Help for the mesh exporter.")
|
|
HDRV = Draw.Toggle("drv", EVT_HELP_DRV, x+3*w, y, w, h, (HTOG==HTOG_DRV),
|
|
"Help for drive line (.drv) file.")
|
|
Draw.Button("back", EVT_HELP, x+4*w, y, w, h, "Return to main screen.")
|
|
|
|
elif CONFIG: # config screen
|
|
BGL.glClearColor(0.4,0.5,0.8,1)
|
|
BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
|
|
BGL.glColor3f(0.9,0.85,0.75)
|
|
BUTTUNE = Draw.String('song: ', EVT_TUNE, 10, 10, 210, 20, \
|
|
BUTTUNE.val, 150, 'Song filename for your race track.')
|
|
Draw.PushButton('browse...', EVT_TUNEBROWSE, 222, 10, 53, 20,
|
|
'Open file selector (select any file in the chosen dir)')
|
|
x = 15
|
|
y = 40
|
|
w = 80
|
|
h = 20
|
|
BGL.glRecti(x-4,y-5,x+205,y+84)
|
|
BGL.glColor3f(1,1,1)
|
|
Draw.Button("BACK", EVT_CONFIG, x+w+127, y-5, 53, 30,
|
|
"Click to return to main screen.")
|
|
Draw.Button("Export Selected...", EVT_AC_SEL, x+w+10, y+4, 110, 30,
|
|
"Export now selected meshes to an AC3D file.")
|
|
Draw.Button("Export All...", EVT_AC_ALL, x+w+10, y+44, 110, 30,
|
|
"Export now all meshes to an AC3D file.")
|
|
Draw.Toggle("Mir2Emis", EVT_AC_EMIS, x, y, w, h, AC_MIRCOL_AS_EMIS,
|
|
"Get AC3D's emissive RGB color for each object from its mirror color "
|
|
"in Blender.")
|
|
Draw.Toggle("Mir2Amb", EVT_AC_MIR, x, y+h, w, h, AC_MIRCOL_AS_AMB,
|
|
"Get AC3D's ambient RGB color for each object from its mirror color "
|
|
"in Blender.")
|
|
Draw.Toggle("Skip data", EVT_AC_SKIP, x, y+2*h, w, h, AC_SKIP_DATA,
|
|
"Don't export mesh names as 'data' info.")
|
|
Draw.Toggle("Default mat", EVT_AC_DMAT, x, y+3*h, w, h, AC_ADD_DEFAULT_MAT,
|
|
"Objects without materials assigned get a default (white) one "
|
|
"automatically.")
|
|
BGL.glRasterPos2i(x-4, y+5*h)
|
|
Draw.Text("Additional Configuration")
|
|
|
|
else: # main screen
|
|
|
|
# let's count our objects first:
|
|
meshnr = meshsel = emptynr = emptysel = 0
|
|
ob = Blender.Object.Get()
|
|
for o in ob:
|
|
if o.getType() == "Mesh":
|
|
meshnr += 1
|
|
if o.sel: meshsel += 1
|
|
elif o.getType() == "Empty":
|
|
emptynr += 1
|
|
if o.sel: emptysel += 1
|
|
obinfo = "%d/%d meshes" % (meshsel, meshnr)
|
|
if emptynr:
|
|
obinfo += ", %d/%d empties" % (emptysel, emptynr)
|
|
if len(obinfo) < 27: obinfo = 'Selected: ' + obinfo
|
|
|
|
BGL.glClearColor(0.4,0.5,0.8,1)
|
|
BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
|
|
BGL.glColor3f(1,1,1)
|
|
x = y = 20
|
|
w = 60
|
|
h = 25
|
|
BGL.glRasterPos2i(x, y + 9)
|
|
Draw.Text(obinfo)
|
|
Draw.PushButton('EXPORT', EVT_EXPORT, x+202, y, w, h,
|
|
'Write track file(s).')
|
|
y += 30
|
|
w = 40
|
|
h = 20
|
|
pathmsg = 'Base dir where files will be saved.'
|
|
if BUTPATH.val[-1] != bsys.sep: pathmsg += ' And data filename head.'
|
|
BUTPATH = Draw.String('path: ', EVT_PATH, x, y, 5*w, h, BUTPATH.val, 100,
|
|
pathmsg)
|
|
Draw.PushButton('browse...', EVT_BROWSE, x+202, y, 60, 20,
|
|
'Open file selector (select any file in the chosen dir)')
|
|
y += 24
|
|
w = 26
|
|
h = 15
|
|
BUTALL = Draw.Toggle('all', EVT_ALL, x, y, w, h, BUTALL.val,
|
|
"Export all meshes and empties, not only selected ones.")
|
|
x += 7
|
|
BUTTRK = Draw.Toggle('trk', EVT_TRK, x+w, y, w, h, BUTTRK.val,
|
|
"Write track (.ac) file.")
|
|
BUTLOC = Draw.Toggle('loc', EVT_LOC, x+2*w, y, w, h, BUTLOC.val,
|
|
"Write location (.loc) file.")
|
|
BUTDRV = Draw.Toggle('drv', EVT_DRV, x+3*w, y, w, h, BUTDRV.val,
|
|
"Write drive line (.drv) file.")
|
|
x += 7 + 4*w
|
|
w = 41
|
|
Draw.PushButton('config', EVT_CONFIG, x, y, w, h,
|
|
'Additional configurations (see help).')
|
|
Draw.PushButton('help', EVT_HELP, x+w, y, w, h,
|
|
'About, links and instructions.')
|
|
Draw.PushButton('exit', EVT_EXIT, x+2*w+2, y, 60, h,
|
|
'Exit from script (shortcuts: Esc, q).')
|
|
x = 20
|
|
y += 46
|
|
BGL.glRasterPos2i(x, y)
|
|
title = "TuxKart Track Exporter v%s" % VERSION
|
|
tlen = Draw.Text(title)
|
|
BGL.glBegin(BGL.GL_LINE_LOOP)
|
|
BGL.glVertex2i(x+2+tlen, y+18)
|
|
BGL.glVertex2i(x+2+tlen, y-10)
|
|
BGL.glVertex2i(x-4, y-10)
|
|
BGL.glVertex2i(x-4, y+18)
|
|
BGL.glEnd()
|
|
|
|
def event(evt, val): # input event callback
|
|
global HELPME, CONFIG
|
|
|
|
if not val: return
|
|
|
|
if HELPME or CONFIG:
|
|
if evt == Draw.ESCKEY or evt == Draw.QKEY:
|
|
HELPME = CONFIG = 0
|
|
Draw.Redraw()
|
|
return
|
|
|
|
if evt == Draw.ESCKEY or evt == Draw.QKEY:
|
|
update_RegistryInfo()
|
|
Draw.Exit()
|
|
return
|
|
|
|
def b_event(evt): # gui button events callback
|
|
global AC_SKIP_DATA, AC_MIRCOL_AS_AMB, AC_MIRCOL_AS_EMIS, AC_ADD_DEFAULT_MAT
|
|
global CONFIG, HELPTEXT, TK_ALL, TK_DRV, TK_LOC, TK_TRK, TK_PATH, TK_TUNE
|
|
global HELPME, ARG, BUTPATH, BUTTRK, BUTDRV, BUTALL, BUTLOC, BUTTUNE
|
|
global HTOG, HTOG_TRK, HTOG_DRV, HTOG_LOC, HTOG_INTRO
|
|
|
|
if evt == EVT_EXPORT:
|
|
fname = TK_PATH
|
|
if bsys.exists(TK_PATH) == 2:
|
|
fname += bsys.makename(Blender.Get('filename'), strip=1)
|
|
FileSelector(fs_callback, "Export TuxKart track", fname)
|
|
else: TK_Export(fname, 0)
|
|
elif evt == EVT_ALL:
|
|
TK_ALL = BUTALL.val
|
|
if TK_ALL: ARG = 'all'
|
|
else: ARG = 'sel'
|
|
elif evt == EVT_DRV:
|
|
TK_DRV = BUTDRV.val
|
|
elif evt == EVT_LOC:
|
|
TK_LOC = BUTLOC.val
|
|
elif evt == EVT_TRK:
|
|
TK_TRK = BUTTRK.val
|
|
elif evt == EVT_PATH:
|
|
TK_PATH = BUTPATH.val
|
|
pathtype = bsys.exists(TK_PATH)
|
|
if pathtype:
|
|
if pathtype == 2 and TK_PATH[-1] != bsys.sep:
|
|
TK_PATH += bsys.sep
|
|
else: TKPATH = bsys.splitext(TK_PATH)[0]
|
|
BUTPATH.val = TK_PATH
|
|
elif evt == EVT_BROWSE:
|
|
FileSelector(fs_cbPath, "Ok", TK_PATH)
|
|
elif evt == EVT_TUNE:
|
|
TK_TUNE = BUTTUNE.val
|
|
if bsys.exists(TK_TUNE) != 1: # 1 is file
|
|
Draw.PupMenu("ERROR%t|No such file.")
|
|
TK_TUNE = ''
|
|
BUTTUNE.val = TK_TUNE
|
|
elif evt == EVT_TUNEBROWSE:
|
|
FileSelector(fs_cbTune, "Select Song", TK_TUNE)
|
|
elif evt == EVT_AC_DMAT:
|
|
AC_ADD_DEFAULT_MAT = 1 - AC_ADD_DEFAULT_MAT
|
|
elif evt == EVT_AC_SKIP:
|
|
AC_SKIP_DATA = 1 - AC_SKIP_DATA
|
|
elif evt == EVT_AC_MIR:
|
|
AC_MIRCOL_AS_AMB = 1 - AC_MIRCOL_AS_AMB
|
|
elif evt == EVT_AC_EMIS:
|
|
AC_MIRCOL_AS_EMIS = 1 - AC_MIRCOL_AS_EMIS
|
|
elif evt == EVT_AC_ALL:
|
|
ARG = 'AC_all'
|
|
fname = bsys.makename(ext=".ac")
|
|
FileSelector(fs_callback, "Export AC3D", fname)
|
|
return
|
|
elif evt == EVT_AC_SEL:
|
|
ARG = 'AC_sel'
|
|
fname = bsys.makename(ext=".ac")
|
|
FileSelector(fs_callback, "Export AC3D", fname)
|
|
return
|
|
elif evt == EVT_CONFIG:
|
|
CONFIG = 1 - CONFIG
|
|
elif evt == EVT_HELP:
|
|
HELPME = 1 - HELPME
|
|
elif evt == EVT_HELP_DRV:
|
|
HELPTEXT = HELPTEXT_DRV
|
|
HTOG = HTOG_DRV
|
|
elif evt == EVT_HELP_LOC:
|
|
HELPTEXT = HELPTEXT_LOC
|
|
HTOG = HTOG_LOC
|
|
elif evt == EVT_HELP_TRK:
|
|
HELPTEXT = HELPTEXT_AC
|
|
HTOG = HTOG_TRK
|
|
elif evt == EVT_HELP_INTRO:
|
|
HELPTEXT = HELPTEXT_INTRO
|
|
HTOG = HTOG_INTRO
|
|
elif evt == EVT_EXIT:
|
|
update_RegistryInfo()
|
|
Draw.Exit()
|
|
return
|
|
else: return
|
|
Draw.Redraw()
|
|
|
|
def TK_Export(filename, onlytrack):
|
|
global ARG
|
|
|
|
starttime = bsys.time()
|
|
|
|
# Here we check if given dir is TuxKart's one.
|
|
# If so we save .ac in models/ and .loc and .drv in data/
|
|
# else we save all in the given dir.
|
|
datapath = modelspath = filename
|
|
basename = bsys.basename(filename)
|
|
dirname = bsys.dirname(filename)
|
|
modelsdir = bsys.join(dirname, 'models')
|
|
datadir = bsys.join(dirname, 'data')
|
|
if bsys.exists (modelsdir) == 2:
|
|
if bsys.exists(datadir) == 2:
|
|
datapath = bsys.join(datadir, basename)
|
|
modelspath = bsys.join(modelsdir, basename)
|
|
|
|
scene = Blender.Scene.GetCurrent()
|
|
objs = []
|
|
|
|
# just the .ac export from the config screen:
|
|
if onlytrack:
|
|
if ARG == ('AC_sel'): objs = Blender.Object.GetSelected()
|
|
else: objs = scene.getChildren()
|
|
test = Track(objs, filename)
|
|
endtime = bsys.time() - starttime
|
|
Blender.Draw.PupMenu("OK|Data exported in %.2f seconds." % endtime)
|
|
return
|
|
|
|
if ARG == 'sel': objs = Blender.Object.GetSelected()
|
|
else: objs = scene.getChildren()
|
|
|
|
track = []
|
|
thingies = []
|
|
list = [o for o in objs if o.getType() == "Mesh" or o.getType() == "Empty"]
|
|
for o in list:
|
|
if o.getType() == "Empty": # empties whose names start with GR_
|
|
if o.name.find("GR_") < 0: # are exported as AC groups info
|
|
thingies.append(o)
|
|
elif o.name == "DRV_LEFT":
|
|
WriteLeftDriveLine(o, datapath)
|
|
elif o.name == "DRV_RIGHT":
|
|
WriteRightDriveLine(o, datapath)
|
|
else: track.append(o)
|
|
WriteLocation(thingies, datapath)
|
|
test = Track(track, modelspath)
|
|
endtime = bsys.time() - starttime
|
|
Blender.Draw.PupMenu("OK|Data exported in %.2f seconds." % endtime)
|
|
|
|
# File Selector callbacks:
|
|
def fs_cbPath(path):
|
|
global TK_PATH, BUTPATH
|
|
TK_PATH = bsys.splitext(path)[0]
|
|
BUTPATH.val = TK_PATH
|
|
|
|
def fs_cbTune(filename):
|
|
global TK_TUNE, BUTTUNE
|
|
TK_TUNE = filename
|
|
BUTTUNE.val = TK_TUNE
|
|
|
|
def fs_callback(filename):
|
|
global ARG
|
|
|
|
#if hasattr(ARG, find):
|
|
# if ARG.find('AC_') == 0: onlytrack = 1
|
|
# else: onlytrack = 0
|
|
onlytrack = 0
|
|
|
|
TK_Export(filename, onlytrack)
|
|
|
|
# -- End of definitions
|
|
|
|
if not ARG or ARG == 'config': # not ARG if executed as loaded Blender Text
|
|
Draw.Register(gui, event, b_event)
|
|
else:
|
|
fname = Blender.sys.makename()
|
|
FileSelector(fs_callback, "Export Tuxkart track", fname)
|