Forum: Poser Python Scripting


Subject: Callback created using parm.SetUpdateCallback() not behaving as expected

3dcheapskate opened this issue on Sep 25, 2021 ยท 16 posts


3dcheapskate posted Sat, 25 September 2021 at 10:36 PM

(This is a rewording of this post, but as a separate, appropriately titled thread)

I've created a very simple script which  should, I believe, cause myUpdateCallback() to be called whenever the xTran dial on the BODY is changed - myUpdateCallback()  simply increments the yRotate dial on the BODY. So as long as I've selected a figure which has an xTran parameter on its BODY, e.g. La Femme or just about anything, when I run the script (and the same figure is selected when I change the dial) then its yRotate should increase by 1 any time I change its xTran, either by manually setting the value of the dial or clicking the left/right arrow to the side of the dial, any of which should do a single update.

However, after running the script, when I change the xTran dial as stated the yRotate value actually increases by between 3 and 5, which indicates to me that the callback is called that many times.

In addition, and just as puzzling , the yRotate value also increases by between 3 and 5 if I change any of the other parameters, e.g. yTranzTranxRotatezRotateScale, etc. 

Here's the script (between the two horizontal lines below). It appears that the post editor ignores tabs so I've copied from Notepad++ after using Edit > Blank Operations > Tab To Space. So if you cut and paste it you'll have to do the reverse, Edit > Blank Operations > Space to Tab (Leading) for it to work as Python. 


def myUpdateCallback(parm,value):

    ac = poser.Scene().ActorByInternalName("BODY")

    yrotval=ac.Parameter("yRotate").Value()

    ac.SetParameter("yRotate",yrotval+1)

    return value

    


def setup():

    print "setup()..."

    ac = poser.Scene().ActorByInternalName("BODY")

    parm = ac.Parameter("xTran")

    parm.SetUpdateCallback(myUpdateCallback)

    print "...done."

    

setup()



And here's a screenshot of that same script just in case:


The (Poser 11) PoserPython manual says this...

...which I (mis?)understand to mean that a callback that's set using this method (remember it's a parameter method) is only called when that particular parameter is updated. 

So my questions are:

1) Is my statement of what I expect to happen after I run this script correct ?

2) If it is, then why doesn't it work like that ?


The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).



adp001 posted Sun, 26 September 2021 at 12:18 AM

First quick answer:
poser.Scene().ActorByInternalName("BODY") returns the first body actor found in the scene. This is not necessarily the one you wanted to find.

Either you specify the index number of the figure from which you want the actor (so for example "BODY:3"), or you use the corresponding function of the figure: poser.Scene().CurrentFigure().InternalName("BODY").

By the way, the "Scene" object remains constant. It does not change. So you can set SCENE=poser.Scene() and later just use SCENE instead of poser.Scene() (capitalization because it is something like a constant; but this is not mandatory but just good style).




adp001 posted Sun, 26 September 2021 at 12:24 AM

undefined posted at 10:36 PM Sat, 25 September 2021 - #2965775

(This is a rewording of this post, but as a separate, appropriately titled thread)

> In addition, and just as puzzling , the yRotate value also increases by between 3 and 5 if I change any of the other parameters, e.g. yTranzTranxRotatezRotateScale, etc.

Should work fine. But after some interruptions because of errors inside a callback, Poser Python may become instable. Just close Poser and start again :)




adp001 posted Sun, 26 September 2021 at 12:34 AM

undefined posted at 10:36 PM Sat, 25 September 2021 - #2965775


Here's the script (between the two horizontal lines below). It appears that the post editor ignores tabs so I've copied from Notepad++ after using Edit > Blank Operations > Tab To Space. So if you cut and paste it you'll have to do the reverse, Edit > Blank Operations > Space to Tab (Leading) for it to work as Python.


Using tabs instead of spaces is not good anyway. Python code depends on correct indentation. And tabs can mean any number of spaces. Depends on definition inside your editor.

Someone wrote an article about it. Worth to read: https://careerkarma.com/blog/python-taberror-inconsistent-use-of-tabs-and-spaces-in-indentation/




