ManipulateMorphs.py
from __future__ import print_function

import numpy as NP
import os
import re
import sys
from PIL import Image as IMG
from PIL import ImageDraw

try:
    import poser
except NameError:
    raise RuntimeError("Script must run in Poser.")

try:
    range = xrange
except NameError:
    pass

SCENE = poser.Scene()
ACTOR = SCENE.CurrentActor()

# Choose what you like for Pathnames: Object Name or object InternalName.
# Used by some functions to get objects name or internalname.
PATHNAME_FUNC = "Name"
# PATHNAME_FUNC = "InternalName"


def is_iterable(obj):
    return hasattr(obj, "__iter__")


def is_actor(obj):
    return isinstance(obj, poser.ActorType)


def is_parm(obj):
    return isinstance(obj, poser.ParmType)


def is_morph(obj):
    return is_parm(obj) and obj.IsMorphTarget()


def morph_empty(morph):
    return morph.NumMorphTargetDeltas() == 0 if is_morph(morph) else True


def yield_morphs(actor, with_delta=None, with_value=None):
    _filter = []
    if with_delta is not None:
        _filter.append("NumDeltas")
    if with_value is not None:
        _filter.append("Value")
    for obj in actor.Parameters():
        if is_morph(obj) and all(getattr(obj, f)() != 0 for f in _filter):
            yield obj


def __yield_pv(actor_or_geom, polytype="p"):
    """ 
    !!! Don't use directly !!! 
    Use 
        yield_poly_vertices(..) 
        or 
        yield_texpoly_vertices(..) 
    insteed. 
 
    Yields all vertices or texvertices (depending on <char>polytype) 
    in polygon-order, avoiding duplicate entries. 
 
    Usefull for building UV-maps or find borders. 
 
    :param actor_or_geom: Actor name or actor object or geometry. 
    :param polytype: "p" for polygons, "t" for texpolygons. 
 
    """
    if isinstance(actor_or_geom, basestring):
        actor_or_geom = _get_actor_(actor_or_geom)
    if isinstance(actor_or_geom, poser.ActorType):
        geom = actor_or_geom.Geometry()
    else:
        geom = actor_or_geom

    if not isinstance(geom, poser.GeomType):
        raise GeneratorExit

    if polytype.lower().startswith("p"):
        polyfunc = geom.Polygons
        sets = geom.Sets()
        vertsattr = "Vertices"
        numv = geom.NumVertices()
        getter = lambda v: (v.X(), v.Y(), v.Z())
    else:
        polyfunc = geom.TexPolygons
        sets = geom.TexSets()
        vertsattr = "TexVertices"
        numv = geom.NumTexVertices()
        getter = lambda v: (v.U(), v.V())

    if numv == 0:
        raise GeneratorExit

    seen = set()

    for poly in polyfunc():
        start_idx = poly.Start()
        for idx, vert in enumerate(getattr(poly, vertsattr)()):
            set_idx = start_idx + idx
            if set_idx not in seen:
                seen.add(set_idx)
                yield sets[set_idx], getter(vert)


def yield_poly_vertices(actor_or_geom):
    """ 
    Yields all vertices in polygon-order, avoiding duplicate entries. 
    """
    return __yield_pv(actor_or_geom, "p")


def yield_texpoly_vertices(actor_or_geom):
    """ 
    Yields all texvertices in texpolygon-order, avoiding duplicate entries. 
    """
    return __yield_pv(actor_or_geom, "t")


def _get_name_(obj):
    if obj is None:
        return None
    try:
        return getattr(obj, "Name")() \
            if PATHNAME_FUNC.startswith("N") \
            else getattr(obj, "InternalName")()
    except poser.error:
        return None


def _get_figure_(figure_name):
    """ 
    Returns poser.Scene().Figure(<str>figure_name) 
    if <str>PATHNAME_FUNC's first character is "N" (for "Name") 
    or poser.Scene().FigureByInternalName(<str>figure_name) otherwise. 
 
    :type figure_name: str, basestring 
    :rtype: poser.FigureType, None 
    """
    if isinstance(figure_name, poser.FigureType):
        return figure_name

    try:
        return SCENE.Figure(figure_name) \
            if PATHNAME_FUNC.startswith("N") \
            else SCENE.FigureByInternalName(figure_name)
    except poser.error:
        return None


