Animating Shader Values via Python



  • I'm trying to write a Python script that will link a shader node input to a parameter dial (like what happens if you "Animate" the input), but there doesn't seem to be a method for that. Is there a way to do this? Any help will be greatly appreciated! Thanks in advance.



  • I think there's an old walkthrough of how to do that on Renderosity, somewhere. (I could be wrong, but I thought it was possible, just not sure how the dial was linked to a material. It might involve manipulating frames, like an animated texture.)



  • @glossaphile @bagginsbill 's Parmatic scripts do that very thing, through specially labelled material nodes (their names have a 'PM:' prefix to allow the script to detect them) and python callback monitored valueParms created (temporary and removable) on the currently selected actor (or figure). The scripts are helpfully (IIRC) readable python. I was even able to modify the script to add a means to input colours ('PMC:' prefix) by separately specifying Red, Green & Blue values.

    I should add, though, that you can certainly animate most, if not all, material node input parameters by clicking on the key symbol to the left of the parameter's value and selecting the 'Animated' option from the pop-up menu. This created a shaderNodeParm on the actor, which can be keyframed like any other non-static parameter to animate material changes, without recourse to python scripts at all.

    The parmatic method is useful as it allows rapid prototyping of material settings, before running the script to remove the animation parameters which drive the materials, once the desired result is achieved. The material can then be saved back to the library to preserve the settings.


  • Poser Ambassadors

    Yep what anomalaus said, and I'd also like to point out that the comment in the file clearly states:

    # 	Parmatic.py
    #	by Bagginsbill
    #
    #	This script is public domain. Do whatever you want with it or
    #	any part of it!
    #
    

    In case Parmatic is unfamiliar, it serves a purpose not addressed by animated parameters: I wanted them synchronized, so that if I adjust "PM:Saturation" in a parameter dial, the numerous copies of that in all those different material zones of a figure automatically match the dial value. With animated parameters, you would have to make duplicate dials, one per material zone, and edit all of them by hand. Or, at least that was true in 2006.

    I supposed dependent parameters can address the issue of synchronization. Before you ask, I have not looked into the Python API support for those.



  • @bagginsbill thanks for your confirmation of Parmatic's code release status. I hadn't looked recently. ShaderNodeParm synchronisation through valueOperations was what I was doing before I became aware of Parmatic.

    Applying material poses which depend on animated shaderNodeparms has some strange behaviours, which have varied over the course of Poser Service Releases. If a figure has no animated shader nodes, applying the materials works as expected. If some exist and some don't, the existing ones can disappear and the absent ones are created. All very confusing for the user.

    Poser has been flaky and unstable when it comes to changing the animation status of dependent parameters from within the Material Room, though, so I still find Parmatic fulfilling a need. With the PM: parameters in place, material setting poses become very useful when one is trying to find just the right shade of hair colour and then be able to replicate it again in a different scene.

    The Python API still has no named constant defining the shaderNodeParms, though parm.TypeCode() does uniquely identify them, returning 102. Apart from that, they are entirely amenable to parm.AddValueOperation(), so scripting their dependencies is no particular drama, except, as previously noted when the user turns off the Animated status of the parameter from within the Materials Room, then the parameter actually gets deleted, rather than just unlinked from the node input.



  • Thanks for all the helpǃ I'll take a look at Parmatic. Just to keep my options open, though, how would a solution using the "addValueOperation" method work? What's the documentation for that function?



  • @glossaphile The Poser Python Reference is your go-to for Python API calls, though it can be a bit slim on worked examples. Check out some .cr2 files for more examples.

    Standard practice (what I do when I'm prototyping in Poser's Python Shell) is as follows (I'll omit all the >>> shell command prompts as the compositor makes the font too small to read when done at the start of a line):

    scene = poser.Scene()
    actor = scene.CurrentActor()
    parm = actor.Parameter('Some Parm Name')
    master = actor.Parameter('Master Parm Name')
    vo = parm.AddValueOperation( poser.kParmCodeDELTAADD, master) # This creates the valueOpDeltaAdd modifier on the parm channel and links it to master
    vo.SetDelta(1.0)

    If you want a keyed relationship (DeltaAdd is just a proportional additive factor), then use poser.kParmCodeKEY, and instead of the vo.SetDelta() use a number of

    vo.InsertKey(<key>, <value>)

    lines, where <key> a value of the master parameter, and <value> is the desired value to be added to the slave parameter's value.



  • Anomalaus, you've done this with shader node parameters before? What version of Poser do you have? When I try to follow your example, Poser crashes when I run the script.



  • @glossaphile I'm running Poser Pro 11.0.7.33999 on MacOS. I submitted many bug reports to SMS throughout the Poser Pro 2014 service releases. Poser 11 seems more stable in that regard (but still crashes on me regularly).

    Looking at your original post again, I see you want the python script to do the setup of an existing shader node input. I've never done that in the exact way you describe. I have long been hand editing cr2 files, so what I have done previously in Poser Pro 2014, is add the shader node dependencies to a material file and then load that from the library, noting that .mc6 does support animated parameters, but .mt5 does not, because it omits the channels block.

    actor $CURRENT
    	{
    	channels
    		{
    		groups
    			{
    			groupNode ShaderNode
    				{
    				parmNode 2_SkinAbs_Black
    				}
    			}
    		shaderNodeParm 2_SkinAbs_Black
    			{
    			name 2_SkinAbs_Black
    			initValue 0
    			hidden 1
    			enabled 1
    			forceLimits 1
    			min 0
    			max 1
    			trackingScale 1
    			masterSynched 0
    			keys
    				{
    				static  0
    				k  0  0
    				sl  1
    				spl
    				sm
    				}
    			interpStyleLocked 0
    			valueOpDeltaAdd
    				Figure 20
    				BODY:20
    				CTRLBlack
    			strength 1.000000
    			deltaAddDelta 1.000000
    			}
    		}
    	}
    mtlCollection
    	{
    	material 2_SkinAbs
    		{
    		...
    			node "blender" "Blender_2"
    				{
    				name "Blender_2"
    				pos 240 106
    				advancedInputsCollapsed 0
    				nodeInput "Input_1"
    					{
    					name "Input_1"
    					value 1 1 1
    					parmR NO_PARM
    					parmG NO_PARM
    					parmB NO_PARM
    					node "Simple_Color"
    					file NO_MAP
    					}
    				nodeInput "Input_2"
    					{
    					name "Input_2"
    					value 0 0 0
    					parmR NO_PARM
    					parmG NO_PARM
    					parmB NO_PARM
    					node NO_NODE
    					file NO_MAP
    					}
    				nodeInput "Blending"
    					{
    					name "Blending"
    					value 0 0 1
    					parmR "2_SkinAbs_Black" # <<< This manually named parameter matches the shaderNodeParm above
    					parmG NO_PARM
    					parmB NO_PARM
    					node NO_NODE
    					file NO_MAP
    					}
    				}
    
    

    I now think that the question you've been asking is for a python script to do what normally gets done manually in the Material Room UI by the user, i.e. click the key and select Animated, in a programmatic way. I have not seen a python API method which says "Pick me, pick me", to do this, so I would resort to having the python script write an appropriately formatted .pz2 file (.pz2 files can do anything a .mc6 can do) to poser.TempLocation() and then call scene.LoadLibraryPose(<tempFilepath>), with the appropriate actor selected in the scene. I might also check to see whether the shaderNodeParm channel already existed, and be prepared to load the material pose file a second time, to account for the behaviour I mentioned two posts back.

    As @bagginsbill reflected, back in the early days, Poser could be quite temperamental. When I was loading figures with multiple, manually coded animated material node parameters linked (as in parmR "channelName") to a single valueParm control channel, it worked on loading, but woe betide subsequently loading a material pose which affected their materials, as Poser was wont to crash. The tedious solution was to create separate, hidden shaderNodeParms for every single material whose common animated input parameters needed to be driven by a single control. Hence @bagginsbill 's Parmatic solution and my own onerous but effective hand coding.



  • Thanks for the in-depth help. I've already tried applying the materials via a PZ2 file with the dependencies coded into it, and strangely, it works just fine...until you try to save the scene after applying it to the figure. Then, Poser reliably crashes.

    Manual CR2 editing has been my go-to solution as well, but it gets tedious after a while, and now that I'm hoping to sell a content package that uses my material parameter dials, I need to automate the process if it's to have any hope of appealing to the end-user.

    After hours of frustrating work, I think I've devised a somewhat roundabout solution which I'll describe in greater detail later. For now, it's already past my bedtime.



  • Okay, if anyone's interested, here's the solution I came up with. I have a PZ2 file with the materials, parameter channels, and dependencies coded into it. I wrote a Python script that begins by saving the currently selected figure as a new CR2. It then treats the new CR2 and the PZ2 like simple text files, using basic reading/writing functions to copy the data from the PZ2 and insert it into the CR2. It then deletes the currently selected figure and loads the edited CR2.

    It's ironic, really. In order to make it work as intended in Poser, I had to resort to a work-around that relies on very few Poser-specific Python functions. Most of the functions are just generic Python stuff. The only real drawback is that it's quite slow (it takes between 5 and 10 minutes, but it gets the job done, and given that the typical end-user wouldn't have to re-do it very often, I think it'll work well enough.

    What I have is a set of dials accessible in the Pose room that control whole-figure shader options. For example, in the eye shaders, I have four maps plugged into a Color Ramp node: dark brown, lighter brown, green, and blue. The input value in each of the three eye material zones is animated, and the three resulting dials are all enslaved to a single master "Eyes" dial at a delta of 1/3. So setting the master dial to 0 gives dark brown eyes, setting it to 1 gives lighter brown eyes, setting it to 2 gives green eyes, and setting it to 3 gives blue eyes. Intermediate values give intermediate colors, so a value like 1.5 results in a kind of hazel, while 2.5 would give the character blue-green eyes. I have an analogous setup with skin color, ranging from very light European to very dark African.

    If I recall correctly, Parmatic (for which the old download link appears to be broken) required you to run the script every time you changed a value. Not so here, although to be fair, the effect of spinning the dials can only be seen by rendering. The preview is static.