• Welcome to the Community Forums at HiveWire 3D! Please note that the user name you choose for our forum will be displayed to the public. Our store was closed as January 4, 2021. You can find HiveWire 3D and Lisa's Botanicals products, as well as many of our Contributing Artists, at Renderosity. This thread lists where many are now selling their products. Renderosity is generously putting products which were purchased at HiveWire 3D and are now sold at their store into customer accounts by gifting them. This is not an overnight process so please be patient, if you have already emailed them about this. If you have NOT emailed them, please see the 2nd post in this thread for instructions on what you need to do

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

3dcheapskate

Engaged
There's already a topic on this subject over there on the Renderosity PoserPython forum but I thought I'd ask here too.

I've detailed the problem in the comments of the script itself, which should appear below as inline code. I've also attached the script, zipped. Can anybody explain why it doesn't work as expected, and instead does what it does ?

# 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."
 

Attachments

  • !CB-INC-TEST-2.zip
    704 bytes · Views: 160

3dcheapskate

Engaged
I've accidentally made some progress...

This script (inline below, and also attached as a zip) 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."
 

Attachments

  • ! Callback Test Setup Version 582.zip
    1.1 KB · Views: 206
Last edited:

3dcheapskate

Engaged
Further progress - I think I'm getting somewhere now (the indentation was lost when I pasted the script below):

# ! Callback Test Setup Version 583
import math,random

# Version 583
# I've now got the figure to move along a randomly wiggling path, using a couple of things I spotted in these callback samples:
# 1) The frame number is used to calculate the parameter's value in C:\Program Files\Poser Software\Poser 11\Runtime\Python\poserScripts\SampleCallbacks\parmCallback.py
# 2) Global variables are used in C:\Program Files\Poser Software\Poser 11\Runtime\Python\poserScripts\SampleCallbacks\randomHeadVerts.py a
# The main reason for using globals in randomHeadVerts.py is for speed, i.e. precalculating a list of random numbers. I'll probably want to do that later, but that's not my main reason for using globals.
# I want to update xTran and zTran with a simple mathematical calculation on each frame.
# However, the callbacks are called multiple times, which means that my calculations are done multiple times, giving an incorrect result.
# By saving the previous value AND the previous frame number as globals I can get around this

# INSTRUCTIONS FOR RUNNING TEST 583
# ---------------------------------
# 1) Load any figure and ensure its body is selected (figures that face along the +ve Z axis will look best)
# 2) [Optional:Increase the frame count from 30 if you want - 30 is enough to prove the principle, but 360 is more fun]
# 3) Run this script
# 4) Use the |> Step Forward button on the Animation Controls pane to go frame by frame - the figure will move along a wiggling path
# N.B. At present this only works as intended if you step forward frame by frame, or slide the frame counter from left to right once. This is because the update check is simply 'if curframe != ###Frame:'

# Callback for the "yRotate" parameter
def cheapskatesYRotateCallback(parm,value):
global yRot
global yRotFrame
curframe = poser.Scene().Frame()
if curframe != yRotFrame:
yRot = (yRot + (30 * (random.random()-0.5))) % 360
yRotFrame = curframe
return yRot

# Callback for the "xTran" parameter
def cheapskatesXTranCallback(parm,value):
global xPos
global xPosFrame
curframe = poser.Scene().Frame()
if curframe != xPosFrame:
xPos = xPos + (onestep*math.sin(math.radians(parmYRot.Value())))
xPosFrame = curframe
return xPos

# Callback for the "zTran" parameter
def cheapskatesZTranCallback(parm,value):
global zPos
global zPosFrame
curframe = poser.Scene().Frame()
if curframe != zPosFrame:
zPos = zPos + (onestep*math.cos(math.radians(parmYRot.Value())))
zPosFrame = curframe
return zPos

# Need to recalculate yRot,XTran, and zTran each frame based on calculated ('keyed', upper figure on dial) value from previous frame, NOT the dialed ('natural', lower figure on dial)
# This (i.e. using globals) seemed the easiest way to do it, at least for my first attempt
global yRot
global yRotFrame
global xPos
global xPosFrame
global zPos
global zPosFrame

# These are just to avoid having to do 'body.Parameter("yRotate")' etc in the callbacks. There are others I could add too.
global parmXTran
global parmZTran
global onestep

# Initialize a few things
body=poser.Scene().CurrentFigure().ActorByInternalName("BODY")
parmYRot=body.Parameter("yRotate")
yRot=0
yRotFrame=0
xPos=0
xPosFrame=0
zPos=0
zPosFrame=0
parmXTran=body.Parameter("xTran")
parmZTran=body.Parameter("zTran")
onestep=0.1

# Kick off the callbacks
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."
 

Attachments

  • ! Callback Test Setup Version 583.zip
    1.6 KB · Views: 193

3dcheapskate