def _get_actor_(actor_name, _from=SCENE):
    """ 
    Returns <_from>.Actor(<str>actor_name) 
    if <str>PATHNAME_FUNC's first character is "N" (for "Name") 
    or <_from>.ActorByInternalName(<str>actor_name) otherwise, 
    where <_from> is a callable object like SCENE or Figure. 
 
    ATTENTION: Poser may ignore numbering in actor internalnames. 
               poser.Scene().ActorByInternalName("BODY:1234") 
               returns the first body-actor found. Even if 
               more than one BODY-actors is in your scene. 
               To be sure to get the wanted BODY actor, use 
               the right figure, not SCENE for lookup. 
 
               HINT: <figure>.ParentActor() returns 
               whatever actor is declared as bodyactor. 
 
    :param actor_name: Actorname 
    :param _from: Scene or Figure to get actor from. 
 
    :type actor_name: str, basestring 
    :type _from: poser.SceneType, poser.figureType 
    :rtype: poser.ActorType, None 
    """
    if isinstance(actor_name, poser.ActorType):
        return actor_name

    try:
        return _from.Actor(actor_name) \
            if PATHNAME_FUNC.startswith("N") \
            else _from.ActorByInternalName(actor_name)
    except poser.error:
        return None


def master_ref(parameter, _masterdial="Body"):
    """ 
    Try to find and return MasterDials in <Poser.ActorType> with name 
    <str>_masterdial for given parameter. 
    Returns None if no MasterDial is found. 
 
    ATTENTION: <str>_masterdial must match <actor>.Name() or 
               <actor>.InternalName() depending on PATHNAME_FUNC. 
 
    :param parameter: Parameter to search for. 
    :param _masterdial: Actorname where masterdial is located. 
    :return: SourceParameter in body-actor. 
 
    :type parameter: poser.ParmType 
    :rtype: poser.ParmType, None 
    """
    assert isinstance(parameter, poser.ParmType)

    for vop in parameter.ValueOperations():
        if _get_name_(vop.SourceParameter().Actor()) == _masterdial:
            return vop.SourceParameter()

    return None


def clear_morph(morph, value=None):
    """ 
        Set all deltas in morphtarget to (0,0,0). 
        If value is not None, it must be an integer value 
        (morph-strength, eg. <poser-param>.Value()). 
    """
    if not morph_empty(morph):
        actor = morph.Actor()
        for idx in range(actor.Geometry().NumVertices()):
            morph.SetMorphTargetDelta(idx, 0, 0, 0)
        if isinstance(value, int):
            morph.SetValue(value)

    return morph


def create_morphtarget(actor, name, set_empty=True):
    """ 
        Creates a new morph-target if it does not exist yet. 
        Return <poser.ParmTyp> parameter found or created. 
 
        If <bool> setEmpty is True, the new (or found) morph is 
        set to all zeros. 
    """
    assert is_actor(actor)
    assert isinstance(name, basestring)

    param = actor.Parameter(name)
    if not param:
        # Create new parameter if it does not exist
        # and try to get a handle into <param>
        try:
            actor.SpawnTarget(name)
        except poser.error as err:
            print("Something wrong with SpawnTarget:")
            print("Actor:", actor.Name())
            print("Param:", name)
            raise err
        else:
            try:
                param = actor.Parameter(name)
            except Exception as err:
                print("Something wrong with actor.Parameter:")
                print("Actor:", actor.Name())
                print("Param:", name)
                raise err

    # if <bool> setEmpty is set, make sure the morph is clean
    if set_empty:
        clear_morph(param)

    return param


def get_morph(arg, actor=None):
    """ 
    Returns one or more morphtargets with name <str>arg from <poser.ActorType>actor. 
    If actor is not given (None), current actor is used. 
 
    If name is a simple string, this one target is returned (or None if not found). 
 
    If name is a regular expression, a tuple of targets is returned. 
    E.g: 
        regex = re.compile("[l|r]_morph") 
        get_morph(regex) 
        # returns <tuple("l_morph", "r_morph")> 
 
    :param arg: Poser parameter (morph) or morphname. 
    :param actor: Poser actor (only important if morph is a name and not an obj). 
    :return: Found morph or None. 
 
    :type arg: str, poser.ParmType 
    :type actor: poser.ActorType, None 
    :rtype: poser.ParmType 
    """
    if is_morph(arg):
        return arg

    if actor is None:
        actor = SCENE.CurrentActor()

    if isinstance(arg, basestring):
        if isinstance(arg, re._pattern_type):
            return (m for m in yield_morphs(actor) if arg.search(m.Name()))

        elif arg.endswith("*"):
            return (m for m in yield_morphs(actor) if m.Name().startswith(arg[:-1]))

        try:
            return actor.Parameter(arg)
        except poser.error:
            pass

    return None


