User Tools

Site Tools


Save Filter Template Save

Summary

Below is an example demonstrating how to save the settings of an asset save filter as a JSON template file that can be loaded and applied by another script.

See Also:

API Areas of Interest

Example

SaveFilter_Template_Save.dsa
// Define an anonymous function;
// serves as our main loop,
// limits the scope of variables
(function(){
 
	// Get the arguments passed in
	var s_aArgs = getArguments();
 
	// 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;
	};
 
	/*********************************************************************/
	// 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;
	};
 
	/*********************************************************************/
	// DzComboBox : A function for building a save filter combobox
	function buildSaveFilterCombobox( wCmb, aExclude )
	{
		// Get the asset IO manager
		var oAssetIOMgr = App.getAssetIOMgr();
 
		// Declare variables we'll be using as we iterate
		var oAssetIOFilter, oSettings;
 
		// Define the list of filters; in display order
		var aFilters = [
			"DzSceneAssetFilter",
			"DzSceneSubsetAssetFilter",
			"DzHierarchicalMaterialAssetFilter",
			"DzHierarchicalPoseAssetFilter",
			"DzWearablesAssetFilter",
			"DzCharacterAssetFilter",
			"DzPropertiesAssetFilter",
			"DzShapingAssetFilter",
			"DzPoseAssetFilter",
			"DzMaterialAssetFilter",
			"DzShaderAssetFilter",
			"DzCameraAssetFilter",
			"DzLightAssetFilter",
			"DzRenderSettingsAssetFilter",
			"DzSimulationSettingsAssetFilter",
			"DzDFormAssetFilter",
			"DzLayerAssetFilter",
			"DzPuppeteerAssetFilter",
			"---",
			"DzSceneSupportAssetFilter",
			"DzNodeSupportAssetFilter",
			"DzMorphSupportAssetFilter",
			"DzUVSupportAssetFilter",
			"DzShaderSupportAssetFilter",
		];
 
		// Declare working variables
		var sFilter;
		var nIdx;
 
		// Get the number of items in our list
		var nFilters = aFilters.length;
		// Iterate over our list 
		for( var i = 0; i < nFilters; i += 1 ){
			// Get the 'current' filter
			sFilter = aFilters[i];
			// If the 'filter' is in the exclude list
			if( aExclude.indexOf( sFilter ) > -1 )
			{
				// Next!!
				continue;
			}
 
			// If the 'filter' is a separator
			if( sFilter == "---" )
			{
				// Insert a separator at the end
				wCmb.insertSeparator( wCmb.count );
				// Next!!
				continue;
			}
 
			// Find the filter index
			nIdx = oAssetIOMgr.findFilter( sFilter );
 
			// If the filter was not found
			if( nIdx < 0 ){
				// Next!!
				continue;
			}
 
			// Get the filter
			oAssetIOFilter = oAssetIOMgr.getFilter( nIdx );
 
			// Add the filter to the choices
			wCmb.addItem(
					oAssetIOFilter.getDescription().replace( "...", "" ),
					oAssetIOFilter.className() );
 
			// Clean up; do not leak memory
			oAssetIOFilter.deleteLater();
		}
 
		// Insert a separator between filters we have
		// explicitly handled and those we have not
		wCmb.insertSeparator( wCmb.count );
 
		// Get the number of filters
		nFilters = oAssetIOMgr.getNumFilters();
		// Iterate over all filters
		for( var i = 0; i < nFilters; i += 1 ){
			// Get the 'current' filter
			oAssetIOFilter = oAssetIOMgr.getFilter( i );
 
			// Get the filter classname
			sFilter = oAssetIOFilter.className();
 
			// If the 'filter' has already been added
			// or it is in the exclude list
			if( aFilters.indexOf( sFilter ) > -1 ||
			aExclude.indexOf( sFilter ) > -1 ){
				// Clean up; do not leak memory
				oAssetIOFilter.deleteLater();
 
				// Next!!
				continue;
			}
 
			// Add the filter to the choices
			wCmb.addItem(
					oAssetIOFilter.getDescription().replace( "...", "" ),
					oAssetIOFilter.className() );
 
			// Clean up; do not leak memory
			oAssetIOFilter.deleteLater();
		}
	};
 
	/*********************************************************************/
	// String : A function for getting input from the user
	function getAssetFilter( sTitle, aExclude )
	{
		// Get the current style
		var oStyle = App.getStyle();
 
		// Get pixel metrics from the current style
		var nMargin = oStyle.pixelMetric( "DZ_GeneralMargin" );
		var nBtnHeight = oStyle.pixelMetric( "DZ_ButtonHeight" );
 
		// Create a basic dialog
		var wDlg = new DzBasicDialog();
 
		// Get the wrapped widget for the dialog
		var oDlgWgt = wDlg.getWidget();
 
		// Strip the space for a settings key
		var sKey = sTitle.replace( / /g, "" ) + "Dlg";
 
		// Set an [unique] object name on the wrapped dialog widget;
		// this is used for recording position and size separately
		// from all other [uniquely named] DzBasicDialog instances
		oDlgWgt.objectName = sKey;
 
		// Set the title of the dialog
		wDlg.caption = text( sTitle );
 
		// Create a widget
		var wMainWgt = new DzWidget( wDlg );
 
		// Create a grid layout
		var lytMain = new DzGridLayout( wMainWgt );
		lytMain.margin = nMargin;
		lytMain.spacing = nMargin;
		lytMain.setColStretch( 1, 1 );
 
		// Initialize
		var nRow = 0;
 
		// Create a label
		var wLabel = new DzLabel( wMainWgt );
		wLabel.text = text( "Filter :" );
		wLabel.alignment = DzWidget.AlignRight | DzWidget.AlignVCenter;
		// Add the label to the layout
		lytMain.addWidget( wLabel, nRow, 0 );
 
		// Create a combo box
		var wSaveFilterCmb = new DzComboBox( wMainWgt );
		// Populate the combobox with save filters
		buildSaveFilterCombobox( wSaveFilterCmb, aExclude );
		// Set the height
		wSaveFilterCmb.setFixedHeight( nBtnHeight );
		// Add the combo box to the layout
		lytMain.addWidget( wSaveFilterCmb, nRow, 1 );
 
		nRow += 1;
 
		// Add the main widget to the dialog
		wDlg.addWidget( wMainWgt );
 
		// Get the minimum size of the dialog
		var sizeHint = oDlgWgt.minimumSizeHint;
 
		// Set the fixed height of the dialog
		wDlg.setFixedHeight( sizeHint.height );
 
		// If the user accepts the dialog
		if( wDlg.exec() ){
			// Return the user's choice
			return wSaveFilterCmb.itemData( wSaveFilterCmb.currentItem );
		}
 
		// Return an empty string
		return "";
	};
 
	/*********************************************************************/
	// String : A function for getting string input from the user
	function getUserString( sTitle, sLabel, sHint, sText, aOptions )
	{		
		// Create a basic dialog
		var wDlg = new DzBasicDialog();
 
		// Get the wrapped widget for the dialog
		var oDlgWgt = wDlg.getWidget();
 
		// Set the title of the dialog
		wDlg.caption = text( sTitle );
 
		// Strip the space for a settings key
		var sKey = sTitle.replace( / /g, "" ) + "Dlg";
 
		// Set an [unique] object name on the wrapped dialog widget;
		// this is used for recording position and size separately
		// from all other [uniquely named] DzBasicDialog instances
		oDlgWgt.objectName = sKey;
 
		// Create a widget
		var wMainWgt = new DzWidget( wDlg );
 
		// Get the current style
		var oStyle = App.getStyle();
 
		// Get the height for buttons
		var nMargin = oStyle.pixelMetric( "DZ_GeneralMargin" );
 
		// Create a horizontal box layout
		var lytMain = new DzHBoxLayout( wMainWgt );
		lytMain.margin = nMargin;
		lytMain.spacing = nMargin;
 
		// Create a label
		var wLabel = new DzLabel( wMainWgt );
		wLabel.text = sLabel;
		wLabel.alignment = DzWidget.AlignRight | DzWidget.AlignVCenter;
		// Add the label to the layout
		lytMain.addWidget( wLabel );
 
		// Create a combo edit
		var wCmbEdit = new DzComboEdit( wMainWgt );
		wCmbEdit.submenuDelimiter = "/";
		wCmbEdit.menuSelectionIncludesPath = true;
		wCmbEdit.placeholderText = sHint;
		wCmbEdit.text = sText;
		wCmbEdit.addItems( aOptions );
		// Add the line edit to the layout
		lytMain.addWidget( wCmbEdit );
 
		// Add the widget to the dialog
		wDlg.addWidget( wMainWgt );
 
		// Get the minimum size of the dialog
		var sizeHint = oDlgWgt.minimumSizeHint;
 
		// Set the fixed size of the dialog
		wDlg.setFixedHeight( sizeHint.height );
 
		// If the user accepts the dialog
		if( wDlg.exec() ){
			// Return the user's input
			return wCmbEdit.text;
		}
 
		// Return an empty string
		return "";
	};
 
	/*********************************************************************/
	// String : A function for getting the path of a script that resides next to this script
	function getAdjacentScriptPath( sName, bNotify )
	{
		// Create a file info object for this file
		var oFileInfo = new DzFileInfo( getScriptFileName() );
 
		// Define the base path of the script we will call; without the file extension
		var sBasePath = String( "%1/%2" ).arg( oFileInfo.path() ).arg( sName );
 
		// Clean up; do not leak memory
		oFileInfo.deleteLater();
 
		// Create a script object
		var oScript = new DzScript();
 
		// Attempt to find our script; doing it this way, we can debug with an
		// ascii file and distribute a binary [encrypted] file with the same name...
		// without having to update the contents of the script or manually handle
		// the file extensions; requires 3.0.1.5 or newer
		var sScriptPath = oScript.getScriptFile( sBasePath );
 
		// Clean up; do not leak memory
		oScript.deleteLater();
 
		// If a script is not found
		if( sScriptPath.isEmpty() && bNotify ){
			// Define message strings
			var sTitle = text( "File Not Found" );
			var sMessage = text( "A '%1.ds(a|b|e)' file could not be found." ).arg( sBasePath );
			var sButton = text( "&OK" );
			// Inform the user
			MessageBox.information( sMessage, sTitle, sButton );
		}
 
		// Return the result
		return sScriptPath;
	};
 
	/*********************************************************************/
	// Boolean : A function for executing a script that resides next to this script
	function executeAdjacentScript( sName, aArgs )
	{
		// Initialize
		var bResult = true;
 
		// Get the path of our script
		var sScriptPath = getAdjacentScriptPath( sName, true );
 
		// If a script is found
		if( !sScriptPath.isEmpty() ){							
			// Create a script object
			var oScript = new DzScript();
 
			// If the script loads
			if( oScript.loadFromFile( sScriptPath ) ){
				// Execute the script with our argument
				if( !oScript.execute( aArgs ) ){
					// Update our variable
					bResult = false;
				}
			// If the script does not load
			} else {
				// Define message strings
				var sTitle = text( "Read Error" );
				var sMessage = text( "The '%1' file could not be loaded." ).arg( sScriptPath );
				var sButton = text( "&OK" );
				// Inform the user
				MessageBox.information( sMessage, sTitle, sButton );
			}
 
			// Clean up; do not leak memory
			oScript.deleteLater();
		}
 
		// Return the result
		return bResult;
	};
 
	/***********************************************************************
	***** DsFileSystem Prototype *****
	***********************************************************************/
 
	// Create an interface object
	var s_oFileSystem = new DsFileSystem();
 
	/*********************************************************************/
	function DsFileSystem()
	{
	};
 
	/***********************************************************************/
	DsFileSystem.superclass = Object;
 
	/*********************************************************************/
	// Array<String|DzDir> : Method for collecting an array of directory objects
	DsFileSystem.prototype.getDirectories = function( oDir, regxFilter, nFilter, nSort, sType, bRecurse )
	{
		// Provide feedback
		debug( String("DsFileSystem::getDirectories( %1, \"%2\", %3, %4, \"%5\", %6 )")
			.arg( oDir.path() )
			.arg( regxFilter )
			.arg( nFilter )
			.arg( nSort )
			.arg( sType )
			.arg( bRecurse ) );
 
		// Declare working variable
		var sAbsPath;
 
		// Get the directory names
		var aDirs = oDir.entryList( regxFilter, nFilter, nSort );
		// Iterate over the directory names
		for( var i = 0, nDirs = aDirs.length; i < nDirs; i += 1 ){
			// Get the absolute path of the 'current' directory
			sAbsPath = String( "%1/%2" ).arg( oDir.absPath() ).arg( aDirs[ i ] );
			// Based on the type requested
			switch( sType ){
				default:
				case "String":
					// Update the name with the absolute path of the directory
					aDirs[ i ] = sAbsPath;
					// If we are recursing
					if( bRecurse ){
						// Recursively collect the directory paths
						aDirs = aDirs.concat( this.getSubDirectories( new DzDir( sAbsPath ),
							regxFilter, nFilter, nSort, sType ) );
					}
					break;
				case "DzDir":					
					// Update the name with a directory object
					aDirs[ i ] = new DzDir( sAbsPath );
					// If we are recursing
					if( bRecurse ){
						// Recursively collect the directories
						aDirs = aDirs.concat( this.getSubDirectories( aDirs[ i ],
							regxFilter, nFilter, nSort, sType ) );
					}
					break;
			}
		}
 
		// Return the result
		return aDirs;
	};
 
	/*********************************************************************/
	// Array<String|DzDir> : Method for recursively collecting an array of directory objects
	DsFileSystem.prototype.getSubDirectories = function( oDir, regxFilter, nFilter, nSort, sType )
	{
		// Provide feedback
		debug( String( "DsFileSystem::getSubDirectories( %1, \"%2\", %3, %4, \"%5\" )" )
			.arg( oDir.path() )
			.arg( regxFilter )
			.arg( nFilter )
			.arg( nSort )
			.arg( sType ) );
 
		// Initialize
		var aSubDirs = [];
 
		// Get the immediate child directories
		var aDirs = this.getDirectories( oDir, regxFilter, nFilter, nSort, sType, true );
		// Iterate over the directories
		for( var i = 0, nDirs = aDirs.length; i < nDirs; i += 1 ){
			// Based on the type requested
			switch( sType ){
				default:
				case "String":
					// Recursively collect the directory paths
					aSubDirs = aDirs.concat( this.getSubDirectories( new DzDir( aDirs[ i ] ),
						regxFilter, nFilter, nSort, sType ) );
					break;
				case "DzDir":	
					// Recursively collect the directories
					aSubDirs = aDirs.concat( this.getSubDirectories( aDirs[ i ],
						regxFilter, nFilter, nSort, sType, true ) );
					break;
			}
		}
 
		// Return the result
		return aSubDirs;
	};
 
	/*********************************************************************/
	// Array<String|DzFileInfo|DzFile> : Method for collecting an array of files
	DsFileSystem.prototype.getFiles = function( oDir, regxFilter, nFilter, nSort, sType )
	{
		// Provide feedback
		debug( String( "DsFileSystem::getFiles( %1, \"%2\", %3, %4, \"%5\" )" )
			.arg( oDir.path() )
			.arg( regxFilter )
			.arg( nFilter )
			.arg( nSort )
			.arg( sType ) );
 
		// Declare working variable
		var sAbsFilePath;
 
		// Get the file names
		var aFiles = oDir.entryList( regxFilter, nFilter, nSort );
		// Iterate over the file names
		for( var i = 0, nFiles = aFiles.length; i < nFiles; i += 1 ){
			// Get the absolute path of the 'current' file
			sAbsFilePath = oDir.absFilePath( aFiles[ i ] );
			// Based on the type requested
			switch( sType ){
				default:
				case "String":
					// Update the name with the absolute path of a file
					aFiles[ i ] = sAbsFilePath;
					break;
				case "DzFileInfo":
					// Update the name with a file info object
					aFiles[ i ] = new DzFileInfo( sAbsFilePath );
					break;
				case "DzFile":
					// Update the name with a file object
					aFiles[ i ] = new DzFile( sAbsFilePath );
					break;
			}
		}
 
		// Return the result
		return aFiles;
	};
 
	/*********************************************************************/
	// Array<String|DzDir> : Method for retrieving a list of directories
	DsFileSystem.prototype.getDirectoryList = function( sPath, sFilter, sType, bRecurse, bRelative )
	{
		// Declare the output
		var aDirs = [];
 
		// Create a directory object
		var oBaseDir = new DzDir( sPath );
		// If the directory doesn't exist
		if( !oBaseDir.exists() ){
			// Clean up; do not leak memory
			oBaseDir.deleteLater();
 
			// We are done...
			return aDirs;
		}
 
		// Get the directories
		var aDirs = this.getDirectories( oBaseDir, sFilter,
			DzDir.Dirs | DzDir.NoDotAndDotDot, DzDir.Name, sType, bRecurse );
 
		// Clean up; do not leak memory
		oBaseDir.deleteLater();
 
		// If we do not want relative paths
		if( !bRelative ){
			// Return the result
			return aDirs;
		}
 
		// Declare working variables
		var sAbsPath, sRelPath;
		var oDir;
 
		// Iterate over the directories
		for( var i = 0, nDirs = aDirs.length; i < nDirs; i += 1 ){
			// Based on the type requested
			switch( sType ){
				default:
				case "String":
					// Get the 'current' path
					sAbsPath = aDirs[ i ];
					// Get the relative portion of the path
					sRelPath = sAbsPath.substring( sPath.length );
					// Update the path
					aDirs[ i ] = sRelPath;
					break;
				case "DzDir":
					// Get the 'current' directory
					oDir = aDirs[ i ];
					// Get the path
					sAbsPath = oDir.path();
					// Get the relative portion of the path
					sRelPath = sAbsPath.substring( sPath.length );
					// Update the path
					oDir.setPath( sRelPath );
					// Update the directory
					aDirs[ i ] = oDir;
					break;
			}
		}
 
		// Return the result
		return aDirs;
	};
 
	/*********************************************************************/
	// Array<String|DzFileInfo|DzFile> : Method for retrieving a list of files
	DsFileSystem.prototype.getFileList = function( sPath, sFilter, sType, bRecurse )
	{
		// Initialize
		var aFiles = [];
 
		// Create a directory object
		var oBaseDir = new DzDir( sPath );
		// If the directory doesn't exist
		if( !oBaseDir.exists() ){
			// Clean up; do not leak memory
			oBaseDir.deleteLater();
 
			// We are done...
			return aFiles;
		}
 
		// Get the files from the specified directory
		aFiles = this.getFiles( oBaseDir, sFilter, DzDir.Files, DzDir.Name, sType );
 
		// Clean up; do not leak memory
		oBaseDir.deleteLater();
 
		// If we are recursing
		if( bRecurse ){	
			// Declare working variable
			var oDir;
 
			// Get the directories
			var aDirs = this.getDirectoryList( sPath, "*", "DzDir", bRecurse );
			// Iterate over the directories
			for( var i = 0, nDirs = aDirs.length; i < nDirs; i += 1 ){
				// Get the 'current' directory
				oDir = aDirs[ i ];
 
				// Append the files from the 'current' directory to the output
				aFiles = aFiles.concat(
					this.getFiles( oDir, sFilter, DzDir.Files, DzDir.Name, sType ) );
 
				// Clean up; do not leak memory
				oDir.deleteLater();
			}
		}
 
		// Return the result
		return aFiles;
	};
 
	/*********************************************************************/
	// Define common message variables
	var sTitle = text( "Critical Error" );
	var sMessage = text( "An asset filter with the class name " +
			"\"%1\" could not be found.").arg( sClassName )
	var sButton = text( "&OK" );
 
	// Get the root node of the primary selection
	var oRootNode = getRootNode( Scene.getPrimarySelection() );
 
	// If we do not have a selection
	if( !oRootNode ){
		// Define message strings
		sTitle = text( "Selection Error" );
		sMessage = text( "A node in the scene must be selected to continue." );
		// Inform the user
		MessageBox.information( sMessage, sTitle, sButton );
 
		// We are done...
		return;
	}
 
	// Initialize
	var sClassName = "";
 
	// If an arg was passed in, and it is a string
	if( s_aArgs.length == 1 && typeof( s_aArgs[0] ) == "string" ){
		// Update the classname of the filter to use
		sClassName = s_aArgs[0];
	// If we are executed "standalone"
	} else {
		// Define the list of filters to exclude; i.e., those that either
		// provide no options, or do not make sense to templatize
		var aExclude = [ "DzSceneAssetFilter", "DzPuppeteerAssetFilter" ];
		// If the version of the application is older than 4.11.0.229
		if( App.version64 < 0x0004000b000000e5 ){
			// Exclude filters that had a bug with displaying their option dialog
			aExclude = aExclude.concat(
				[ "DzMaterialAssetFilter", "DzShaderAssetFilter" ] );
		}
		// Define the class name of the asset filter we want to use
		sClassName = getAssetFilter( "Save Filter Template Save", aExclude );
	}
 
	// If the user cancelled
	if( sClassName.isEmpty() ){
		// We are done...
		return;
	}
 
	// Get the asset IO manager
	var oAssetIOMgr = App.getAssetIOMgr();
	// Find the index of the asset filter with the class name we want
	var nAssetIOFilter = oAssetIOMgr.findFilter( sClassName );
	// If we did not find an asset filter with the class name we wanted
	if( nAssetIOFilter < 0 ){
		// Inform the user
		MessageBox.critical( sMessage, sTitle, sButton );
 
		// We are done...
		return;
	}
 
	// Get the asset filter at the prescribed index
	var oAssetIOFilter = oAssetIOMgr.getFilter( nAssetIOFilter );
	// If we do not have a valid asset filter
	if( !oAssetIOFilter ){
		// Inform the user
		MessageBox.critical( sMessage, sTitle, sButton );
 
		// We are done...
		return;
	}
 
	// Construct the path for the template file
	var sTemplatePath = String( "%1/Save Filter Settings Templates/%2/%3" )
				.arg( App.getAppDataPath() )
				.arg( sClassName )
				.arg( oRootNode.name );
 
	// Create a directory object for template files
	var oTemplateDir = new DzDir( sTemplatePath );
	// Make sure the template directory exists;
	// if it does not exist and cannot be created
	if( !oTemplateDir.mkpath( sTemplatePath ) ){
		// Define message strings
		sTitle = text( "Resource Error" );
		sMessage = text( "The settings template directory could not be created: %1" )
						.arg( sTemplatePath );
		// Inform the user
		MessageBox.warning( sMessage, sTitle, sButton, "" );
 
		// Clean up; do not leak memory
		oAssetIOFilter.deleteLater();
 
		// We are done...
		return;
	}
 
	// Create a settings object
	var oSettings = new DzFileIOSettings();
 
	// Display the options dialog and record the settings;
	// if the user cancels
	if( !oAssetIOFilter.getOptions( oSettings, true, "" ) ){
		// Clean up; do not leak memory
		oAssetIOFilter.deleteLater();
 
		// We are done...
		return;
	}
 
	// Debug
	debug( "Settings:", oSettings.toJsonString() );
 
	// Define the file extension
	var sExt = "json";
 
	// Get the list of template files
	var aPaths = s_oFileSystem.getFileList(
					sTemplatePath, "*." + sExt, "String", true );
 
	// Declare working variables
	var sPath;
	var nStart, nEnd;
 
	// Iterate over the template paths
	for( var i = 0, nPaths = aPaths.length; i < nPaths; i += 1 ){
		// Get the 'current' path
		sPath = aPaths[ i ];
 
		// Set the start/end of the substring
		nStart = sTemplatePath.length + 1;
		nEnd = sPath.length - sExt.length - 1;
 
		// Get the relative basename
		aPaths[ i ] = sPath.substring( nStart, nEnd );
	}
 
	// Initialize
	var sTemplateFile = "";
 
	// Declasre working variable
	var nIdx;
 
	// Define common variables
	var sTmpTitle = text( "Save Filter Template Save" );
	var sTmpLabel = text( "Name:" );
	var sTmpHint = text( "Enter a name for the template..." );
	var sTmpText = "";
 
	// Do this at least once
	do {
		// Prompt the user for the path/name of the template
		sTmpText = getUserString(
			sTmpTitle, sTmpLabel, sTmpHint, sTmpText, aPaths );
 
		// If the user cancelled
		if( sTmpText.isEmpty() ){
			// Clear
			sTemplateFile = "";
		// If the user did not cancel
		} else {
			// Construct the path
			sTemplateFile = String( "%1/%2.json" )
								.arg( sTemplatePath )
								.arg( sTmpText );
		}
 
		// If the template path is not defined
		if( sTemplateFile.isEmpty() ){
			// Clean up; do not leak memory
			oAssetIOFilter.deleteLater();
			// We are done...
			return;
		}
 
		// Get the index of the last slash
		nIdx = sTemplateFile.lastIndexOf( "/" );
		// Make sure the template directory exists;
		// if it does not exist and cannot be created
		if( nIdx > -1 && !oTemplateDir.mkpath( sTemplateFile.substring( 0, nIdx ) ) ){
			// Define message strings
			sTitle = text( "Resource Error" );
			sMessage = text( "The template directory could not be created: %1" )
							.arg( sTemplatePath );
			// Inform the user
			MessageBox.warning( sMessage, sTitle, sButton, "" );
		}
	// Keep prompting until the user indicates that it is either
	// OK to (over)write the file, or they provide a unique path/name
	} while( !MainWindow.checkExistingFile( sTemplateFile ) );
 
	// Define the key used to select a node in node based filters
	var sKey = "RootLabel";
	// If this key is present
	if( oSettings.hasKey( sKey ) ){
		// Remove the value; cause the filter to use primary selection
		oSettings.removeValue( sKey );
	}
 
	// Define the key used to refer to a node in node based filters
	sKey = "NodeNames";
	// If the key is present
	if( oSettings.hasKey( sKey ) ){
		// Get the settings
		var oNamesSettings = oSettings.getSettingsValue( sKey );
		// If we have the settings and it has the root node's name as a key
		if( oNamesSettings && oNamesSettings.hasKey( oRootNode.name ) ){
			// Get the node settings
			var oNodeSettings = oNamesSettings.getSettingsValue( oRootNode.name );
			// If we have the settings
			if( oNodeSettings ){
				// Copy the settings to a new settings with a name we can replace on read
				var oSelectionSettings = oNamesSettings.setSettingsValue( "@selection", oNodeSettings );
				// Remove the node named setting
				oNamesSettings.removeValue( oRootNode.name );
			}
		}
	}
 
	// Create a new file object
	var oTemplateFile = new DzFile( sTemplateFile );
 
	// Open the file for writing
	oTemplateFile.open( DzFile.WriteOnly );
 
	// Convert the settings object to a string
	var sTemplateData = oSettings.toJsonString();
 
	// Write the template data to the file
	var nResult = oTemplateFile.write( sTemplateData );
 
	// Close the file
	oTemplateFile.close();
 
	// If there was no error
	if( nResult > -1 ){
		// Debug
		debug( "Saved:", sTemplateFile );
	// If there was an error
	} else {
		// Debug
		debug( "Error saving:", sTemplateFile );
	}
 
	// Clean up; do not leak memory
	oTemplateFile.deleteLater();
	oTemplateDir.deleteLater();
	oAssetIOFilter.deleteLater();
 
	// Create a file info object for this file
	var oFileInfo = new DzFileInfo( getScriptFileName() );
 
	// Construct the name of an adjacent script to look for
	var sScriptName = String("%1_%2").arg( oFileInfo.baseName() ).arg( sClassName );
	// If we have an adjacent script for the chosen filter
	if( !getAdjacentScriptPath( sScriptName, false ).isEmpty() )
	{
		// Execute the adjacent script
		executeAdjacentScript( sScriptName, [ sTemplateFile ] );
	}
 
	// Clean up; do not leak memory
	oFileInfo.deleteLater();
 
// Finalize the function and invoke
})();