Forum: Poser Python Scripting


Subject: How to mirror a light

sergio777 opened this issue on Sep 18, 2021 ยท 13 posts


sergio777 posted Sat, 18 September 2021 at 2:43 PM

I can move a light manually easily but sometimes it's not so accurate, so I tried to make a script to mirror a light by changing the sign of the Yrotation or/and Trans values, but it didn't work. Does anyone have any idea how to do it?




structure posted Sat, 18 September 2021 at 7:30 PM Forum Coordinator

it seems that you can only mirror the parameters if you mirror them separately. and don't forget to scene.Draw() or scene.DrawAll() at the end.


```

if transitions().IsYRotate( parm ):

    parm.SetValue( -parm.Value() )

if transitions().IsXTranslate( parm ):

    parm.SetValue( -parm.Value() )

```


and the forum appears to be borked again.

Locked Out


structure posted Sat, 18 September 2021 at 7:56 PM Forum Coordinator


this works for me

Locked Out


sergio777 posted Sat, 18 September 2021 at 11:12 PM

Thanks for your answer Structure, the code works fine as long as the light parameter Zrot is 0, in cases where Z rotatation is different from zero the results are wrong.

There should be some way to translate any light position to one where the Zrot parameter is 0 and then run the script, but I haven't figured out how to do it.


adp001 posted Sun, 19 September 2021 at 12:51 AM

from __future__ import print_function, division

import sys
if sys.version_info.major > 2:
    # Python 3 (Poser 12 and above)
    basestring = str

SCENE = poser.Scene()
P_ROTS = poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT
P_TRANS = poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN


def copy_light(obj):
    """
    Create a new light object and copy as many attributes as possible from
    the old light to the new one. Attention: Maybe not all attributes are reachable
    via Python!
    """
    assert obj.IsLight()
    new_light = SCENE.CreateLight()

    for attr in dir(poser.ActorType):
        if attr.startswith("Set") and any((_p in attr for _p in
                                           ("Ambient", "Light", "Display", "Atmosphere",
                                            "Shading", "Shadow", "Visible", "RayTrace"))):
            getattr(new_light, attr)(getattr(obj, attr[3:])())

    for new_parm in new_light.Parameters():
        old_parm = obj.Parameter(new_parm.Name())
        if not old_parm:
            continue

        for p in dir(poser.ParmType):
            # Copy all attribute values of parameter we have a "Set.." function for.
            if p.startswith("Set"):
                # Try to get the value by shorten the attribute name
                # (by removing "Set").
                try:
                    value = getattr(old_parm, p[3:])()
                    # Because lights has a different parameter set
                    # than other parameters this may fail without consequences.
                except poser.error:
                    pass
                except AttributeError:
                    pass
                else:
                    # Set the value.
                    getattr(new_parm, p)(value)

    return new_light


def copy_mirrored_light(obj, trans="XYZ", rot="XYZ"):
    if isinstance(trans, basestring):
        trans = [(ord(c) - ord("X")) for c in trans.upper()]
    assert isinstance(trans, (list, tuple))
    if isinstance(rot, basestring):
        rot = [(ord(c) - ord("X")) for c in rot.upper()]
    assert isinstance(rot, (list, tuple))

    new_light = copy_light(obj)
    for idx, code in enumerate(P_TRANS):
        if idx in trans:
            p = new_light.ParameterByCode(code)
            p.SetValue(-p.Value())

    for idx, code in enumerate(P_ROTS):
        if idx in rot:
            p = new_light.ParameterByCode(code)
            v = p.Value() % 360
            p.SetValue(180-v)
    return new_light


light = copy_mirrored_light(SCENE.Actor("Light 1"), trans="X", rot="XYZ")




adp001 posted Sun, 19 September 2021 at 1:18 AM

The above function "copy_mirrored_light" takes an existing light and creates a mirrored copy of it. Parameter "trans" is a string containing the translation axis to mirror (X, Y and Z in one string, like "XYZ"). The same for "rot", the rotation axis. The last line above shows the most typical parameters: trans="X" and rot="XYZ".

Instead of a string a tuple or list with up to 3 values (0=="X", 1=="Y", 2=="Z") is also fine.




adp001 posted Sun, 19 September 2021 at 2:02 AM

There is an error in the code above. Mirror rotation does not work correct. Seems we have to go the hard way and use Quaternions :)





adp001 posted Sun, 19 September 2021 at 2:21 PM

Reworked the whole thing (rotation seems to work now) and splitted it into single functions that can be used independently (copy_trans(...), copy_rot(...), copy_scales(...), mirror_trans(...), mirror_rot(...)).

I also added a selection dialog.


from __future__ import print_function, division

try:
    import poser
except ImportError:
    # Not required while inside Poser Python, but very helpful for external editors.
    # See https://adp.spdns.org?#FakePoser
    from PoserLibs import POSER_FAKE as poser

import wx, sys

if sys.version_info.major > 2:
    # Python 3 (Poser 12 and above)
    basestring = str

SCENE = poser.Scene()
P_ROTS = poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT
P_TRANS = poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN
P_SCALES = poser.kParmCodeXSCALE, poser.kParmCodeYSCALE, poser.kParmCodeZSCALE, poser.kParmCodeASCALE