def morph2numarray(morph=None):
    """ 
    Return numpy array containing raw morph-deltas (vectors). 
    :param morph: poser.ParmType aka morph. 
    :return: Numpy array. 
 
    :type morph: poser.ParmType, None 
    :rtype: NP.ndarray 
    """
    if morph_empty(morph):
        return None
    assert is_parm(morph)

    geom = morph.Actor().Geometry()
    if geom is None or geom.NumVertices() == 0:
        return None

    return NP.array([morph.MorphTargetDelta(i) for i in range(geom.NumVertices())], NP.float64)


def numarray2morph(num_ar, morph, actor=None):
    """ 
    Puts data from numpy-array into morphtarget. 
    :param num_ar: 
    :param morph: 
    :param actor: 
    :return: 
    """
    if isinstance(morph, basestring) and actor is not None:
        morph = create_morphtarget(actor, morph, True)
    assert is_morph(morph)
    numv = morph.Actor().Geometry().NumVertices()
    assert numv == len(num_ar)

    for i, v in enumerate(num_ar):
        morph.SetMorphTargetDelta(i, *v)

    return morph


def normalize_morph(morph, actor=None):
    """ 
    Scales current morph-deltas so that Value = 1.0 reflects current 
    morph state. 
 
    Useful if a morph needs a big or low value and better should 
    have a range with 0 to 1. 
    """
    actor = actor or SCENE.CurrentActor()
    if isinstance(morph, basestring):
        morph = get_morph(morph, actor or SCENE.CurrentActor())
    if morph_empty(morph):
        return

    deltas = morph2numarray(morph)
    deltas *= morph.Value()
    numarray2morph(deltas, morph)
    masterdial = master_ref(morph)
    if masterdial:
        morph.SetValue(0.0)
        masterdial.SetValue(1.0)
    else:
        morph.SetValue(1.0)


def zero_side(morph, side=False):
    """ 
    Set a morphs left or right side to zero. 
    Parameter <bool|str>side: 'left' or 'right', or False/True 
    """
    if not is_morph(morph):
        morph = get_morph(morph, SCENE.CurrentActor())
    if morph_empty(morph):
        return False

    if isinstance(side, basestring):
        side = side == "left"
    if side:
        should_be_zero = lambda a: a > 0
    else:
        should_be_zero = lambda a: a < 0

    geom = morph.Actor().Geometry()
    for idx, v in enumerate(geom.Vertices()):
        if should_be_zero(v.X()):
            morph.SetMorphTargetDelta(idx, 0, 0, 0)

    return True


def merge_morphs(targetmorph, morphlist, force_2_one=False):
    """ 
    merge_morph(<Parameter>targetmorph, <iterable>morphlist, <bool>force_2_one) 
    Merges morphs listed in morphlist into targetmorph. 
 
    <Parameter>targetmorph: Must exist and must be able to hold morph-deltas. 
    <iterable>morphlist: List, Tuple, Generator or any class with "__iter__". 
    <bool>force_2_one: If False, resulting morph will reflect exact what is 
                        currently shown on screen. If True, all morphs are 
                        forced to Value 1 (mostly not what you want). 
 
    Returns morphtarget <poser.ParmType>targetmorph. 
    """
    if not is_iterable(morphlist):
        raise TypeError("Morphlist not iterable.")

    if isinstance(targetmorph, basestring):
        targetmorph = create_morphtarget(SCENE.CurrentActor(), targetmorph)

    if not is_morph(targetmorph):
        raise TypeError("Target is not a morph.")

    targetActor = targetmorph.Actor()
    geom = targetActor.Geometry()
    deltas = NP.array([[0, 0, 0] for i in range(geom.NumVertices())], NP.float64)
    for morphparm in morphlist:
        morph = get_morph(morphparm, targetActor)
        if not is_morph(morph):
            continue

        if morph.NumMorphTargetDeltas() > 0:
            v = 1.0 if force_2_one else morph.Value()
            d = morph2numarray(morph) * v
            deltas += d

    numarray2morph(deltas, targetmorph)
    return targetmorph


