Below is an example demonstrating how you can create proxy properties on a node for a given set of properties on a surface, and link the properties on the surface to the corresponding properties on the node, so that the surface properties can be animated. The example also demonstrates how to embed post-load data on the node and cause a script to be executed after the node has been loaded into the scene in order to re-establish the links between the properties which are not otherwise saved.
See Also: Post-Load Material Proxy Link Properties
// DAZ Studio version 4.10.0.123 filetype DAZ Script // Define an anonymous function; // serves as our main loop, // limits the scope of variables (function(){ /*********************************************************************/ // String : A function for retrieving a translation if one exists function text( sText ) { // If the version of the application supports qsTr() if( typeof( qsTr ) != "undefined" ){ // Return the translated (if any) text return qsTr( sText ); } // Return the original text return sText; }; /*********************************************************************/ // Array<DzProperty> : A function for getting a list of the properties in a group function getGroupProperties( oGroup, bTraverse, bRecurse ) { // Declare an array to hold properties var aProperties = []; // If a group isn't passed in if( !oGroup ){ // We're done, return an empty array return aProperties; } // Get the number of proeprties in the group var nProperties = oGroup.getNumProperties(); // Pre-size the properties array aProperties = new Array( nProperties ); // Iterate over the properties, setting each element in the array for( var i = 0; i < nProperties; i += 1 ){ // Assign the property to the position in the array aProperties[ i ] = oGroup.getProperty( i ); } // If we are recursing if( bRecurse ){ // Concatenate the properties array from child groups aProperties = aProperties.concat( getGroupProperties( oGroup.getFirstChild(), true, bRecurse ) ); } // If we are traversing if( bTraverse ){ // Concatenate the properties array from sibling groups aProperties = aProperties.concat( getGroupProperties( oGroup.getNextSibling(), bTraverse, bRecurse ) ); } // Return the array of properties return aProperties; }; /*********************************************************************/ // Array<DzProperty> : A function for getting the list properties for an element function getElementProperties( oElement, bTraverse, bRecurse ) { // Get the property group tree for the element var oPropertyGroupTree = oElement.getPropertyGroups(); // If the application version is 4.9.4.101 or newer and we want all properties if( App.version64 >= 0x0004000900040065 && bTraverse && bRecurse ){ // Return the properties for the element return oPropertyGroupTree.getAllProperties(); } // Get the first group in the tree var oPropertyGroup = oPropertyGroupTree.getFirstChild(); // Return the properties for the element return getGroupProperties( oPropertyGroup, bTraverse, bRecurse ); }; /*********************************************************************/ // DzProperty : A function for finding a property associated with an element function findElementProperty( oElement, sProperty, bUseLabel ) { // Whether or not to use optimizations; 4.7.1.44 or newer var bUseOptimization = (App.version64 >= 0x000400070001002c); // Declare a working variable var oProperty; // If the application version is 4.7.1.44 or newer and we're not using // the label to find, or the application version is 4.11.0.166 or newer if( (bUseOptimization && !bUseLabel) || App.version64 >= 0x0004000b000000a6 ){ // Get the property group tree for the element var oPropertyGroupTree = oElement.getPropertyGroups(); // If we're using the label if( bUseLabel ){ // Attempt to find the property oProperty = oPropertyGroupTree.findPropertyByLabel( sProperty ); // If we're not using the label } else { // Attempt to find the property oProperty = oPropertyGroupTree.findProperty( sProperty ); } // If we found a property if( oProperty ){ // We're done, return it return oProperty; } // Otherwise } else { // Get the properties of the element var aProperties = getElementProperties( oElement, true, true ); // Iterate over the properties for( var i = 0; i < aProperties.length; i += 1 ){ // Get the 'current' property oProperty = aProperties[i]; // If we're using the label if( bUseLabel ){ // If the label of the property is the one we're looking for if( oProperty.getLabel() == sProperty ){ // We're done, return it return oProperty; } // If we're not using the label } else { // If the name of the property is the one we're looking for if( oProperty.name == sProperty ){ // We're done, return it return oProperty; } } } } return null; }; /*********************************************************************/ // DzNode : A function for getting the root of a node function getRootNode( oNode ) { // If a node is selected and it is a bone if( oNode && oNode.inherits( "DzBone" ) ){ // We want the skeleton return oNode.getSkeleton(); } // Return the original node return oNode; }; /*********************************************************************/ // String : A function for finding an absolute file path of a relative one function findAbsContentFilePath( sRelFilePath ) { // If the relative path is empty if( sRelFilePath.isEmpty() ){ // We're done... return ""; } // Get the content manager var oContentMgr = App.getContentMgr(); // Declare working variable var sFilePath; // Initialize var bHasAllEnums = (App.version64 >= 0x000400090000002e);//4.9.0.46 var nDirType = DzContentMgr.AllDirs; // Get the import manager var oImportMgr = App.getImportMgr(); // If the file type is imported if( oImportMgr.findImporter( sRelFilePath ) ){ // If the version does not provide all enumerated values if( !bHasAllEnums ){ // Update the directory type nDirType = DzContentMgr.PoserDirs | DzContentMgr.ImportDirs | 0x20; //DzContentMgr.CloudDB // Otherwise } else { // Update the directory type nDirType = DzContentMgr.PoserDirs | DzContentMgr.ImportDirs | DzContentMgr.CloudDB; } // If the file type is native } else { // If the version does not provide all enumerated values if( !bHasAllEnums ){ // Update the directory type nDirType = DzContentMgr.NativeDirs | 0x20; //DzContentMgr.CloudDB // Otherwise } else { // Update the directory type nDirType = DzContentMgr.NativeDirs | DzContentMgr.CloudDB; } } // Return the path return oContentMgr.findFile( sRelFilePath, nDirType ); }; /*********************************************************************/ // String : A function for retrieving the name of the vendor function getVendorName( sOverride ) { // Set the vendor to the registered author var sVendor = App.getCurrentAuthor().name; // If a name was specified if( !sOverride.isEmpty() ){ // Use the specified name sVendor = sOverride; } // If we still don't have a name if( sVendor.isEmpty() ){ // Use a default sVendor = "Vendor Name"; } // Return the vendor name return sVendor; }; /*********************************************************************/ // String : A function for retrieving a name for a product function getProductName( sDefault ) { // Initialize var sProductName = "Product Name"; // Create a basic dialog var wDlg = new DzBasicDialog(); wDlg.caption = text( sProductName ); // Add a label var wLbl = new DzLabel( wDlg ); wLbl.text = text( sProductName ) + ":"; wDlg.addWidget( wLbl ); // Add a line edit var wNameLEdit = new DzLineEdit( wDlg ); wNameLEdit.text = sDefault; wDlg.addWidget( wNameLEdit ); // Get the wrapped widget var oDlg = wDlg.getWidget(); // Set the object name of the wrapped widget; // this is used for recording position and size oDlg.objectName = sProductName.replace( / /g, "" ); // Get the minimum height var sizeHint = oDlg.minimumSizeHint; var nHeight = sizeHint.height; // Set the fixed height to the minimum wDlg.setFixedHeight( nHeight ); // If the user didn't cancel the dialog if( wDlg.exec() ){ // Get the name specified var sUserInput = wNameLEdit.text; // If the input is valid if( !sUserInput.isEmpty() ){ // Update the product name sProductName = sUserInput; } } // Return the product name return sProductName; }; /*********************************************************************/ // void : A function for copying the attribute of one property to another function copyPropertyAttributes( oSrcProperty, oTgtProperty, oSettings ) { // Get the attributes of the source property oSrcProperty.getAttributes( oSettings ); // Set the attributes of the target property oTgtProperty.setAttributes( oSettings ); // Clean up oSettings.clear(); }; /*********************************************************************/ // void : A function for creating a one to one link between properties function linkProperties( oSlaveProperty, oMasterProperty ) { // Link the slave property to the master property oSlaveProperty.linkTo( oMasterProperty ); }; /*********************************************************************/ // String : A function for establishing the post-load for linking properties function setLinkPropertiesPostLoad( oProxyElement, oTargetElement, aProperties ) { // Get the name of the target element var sTargetName = oTargetElement.name; // Define a unique name for our data item var sDataName = String("%1__%2__%3__LinkPropertiesPostLoad") .arg( m_sVendorName ) .arg( m_sProductName ) .arg( sTargetName ); // Get our data item var oDataItem = oProxyElement.findDataItem( sDataName ); // If we didn't find our data item if( !oDataItem ){ // Create our data item; we want it to be saved with the element oDataItem = new DzSimpleElementScriptData( sDataName, true ); // Set the path to our script oDataItem.setScriptFilePath( m_sScriptRelPath ); // Add the data item to the element oProxyElement.addDataItem( oDataItem ); } // Get the settings for the data item var oSettings = oDataItem.getSettings(); // Assign key/value pairs oSettings.setStringValue( "vendor", m_sVendorName ); oSettings.setStringValue( "product", m_sProductName ); oSettings.setStringValue( "target", sTargetName ); // Get the properties settings var oPropertySettings = oSettings.getSettingsValue( "properties" ); // If the properties settings does not exist if( !oPropertySettings ){ // Create the properties settings oPropertySettings = new DzSettings(); // If the properties settings does exist } else { // Clear the list of properties oPropertySettings.clear(); } // Declare working variables var sProperty; // Iterate over the property names for( var i = 0, nProps = aProperties.length; i < nProps; i += 1 ){ // Get the 'current' property name sProperty = aProperties[ i ]; // Add the setting for the property oPropertySettings.setBoolValue( sProperty, true ); } // Assign key/value pairs oSettings.setSettingsValue( "properties", oPropertySettings ); }; /*********************************************************************/ // Define text variables for the message var sTitle = text( "Selection Error" ); var sMessage = text( "A node with geometry must be selected to continue." ); var sButton = text( "&OK" ); // Get the primary selection var oNode = getRootNode( Scene.getPrimarySelection() ); // If nothing is selected if( !oNode ){ // Inform the user MessageBox.information( sMessage, sTitle, sButton ); // We're done... return; } // Get the object for the node var oObject = oNode.getObject(); // If we don't have an object if( !oObject ){ // Inform the user MessageBox.information( sMessage, sTitle, sButton ); // We're done... return; } // Get the current shape for the object var oShape = oObject.getCurrentShape(); // If we don't have a shape if( !oShape ){ // Inform the user MessageBox.information( sMessage, sTitle, sButton ); // We're done... return; } // Get the vendor name var m_sVendorName = getVendorName( "" ); // Get the product name var m_sProductName = getProductName( "" ); // Define the post-load script name var m_sScriptName = "Post_Load_Material_Proxy_Link_Properties"; // Define the script extension var sExtension = "dsa"; // Construct the relative path of the post-load script; // by placing the script within the data folder, under a // vendor/product/item name for organizational purposes, // you have prepared it for product-ization and for being // installed via Daz Connect or Daz Install Manager var m_sScriptRelPath = String("data/%1/%2/%3.%4") .arg( m_sVendorName ) .arg( m_sProductName ) .arg( m_sScriptName ) .arg( sExtension ); // Find the post-load script within the content management system var sScriptAbsPath = findAbsContentFilePath( m_sScriptRelPath ); // If the script could not be found if( sScriptAbsPath.isEmpty() ){ // Define text variables for the message sTitle = text( "Resource Error" ); sMessage = text( "The '%1' file could not be found." ) .arg( m_sScriptRelPath ); // Inform the user MessageBox.information( sMessage, sTitle, sButton ); // We're done... return; } // Define the list of properties on the // target material to create a proxy for var oSurfaceProperties = { "Default" : [ "Diffuse Color", "Specular Color", "Ambient Color" ] }; // Create a settings object for copying property attributes var oSettings = new DzPropertySettings(); // Declare working variables var sSurface, sProperty, sProxyName, sProxyPath; var oMaterial, oMaterialProperty, oProxyProperty; var aPropertyNames, aLinkProperties; // Get the list of surface names var aSurfaceNames = Object.keys( oSurfaceProperties ); // Iterate over the surface names for( var i = 0; i < aSurfaceNames.length; i += 1 ){ // Get the 'current' name sSurface = aSurfaceNames[ i ]; // Find the material by name oMaterial = oShape.findMaterial( sSurface ); // If we didn't find the material if( !oMaterial ){ // Next!! continue; } // Initialize a list of properties aLinkProperties = []; // Get the list of property names for the surface aPropertyNames = oSurfaceProperties[ sSurface ]; // Iterate over the property names for( var j = 0; j < aPropertyNames.length; j += 1 ){ // Get the 'current' name sProperty = aPropertyNames[ j ]; // Find the property by name oMaterialProperty = oMaterial.findProperty( sProperty ); // If we didn't find the property if( !oMaterialProperty ){ // Next!! continue; } // Construct the proxy property name sProxyName = String("%1 %2 Proxy").arg( oMaterial.name ).arg( sProperty ); // Construct the proxy property path sProxyPath = String("Surfaces/%1/%2").arg( oMaterial.name ).arg( oMaterialProperty.getPath() ); oProxyProperty = findElementProperty( oNode, sProxyName, false ); if( !oProxyProperty ){ // Duplicate the property oProxyProperty = oMaterialProperty.propertyDuplicate(); oProxyProperty.name = sProxyName; oProxyProperty.setNew( true ); // Add the property to the proxy node oNode.addProperty( oProxyProperty ); } // Copy the setup of the material property to the proxy property copyPropertyAttributes( oMaterialProperty, oProxyProperty, oSettings ); oProxyProperty.setPath( sProxyPath ); oProxyProperty.setIsUserProperty( true ); // If the property is numeric if( oMaterialProperty.inherits( "DzNumericProperty" ) ){ // Link the material property to the proxy property linkProperties( oMaterialProperty, oProxyProperty ); oProxyProperty.setCanAnimate( true ); // Capture the name of the property aLinkProperties.push( oMaterialProperty.name ); } } // If we've linked properties if( aLinkProperties.length > 0 ){ // Set/Create the post-load that will re-establish the links setLinkPropertiesPostLoad( oNode, oMaterial, aLinkProperties ); } } // Finalize the function and invoke })();