def copy_trans(source_obj, target_obj):
    for code in P_TRANS:
        target_obj.ParameterByCode(code).SetValue(source_obj.ParameterByCode(code).Value())


def copy_rot(source_obj, target_obj):
    for code in P_ROTS:
        target_obj.ParameterByCode(code).SetValue(source_obj.ParameterByCode(code).Value())


def copy_scales(source_obj, target_obj):
    for code in P_SCALES:
        target_obj.ParameterByCode(code).SetValue(source_obj.ParameterByCode(code).Value())


def mirror_trans(source_obj, target_obj, axis="XYZ", copyflag=False):
    if isinstance(axis, basestring):
        axis = [(ord(c) - ord("X")) for c in axis.upper()]
    assert isinstance(axis, (list, tuple)), \
        "Must be a string ('XYZ') or a list of integers (0, 1, 2)."

    for idx, code in enumerate(P_TRANS):
        if idx in axis:
            target_obj.ParameterByCode(code).SetValue(-source_obj.ParameterByCode(code).Value())
        elif copyflag:
            target_obj.ParameterByCode(code).SetValue(source_obj.ParameterByCode(code).Value())


def mirror_rot(source_obj, target_obj, axis="XYZ", copyflag=False):
    if isinstance(axis, basestring):
        axis = [(ord(c) - ord("X")) for c in axis.upper()]
    assert isinstance(axis, (list, tuple)), \
        "Must be a string ('XYZ') or a list of integers (0, 1, 2)."

    for idx, code in enumerate(P_ROTS):
        if idx in axis:
            p = source_obj.ParameterByCode(code)
            v = p.Value() % 360
            if v < 0:
                v = 360 - v
            if idx == 2:  # Z rotation
                target_obj.ParameterByCode(code).SetValue((180 - v) % 360)
            else:
                target_obj.ParameterByCode(code).SetValue((180 + v) % 360)
        elif copyflag:
            target_obj.ParameterByCode(code).SetValue(source_obj.ParameterByCode(code).Value())


def copy_light(obj, copyparms=True):
    """
    Create a new light object and copy as many attributes as possible from
    the old light to the new one. Attention: Maybe not all attributes are reachable
    via Python!
    """
    assert obj.IsLight()
    new_light = SCENE.CreateLight()

    for attr in dir(poser.ActorType):
        if attr.startswith("Set") and any((_p in attr for _p in
                                           ("Ambient", "Light", "Display", "Atmosphere",
                                            "Shading", "Shadow", "Visible", "RayTrace"))):
            getattr(new_light, attr)(getattr(obj, attr[3:])())
            # Attention: Don't use the above unfiltered! Your Poser may crash!

    if copyparms:
        # Copy objects parameters if required.
        for new_parm in new_light.Parameters():
            old_parm = obj.Parameter(new_parm.Name())
            if not old_parm:
                continue

            for p in dir(poser.ParmType):
                # Copy all attribute values of parameter we have a "Set.." function for.
                if p.startswith("Set"):
                    # Try to get the value by shorten the attribute name
                    # (by removing "Set").
                    try:
                        value = getattr(old_parm, p[3:])()
                        # Because lights has a different parameter set
                        # than other parameters this may fail without consequences.
                    except poser.error:
                        pass
                    except AttributeError:
                        pass
                    else:
                        # Set the value.
                        getattr(new_parm, p)(value)

    return new_light


def copy_mirrored_light(obj):
    new_light = copy_light(obj, copyparms=True)  # copyparms to get scales and position copied
    mirror_trans(obj, new_light, axis="X")
    mirror_rot(obj, new_light, axis="XYZ")

    return new_light


#### A bit wxPython stuff ###

with wx.SingleChoiceDialog(None, message="Select a light to mirror", caption="Mirror Light",
                           choices=[o.Name() for o in SCENE.Lights()]) as dlg:
    if dlg.ShowModal() == wx.ID_OK:
        try:
            light = SCENE.Actor(dlg.GetStringSelection())
        except poser.error:
            print("Selected light not available: '%s'" % dlg.GetStringSelection())
            new_light = None
        else:
            new_light = copy_mirrored_light(light)




sergio777 posted Sun, 19 September 2021 at 3:42 PM

Thanks Adp001, the solution is pretty good, but it fails when using HDR, IBL lights or image based lights because those kind of lights contain a texture attached, also I noticed that it doesn't copy the params where the color picker is used like Color, Diffuse and Specular values.


adp001 posted Sun, 19 September 2021 at 11:14 PM

There are some other things that are not copied. Not only materials.

But this was not asked for. The question was:

How to mirror a light

Which means the job is more than done.




sergio777 posted Mon, 20 September 2021 at 12:39 PM


Adp001, technically an IBL light is a light too isn't it?


adp001 posted Mon, 20 September 2021 at 5:00 PM

Pretty much anything can be made into a light using emitters.

As I wrote in the source as a comment: Poser Python does not allow access to everything. Or only in a very cumbersome way.
So there is nothing else than manual work (and the hope that there will be a corresponding function in Poser Python someday):

Use the menu item "Copy Object" in Poser to get a 1:1 copy. And then apply the mirror functions (that's the reason why I split this into single functions).




adp001 posted Sat, 25 September 2021 at 1:06 AM

To copy materials from one Poser object to another, see:

https://www.renderosity.com/forums/threads/2959992/something-more-about-poser-materials