def subtract_morph(targetmorph, sub):
    """ 
    subtract_morph(<morphParm|basestring>targetmorph, 
                   <morphParm|basestring>sub) 
    Subtract <morphParm>sub from <morphParm>targetmorph. 
 
    returns <morphParm>targetmorph 
    """
    if isinstance(targetmorph, basestring):
        targetmorph = get_morph(targetmorph, SCENE.CurrentActor())
    if isinstance(sub, basestring):
        sub = get_morph(targetmorph, SCENE.CurrentActor())

    assert is_morph(targetmorph), "Target must exist."
    assert is_morph(sub), "Morph to extract must exist."

    numarray2morph(targetmorph,
                   morph2numarray(targetmorph) - morph2numarray(sub)
                   )
    return targetmorph


def morph2actor_geometry(actor, morph):
    """ 
    Add morphdata from <morph> found in actor <actor> to actors 
    geometry. 
    Because geometry is changed, Poser may write the geometry out to 
    one of Posers folders. 
 
    Parameter actor: Existing Poser actor with Geometry to change. 
    Parameter morph: Existing morph-parameter or parameter-name 
                     or a numpy array with deltas. 
 
    Returns: Actor <actor> or raised exeption-object in case of an error. 
    """

    def add_array2geometry(actor, ar_data):
        try:
            geom = actor.Geometry()
            for (x, y, z), vertex in zip(ar_data, geom.Vertices()):
                vertex.SetX(vertex.X() + x)
                vertex.SetY(vertex.Y() + y)
                vertex.SetZ(vertex.Z() + z)
            actor.MarkGeomChanged()
        except Exception as err:
            return err
        return actor

    if isinstance(morph, basestring):
        morph = get_morph(morph, actor)

    if is_morph(morph):
        ar = morph2numarray(morph) * morph.Value()
    elif isinstance(morph, NP.ndarray):
        ar = morph
    else:
        raise TypeError("Morph must be {} or {}.".format(poser.ParmType, NP.ndarray))

    return add_array2geometry(actor=actor, ar_data=ar)


def create_masterdial(masterdial, morphnamelist, defaultValue=None):
    """ 
    Create a masterdial for a list of morphs in the body of a figure. 
    Morphs without deltas are ignored. 
 
    """
    figure = SCENE.CurrentFigure()

    if isinstance(masterdial, basestring):
        body_actor = figure.ParentActor()
        masterdial = body_actor.Parameter(masterdial)
        if masterdial is None:
            masterdial = body_actor.CreateValueParameter(masterdial)
    elif isinstance(masterdial, poser.ParmType):
        figure = masterdial.Actor().ItsFigure()
    else:
        raise TypeError("'Masterdial' must be {} or {}.".format(poser.ParmType, type("")))

    body_actor = figure.ParentActor()
    assert body_actor.IsBodyPart()
    assert masterdial.IsValueParameter()

    if isinstance(morphnamelist, basestring):
        # this allows one single morphname
        morphnamelist = [morphnamelist, ]

    for actor in (ac for ac in figure.Actors() if ac.IsBodyPart()):
        for morphname in (n for n in morphnamelist if actor.Parameter(n)):
            parm = actor.Parameter(morphname)
            if morph_empty(parm):
                while parm.NumValueOperations():
                    parm.DeleteValueOperation(0)
                parm.AddValueOperation(poser.kValueOpTypeCodeKEY, masterdial)
                v = parm.Value()
                if -0.0001 <= v <= 0.0001 and defaultValue is not None:
                    v = defaultValue
                vop = parm.ValueOperations()[0]
                vop.InsertKey(0, 0)
                vop.InsertKey(1, v)
                parm.SetValue(0)

    masterdial.SetValue(1)


def collect_masterparms(actorlist=None):
    if actorlist is None:
        actorlist = SCENE.Actors()

    storage = dict()
    for actor in actorlist:
        fig = actor.ItsFigure()
        figurename = _get_name_(fig) if fig else "UNIVERSE"
        d = storage.setdefault(figurename, dict())
        for parm in actor.Parameters():
            if parm.NumValueOperations():
                for vop in parm.ValueOperations():
                    sourceparm = vop.SourceParameter()
                    d.setdefault(_get_name_(sourceparm.Actor()), dict()) \
                        .setdefault(_get_name_(sourceparm), list()) \
                        .append((_get_name_(parm.Actor()), _get_name_(parm)))
        if not d:
            del storage[figurename]

    return storage