adp001 posted Sun, 26 September 2021 at 12:42 AM

If the parameter to be changed on an event is in the same actor where the event was triggered, then you can use "parm" to reach the actor uniquely and quickly:

target_actor = parm.Actor()

or

parameter = parm.Actor().Parameter("yRotate")

or

parm.Actor().Parameter("yRotate").SetValue(value)




adp001 posted Sun, 26 September 2021 at 1:01 AM

About tabs: I posted the wrong link. I meant this one: https://www.code-learner.com/python-indentation-tab-vs-space/

And here is the Python Style Guide:  https://www.python.org/dev/peps/pep-0008/




3dcheapskate posted Sun, 26 September 2021 at 4:23 AM

adp001 posted at 12:18 AM Sun, 26 September 2021 - #4428089

First quick answer:
poser.Scene().ActorByInternalName("BODY") returns the first body actor found in the scene. This is not necessarily the one you wanted to find.

Since I just had one figure in the scene  that won't have been a problem for this test I did.

(But point taken regarding being more careful for generic use)


The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).



3dcheapskate posted Sun, 26 September 2021 at 4:40 AM

adp001 posted at 12:24 AM Sun, 26 September 2021 - #4428090

undefined posted at 10:36 PM Sat, 25 September 2021 - #2965775

(This is a rewording of this post, but as a separate, appropriately titled thread)

> In addition, and just as puzzling , the yRotate value also increases by between 3 and 5 if I change any of the other parameters, e.g. yTranzTranxRotatezRotateScale, etc.

Should work fine. But after some interruptions because of errors inside a callback, Poser Python may become instable. Just close Poser and start again :)

But it doesn't (work fine that is).

I open Poser (11) - my default scene is the factory set one with construct, lights and La Femme. La Femme is selected (Chest) by default. I select 'Body' and see that all translations, rotations, and scales are zero. I run my script and see "setup()..." and "...done" in the debug window. All values still zero. I now expect to see La Femme's Body's yRotate increment if, and only if, I change La Femme's Body's xTran. I think that with just the one figure in the scene that should be right, even with my sloppy code. :)

But that's not what happens. :(

It appears that the callback is called whatever I do next - even "Edit > General Preferences > Cancel" cause yRotate to go up by 3 !


The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).



3dcheapskate posted Sun, 26 September 2021 at 4:56 AM

adp001 posted at 12:34 AM Sun, 26 September 2021 - #4428091
undefined posted at 10:36 PM Sat, 25 September 2021 - #2965775


Here's the script (between the two horizontal lines below). It appears that the post editor ignores tabs so I've copied from Notepad++ after using Edit > Blank Operations > Tab To Space. So if you cut and paste it you'll have to do the reverse, Edit > Blank Operations > Space to Tab (Leading) for it to work as Python.


Using tabs instead of spaces is not good anyway. Python code depends on correct indentation. And tabs can mean any number of spaces. Depends on definition inside your editor.

Someone wrote an article about it. Worth to read: https://careerkarma.com/blog/python-taberror-inconsistent-use-of-tabs-and-spaces-in-indentation/

Good practice isn't something I usually think about... ;)

The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).



3dcheapskate posted Sun, 26 September 2021 at 5:32 AM

undefined posted at 10:36 PM Sat, 25 September 2021 - #2965775

(This is a rewording of this post, but as a separate, appropriately titled thread)

...So if you cut and paste it you'll have to do the reverse, Edit > Blank Operations > Space to Tab (Leading) for it to work as Python...

I've just realized that what I said there about tabs was a load of tosh ! :rolleyes:

The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).



3dcheapskate posted Sun, 26 September 2021 at 11:22 AM

Well, it's definitely something to do with putting those three lines in the callback that causes all the oddities (multiple apparent calls of the callback, and the callback apparently being called multiple times if ANY parameter ON THE BODY actor is changed (or even if an unrelated UI action is performed such as "Edit > General Preferences > Cancel" )

These three lines:


ac = poser.Scene().ActorByInternalName("BODY")
yrotval=ac.Parameter("yRotate").Value()
ac.SetParameter("yRotate",yrotval+1)



