Finding Master Parameters (ERC) in Python



  • I'm currently writing a script to "normalize" a given pose which was dialed with some "Master Dials" like "NeckHeadTwist"
    This obviously influences the "yRotate" dials of the neck and head.

    Question is:
    If I read the "yRotate" dial value, is there a way in Python to find out which "Master Dial", and in which body part, influences (dependencies!) this particular dial ?

    Thank you!

    Karina



  • [Parameter].GetValueOperations() returns a list (with ValueOps).
    This list gives you references to involved Parameters.

    [vop.SourceParameter().Actor().Name() for vop in param.ValueOperations()] should return a list with involved actors.



  • @adp said in Finding Master Parameters (ERC) in Python:

    [Parameter].GetValueOperations()

    Thank you!
    This will help to make the script more lean, and much faster.
    But this is P11 only I suppose?
    Any chance to have this in P10/PP2014 too?
    (like this I won't need to write separate scripts for either Poser version)

    Karina



  • @karina said in Finding Master Parameters (ERC) in Python:

    I'm currently writing a script to "normalize" a given pose which was dialed with some "Master Dials" like "NeckHeadTwist"

    That would be very handy indeed.



  • @adp said in Finding Master Parameters (ERC) in Python:

    [Parameter].GetValueOperations() returns a list (with ValueOps).
    This list gives you references to involved Parameters.

    [vop.SourceParameter().Actor().Name() for vop in param.ValueOperations()] should return a list with involved actors.

    The list example is correct, but for clarity, there shouldn't be a "Get" in your initial syntax example:

    	theValOps = theParm.ValueOperations()
    

    I have a figure specific script for baking Morphforms (valueParms which control joint transforms and/or targetGeom parameters). This has a number of complications, including attempting to deal with different Poser versions with various Python API implementations.

    The complications include implementing wrapper methods, which test for the existence of certain actor and parameter methods [try:except] and use them if present, or attempt to fall back to another implementation.

    Prior to Poser 11.0.4.32467 supporting the parameter.UnaffectedValue() method, which returns the dial value of a parameter free from any valueOperation, linkParm or python callback influences, the only certain method of obtaining dial values was to record and then remove all valueOperations, gather the parameter value, and then reinstate the valueOperations which affected that parameter. That method also proved difficult due to Poser's propensity for (semi-randomly) crashing when valueOperations were removed.

    There is no mechanism yet which can reinstate callbacks, unless they are one's the user has initiated themselves. LinkParms are similarly impenetrable, without editing CR2 files and reloading figures (not a pleasant prospect).

    Prior to removing and restoring valueOperations, I even attempted to use the SciPy module's spline interpolation to derive values for valueOpKey parameters, until I discovered that there is no exact documentation for how Poser interpolates these itself, and SciPy's method is not perfectly accurate for Cubic and higher splines (those with 4 or more keys). SciPy also requires replacement of Poser's bundled NumPy, which is too old to be compatible with the currently downloadable SciPy modules, and requires both fortitude and courage to install, as well as needing to be repeated every time SMS releases a new version of Poser, which replaces any additional modules you've added to Python (apart from addons).

    Here's my devlog comment block from the module I use to define all of the parameter value methods I regularly use:

    # PoserDialValue.py
    # (c) 2014,2019 an0malaus
    # 
    # This module provides a function to determine (as accurately as possible) the actual dial setting
    # of a Poser parameter value. Initial attempts to reverse the affects of ERC influence included in
    # the parameter.Value() output were limited to linear regression. It is still not possible to fully
    # account for the effect of parameter limit applied discontinuities in the inverse ERC function,
    # but the recent exposure to python of parameter ValueOperations() has improved the situation greatly.
    # As a consequence of the need to interpolate cubic and higher equations, numpy, scipy.interpolate
    # and collections modules must be imported. Scipy is slightly problematic as it is not currently
    # bundled with Python 2.7 libraries as used internally by Poser Pro 2014 and thus must be installed
    # manually within Poser's python bundle. Note also that the quadratic spline interpolation (3 points)
    # is not an accurate replication of Poser's interpolation which is currently undocumented.
    # 
    # Using SciPy with Poser Python 2.7
    # 
    # Procedure to allow PoserPro2012 on OS X to use SciPy python package for spline interpolation
    # As determined by GeoffIX (Geoff Hicks) 15th November 2012
    # 
    # *** WARNING: NOT FOR THE FAINT OF HEART ***
    # 
    # Since SciPy relies on NumPy, the version of SciPy must be compiled against a compatible version of NumPy.
    # Unfortunately, the NumPy bundled with PoserPro2012 is too old to support the latest builds of SciPy that
    # work with the Python 2.7 installation bundled with PoserPro2012 and Mac OSX 10.7/8.
    # 
    # Thus both SciPy and NumPy packages compatible with Python 2.7 need to be built and installed in Mac OS X's
    # /Library/Python/2.7/site-packages/ folder
    # 
    # Once that's done, we need to tell PoserPro2012 to use our newer version of NumPy and the SciPy package
    # 
    # Python has several mechanisms to help it locate packages. The relevant one for this operation is the .pth file.
    # Poser Python will also locate packages contained within a folder named for the package and containing an __init__.py
    # file.
    # 
    # Within the PoserPro2012 Application (Show Package Contents in the Finder) go to Contents/Resources/site-packages
    # 
    # In here you will find a 'numpy' folder with PoserPro2012's bundled numpy package version 1.5.1 or so. This folder
    # will need to be renamed ( to oldnumpy or somesuch ) or its existence will override the numpy.pth file we will create.
    # 
    # Create a text file named scipy.pth with the content: (pointing to where you've installed the new scipy module)
    # /Library/Python/2.7/site-package/scipy
    # 
    # Create a text file named numpy.pth with the content: (pointing to where you've installed the new numpy module)
    # /Library/Python/2.7/site-package/numpy
    # 
    # NOTE:
    # Every time you install an update to PoserPro2012, you will need to check whether a replacement numpy folder has been
    # created in /Applications/Poser Pro 2012/Poser Pro 2012/Contents/Resources/site-packages to replace the one you
    # renamed to oldnumpy and remove it. The installation process does not seem to remove any extraneous files, so the
    # numpy.pth and scipy.pth added previously should remain untouched.
    #
    # v1.0	20140603	Initial module version
    # v1.1	20140605	Sudden realisation that the ValueOperation exposure allows all ERC influence to be recorded,
    #					then removed, allowing the uninfluenced dial value to be determined, before re-instatement.
    #		20140723	Implemented valueOperation removal and re-creation to avoid interpolation failure when
    #					limits are forced. set "remove" flag to False (defaults to True) for interpolated method.
    #					Added ListValueOperations() method for Python Shell debugging.
    # v1.2	20140916	Extracted and exposed RemoveValueOperations() and RestoreValueOperations() from DialValue()
    #					to allow extra-modular control of parameters without ValueOperation influence.
    # v1.3	20141022	Removed reliance on numpy, scipy and collections.
    # v1.4	20150123	Provided method to determine dial values for a range of frames without having to remove and
    #					re-apply value operations at each frame.
    #					Added exception handling to RemoveValueOperations to help debug Poser crashes in calls to
    #					DeleteValueOperation().
    # v1.5	20150713	Most crashes appear to occur when the value operations are corrupted by defining a channel
    #					dependency before the channel has been created during loading, resulting in SourceParameter()
    #					returning None. Since DeleteValueOperation() crashes Poser back to the OS when attempting to 
    #					remove such corrupted items before python gets a chance to handle any error, the solutions are
    #					to extract forward reference value operations until after both actors have been loaded and their
    #					channels created (not a problem within a figure's own actors - Poser copes with this), and to
    #					pre-test the value operation's SourceParameter() is not None and skip deletion if it is.
    # v1.6	20150720	Improve debugging output at point where an exception will be raised by SourceParameter() None.
    #					Added FigureName() method to protect against attribute exceptions if Figure is None.
    # v1.7	20150805	Added DialAnimation() method returning interpolation and continuity and keyframe status as well 
    #					as values for a specified range of frames.
    #					Import namedtuple from collections for easy keyframe tuple field extraction
    # v1.8	20160527	Use new P11.0.4 build 32467 parameter method UnaffectedValue() when available instead of deleting
    #					and recreating parameter value operations for the dial value at a single frame. Unfortunately,
    #					no UnaffectedValueFrame() method is available to get the dial values at frames other than the 
    #					current frame, so DialValueFrames and DialAnimation still remove and reapply value operations.
    # v1.9	20160908	Still no UnaffectedValueFrame() in P11.0.5 build 32974, but have DialValueFrames and DialAnimation
    #					test for operation on a single frame and use UnaffectedValue() to avoid removing valueOperations.
    #					Fix RestoreValueOperations() which seems to be using a 1 based KeyValueOp index rather than 0.
    #					Fix ListValueOperations() which also incorrectly uses a 1 base GetKey() index.
    #					Fix LogValueOperations() which also incorrectly uses a 1 base GetKey() index.
    ###############################################################################################
    

    There are also problems with trying to interpolate a dial value when the parameter has limits applied. The limits create discontinuities, which are impenetrable to spline based inverse functions.

    @karina I am happy to share this module, but without Poser 11, there are serious limitations on its usability. Let me know if you'd like to see it.



  • @adp:
    Yikes! It works in PP2014 too!

    I took me a while to learn how to deal with the result, but now I can actially read the rotations and reset the master parameters!
    .
    .
    @anomalaus:

    Thank you for the offer mate!
    But I'm afraid you and me are trying to achieve the exact opposite:

    -> YOU
    are trying to read the "unaffectedValue" of a dial, while

    -> I
    need the "absolute" value of a dial (and also read it's master parameters)
    .
    .
    .

    But now this is solved:
    I tinkered a script (not very erlegant, admittedly - rather the "village smithy" type...)
    But it does what I want for @SASHA-16, and that's all that matters to me atm.

    However, I've tried to write the script in a way that it might also work for other Poser figures!
    Just TRY!

    I'll post it here so that people like you who have far better Python skills than me can improve it, for the better of the community.

    Please pardon my unusual coding style:
    I've come a long way from an "antique" programing language.
    I'm trying to keep my old structures in order to not go completely bonkers over the Python mess...

    Take it! It's public domain!

    # A script to "normalize" a body pose created with SASHA's 
    # "Easy Pose" dials.
    # It reads the accumulated rotation values of the body parts,
    # zeroes the "Easy Pose" dials, and then writes back the 
    # absolute rotation values.
    # Now you have the same pose as before, but without any of 
    # the "Easy Pose" dials set!
    #
    #
    # --> NOTE:
    # =========
    #
    # If there are actors that you want to permanently 
    # exclude from anything this script does:
    # -> Add them to the "excludeList" below.
    #
    # If some of your master dials shall be set to 1
    # instead of zero:
    # -> Add them to the "setToOneList" below.  
    #
    # If you have other dials you want to set:
    # -> Add them to the "customDialList" below.
    #
    # IMPORTANT!
    # This special list must be written in the format
    # ["{actor1}","{morph name1}","{morph value1}","{actor2}","{morph name2}","{morph value2}"] etc.
    # --> ALWAYS use this "triple" grouping, i.e. 3 parameters per 
    # custom dial!
    #
    # !!! This script is public domain !!!
    # !!! So feel free to modify and redistribute it !!!
    #
    # Karina 22.04.2019
    
    '''
    ================================================================
    '''
    
    ##################################
    '''       USER SETTINGS:       '''
    ##################################
    
    '''
    Mind the pattern of commas and quotes:
    Example: excludeList = ["part1","part2","part3"] etc.
    
    --> IMPORTANT: YOU MUST USE THE -INTERNAL- NAMES <--
    '''
    
    excludeList = ["BODY","GoalCenterOfMass","BodyMorphs","eyeBrow","rEye","lEye","rEyeAxis","lEyeAxis","upperJaw","lowerJaw","tongueBase","tongue01","tongue02","tongue03","tongue04","tongue05","tongueTip","rBreast","lBreast","rNipple","lNipple","karinaVagina","clitHood","rMinora","lMinora","vagina","rButtCheek","lButtCheek","rButtockFix","lButtockFix"]
    
    setToOneList = ["SCTRL Pose Strength All","SCTRL Pose Strength Torso","SCTRL Pose Strength Arms","SCTRL Pose Strength Fingers","SCTRL Pose Strength Legs","SCTRL Toes Mobility","SCTRL rToe Mobility","SCTRL lToe Mobility","SCTRL Allow Hip yRotation"]
    
    customDialList = ["BODY","SCTRL Allow Hip yRotation","1","BODY","SCTRL Toes Mobility","1","BODY","SCTRL Compensate Barefoot Pose","0"]
    
    
    ###########################
    '''  END USER SETTINGS  '''
    ###########################
    
    #################################################
    '''  DO NOT CHANGE ANYTHING BELOW THIS LINE!  '''#################################################
    
    '''
    ================================================================
    '''
    
    
    import poser
    scene = poser.Scene()
    try:
    	figura = scene.CurrentFigure()
    	figuraID = figura.InternalName()
    except:
    	figura = 0
    	doNothing()
    
    
    ########################
    '''    DEF_Procs:    '''
    ########################
    
    #
    def main():
    		
    	if not figura:
    		poser.DialogSimple.MessageBox("No figure selected...")
    		return
    
    	CollarControl = poser.DialogSimple.YesNo("Work with Collar Control?\n\n-> If this isn't SASHA-16, click > NO < !")
    	if CollarControl:
    		excludeList = excludeCollars()
    	
    	try:
    		numberOfActors = 0
    		numberOfActors = countActors(numberOfActors)		
    		initArray(numberOfActors)
    		readActors()
    		zeroMasters(numberOfActors)
    		setCustoms()
    		writePose(numberOfActors)
    		
    	except:
    		poser.DialogSimple.MessageBox("An Error Occured.\n\n This pose can't be normalized!\n\n Wrong Figure?")
    	
    	scene.DrawAll()
    
    		
    #		
    def excludeCollars():
    
    	global excludeList
    	excludeList = excludeList + ["rCollar","lCollar"]
    
    	return excludeList
    
    	
    #
    def countActors(numberOfActors):
    
    	for a in figura.Actors():
    		interName = a.InternalName()[0:a.InternalName().find(":")]
    		if interName not in excludeList and a.IsBodyPart():
    			numberOfActors = numberOfActors + 1
    
    	return numberOfActors
    
    
    #	
    def initArray(numberOfActors):
    
    	global value_Array
    	# format: (actor#, Xrot, Yrot, Zrot, mastersX, mastersY, mastersZ)
    	value_Array=[[0 for x in range(7)] for y in range(numberOfActors)]
    	
    
    #	
    def readActors():
    
    	inx = 0
    	for a in figura.Actors():
    		interName = a.InternalName()[0:a.InternalName().find(":")]
    		if interName not in excludeList and a.IsBodyPart():
    			value_Array[inx][0] = a.InternalName()
    			value_Array[inx][1] = str(a.ParameterByCode(poser.kParmCodeXROT).Value())
    			value_Array[inx][2] = str(a.ParameterByCode(poser.kParmCodeYROT).Value())
    			value_Array[inx][3] = str(a.ParameterByCode(poser.kParmCodeZROT).Value())
    			value_Array[inx][4] = a.ParameterByCode(poser.kParmCodeXROT).ValueOperations()
    			value_Array[inx][5] = a.ParameterByCode(poser.kParmCodeZROT).ValueOperations()
    			value_Array[inx][6] = a.ParameterByCode(poser.kParmCodeZROT).ValueOperations()
    			inx = inx + 1
    
    			
    #
    def zeroMasters(numberOfActors):
    
    	for inx in range(0,numberOfActors):
    		for col in range (4,7):
    			if value_Array[inx][col]:
    				for p in value_Array[inx][col]:
    					parm = p.SourceParameter()
    					try:
    						if parm.IsValueParameter():
    							if parm.InternalName() in setToOneList:
    								parm.SetValue(1)
    							else:
    								parm.SetValue(0)	
    					except:
    						pass
    						
    						
    #
    def setCustoms():
    	rows = 0
    	for a in customDialList:
    		rows = rows + 1
    		
    	try:	
    		for a in range(0, rows, 3):
    			actor = figura.ActorByInternalName(customDialList[a])
    			parm = actor.Parameter(customDialList[a+1])
    			parm.SetValue(float(customDialList[a+2]))
    	except:
    		poser.DialogSimple.MessageBox("Ten thousands thundering typhoons!\nThis is an error :o/\n\nPlease check that your customDialList[] is written correctly.\n--> Mind the correct 'triplet' format!")
    		pass
    
    
    #	
    def writePose(numberOfActors):
    
    	for inx in range(0,numberOfActors):
    		actor = figura.ActorByInternalName(value_Array[inx][0])
    		actor.ParameterByCode(poser.kParmCodeXROT).SetValue(float(value_Array[inx][1]))
    		actor.ParameterByCode(poser.kParmCodeYROT).SetValue(float(value_Array[inx][2]))
    		actor.ParameterByCode(poser.kParmCodeZROT).SetValue(float(value_Array[inx][3]))
    
    
    #				
    def doNothing():
    	_void = 0
    
    	
    	
    ##################	
    '''    RUN     '''
    ##################
    
    main()
    
    

    .

    Thank you again @all for your great help!

    Karina



  • UPDATE:
    The final script has been posted here:

    https://forum.smithmicro.com/topic/9616/a-python-script-to-normalize-a-pose

    Disregard the code posted in my previous post!!!

    K