def frompath(path, storage=None):
    assert isinstance(path, basestring)
    path = path.split("/")

    if storage is None:
        if path[0] in (_get_name_(f) for f in SCENE.Figures()):
            figure = _get_figure_(path.pop(0))
            if not path:
                return figure
        else:
            figure = SCENE.CurrentFigure()

        if path[0] in (_get_name_(a) for a in figure.Actors()):
            actor = _get_actor_(path.pop(0), figure)
            if not path:
                return actor
        else:
            actor = SCENE.CurrentActor()

        if path[0] in (_get_name_(p) for p in actor.Parameters()):
            parameter = actor.Parameter(path.pop(0))
            if not path:
                return parameter
        else:
            return None

        if hasattr(parameter, path[0]):
            return getattr(parameter, path.pop(0))()
        else:
            return None

    elif isinstance(storage, dict):
        d = storage.get(path.pop(0))
        while path and d:
            d = d.get(path.pop(0))

        return d

    return None


def its_path(obj):
    """ 
    Return a path-string ("/" seperated) for given poser object. 
 
    Assume a Prop parented to a figures right hand. The props path is: 
        [figurename]/Bodyname/Hipname/.../Forearmname/Handname/Propname 
 
    This is what a 2DTriangle-prop parented to LaFemme's hand will give: 
        print its_path(SCENE.CurrentActor()) 
        "[LaFemme 1R1]/Body/Hip/Waist/Abdomen/Chest/Right Collar/" 
        +"Right Shoulder/Right Forearm/Right Hand/2DTriangle_1" 
 
    Figurenames in a path are set in brackets ("[Figurename]"). 
 
    :return: Path string or None if obj has no path. 
 
    :rtype: str, None 
    """
    ref = list()
    figure = obj.ItsFigure() if hasattr(obj, "ItsFigure") else SCENE.CurrentFigure()

    while obj:
        ref.insert(0, _get_name_(obj))
        if hasattr(obj, "Parent"):
            if _get_name_(obj.Parent()) == "UNIVERSE":
                ref.insert(0, "[%s]" % _get_name_(obj.ItsFigure()))
                break
            if _get_name_(figure) != _get_name_(obj.ItsFigure()):
                ref.insert(0, "[%s]" % _get_name_(figure))
                figure = obj.ItsFigure()
            obj = obj.Parent()
        elif hasattr(obj, "Actor"):
            obj = obj.Actor()
        else:
            break

    return "/".join(ref)


def export_UVmap(actorname, imgname, imagesize=(2048, 2048), matlist=None):
    actor = SCENE.CurrentActor() if actorname is None else _get_actor_(actorname)
    geom = actor.Geometry()
    polys = geom.Polygons()
    tverts = geom.TexVertices()
    tpolys = geom.TexPolygons()
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)

    if not len(tverts):
        return None

    img = IMG.new("RGB", imagesize, (255, 255, 255))
    draw = ImageDraw.Draw(img)
    width, height = imagesize

    def mapped_poly(uv_verts, width=width, height=width):
        return map(lambda uv: (int((uv[0] % 1.0) * width), int(height - uv[1] * height)), uv_verts)

    for idx, tpoly in enumerate(tpolys):
        if matlist and not polys[idx].MaterialName() in matlist:
            continue
        draw.polygon(mapped_poly((v.U(), v.V()) for v in tpoly.TexVertices()),
                     fill=WHITE, outline=BLACK)

    del draw
    if not os.path.dirname(imgname) == imgname:
        imgname = os.path.dirname(sys.argv[0]) + os.sep + imgname
    print(os.path.abspath(imgname))
    img.save(imgname)
    try:
        img.close()
    except:
        pass