I added those three lines to the 'parmCallback.py' sample callback and I got the same weird effects that I've noted



The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).



3dcheapskate posted Mon, 27 September 2021 at 9:30 AM

Still trying to get to the bottom of this.

Here are two versions of an update callback for yRotate, one of them modifies the yRotate value (which is the intended use of a callback), the other modifies a DIFFERENT parameter (probably NOT the intended use, and thus the reason for the odd behaviour)

- The one on the left !CB-OK.PY simply increments the yRotate value. This one works 99% as expected - I believe that the other 1% may hold the key (see note after the image below)

- The one on the right !CB-WEIRD.PY simply increments the zTran value. This one does NOT work as expected - the zTran value increases by FIVE whenever ANY body parameter is changed... or when you do something on the Poser UI menu like "Edit > Preferences > Cancel"... or when you go to the Material room and back to the Pose room... or if you select a different body part and then reselect the Body... or... etc.


This is the 1% of running !CB-OK.PY that I think is key:

Open Poser (11) with a scene containing a single figure (I use the factory scene with La Femme).
Select the Body actor and verify that all translations/rotations are zero and all scales are 100%
Run !CB-OK.PY, verify that "setup()..." and "...done" are shown in the debug window, and again verify that all translations/rotations are zero and all scales are 100%.
Modify any of the BODY parameters EXCEPT yRotate.
Verify that all translations/rotations EXCEPT the one you modified are still zero or 100% as they should be...

They're not - while the ENTERED yRotate value is still zero, the DRIVEN yRotate value is now 1. 

So it appears to me that if, like me, you use yRotateParm.SetUpdateCallback(yRotateCallback) thinking that yRotateCallback() will ONLY be called if yRotate changes then you're going to be disappointed. It appears to be called whenever ANY parameter (on the same body part perhaps?) is changed, or when you do other UI-type things.

Also, since the callback in !CB-OK.PY will return the same value regardless of whether it's actually called once, twice, or however many times, I believe that the callback being called several times is probably normal too - you just don't usually see it.


The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).



3dcheapskate posted Mon, 27 September 2021 at 10:47 AM

I now make a slight change to !CB-OK.PY by using a global variable (as per the randomHeadVerts.py sample script) as an incrementing callback counter to get this, which will increment the value of yRotate each time the callback is called:


And yes, modifying ANY parameter on the Body (or doing any of the other UI things*) causes yRotate to increase by 4, meaning that the callback IS called multiple times.

So I'm not going mad - at least no more than I already was.


So WHY does the SetUpdateCallback() cause this to happen ?


*Not true - selecting another body part and then reselecting Body appears to cause a single callback, and Edit > General Preferences > Cancel doesn't actually cause any callbacks, but going to the material room and back does cause four.


The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).



3dcheapskate posted Thu, 30 September 2021 at 12:21 PM

(I've also asked over there on the Hivewire Poser Scripting forum), where I've also uploaded the latest version of this script if anybody wants to download and confirm that I'm not talking rubbish.

I've now detailed the problem in the comments of the script itself, which I've cut-and-pasted below from Notepad++ for good measure (the double line spacing must be the CR/LF)...



# This script sets up a simple update callback on the yRotate dial of the currently selected actor


# My understanding of what SHOULD happen: 

# - 1) This callback should be called ONLY if the natural* (lower) value of the yRotate dial on the currently selected actor is changed.

# - 2) Each time this callback runs it should increment the keyed* (upper) value of the yRotate dial.

#

# *using the terminology under the heading "Dependant Parameter Dials" on page 222 of the Poser 11 Reference Manual


# What ACTUALLY happens:

# - 1) The callback seems to be called if ANY dial is changed. Or if a different body part is selected, or if you change rooms, etc...

# - 2) The callback seems to be called multiple times, usually between 1 and 5, when any of these things happen


def cheapskatesYRotateCallback(parm,value):

    global cheapskatescount

    cheapskatescount=cheapskatescount+1

    return cheapskatescount


print "Setting up the yRotate callback..."

global cheapskatescount

cheapskatescount=0

poser.Scene().CurrentActor().Parameter("yRotate").SetUpdateCallback(cheapskatesYRotateCallback)

print "...done."




I would really appreciate it if somebody other than me could try running this script to confirm that I'm not talking rubbish !

(I'd also appreciate it even more if somebody can make it do what I want it to ! :0)


The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).



3dcheapskate posted Sun, 03 October 2021 at 6:14 AM

Okay, it's looking as if the parm.SetUpdateCallback() is ONLY intended for setting the parameter itself, not anything else - after all the manual does say "Set a per-update callback for calculating THIS parameter value".


The three sample scripts that use it (parmCallback.py, blinkChannels.py, and muscleMag.py) do just that, and nothing more - here's a screenshot showing the callback functions set using parm.SetUpdateCallback() from those three files:


My initial reason for using a callback was to ensure that the parameter in question was modulo 360, and that does indeed work as expected - so thanks adp001 for that :)

