User Tools

Site Tools


List Products Used

Summary

Below is an example demonstrating how to obtain a list of products in the database used by an object in the scene.

API Areas of Interest

Example

DB_List_Products_Used.dsa
// DAZ Studio version 4.9.2.27
// Define an anonymous function;
// serves as our main loop,
// limits the scope of variables
(function( aArgs ){
 
	// Initialize 'static' variables that hold modifier key state
	var s_bShiftPressed = false;
	var s_bControlPressed = false;
	var s_bAltPressed = false;
	var s_bMetaPressed = false;
 
	// If the "Action" global transient is defined, and its the correct type
	if( typeof( Action ) != "undefined" && Action.inherits( "DzScriptAction" ) ){
		// If the current key sequence for the action is not pressed
		if( !App.isKeySequenceDown( Action.shortcut ) ){
			updateModifierKeyState();
		}
	// If the "Action" global transient is not defined
	} else if( typeof( Action ) == "undefined" ) {
		updateModifierKeyState();
	}
 
	/*********************************************************************/
	// void : A function for updating the keyboard modifier state
	function updateModifierKeyState()
	{
		// Get the current modifier key state
		var nModifierState = App.modifierKeyState();
		// Update variables that hold modifier key state
		s_bShiftPressed = (nModifierState & 0x02000000) != 0;
		s_bControlPressed = (nModifierState & 0x04000000) != 0;
		s_bAltPressed = (nModifierState & 0x08000000) != 0;
		s_bMetaPressed = (nModifierState & 0x10000000) != 0;
	};
 
	/*********************************************************************/
	// void : A function for printing only if debugging
	function debug()
	{
		// If we are not debugging
		if( !s_bAltPressed ){
			// We are done...
			return;
		}
 
		// Convert the arguments object into an array
		var aArguments = [].slice.call( arguments );
 
		// Print the array
		print( aArguments.join(" ") );
	};
 
	/*********************************************************************/
	// 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;
	};
 
	/*********************************************************************/
	// Boolean : A function for testing whether or not a QObject instance
	// inherits one of a list of types
	function inheritsType( oObject, aTypeNames )
	{
		// If the object does not define the 'inherits' function
		if( !oObject || typeof( oObject.inherits ) != "function" ){
			// We are done... it is not a QObject
			return false;
		}
 
		// Iterate over the list of type names
		for( var i = 0, nTypes = aTypeNames.length; i < nTypes; i += 1 ){
			// If the object does not inherit the 'current' type
			if( !oObject.inherits( aTypeNames[i] ) ){
				// Next!!
				continue;
			}
 
			// Return the result
			return true;
		}
 
		// Return the result
		return false;
	};
 
	/*********************************************************************/
	// String : A function for converting an ECMA Object to a JSON string
	function getStringified( oObject )
	{
		return JSON.stringify( oObject, null, "\t" );
	};
 
	/*********************************************************************/
	// Array : A function for getting the keys of an ECMA Object
	function getKeys( oObject )
	{
		return Object.keys( oObject );
	};
 
	/*********************************************************************/
	// Array : A function for getting the values of an ECMA Object
	function getValues( oObject )
	{
		return getKeys( oObject )
				.map( function( sProperty ){ return oObject[ sProperty ]; } );
	};
 
	/*********************************************************************/
	// Object : A function for creating a deep copy of an ECMA Object
	function deepCopy( oObject )
	{
		// Initialize depending on whether or not it is an array
		var oCopy = (Array.isArray( oObject ) ? [] : {});
		// Iterate over each property of the object
		for( var sProperty in oObject ){
			// If the property holds an object
			if( typeof( oObject[ sProperty ] ) === "object" ){
				// Recurse
				oCopy[ sProperty ] = deepCopy( oObject[ sProperty ] );
			// Otherwise
			} else {
				// Copy the value
				oCopy[ sProperty ] = oObject[ sProperty ];
			}
		}
 
		// Return the copy
		return oCopy;
	};
 
	/*********************************************************************/
	// String : A function for getting a file error string
	function getFileErrorString( oFile )
	{
		// Initialize
		var aResult = [];
 
		// Based on the error type
		switch( oFile.error() ){
			case DzFile.NoError:
				break;
			case DzFile.ReadError:
				aResult.push( "Read Error" );
				break;
			case DzFile.WriteError:
				aResult.push( "Write Error" );
				break;
			case DzFile.FatalError:
				aResult.push( "Fatal Error" );
				break;
			case DzFile.ResourceError:
				aResult.push( "Resource Error" );
				break;
			case DzFile.OpenError:
				aResult.push( "Open Error" );
				break;
			case DzFile.AbortError:
				aResult.push( "Abort Error" );
				break;
			case DzFile.TimeOutError:
				aResult.push( "TimeOut Error" );
				break;
			case DzFile.UnspecifiedError:
				aResult.push( "Unspecified Error" );
				break;
			case DzFile.RemoveError:
				aResult.push( "Remove Error" );
				break;
			case DzFile.RenameError:
				aResult.push( "Rename Error" );
				break;
			case DzFile.PositionError:
				aResult.push( "Position Error" );
				break;
			case DzFile.ResizeError:
				aResult.push( "Resize Error" );
				break;
			case DzFile.PermissionsError:
				aResult.push( "Permissions Error" );
				break;
			case DzFile.CopyError:
				aResult.push( "Copy Error" );
				break;
		}
 
		// Append the error string
		aResult.push( oFile.errorString() );
 
		// Return the result string
		return aResult.length > 0 ? aResult.join( ": " ) : "";
	};
 
	/*********************************************************************/
	// String : A function for writing data to file
	function writeToFile( sFilename, vData, nMode )
	{
		// If we do not have a file path
		if( sFilename.isEmpty() ){
			// Return failure
			return "Empty filename.";
		}
 
		// Create a new file object
		var oFile = new DzFile( sFilename );
 
		// If the file is already open
		if( oFile.isOpen() ){
			// Return failure
			return sFilename + " is already open.";
		}
 
		// Open the file
		if( !oFile.open( nMode ) ){
			// Return failure
			return sFilename + " could not be opened. " + getFileErrorString( oFile );
		}
 
		// Initialize
		var nBytes = 0;
 
		// Based on the type of the data
		switch( typeof( vData ) ){
			case "string":
				// If we have data to write
				if( !vData.isEmpty() ){
					// Write the data to the file
					nBytes = oFile.write( vData );
				}
				break;
			case "object":
				// If we have a ByteArray
				if( inheritsType( vData, ["QByteArrayWrapper"] )
				&& vData.length > 0 ){
					// Write the data to the file
					nBytes = oFile.writeBytes( vData );
				}
				break;
		}
 
		// Close the file
		oFile.close();
 
		// Initialize
		var sError = "";
 
		// If an error occured
		if( nBytes < 0 ){
			// Provide feedback
			sError = getFileErrorString( oFile );
		}
 
		// If bytes were not written
		if( nBytes < 1 ){
			// Return failure
			return "No bytes written.";
		}
 
		// Return result
		return sError;
	};
 
	/*********************************************************************/
	// String : A function for opening the application log
	function openApplicationLog()
	{
		// Cause any data in the log buffer to be written to file
		App.flushLogBuffer();
		// Cause the log file to be shown
		App.showURL( App.getLogFilename() );
	};
 
	/*********************************************************************/
	// String : A function for getting a contextual label for a node
	function getContextualNodeLabel( oNode )
	{
		// If we do not have a node
		if( !oNode || !inheritsType( oNode, ["DzNode"] )){
			// Return an empty string
			return "";
		}
 
		// Declare working variable
		var oSkeleton;
 
		// If we have a bone
		if( inheritsType( oNode, ["DzBone"] ) ){
			// Get the skeleton
			oSkeleton = getRootNode( oNode );
 
			// Return the label that provides enough context
			return oSkeleton.getLabel() + ":" + oNode.getLabel();
		// If we have any other kind of node
		} else {
			// Return the node label
			return oNode.getLabel();
		}
	};
 
	/*********************************************************************/
	// DzNode : A function for getting the root of a node
	function getRootNode( oNode )
	{
		// If we have a node and it is a bone
		if( oNode && inheritsType( oNode, ["DzBone"] ) ){
			// We want the skeleton
			return oNode.getSkeleton();
		}
 
		// Return the original node
		return oNode;
	};
 
	/*********************************************************************/
	// DzObject : A function for getting the object for a node
	function getObjectForNode( oNode, bRoot )
	{
		// Get the node
		var oContextNode = bRoot ? getRootNode( oNode ) : oNode;
		// If we do not have a root node
		if( !oContextNode ){
			// We are done...
			return null;
		}
 
		// Get the object of the root node
		var oObject = oContextNode.getObject();
		// If we do not have an object
		if( !oObject ){
			// We are done...
			return null;
		}
 
		// Return the object
		return oObject;
	};
 
	/*********************************************************************/
	// DzShape : A function for getting the shape for a node
	function getShapeForNode( oNode, bRoot )
	{
		// Get the object of the node
		var oObject = getObjectForNode( oNode, bRoot );
		// If we do not have an object
		if( !oObject ){
			// We are done...
			return null;
		}
 
		// Get the shape of the root node
		var oShape = oObject.getCurrentShape();
		// If we do not have a shape
		if( !oShape ){
			// We are done...
			return null;
		}
 
		// Return the shape
		return oShape;
	};
 
	/*********************************************************************/
	// DzGeometry : A function for getting the geometry for the root of a node
	function getGeometryForNode( oNode, bRoot, bCached )
	{
		// Get the shape of the root node
		var oShape = getShapeForNode( oNode, bRoot );
		// If we do not have a shape
		if( !oShape ){
			// We are done...
			return null;
		}
 
		// If we are getting the cached geometry
		if( bCached ){
			// Update the working mesh
			//oShape.invalidateWorkingMesh();
 
			// Get the object of the root node
			var oObject = getObjectForNode( oNode, bRoot );
 
			// Return the geometry
			return oObject.getCachedGeom();
		}
 
		// Get the geometry of the root node
		var oGeometry = oShape.getGeometry();
		// If we do not have a geometry
		if( !oGeometry ){
			// We are done...
			return null;
		}
 
		// Return the geometry
		return oGeometry;
	};
 
	/*********************************************************************/
	// Array<DzNode> : A function for getting the followers of a node
	function getFollowNodes( oNode )
	{
		// If we do not have a node
		if( !oNode ){
			// Return an empty list
			return [];
		}
 
		// If we have a bone
		if( inheritsType( oNode, ["DzBone"] ) ){
			// Get the root node
			oNode = getRootNode( oNode );
		}
 
		// If we have a skeleton
		if( inheritsType( oNode, ["DzSkeleton"] ) ){
			// Get the number of skeletons following this one
			var nFollowers = oNode.getNumFollowSkeletons();
 
			// Pre-size the results array
			var aResult = new Array( nNodes );
 
			// Iterate over the followers
			for( var i = 0; i < nFollowers; i += 1 ){
				// Get the 'current' follower
				oFollower = oNode.getFollowSkeleton( i );
 
				// Assign the node to the result
				aResult[i] = oFollower;
			}
 
			// Return the list
			return aResult;
		}
 
		// Return an empty list
		return [];
	};
 
	/*********************************************************************/
	// Array<DzNode> : A function for getting the nodes parented to a node
	function getNodeChildren( oNode, bRecurse )
	{
		// If we have a node
		if( oNode && inheritsType( oNode, ["DzNode"] ) ){
			// Return a list of the node children
			return oNode.getNodeChildren( bRecurse );
		}
 
		// Return an empty list
		return [];
	};
 
	/*********************************************************************/
	// Array<DzNode> : A function for extracting unique nodes
	function getUniqueNodes( aNodes, bRoots, bFollowers, bParented, bRecursive )
	{
		// If we do not have an array
		if( typeof( aNodes ) != "object" || !Array.isArray( aNodes ) ){
			// We are done...
			return [];
		}
 
		// Get the number of nodes in the list
		var nNodes = aNodes.length;
 
		// Declare working variables
		var oNode;
		var aSubNodes;
 
		// If we are processing followers or parented nodes
		if( bFollowers || bParented ){
			// Iterate over the nodes
			for( var i = 0; i < nNodes; i += 1 ){
				// Get the 'current' node
				oNode = aNodes[ i ];
 
				// If we are interested in followers
				if( bFollowers ){
					// Get the followers
					aSubNodes = getFollowNodes( oNode );
 
					// If we are recursing
					if( bRecursive ){
						// Get the unique nodes of the followers
						aSubNodes = getUniqueNodes( aSubNodes,
								false, bFollowers, bParented, bRecursive );
					}
 
					// Append the nodes
					aNodes = aNodes.concat( aSubNodes );
				}
 
				// If we are interested in parented nodes
				if( bParented ){
					// If we are interested in roots, recursively
					if( bRoots && bRecursive ){
						// Get the nodes in the hierarchy of the root
						aSubNodes = getNodeChildren( getRootNode( oNode ), true );
					// If we are not interested in roots
					} else {
						// Get the parented nodes
						aSubNodes = getNodeChildren( oNode, bRecursive );
					}
 
					// Append the nodes
					aNodes = aNodes.concat( aSubNodes );
				}
			}
		}
 
		// Update the number of nodes
		nNodes = aNodes.length;
 
		// Pre-size the results array
		var aResult = new Array( nNodes );
 
		// Declare working variable
		var nNodeId;
 
		// Initialize
		var oNodeIds = {};
		var nIdx = 0;
 
		// Iterate over the list of nodes
		for( var i = 0; i < nNodes; i += 1 ){
			// Get the 'current' node
			oNode = aNodes[ i ];
 
			// If we do not have a node
			if( !oNode || !inheritsType( oNode, ["DzNode"] ) ){
				// Next!!
				continue;
			}
 
			// If we are interested in root nodes
			if( bRoots ){
				// Get the root for the node
				oNode = getRootNode( oNode );
			}
 
			// Get the element ID for the node
			nNodeId = oNode.elementID;
			// If we already have that ID
			if( oNodeIds[ nNodeId ] ){
				// Next!!
				continue;
			}
 
			// Record the node for the ID
			oNodeIds[ nNodeId ] = oNode;
 
			// Assign the node to the result
			aResult[ nIdx ] = oNode;
 
			// Increment our index
			nIdx += 1;
		}
 
		// Return the list, without any empty/invalid values
		return aResult.filter( Boolean );
	};
 
	/*********************************************************************/
	// 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 is not passed in
		if( !oGroup ){
			// We are 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 );
	};
 
	/*********************************************************************/
	// Boolean : A function for testing whether an object is a scene asset
	function isSceneAsset( oObject )
	{
		// Return the result
		return inheritsType( oObject, [ "DzSceneAsset" ] );
	};
 
	/*********************************************************************/
	// Boolean : A function for testing whether a modifier is controlled by
	// a property
	function isPropertyControlledModifier( oModifier )
	{
		// Define the list of modifier types that are controlled by a property
		var aTypes = [
			"DzDFormModifier",
			"DzConditionalGraftModifier",
			"DzMeshSmoothModifier",
			"DzMorph",
			"DzPushModifier"
		];
 
		// Return the result
		return inheritsType( oModifier, aTypes );
	};
 
	/*********************************************************************/
	// Boolean : A function for testing whether a property's default value is on
	function isPropertyDefaultOn( oProperty )
	{
		// If the property is not of the types we need
		if( !inheritsType( oProperty, [ "DzFloatProperty", "DzIntProperty" ] ) ){
			// We are done...
			return false;
		}
 
		// Return whether or not the default value is non-zero
		return Math.abs( oProperty.getDefaultValue() ) > 0;
	};
 
	/*********************************************************************/
	// Boolean : A function for testing whether a modifier's default value is on
	function isModifierDefaultOn( oModifier )
	{
		// If the object is not a modifier
		if( !inheritsType( oModifier, [ "DzModifier" ] ) ){
			// We are done...
			return false;
		}
 
		// Declare working variable
		var oProperty;
 
		// If the modifier has the getValueControl() function
		if( typeof( oModifier.getValueControl ) == "function" ){
			// Get the value control property
			oProperty = oModifier.getValueControl();
		// If the modifier has the getValueChannel() function
		} else if( typeof( oModifier.getValueChannel ) == "function" ){
			// Get the value control property
			oProperty = oModifier.getValueChannel();
		// If the modifier has the getEnableSmoothingControl() function
		} else if( typeof( oModifier.getEnableSmoothingControl ) == "function" ){
			// Get the value control property
			oProperty = oModifier.getEnableSmoothingControl();
		}
 
		// If we do not have a property
		if( !oProperty ){
			// We are done...
			return false;
		}
 
		// Return whether or not the default value is non-zero
		return isPropertyDefaultOn( oProperty );
	};
 
	/*********************************************************************/
	// void : A function for collecting product information from a local absolute path
	function captureAssetPathProductInfo( sPath, bIsRelative )
	{
		// If the path is empty
		if( sPath.isEmpty() ){
			// We are done...
			return;
		}
 
		// Declare working variables
		var oContentMgr;
		var sRelPath;
 
		// If the path is relative
		if( bIsRelative ){
			// Update our variable
			sRelPath = sPath;
		// If the path is absolute
		} else {
			// Get the content manager
			oContentMgr = App.getContentMgr();
			// If we do not have a content manager
			if( !oContentMgr ){
				// We are done...
				return;
			}
 
			// Get the relative path of the file
			sRelPath = oContentMgr.getRelativePath( DzContentMgr.AllDirsAndCloud, sPath );
			// If the path is not relative
			if( sRelPath == sPath ){
				// We are done... (not mapped)
				return;
			}
		}
 
		// Get the asset manager
		var oAssetMgr = App.getAssetMgr();
		// If we do not have a asset manager
		if( !oAssetMgr ){
			// We are done...
			return;
		}
 
		// Get the products that this relative path is in
		var aProducts = oAssetMgr.findProductsForFile( sRelPath );
 
		// Declare working variables
		var oProduct;
		var sGuid;
		var aPaths;
 
		// Iterate over the products
		for( var i = 0, nProducts = aProducts.length; i < nProducts; i += 1 ){
			// Get the 'current' product
			oProduct = aProducts[ i ];
 
			// Get the GUID
			sGuid = oProduct.guid;
 
			// If the product does not have a GUID;
			// this "should" not happen, but it has
			// so we need to handle the case
			if( sGuid.isEmpty() ){
				// Use the store id and token, separated by a
				// character that is not valid in a GUID so
				// that we can detect the case in a later step
				sGuid = oProduct.store + "," + oProduct.token;
			}
 
			// Get the list of relative paths
			aPaths = s_oProductsMap[ sGuid ];
			// If the list does not exist
			if( !Array.isArray( aPaths ) ){
				// Initialize the list with the 'current' path
				aPaths = [ sRelPath ];
			// If the list does exist
			} else {
				// Assign the next item in the list
				aPaths[ aPaths.length ] = sRelPath;
			}
 
			// Update the products map
			s_oProductsMap[ sGuid ] = aPaths;
		}
	};
 
	/*********************************************************************/
	// void : A function for collecting product information from an asset URI
	function captureUriProductInfo( oUri, sDebug )
	{
		// If we do not have a DzUri
		if( !oUri || !inheritsType( oUri, [ "DzUri" ] ) ){
			// We are done...
			return;
		}
 
		// If the URI is empty
		if( oUri.isEmpty() ){
			// Provide feedback
			debug( "Empty URI:", sDebug );
 
			// We are done...
			return;
		}
 
		// Get the file path portion of the URI
		var sRelPath = oUri.filePath;
		// If we do not have a local file
		if( sRelPath.isEmpty() ){
			// Provide feedback
			debug( "File not found:", sDebug, "~", oUri.toString() );
 
			// We are done...
			return;
		}
 
		// Capture product info from the path
		captureAssetPathProductInfo( sRelPath, true );
	};
 
	/*********************************************************************/
	// void : A function for collecting product information for a scene asset
	function captureSceneAssetInfo( oObject, sDebug )
	{
		// If we do not have a scene asset
		if( !oObject || !isSceneAsset( oObject ) ){
			// We are done...
			return;
		}
 
		// Capture product info from the URI
		captureUriProductInfo( oObject.assetUri, "URI - " + sDebug );
 
		// Capture product info from the source URI (if any)
		captureUriProductInfo( oObject.assetSource, "Source - " + sDebug );
	};
 
	/*********************************************************************/
	// void : A function for collecting product information for a property
	function capturePropertyProductInfo( oProperty, oOwner )
	{
		// If we do not have a property or an owner
		if( !oProperty || !oOwner ){
			// We are done...
			return;
		}
 
		// If the owner is a modifier that provides the scene asset API
		if( inheritsType( oOwner, [ "DzModifier" ] ) && isSceneAsset( oOwner ) ){
			// Capture product info from the modifier
			captureSceneAssetInfo( oOwner, "Modifier: " + oOwner.assetId + " : " + oOwner.getName() );
		// Otherwise
		} else {
			// Capture product info from the property
			captureSceneAssetInfo( oProperty, "Property: " + oProperty.assetId + " : " + oProperty.getLabel() );
		}
 
		//Declare working variable
		var oDeltas;
 
		// If the owner is a morph
		if( inheritsType( oOwner, [ "DzMorph" ] ) ){
			// Get the deltas for the morph
			oDeltas = oOwner.getDeltas();
			// If we have deltas
			if( oDeltas ){
				// Capture product info from the HD asset
				captureUriProductInfo( oDeltas.getHDUrl(), "Morph HD: " + oOwner.assetId + " : " + oOwner.getName() );
			}
		}
 
		// If the property is not an alias
		if( !oProperty.isAlias() ){
			// We are done...
			return;
		}
 
		// Get the target property of the alias
		var oTargetProperty = oProperty.getAliasTarget();
		// Get the owner of the target property
		oTargetOwner = oTargetProperty.getOwner();
 
		// Capture product info from the target property
		capturePropertyProductInfo( oTargetProperty, oTargetOwner );
	};
 
	/*********************************************************************/
	// void : A function for collecting product information for active
	// properties associated with an element
	function capturePropertiesProductInfo( oElement )
	{
		// If we do not have an element
		if( !oElement ){
			// We are done...
			return;
		}
 
		// Declare working variables
		var oProperty, oOwner;
		var sTmpPath;
		var nType;
 
		// Get the properties available to the user by way of the selected node
		var aProperties = getElementProperties( oElement, true, true );
		// Iterate over all properties
		for( var i = 0, n = aProperties.length; i < n; i += 1 ){
			// Get the 'current' property
			oProperty = aProperties[ i ];
 
			// If we have a numeric property
			if( inheritsType( oProperty, [ "DzNumericProperty" ] ) ){
				// Capture product info from the map value (if mappable/mapped)
				captureTextureProductInfo( oProperty.getMapValue() );
			// If we have an image property
			} else if( inheritsType( oProperty, [ "DzImageProperty" ] ) ){
				// Capture product info from the value (if any)
				captureTextureProductInfo( oProperty.getValue() );
			// If we have a file property
			} else if( inheritsType( oProperty, [ "DzFileProperty" ] ) ){
				// Get the value of the property
				sTmpPath = oProperty.getValue();
				// Get the type of the property
				nType = oProperty.getType();
				// If the property is configured for consuming (open/load) a file and has a value
				if( nType != DzFileProperty.FileSave && nType != DzFileProperty.Dir && !sTmpPath.isEmpty() ){
					// Capture product info from the value
					captureAssetPathProductInfo( sTmpPath, sTmpPath != oProperty.getAbsolutePath( sTmpPath ) );
				}
			}
 
			// Get the owner of the property
			oOwner = oProperty.getOwner();	
 
			// If the owner is not a material
			if( !inheritsType( oOwner, [ "DzMaterial" ] ) ){
				// If the property's current value and raw value are the same as
				// the definition, and the property is not animated, and the
				// owner is not a modifier that is on by default
				if( oProperty.currentValueIsDefinitionValue()
				&& oProperty.rawValueIsDefinitionValue()
				&& oProperty.getNumKeys() < 2
				&& !isModifierDefaultOn( oOwner ) ){
					// Next!!
					continue;
				}
 
				/*
				// If the property is hidden
				if( oProperty.isHidden() || oProperty.isDynamicallyHidden() ){
					// Next!!
					continue;
				}
				*/
			}
 
			// Capture product info from the property
			capturePropertyProductInfo( oProperty, oOwner );
		}
	};
 
	/*********************************************************************/
	// void : A function for collecting product information from a UV set
	function captureUVSetProductInfo( oMap )
	{
		// If we do not have a map or it is not a UV Set
		if( !oMap || !inheritsType( oMap, [ "DzUVSet" ] ) ){
			// We are done...
			return;
		}
 
		// Capture product info from the UV set
		captureSceneAssetInfo( oMap, "UV Set: " + oMap.assetId + " : " + oMap.getLabel() );
	};
 
	/*********************************************************************/
	// void : A function for collecting product information from a geometry
	function captureFacetMeshProductInfo( oGeometry )
	{
		// If we do not have a geometry or it is not a facet mesh
		if( !oGeometry || !inheritsType( oGeometry, [ "DzFacetMesh" ] ) ){
			// We are done...
			return;
		}
 
		// Capture product info from the geometry
		captureSceneAssetInfo( oGeometry, "Mesh: " + oGeometry.assetId + " : " + oGeometry.getName() );
 
		// Get the active UVs
		var oActiveUVs = oGeometry.getUVs();
		// Capture product info for the active UVs
		captureUVSetProductInfo( oActiveUVs );
 
		// Get the number of UV sets
		var nUVSets = oGeometry.getNumUVSets();
 
		// If we only have one UV set, or the active one is the default
		if( nUVSets == 1 || oActiveUVs.name == "default" ){
			// We are done...
			return;
		}
 
		// Declare working variable
		var oUVs;
 
		// Iterate over the UV sets
		for( var i = 0; i < nUVSets; i += 1 ){
			// Get the 'current' UV set
			oUVs = oGeometry.getUVSet( i );
 
			// If it is not the default
			if( oUVs.name != "default" ){
				// Next!!
				continue;
			}
 
			// Capture product info for the UV set
			captureUVSetProductInfo( oUVs );
 
			// We are done...
			break;
		}
	};
 
	/*********************************************************************/
	// void : A function for collecting product information from a layered texture
	function captureLayeredTextureProductInfo( oTexture )
	{
		// If we do not have a texture or it is not a layered texture
		if( !oTexture || !inheritsType( oTexture, [ "DzLayeredTexture" ] ) ){
			// We are done...
			return;
		}
 
		// Declare working variables
		var oLayer;
		var oMask;
 
		// Iterate over the layers
		for( var i = 0, n = oTexture.getNumLayers(); i < n; i += 1 ){
			// Get the 'current' layer
			oLayer = oTexture.getLayer( i );
			// Capture product info from the image path
			captureAssetPathProductInfo( oLayer.imageFile, false );
 
			// Get the mask for the layer
			oMask = oLayer.getMask();
			// If we have a mask
			if( oMask ){
				// Capture product info from the image path
				captureAssetPathProductInfo( oMask.imageFile, false );
			}
		}
	};
 
	/*********************************************************************/
	// void : A function for collecting product information from a texture
	function captureTextureProductInfo( oTexture )
	{
		// If we do not have a texture
		if( !oTexture ){
			// We are done...
			return;
		}
 
		// Get the content manager
		var oContentMgr = App.getContentMgr();
 
		// Get the path of the file
		var sAbsPath = oTexture.getFilename();
 
		// Get the relative path of the file; so that we can resolve
		// the original casing of assets installed via Daz Connect
		var sRelPath = oContentMgr.getRelativePath( DzContentMgr.AllDirsAndCloud, sAbsPath );
 
		// Get the (absolute) asset URI
		var oUri = oContentMgr.getAbsoluteUri( DzContentMgr.AllDirsAndCloud, sRelPath );
		// Get the (relative) file path portion of the asset URI
		sRelPath = oUri.filePath;
 
		// If the texture is not of a type that provides the scene asset API
		if( !isSceneAsset( oTexture ) ){
			// If we do not have a local file
			if( sRelPath.isEmpty() ){
				// Provide feedback
				debug( "Path not relative -", "Texture: " + sAbsPath );
 
				// Capture product info from the absolute path
				captureAssetPathProductInfo( sAbsPath, false );
 
				// We are done...
				return;
			}
 
			// Capture product info from the asset path
			captureAssetPathProductInfo( sRelPath, true );
 
			// We are done...
			return;
		}
 
		// If we do not have a local file
		if( sRelPath.isEmpty() ){
			// Provide feedback
			debug( "File not found -", "Texture: " + oTexture.assetId, ":", sAbsPath );
 
			// We are done...
			return;
		}
 
		// Capture product info from the file assigned to the texture
		captureAssetPathProductInfo( sRelPath, true );
 
		// Capture product info from the texture object
		captureSceneAssetInfo( oTexture, "Texture: " + oTexture.assetId + " : " + sRelPath );
 
		// Capture product info for the layers in a layered texture
		captureLayeredTextureProductInfo( oTexture );
	};
 
	/*********************************************************************/
	// void : A function for collecting product information from materials
	function captureMaterialsProductInfo( oShape )
	{
		// If we do not have a shape
		if( !oShape ){
			// We are done...
			return;
		}
 
		// Declare working variables
		var oMaterial;
		var aMaps;
 
		// Get the materials on the current shape
		var aMaterials = oShape.getAllMaterials();
		// Iterate over the materials
		for( var i = 0, nMaterials = aMaterials.length; i < nMaterials; i += 1 ){
			// Get the 'current' material
			oMaterial = aMaterials[ i ];
 
			// Capture product info from the active UV set
			captureUVSetProductInfo( oMaterial.getActiveUVSet( oShape ) );
 
			// Get all the maps used by the material
			aMaps = oMaterial.getAllMaps();
			// Iterate over the maps
			for( var j = 0, nMaps = aMaps.length; j < nMaps; j += 1 ){
				// Capture product info from the map
				captureTextureProductInfo( aMaps[ j ] );
			}
 
			// Capture product info from the material
			captureSceneAssetInfo( oMaterial, "Material: " + oMaterial.assetId + " : " + oMaterial.getLabel() );
 
			// We do not need to do this because it is already being handled
			// by the textures and a material's properties reside in the same
			// asset as the material
 
			// Capture product info from properties on the material
			//capturePropertiesProductInfo( oMaterial );
		}
 
		// Declare working variables
		var oProvider;
 
		// Get the list of simulation settings provider names
		var aSimProviderNames = oShape.getSimulationProviderNames();
		// Iterate over the simulation provider names
		for( var i = 0, nProviders = aSimProviderNames.length; i < nProviders; i += 1 ){
			// Get the 'current' simulation settings provider
			oProvider = oShape.findSimulationSettingsProvider( aSimProviderNames[ i ] );
 
			// Capture product info from the simulation settings provider
			captureSceneAssetInfo( oProvider, "Provider: " + oProvider.assetId + " : " + oProvider.getName() );
 
			// Capture product info from properties on the provider
			capturePropertiesProductInfo( oProvider );
		}
	};
 
	/*********************************************************************/
	// void : A function for collecting product information from modifiers
	function captureModifiersProductInfo( oObject )
	{
		// If we do not have a object
		if( !oObject ){
			// We are done...
			return;
		}
 
		// Declare working variable
		var oModifier;
 
		// Iterate over the modifiers
		for( var i = 0, nModifiers = oObject.getNumModifiers(); i < nModifiers; i += 1 ){
			// Get the 'current' modifier
			oModifier = oObject.getModifier( i );
 
			// If the modifier is not of a type that provides the scene asset API
			if( !isSceneAsset( oModifier ) ){
				// Next!!
				continue;
			}
 
			// If the modifier is of a type that is controlled by a property
			if( isPropertyControlledModifier( oModifier ) ){
				// Next!!
				continue;
			}
 
			// Capture product info from the modifier
			captureSceneAssetInfo( oModifier, "Modifier: " + oModifier.assetId + " : " + oModifier.getName() );
 
			// We do not need to do this because it is already being handled
			// by the properties being handled by the node
 
			// Capture product info from properties on the modifier
			//capturePropertiesProductInfo( oModifier );
		}
	};
 
	/*********************************************************************/
	// void : A function for collecting product information from a node
	function captureNodeProductInfo( oNode )
	{
		// Get the asset manager
		var oAssetMgr = App.getAssetMgr();
		// If we do not have an asset manager
		if( !oAssetMgr ){
			// We are done...
			return;
		}
 
		// Get the asset URI path for the node
		var sUri = oAssetMgr.getAssetUriForNode( oNode );
		// Create a URI from the path
		var oUri = new DzUri( sUri );
		// Get the file path portion of the asset URI
		var sRelPath = oUri.filePath;
		// If the path is empty
		if( !sRelPath.isEmpty() ){
			// Capture product info from the asset path
			captureAssetPathProductInfo( sRelPath, true );
		}
 
		// Capture product info from properties on the node
		capturePropertiesProductInfo( oNode );
	};
 
	/*********************************************************************/
	// Object : A function for getting products information
	function getProductsInfo( bUnified )
	{
		// Initialize
		var oProducts = {};
 
		// Get the asset manager
		var oAssetMgr = App.getAssetMgr();
		// If we do not have an asset manager
		if( !oAssetMgr ){
			// We are done...
			return oProducts;
		}
 
		// Declare working variables
		var oProduct, oProductInfo;
		var aGuids, aPaths;
		var sGuid, sStore, sToken;
		var nIdx;
 
		// Get the list of product GUIDs
		var aGuids = getKeys( s_oProductsMap );
		// Iterate over the list of GUIDs
		for( var i = 0, nGuids = aGuids.length; i < nGuids; i += 1 ){
			// Get the 'current' GUID
			sGuid = aGuids[ i ];
 
			// Check whether or not we have a real GUID;
			// comma is not a valid GUID character, so if
			// we find one, we assume that what we really
			// have is a store ID and product token
			nIdx = sGuid.indexOf( "," );
 
			// If we have a valid GUID; we did not find a comma
			if( nIdx < 0 ){
				// Get the product for the GUID
				oProduct = oAssetMgr.findProductByGuid( sGuid );
			// If we do not have a valid GUID; we found a comma
			} else {
				// Get the store ID
				sStore = sGuid.substring( 0, nIdx );
				// Get product token
				sToken = sGuid.substring( nIdx + 1 );
				// Get the product for the store ID and token combination
				oProduct = oAssetMgr.findProductByStoreToken( sStore, sToken );
			}
 
			// If no product was found
			if( !oProduct ){
				// Provide feedback
				print( "Product", sGuid, "could not be found." );
 
				// Next!!
				continue;
			}
 
			// Initialize; capture the title
			oProductInfo = { "title" : oProduct.title };
 
			// If this is not the 'LOCAL_USER' product
			if( !oProduct.isLocalUser ){
				// Capture the store
				oProductInfo[ "store" ] = oProduct.store;
				// Capture the token - SKU, id, etc
				oProductInfo[ "token" ] = oProduct.token;
			}
 
			//If we are unifying the results
			if( bUnified ){
				// Capture the product GUID
				oProductInfo[ "guid" ] = sGuid;
			}
 
			// Get the list of paths
			aPaths = s_oProductsMap[ sGuid ];
			// Sort and remove duplicates
			aPaths = aPaths.sort().filter(
				function( sValue, nIdx, aList ){
					return !nIdx || sValue != aList[ nIdx - 1 ];
				});
 
			// Capture the paths
			oProductInfo[ "files" ] = aPaths;
 
			// Capture the product info
			oProducts[ sGuid ] = oProductInfo;
		}
 
		// Return the result
		return oProducts;
	};
 
	/*********************************************************************/
	// Object : A function for getting products information for a node
	function getNodeProductsMap( oNode, bUnified )
	{
		// Declare working variable
		var oProductsMap;
 
		// If we are unifying the results and there is something to copy
		if( bUnified && getKeys( s_oProductsMap ).length > 0 ){
			// Copy the current products map
			oProductsMap = deepCopy( s_oProductsMap );
		}
 
		// Initialize for this node
		s_oProductsMap = {};
 
		// Capture product information
		captureNodeProductInfo( oNode );
 
		// We do not need to do this because it is already being handled
		// by the properties being handled by the node
 
		// Get the object 
		var oObject = getObjectForNode( oNode, false );
 
		// If we have an object
		if( oObject ){
			// Capture product information
			captureModifiersProductInfo( oObject );
		}
 
		// Get the current shape 
		var oShape = getShapeForNode( oNode, false );
		// If we have a shape
		if( oShape ){
			// Capture product information
			captureMaterialsProductInfo( oShape );
		}
 
		// Get the geometry 
		var oGeometry = getGeometryForNode( oNode, false, false );
		// If we have a geometry
		if( oGeometry ){
			// Capture product information
			captureFacetMeshProductInfo( oGeometry );
		}
 
		// Get the product GUIDs
		var aGuids = getKeys( s_oProductsMap );
 
		// Declare working variables
		var sGuid;
		var aMapFiles, aNodeFiles;
		var bHasMapFiles, bHasNodeFiles;
 
		// If we have a stored products map
		if( oProductsMap ){
			// Iterate over the product GUIDs
			for( var i = 0, nGuids = aGuids.length; i < nGuids; i += 1 ){
				// Get the 'current' GUID
				sGuid = aGuids[ i ];
 
				// Get the stored map file list
				aMapFiles = oProductsMap[ sGuid ];
				bHasMapFiles = Array.isArray( aMapFiles );
 
				// Get the node file list
				aNodeFiles = s_oProductsMap[ sGuid ];
				bHasNodeFiles = Array.isArray( aNodeFiles );
 
				// If we have lists to merge
				if( bHasMapFiles && bHasNodeFiles ){
					// Append this node files to the map
					aMapFiles = aMapFiles.concat( aNodeFiles );
					// Update the map
					oProductsMap[ sGuid ] = aMapFiles;
				// If we only have node files
				} else if( bHasNodeFiles ){
					// Update the map
					oProductsMap[ sGuid ] = aNodeFiles;
				}
			}
 
			// Update the product map
			s_oProductsMap = deepCopy( oProductsMap );
		}
 
		// If we are unifying the results
		if( bUnified ){
			// Return the product GUIDs
			return aGuids;
		}
 
		// Initialize
		var oNodeInfo = {};
 
		// Capture the node label
		oNodeInfo[ "node" ] = getContextualNodeLabel( oNode );
 
		// Capture the products used by the node
		oNodeInfo[ "products" ] = getProductsInfo( bUnified );
 
		// Return the node products information
		return oNodeInfo;
	};
 
	/*********************************************************************/
	// Object : A function for getting unified products information
	function getUnifiedProductsMap( oNodeGuidMap )
	{
		// Get the products information
		var oProductsMap = getProductsInfo( true );
 
		// Declare working variables
		var sNodeID, sNodeLabel, sGuid;
		var oNodeInfo, oInfo;
		var aGuids, aNodes;
 
		// Get the node IDs
		var aNodeIDs = getKeys( oNodeGuidMap );
		// Iterate over the node IDs
		for( var i = 0, nNodeIDs = aNodeIDs.length; i < nNodeIDs; i += 1 ){
			// Get the 'current' node ID
			sNodeID = aNodeIDs[ i ];
			// Get the node info
			oNodeInfo = oNodeGuidMap[ sNodeID ];
			// Get the node label
			sNodeLabel = oNodeInfo[ "label" ];
			// Get the product GUIDs
			aGuids = oNodeInfo[ "guids" ];
			// Iterate over the GUIDs
			for( var j = 0, nGuids = aGuids.length; j < nGuids; j += 1 ){
				// Get the 'current' GUID
				sGuid = aGuids[ j ];
				// Get the product info
				oInfo = oProductsMap[ sGuid ];
				// Get the list of nodes
				aNodes = oInfo[ "nodes" ];
				// If the list does not exist
				if( !Array.isArray( aNodes ) ){
					// Initialize the list
					aNodes = [ sNodeLabel ];
				// If the list does exist
				} else {
					// Assign the next item in the list
					aNodes[ aNodes.length ] = sNodeLabel;
				}
 
				// Update the list of nodes
				oInfo[ "nodes" ] = aNodes;
 
				// Update the product info
				oProductsMap[ sGuid ] = oInfo;
			}
		}
 
		// Return the unified 
		return oProductsMap;
	};
 
	/*********************************************************************/
	// Construct the message
	var sMessage = text( "This script accepts exactly 1 argument - an ECMA Object.  " +
		"This Object may define five (5) optional members, named 'target_path', " +
		"'unify_products', 'selected_only', 'open_results' and 'run_silent'.  The " +
		"values of the 'unify_products', 'selected_only', 'open_results' and 'run_silent' " +
		"members must be of the Boolean type.  The 'unify_products' member is used " +
		"to control whether or not results are presented in the context of products " +
		"(true), or the context of nodes (false).  The 'selected_only' member is used " +
		"to control whether or not only the selected nodes will be evaluated (true), " +
		"or whether the entire scene is evaluated (false).  The 'open_results' member " +
		"is used to control whether or not results should be opened upon completion - " +
		"when 'target_path' is defined.  The 'run_silent' member is used to control " +
		"whether or not modal messages to the user are supressed.\n\n" +
		"Example:\n" +
		"{\n" +
		"\t\"target_path\": \"C:/Temp/ProductsUsed.json\",\n" +
		"\t\"unify_products\": true,\n" +
		"\t\"selected_only\": false,\n" +
		"\t\"open_results\": false,\n" +
		"\t\"run_silent\": false\n" +
		"}" );
 
	var sTitle = text( "Usage" );
	var sOk = text( "&OK" );
 
	// Define the psth of the results file
	var sFile = "";
 
	// Define whether or not we want to unify (combine)
	// the node product result(s) in a single product map
	var bUnifyProducts = true;
 
	// Define whether or not to limit which nodes are processed
	var bSelectedNodes = false;
 
	// Define whether or not to open the results
	var bOpenResults = false;
 
	// Define whether or not to suppress modal prompting
	var bSilent = false;
 
	// Declare working variables
	var vArg0;
	var vFile;
	var vUnifyProducts;
	var vSelectedOnly;
	var vOpenResults;
	var vSilent;
 
	// If this script was executed remotely
	if( aArgs.length > 0 ){
		// Get the first argument
		vArg0 = aArgs[0];
		// If it is an ECMA Object
		if( typeof( vArg0 ) == "object" ){		
			// If it has a member named "target_path"
			if( vArg0.hasOwnProperty( "target_path" ) ){
				// Get the "target_path" value
				vFile = vArg0[ "target_path" ];
				// If "target_path" is not a string
				if( typeof( vFile ) != "string" ){
					// Inform the user
					MessageBox.information( text( sMessage ), text( sTitle ), sOk );
 
					// We are done...
					return;
				}
 
				// Capture the value
				sFile = vFile;
 
				// If the value does not end with ".json"
				if( !sFile.endsWith( ".json" ) ){
					// Inform the user
					MessageBox.information( text( sMessage ), text( sTitle ), sOk );
 
					// We are done...
					return;
				}
			}
 
			// If it has a member named "unify_products"
			if( vArg0.hasOwnProperty( "unify_products" ) ){
				// Get the "unify_products" value
				vUnifyProducts = vArg0[ "unify_products" ];
				// If "unify_products" is not a Boolean
				if( typeof( vUnifyProducts ) != "boolean" ){
					// Inform the user
					MessageBox.information( text( sMessage ), text( sTitle ), sOk );
 
					// We are done...
					return;
				}
 
				// Capture the value
				bUnifyProducts = vUnifyProducts;
			}
 
			// If it has a member named "selected_only"
			if( vArg0.hasOwnProperty( "selected_only" ) ){
				// Get the "selected_only" value
				vSelectedOnly = vArg0[ "selected_only" ];
				// If "selected_only" is not a Boolean
				if( typeof( vSelectedOnly ) != "boolean" ){
					// Inform the user
					MessageBox.information( text( sMessage ), text( sTitle ), sOk );
 
					// We are done...
					return;
				}
 
				// Capture the value
				bSelectedNodes = vSelectedOnly;
			}
 
			// If it has a member named "open_results"
			if( vArg0.hasOwnProperty( "open_results" ) ){
				// Get the "open_results" value
				vOpenResults = vArg0[ "open_results" ];
				// If "open_results" is not a Boolean
				if( typeof( vOpenResults ) != "boolean" ){
					// Inform the user
					MessageBox.information( text( sMessage ), text( sTitle ), sOk );
 
					// We are done...
					return;
				}
 
				// Capture the value
				bOpenResults = vOpenResults;
			}
 
			// If it has a member named "run_silent"
			if( vArg0.hasOwnProperty( "run_silent" ) ){
				// Get the "run_silent" value
				vSilent = vArg0[ "run_silent" ];
				// If "run_silent" is not a Boolean
				if( typeof( vSilent ) != "boolean" ){
					// Inform the user
					MessageBox.information( text( sMessage ), text( sTitle ), sOk );
 
					// We are done...
					return;
				}
 
				// Capture the value
				bSilent = vSilent;
			}
		}
	}
 
	// Declare working variables
	var oNode;
	var aNodes;
 
	// Define a 'static' variable to collect product data
	var s_oProductsMap = {};
 
	// Define how to handle selection
	var bRoots = true;
	var bFollowers = true;
	var bParented = true;
	var bRecursive = true;
 
	// If we are processing the selected nodes only
	if( bSelectedNodes ){
		// Get the primary selection
		oNode = Scene.getPrimarySelection();
		// If nothing is selected
		if( !oNode ){
			// Define the message
			sTitle = text( "Selection Error" );
			sMessage = text( "A node in the scene must be selected to continue." );
 
			// If prompts are not being supressed
			if( !bSilent && App.showPrompts() ){
				// Inform the user
				MessageBox.information( sMessage, sTitle, sOk );
			// If the application is not showing prompts
			} else {
				// Provide feedback
				print( sTitle, ":", sMessage );
 
				// If we are opening the result
				if( bOpenResults ){
					// Cause the log file to be shown
					openApplicationLog();
				}
			}
 
			// We are done..
			return;
		}
 
		// Get the list of unique selected nodes
		aNodes = getUniqueNodes( Scene.getSelectedNodeList(),
				bRoots, bFollowers, bParented, bRecursive );
	// If we are processing all nodes in the scene
	} else {
		// Get the list of unique nodes;
		// if bRoots is true, bones of a skeleton are
		// not independently evaluated - they are promoted
		// to the owning skeleton; if bRoots is false, all
		// nodes in the scene are independently considered
		aNodes = getUniqueNodes( Scene.getNodeList(),
				bRoots, false, false, false );
	}
 
	// Let the user know we are busy
	setBusyCursor();
 
	// Declare working variable
	var oNodeInfo;
	var sData;
 
	// Get the number of nodes
	var nNodes = aNodes.length;
 
	// Define an array for capturing non-unified results; pre-size
	var aNonUnified = bUnifyProducts ? new Array( nNodes ) : [];
 
	// Initialize
	var oNodeGuidMap = {};
 
	// Iterate over the root nodes
	for( var i = 0; i < nNodes; i += 1 ){
		// Get the 'current' node
		oNode = aNodes[ i ];
 
		// Get the product information for the node
		oNodeInfo = getNodeProductsMap( oNode, bUnifyProducts );
 
		// If we are not unifying results
		if( !bUnifyProducts ){
			// Capture the info
			aNonUnified[i] = oNodeInfo;
 
			// Next!!
			continue;
		}
 
		// Get the list of product GUIDs
		oNodeGuidMap[ oNode.elementID ] = {
				"label" : getContextualNodeLabel( oNode ),
				"guids" : oNodeInfo
			};
	}
 
	// Declare working variable
	var oUnifiedMap;
 
	// If we are unifying results
	if( bUnifyProducts ){
		// Get the unified products map
		oUnifiedMap = getUnifiedProductsMap( oNodeGuidMap );
 
		// Get a stringified version of the info
		sData = getStringified( getValues( oUnifiedMap ) );
	// If we are not unifying results
	} else {
		// Remove any empty entries
		aNonUnified = aNonUnified.filter( Boolean );
 
		// Get a stringified version of the info
		sData = getStringified( aNonUnified );
	}
 
	// Declare working variable
	var sError;
 
	// If we have a path for a file to write to
	if( !sFile.isEmpty() ){
		// Write the data to file
		sError = writeToFile( sFile, sData, DzFile.WriteOnly );
 
		// If we do not have any errors
		if( sError.isEmpty() ){
			// If we are opening the result
			if( bOpenResults ){
				// Cause the file to be shown
				App.showURL( sFile );
			// If prompts are not being supressed
			} else if( !bSilent && App.showPrompts() ){
				//
				sTitle = text( "Data Saved" );
				sMessage = text( "Saved product data to:" ) + "\"" + sFile + "\"";
 
				// Inform the user
				switch( MessageBox.information( sMessage, sTitle, sOk, !bOpenResults ? text( "Open" ): "", !bOpenResults ? text( "Show" ): "" ) ){
					case 1:
						// Cause the file to be opened
						App.showURL( sFile );
						break;
					case 2:
						// Cause the OS file browser to be opened
						App.showInNativeBrowser( sFile );
						break;
					default:
						break
				}
			}
		// If prompts are not being supressed
		} else if( !bSilent && App.showPrompts() ){
			// Inform the user
			MessageBox.warning( sError, text( "Save Error" ), sOk, "" );
		// If we are opening the result
		} else if( bOpenResults ){
			// Provide feedback
			print( sError );
 
			// Cause the log file to be shown
			openApplicationLog();
		}
	// If we are not writing to file
	} else {
		// Provide feedback
		print( sData );
 
		// If we are opening the result
		if( bOpenResults ){
			// Cause the log file to be shown
			openApplicationLog();
		}
	}
 
	// Let the user know we are done
	clearBusyCursor();
 
// Finalize the function and invoke
})( getArguments() );