def create_morph_from_UV(morph, imagefile, grayscale_factor=1.0 / 300000):
    """ 
    Creates a morph based on a bitmap. 
 
    Bitmap is converted to grayscale before used. 
    Each pixel from image at UV-Vertex-Position is divided by 
    <float>grayscale_factor (actually delta offset or morph intensity). 
 
    :param morph: str|morph-param 
    :param imagefile: str 
    :param reverse: bool 
    :param grayscale_factor: float 
    :return: morph 
    """

    if isinstance(imagefile, IMG.Image):
        image = imagefile
    else:
        assert os.path.isfile(imagefile)
        image = IMG.open(imagefile).convert("L")

    width, height = image.size[0] - 1, image.size[1] - 1

    if isinstance(morph, basestring):
        morph = get_morph(morph) or create_morphtarget(SCENE.CurrentActor(), morph, False)

    assert is_morph(morph)
    clear_morph(morph)

    actor = morph.Actor()
    geom = actor.Geometry()
    nrmls = geom.Normals()
    sets = geom.Sets()

    seen = set()

    for tpoly in geom.TexPolygons():
        start_idx = tpoly.Start()
        for idx, tp in enumerate(tpoly.TexVertices()):
            vertex_idx = sets[start_idx + idx]
            if vertex_idx not in seen:
                seen.add(vertex_idx)
                c = image.getpixel(((width * tp.U()) % width,
                                    (height - height * tp.V()) % height)
                                   ) * grayscale_factor
                n = nrmls[vertex_idx]
                morph.SetMorphTargetDelta(vertex_idx,
                                          c * n.X(), c * n.Y(), c * n.Z())
    del image
    return morph


morph_from_image = create_morph_from_UV


def create_weightmap_from_UV(actorname,
                             weightmapname,
                             imagefile,
                             poserfile,
                             grayscale_factor=1.0 / 300000):
    """ 
    Creates a weightmap based on a bitmap. 
 
    Bitmap is converted to grayscale before used. 
    Each pixel from image at UV-Vertex-Position is divided by 
    <float>grayscale_factor (actually delta offset or morph intensity). 
 
    :param actorname: str, poser.ActorType 
    :param weightmapname: str, weightmap name 
    :param imagefile: str 
    :param reverse: bool 
    :param grayscale_factor: float 
    :return: morph 
    """
    __poserheader__ = """{ 
    version 
        { 
        number 10 
        } 
    actor %s 
        { 
        weightMap %s 
            { 
""" % (actorname, weightmapname)

    if isinstance(imagefile, IMG.Image):
        image = imagefile
    else:
        assert os.path.isfile(imagefile)
        image = IMG.open(imagefile).convert("L")

    width, height = image.size[0] - 1, image.size[1] - 1

    actor = _get_actor_(actorname)
    geom = actor.Geometry()
    sets = geom.Sets()
    seen = set()
    wm_ar = list()

    for tpoly in geom.TexPolygons():
        start_idx = tpoly.Start()
        for idx, tp in enumerate(tpoly.TexVertices()):
            vertex_idx = sets[start_idx + idx]
            if vertex_idx not in seen:
                seen.add(vertex_idx)
                wm_ar.append(image.getpixel(((width * tp.U()) % width,
                                             (height - height * tp.V()) % height)
                                            ) * grayscale_factor
                             )

    with open(poserfile, "w") as fh:
        print(__poserheader__, file=fh)
        print("\t\t\tnumbVerts\t", geom.NumVertices(), file=fh)
        for idx, v in wm_ar:
            print("\t\t\tv {} {}".format(idx, v))
        print("\t\t\n}"
              "\t\n}"
              "}", file=fh)

        del image


weightmap_from_image = create_weightmap_from_UV

# ****************************************************************************
# wx routines
# ****************************************************************************

import wx
from wx.lib.scrolledpanel import ScrolledPanel


