FullbodyMorphs_wx4.py
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
from shutil import copyfile

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, caption=err, message=msg, style=wx.ICON_ERROR)
    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 find_parms_with_name(parmname):
    """ 
    Return dict with actornames as key an parameter as content. 
    :param parmname: 
    :return: 
    """
    assert isinstance(parmname, basestring)
    actor_dict = dict()
    for actor in poser.Scene().Actors():
        ac_name = actor.Name()
        for parm in actor.Parameters():
            if parm.Name() == parmname:
                actor_dict[ac_name] = parm

    return actor_dict


def write_matfile(filename, materials):
    """ 
    Write out a simple material-file. 
    """

    def move_image(map2move):
        fname = os.path.join(CONFIG.get("MatPath", "."), os.path.basename(map2move))
        if os.path.exists(fname):
            if os.path.getmtime(map2move) == os.path.getmtime(fname):
                copyfile(map2move, fname)
        else:
            copyfile(map2move, fname)
        return fname

    try:
        tmp = open(filename, "w")
        tmp.close()
    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():
                texmap = mat.TextureMapFileName()
                if CONFIG.get("CTRL_MoveTextures", False):
                    texmap = move_image(texmap)
                print("map_Kd", texmap, file=mfh)
            if mat.BumpMapFileName():
                texmap = mat.BumpMapFileName()
                if CONFIG.get("CTRL_MoveTextures", False):
                    texmap = move_image(texmap)
                print("map_Bump", texmap, file=mfh)


def collect_geometry(figure):
    if figure is None:
        return None

    def np_vertex(v):
        return 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). 
    """
    vertices = list()
    try:
        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
    except IndexError:
        return ErrDialog("Vertex Error.",
                         "Vertices in file '%filename' corrupted.")
    except IOError:
        return ErrDialog("File Error.",
                         "File '{}' does not exist or is not accessible.".
                         format(filename))

    return NP.array(vertices, NP_PRECISION)


def do_export(figure, onFinish=None):
    """ 
    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", 1))

    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"] = os.path.dirname(dlg.GetPath())
            filename = dlg.GetFilename()
    else:
        filename = os.path.join(CONFIG["ExportPath"], "{}-{}.obj".format(figurename, morphname))

    try:
        tmp = open(filename, "w")
        tmp.close()
    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)

        vertex_crosslist = NP.zeros(len(vertices), NP.int32)
        not_empty_idx = 0
        for idx, vertex in enumerate(vertices):
            if vertex[0] != 0.0 and vertex[1] != 0.0 and vertex[2] != 0.0:
                print("v {} {} {}".format(*vertex), file=fh)
                vertex_crosslist[idx] = not_empty_idx
                not_empty_idx += 1

        if use_material:
            for idx, tvert in enumerate(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)
            # experimental --- define smoothgroup
            print("s", figurename, file=fh)

        polys = geom.Polygons()
        tpolys = geom.TexPolygons()
        sets = geom.Sets()
        for idx in range(len(sets)):
            sets[idx] = vertex_crosslist[sets[idx]]
        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)
                    # experimental --- define smoothgroup
                    print("s", ", ".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
    import pickle
    with open(filename + ".cross", "wb") as fh:
        pickle.dump(vertex_crosslist, fh)

    if onFinish is not None:
        onFinish()
    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["ImportPath"] = os.path.dirname(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", 1))
    del verts_new
    del verts_old

    import pickle
    with open(old_filename + ".cross", "rb") as fh:
        verts_crosslist = pickle.load(fh)

    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])
            x, y, z = map(lambda a: round(a, PRECISION),
                          vertices[verts_crosslist[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.setdefault("CTRL_MorphName", "Morph"),
                                           name="CTRL_MorphName", style=wx.BORDER_SIMPLE),
                               bgcolor=BG_COLOR, fgcolor=TX_COLOR))

        gbm.addrow(LabelCtrl(self, label="Increment Morph\non each import",
                             fgcolor=STD_COLOR, bgcolor=BG_COLOR),
                   PrepCtrl(self, wx.CheckBox, value=CONFIG.setdefault("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.setdefault("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.setdefault("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.setdefault("CTRL_ExportTexture", True),
                            name="CTRL_ExportTexture",
                            bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))

        gbm.addrow(LabelCtrl(self, label="Move Texturefiles",
                             fgcolor=STD_COLOR, bgcolor=BG_COLOR),
                   PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
                            value=CONFIG.setdefault("CTRL_MoveTextures", True),
                            name="CTRL_MoveTextures",
                            bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))

        gbm.addrow(LabelCtrl(self, label="Scale",
                             fgcolor=STD_COLOR, bgcolor=BG_COLOR),
                   ColoredCtrl(wx.TextCtrl(self, name="CTRL_Scale",
                                           value=str(CONFIG.setdefault("Scale", 1)),
                                           style=wx.SIMPLE_BORDER | wx.TE_RIGHT),
                               fgcolor=FG_COLOR, bgcolor=BG_COLOR))

        gbm.addrow(LabelCtrl(self, label="Edit Light",
                             fgcolor=STD_COLOR, bgcolor=BG_COLOR),
                   PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
                            value=False,
                            name="CTRL_EditLight",
                            bind=((wx.EVT_CHECKBOX, self.onEditLight),)))

        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.GetName() == "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 onEditLight(self, ev):
        try:
            light = poser.Scene().Actor("Edit Light")
        except poser.error:
            pass
        else:
            light.SetLightOn(ev.GetEventObject().Value)
            poser.Scene().DrawAll()

    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"""

        def reset_color():
            obj.SetBackgroundColour(STD_COLOR)
            obj.Update()

        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, onFinish=None)
            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())
            morph_ctrl = wx.FindWindowByName("CTRL_MorphName")
            morphname = morph_ctrl.GetValue().strip() if morph_ctrl 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)
                morph_ctrl.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()
        if obj.BackgroundColour == STD_COLOR:
            obj.SetBackgroundColour(HL_COLOR)
        ev.Skip()

    def onLeave_button(self, event):
        obj = event.GetEventObject()
        if obj.BackgroundColour == HL_COLOR:
            obj.SetBackgroundColour(STD_COLOR)
        event.Skip()

    def onClose(self, event):
        global STOPP_UPDATE_UI
        STOPP_UPDATE_UI = True

        CONFIG["Perspective"] = aui.SavePaneInfo(paneInfo)
        CONFIG["CTRL_MorphName"] = wx.FindWindowByName(
                "CTRL_MorphName").GetValue()
        CONFIG["Scale"] = wx.FindWindowByName("CTRL_Scale").GetValue()

        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()
    savedinfo = CONFIG.get("Perspective", None)
    if savedinfo is not None:
        aui.LoadPaneInfo(savedinfo, paneInfo)
    else:
        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("   PoMo Bridge +++ 0.98a  ")

    MainPanel = AppPanel(parent=root, name=APP_NAME)
    aui.AddPane(MainPanel, paneInfo)
    paneInfo.Show()
    aui.Update()