Forum: Poser Python Scripting


Subject: Snap for Macs - in development

HartyBart opened this issue on Jul 16, 2021 · 12 posts


HartyBart posted Fri, 16 July 2021 at 2:27 AM

The following was kindly posted by adp001 for a new user a few days ago, and slightly extended by me. It is working and adp001 suggested I post it here for further comments and development. As you may know, Ockham's vital Snap mover script used Tkinter for looping, and thus was not for Mac users of Poser. The script below is a working demo of the same, but using WX rather than Tkinter. Thus it can (should) work for Mac users, providing them with a vital addition to Poser 11 and possibly also 12.

###
# A Poser script that offers a Mac user version of Ockham's famous SnapTo.
# Moves items around in a Poser scene, in an easy 'teleporting' manner.
# By adp001, version 1.0 July 2021.
# Tweaked and commented by HartyBart. Version 1.1.
###
import poser
import wx

SCENE = poser.Scene()

# Get the currently selected Scene actor by its internal name.
actor_iname = SCENE.CurrentActor().InternalName()
# Prompts the user to select the destination target by clicking once on it.
msg = wx.BusyInfo("Select Target Actor")

def theloop():
    global  msg
    #Check we have the correct actor selected, before starting the loop.
    if SCENE.CurrentActor().InternalName() != actor_iname:
        del msg # Delete the user message box.
        actor_A = SCENE.ActorByInternalName(actor_iname)
        actor_B = SCENE.CurrentActor()
        # Parent the first and second actors, here A (mover) and B (destination).
        actor_A.SetParent(actor_B)
        # A nas now moved to the position of B. Clear the XYZ location parameters for A.
        for code in (poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN):
            actor_A.ParameterByCode(code).SetValue(0)
            # Now finesse the location of the mover. Such that it is not on top of, or inside, B.
            # Adjust the Z and X values if you have larger structures to move around, such as houses.
            actor_A.SetParameter("Ytran",0.0)
            actor_A.SetParameter("Ztran",0.2)
            actor_A.SetParameter("Xtran",0.2)
        # Ensure that A is the selected actor, and re-parent it to UNIVERSE to it is free again.
        SCENE.SelectActor(actor_A)
        actor_A.SetParent(SCENE.ActorByInternalName("UNIVERSE"))
        # Have OpenGL redraw the Preview viewport for the scene.
        SCENE.DrawAll()
    else:
        wx.CallLater(300, theloop)

theloop()

snap2-2.jpg



Learn the Secrets of Poser 11 and Line-art Filters.


adp001 posted Fri, 16 July 2021 at 8:32 AM

But now. Meanwhile I am back home (6 hours train ride from Munich to Cologne).I visited my sister who lives at the Tegernsee. Have unfortunately since yesterday not found the time to enter here faster.

Actually I find the problem of being able to move a prop or figure near another object with one click useful and quite interesting when it comes to solving it.

@Hartybart: Thanks for the descriptive comments. However, I have something to note about the first comment in theloop() function.

You wrote: "Check we have the correct actor selected, before starting the loop."

But actually it should say: Check if we have another actor and, if so, break the loop.




HartyBart posted Fri, 16 July 2021 at 10:10 AM

Ok, thanks - I try to keep track of the suggested changes, and then re-post the entire tested script when enough changes have accrued. I've also proof-read some of my comments and made minor fixes there.



Learn the Secrets of Poser 11 and Line-art Filters.


adp001 posted Fri, 16 July 2021 at 10:51 AM

What about having a dialog with some options displayed while waiting for actor change? And which options are needed?




HartyBart posted Sat, 17 July 2021 at 12:22 PM

I can imagine a dialog box that asks me if I want to switch to a overhead "Bird's Eye" zoomed-out view of the scene, and if I do then the script does that for me. Use case: user has a big complex battle scene and cannot see in the Viewport where the destination actor is. They then realise they need to back out of the current script, switch-zoom-frame for a wider view, and re-run the script.

Or the choice could be done via a drop-down of other actors in the scene, and the user would select the destination actor by name. But that gets away from the ease of just clicking on the destination actor, which I think is what most users would want.

Another thing I can imagine is a three-choice dialog: "Do you have a small, medium or BIG mover actor?" If the user selects BIG then the destination location offsets are changed from 0.2 to 1.2.



Learn the Secrets of Poser 11 and Line-art Filters.


adp001 posted Thu, 22 July 2021 at 11:41 AM

I will do a popup dialog tomorrow.

Meanwhile I tried another method of doing the "snap".

import wx

SCENE = poser.Scene()
actor_iname = SCENE.CurrentActor().InternalName()
offsets = (0, -.2, 0)
msg = wx.BusyInfo("Select Target Actor")


def add_ar(a, b):
    if isinstance(b, (int, float)):
        return [v + b for v in a]
    return [av + bv for av, bv in zip(a, b)]


def mul_ar(a, b):
    if isinstance(b, (int, float)):
        return [v * b for v in a]
    return [av * bv for av, bv in zip(a, b)]


def set_coords(actor, coords=None):
    codes = poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN
    if coords is None:
        coords = 0, 0, 0
    for code, v in zip(codes, coords):
        actor.ParameterByCode(code).SetValue(v)