class _SelectDialog(wx.Dialog):
    """ 
    Used by function SelectDialog([...]) 
 
    Set up a dialog to choose or check from a list of items. 
    Parameters: 
        select_ctrl: wx.CheckBox or wx.RadioButton (choose or check). 
        labels: List of <str> labels to choose from. 
        header: Text displayed above message. 
        footer: Text displayed below message. 
    """

    def __init__(self, select_ctrl=wx.CheckBox, *args, **kwargs):
        title = kwargs.pop("title", kwargs.pop("Title", "Select Dialog"))
        self.labels = kwargs.pop("labels", kwargs.pop("Labels", list()))
        self.header = kwargs.pop("header", kwargs.pop("Header", None))
        self.footer = kwargs.pop("footer", kwargs.pop("Footer", None))

        # List of generated controls (checkboxes or radiobuttons)
        self.ctrls = []
        # Type of controls used (check/radio)
        self.select_ctrl = select_ctrl
        # Close dialog first time a selection changed (click)?
        # (Makes sense for Radiobuttons)
        self.closeOnClick = kwargs.pop("closeOnClick", False)

        wx.Dialog.__init__(self, *args, title=title, **kwargs)
        self.InitUI()

    def InitUI(self):
        sizer = wx.BoxSizer(wx.VERTICAL)
        btn_sizer = wx.StdDialogButtonSizer()
        if not self.closeOnClick:
            # No OK button needed if we close automatically.
            btn_ok = wx.Button(self, id=wx.ID_OK)
            btn_ok.SetDefault()
            btn_sizer.AddButton(btn_ok)

        btn_close = wx.Button(self, id=wx.ID_CANCEL)
        btn_close.SetDefault()
        btn_sizer.AddButton(btn_close)
        btn_sizer.Realize()

        font = self.GetFont()
        if self.header is not None:
            if isinstance(self.header, wx.Panel):
                # if header is a wx.Window, add it as is to sizer.
                h = self.header
                h.SetFont(font)
            elif isinstance(self.header, basestring):
                # Some Text displayed above of selection-list.
                h = wx.StaticText(self, label=self.header)
                h.SetFont(font)
                h.SetFont(font.Bold())
            else:
                raise TypeError("'Header'")

            sizer.Add((1, 10), 0)
            sizer.Add(h, 0, wx.ALIGN_CENTER_HORIZONTAL)
            sizer.Add((1, 16), 0)

        # Get a scrolled panel to be able to display long lists too.
        panel = ScrolledPanel(self, size=self.GetSize(), style=wx.SIMPLE_BORDER)
        panel.SetupScrolling()

        ctrl_sizer = wx.BoxSizer(wx.VERTICAL)
        for idx, entry in enumerate(self.labels):
            # Add the labels we need to show.
            # Each entry in self.labels is a tuple
            # of (<string label>, <bool status>, <bool flag>).
            if len(entry) == 3:
                label, state, disabled = entry
                state = bool(state) if state is not None else False
            else:
                label = entry
                state, disabled = True, False
            self.ctrls.append(self.select_ctrl(panel, label=label))
            self.ctrls[idx].index = idx
            self.ctrls[idx].state = state
            self.ctrls[idx].Enable(not disabled)
            self.ctrls[idx].SetValue(state)

            ctrl_sizer.Add(self.ctrls[idx], 0, wx.ALIGN_LEFT | wx.EXPAND)

        # Add the sizer containing the selection-controls to
        # the scrollable panel.
        panel.SetSizer(ctrl_sizer)

        if len(self.ctrls) and not isinstance(self.ctrls[0], wx.RadioButton):
            # If we have a list of checkboxes, we need to have
            # a "Select All" and "Deselect All" Button.
            btn_sizer2 = wx.BoxSizer(wx.HORIZONTAL)
            font = self.GetFont()
            font.SetPointSize(8)

            for (_label, _id) in (("Select All", wx.ID_SELECTALL),
                                  ("Deselect All", wx.NewId())):
                btn = wx.Button(self, id=_id, label=_label)
                btn.SetFont(font)
                btn.Bind(wx.EVT_BUTTON, self.onSelectButton)
                btn_sizer2.Add(btn, 0, wx.ALIGN_RIGHT, 10)

            sizer.Add(btn_sizer2, 0, wx.ALIGN_RIGHT)

        sizer.Add(panel, 1, wx.EXPAND)

        if self.footer is not None:
            if isinstance(self.footer, wx.Panel):
                h = self.footer
                h.SetFont(font)
            else:
                # Some Text displayed below of selection-list.
                h = wx.StaticText(self, label=self.footer)
                h.SetFont(font)
                h.SetFont(font.Bold())

            sizer.Add((1, 16), 0)
            sizer.Add(h, 0, wx.ALIGN_CENTER_HORIZONTAL)
            sizer.Add((1, 16), 0)

        # Add OK and Cancel buttons last.
        sizer.Add(btn_sizer, 0, wx.EXPAND | wx.ALIGN_RIGHT)
        self.SetSizer(sizer)
        sizer.Fit(self)

        if self.closeOnClick:
            # Make sure we get informed if a control is clicked.
            self.Bind(wx.EVT_RADIOBUTTON, self.onClick)

    def onClick(self, ev):
        # Send EndModal-Event after the current event
        # is completly processed (to avoid a crash :) ).
        wx.CallAfter(self.EndModal(wx.ID_OK))

    def onSelectButton(self, ev):
        obj = ev.GetEventObject()
        for ctrl in self.ctrls:
            ctrl.SetValue(obj.GetId() == wx.ID_SELECTALL)


