User Tools

Site Tools


Element Post-Load Create Callbacks

Summary

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.

API Areas of Interest

Overview

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 FindProperty Support Script

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.

Setup

This set of scripts assumes a few things:

  • A slightly modified version of the Post-Load Script Data Item Add sample, where:
    • The line…
      var sScriptName = "Post_Load_Script_Data_Item_Read";

      … is replaced with…

      var sScriptName = "Callbacks_Element_Post_Load_Create";
    • The line…
      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" );
    • The line…
      oSettings.setStringValue( "material", oMaterial.getMaterialName() );

      … is replaced with…

      oSettings.setStringValue( "Diffuse Color", "Environment Map" );
  • The sample below, and all samples on the callback pages linked above, live in the same folder; as determined by the sample mentioned immediately above.

Example

Callbacks_Element_Post_Load_Create.dsa
// 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
})();