Useful Python Script



  • This script is made to be used with 'Python-Script-Buttons' and has two modes:

    1. Fix selection on current figure
    2. Fix selection on current actor.

    If 1) is choosen, one can not select actors if they are not part of the current figure or parented to this figure.
    If 2) is choosen, it is not possible to select another actor at all.

    I made this while working on weightmaps and morphs. It helps a lot. Also with posing.

    There is one main script that does the work: FixPoserActor.py

    The other scripts (FixPoserActor_1.py, FixPoserActor_2.py, FixPoserActor_3.py, FixPoserActor_4.py) are needed to support the script buttons and have the same content; just the button-definition is different.

    It doesn't matter witch script is started, all scripts will load the main script if needed. Best methode is to make a sub directory for the files. Then just run one of the scripts (don't forget to activate Script-Menu).

    Save as FixPoserActor.py:

    import os
    import sys
    import re
    
    try:
        import poser
    except ImportError:
        raise RuntimeError("Must be run in Poser")
    
    DEBUG = False
    SCENE = poser.Scene()
    
    
    def global_const(const_str, start_idx=-1):
        start_idx = int(start_idx)
        assert isinstance(const_str, (unicode, str))
    
        flag = start_idx < 0
        if flag:
            start_idx = globals().setdefault("__const_idx__", 0)
    
        for i, s in enumerate(re.split("[\s,]+", const_str)):
            assert re.match("[a-z]", s[0], re.IGNORECASE), "Constants must start with a letter: %s" % s
    
            globals()[s] = i + start_idx
            if flag:
                globals()["__const_idx__"] = i + start_idx
    
    
    def resolve_str(ss):
        if isinstance(ss, (list, tuple)):
            return [resolve_str(p) for p in ss]
    
        if isinstance(ss, dict):
            d = dict()
            for k, v in ss.items():
                d[k] = resolve_str(v)
            return str(d)
    
        try:
            return str(ss.Name())
        except Exception as err:
            return str(ss)
    
    
    def debug(*debug_strings):
        if not DEBUG:
            return
    
        for s in debug_strings:
            print resolve_str(s),
        print
    
    
    global_const("NO_MODE, FIGURE_MODE, ACTOR_MODE", 0)
    
    
    def _cb_(iScene, iEventType):
        if (iEventType & poser.kEventCodeACTORSELECTIONCHANGED) != 0:
            cb_data = globals().get("__cb_data__")
            if not cb_data:
                return
    
            mode = int(cb_data.get("mode", NO_MODE))
            if mode == ACTOR_MODE:
                cb_actor, active = cb_data.setdefault("cb_actor", (None, False))
                if cb_actor and active:
                    SCENE.SelectActor(cb_actor)
    
            elif mode == FIGURE_MODE:
                cb_figure, active = cb_data.setdefault("cb_figure", (None, False))
                if cb_figure and active:
                    ac = SCENE.CurrentActor()
                    if ac.ItsFigure() != cb_figure:
                        cb_actor, active = cb_data.setdefault("cb_actor", (None, False))
                        if cb_actor and active:
                            SCENE.SelectActor(cb_actor)
                    else:
                        cb_data["cb_actor"] = (ac, True)
    
            elif mode != NO_MODE:
                debug("Mode not in range:", mode)
    
    
    def set_figureMode(figure=None, actor=None):
        cb_data = globals().setdefault("__cb_data__", dict())
        if figure is None:
            figure = SCENE.CurrentFigure()
            if figure is None:
                cb_data["mode"] = NO_MODE
                cb_data["cb_figure"] = (None, False)
                return
    
        if isinstance(actor, (unicode, str)):
            actor = SCENE.ActorByInternalName(actor)
    
        if actor is None:
            actor = SCENE.CurrentActor()
    
        if actor.ItsFigure() != figure:
            actor = SCENE.ActorByInternalName("BODY")
    
        if not isinstance(actor, poser.ActorType):
            SCENE.ClearEventCallback()
            globals()["__cb_data__"] = dict()
            raise RuntimeError("Actor unknown: %s" % str(actor))
    
        assert isinstance(figure, poser.FigureType)
        assert isinstance(actor, poser.ActorType)
    
        cb_data["mode"] = FIGURE_MODE
        cb_data["cb_figure"] = (figure, True)
        cb_data["cb_actor"] = (actor, True)
        SCENE.SetEventCallback(_cb_)
        debug("Figuremode set:\n", cb_data)
    
    
    def set_actorMode(actor=None):
        cb_data = globals().setdefault("__cb_data__", dict())
        if actor is None:
            actor = SCENE.CurrentActor()
            if actor is None:
                cb_data["mode"] = NO_MODE
                debug("No actor to set.")
                return
    
        cb_data["mode"] = ACTOR_MODE
        cb_data["cb_actor"] = (actor, True)
        cb_data["cb_figure"] = (actor.ItsFigure(), True)
        SCENE.SetEventCallback(_cb_)
        debug("Actormode set:\n", cb_data)
    
    
    def clearmode():
        SCENE.ClearEventCallback()
        globals()["__cb_data__"] = dict(mode=NO_MODE,
                                        cb_figure=(None, False),
                                        cb_actor=(None, False))
        debug("Mode cleared.")
    
    
    def setmode(mode):
        if mode not in range(3):
            raise SyntaxError("Mode must be a value: 0, 1 or 2.\n" +
                              "Try global names NO_MODE (0), FIGURE_MODE (1), ACTOR_MODE (2)")
    
        (clearmode, set_figureMode, set_actorMode)[mode]()
    
    
    path = os.path.abspath(os.path.dirname(sys.argv[0]))
    clearmode()
    
    poser.DefineScriptButton(1, os.path.join(path, "FixPoserActor_1.py"), "Fixed Selection: Figure")
    poser.DefineScriptButton(2, os.path.join(path, "FixPoserActor_2.py"), "Fixed Selection: Actor")
    poser.DefineScriptButton(3, os.path.join(path, "FixPoserActor_3.py"), "Free Selection")
    poser.DefineScriptButton(4, os.path.join(path, "FixPoserActor_4.py"), "Debug on/off")
    poser.DefineScriptButton(5, "", "")
    poser.DefineScriptButton(6, "", "")
    poser.DefineScriptButton(7, "", "")
    poser.DefineScriptButton(8, "", "")
    poser.DefineScriptButton(9, "", "")
    try:
        poser.DefineScriptButton(10, mainButtonsPath, "Back")
    except NameError:
        poser.DefineScriptButton(10, "", "")
    

    Save as FixPoserActor_1.py:

    try:
        DEBUG
        _cb_
        setmode
    except:
        import sys, os
        execfile(os.path.join(os.path.dirname(sys.argv[0]), "FixPoserActor.py"))
    
    setmode(FIGURE_MODE)
    

    Save as FixPoserActor_2.py:

    try:
        DEBUG
        _cb_
        setmode
    except:
        import sys, os
        execfile(os.path.join(os.path.dirname(sys.argv[0]), "FixPoserActor.py"))
    
    setmode(ACTOR_MODE)
    

    Save as FixPoserActor_3.py:

    try:
        DEBUG
        _cb_
        setmode
    except:
        import sys, os
        execfile(os.path.join(os.path.dirname(sys.argv[0]), "FixPoserActor.py"))
    
    setmode(NO_MODE)
    

    Save as FixPoserActor_4.py:

    try:
        DEBUG
        _cb_
        setmode
    except:
        import sys, os
        execfile(os.path.join(os.path.dirname(sys.argv[0]), "FixPoserActor.py"))
    
    DEBUG = not DEBUG
    print "DEBUG is now", ["off", "on"][DEBUG]
    


  • Interesting, and looks very useful.
    Had to scan the code to find there is a third choice: 0, which clears the locks.



  • @F_Verbaas
    Clear locks is included (number 3, 'NO_MODE').



  • @adp
    Sorry was confused by the error message:
    raise SyntaxError("Mode must be a value: 0, 1 or 2.\n" +
    "Try global names NO_MODE (0), FIGURE_MODE (1), ACTOR_MODE (2)")



  • @adp being self-taught (well, not if I include all the book and helpful web reference material I've avidly inhaled on the subject over the years) in Python, and only for hobby, not professional use, but having come from a software engineering background, I'm always delighted to find useful instances of techniques I've yet to try, such as accessing globals(). It's also instructive to pick apart others' variable nomenclature quirks and speculate where they've come from :-)

    I was looking for something similar recently, when trying to use Poser Pro 11's measurement tools, only to be desperately frustrated by their inability to be applied to arbitrary locations (not identified by a vertex), and propensity for locking onto vertices on the environment sphere at virtual infinity, rather than the object I wanted to select (a camera, whose vertices can't be selected at all!). Perhaps your scripts will be a useful starting point for that kind of scenario. I ended up having to just hide every single actor in the line of sight which wasn't what I wanted to measure, and parent a prop to the camera to get the result I was after.


  • Poser Ambassadors

    @adp said in Useful Python Script:

    FixPoserActor_4.py

    Thanks, looks very useful.



  • I made an extension to hide/unhide anything but the selected actor or figure.
    Hidden status of actors are preserved, so that after 'Unhide' all actors are visible or not as they where before.

    Here is the new FixPoserActor.py:

    import os
    import sys
    import re
    from types import NoneType
    
    try:
        import poser
    except ImportError:
        raise RuntimeError("Must be run in Poser")
    
    DEBUG = False
    SCENE = poser.Scene()
    
    
    def global_const(const_str, start_idx=-1):
        start_idx = int(start_idx)
        assert isinstance(const_str, (unicode, str))
    
        flag = start_idx < 0
        if flag:
            start_idx = globals().setdefault("__const_idx__", 0)
    
        for i, s in enumerate(re.split("[\s,]+", const_str)):
            assert re.match("[a-z]", s[0], re.IGNORECASE), "Constants must start with a letter: %s" % s
    
            globals()[s] = i + start_idx
            if flag:
                globals()["__const_idx__"] = i + start_idx
    
    
    def resolve_str(ss):
        if isinstance(ss, (list, tuple)):
            return [resolve_str(p) for p in ss]
    
        if isinstance(ss, dict):
            d = dict()
            for k, v in ss.items():
                d[k] = resolve_str(v)
            return str(d)
    
        try:
            return str(ss.Name())
        except Exception as err:
            return str(ss)
    
    
    def debug(*debug_strings):
        if not DEBUG:
            return
    
        for s in debug_strings:
            print resolve_str(s),
        print
    
    
    global_const("NO_MODE, FIGURE_MODE, ACTOR_MODE", 0)
    
    
    def _cb_(iScene, iEventType):
        if (iEventType & poser.kEventCodeACTORSELECTIONCHANGED) != 0:
            cb_data = globals().get("__cb_data__")
            if not cb_data:
                return
    
            mode = int(cb_data.get("mode", NO_MODE))
            if mode == ACTOR_MODE:
                cb_actor, active = cb_data.setdefault("cb_actor", (None, False))
                if cb_actor and active:
                    SCENE.SelectActor(cb_actor)
    
            elif mode == FIGURE_MODE:
                cb_figure, active = cb_data.setdefault("cb_figure", (None, False))
                if cb_figure and active:
                    ac = SCENE.CurrentActor()
                    if ac.ItsFigure() != cb_figure:
                        cb_actor, active = cb_data.setdefault("cb_actor", (None, False))
                        if cb_actor and active:
                            SCENE.SelectActor(cb_actor)
                    else:
                        cb_data["cb_actor"] = (ac, True)
    
            elif mode != NO_MODE:
                debug("Mode not in range:", mode)
    
    
    def set_figureMode(figure=None, actor=None):
        cb_data = globals().setdefault("__cb_data__", dict())
        if figure is None:
            figure = SCENE.CurrentFigure()
            if figure is None:
                cb_data["mode"] = NO_MODE
                cb_data["cb_figure"] = (None, False)
                return
    
        if isinstance(actor, (unicode, str)):
            actor = SCENE.ActorByInternalName(actor)
    
        if actor is None:
            actor = SCENE.CurrentActor()
    
        if actor.ItsFigure() != figure:
            actor = SCENE.ActorByInternalName("BODY")
    
        if not isinstance(actor, poser.ActorType):
            SCENE.ClearEventCallback()
            globals()["__cb_data__"] = dict()
            raise RuntimeError("Actor unknown: %s" % str(actor))
    
        assert isinstance(figure, poser.FigureType)
        assert isinstance(actor, poser.ActorType)
    
        cb_data["mode"] = FIGURE_MODE
        cb_data["cb_figure"] = (figure, True)
        cb_data["cb_actor"] = (actor, True)
        SCENE.SetEventCallback(_cb_)
        debug("Figuremode set:\n", cb_data)
    
    
    def set_actorMode(actor=None):
        cb_data = globals().setdefault("__cb_data__", dict())
        if actor is None:
            actor = SCENE.CurrentActor()
            if actor is None:
                cb_data["mode"] = NO_MODE
                debug("No actor to set.")
                return
    
        cb_data["mode"] = ACTOR_MODE
        cb_data["cb_actor"] = (actor, True)
        cb_data["cb_figure"] = (actor.ItsFigure(), True)
        SCENE.SetEventCallback(_cb_)
        debug("Actormode set:\n", cb_data)
    
    
    def clearmode():
        SCENE.ClearEventCallback()
        globals()["__cb_data__"] = dict(mode=NO_MODE,
                                        cb_figure=(None, False),
                                        cb_actor=(None, False))
        debug("Mode cleared.")
    
    
    def setmode(mode):
        if mode not in range(3):
            raise SyntaxError("Mode must be a value: 0, 1 or 2.\n" +
                              "Try global names NO_MODE (0), FIGURE_MODE (1), ACTOR_MODE (2)")
    
        (clearmode, set_figureMode, set_actorMode)[mode]()
    
    
    def hideUnhide(ac_or_fig_tohide=None):
        assert isinstance(ac_or_fig_tohide, (poser.ActorType, poser.FigureType, NoneType))
        cb_data = globals().setdefault("__cb_data__", dict())
        hidden = cb_data.setdefault("hidden", list())
    
        if ac_or_fig_tohide is None:
            idx = 0
            while hidden:
                idx += 1
                ac, vis = hidden.pop()
                ac.SetVisible(vis)
            debug(idx, "Actors set to visible.")
        else:
            while hidden:
                debug("Restoring", len(hidden), "actors.")
                ac, vis = hidden.pop()
                ac.SetVisible(vis)
    
            if isinstance(ac_or_fig_tohide, poser.ActorType):
                debug("Hiding actors")
                for ac in SCENE.Actors():
                    if ac == ac_or_fig_tohide:
                        continue
                    geom = ac.Geometry() # we assume an actor to hide/unhide has a geometry
                    if hasattr(geom, "NumVertices") and geom.NumVertices() > 0:
                        hidden.append((ac, ac.Visible()))
                        ac.SetVisible(0)
            elif isinstance(ac_or_fig_tohide, poser.FigureType):
                debug("Hiding figures")
                for ac in ac_or_fig_tohide.Actors():
                    if not ac.IsBodyPart():
                        continue
    
                    hidden.append((ac, ac.Visible()))
                    ac.SetVisible(0)
    
            debug("Actors hidden:", len(hidden))
            cb_data["hidden"] = hidden
    
        SCENE.DrawAll()
    
    
    path = os.path.abspath(os.path.dirname(sys.argv[0]))
    clearmode()
    
    poser.DefineScriptButton(1, os.path.join(path, "FixPoserActor_1.py"), "Fixed Selection: Figure")
    poser.DefineScriptButton(2, os.path.join(path, "FixPoserActor_2.py"), "Fixed Selection: Actor")
    poser.DefineScriptButton(3, os.path.join(path, "HidePoserActor_1.py"), "Hide unselected Figures")
    poser.DefineScriptButton(4, os.path.join(path, "HidePoserActor_2.py"), "Hide unselected Actors")
    poser.DefineScriptButton(5, os.path.join(path, "HidePoserActor_3.py"), "Unhide")
    poser.DefineScriptButton(6, os.path.join(path, "FixPoserActor_4.py"), "Debug on/off")
    poser.DefineScriptButton(7, "", "")
    poser.DefineScriptButton(8, "", "")
    poser.DefineScriptButton(9, "", "")
    try:
        poser.DefineScriptButton(10, mainButtonsPath, "Back")
    except NameError:
        poser.DefineScriptButton(10, "", "")
    
    

    Additionally 3 miniscripts to activate the buttons are required:
    Save as HidePoserActor_1.py:

    try:
        DEBUG
        _cb_
        setmode
    except:
        import sys, os
        execfile(os.path.join(os.path.dirname(sys.argv[0]), "FixPoserActor.py"))
    
    hideUnhide(SCENE.CurrentFigure())
    

    Save as HidePoserActor_2.py:

    try:
        DEBUG
        _cb_
        setmode
    except:
        import sys, os
        execfile(os.path.join(os.path.dirname(sys.argv[0]), "FixPoserActor.py"))
    
    hideUnhide(SCENE.CurrentActor())
    

    Save as HidePoserActor_3.py:

    try:
        DEBUG
        _cb_
        setmode
    except:
        import sys, os
        execfile(os.path.join(os.path.dirname(sys.argv[0]), "FixPoserActor.py"))
    
    hideUnhide()
    
    


  • @adp Looks very useful. But when I click on the script's name, I get "fixposeractor.py’s server IP address could not be found."

    Oh, wait! I'm supposed to copy and save as? I need more coffee.



  • @willshetterly I uploaded a fixed and modified version to ShareCG for download:
    https://sharecg.com/v/91733/view/8/Script/Poser-Python-Hide-and-Lock-actors-figures



  • @adp thank you for this :-)



  • @anomalaus What about fixing move directions? I mean: prevent an actor or prop from moving in X and/or Y and/or Z?
    I have to delay my Importer/Exporter to think about the fastest method to find duplicate vertices in Python, output corrected polygons and read them back correctly. So I may have some time to write this.



  • @adp I've just been setting limits and forceLimits to do that kind of thing for rotations. I guess it would work as well for translations, provided Poser will apply the forced limits (I seem to remember there being some things it didn't enforce).

    On your Im/Exporter, I was just looking at your post in the other thread and it reminded me I'd recently done something relevant with creating a 2-manifold version of a hair prop from the original transmapped, non-manifold version, because I wanted to be able to use SuperFly Volumetrics for wet hair effects without getting transmap artifacts (due to transparency only applying to UV mapped surface facets, not the volume enclosed by the model). Anyway, TL;DR, the code I have specifically detects facet seams with duplicated vertices in the geometry, and has code for direct import and export of wavefront obj geometry, if you're interested, I will post the file.



  • @adp Make2Manifold.py

    from the comment header:

    # Convert the currently selected prop from an open (non-manifold) surface to a 2-manifold enclosed volume.
    # Non-2-manifolds are detected by a non-zero count of edges which border only one facet. The vertices on such edges
    # remain unchanged, while all other vertices are duplicated and offset by a user-specified distance along the inverse
    # of the original vertex's normal. All facets will be duplicated, incorporating either duplicate vertices or original
    # edge vertices. All morphs will have the additional vertices appended, with delta values identical to the original
    # deltas of the vertex from which they were derived. 
    

    The script contains an object class definition which collects lots of additional analysis information of the mesh, such as seams and borders, and methods to load from in-scene geometry or direct from .obj file. Within the CreateManifold() method, there's a pure .obj file creation section which bypasses Poser's obj export routines and writes the object class instance geometry straight to a .obj file, so it can be subsequently loaded by Poser's import routine, which is the only way to create new facet groups within the current Python API (without resorting to manually using the Grouping Tool).

    The script can also preserve existing morphs on the newly cloned object.



  • @adp I should add that Make2Manifold is not particularly optimised for speed (though it does make use of frozensets), so please don't hesitate to suggest ways to improve performance. :-) Please feel free to use anything you find beneficial.



  • @anomalaus Sounds useful. Where is the code? :)



  • @anomalaus Oh, I'm blind... Overlooked the link.



  • @anomalaus Sorry, can't load anything from mega.nz with any of my browsers on no machine I have. This is what I get:
    0_1531062175656_Bildschirmfoto vom 2018-07-08 17-01-04.png



  • @adp Oh, that's frustrating. I've not seen that before. Do you have an ad blocker or something that's preventing javascript?

    I see this when I click the link (though I have a mega account so that may be relevant):
    0_1531063007359_Screen Shot 2018-07-09 at 1.15.04 am.png

    OK, try this Make2Manifold.py first time I've uploaded anything to ShareCG!



  • @anomalaus I disabled adblockers in firefox and chromium, tried with a very simple webbrowser (without any extensions), and tried with tablets/smartphones.
    But: I'm behind a firewall that blocks .ru and .cn IPs and rejects calls to/from IPs listed as malicious.
    I can ping mega.nz and get the first part from their index-page. But it seems they do load something from somewhere my firewall calls "malicious".

    Anyway, thanks for uploading to ShareCG. Now I'm off for a moment or two, looking at your script...



  • I think I'll go with a binary searched list to get a pseudo-sorted vertexlist.
    In a test I was able to compute and write an OBJ file with a vertexlist (no tex-vertices jet) and new constructed polygon-indices from Roxie (~30000 vertices) within rund about 2 seconds (but the polygons are not correctly constructed yet :)).

    Tested with an older laptop (SSD, 8 gig Memory, I5 processor), Poser Python, empty scene, just plain Roxie loaded.
    With a "busy" Poser scene and more vertices it may last a bit longer because of memory swapping. I'll test that later. First I have to look why I get a polygonset that looks like a pot full of spaghetti. :)