1#!BPY 2 3""" 4Name: 'Warzone model (.pie)...' 5Blender: 244 6Group: 'Import' 7Tooltip: 'Load a Warzone model file' 8""" 9 10__author__ = "Rodolphe Suescun, Gerard Krol, Kevin Gillette" 11__url__ = ["blender"] 12__version__ = "1.2" 13 14__bpydoc__ = """\ 15This script imports PIE files to Blender. 16 17Usage: 18 19Run this script from "File->Import" menu and then load the desired PIE file. 20""" 21 22# 23# -------------------------------------------------------------------------- 24# PIE Import v0.1 by Rodolphe Suescun (AKA RodZilla) 25# v0.2 by Gerard Krol (gerard_) 26# v0.3 by Kevin Gillette (kage) 27# v1.0 -- 28# -------------------------------------------------------------------------- 29# ***** BEGIN GPL LICENSE BLOCK ***** 30# 31# This program is free software; you can redistribute it and/or 32# modify it under the terms of the GNU General Public License 33# as published by the Free Software Foundation; either version 2 34# of the License, or (at your option) any later version. 35# 36# This program is distributed in the hope that it will be useful, 37# but WITHOUT ANY WARRANTY; without even the implied warranty of 38# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 39# GNU General Public License for more details. 40# 41# You should have received a copy of the GNU General Public License 42# along with this program; if not, write to the Free Software Foundation, 43# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 44# 45# ***** END GPL LICENCE BLOCK ***** 46# -------------------------------------------------------------------------- 47 48from Blender import * 49from pie_common import * 50import os, pie 51 52#==================================================================================# 53# Draws a thumbnail from a Blender image at x,y with sides that are edgelen long # 54#==================================================================================# 55 56gen = None 57ui_ref = None 58 59def fresh_generator(filename): 60 return pie.data_mutator(pie.parse(filename)) 61 62def seek_to_directive(err_on_nonmatch, fatal_if_not_found, *directives): 63 for i in gen: 64 type = i[pie.TOKEN_TYPE] 65 if type == "directive" and i[pie.DIRECTIVE_NAME] in directives: 66 return i 67 elif type == "error": 68 ui.debug(i[pie.ERROR].args[0], "error", i[pie.LINENO]) 69 elif err_on_nonmatch: 70 ui.debug("expected %s directive." % directives[0], "warning", i[pie.LINENO]) 71 if fatal_if_not_found: 72 ui.debug(directives[0] + " directive not found. cannot continue.", 73 "fatal-error") 74 75def generate_opt_gui(rect, optlist, tooltips, limits): 76 """ expects rect to be [xmin, ymin, xmax, ymax] """ 77 78 BGL.glRecti(*rect) 79 buttonwidth = 120 80 buttonheight = 20 81 margin = 5 82 defbuttonwidth = 20 # default button 83 defbuttonpos = rect[2] - defbuttonwidth - margin 84 buttonpos = defbuttonpos - margin - buttonwidth 85 labelwidth = buttonpos - rect[0] - margin 86 numopts = len(optlist) 87 for i, opt in enumerate(optlist): 88 name = opt['name'] 89 val = scalar_value(opt['val']) 90 posY = rect[3] - (i + 1) * (buttonheight + margin) 91 if posY < rect[1] + margin: break 92 Draw.PushButton("D", i + numopts, defbuttonpos, posY, defbuttonwidth, buttonheight, 93 "Set this option to its default") 94 tooltip = tooltips.get(name, "") 95 if isinstance(opt['opts'], basestring): 96 title = opt.get('title', "") 97 if opt['opts'] is 'number': 98 opt['val'] = Draw.Number(title, i, buttonpos, posY, buttonwidth, 99 buttonheight, val, limits[name][0], 100 limits[name][1], tooltip) 101 elif opt['opts'] is 'bool': 102 opt['val'] = Draw.Toggle(title, i, buttonpos, posY, buttonwidth, 103 buttonheight, val, tooltip) 104 else: 105 numopts = len(opt['opts']) 106 if numopts < 2: 107 ui.debug("error: invalid option supplied to generate_opt_gui") 108 continue 109 if numopts is 2: 110 Draw.PushButton(opt['opts'][val], i, buttonpos, posY, 111 buttonwidth, buttonheight, tooltip) 112 else: 113 menustr = opt.get('title', "") 114 if menustr: menustr += "%t|" 115 menustr += '|'.join("%s %%x%i" % (opt['opts'][j], j) for j in xrange(numopts)) 116 opt['val'] = Draw.Menu(menustr, i, buttonpos, posY, buttonwidth, 117 buttonheight, val, tooltip) 118 Draw.Label(opt['label'], rect[0] + margin, posY, labelwidth, buttonheight) 119 120def opt_process(ui): 121 ui.setData('defaults/script', {'auto-layer': True, 'import-scale': 1.0, 122 'scripts/layers': "pie_levels_to_layers.py", 123 'scripts/validate': "pie_validate.py"}) 124 ui.setData('defaults/user', Registry.GetKey('warzone_pie', True) or dict()) 125 ui.setData('limits', {'import-scale': (0.1, 1000.0)}) 126 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\"", 127 '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.", 128 'scripts/layers': "Basename of the \"PIE levels -> layers\" script. Do not include the path. Whitespace is not trimmed", 129 'scripts/validate': "Basename of the \"PIE validate\" script. Do not include the path. Whitespace is not trimmed"}) 130 131 i = seek_to_directive(True, False, "PIE") 132 if i is None: 133 ui.debug("not a valid PIE. PIE directive is required", "warning") 134 gen = fresh_generator(ui.getData('filename')) 135 ui.setData('pie-version', 2, True) 136 else: 137 ui.setData('pie-version', i[pie.DIRECTIVE_VALUE], True) 138 ui.debug("version %i pie detected" % ui.getData('pie-version')) 139 i = seek_to_directive(True, False, "TYPE") 140 if i is None: 141 ui.debug("not a valid PIE. TYPE directive is required", "warning") 142 gen = fresh_generator(ui.getData('filename')) 143 ui.setData('pie-type', 0x200, True) 144 else: 145 ui.setData('pie-type', i[pie.DIRECTIVE_VALUE], True) 146 ui.debug("type %i pie detected" % ui.getData('pie-type')) 147 optlist = list() 148 dirs = Get('uscriptsdir'), Get('scriptsdir') 149 script = default_value(ui, 'scripts/layers') 150 for d in dirs: 151 if d and script in os.listdir(d): 152 optlist.append({'name': "auto-layer", 153 'label': "Assign levels to different layers?", 154 'opts': 'bool', 'title': "Automatically Layer"}) 155 ui.setData('scripts/layers', os.path.join(d, script), True) 156 break 157 else: 158 ui.debug("Could not find '%s'. automatic layering will not be available" % script) 159 optlist.append({'name': "import-scale", 'label': "Scale all points by a factor.", 160 'opts': 'number', 'title': "Scale"}) 161 for opt in optlist: 162 opt.setdefault('val', default_value(ui, opt['name'])) 163 ui.setData('optlist', optlist) 164 165def opt_draw(ui): 166 optlist = ui.getData('optlist') 167 numopts = len(optlist) 168 Draw.PushButton("Cancel", numopts * 2 + 1, 68, 15, 140, 30, "Cancel the import operation") 169 Draw.PushButton("Proceed", numopts * 2, 217, 15, 140, 30, "Confirm texpage selection and continue") 170 Draw.PushButton("Save Defaults", numopts * 2 + 3, 68, 321, 140, 30, "Save options in their current state as the default") 171 Draw.PushButton("Default All", numopts * 2 + 2, 217, 321, 140, 30, "Revert all options to their defaults") 172 BGL.glClearColor(0.7, 0.7, 0.7, 1) 173 BGL.glClear(BGL.GL_COLOR_BUFFER_BIT) 174 BGL.glColor3f(0.8, 0.8, 0.8) 175 BGL.glRecti(5, 5, 431, 411) 176 BGL.glColor3f(0.7, 0.7, 0.7) 177 BGL.glRecti(15, 361, 421, 401) 178 generate_opt_gui([15, 55, 421, 311], optlist, ui.getData('tooltips'), ui.getData('limits')) 179 BGL.glColor3i(0, 0, 0) 180 text = ("General Options", "large") 181 BGL.glRasterPos2i(int((406 - Draw.GetStringWidth(*text)) / 2 + 15), 377) 182 Draw.Text(*text) 183 184def opt_evt(ui, val): 185 opts = ui.getData('optlist') 186 numopts = len(opts) 187 if val >= numopts * 2: 188 val -= numopts * 2 189 if val is 0: 190 if not ui.getData('defaults/user'): save_defaults(ui) 191 for opt in opts: 192 ui.setData(opt['name'], scalar_value(opt['val']), True) 193 return True 194 elif val is 1: 195 return False 196 elif val is 2: 197 if ui.getData('defaults/user'): 198 def_src = Draw.PupMenu( 199 "Source of default value %t|Script default|User default") 200 else: 201 def_src = 1 202 if def_src > 0: 203 for opt in opts: 204 name = opt['name'] 205 if def_src is 1: opt['val'] = ui.getData('defaults/script')[name] 206 elif def_src is 2: opt['val'] = default_value(ui, name) 207 else: break 208 Draw.Redraw() 209 elif val is 3: 210 save_defaults(ui) 211 return 212 213 if val < numopts: 214 opt = opts[val] 215 if not isinstance(opt, basestring): 216 if len(opt['opts']) is 2: 217 opt['val'] = abs(opt['val'] - 1) # toggle it between 0 and 1 218 elif val < numopts * 2: 219 opt = ui.getData('optlist')[val - numopts] 220 name = opt['name'] 221 if name in ui.getData('defaults/user'): 222 def_src = Draw.PupMenu( 223 "Source of default value %t|Script default|User default") 224 else: 225 def_src = 1 226 if def_src is 1: opt['val'] = ui.getData('defaults/script')[name] 227 elif def_src is 2: opt['val'] = ui.getData('defaults/user')[name] 228 else: return 229 Draw.Redraw() 230 231def thumbnailize(img, x, y, edgelen): 232 try: 233 # BGL.glClearColor(0.7, 0.7, 0.7, 1) 234 # BGL.glClear(BGL.GL_COLOR_BUFFER_BIT) 235 if img: BGL.glColor3f(0.0, 0.0, 0.0) 236 else: BGL.glColor3f(1.0, 1.0, 1.0) 237 BGL.glRecti(x, y, x + edgelen, y + edgelen) 238 except NameError, AttributeError: 239 ui.debug("unable to load BGL. will not affect anything, though") 240 if not img: return 241 width, height = img.getSize() 242 edgelen = float(edgelen) 243 if width > height: primary, secondary, reverse = width, height, False 244 else: primary, secondary, reverse = height, width, True 245 if primary < edgelen: zoom = 1.0 246 else: zoom = 1.0 / (primary / edgelen) 247 offset = int((edgelen - zoom * secondary) / 2) 248 if zoom == 1.0: offset = [int((edgelen - zoom * primary) / 2), offset] 249 else: offset = [0, offset] 250 if reverse: offset.reverse() 251 Draw.Image(img, offset[0] + x, offset[1] + y, zoom, zoom) 252 253def new_texpage(filename): 254 texpage_cache = ui_ref.getData('texpage-cache') 255 options = ui_ref.getData('texpage-opts') 256 if filename in texpage_cache: 257 img = texpage_cache[filename] 258 else: 259 img = Image.Load(filename) 260 texpage_cache[filename] = img 261 if filename not in options: 262 ui.getData('texpage-menu').val = len(options) 263 options.append(filename) 264 if 'texpage-dir' not in ui: 265 ui.setData('texpage-dir', os.path.dirname(filename)) 266 return img 267 268def texpage_process(ui): 269 global gen 270 ui.setData('texpage-menu', Draw.Create(0)) 271 ui.setData('texpage-opts', list()) 272 ui.setData('texpage-cache', dict()) 273 i = seek_to_directive(False, False, "NOTEXTURE", "TEXTURE") 274 if i is None: # else assume NOTEXTURE and run it again after everything else 275 ui.debug("not a valid PIE. Either a TEXTURE or NOTEXTURE directive is required", "warning") 276 gen = fresh_generator(ui.getData('filename')) 277 texfilename = "" 278 ui.setData('texpage-width', 256, True) 279 ui.setData('texpage-height', 256, True) 280 else: 281 texfilename = i[pie.TEXTURE_FILENAME] 282 ui.setData('texpage-width', i[pie.TEXTURE_WIDTH], True) 283 ui.setData('texpage-height', i[pie.TEXTURE_HEIGHT], True) 284 285 basename, ext = os.path.splitext(texfilename.lower()) 286 namelen = len(basename) + len(ext) 287 texpage_opts = ui.getData('texpage-opts') 288 ui.debug('basename: ' + basename) 289 ui.debug('ext: ' + ext) 290 for d in (('..', 'texpages'), ('..', '..', 'texpages'), ('..', '..', '..', 'texpages')): 291 d = os.path.join(ui.getData('dir'), *d) 292 if not os.path.exists(d): continue 293 ui.setData('texpage-dir', d) 294 for fn in os.listdir(d): 295 fnlower = fn.lower() 296 if fnlower.startswith(basename): 297 canonical = os.path.abspath(os.path.join(d, fn)) 298 if fnlower.endswith(ext) and len(fn) == namelen: 299 texpage_opts.insert(0, canonical) 300 else: 301 texpage_opts.append(canonical) 302 ui.debug('texpage options: ' + str(ui.getData('texpage-opts'))) 303 304def texpage_draw(ui): 305 Draw.PushButton("Other texpage", 2, 15, 135, 140, 30, "Select another texpage from your filesystem") 306 Draw.PushButton("Cancel", 1, 15, 95, 140, 30, "Cancel the import operation") 307 Draw.PushButton("Proceed", 0, 15, 55, 140, 30, "Confirm texpage selection and continue") 308 options = ui.getData('texpage-opts') 309 numopts = len(options) 310 menustr = "Texpages %t" 311 menustr += ''.join("|%s %%x%i" % (options[i], i) for i in xrange(numopts)) 312 menustr += "|NOTEXTURE %%x%i" % numopts 313 ui.setData('texpage-menu', Draw.Menu(menustr, 3, 15, 15, 406, 30, 314 ui.getData('texpage-menu').val, "Select a texpage to bind to the model")) 315 menu = ui.getData('texpage-menu') 316 if menu.val < numopts: 317 selected = options[menu.val] 318 selected = new_texpage(selected) 319 w, h = selected.getSize() 320 Draw.Label("%ix%i at %i bpp" % (w, h, selected.getDepth()), 16, 289, 136, 20) 321 else: 322 Draw.Label("model will appear white", 16, 289, 136, 20) 323 selected = None 324 BGL.glClearColor(0.7, 0.7, 0.7, 1) 325 BGL.glClear(BGL.GL_COLOR_BUFFER_BIT) 326 BGL.glColor3f(0.8, 0.8, 0.8) 327 BGL.glRecti(5, 5, 431, 361) 328 BGL.glColor3f(0.7, 0.7, 0.7) 329 BGL.glRecti(15, 241, 155, 311) 330 BGL.glRecti(15, 321, 421, 351) 331 BGL.glColor3i(0, 0, 0) 332 text = ("Texpage Selection", "large") 333 BGL.glRasterPos2i(int((406 - Draw.GetStringWidth(*text)) / 2 + 15), 332) 334 Draw.Text(*text) 335 thumbnailize(selected, 165, 55, 256) 336 337def texpage_evt(ui, val): 338 if 0 == val: 339 options = ui.getData('texpage-opts') 340 texpage_cache = ui.getData('texpage-cache') 341 menu = ui.getData('texpage-menu') 342 msg = "selected texpage: " 343 currentMat = Material.New('PIE_mat') 344 texture = Texture.New() 345 if menu.val < len(options): 346 msg += options[menu.val] 347 ui.debug("binding texture to texpage") 348 texture.setType('Image') 349 image = texpage_cache[options[menu.val]] 350 texture.image = image 351 else: 352 msg += "NOTEXTURE" 353 texture.setType('None') 354 image = None 355 ui.debug(msg) 356 currentMat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL) 357 ui.setData('material', currentMat, True) 358 ui.setData('image', image, True) 359 return True 360 if 1 == val: 361 return False 362 elif 2 == val: 363 Window.ImageSelector(new_texpage, "Select a texpage", 364 ui.getData('texpage-dir', ui.getData('dir'))) 365 Draw.Redraw() 366 elif 3 == val: 367 Draw.Redraw() 368 369def model_process(ui): 370 i = seek_to_directive(True, True, "LEVELS") 371 numlevels = i[pie.DIRECTIVE_VALUE] 372 level, nbActualPoints, default_coords, mesh = 0, 0, None, None 373 scale = ui.getData('import-scale') 374 if scale is not 1.0: 375 ui.debug("scaling by a factor of %.1f" % scale) 376 point_divisor = 128.0 / scale 377 378 def new_point(): 379 mesh.verts.extend(*default_coords) 380 ui.debug("patching invalid point or point reference: new point is at (%.1f, %.1f, %.1f)" % \ 381 tuple(default_coords), "error") 382 default_coords[1] -= 0.1 383 return nbActualPoints + 1 384 385 if ui.getData('pie-version') >= 5: 386 divisorX, divisorY = 1, 1 387 else: 388 divisorX = ui.getData('texpage-width') 389 divisorY = ui.getData('texpage-height') 390 scn = Scene.GetCurrent() # link object to current scene 391 pieobj = scn.objects.new("Empty", "PIE_" + os.path.splitext( 392 os.path.basename(ui.getData('filename')))[0].upper()) 393 while level < numlevels: 394 i = seek_to_directive(True, True, "LEVEL") 395 level += 1 396 if level != i[pie.DIRECTIVE_VALUE]: 397 ui.debug("LEVEL should have value of %i on line %i. reordered to %i" % \ 398 (level, i[pie.LINENO], level), "warning") 399 mesh = Mesh.New('clean') 400 mesh.materials += [ui.getData('material')] 401 mesh.addUVLayer('base') 402 mesh.addUVLayer('teamcolor_meta') 403 i = seek_to_directive(True, True, "POINTS") 404 num, nbOfficialPoints, nbActualPoints = 0, i[pie.DIRECTIVE_VALUE], 0 405 default_coords = [1.0, 0.5, 0.0] 406 abandon_level = True 407 try: 408 while num < nbOfficialPoints: 409 i = gen.next() 410 if i[pie.TOKEN_TYPE] == "error": 411 if isinstance(i[pie.ERROR], pie.PIESyntaxError) and \ 412 i[pie.ERROR_ASSUMED_TYPE] == "point": 413 num += 1 414 ui.debug("point no. %i is not valid." % num, "error", i[pie.LINENO]) 415 nbActualPoints = new_point() 416 else: 417 ui.debug(i[pie.ERROR].args, "error") 418 break 419 else: 420 num += 1 421 x, y, z = i[pie.FIRST:] 422 #todo: convert to Mesh code 423 mesh.verts.extend(-x / point_divisor, -z / point_divisor, y / point_divisor) 424 nbActualPoints += 1 425 else: 426 abandon_level = False 427 if abandon_level: 428 ui.debug("remaining data in this LEVEL cannot be trusted.", "error") 429 continue 430 i = seek_to_directive(True, True, "POLYGONS") 431 num, numtotal = 0, i[pie.DIRECTIVE_VALUE] 432 while num < numtotal: 433 i = gen.next() 434 force_valid_points = list() 435 if i[pie.TOKEN_TYPE] == "error": 436 error = i[pie.ERROR] 437 if isinstance(error, pie.PIESyntaxError) and \ 438 i[pie.ERROR_ASSUMED_TYPE] == "polygon": 439 num += 1 440 ui.debug("polygon no. %i is not valid. omitting" % num, "error", i[pie.LINENO]) 441 continue 442 else: 443 ui.debug(str(i[pie.ERROR].args[0]), "error", i[pie.LINENO]) 444 if isinstance(error, pie.PIEStructuralError): 445 i = gen.next() 446 if i[pie.TOKEN_TYPE] != "polygon": 447 ui.debug("expected polygon data. abandoning this LEVEL", "error") 448 break 449 nbPoints, pos = i[pie.FIRST + 1], pie.FIRST + 2 450 points = i[pos:pos + nbPoints] 451 for p in (nbPoints - p for p in xrange(1, nbPoints)): 452 if points.count(points[p]) > 1: 453 i[pos + p], nbActualPoints = nbActualPoints, new_point() 454 force_valid_points.append(p) 455 else: break 456 if i[pie.TOKEN_TYPE] != "polygon": 457 ui.debug("expected polygon data. abandoning this LEVEL", "error") 458 break 459 flags = i[pie.FIRST] 460 nbPoints = i[pie.FIRST + 1] 461 pos = pie.FIRST + 2 462 points = i[pos:pos + nbPoints] 463 for p in xrange(nbPoints): 464 if points[p] >= nbOfficialPoints and p not in force_valid_points: 465 points[p], nbActualPoints = nbActualPoints, new_point() 466 mesh.faces.extend(points, ignoreDups=True) 467 if not flags & 0x200: continue 468 pos += nbPoints 469 f = mesh.faces[num] 470 num += 1 471 f.mat = 0 472 if flags & 0x4000: 473 mesh.activeUVLayer = 'teamcolor_meta' 474 nbFrames = i[pos] 475 framedelay = i[pos + 1] 476 width = i[pos + 2] 477 height = i[pos + 3] 478 pos += 4 479 ui.debug('max ' + str(nbFrames)) 480 ui.debug('time ' + str(framedelay)) 481 ui.debug('width ' + str(width)) 482 ui.debug('height ' + str(height)) 483 if nbFrames < 1: 484 ui.debug("maximum number of teamcolors/animation frames must be at least 1", "error") 485 if nbFrames is 1: 486 ui.debug("maximum number of teamcolors/animation frames should be greater than 1", "warning") 487 width /= divisorX 488 height /= divisorY 489 f.uv = create_teamcolor_meta(nbPoints, width, height, nbFrames, framedelay) 490 mesh.activeUVLayer = 'base' 491 f.image = ui.getData('image') 492 if ui.getData('pie-version') >= 5: 493 uv = [Mathutils.Vector(i[pos], i[pos + 1]) for pos in range(pos, pos + 2 * nbPoints, 2)] 494 else: 495 uv = [Mathutils.Vector(i[pos] / divisorX, 1 - i[pos + 1] / divisorY) for pos in range(pos, pos + 2 * nbPoints, 2)] 496 ui.debug("UVs: " + repr(uv)) 497 f.uv = uv 498 if flags & 0x2000: 499 # double sided 500 f.mode |= Mesh.FaceModes['TWOSIDE'] 501 if flags & 0x800: 502 # transparent 503 f.transp = Mesh.FaceTranspModes['ALPHA'] 504 except StopIteration: pass 505 ob = scn.objects.new(mesh, 'LEVEL_%i' % level) 506 mesh.flipNormals() 507 pieobj.makeParent([ob], 0, 0) 508 i = seek_to_directive(False, False, "CONNECTORS") 509 if i is not None: 510 num, numtotal = 0, i[pie.DIRECTIVE_VALUE] 511 while num < numtotal: 512 i = gen.next() 513 if i[pie.TOKEN_TYPE] == "error": 514 if isinstance(i[pie.ERROR], pie.PIESyntaxError) and \ 515 i[pie.ERROR_ASSUMED_TYPE] == "connector": 516 num += 1 517 ui.debug("connector no. %i is not valid. omitting" % num, "error", i[pie.LINENO]) 518 continue 519 else: 520 ui.debug(i[pie.ERROR].args[0], "error", i[pie.LINENO]) 521 break 522 num += 1 523 x, y, z = i[pie.FIRST:] 524 #empty = scn.objects.new('Empty', "CONNECTOR_%i" % num) 525 empty = scn.objects.new('Empty', "CONNECTOR_%i" % num) 526 empty.loc = x / point_divisor, -y / point_divisor, z / point_divisor 527 empty.setSize(0.15, 0.15, 0.15) 528 pieobj.makeParent([empty], 0, 0) 529 if ui.getData('auto-layer'): 530 ui.debug("layering all levels") 531 Run(ui.getData('scripts/layers')) 532 else: 533 Redraw() 534 return True 535 536def load_pie(filename): 537 global gen, ui_ref 538 ui = BeltFedUI(True) 539 ui_ref = ui 540 ui.append(opt_process, opt_draw, opt_evt) 541 ui.append(texpage_process, texpage_draw, texpage_evt) 542 ui.append(model_process) 543 gen = fresh_generator(filename) 544 ui.setData('dir', os.path.dirname(filename), True) 545 ui.setData('filename', filename, True) 546 ui.Run() 547 548Window.FileSelector(load_pie, 'Import Warzone model', '*.pie') 549