This script is an adaption of the much simpler Post-Load Script Data Item Read sample. It is highly recommended that the mechanics of said simpler sample is well understood before exploring the sample on this page and associated pages.
In short, the purpose of this script is to execute when specific elements (i.e., node, material) load into the scene and create a set of callbacks that bidirectionally link pairs of properties together so that when the value of one changes, the value of the other changes in kind and vice versa.
The sample on this page is one in a set that are designed to be used together to achieve a particular goal; to connect properties on a sphere (DzNode) and on that node's material (DzMaterial) when they are loaded into the scene to another set of properties on the NVIDIA Iray renderer (DzIrayRenderer) that control the orientation of the Environment Dome and the map assigned to said dome. This is accomplished using a DzCallBack, instead of a DzERCLink (which does not allow circular references), in order to demonstrate how pairs of properties can be bidirectionally “linked” to one another and scripts can be used to implement logic that breaks out of what would otherwise constitute an infinite loop.
The purpose of this particular script in the set is to execute when specific elements (i.e., node, material) load; to locate the other scripts involved and to create callbacks that incorporate/invoke said scripts. The other scripts involved in achieving the stated goal are:
The script on this page, as well as those linked above, include another script (i.e., “FindProperty”) that contains functions that are common to several property related samples. In particular, the sample that contains the versions of functions expected by the script on this page is Render Settings - Find Property.
Creating the “FindProperty” support script referenced throughout this set of scripts is most easily accomplished by downloading a copy of the linked sample and stripping the anonymous function that encapsulates the named functions, along with the handful of lines that trail the named functions in the body of the anonymous function, leaving you with the following named functions and the copyright/license information at the top:
getGroupProperties(…)
getElementProperties(…)
findElementProperty(…)
findRendererProperty(…)
The reason for stripping the anonymous function from the sample when using it the way we are here is so that the functions are accessible to the callback scripts; being encapsulated in an anonymous function would cause them to be inaccessible from the scope of the callback script.
This set of scripts assumes a few things:
var sScriptName = "Post_Load_Script_Data_Item_Read";
… is replaced with…
var sScriptName = "Callbacks_Element_Post_Load_Create";
oSettings.setStringValue( "node", oNode.getLabel() );
… is replaced with…
oSettings.setStringValue( "XRotate", "Dome Orientation X" ); oSettings.setStringValue( "YRotate", "Dome Orientation Y" ); oSettings.setStringValue( "ZRotate", "Dome Orientation Z" );
oSettings.setStringValue( "material", oMaterial.getMaterialName() );
… is replaced with…
oSettings.setStringValue( "Diffuse Color", "Environment Map" );
// Define an anonymous function; // serves as our main loop, // limits the scope of variables (function(){ /*********************************************************************/ // DzCallBack : A function for finding or creating a callback function getCallBack( sGroup, sName, oSender, sSignal, sCode, bAsEvent, bGarbageCollect ) { // If the callback name, sender, signal, or code are not defined if( sName.isEmpty() || !oSender || sSignal.isEmpty() || sCode.isEmpty() ){ // We are done... return undefined; } // Get the callback manager var oCallBackMgr = App.getCallBackMgr(); // Get the callback with the name var oCallBack = oCallBackMgr.getCallBack( sName ); // If we have a callback if( oCallBack ){ // Return the callback return oCallBack; } // Create a callback with the name oCallBack = oCallBackMgr.createCallBack( sName ); // We want the callback to be processed as an event oCallBack.setProcessAsEvent( bAsEvent ); // Add the callback to the group oCallBack.addToGroup( sGroup ); // Set/Embed the script in the callback oCallBack.setScript( sCode, true ); // Connect the callback to the signal on the sender oCallBack.setConnection( oSender, sSignal, bGarbageCollect ); // Return the callback return oCallBack; }; /*********************************************************************/ // DzCallBack : A function for getting the statement to find an element by ID function getSceneElementIDLookup( oElement ) { // If we do not have an element if( !oElement ){ // We are done... return "null"; } // Get the element identifier var nElementId = oElement.elementID; // If the element is a node if( oElement.inherits( "DzNode" ) ){ // Return the statement to find it in the scene return String("Scene.findNodeByElementID( %1 )").arg( nElementId ); // If the element is an object } else if( oElement.inherits( "DzObject" ) ){ // Return the statement to find it in the scene return String("Scene.findObjectByElementID( %1 )").arg( nElementId ); // If the element is a shape } else if( oElement.inherits( "DzShape" ) ){ // Return the statement to find it in the scene return String("Scene.findShapeByElementID( %1 )").arg( nElementId ); // If the element is a modifier } else if( oElement.inherits( "DzModifier" ) ){ // Return the statement to find it in the scene return String("Scene.findModifierByElementID( %1 )").arg( nElementId ); // If the element is a material } else if( oElement.inherits( "DzMaterial" ) ){ // Return the statement to find it in the scene return String("Scene.findMaterialByElementID( %1 )").arg( nElementId ); } // Return invalid return "null"; }; /*********************************************************************/ // DzCallBack : A function for finding or creating a callback for an element function getElementCallBack( oElement, sGroup, sName, oSender, sSignal, sCode, bAsEvent, bGarbageCollect ) { // If the element, callback name, sender, signal, or code are not defined if( !oElement || sName.isEmpty() || !oSender || sSignal.isEmpty() || sCode.isEmpty() ){ // We are done... return undefined; } // Get the element identifier var nElementId = oElement.elementID; // Construct a name for the callback var sCallBackName = String("%1@%2").arg( nElementId ).arg( sName ); // Get the element lookup statement var sElement = getSceneElementIDLookup( oElement ); // Define the lines of the callback script; // the element from the scene stored in a variable, // followed by the code for the callback var sScript = String("var oElement = %1;\n%2").arg( sElement ).arg( sCode ); // Return the callback return getCallBack( sGroup, sCallBackName, oSender, sSignal, sScript, bAsEvent, bGarbageCollect ); }; /*********************************************************************/ // DzCallBack : A function for finding or creating a callback between two properties function getInterPropertyCallBack( oElement, sGroup, oPropertyA, oPropertyB, sSignal, sCode, bAsEvent, bGarbageCollect ) { // If the element, either property, the signal, or code are not defined if( !oElement || !oPropertyA || !oPropertyB || sSignal.isEmpty() || sCode.isEmpty() ){ // We are done... return undefined; } // Construct a name for the callback; the element name will be prepended var sCallBackName = String("%1@%2@%3") .arg( oPropertyA.name ) .arg( oPropertyB.name ) .arg( sSignal.replace( /\(.*\)/g, "" ) ); // Define the lines of the callback script; // the name of the property stored in a variable, // followed by the code for the callback var sScript = String("var sProperty = \"%1\";\n%2") .arg( oPropertyB.name ) .arg( sCode ); // Create and return an element callback return getElementCallBack( oElement, sGroup, sCallBackName, oPropertyA, sSignal, sScript, bAsEvent, bGarbageCollect ); }; /*********************************************************************/ // DzCallBack : A function for finding or creating a callback for an element function getRenderMgrCallBack( oElement, sGroup, sSignal, oProperty, sPropSignal, sCode, bAsEvent, bGarbageCollect ) { // Construct a name for the callback; the element name will be prepended var sCallBackName = String("RenderMgr@%1@%2@%3") .arg( sSignal.replace( /\(.*\)/g, "" ) ) .arg( oProperty.name ) .arg( sPropSignal.replace( /\(.*\)/g, "" ) ); return getElementCallBack( oElement, sGroup, sCallBackName, App.getRenderMgr(), sSignal, String("var sProperty = \"%1\";\n%2") .arg( oProperty.name ) .arg( sCode ), bAsEvent, bGarbageCollect ); }; /*********************************************************************/ // Create a file info object for easy access var oFileInfo = new DzFileInfo( getScriptFileName() ); // Get the base path of this script var sBasePath = oFileInfo.path(); // Create a script var oScript = new DzScript(); // Get the path of the FindProperty script. Doing it this way, we can debug // with an ascii file and ship a binary [encrypted] file with the same // name... without having to update the contents of the script or manually // handle the file extensions. var sFindPropertyPath = oScript.getScriptFile( String("%1/FindProperty").arg( sBasePath ) ); // If the script was not found if( sFindPropertyPath.isEmpty() ){ // We are done... return; } // Include the FindProperty script include( sFindPropertyPath ); // 'DataItem' is a global transient variable, available when // this script is executed as a 'post load data item' // If we did not find the 'DataItem' global transient; // this script was executed outside the context of a 'post load data item' if( typeof( DataItem ) == "undefined" ){ // We are done... return; } // Get the owner of the data item var oElement = DataItem.getOwner(); // Get the base path of this script var sBasePath = oFileInfo.path(); // Create a unique identifier for the group of callbacks var sGroupGuid = App.createDigest( sBasePath ); // Define the lines of a callback script; only the lines that are common. // We will be creating callbacks for multiple properties, but each callback uses // functions that are common to each callback and to this script, so instead of // replicating the same code in multiple scripts we extract the common functions // (e.g., the contents of FindProperty.dsa) and inlcude it in the callback. oScript.addLine( String("var sBasePath = \"%1\";").arg( sBasePath ) ); oScript.addLine( "var oScript = new DzScript();" ); oScript.addLine( "" ); oScript.addLine( "(function(){" ); oScript.addLine( "var sCallBackPath = oScript.getScriptFile( String(\"%1/Callback_##\").arg( sBasePath ) );", 1 ); oScript.addLine( "if( sCallBackPath.isEmpty() ){", 1 ); oScript.addLine( "return;", 2 ); oScript.addLine( "}", 1 ); oScript.addLine( "", 1 ); oScript.addLine( "include( sCallBackPath );", 1 ); oScript.addLine( "})();" ); // Initialize a list of callback names var aCallBackNames = []; // Declare working variables var sPropertyNameA, sPropertyNameB, sPropertyClassA, sPropertyClassB; var oPropertyA, oPropertyB, oCallBack, oTexture; var vValue; // Get the render manager var oRenderMgr = App.getRenderMgr(); // Get the active renderer var oRenderer = oRenderMgr.getActiveRenderer(); // Get the settings for the data item var oSettings = DataItem.getSettings(); // Iterate over the settings for( var i = 0, nSettings = oSettings.getNumValues(); i < nSettings; i += 1 ){ // Get the 'current' key sPropertyNameA = oSettings.getKey( i ); // Find the 'A' property oPropertyA = findElementProperty( oElement, sPropertyNameA, false ); // If the property was not found if( !oPropertyA ){ // Next!! continue; } // Get the 'current' value sPropertyNameB = oSettings.getStringValue( sPropertyNameA ); // Find the 'B' property oPropertyB = findRendererProperty( "DzIrayRenderer", "", sPropertyNameB, false, true ); // If the property was not found if( !oPropertyB ){ // Next!! continue; } // Get the class names of the properties sPropertyClassA = oPropertyA.className(); sPropertyClassB = oPropertyB.className(); // If the class names are the same if( sPropertyClassA == sPropertyClassB ){ // Create a callback for the pair of properties oCallBack = getInterPropertyCallBack( oElement, sGroupGuid, oPropertyA, oPropertyB, "currentValueChanged()", oScript.getCode() .replace( "##", "currentValueChanged_Renderer" ), true, true ); // Append the name of the callback to our list //aCallBackNames.push( oCallBack.name ); // If the callback was created if( oCallBack ){ // Get the map value of the 'B' property vValue = oPropertyB.getValue(); // If the value of the 'A' property is not the same as the 'B' property if( oPropertyA.getValue() != vValue ){ // Update the value of the 'A' property with the the value of the 'B' property oPropertyA.setValue( vValue ); } } // Create a callback for the pair of properties in the opposite direction oCallBack = getInterPropertyCallBack( oElement, sGroupGuid, oPropertyB, oPropertyA, "currentValueChanged()", oScript.getCode() .replace( "##", "currentValueChanged" ), true, false ); // Append the name of the callback to our list aCallBackNames.push( oCallBack.name ); // Create a callback for triggering signals from the property when the active renderer changes oCallBack = getRenderMgrCallBack( oElement, sGroupGuid, "activeRendererChanged(DzRenderer*)", oPropertyA, "currentValueChanged()", oScript.getCode() .replace( "##", "activeRendererChanged_currentValueChanged" ), false, true ); // Append the name of the callback to our list aCallBackNames.push( oCallBack.name ); } // If the properties are numeric and mappable if( oPropertyA.inherits( "DzNumericProperty" ) && oPropertyA.isMappable() && oPropertyB.inherits( "DzNumericProperty" ) && oPropertyB.isMappable() ){ // Create a callback for the pair of properties oCallBack = getInterPropertyCallBack( oElement, sGroupGuid, oPropertyA, oPropertyB, "mapChanged()", oScript.getCode() .replace( "##", "mapChanged_Renderer" ), true, true ); // Append the name of the callback to our list //aCallBackNames.push( oCallBack.name ); // If the callback was created if( oCallBack ){ // Get the map value of the 'B' property oTexture = oPropertyB.getMapValue(); // If the map value of the 'A' property is not the same as the 'B' property if( !pointersAreEqual( oPropertyA.getMapValue(), oTexture ) ){ // Update the map of the 'A' property with the the map of the 'B' property oPropertyA.setMap( oTexture ); } } // Create a callback for the pair of properties in the opposite direction oCallBack = getInterPropertyCallBack( oElement, sGroupGuid, oPropertyB, oPropertyA, "currentValueChanged()", oScript.getCode() .replace( "##", "mapChanged" ), true, false ); // Append the name of the callback to our list aCallBackNames.push( oCallBack.name ); // Create a callback for triggering signals from the property when the active renderer changes oCallBack = getRenderMgrCallBack( oElement, sGroupGuid, "activeRendererChanged(DzRenderer*)", oPropertyA, "mapChanged()", oScript.getCode() .replace( "##", "activeRendererChanged_mapChanged" ), false, true ); // Append the name of the callback to our list aCallBackNames.push( oCallBack.name ); } } // Restore the active renderer oRenderMgr.setActiveRenderer( oRenderer ); // Construct a list of callback names var sCallBackNames = String("var aCallBackNames = [\n\t\"%1\"\n];") .arg( aCallBackNames.join( "\",\n\t\"" ) ); // Create a callback for deleting the render manager callback when the element is deleted oCallBack = getCallBack( String("%1-gc").arg( sGroupGuid ), String("%1@destroyed").arg( oElement.elementID ), oElement, "destroyed(QObject*)", String("var sCallBackGroup = \"%1\";\n%2\n%3") .arg( sGroupGuid ) .arg( sCallBackNames ) .arg( oScript.getCode() .replace( "##", "destroyed" ) ), false, true ); // Finalize the function and invoke })();