def SelectDialog(labels, **kwargs):
    """ 
    Display a Selection-Dialog. 
    :param labels: List of Strings to display in selectionlist. 
    :param kwargs: <dict> with keywords/values: 
                    control: <wx.CheckBox> or <wx.RadioButton> (choose or check) 
                    closeOnClick: <bool>, close dialog after an item is clicked. 
 
    :return: List of <str>item-names for each selected item from <list>labels. 
 
    E.g.: 
        sel = SelectDialog(["AAA", "BBB", "CCC"], wx.RadioButton, True) 
        print("Result:", sel) 
        # Returned list is type <namedtuple> with each item as (<str>Name, <int>Value). 
        print("Names only:", [s.Name for s in sel]) 
    """
    assert is_iterable(labels)
    control = kwargs.pop("control", None)
    if control is None:
        control = wx.CheckBox
    if callable(control):
        control = control.__class__
    if isinstance(control, str):
        if control.lower().startswith("radio"):
            control = wx.RadioButton
        elif control.lower().startswith("check"):
            control = wx.CheckBox

    if control is wx.RadioButton and not "closeOnClick" in kwargs:
        # Make "closeOnClick" default True.
        kwargs["closeOnClick"] = True

    man = poser.WxAuiManager()
    root = man.GetManagedWindow()

    style = kwargs.pop("style", 0)
    with _SelectDialog(parent=root,
                       select_ctrl=control,
                       style=wx.DEFAULT_DIALOG_STYLE | style,
                       labels=labels, **kwargs) as dlg:

        res = dlg.ShowModal()
        if res == wx.ID_OK:
            # Return a list of named tuples (<str label>, <int value>)
            # with each list item selected. List may be empty.
            return [labels[i] for i, c in enumerate(dlg.ctrls) if
                    c.GetValue() != 0]

    return None


def wx_combine_morphs(targetmorph, whole_figure=False):
    if isinstance(targetmorph, basestring):
        targetmorph = create_morphtarget(SCENE.CurrentActor(), targetmorph, True)
    else:
        assert is_morph(targetmorph)

    targetmorph_name = targetmorph.Name()
    targetactor = targetmorph.Actor()
    selected_morphnames = SelectDialog(
            sorted(m.Name() for m in yield_morphs(targetactor)),
            control="check",
            title="Combine: Morph Selection",
            header="Select some morphs to combine in Actor {}.".format(targetactor.Name()),
            footer="(Only morphs with Deltas are shown)"
    )
    if not selected_morphnames:
        return

    if not whole_figure:
        merge_morphs(targetmorph, selected_morphnames)

    else:
        global valueparm_actors
        figure = targetactor.ItsFigure()
        actor_list = set()

        for actor in (a for a in figure.Actors() if a.IsBodyPart()):
            if actor.InternalName().startswith("BODY"):
                continue
            hitlist = [n for n in selected_morphnames if actor.Parameter(n)]
            if len(hitlist):
                # we are here because at least one morph to combine is in hitlist.
                merge_morphs(create_morphtarget(actor, targetmorph_name), hitlist)
                actor_list.add(actor)

        # To avoid problems we set morph-values after all morphs are combined.

        body_actor = figure.Actor("Body")
        body_parm = body_actor.Parameter(targetmorph_name)
        if body_parm is None:
            body_parm = body_actor.CreateValueParameter(targetmorph_name)
        body_parm.SetValue(1)

        for actor in actor_list:
            for name in [n for n in selected_morphnames if actor.Parameter(n)]:
                parm = actor.Parameter(name)
                if parm:
                    for vop in parm.ValueOperations() or []:
                        vop.SourceParameter().SetValue(0)

            # Add new created morph to master-dial.
            parm = actor.Parameter(targetmorph_name)
            parm.SetValue(0)
            parm.AddValueOperation(poser.kValueOpTypeCodeDELTAADD, body_parm)