adp001 opened this issue on Dec 27, 2019 ยท 92 posts
adp001 posted Fri, 27 December 2019 at 3:11 PM
from __future__ import print_function
try:
import poser
except ImportError:
raise RuntimeError("Script must run in Poser.")
import wx
import wx.aui
import sys
import os
import time
import json
import numpy as NP
CONFIG = dict()
BASEPATH = os.path.abspath(os.path.dirname(sys.argv[0]))
SCRIPT_FILENAME = os.path.basename(sys.argv[0])
CONFIG_FILENAME = SCRIPT_FILENAME.rsplit(".")[0] + ".cfg"
# Forced floatingpoint precision. Mainly to help avoiding floatingpoint
# errors while reading files from external modelers.
PRECISION = 8
NP_PRECISION = NP.float32
def ErrDialog(err, msg=None):
dlg = wx.MessageDialog(None, err, style=wx.ICON_ERROR)
if msg:
dlg.SetMessage(msg)
dlg.ShowModal()
dlg.Close()
def read_config():
global CONFIG
fname = os.path.join(BASEPATH, CONFIG_FILENAME)
if os.path.isfile(fname):
try:
with open(fname, "r") as fh:
CONFIG = json.load(fh, encoding="utf-8")
except IOError:
pass
def write_config():
global CONFIG
fname = os.path.join(BASEPATH, CONFIG_FILENAME)
try:
with open(fname, "w") as fh:
json.dump(CONFIG, fh)
except IOError:
ErrDialog("File Error.", "Can't write configfile '{}.".format(fname))
def write_matfile(filename, materials):
"""
Write out a simple material-file.
"""
try:
open(filename, "w")
except IOError:
return ErrDialog("File Error.", "Can't create or write to file '{}'.n"
"Make sure directory '{}' exist and is writable.".
format(filename, os.path.dirname(filename)))
with open(filename, "w") as mfh:
for mat in materials:
print("newmtl", mat.Name(), file=mfh)
print("Ns", mat.Ns(), file=mfh)
print("Ka", "0 0 0", file=mfh)
print("Kd", " ".join(map(str, mat.DiffuseColor())), file=mfh)
print("Ks", "0 0 0", file=mfh)
if mat.TextureMapFileName():
print("map_Kd", mat.TextureMapFileName(), file=mfh)
if mat.BumpMapFileName():
print("map_Bump", mat.BumpMapFileName(), file=mfh)
def collect_geometry(figure):
np_vertex = lambda v: NP.array((v.X(), v.Y(), v.Z()), NP_PRECISION)
geom, actorlist, actor_indices = figure.UnimeshInfo()
verts = NP.zeros((geom.NumVertices(), 3), NP_PRECISION)
for actor_idx, actor in enumerate(actorlist):
world_verts = actor.Geometry().WorldVertices()
for i, vertex_idx in enumerate(actor_indices[actor_idx]):
verts[vertex_idx] = np_vertex(world_verts[i])
return dict(vertices=verts,
geom=geom,
actorlist=actorlist,
actor_indices=actor_indices)
def read_vertices(filename):
"""
Read Wavefront obj-file saved to file. Typically a figure exported
from Poser and modified with an external modeller (Blender etc).
"""
try:
open(filename, "r")
except IOError:
raise RuntimeError("File '{}' does not exist or is not accessible.".
format(filename))
vertices = list()
with open(filename, "r") as fh:
for line in fh:
if not line:
break
c, _, v = line.strip().partition(" ")
if c == "v":
vertices.append(map(float, v.split()))
# Remove following line if vertices are not in one block,
# so the whole file is processed.
elif c in ("vt", "vn", "f"):
break
return NP.array(vertices, NP_PRECISION)
def do_export(figure):
"""
Export figure to Wavefront obj file.
"""
assert isinstance(figure, poser.FigureType)
figurename = figure.Name()
parms = collect_geometry(figure)
vertices = parms["vertices"]
geom = parms["geom"]
use_material = CONFIG.get("CTRL_ExportTexture", True)
use_groups = CONFIG.get("CTRL_ExportGroups", False)
morphname = CONFIG["CTRL_MorphName"].strip()
vertices *= int(CONFIG.get("Scale", 100))
if CONFIG.get("CTRL_SingleSelectFile", True):
with wx.FileDialog(None, "Export Wavefront file",
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR,
defaultDir=CONFIG.get("ExportPath", BASEPATH),
defaultFile="{}-{}.obj".format(figurename, morphname)
) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return
CONFIG["ExportPath"] = dlg.GetPath()
filename = dlg.GetFilename()
else:
filename = os.path.join(CONFIG["ExportPath"], "{}-{}.obj".format(figurename, morphname))
try:
open(filename, "w")
except IOError:
ErrDialog("Can't create or write to file '{}'.",
"Maybe you have to select another directory first.")
return
with open(filename, "w") as fh:
print("### Date : %s" % time.asctime(), file=fh)
print("### Figure : %s" % figurename, file=fh)
print("### Vertices: %s" % len(vertices), file=fh)
if use_material and geom.Materials():
matfile = filename.rsplit(".", 1)[0] + ".mtl"
write_matfile(matfile, geom.Materials())
print("mtllib ./" + os.path.basename(matfile), file=fh)
for vertex in vertices:
print("v {} {} {}".format(*vertex), file=fh)
if use_material:
for tvert in geom.TexVertices():
print("vt {} {}".format(tvert.U(), tvert.V()), file=fh)
current_groups = list()
current_mat = list()
if not use_groups:
print("g", figurename, file=fh)
polys = geom.Polygons()
tpolys = geom.TexPolygons()
sets = geom.Sets()
tsets = geom.TexSets()
for index, poly in enumerate(polys):
if use_groups:
if poly.Groups() != current_groups:
current_groups = poly.Groups()
print("g", ", ".join(current_groups), file=fh)
if use_material:
if poly.MaterialName() != current_mat:
current_mat = poly.MaterialName()
print("usemtl", current_mat, file=fh)
line = [str(sets[idx + poly.Start()] + 1) for idx in range(poly.NumVertices())]
if use_material:
tpoly = tpolys[index]
for tidx, v in enumerate((tsets[idx + tpoly.Start()] + 1) for idx in range(tpoly.NumTexVertices())):
line[tidx] += "/%d" % v
print("f", " ".join(map(str, line)), file=fh)
CONFIG["LastExported"] = filename
return filename
def do_import(figure, morphname):
assert isinstance(figure, poser.FigureType)
figurename = figure.Name()
geom, actorlist, actor_indices = figure.UnimeshInfo()
if CONFIG.get("CTRL_SingleSelectFile", True):
old_filename = CONFIG.get("LastExported", None)
if not old_filename:
with wx.FileDialog(None, "Import Original exported Wavefront file",
style=wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST,
defaultDir=CONFIG.get("ImportPath", BASEPATH),
defaultFile="{}-{}.obj".format(figurename, morphname)
) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return
old_filename = dlg.GetFilename()
with wx.FileDialog(None, "Import Wavefront file as morph",
style=wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST,
defaultDir=CONFIG.get("ImportPath", BASEPATH),
defaultFile="{}-{}_mod.obj".format(figurename, morphname)
) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return
CONFIG["ExportPath"] = dlg.GetPath()
new_filename = dlg.GetFilename()
else:
old_filename = os.path.join(CONFIG["ExportPath"], "{}-{}.obj".format(figurename, morphname))
new_filename = os.path.join(CONFIG["ImportPath"], "{}-{}_mod.obj".format(figurename, morphname))
verts_new = read_vertices(new_filename)
verts_old = read_vertices(old_filename)
if len(verts_old) != len(verts_new):
ErrDialog("Vertices mismatch.", "Old number of vertices: {}."
"New number of vertices: {}.".
format(len(verts_old), len(verts_new)))
return
vertices = (verts_new - verts_old) / int(CONFIG.get("Scale", 100))
del verts_new
del verts_old
body = figure.ParentActor()
masterdial = body.Parameter(morphname)
if masterdial is None:
body.CreateValueParameter(morphname)
masterdial = body.Parameter(morphname)
if masterdial is None:
return ErrDialog("Morph Error.", "Can't find or create morph in body actor.")
for actor_idx, actor in enumerate(actorlist):
morph = list()
for i, v_idx in enumerate(actor_indices[actor_idx]):
x, y, z = map(lambda a: round(a, PRECISION), vertices[v_idx])
if x != 0 or y != 0 or z != 0:
morph.append((i, x, y, z))
if len(morph) == 0:
continue
morphparm = actor.Parameter(morphname)
if morphparm is None:
actor.SpawnTarget(morphname)
morphparm = actor.Parameter(morphname)
if morphparm is None:
return ErrDialog("Morph Error", "Can't create Morphtarget.")
if not morphparm.IsMorphTarget():
return ErrDialog("Morph error.", "Morph Parametername ('%s')n"
"already exist but is not a morph"
% morphname)
for i in range(actor.Geometry().NumVertices()):
morphparm.SetMorphTargetDelta(i, 0, 0, 0)
for i, x, y, z in morph:
morphparm.SetMorphTargetDelta(i, x, y, z)
while morphparm.NumValueOperations():
morphparm.DeleteValueOperation(0)
morphparm.AddValueOperation(poser.kValueOpTypeCodeKEY, masterdial)
vop = morphparm.ValueOperations()[0]
vop.InsertKey(0, 0)
vop.InsertKey(1, 1)
masterdial.SetMinValue(-.5)
masterdial.SetMaxValue(1.0)
APP_NAME = "Figure Importer/Exporter"
STD_COLOR = 120, 120, 120
LBL_COLOR = 80, 80, 80
BG_COLOR = 75, 75, 75
HL_COLOR = 100, 100, 150
FG_COLOR = 200, 200, 200
TX_COLOR = 0xfe, 0xfe, 0xfe
STOPP_UPDATE_UI = False
class GridBagManager(object):
__slots__ = "parent", "sizer", "flag", "border", "current_row", "font"
def __init__(self, *args, **kwargs):
for varname in self.__slots__:
setattr(self, varname, kwargs.get(varname, None))
for idx, entry in enumerate(args):
if self.__slots__[idx] not in kwargs:
setattr(self, self.__slots__[idx], entry)
self.current_row = 0
assert isinstance(self.parent, wx.Panel)
assert isinstance(self.sizer, wx.GridBagSizer)
assert isinstance(self.flag, int)
assert isinstance(self.border, int)
def addrow(self, *args, **kwargs):
row = int(kwargs.get("row", self.current_row))
col = int(kwargs.get("startcol", 0))
flag = kwargs.get("flag", self.flag)
font = kwargs.pop("font", self.font)
widgets = []
for idx, widget in enumerate(args):
if font: widget.SetFont(font)
self.sizer.Add(widget, pos=(row, col + idx),
flag=flag,
border=self.border)
widgets.append(widget)
self.current_row += 1
return widgets
def setTooltip(ctrl, text):
if text and ctrl:
t = wx.ToolTip(text)
t.SetAutoPop(5000)
ctrl.SetToolTip(t)
return ctrl
def ColoredCtrl(ctrl, **kwargs):
ctrl.SetBackgroundColour(kwargs.pop("bgcolor", BG_COLOR))
ctrl.SetForegroundColour(kwargs.pop("fgcolor", FG_COLOR))
return setTooltip(ctrl, kwargs.pop("tooltip", None))
def LabelCtrl(parent, **kwargs):
bg_color = kwargs.pop("bgcolor", BG_COLOR)
fg_color = kwargs.pop("fgcolor", FG_COLOR)
font = kwargs.pop("font", SYS_FONT)
tooltip = kwargs.pop("tooltip", None)
ctrl = wx.StaticText(parent, **kwargs)
ctrl.SetFont(font)
ctrl.SetBackgroundColour(bg_color)
ctrl.SetForegroundColour(fg_color)
return setTooltip(ctrl, tooltip)
def PrepCtrl(parent, ctrl, **kwargs):
bind = kwargs.pop("bind", [])
fgcolor = kwargs.pop("fgcolor", FG_COLOR)
bgcolor = kwargs.pop("bgcolor", BG_COLOR)
font = kwargs.pop("font", None)
value = kwargs.pop("value", None)
tooltip = kwargs.pop("tooltip", None)
ctrl = ctrl(parent, **kwargs)
if bind:
if isinstance(bind[0], (tuple, list)):
for bind_entry in bind:
assert len(bind_entry) == 2
ctrl.Bind(bind_entry[0], bind_entry[1])
else:
assert len(bind) == 2
ctrl.Bind(bind[0], bind[1])
ctrl.SetForegroundColour(fgcolor)
ctrl.SetBackgroundColour(bgcolor)
if font:
ctrl.SetFont(font)
if value:
ctrl.SetValue(value)
return setTooltip(ctrl, tooltip)
class AppPanel(wx.Panel):
_CONFIGCHANGED = False
def __init__(self, parent,
name="Fullbody Morphs",
style=wx.DEFAULT,
size=(270, 400),
**kwargs):
super(self.__class__, self).__init__(parent=parent,
id=wx.ID_ANY,
name=name,
style=style,
size=size,
**kwargs)
read_config()
self.stdfont = SYS_FONT
self.gb_manager = GridBagManager(parent=self,
sizer=wx.GridBagSizer(0, 0),
flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
border=2
)
self.figurelist = PrepCtrl(self, wx.Choice, choices=[],
style=wx.CB_SORT | wx.NO_BORDER,
size=(80, 20), name="figurelist",
font=self.stdfont.SetPointSize(8),
tooltip="Right click to reload Figurelist.",
bind=((wx.EVT_RIGHT_UP, self.updateFigurelist),
(wx.EVT_CHOICE, self.onChoice))
)
self.updateFigurelist()
self.initUI(self.gb_manager)
self.Bind(wx.EVT_WINDOW_DESTROY, self.onClose)
self.Bind(wx.EVT_MOUSE_EVENTS, lambda ev: ev.StopPropagation())
self.Bind(wx.EVT_SIZE, self.onSize)
self.updateUI()
self.Layout()
def initUI(self, gbm):
assert isinstance(gbm, GridBagManager)
self.SetBackgroundColour(BG_COLOR)
self.SetForegroundColour(FG_COLOR)
self.SetFont(self.stdfont)
btn_size = (80, 15)
gbm.addrow(PrepCtrl(self, wx.Button, label="Export Figure",
name="BTN_ExportFigure",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=(50, 30),
bgcolor=STD_COLOR, fgcolor=(0xD0, 0xC0, 0x30),
tooltip="Export currently selected figure.",
bind=((wx.EVT_LEFT_UP, self.onExportModel),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
),
PrepCtrl(self, wx.Button, label="Import Morph",
name="BTN_ImportMorph",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=(50, 30),
bgcolor=STD_COLOR, fgcolor=(0xD0, 0xC0, 0x30),
tooltip="Import modified figure as morph.",
bind=((wx.EVT_LEFT_UP, self.onImportModel),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
))
gbm.addrow(LabelCtrl(self, label="Figure",
fgcolor=STD_COLOR, name="LBL_FigureList"),
self.figurelist)
gbm.addrow(LabelCtrl(self, label="Morphname",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
ColoredCtrl(wx.TextCtrl(self, value=CONFIG.get("CTRL_MorphName", "Morph"),
name="CTRL_MorphName", style=wx.BORDER_SIMPLE),
bgcolor=BG_COLOR, fgcolor=TX_COLOR))
gbm.addrow(LabelCtrl(self, label="Increment Morphnon each import",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, value=CONFIG.get("CTRL_RenumberMorph", False),
name="CTRL_RenumberMorph", style=wx.NO_BORDER,
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(PrepCtrl(self, wx.Button, label="Export Path",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=btn_size,
bgcolor=STD_COLOR, fgcolor=FG_COLOR,
tooltip="Path to export current figure to.",
bind=((wx.EVT_LEFT_UP, self.onExportPath),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
),
PrepCtrl(self, wx.Button, label="Import Path",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=btn_size,
bgcolor=STD_COLOR, fgcolor=FG_COLOR,
tooltip="Path where exported figures are stored.",
bind=((wx.EVT_LEFT_UP, self.onImportPath),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
))
gbm.addrow(LabelCtrl(self, label="Use Fileselection",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
value=CONFIG.get("CTRL_SingleSelectFile", True),
name="CTRL_SingleSelectFile",
tooltip="Select file on each Import/Export",
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(LabelCtrl(self, label="Export groups",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
value=CONFIG.get("CTRL_ExportGroups", False),
name="CTRL_ExportGroups",
tooltip="Export actor groups",
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(LabelCtrl(self, label="Export UV",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
value=CONFIG.get("CTRL_ExportTexture", True),
name="CTRL_ExportTexture",
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(LabelCtrl(self, label="LBL_Scale",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
ColoredCtrl(wx.TextCtrl(self, name="CTRL_Scale",
value=str(CONFIG.get("Scale", 1)),
style=wx.SIMPLE_BORDER | wx.TE_RIGHT),
fgcolor=FG_COLOR, bgcolor=BG_COLOR))
gbm.sizer.AddGrowableCol(0)
gbm.sizer.AddGrowableCol(1)
sz = wx.BoxSizer(wx.VERTICAL)
sz.AddMany([
(gbm.sizer, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 10),
])
sz.SetSizeHints(self)
self.SetSizer(sz)
wx.CallAfter(self.updateUI)
def onSize(self, ev):
CONFIG["Perspective"] = aui.SavePaneInfo(paneInfo)
self._CONFIGCHANGED = True
ev.Skip()
def updateUI(self):
obj = self.FindWindowByName("LBL_FigureList")
if obj:
c = (0xff, 0, 0) if self.figurelist.GetCount() == 0 else STD_COLOR
obj.SetForegroundColour(c)
if self._CONFIGCHANGED:
write_config()
self._CONFIGCHANGED = False
if not STOPP_UPDATE_UI:
wx.CallLater(300, self.updateUI)
def onChoice(self, ev):
choice = ev.GetEventObject()
if choice.name == "figurelist": # type: wx.Choice
idx = choice.GetCurrentSelection()
if idx == -1:
CONFIG["figure"] = ""
else:
CONFIG["figure"] = choice.GetString(idx)
def onRadioBox(self, ev):
try:
obj = ev.GetEventObject()
CONFIG[obj.GetName()] = obj.GetValue()
except Exception:
raise
finally:
ev.Skip()
def onCheckBox(self, ev):
try:
obj = ev.GetEventObject()
CONFIG[obj.GetName()] = obj.GetValue()
except Exception:
raise
finally:
ev.Skip()
def onExportModel(self, ev):
""":type ev: wx.Event"""
try:
idx = self.figurelist.GetCurrentSelection()
if idx < 0:
ErrDialog("No figure selected.")
return
sc = wx.FindWindowByName("CTRL_Scale")
CONFIG["Scale"] = sc.GetValue()
obj = wx.FindWindowByName("CTRL_MorphName")
CONFIG["CTRL_MorphName"] = obj.GetValue()
obj = ev.GetEventObject()
obj.SetBackgroundColour((0xff, 0, 0))
obj.Update()
figurename = self.figurelist.GetString(idx)
figure = poser.Scene().Figure(figurename.strip())
do_export(figure)
obj.SetBackgroundColour(STD_COLOR)
obj.Update()
except Exception:
raise
finally:
ev.Skip()
def onImportModel(self, ev):
try:
sc = wx.FindWindowByName("CTRL_Scale")
CONFIG["Scale"] = sc.GetValue()
idx = self.figurelist.GetCurrentSelection()
if idx < 0:
ErrDialog("No figure selected.")
return
figurename = self.figurelist.GetString(idx)
figure = poser.Scene().Figure(figurename.strip())
obj = wx.FindWindowByName("CTRL_MorphName")
morphname = obj.GetValue().strip() if obj else "MORPH"
obj = ev.GetEventObject()
obj.SetBackgroundColour((0xff, 0, 0))
obj.Update()
do_import(figure, morphname)
obj.SetBackgroundColour(STD_COLOR)
check = wx.FindWindowByName("CTRL_RenumberMorph")
if check and check.GetValue():
morphname, _, nr = morphname.partition(" ")
morphname += " " + str(int(nr or 0) + 1)
obj.SetValue(morphname)
except Exception:
raise
finally:
ev.Skip()
def onExportPath(self, ev):
try:
defaultpath = CONFIG.get("ExportPath",
CONFIG.get("ImportPath",
CONFIG.get("BASEPATH", BASEPATH)))
with wx.DirDialog(self,
"Choose Export Directory", defaultpath,
style=wx.DD_DEFAULT_STYLE
) as dlg:
if dlg.ShowModal() == wx.ID_OK:
CONFIG["ExportPath"] = dlg.GetPath()
self._CONFIGCHANGED = True
except Exception:
raise
def onImportPath(self, ev):
try:
defaultpath = CONFIG.get("ImportPath",
CONFIG.get("ExportPath",
CONFIG.get("BASEPATH", BASEPATH)))
with wx.DirDialog(None,
"Choose Import Directory", defaultpath,
style=wx.DD_DEFAULT_STYLE
) as dlg:
if dlg.ShowModal() == wx.ID_OK:
CONFIG["ImportPath"] = dlg.GetPath()
except Exception:
raise
finally:
ev.Skip()
def onEnter_button(self, ev):
obj = ev.GetEventObject()
obj.SetBackgroundColour(HL_COLOR)
ev.Skip()
def onLeave_button(self, event):
obj = event.GetEventObject()
obj.SetBackgroundColour(STD_COLOR)
event.Skip()
def onClose(self, event):
global STOPP_UPDATE_UI
STOPP_UPDATE_UI = True
CONFIG["CTRL_MorphName"] = wx.FindWindowByName("CTRL_MorphName").GetValue()
CONFIG["Scale"] = wx.FindWindowByName("CTRL_Scale").GetValue()
CONFIG["Perspective"] = aui.SavePaneInfo(paneInfo)
write_config()
def updateFigurelist(self, *ev):
if poser:
while self.figurelist.GetCount():
self.figurelist.Delete(0)
for idx, fig in enumerate(poser.Scene().Figures()):
self.figurelist.Append(fig.Name())
if fig == poser.Scene().CurrentFigure():
self.figurelist.SetSelection(idx)
if __name__ == "__main__":
read_config()
aui = poser.WxAuiManager()
root = aui.GetManagedWindow()
SYS_FONT = root.GetFont()
paneInfo = aui.GetPane(APP_NAME)
if paneInfo.IsOk():
aui.ClosePane(paneInfo)
paneInfo = wx.aui.AuiPaneInfo()
paneInfo.name = APP_NAME
paneInfo.Resizable(True).FloatingSize(wx.Size(250, 280)).BestSize(wx.Size(200, 230))
paneInfo.Movable(True).DestroyOnClose(True)
paneInfo.Name(APP_NAME).Caption("+++ NEED A NAME FOR THIS +++")
savedinfo = CONFIG.get("Perspective", None)
if savedinfo is not None:
aui.LoadPaneInfo(savedinfo, paneInfo)
MainPanel = AppPanel(parent=root, name=APP_NAME)
aui.AddPane(MainPanel, paneInfo)
paneInfo.Show()
aui.Update()