Engaged
Now we're cookin' ! So I think it's time to try applying this to the millipede, which was the reason I started this. I want to make the y rotation of each successive body segment equal to the previous segment's y rotation from the previous frame, and I think I now have an easy way to do that. Of course I'll have to add separate callbacks for the y rotation of each segment, but thankfully millipedes don't actually have 1000 legs... :applause:

# ! Callback Test Setup Version 584
import math,random

# Version 584
# I'm now precalculating the yRotate, xTran and zTran values, inspired by the precalculation of random numbers in the sample callback...
# - C:\Program Files\Poser Software\Poser 11\Runtime\Python\poserScripts\SampleCallbacks\randomHeadVerts.py a
# Now I can go backwards and forwards on the timeline and

# INSTRUCTIONS FOR RUNNING TEST 584
# ---------------------------------
# 1) Load any figure and ensure its body is selected (figures that face along the +ve Z axis will look best)
# 2) [Optional:Increase the frame count from 30 if you want - 30 is enough to prove the principle, but 360 is more fun - I've arbitrarily set the limit to 360 frames of precalculated values]
# 3) Run this script - the precalculated values will be displayed in the debug window.
# 4) Use the animation controls

#--------------------------------------------------------------------
# Initialize the precalculated y rotations
#--------------------------------------------------------------------
def InitYRots():
global ARRAY_SIZE
global yRotArray
global xTranArray
global zTranArray
ARRAY_SIZE=360
yRotArray = range(ARRAY_SIZE)
xTranArray = range(ARRAY_SIZE)
zTranArray = range(ARRAY_SIZE)
for i in range(ARRAY_SIZE):
if i == 0:
yRotArray = 0
xTranArray = 0
zTranArray = 0
else:
yRotArray = (yRotArray[i-1] + (30 * (random.random()-0.5))) % 360
xTranArray = xTranArray[i-1] + (onestep*math.sin(math.radians(yRotArray)))
zTranArray = zTranArray[i-1] + (onestep*math.cos(math.radians(yRotArray)))
print "Frame"+str(i)+": yR="+str(yRotArray)+", xT="+str(xTranArray)+", zT="+str(zTranArray)

#---------------------------------------------------------------------
# return the next precalculated y rotation
#---------------------------------------------------------------------
def GetYRot(frame):
global yRotArray
global ARRAY_SIZE
if frame >ARRAY_SIZE:
return yRotArray[ARRAY_SIZE]
else:
return yRotArray[frame]
#---------------------------------------------------------------------
# return the next precalculated x translation
#---------------------------------------------------------------------
def GetXTran(frame):
global xTranArray
global ARRAY_SIZE
if frame >ARRAY_SIZE:
return xTranArray[ARRAY_SIZE]
else:
return xTranArray[frame]
#---------------------------------------------------------------------
# return the next precalculated z translation
#---------------------------------------------------------------------
def GetZTran(frame):
global zTranArray
global ARRAY_SIZE
if frame >ARRAY_SIZE:
return zTranArray[ARRAY_SIZE]
else:
return zTranArray[frame]


# Callback for the "yRotate" parameter
def cheapskatesYRotateCallback(parm,value):
global yRot
global yRotFrame
curframe = poser.Scene().Frame()
if curframe != yRotFrame:
yRot = GetYRot(curframe)
yRotFrame = curframe
return yRot

# Callback for the "xTran" parameter
def cheapskatesXTranCallback(parm,value):
global xPos
global xPosFrame
curframe = poser.Scene().Frame()
if curframe != xPosFrame:
xPos = GetXTran(curframe)
xPosFrame = curframe
return xPos

# Callback for the "zTran" parameter
def cheapskatesZTranCallback(parm,value):
global zPos
global zPosFrame
curframe = poser.Scene().Frame()
if curframe != zPosFrame:
zPos = GetZTran(curframe)
zPosFrame = curframe
return zPos

# Need to recalculate yRot,XTran, and zTran each frame based on calculated ('keyed', upper figure on dial) value from previous frame, NOT the dialed ('natural', lower figure on dial)
# This (i.e. using globals) seemed the easiest way to do it, at least for my first attempt
global yRot
global yRotFrame
global xPos
global xPosFrame
global zPos
global zPosFrame

# Arrays to hold precalulated values
global ARRAY_SIZE
global yRotArray
global xTranArray
global zTranArray

# These are just to avoid having to do 'body.Parameter("yRotate")' etc in the callbacks. There are others I could add too.
global parmXTran
global parmZTran
global onestep

# Initialize a few things
body=poser.Scene().CurrentFigure().ActorByInternalName("BODY")
parmYRot=body.Parameter("yRotate")
yRot=0
yRotFrame=0
xPos=0
xPosFrame=0
zPos=0
zPosFrame=0
parmXTran=body.Parameter("xTran")
parmZTran=body.Parameter("zTran")
onestep=0.1

# Initialize the precalculated array
InitYRots()

# Kick off the callbacks
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."
 

Attachments

  • ! Callback Test Setup Version 584.zip
    1.7 KB · Views: 196
Top