Creating dependencies between settings on the Surfaces tab and Parameters tab
(This thread is a continuation of the "follow-on stuff" that was going on in the DS: Creating a slider on the Parameters tab that can ONLY have integer values (+follow-on stuff...) thread over on the Technical Help forum)
I've now created very simple test script to apply bidirectional linking of parameters on the Surfaces tab to parmeters on the Parameters tab.
The script was cobbled together from bits of the post_load_material_proxy_create.dsa and post_load_material_proxy_link_properties.dsa sample scripts and the DzSettings documentation, replacing the ephemeral 'DataItem' with good old hard-coded stuff.
I left the script using the bidirectional DzNumericProperty::linkTo() to set up the links because I couldn't work out how to create a unidirectional DzERCLink (the DzERCLink constructors creating 'controllers' is one level of Object Obfuscation beyond me at present!)
The main problem I'd come up against over on the other thread was that applying a material preset sometimes (i.e. under certain criteria) breaks these links.
What I'm trying to do is find a way to maintain (or reinstate) these links when (after) a material preset has been applied.
Use of the DzCallBack was proposed as a solution, but I really can't get my head around that stuff.
So that's the introduction to what this thread is about.
Here's the script in-line (edit: replacing tabs with ' ', using the 'insert code snippet' icon next to image on the forum post editor, and using 'javascript'option , as suggested by Syrus_Dante) if you don't want to download it but could do with a laugh!
// This is just a simple test script to apply BIdirectional linking of parameters // HOW TO USE: // - Open DS4.8+, // - Create a primitive sphere // - apply a texture to the Diffuse Color (one that make changes to tiling offsets obvious) // - create two new float parameters on the parameters tab (in a new group/path called 'TEST',or anything you want) - names are "Vertical Offset" and "Horizontal Offset" // - run this script // The tiling offsets on the Surfaces tab are now BIdirectionally linked to the twonewsliders on the parameters tab MessageBox.information("STARTED...","INFO","OK"); var sSettings = [ '<Settings>', ' <Setting Type="Float" Key="Horizontal Offset">0</Setting>', ' <Setting Type="Float" Key="Vertical Offset">0</Setting>', '</Settings>' ].join( "\n" ); var aNodes = Scene.getSelectedNodeList(); if ( aNodes.length == 1 ){ var oNode = aNodes[0]; var sName = "Default" // The material we're going to work with if( oNode ){ var oObject = oNode.getObject(); if( oObject ){ var oShape = oObject.getCurrentShape(); if( oShape ){ var oMaterial = oShape.findMaterial( sName ); if( oMaterial ){ MessageBox.information("...FOUND MATERIAL","INFO","OK"); // var oPropertySettings = oSettings.getSettingsValue( "properties" ); var oPropertySettings = new DzSettings(); if( oPropertySettings.fromString( sSettings ) ){ MessageBox.information("Read of XML encoded data was successful.","INFO","OK"); print ("oPropertySettings = "+oPropertySettings); if( oPropertySettings ){ MessageBox.information("...FOUND PROPERTY SETTINGS","INFO","OK"); var oMaterialProperty, oProxyProperty; var sPropertyName, sProxyName; // Iterate over the properties for( var i = 0, nProps = oPropertySettings.getNumValues(); i < nProps; i += 1 ){ sPropertyName = oPropertySettings.getKey( i ); MessageBox.information("Searching for "+sPropertyName,"INFO","OK"); oMaterialProperty = oMaterial.findProperty( sPropertyName ); if( oMaterialProperty ){ MessageBox.information("...FOUND MATERIAL PROPERTY","INFO","OK"); sProxyName = String("%1 %2 Proxy").arg( oMaterial.name ).arg( sPropertyName ); sProxyName = oMaterial.name print ("sProxyName = "+sProxyName); //oProxyProperty = oNode.findProperty( sProxyName ); oProxyProperty = oNode.findProperty( sPropertyName ); if( oProxyProperty ){ MessageBox.information("...FOUND PROXY PROPERTY","INFO","OK"); if( oMaterialProperty.inherits( "DzNumericProperty" ) ){ oMaterialProperty.linkTo(oProxyProperty); MessageBox.information("...SUCCESS!...","INFO","OK"); } } } } } } else { MessageBox.information("Could not read XML encoded data.","INFO","OK"); } } } } } } MessageBox.information("...FINISHED","INFO","OK");
Edit: Note that if the "Vertical/Horizontal Offset"values on the Parameters tab and/or the Surfaces tab are given non-zero values before running the script, then the "Vertical/Horizontal Offset" values on the Surfaces tab will be set to match those on the Parameters tab after running the script. So those on the Parameters tab are the 'masters'. which makes sense from the DzNumericProperty::linkTo() documentation - you call the method on the slave property, and pass the master property,i.e. 'oMaterialProperty.linkTo(oProxyProperty);'
Comments
Hmm... interesting.
Simply adding that script as a post-load item via main menu >Edit > Object > Element Data, and then saving a material preset* seems to work.
I can create an !Iray Uber Base materials preset that calls this script to reestablish the links.
And this Iray materials preset works(and correctly reestablishes the links), even when applied to a prop which has DS Default materials (as long as it has the "Vertical/Horizontal Offset"values on the Parameters tab).
Which was my biggest problem.
Here's a snippet from the end of the materials preset showing the post-load script (edit: using 'insert code snippet' with style = 'javascript'):
I'll have to do a bit more testing, once I've had my morning coffee, to be sure that I haven't overlooked something.
But this does seem to work. And it seems much, much easier (i.e. my type of solution) than using callbacks.
*Edit: However, saving as a Figure/Prop Asset has a problem - see next post
As mentioned in the edit to the post above, simply adding that script as a post-load item via main menu >Edit > Object > Element Data, and then saving as a Figure/Prop Asset runs into a problem.
When the new prop is loaded and the post-load script is run, the node for the prop is not selected, so the script fails to reestablish the links.
I guess that's why the post_load_material_proxy_create.dsa and post_load_material_proxy_link_properties.dsa sample scripts use that DataItem (the 'settings' bit in the blue code snippet in this post on the earlier thread.
But I'm fairly certain that I got around this problem (i.e. the node that's just been loaded isn't selected) in a different way before. Either forcing it to be automatically selected when it loads, or by picking up the most recently loaded node I think ?
But how ?
Edit: Found one answer which doesn't really help - search all nodes for a name match. The script actually loaded a Poser prop PP2 with 'App.getImportMgr().readFile()', and I knew that this prop would be the only one with a specific name in the scene,so I did this:
But for this new case I expect to have several props with the same name in the scene, so this wouldn't work.
Edit2: Ah! Found this old mid-2014 thread - DAZ Script (DS4) - Identifying the node that's just been loaded, which refers back to the Can I automatically run a DSA script when I load a prop from a DUF/DSF file? thread. I managed to get what I wasdoing back then to work using that advice.
The Prop DUF had this bit to call the post-load script:
and the script started with this:
So I've sort of come full circle. Adding the 'DataItem' bit above to the start of my script should get it to work when it's run as a post-load from the prop DUF.
But if I then run same script as a post-load from a material preset DUF... ? I'll have to go and play as I can't work out whether that should or shouldn't work.
Edit: oOwner=DataItem.getOwner(); returns a scene node DzNode (oOwner.name=sphere) when called from the prop DUF, but a material DzMaterial (oOwner.name=Default) when called from the materialpreset DUF, which makes perfect sense. So the modified script works fine when calledfrom the Prop DUF, but crashes when called from a material preset DUF (oNode.getObject()doesn't work as oNode is a DzMaterial.
So next idea is to test the result of oOwner=DataItem.getOwner(). If it's a DzNode then select that node, if it's a DzMaterial then pick the currently selected node (as per the script in the OP), and if it's 'undefined' (i.e.the script's been run manually) then also pick the currently selected node.
Might work...
It does work ! It's extremely ropey, held-together-with-chewing-gum code at present*, but it does work:
- It works (and sets up the bidirectional links) when run as a post-load file from a prop with Ds Default material, and from a prop with Iray UberBase** material.
- It works (and reestablishes the bidirectional links) when run as a post-load file from a DS Default or Iray UberBase material preset, regardless of whether the prop originally/previously had a DSDefault or Iray Uner Base material.
- If a DS Default or Iray UberBase material preset WITHOUT a post-load file is applied thebidirectional links are lost. But simply running thescript manually reestablishes them
DSA attached basically so that I don't lose it !
*I do plan to tidy it up - probably replace the chewing gum with neatly cut pieces of sticky tape...
*One point - when run as a post-load from aprop with Iray Uber Base material the script is called twice, once with the DataItem 'owner' a DzNode, then again with the DataItem 'owner' a DzUberIraymaterial. Edit2: That would be because I called it twice ! Just deleted the spurious second call from the "materials" section of the PropDUF and that's resolved
Edit: One neatly cut piece of sticky tape - of course it's more logical to check for oNode.inherits("DzMaterial")rather than oNode.className()=="DzDefaultMaterial" or oNode.className()=="DzUberIrayMaterial"
Right, I'm doing some tidying up.
If the script is called as a post-load from a materials preset DUF then var oElement = DataItem.getOwner() will return something that inherits from DzMaterial.
I assume that from this I should be able to find the node for the prop to which that material is applied, yes ?
The tree for going the other way, i.e. from a prop to its material(s) is this (distilled from the Material Properties sample script):
But I'm having trouble finding out how to go the other way, i.e. oMaterial > oShape > oObject > oNode
Can anybody assist ?
DzElement has getElementParent(), but that returns 'null' from my DzMaterials (i.e. oMaterial.getElementParent() ). I don't see anything else there that might help.
The two classes from which DzElement inherits (QObject,DzBase) don't seem to have anything to help me
Edit: Looking through the online Object Index:
- I can get from DzMaterial to DzShape using DzMaterial::getShapeList() - since it returns an array I'll have to check whether it's a single shape.
- DzShape isn't in the online documentation, so I'm stuck on getting from DzShape to a DzObject.
- I can't see any DzObject methods to get to the DzNode
Nobody ?
No matter.
I've more or less finished tidying up the script, and I've modified it for my specific requirement.
Slightly less ropey than the earlier version.
The script is added to the prop as a post-load item (main menu > Edit > Object > Element Data) before saving as a Figure/Prop Asset DUF.
The script is manually added as a post-load item to the Material Preset DUFs (both 3Delight DS Default based, and Iray Uber Base based) in a text editor, as noted in the second post on this thread.
The prop loads fine with the proxies connected. If I apply a material preset (3Delight/Iray),the proxies are correctly reconnected.
So everything works beautifully.
One minor problem. If I save a prop after I've applied a material preset, the new prop has two identical post-load items - one is the prop post-load, and the other is the material post-load.
Well, that actually turns out to be the solution* !
With my script as it stands, the second time it is called from the Prop DUF (from the scene > materials bit of the file) it simply alerts the user that there is no selected node.
The answer appears to be to make the first post-load call from the Prop DUF (from the scene > nodes bit) to a different script which simply selects the node that's just been loaded, i.e.
Then when the original script is called from the scene > materials bit of the file, the correct node is already selected, and it reconnects theproxies. (The bit of script above should probably be modified to make sure that only the node we've just loaded is selected.)
And now, if I save a materials preset, the post-load from the scene > materials bit is automatically saved, so the proxies will be reconnected after applying a material preset.
*Not thoroughly tested yet,but it's looking promising...
P.S. I tried to use the 'Computer Code' style from the 'Styles' dropdownof this forum's post editor, but it does this:
// Get the node that caused this script call, i.e. the prop that's just been loaded ( http://www.daz3d.com/forums/viewthread/41175/ and http://www.daz3d.com/forums/viewthread/41334/ )<br /> if( typeof( DataItem ) != "undefined" ){<br /> var oOwner = DataItem.getOwner();<br /> if (oOwner){ <br /> // Select the prop we've just loaded<br /> oOwner.select();<br /> if (SHOWDEBUG) showDebug( "The node we've just loaded ("+oOwner.name+") is now selected");<br /> } else showError("oOwner not valid");<br /> } else showError("Undefined DataItem");
Odd (and just in case other people don't see what I see, I've added a screenshot of what I see after I posted that). So I started using both 'Typewriter' and 'Special Container'. And then Syrus_Dante reminded me of the 'insert code snippet'.
But regardless of that, it would be nice to know what I need here, as mentioned earlier:
Edit: Just found the bit in the Select by Tag sample summary that says "... an example demonstrating how to select materials that have a particular tag ... and how to use element parent information to get the node(s) that a material or shape is associated with."- so that should do the trick.
Here are the 3 functions that are required - getNodesFromMaterial(), getNodeFromElement(), and inheritsType():
Hi 3dcheapskate,
all in all a very interesting project using the example script and rewrite it to make use of those proxy-load features regarding animation controls for material and shader properties.
I hope you get the things figured out I will have a closer look at your script if I find the time.
As discussed here: Animation for surface tab timeline not working I was thinking of trying that too one day but currently I am working on another script: PowerPose Templates generator script
By quickly looking at the posted scripts, thus far I can only tell you that it would propably better readable if you use the "Insert Code Snippet" script container in the edit comment bar right next to "Image".
You copy-paste the code in there and choose a code highlighting style, I would pick Java or JavaScript, this would make the script much better readable directly on the page.
The Code Snippet container box has the disadvantage that if your code includes tabs for indents those get unbelievable 8 spaces pushig everything too far to the right. As a workaround you could convert tabs to spaces in the DS Script IDE pane right-click menu.
A hint to the forum page admins: by default tabs are 3 or 4 spaces long no one ever wants to use 8 spaces for a tab and please add a button to collapse those Code Snippet container boxes.
Regarding guessing and undocumented methods, as I discovered it is hard enougth to understand the documented scripting stuff and not even that is guaranteed to work. If you start guessing there must be other methods you should somehow be able to print out all methods and properties of a given object. I can vaguely remember Richard was posting something about that, but I can't remeber if it was about getting undocumented methods and/or properties of Dz type objects printed out.
I'm pretty shure I have bookmarked that thread with the forum but then if you have over 1000 bookmarks and I lost the overview, only getting notifications if someone posted in those threads again.
@Richard if you read this please could you give us a hint again how to get undocumented methods and/or properties of Dz type objects printed out. I know it is not good practice to use undocumented stuff because this may be a subject to change and may work or not work like intended now or in future updates. But if you get something to work with the undocumented things, that where not possible before or will not be possible like this in later versions you can always check for the DS version first.
Thanks - I think I'm almost there now.
I do remember that "Insert Code Snippet"... obvious when it's pointed out. (Edit: Ha! Forum WYSIWYG Issues from 2016 - not my title, I'd have used 'Probems' )
And now that you've mentioned it, I also remember somebody (Richard? Rob?) posting a simple way to get an object to tell you all its methods/properties. I vaguely recall asking the question before too, so I'll go and see if I can find it. (Edit2: Jag11 posted a way to do it here, way back in 2015)
From that Animation for surface tab timeline not working link, I see that MCasual has a script covering the basics of what I want to do (MatAnim for DS 1, 2, 3, 4)* - as is often the case !
Thinks: I really do have to put mCasual's DAZ Scripts alongside DAZ Script Object Index in my bookmarks !
Edit3: I now need to add these identical sliders and dependencies to several props.Doing it manually is tedious and error-prone.So I've decided to write a short script to do it for me. The process of writing a script is also tedious and error-prone for me, but once it works well enough to run for one prop it's good enough for me. I'm nearly there - I just need to work out how to do the last bit, Creating a simple DzERCLink between two new parameters
Edit4: All problems now appear to be resolved, so I just need to convert my 14 Poser props to DS-native props that use all the stuff from this thread. These are just more notes to myself:
- Rename each PP2 (DS uses the PP2 filename as the node name - it took me a while to remember that!) E.g. 'Row08(2Mat).pp2' becomes 'Books (Row Of 8) Map64.pp2'
- In the PP2s change both internal/external name of the prop to be the same as the filename
- In the PP2s change all the 'ROW08-SPINE-WIDTHS' type morph names to something less computer-code-y and without the prop-specific prefix, e.g. 'Adjust Spine Widths' (regular expression ROW..- works nicely)
- In the PP2s change 'groupNode Morph' to 'groupNode Book Adjustments''
- Import the PP2 into DS4.8 and select it
- Run 'AddParameters.dsa' to create the 4 new parametersand the 2 ERC links between them. Check the ERC is working, but be sure to set values back to 0 afterwards !
- Apply the 3Delight Pulp1 material preset (which already has the post-load 'ConnectOrReconnectExistingTilingOffsetProxies.dsa'
- Check that the 2 main controls change the texture offsets correctly. Be sure to set values back to 0 afterwards !
- Hide the two intermediate/proxy values
- Edit > Object > Element Data and add the 'SelectThisNode.dsa' post-load
- Save as a Prop/Figure Asset
- Close and restart DS4.8,load the new prop, and test it. Apply 3Delight presets and Iray presets to check that everything works.
*Actually I'm not sure... I've had a look and can't make head or tail of it. Too many functions for my poor brain to handle. Give me monolithic code any day - start at the top, carry on till you reach the bottom !