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