2014-08-16 00:50:36 -07:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
import zlib
|
|
|
|
import struct
|
|
|
|
import sys
|
|
|
|
import time
|
2014-08-16 09:34:24 -07:00
|
|
|
import getopt
|
2014-08-16 11:50:08 -07:00
|
|
|
import re
|
2014-08-16 00:50:36 -07:00
|
|
|
|
|
|
|
# mt2obj - MTS schematic to OBJ converter
|
|
|
|
# Copyright (C) 2014 sfan5
|
|
|
|
#
|
|
|
|
# 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.
|
2014-08-16 09:48:55 -07:00
|
|
|
#
|
2014-08-16 09:22:27 -07:00
|
|
|
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
2014-08-16 00:50:36 -07:00
|
|
|
|
2014-08-16 11:50:08 -07:00
|
|
|
def convert(desc, vals):
|
|
|
|
out = tuple()
|
|
|
|
i = 0
|
|
|
|
for val in vals:
|
|
|
|
if val is None:
|
|
|
|
i += 1
|
|
|
|
continue
|
|
|
|
c = desc[i]
|
2014-08-16 13:21:48 -07:00
|
|
|
if c == '0': # skip
|
|
|
|
pass
|
|
|
|
elif c == 'x': # copy
|
2014-08-16 11:50:08 -07:00
|
|
|
out += val,
|
|
|
|
elif c == 's': # string
|
|
|
|
out += str(val),
|
|
|
|
elif c == 'i': # int
|
|
|
|
out += int(val),
|
|
|
|
elif c == 'f': # float
|
|
|
|
out += float(val),
|
|
|
|
elif c == 'h': # hexadecimal int
|
|
|
|
out += int(val, 16),
|
|
|
|
elif c == 'b': # bool
|
|
|
|
out += val.strip().lower() in ['1', 'true', 'yes'],
|
|
|
|
i += 1
|
|
|
|
return out
|
|
|
|
|
2014-08-16 13:21:48 -07:00
|
|
|
def pp_new(var, strip_empty=False):
|
|
|
|
return ({'if_state': 0, '_strip_empty': strip_empty}, var)
|
|
|
|
|
|
|
|
def pp_process(ctx, line):
|
|
|
|
for k in ctx[1]:
|
|
|
|
line = line.replace('{' + k + '}', ctx[1][k])
|
|
|
|
if ctx[0]['_strip_empty'] and line.strip() == '':
|
|
|
|
return None
|
|
|
|
elif line.startswith('# '): # e.g.: # comment
|
|
|
|
return None
|
|
|
|
elif line.startswith('#define'): # e.g.: #define name value that may contain spaces
|
|
|
|
tmp = line.split(' ')
|
|
|
|
if len(tmp) < 3:
|
|
|
|
raise Exception('missing something...')
|
|
|
|
ctx[1][tmp[1]] = ' '.join(tmp[2:])[:-1] # [:-1] to cut off the \n at the end
|
|
|
|
return None
|
|
|
|
elif line.startswith('#if'): #e.g.: #if {name}
|
|
|
|
tmp = line.split(' ')
|
|
|
|
if len(tmp) < 2:
|
|
|
|
raise Exception('Missing something..')
|
|
|
|
tmp = tmp[1]
|
|
|
|
if tmp.strip().lower() in ['1', 'true', 'yes']:
|
|
|
|
ctx[0]['if_state'] = 1 # don't ignore current lines, expecting #else or #endif
|
|
|
|
else:
|
|
|
|
ctx[0]['if_state'] = 2 # ignore current lines, expecting #else or #endif
|
|
|
|
return None
|
|
|
|
elif line.startswith('#else') and ctx[0]['if_state'] == 1:
|
|
|
|
ctx[0]['if_state'] = 4 # ignore current lines, expecting #endif
|
|
|
|
elif line.startswith('#else') and ctx[0]['if_state'] == 2:
|
|
|
|
ctx[0]['if_state'] = 3 # don't ignore current lines, expecting #endif
|
|
|
|
elif line.startswith('#endif'):
|
|
|
|
if ctx[0]['if_state'] not in [3, 4]:
|
|
|
|
raise Exception('stray #endif')
|
|
|
|
ctx[0]['if_state'] = 0 # no #if in sight
|
|
|
|
else:
|
|
|
|
if ctx[0]['if_state'] in [2, 4]:
|
|
|
|
pass # ignore, see above
|
|
|
|
else:
|
|
|
|
return line
|
|
|
|
return None
|
|
|
|
|
|
|
|
def parse_arglist(s):
|
|
|
|
if s == ':':
|
|
|
|
return {}
|
|
|
|
state = 1
|
|
|
|
tmp1 = ''
|
|
|
|
tmp2 = ''
|
|
|
|
out = {}
|
|
|
|
for c in s:
|
|
|
|
if state == 1: # part before =
|
|
|
|
if c == ':':
|
|
|
|
if tmp1 == '':
|
|
|
|
raise Exception('name must be non-empty')
|
|
|
|
out[tmp1] = '1' # set to '1' if no value given, e.g. 'foo=1:bar:cats=yes' would set bar to '1'
|
|
|
|
elif c == '=':
|
|
|
|
state = 2
|
|
|
|
else:
|
|
|
|
tmp1 += c
|
|
|
|
elif state == 2: # part after =
|
|
|
|
if c == ':':
|
|
|
|
if tmp1 == '':
|
|
|
|
raise Exception('name must be non-empty')
|
|
|
|
out[tmp1] = tmp2
|
|
|
|
else:
|
|
|
|
tmp2 += c
|
|
|
|
if tmp1 != '' and state == 1:
|
|
|
|
out[tmp1] = '1'
|
|
|
|
elif tmp1 != '' and state == 2:
|
|
|
|
out[tmp1] = tmp2
|
|
|
|
return out
|
|
|
|
|
2014-08-16 11:50:08 -07:00
|
|
|
|
|
|
|
nodetbl = {}
|
|
|
|
|
2014-08-16 13:21:48 -07:00
|
|
|
r_entry = re.compile(r'^(\S+) (\S+) (\d+) (\d+) (\d+) (\S+)(?: (\d+))?$')
|
2014-08-16 11:50:08 -07:00
|
|
|
|
|
|
|
f = open("nodes.txt", "r")
|
|
|
|
for l in f:
|
|
|
|
m = r_entry.match(l)
|
2014-08-16 13:21:48 -07:00
|
|
|
nodetbl[m.group(1)] = convert('siiisi', m.groups()[1:])
|
2014-08-16 11:50:08 -07:00
|
|
|
f.close()
|
2014-08-16 00:50:36 -07:00
|
|
|
|
2014-08-16 13:21:48 -07:00
|
|
|
optargs, args = getopt.getopt(sys.argv[1:], 't')
|
2014-08-16 09:34:24 -07:00
|
|
|
|
2014-08-16 09:48:55 -07:00
|
|
|
if len(args) < 1:
|
2014-08-16 09:34:24 -07:00
|
|
|
print("Usage: %s <.mts schematic>" % sys.argv[0])
|
|
|
|
print("Converts .mts schematics to Wavefront .obj geometry files")
|
2014-08-16 00:50:36 -07:00
|
|
|
print("")
|
2014-08-16 11:50:08 -07:00
|
|
|
print("Output files are written into directory of source file.")
|
2014-08-16 13:21:48 -07:00
|
|
|
print('')
|
|
|
|
print('Options:')
|
|
|
|
print('\t-t\t\tEnable textures')
|
2014-08-16 00:50:36 -07:00
|
|
|
exit(1)
|
|
|
|
else:
|
2014-08-16 09:48:55 -07:00
|
|
|
sch = open(args[0], "rb")
|
2014-08-16 00:50:36 -07:00
|
|
|
if sch.read(4) != b"MTSM":
|
|
|
|
print("This file does not look like an MTS schematic..")
|
|
|
|
exit(1)
|
2014-08-16 09:34:24 -07:00
|
|
|
v = struct.unpack("!H", sch.read(2))[0]
|
|
|
|
if v != 3:
|
|
|
|
print("Wrong file version: got %d, expected %d" % (v, 3))
|
2014-08-16 00:50:36 -07:00
|
|
|
exit(1)
|
|
|
|
width, height, depth = struct.unpack("!HHH", sch.read(6))
|
|
|
|
sch.seek(height, 1)
|
|
|
|
nodecount = struct.unpack("!H", sch.read(2))[0]
|
|
|
|
nodemap = {}
|
|
|
|
for i in range(nodecount):
|
|
|
|
l = struct.unpack("!H", sch.read(2))[0]
|
|
|
|
name = sch.read(l).decode('ascii')
|
|
|
|
nodemap[i] = name
|
2014-08-16 13:21:48 -07:00
|
|
|
# TODO use zlib.decompressobj() instead of decompressing everything at once
|
2014-08-16 00:50:36 -07:00
|
|
|
cdata = sch.read()
|
|
|
|
sch.close()
|
|
|
|
data = zlib.decompress(cdata)
|
|
|
|
del cdata
|
2014-08-16 09:48:55 -07:00
|
|
|
filepart = args[0][:args[0].find(".")]
|
2014-08-16 00:50:36 -07:00
|
|
|
obj = open(filepart + ".obj", "w")
|
|
|
|
obj.write("# Exported by mt2obj\nmtllib %s\n\n\n" % (filepart + ".mtl", ))
|
|
|
|
i = 0
|
|
|
|
foundnodes = []
|
|
|
|
unknownnodes = []
|
|
|
|
for x in range(width):
|
|
|
|
for y in range(height):
|
|
|
|
for z in range(depth):
|
|
|
|
off = (x + y*width + z*width*height) * 2
|
|
|
|
nid = struct.unpack("!H", data[off:off + 2])[0]
|
|
|
|
nname = nodemap[nid]
|
|
|
|
if nname == "air":
|
|
|
|
continue
|
2014-08-16 11:50:08 -07:00
|
|
|
if not nname in nodetbl.keys():
|
2014-08-16 00:50:36 -07:00
|
|
|
if not nname in unknownnodes:
|
|
|
|
unknownnodes.append(nname)
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
if not nname in foundnodes:
|
|
|
|
foundnodes.append(nname)
|
|
|
|
obj.write("o node%d\n" % i)
|
|
|
|
obj.write("usemtl %s\n" % nname.replace(":", "__"))
|
2014-08-16 11:50:08 -07:00
|
|
|
objd = open("models/" + nodetbl[nname][0] + ".obj", 'r')
|
2014-08-16 13:21:48 -07:00
|
|
|
tmp = parse_arglist(nodetbl[nname][4])
|
2014-08-16 13:31:17 -07:00
|
|
|
tmp['TEXTURES'] = str(('-t', '') in optargs)
|
2014-08-16 13:21:48 -07:00
|
|
|
ppctx = pp_new(tmp, strip_empty=True)
|
2014-08-16 11:19:11 -07:00
|
|
|
for line in objd:
|
2014-08-16 13:21:48 -07:00
|
|
|
line = pp_process(ppctx, line)
|
|
|
|
if line is None:
|
|
|
|
continue
|
|
|
|
if line.startswith("v "):
|
2014-08-16 11:19:11 -07:00
|
|
|
tmp = line.split(" ")
|
|
|
|
vx, vy, vz = float(tmp[1]), float(tmp[2]), float(tmp[3])
|
|
|
|
vx += x
|
|
|
|
vy += y
|
|
|
|
vz += z
|
|
|
|
obj.write("v %f %f %f\n" % (vx, vy, vz))
|
|
|
|
else:
|
|
|
|
obj.write(line)
|
2014-08-16 13:21:48 -07:00
|
|
|
del ppctx
|
2014-08-16 11:19:11 -07:00
|
|
|
objd.close()
|
|
|
|
obj.write("\n")
|
2014-08-16 00:50:36 -07:00
|
|
|
i += 1
|
|
|
|
obj.close()
|
|
|
|
mtl = open(filepart + ".mtl", "w")
|
|
|
|
mtl.write("# Generated by mt2obj\n\n\n")
|
|
|
|
for node in foundnodes:
|
|
|
|
mtl.write("newmtl %s\n" % node.replace(":", "__"))
|
2014-08-16 11:50:08 -07:00
|
|
|
c = nodetbl[node]
|
|
|
|
mtld = open("models/" + nodetbl[node][0] + ".mtl", 'r')
|
2014-08-16 13:21:48 -07:00
|
|
|
if len(c) > 5: # if there is transparency
|
|
|
|
tmp1 = c[5]/255
|
|
|
|
else:
|
|
|
|
tmp1 = 1.0
|
|
|
|
tmp = {
|
|
|
|
'r': str(c[1]/255),
|
|
|
|
'g': str(c[2]/255),
|
|
|
|
'b': str(c[3]/255),
|
|
|
|
'a': str(tmp1),
|
2014-08-16 13:31:17 -07:00
|
|
|
'TEXTURES': str(('-t', '') in optargs),
|
2014-08-16 13:21:48 -07:00
|
|
|
}
|
|
|
|
tmp.update(parse_arglist(nodetbl[node][4]))
|
|
|
|
ppctx = pp_new(tmp, strip_empty=True)
|
2014-08-16 11:19:11 -07:00
|
|
|
for line in mtld:
|
2014-08-16 13:21:48 -07:00
|
|
|
line = pp_process(ppctx, line)
|
|
|
|
if line is None:
|
|
|
|
continue
|
|
|
|
mtl.write(line)
|
|
|
|
del ppctx
|
2014-08-16 00:50:36 -07:00
|
|
|
mtl.write("\n")
|
2014-08-16 11:19:11 -07:00
|
|
|
mtld.close()
|
2014-08-16 00:50:36 -07:00
|
|
|
mtl.close()
|
|
|
|
if len(unknownnodes) > 0:
|
|
|
|
print("There were some unknown nodes that were ignored during the conversion:")
|
|
|
|
for e in unknownnodes:
|
|
|
|
print(e)
|
|
|
|
|