The problem came when I thought "if I'm going to have to do that bit in Python, why not do the rest of the maths there too?"


The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).



3dcheapskate posted Sun, 03 October 2021 at 11:13 PM

HA !

I spoke too soon ! 

Try this...

(This script sets the xTran and zTran of the selected figure's Body based on its yRotate, using the bog-standard right-angle triangle sine/cosine mathemagical stuff to move the figure in a circle. Not quite what I was trying to do, but it highlights one of my major misunderstandings about callbacks, and gives a clue as to how I should be doing it.)



# ! Callback Test Setup Version 582

import math


# I spotted this technique in C:\Program Files\Poser Software\Poser 11\Runtime\Python\poserScripts\SampleCallbacks\muscleMag.py

# 1) Do NOT try and modify parameter B from a parameter A callback

# 2) Instead, create a callback for parameter B and use parameter A there to do the required calculation

# 3) Parameter B does NOT have to change for the parameter B callback to be called.


# INSTRUCTIONS FOR RUNNING TEST

# -----------------------------

# 1) Load any figure and ensure its body is selected

# 2) Increase the frame count from 30 to 360

# 3) Go to frame 360 and set yRotate to -360 (yRotate increases anticlockwise when viewed from above, I want this to go clockwise)

# 4) Change camera to top view and zoom out a bit, enough to see a circle of radius 1PNU around the worldorigin.

# 5) Slide the frame counter back and forth a few times - the figure should rotates about its origin

# 6) Set the frame counter back to frame 1

# 7) Run this script

# 8)Slide the frame counter back and forth a few times - the figure will rotate along a circle radius 1PNU centred at the origin.


# Callback for the "yRotate" parameter

def cheapskatesYRotateCallback(parm,value):

return value % 360 # Go round in a circle


# Callback for the "xTran" parameter

def cheapskatesXTranCallback(parm,value):

return value - (onestep*math.sin(math.radians(parmYRot.Value())))

# Callback for the "zTran" parameter

def cheapskatesZTranCallback(parm,value):

return value - (onestep*math.cos(math.radians(parmYRot.Value())))


global parmYRot

global parmXTran

global parmZTran

global onestep


body=poser.Scene().CurrentFigure().ActorByInternalName("BODY")

parmYRot=body.Parameter("yRotate")

parmXTran=body.Parameter("xTran")

parmZTran=body.Parameter("zTran")

onestep=1.0

print "Setting up the 'yRotate' callback..."

poser.Scene().CurrentActor().Parameter("yRotate").SetUpdateCallback(cheapskatesYRotateCallback)

print "Setting up the 'xTran' callback..."

poser.Scene().CurrentActor().Parameter("xTran").SetUpdateCallback(cheapskatesXTranCallback)

print "Setting up the 'zTran' callback..."

poser.Scene().CurrentActor().Parameter("zTran").SetUpdateCallback(cheapskatesZTranCallback)

print "...done."






The 3Dcheapskate* occasionally posts sensible stuff. Usually by accident.
And it usually uses Poser 11, with units set to inches. Except when it's using Poser 6 or PP2014, or when its units are set to PNU.

*also available in ShareCG, DAZ, and HiveWire3D flavours (the DeviantArt and CGBytes flavour have been discontinued).