#!BPY """ Name: 'Warzone model (.pie)...' Blender: 244 Group: 'Import' Tooltip: 'Load a Warzone model file' """ __author__ = "Rodolphe Suescun, Gerard Krol, Kevin Gillette" __url__ = ["blender"] __version__ = "1.2" __bpydoc__ = """\ This script imports PIE files to Blender. Usage: Run this script from "File->Import" menu and then load the desired PIE file. """ # # -------------------------------------------------------------------------- # PIE Import v0.1 by Rodolphe Suescun (AKA RodZilla) # v0.2 by Gerard Krol (gerard_) # v0.3 by Kevin Gillette (kage) # v1.0 -- # -------------------------------------------------------------------------- # ***** BEGIN GPL LICENSE BLOCK ***** # # 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 ***** # -------------------------------------------------------------------------- from Blender import * from pie_common import * import os, pie #==================================================================================# # Draws a thumbnail from a Blender image at x,y with sides that are edgelen long # #==================================================================================# gen = None ui_ref = None def fresh_generator(filename): return pie.data_mutator(pie.parse(filename)) def seek_to_directive(err_on_nonmatch, fatal_if_not_found, *directives): for i in gen: type = i[pie.TOKEN_TYPE] if type == "directive" and i[pie.DIRECTIVE_NAME] in directives: return i elif type == "error": ui.debug(i[pie.ERROR].args[0], "error", i[pie.LINENO]) elif err_on_nonmatch: ui.debug("expected %s directive." % directives[0], "warning", i[pie.LINENO]) if fatal_if_not_found: ui.debug(directives[0] + " directive not found. cannot continue.", "fatal-error") def generate_opt_gui(rect, optlist, tooltips, limits): """ expects rect to be [xmin, ymin, xmax, ymax] """ BGL.glRecti(*rect) buttonwidth = 120 buttonheight = 20 margin = 5 defbuttonwidth = 20 # default button defbuttonpos = rect[2] - defbuttonwidth - margin buttonpos = defbuttonpos - margin - buttonwidth labelwidth = buttonpos - rect[0] - margin numopts = len(optlist) for i, opt in enumerate(optlist): name = opt['name'] val = scalar_value(opt['val']) posY = rect[3] - (i + 1) * (buttonheight + margin) if posY < rect[1] + margin: break Draw.PushButton("D", i + numopts, defbuttonpos, posY, defbuttonwidth, buttonheight, "Set this option to its default") tooltip = tooltips.get(name, "") if isinstance(opt['opts'], basestring): title = opt.get('title', "") if opt['opts'] is 'number': opt['val'] = Draw.Number(title, i, buttonpos, posY, buttonwidth, buttonheight, val, limits[name][0], limits[name][1], tooltip) elif opt['opts'] is 'bool': opt['val'] = Draw.Toggle(title, i, buttonpos, posY, buttonwidth, buttonheight, val, tooltip) else: numopts = len(opt['opts']) if numopts < 2: ui.debug("error: invalid option supplied to generate_opt_gui") continue if numopts is 2: Draw.PushButton(opt['opts'][val], i, buttonpos, posY, buttonwidth, buttonheight, tooltip) else: menustr = opt.get('title', "") if menustr: menustr += "%t|" menustr += '|'.join("%s %%x%i" % (opt['opts'][j], j) for j in xrange(numopts)) opt['val'] = Draw.Menu(menustr, i, buttonpos, posY, buttonwidth, buttonheight, val, tooltip) Draw.Label(opt['label'], rect[0] + margin, posY, labelwidth, buttonheight) def opt_process(ui): ui.setData('defaults/script', {'auto-layer': True, 'import-scale': 1.0, 'scripts/layers': "pie_levels_to_layers.py", 'scripts/validate': "pie_validate.py"}) ui.setData('defaults/user', Registry.GetKey('warzone_pie', True) or dict()) ui.setData('limits', {'import-scale': (0.1, 1000.0)}) ui.setData('tooltips', {'auto-layer': "Selecting this would be the same as selecting all the newly created objects and running Object -> Scripts -> \"PIE levels -> layers\"", 'import-scale': "Values under 1.0 may be useful when importing from a model edited at abnormally large size in pie slicer. Values over 1.0 probably won't be useful.", 'scripts/layers': "Basename of the \"PIE levels -> layers\" script. Do not include the path. Whitespace is not trimmed", 'scripts/validate': "Basename of the \"PIE validate\" script. Do not include the path. Whitespace is not trimmed"}) i = seek_to_directive(True, False, "PIE") if i is None: ui.debug("not a valid PIE. PIE directive is required", "warning") gen = fresh_generator(ui.getData('filename')) ui.setData('pie-version', 2, True) else: ui.setData('pie-version', i[pie.DIRECTIVE_VALUE], True) ui.debug("version %i pie detected" % ui.getData('pie-version')) i = seek_to_directive(True, False, "TYPE") if i is None: ui.debug("not a valid PIE. TYPE directive is required", "warning") gen = fresh_generator(ui.getData('filename')) ui.setData('pie-type', 0x200, True) else: ui.setData('pie-type', i[pie.DIRECTIVE_VALUE], True) ui.debug("type %i pie detected" % ui.getData('pie-type')) optlist = list() dirs = Get('uscriptsdir'), Get('scriptsdir') script = default_value(ui, 'scripts/layers') for d in dirs: if d and script in os.listdir(d): optlist.append({'name': "auto-layer", 'label': "Assign levels to different layers?", 'opts': 'bool', 'title': "Automatically Layer"}) ui.setData('scripts/layers', os.path.join(d, script), True) break else: ui.debug("Could not find '%s'. automatic layering will not be available" % script) optlist.append({'name': "import-scale", 'label': "Scale all points by a factor.", 'opts': 'number', 'title': "Scale"}) for opt in optlist: opt.setdefault('val', default_value(ui, opt['name'])) ui.setData('optlist', optlist) def opt_draw(ui): optlist = ui.getData('optlist') numopts = len(optlist) Draw.PushButton("Cancel", numopts * 2 + 1, 68, 15, 140, 30, "Cancel the import operation") Draw.PushButton("Proceed", numopts * 2, 217, 15, 140, 30, "Confirm texpage selection and continue") Draw.PushButton("Save Defaults", numopts * 2 + 3, 68, 321, 140, 30, "Save options in their current state as the default") Draw.PushButton("Default All", numopts * 2 + 2, 217, 321, 140, 30, "Revert all options to their defaults") BGL.glClearColor(0.7, 0.7, 0.7, 1) BGL.glClear(BGL.GL_COLOR_BUFFER_BIT) BGL.glColor3f(0.8, 0.8, 0.8) BGL.glRecti(5, 5, 431, 411) BGL.glColor3f(0.7, 0.7, 0.7) BGL.glRecti(15, 361, 421, 401) generate_opt_gui([15, 55, 421, 311], optlist, ui.getData('tooltips'), ui.getData('limits')) BGL.glColor3i(0, 0, 0) text = ("General Options", "large") BGL.glRasterPos2i(int((406 - Draw.GetStringWidth(*text)) / 2 + 15), 377) Draw.Text(*text) def opt_evt(ui, val): opts = ui.getData('optlist') numopts = len(opts) if val >= numopts * 2: val -= numopts * 2 if val is 0: if not ui.getData('defaults/user'): save_defaults(ui) for opt in opts: ui.setData(opt['name'], scalar_value(opt['val']), True) return True elif val is 1: return False elif val is 2: if ui.getData('defaults/user'): def_src = Draw.PupMenu( "Source of default value %t|Script default|User default") else: def_src = 1 if def_src > 0: for opt in opts: name = opt['name'] if def_src is 1: opt['val'] = ui.getData('defaults/script')[name] elif def_src is 2: opt['val'] = default_value(ui, name) else: break Draw.Redraw() elif val is 3: save_defaults(ui) return if val < numopts: opt = opts[val] if not isinstance(opt, basestring): if len(opt['opts']) is 2: opt['val'] = abs(opt['val'] - 1) # toggle it between 0 and 1 elif val < numopts * 2: opt = ui.getData('optlist')[val - numopts] name = opt['name'] if name in ui.getData('defaults/user'): def_src = Draw.PupMenu( "Source of default value %t|Script default|User default") else: def_src = 1 if def_src is 1: opt['val'] = ui.getData('defaults/script')[name] elif def_src is 2: opt['val'] = ui.getData('defaults/user')[name] else: return Draw.Redraw() def thumbnailize(img, x, y, edgelen): try: # BGL.glClearColor(0.7, 0.7, 0.7, 1) # BGL.glClear(BGL.GL_COLOR_BUFFER_BIT) if img: BGL.glColor3f(0.0, 0.0, 0.0) else: BGL.glColor3f(1.0, 1.0, 1.0) BGL.glRecti(x, y, x + edgelen, y + edgelen) except NameError, AttributeError: ui.debug("unable to load BGL. will not affect anything, though") if not img: return width, height = img.getSize() edgelen = float(edgelen) if width > height: primary, secondary, reverse = width, height, False else: primary, secondary, reverse = height, width, True if primary < edgelen: zoom = 1.0 else: zoom = 1.0 / (primary / edgelen) offset = int((edgelen - zoom * secondary) / 2) if zoom == 1.0: offset = [int((edgelen - zoom * primary) / 2), offset] else: offset = [0, offset] if reverse: offset.reverse() Draw.Image(img, offset[0] + x, offset[1] + y, zoom, zoom) def new_texpage(filename): texpage_cache = ui_ref.getData('texpage-cache') options = ui_ref.getData('texpage-opts') if filename in texpage_cache: img = texpage_cache[filename] else: img = Image.Load(filename) texpage_cache[filename] = img if filename not in options: ui.getData('texpage-menu').val = len(options) options.append(filename) if 'texpage-dir' not in ui: ui.setData('texpage-dir', os.path.dirname(filename)) return img def texpage_process(ui): global gen ui.setData('texpage-menu', Draw.Create(0)) ui.setData('texpage-opts', list()) ui.setData('texpage-cache', dict()) i = seek_to_directive(False, False, "NOTEXTURE", "TEXTURE") if i is None: # else assume NOTEXTURE and run it again after everything else ui.debug("not a valid PIE. Either a TEXTURE or NOTEXTURE directive is required", "warning") gen = fresh_generator(ui.getData('filename')) texfilename = "" ui.setData('texpage-width', 256, True) ui.setData('texpage-height', 256, True) else: texfilename = i[pie.TEXTURE_FILENAME] ui.setData('texpage-width', i[pie.TEXTURE_WIDTH], True) ui.setData('texpage-height', i[pie.TEXTURE_HEIGHT], True) basename, ext = os.path.splitext(texfilename.lower()) namelen = len(basename) + len(ext) texpage_opts = ui.getData('texpage-opts') ui.debug('basename: ' + basename) ui.debug('ext: ' + ext) for d in (('..', 'texpages'), ('..', '..', 'texpages'), ('..', '..', '..', 'texpages')): d = os.path.join(ui.getData('dir'), *d) if not os.path.exists(d): continue ui.setData('texpage-dir', d) for fn in os.listdir(d): fnlower = fn.lower() if fnlower.startswith(basename): canonical = os.path.abspath(os.path.join(d, fn)) if fnlower.endswith(ext) and len(fn) == namelen: texpage_opts.insert(0, canonical) else: texpage_opts.append(canonical) ui.debug('texpage options: ' + str(ui.getData('texpage-opts'))) def texpage_draw(ui): Draw.PushButton("Other texpage", 2, 15, 135, 140, 30, "Select another texpage from your filesystem") Draw.PushButton("Cancel", 1, 15, 95, 140, 30, "Cancel the import operation") Draw.PushButton("Proceed", 0, 15, 55, 140, 30, "Confirm texpage selection and continue") options = ui.getData('texpage-opts') numopts = len(options) menustr = "Texpages %t" menustr += ''.join("|%s %%x%i" % (options[i], i) for i in xrange(numopts)) menustr += "|NOTEXTURE %%x%i" % numopts ui.setData('texpage-menu', Draw.Menu(menustr, 3, 15, 15, 406, 30, ui.getData('texpage-menu').val, "Select a texpage to bind to the model")) menu = ui.getData('texpage-menu') if menu.val < numopts: selected = options[menu.val] selected = new_texpage(selected) w, h = selected.getSize() Draw.Label("%ix%i at %i bpp" % (w, h, selected.getDepth()), 16, 289, 136, 20) else: Draw.Label("model will appear white", 16, 289, 136, 20) selected = None BGL.glClearColor(0.7, 0.7, 0.7, 1) BGL.glClear(BGL.GL_COLOR_BUFFER_BIT) BGL.glColor3f(0.8, 0.8, 0.8) BGL.glRecti(5, 5, 431, 361) BGL.glColor3f(0.7, 0.7, 0.7) BGL.glRecti(15, 241, 155, 311) BGL.glRecti(15, 321, 421, 351) BGL.glColor3i(0, 0, 0) text = ("Texpage Selection", "large") BGL.glRasterPos2i(int((406 - Draw.GetStringWidth(*text)) / 2 + 15), 332) Draw.Text(*text) thumbnailize(selected, 165, 55, 256) def texpage_evt(ui, val): if 0 == val: options = ui.getData('texpage-opts') texpage_cache = ui.getData('texpage-cache') menu = ui.getData('texpage-menu') msg = "selected texpage: " currentMat = Material.New('PIE_mat') texture = Texture.New() if menu.val < len(options): msg += options[menu.val] ui.debug("binding texture to texpage") texture.setType('Image') image = texpage_cache[options[menu.val]] texture.image = image else: msg += "NOTEXTURE" texture.setType('None') image = None ui.debug(msg) currentMat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL) ui.setData('material', currentMat, True) ui.setData('image', image, True) return True if 1 == val: return False elif 2 == val: Window.ImageSelector(new_texpage, "Select a texpage", ui.getData('texpage-dir', ui.getData('dir'))) Draw.Redraw() elif 3 == val: Draw.Redraw() def model_process(ui): i = seek_to_directive(True, True, "LEVELS") numlevels = i[pie.DIRECTIVE_VALUE] level, nbActualPoints, default_coords, mesh = 0, 0, None, None scale = ui.getData('import-scale') if scale is not 1.0: ui.debug("scaling by a factor of %.1f" % scale) point_divisor = 128.0 / scale def new_point(): mesh.verts.extend(*default_coords) ui.debug("patching invalid point or point reference: new point is at (%.1f, %.1f, %.1f)" % \ tuple(default_coords), "error") default_coords[1] -= 0.1 return nbActualPoints + 1 if ui.getData('pie-version') >= 5: divisorX, divisorY = 1, 1 else: divisorX = ui.getData('texpage-width') divisorY = ui.getData('texpage-height') scn = Scene.GetCurrent() # link object to current scene pieobj = scn.objects.new("Empty", "PIE_" + os.path.splitext( os.path.basename(ui.getData('filename')))[0].upper()) while level < numlevels: i = seek_to_directive(True, True, "LEVEL") level += 1 if level != i[pie.DIRECTIVE_VALUE]: ui.debug("LEVEL should have value of %i on line %i. reordered to %i" % \ (level, i[pie.LINENO], level), "warning") mesh = Mesh.New('clean') mesh.materials += [ui.getData('material')] mesh.addUVLayer('base') mesh.addUVLayer('teamcolor_meta') i = seek_to_directive(True, True, "POINTS") num, nbOfficialPoints, nbActualPoints = 0, i[pie.DIRECTIVE_VALUE], 0 default_coords = [1.0, 0.5, 0.0] abandon_level = True try: while num < nbOfficialPoints: i = gen.next() if i[pie.TOKEN_TYPE] == "error": if isinstance(i[pie.ERROR], pie.PIESyntaxError) and \ i[pie.ERROR_ASSUMED_TYPE] == "point": num += 1 ui.debug("point no. %i is not valid." % num, "error", i[pie.LINENO]) nbActualPoints = new_point() else: ui.debug(i[pie.ERROR].args, "error") break else: num += 1 x, y, z = i[pie.FIRST:] #todo: convert to Mesh code mesh.verts.extend(-x / point_divisor, -z / point_divisor, y / point_divisor) nbActualPoints += 1 else: abandon_level = False if abandon_level: ui.debug("remaining data in this LEVEL cannot be trusted.", "error") continue i = seek_to_directive(True, True, "POLYGONS") num, numtotal = 0, i[pie.DIRECTIVE_VALUE] while num < numtotal: i = gen.next() force_valid_points = list() if i[pie.TOKEN_TYPE] == "error": error = i[pie.ERROR] if isinstance(error, pie.PIESyntaxError) and \ i[pie.ERROR_ASSUMED_TYPE] == "polygon": num += 1 ui.debug("polygon no. %i is not valid. omitting" % num, "error", i[pie.LINENO]) continue else: ui.debug(str(i[pie.ERROR].args[0]), "error", i[pie.LINENO]) if isinstance(error, pie.PIEStructuralError): i = gen.next() if i[pie.TOKEN_TYPE] != "polygon": ui.debug("expected polygon data. abandoning this LEVEL", "error") break nbPoints, pos = i[pie.FIRST + 1], pie.FIRST + 2 points = i[pos:pos + nbPoints] for p in (nbPoints - p for p in xrange(1, nbPoints)): if points.count(points[p]) > 1: i[pos + p], nbActualPoints = nbActualPoints, new_point() force_valid_points.append(p) else: break if i[pie.TOKEN_TYPE] != "polygon": ui.debug("expected polygon data. abandoning this LEVEL", "error") break flags = i[pie.FIRST] nbPoints = i[pie.FIRST + 1] pos = pie.FIRST + 2 points = i[pos:pos + nbPoints] for p in xrange(nbPoints): if points[p] >= nbOfficialPoints and p not in force_valid_points: points[p], nbActualPoints = nbActualPoints, new_point() mesh.faces.extend(points, ignoreDups=True) if not flags & 0x200: continue pos += nbPoints f = mesh.faces[num] num += 1 f.mat = 0 if flags & 0x4000: mesh.activeUVLayer = 'teamcolor_meta' nbFrames = i[pos] framedelay = i[pos + 1] width = i[pos + 2] height = i[pos + 3] pos += 4 ui.debug('max ' + str(nbFrames)) ui.debug('time ' + str(framedelay)) ui.debug('width ' + str(width)) ui.debug('height ' + str(height)) if nbFrames < 1: ui.debug("maximum number of teamcolors/animation frames must be at least 1", "error") if nbFrames is 1: ui.debug("maximum number of teamcolors/animation frames should be greater than 1", "warning") width /= divisorX height /= divisorY f.uv = create_teamcolor_meta(nbPoints, width, height, nbFrames, framedelay) mesh.activeUVLayer = 'base' f.image = ui.getData('image') if ui.getData('pie-version') >= 5: uv = [Mathutils.Vector(i[pos], i[pos + 1]) for pos in range(pos, pos + 2 * nbPoints, 2)] else: uv = [Mathutils.Vector(i[pos] / divisorX, 1 - i[pos + 1] / divisorY) for pos in range(pos, pos + 2 * nbPoints, 2)] ui.debug("UVs: " + repr(uv)) f.uv = uv if flags & 0x2000: # double sided f.mode |= Mesh.FaceModes['TWOSIDE'] if flags & 0x800: # transparent f.transp = Mesh.FaceTranspModes['ALPHA'] except StopIteration: pass ob = scn.objects.new(mesh, 'LEVEL_%i' % level) mesh.flipNormals() pieobj.makeParent([ob], 0, 0) i = seek_to_directive(False, False, "CONNECTORS") if i is not None: num, numtotal = 0, i[pie.DIRECTIVE_VALUE] while num < numtotal: i = gen.next() if i[pie.TOKEN_TYPE] == "error": if isinstance(i[pie.ERROR], pie.PIESyntaxError) and \ i[pie.ERROR_ASSUMED_TYPE] == "connector": num += 1 ui.debug("connector no. %i is not valid. omitting" % num, "error", i[pie.LINENO]) continue else: ui.debug(i[pie.ERROR].args[0], "error", i[pie.LINENO]) break num += 1 x, y, z = i[pie.FIRST:] #empty = scn.objects.new('Empty', "CONNECTOR_%i" % num) empty = scn.objects.new('Empty', "CONNECTOR_%i" % num) empty.loc = x / point_divisor, -y / point_divisor, z / point_divisor empty.setSize(0.15, 0.15, 0.15) pieobj.makeParent([empty], 0, 0) if ui.getData('auto-layer'): ui.debug("layering all levels") Run(ui.getData('scripts/layers')) else: Redraw() return True def load_pie(filename): global gen, ui_ref ui = BeltFedUI(True) ui_ref = ui ui.append(opt_process, opt_draw, opt_evt) ui.append(texpage_process, texpage_draw, texpage_evt) ui.append(model_process) gen = fresh_generator(filename) ui.setData('dir', os.path.dirname(filename), True) ui.setData('filename', filename, True) ui.Run() Window.FileSelector(load_pie, 'Import Warzone model', '*.pie')