def get_firstactor(obj):
    """Return the first movable actor of a figure, usually the hip"""
    if isinstance(obj, poser.FigureType):
        root = obj.RootActor()
    elif isinstance(obj, poser.ActorType) and obj.IsBodyPart():
        root = obj.ItsFigure().RootActor()
    else:
        return obj
    li = [a for a in root.Children() if a.IsBodyPart()]
    return li[0] if li else obj


def theloop():
    global msg
    if SCENE.CurrentActor().InternalName() != actor_iname:
        del msg

        actor_B = SCENE.CurrentActor()  # Selected target actor.
        if actor_B.InternalName() == actor_iname:
            return  # Actor to move and target actor are the same. Just break the loop.

        if actor_B.IsBodyPart():
            actor_B = get_firstactor(actor_B)
            coords = add_ar(actor_B.WorldDisplacement(), actor_B.Origin())
        else:
            coords = actor_B.WorldDisplacement()

        actor_A = SCENE.ActorByInternalName(actor_iname)  # Actor to move.
        if actor_A.IsBodyPart():
            actor_A = actor_A.ItsFigure().RootActor()
            hip = get_firstactor(actor_A)
            set_coords(actor_A)  # zero body
            set_coords(hip)  # zero hip
            coords = add_ar(coords, mul_ar(hip.Origin(), -1))
        else:
            coords = add_ar(coords, mul_ar(actor_A.Origin(), -1))

        coords = add_ar(coords, offsets)
        for code, v in zip((poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN), coords):
            actor_A.ParameterByCode(code).SetValue(v)
        SCENE.SelectActor(actor_A)
        SCENE.DrawAll()
    else:
        wx.CallLater(300, theloop)


theloop()




adp001 posted Fri, 30 July 2021 at 4:21 PM

Bildschirmfoto vom 2021-07-30 22-52-28.png

Took me a while, but I'm starting to see land.

I'm hooked on user interface tinkering. It's hard to believe that wxPython can't make dialogs look the same in different versions and machines. The problem is that wxPython tries to leave the design of the controls to the system. So it happens that an interface looks like this on one system and completely different on another. In addition, wxPython works differently under P11 than under P12.

I have now built a part of the controls "discrete". As you can see, the radio buttons and the check controls are still original. But they will be added as well. The actual programming is done with Python 3.9 under Linux and then tested with Python 2.7 under Poser 11 in Windows (running in a virtual machine). This should ensure that it all works with P12 and not just Windows.

The controls for entering offsets are interesting. They can be compared to the controls in Blender. However, I added a few more things:If you change the values with the mouse (as with the standard Poser dials), and hold down Shift and/or Ctrl at the same time, the "scale factor" changes ("sensitivity" is what it's called in Poser). And the right mouse button shows a drop-down list with the last ten values ("History"), from which you can select one.

About the functionality: I think I've finished that.It works as I want it to work (which doesn't mean that others would prefer it to work differently). Since the logic is quite simple, it can be easily changed if needed.

I just have to clean it up a bit (there are some debug prints in there and some other stuff I don't need anymore) and then I'll post the whole thing. As a ZIP, because there are several files - and a lot of code, just for the UI.




adp001 posted Fri, 30 July 2021 at 4:26 PM

Veregessen zu erwähnen: Man kann in den eingabefeldern auch Formeln eingeben. Also so was wie "(100.0/2.123)+24.2". Ich dachte dass das speziell bei der Eingabe von Offsets Sinn macht :)




adp001 posted Fri, 30 July 2021 at 4:35 PM

Why actually are screenshots posted here twice the size they are on my monitor?




adp001 posted Sat, 31 July 2021 at 1:51 PM

I have to stop for the weekend. But if you want to try how it works, just download the ZIP, extract it to a folder and start "Snap2Object.py". Should run with P11 and P12.

https://adp.spdns.org/Snap2Object.zip

Any feedback is welcome. Also any ideas how to proceed with it. Any errors? What makes sens to add or change?




adp001 posted Sat, 31 July 2021 at 2:02 PM

HartyBart posted at 1:54PM Sat, 31 July 2021 - #4423398

I can imagine a dialog box that asks me if I want to switch to a overhead "Bird's Eye" zoomed-out view of the scene, and if I do then the script does that for me. Use case: user has a big complex battle scene and cannot see in the Viewport where the destination actor is. They then realise they need to back out of the current script, switch-zoom-frame for a wider view, and re-run the script.

This shouldn't be a problem anymore, because the script can be part of Posers UI (drag the script window somewhere into Posers UI). The "Activate" Button can be switched on and off (becomes red if active).

Or the choice could be done via a drop-down of other actors in the scene, and the user would select the destination actor by name. But that gets away from the ease of just clicking on the destination actor, which I think is what most users would want.

Drop-down is there. If active and something other then "Current Actor" is selected, this actor is moved to the next clicked actor/figure.

Another thing I can imagine is a three-choice dialog: "Do you have a small, medium or BIG mover actor?" If the user selects BIG then the destination location offsets are changed from 0.2 to 1.2.

The offset dials can be dragged with the mouse even if the active button is red (the script is active). And because the inputs have a history (right mouse click), you can easily switch between up to ten positions.




estherau posted Fri, 28 January 2022 at 3:58 AM

Hi, did you ever get this working in poser12?


MY ONLINE COMIC IS NOW LIVE

I aim to update it about once a month.  Oh, and it's free!