User Tools

Site Tools


Face Transfer Settings

Summary

Below is an example that demonstrates how to display/edit the application settings supported by the Face Transfer (WIP) pane for overriding which assets to load (per supported figure generation/gender) when a figure is not selected, when applying a Material(s) Preset, and/or during a save operation, via script.

The example checks for the platform being executed on, and plugin registration/activation state, to control the creation, display, and enabled state of options - in accordance with operation of the pane. The options follow a common pattern, and so each option's unique data is defined in an Object and passed to functions that use the data to construct the widgets.

API Areas of Interest

Example

AppSettings_Face_Transfer.dsa
// DAZ Studio version 4.22.0.9 filetype DAZ Script
 
// Define an anonymous function;
// serves as our main loop,
// limits the scope of variables
(function(){
 
	// Define 'static' variables
 
	// Create an application settings object
	var s_oAppSettings = new DzAppSettings();
 
	// Get the Style
	var s_oStyle = App.getStyle();
	// Get pixel metrics from the style
	var s_nMargin = s_oStyle.pixelMetric( "DZ_GeneralMargin" );
	var s_nButtonHeight = s_oStyle.pixelMetric( "DZ_ButtonHeight" );
 
	/*********************************************************************/
	// 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;
	};
 
	/*********************************************************************/
	// String : A function that constructs a native formats filter for a file dialog
	function getNativeFileFilter()
	{
		// Get the content manager
		var oContentMgr = App.getContentMgr();
		// If we do not have a content manager
		if( !oContentMgr ){
			// We are done...
			return "";
		}
 
		// Return the constructed file filter
		return text( "%1 Formats" ).arg( App.appName ) +
				" (*." + oContentMgr.getNativeFileExtensions().join( " *." ) + ")";
	};
 
	/*********************************************************************/
	// String : A function that constructs a script formats filter for a file dialog
	function getScriptFileFilter()
	{
		// Create a script object
		var oScript = new DzScript();
 
		// Return the constructed file filter
		return "Daz Script" +
				" (*." + oScript.getScriptExtensions().join( " *." ) + ")";
	};
 
	/*********************************************************************/
	// String : A function for finding a relative path for an absolute path
	function findRelativeContentFilePath( sAbsFilePath )
	{
		// If the relative path is empty
		if( sAbsFilePath.isEmpty() ){
			// We are done...
			return "";
		}
 
		// Get the content manager
		var oContentMgr = App.getContentMgr();
		// If we do not have a content manager
		if( !oContentMgr ){
			// We are done...
			return "";
		}
 
		// Get the relative path of the file
		var sRelFilePath = oContentMgr.getRelativePath( DzContentMgr.AllDirsAndCloud, sAbsFilePath );
		// If the path is not relative
		if( sRelFilePath == sAbsFilePath ){
			// We are done... (not mapped)
			return "";
		}
 
		// Return the relative path
		return sRelFilePath;
	};
 
	/*********************************************************************/
	// String : A function for finding an absolute path for a relative path
	function findAbsContentFilePath( sRelFilePath )
	{
		// If the relative path is empty
		if( sRelFilePath.isEmpty() ){
			// We are done...
			return "";
		}
 
		// Get the content manager
		var oContentMgr = App.getContentMgr();
		// If we do not have a content manager
		if( !oContentMgr ){
			// We are done...
			return "";
		}
 
		// Declare working variable
		var sFilePath;
 
		// Initialize
		var bHasAllEnums = (App.version64 >= 0x000400090000002e);//4.9.0.46
		var nDirType = DzContentMgr.AllDirs;
 
		// Get the import manager
		var oImportMgr = App.getImportMgr();
		// If the file type is imported
		if( oImportMgr.findImporter( sRelFilePath ) ){
			// If the version does not provide all enumerated values
			if( !bHasAllEnums ){
				// Update the directory type
				nDirType = DzContentMgr.PoserDirs |
							DzContentMgr.ImportDirs |
							0x20; //DzContentMgr.CloudDB
			// Otherwise
			} else {
				// Update the directory type
				nDirType = DzContentMgr.PoserDirs |
							DzContentMgr.ImportDirs |
							DzContentMgr.CloudDB;
			}
		// If the file type is native
		} else {
			// If the version does not provide all enumerated values
			if( !bHasAllEnums ){
				// Update the directory type
				nDirType = DzContentMgr.NativeDirs |
							0x20; //DzContentMgr.CloudDB
			// Otherwise
			} else {
				// Update the directory type
				nDirType = DzContentMgr.NativeDirs |
							DzContentMgr.CloudDB;
			}
		}
 
		// Return the path
		return oContentMgr.findFile( sRelFilePath, nDirType );
	};
 
	/*********************************************************************/
	// Boolean : A function for determining whether or not a feature provided
	// by a plugin is enabled; i.e., the plugin is registered or within trial
	function isFeatureEnabled( sPlugin, bTrialEnabled )
	{
		// Get the plugin manager
		var oPluginMgr = App.getPluginMgr();
		// Get the plugin
		var oPlugin = oPluginMgr.findPlugin( sPlugin );
		// If the plugin was not found
		if( !oPlugin ){
			// We are done...
			return false;
		}
 
		// If the feature is enabled during a trial
		if( bTrialEnabled ){
			// Return whether or not the plugin is activated
			return oPlugin.isActivated();
		}
 
		// Return whether or not the plugin is registered
		return oPlugin.isRegistered();
	};
 
	/*********************************************************************/
	// void : A function for responding to an asset 'browse' button being clicked;
	// 'this' is expected to be a DzComboEdit associated with the button
	function handleAssetBrowseClicked()
	{
		// Get the absolute path of the current value
		var sAbsFilename = findAbsContentFilePath( this.text );
 
		// Prompt the user to select a native format file
		sAbsFilename = FileDialog.doFileDialog( true, text( "Select an asset" ),
				sAbsFilename, getNativeFileFilter() );
 
		// If the user cancelled
		if( sAbsFilename.isEmpty() ){
			// We are done...
			return;
		}
 
		// Get the relative filename
		var sRelFilename = findRelativeContentFilePath( sAbsFilename );
 
		// If we do not have a relative filename
		if( sRelFilename.isEmpty() ){
			// If the absolute filename cannot be found in the list;
			// this call is case-insensetive, so we do not need to
			// discretely handle paths for Daz Connect installed
			// assets separately from those that are not
			if( this.findItem( sAbsFilename ) < 0 ){
				// Add the absolute filename to the list of items
				this.addItem( sAbsFilename );
			}
 
			// Set the field to the absolute filename
			this.text = sAbsFilename;
 
			// We are done...
			return;
		}
 
		// If the relative filename does not start with a slash
		if( sRelFilename.charAt( 0 ) != "/" ){
			// Prepend a slash to the relative filename
			sRelFilename = "/" + sRelFilename;
		}
 
		// If the relative filename cannot be found in the list;
		// this call is case-insensetive, so we do not need to
		// discretely handle paths for Daz Connect installed
		// assets separately from those that are not
		if( this.findItem( sRelFilename ) < 0 ){
			// Add the relative filename to the list of items
			this.addItem( sRelFilename );
		}
 
		// Set the field to the relative filename
		this.text = sRelFilename;
	};
 
	/*********************************************************************/
	// void : A function for responding to a script 'browse' button being clicked;
	// 'this' is expected to be a DzComboEdit associated with the button
	function handleScriptBrowseClicked()
	{
		// Get the absolute path of the current value
		var sAbsFilename = findAbsContentFilePath( this.text );
 
		// Prompt the user to select a native format file
		sAbsFilename = FileDialog.doFileDialog( true, text( "Select a script" ),
				sAbsFilename, getScriptFileFilter() );
 
		// If the user cancelled
		if( sAbsFilename.isEmpty() ){
			// We are done...
			return;
		}
 
		// Get the relative filename
		var sRelFilename = findRelativeContentFilePath( sAbsFilename );
 
		// If we do not have a relative filename
		if( sRelFilename.isEmpty() ){
			// If the absolute filename cannot be found in the list;
			// this call is case-insensetive, so we do not need to
			// discretely handle paths for Daz Connect installed
			// assets separately from those that are not
			if( this.findItem( sAbsFilename ) < 0 ){
				// Add the absolute filename to the list of items
				this.addItem( sAbsFilename );
			}
 
			// Set the field to the absolute filename
			this.text = sAbsFilename;
 
			// We are done...
			return;
		}
 
		// If the relative filename does not start with a slash
		if( sRelFilename.charAt( 0 ) != "/" ){
			// Prepend a slash to the relative filename
			sRelFilename = "/" + sRelFilename;
		}
 
		// If the relative filename cannot be found in the list;
		// this call is case-insensetive, so we do not need to
		// discretely handle paths for Daz Connect installed
		// assets separately from those that are not
		if( this.findItem( sRelFilename ) < 0 ){
			// Add the relative filename to the list of items
			this.addItem( sRelFilename );
		}
 
		// Set the field to the relative filename
		this.text = sRelFilename;
	};
 
	/*********************************************************************/
	// Object : A function for creating a DzComboEdit with an adjacent
	// browse button for choosing the path of an asset
	function createAssetBrowseEdit( wParent, lytGrid, nRow, sValue, sDefault )
	{
		// Create a container widget
		var wContainerWgt = new DzWidget( wParent );
		lytGrid.addWidget( wContainerWgt, nRow, 1 );
 
		// Create a layout for the container
		var lytContainer = new DzHBoxLayout( wContainerWgt );
		lytContainer.margin = s_nMargin;
		lytContainer.spacing = 0;
 
		// Create a combo edit for the asset path
		var wComboEdit = new DzComboEdit( wParent );
		wComboEdit.placeholderText = text( "Provide an asset path..." );
		wComboEdit.readOnly = true;
		if( !sDefault.isEmpty() ){
			wComboEdit.addItems( [ sDefault, "-" ] );
		}
		lytContainer.addWidget( wComboEdit );
 
		// Create a "browse" button
		var wBrowseBtn = new DzPushButton( wParent );
		wBrowseBtn.text = "...";
		wBrowseBtn.maxWidth = s_nButtonHeight;
		lytContainer.addWidget( wBrowseBtn );
 
		// Connect the clicked signal on the browse button to the
		// handleBrowseClicked function, with the DzComboEdit as
		// the 'this' object
		connect( wBrowseBtn, "clicked()", wComboEdit, handleAssetBrowseClicked );
 
		// If the value is not empty
		if( !sValue.isEmpty() ){
			// Add the value to the list, along with a separator
			wComboEdit.addItems( [ sValue, "-" ] );
			// Set the value of the combo edit to the setting
			wComboEdit.text = sValue;
		// If the value is empty
		} else {
			// Set the value of the combo edit to the default
			wComboEdit.text = sDefault;
		}
 
		// Return an object with the widgets
		return {
			"wContainerWgt": wContainerWgt,
			"wComboEdit": wComboEdit,
			"wBrowseBtn": wBrowseBtn
		};
	}
 
	/*********************************************************************/
	// Object : A function for creating a labeled DzComboEdit with an
	// adjacent browse button for choosing the path of an asset
	function createLabeledAssetBrowseEdit( wParent, lytGrid, nRow, sLabel, sValue, sDefault )
	{
		// Create a label for the "browse edit"
		var wLabel = new DzLabel( wParent );
		wLabel.text = sLabel + " :";
		wLabel.alignment = DzWidget.AlignRight | DzWidget.AlignVCenter;
		lytGrid.addWidget( wLabel, nRow, 0 );
 
		// Create a combo edit and browse button for the asset
		var oWidgets = createAssetBrowseEdit( wParent, lytGrid, nRow, sValue, sDefault );
 
		// Add the label to the widgets object
		oWidgets.wLabel = wLabel;
 
		// Return the created widgets
		return oWidgets;
	};
 
	/*********************************************************************/
	// Object : A function for creating a DzComboEdit with an adjacent
	// browse button for choosing the path of a script
	function createScriptBrowseEdit( wParent, lytGrid, nRow, sValue, sDefault )
	{
		// Create a container widget
		var wContainerWgt = new DzWidget( wParent );
		lytGrid.addWidget( wContainerWgt, nRow, 1 );
 
		// Create a layout for the container
		var lytContainer = new DzHBoxLayout( wContainerWgt );
		lytContainer.margin = s_nMargin;
		lytContainer.spacing = 0;
 
		// Create a combo edit for the script path
		var wComboEdit = new DzComboEdit( wParent );
		wComboEdit.placeholderText = text( "Provide a script path..." );
		wComboEdit.readOnly = true;
		if( !sDefault.isEmpty() ){
			wComboEdit.addItems( [ sDefault, "-" ] );
		}
		lytContainer.addWidget( wComboEdit );
 
		// Create a "browse" button
		var wBrowseBtn = new DzPushButton( wParent );
		wBrowseBtn.text = "...";
		wBrowseBtn.maxWidth = s_nButtonHeight;
		lytContainer.addWidget( wBrowseBtn );
 
		// Connect the clicked signal on the browse button to the
		// handleBrowseClicked function, with the DzComboEdit as
		// the 'this' object
		connect( wBrowseBtn, "clicked()", wComboEdit, handleScriptBrowseClicked );
 
		// If the value is not empty
		if( !sValue.isEmpty() ){
			// Add the value to the list, along with a separator
			wComboEdit.addItems( [ sValue, "-" ] );
			// Set the value of the combo edit to the setting
			wComboEdit.text = sValue;
		// If the value is empty
		} else {
			// Set the value of the combo edit to the default
			wComboEdit.text = sDefault;
		}
 
		// Return an object with the widgets
		return {
			"wContainerWgt": wContainerWgt,
			"wComboEdit": wComboEdit,
			"wBrowseBtn": wBrowseBtn
		};
	}
 
	/*********************************************************************/
	// Object : A function for creating a labeled DzComboEdit with an
	// adjacent browse button for choosing the path of a script
	function createLabeledScriptBrowseEdit( wParent, lytGrid, nRow, sLabel, sValue, sDefault )
	{
		// Create a label for the "browse edit"
		var wLabel = new DzLabel( wParent );
		wLabel.text = sLabel + " :";
		wLabel.alignment = DzWidget.AlignRight | DzWidget.AlignVCenter;
		lytGrid.addWidget( wLabel, nRow, 0 );
 
		// Create a combo edit and browse button for the script
		var oWidgets = createScriptBrowseEdit( wParent, lytGrid, nRow, sValue, sDefault );
 
		// Add the label to the widgets object
		oWidgets.wLabel = wLabel;
 
		// Return the created widgets
		return oWidgets;
	};
 
	/*********************************************************************/
	// Object : A function for creating a labeled DzComboEdit with an
	// adjacent browse button for choosing the path of a file
	function createLabeledAssetGroup( oData )
	{
		// Get the group title
		var sGroupTitle = oData.sGroupTitle;
		// If the title has a placeholder
		if ( sGroupTitle.indexOf( "%1" ) > -1 ){
			// Replace the placeholder with the Genesis version
			sGroupTitle = sGroupTitle.arg( oData.nGenesisVer );
		}
 
		// Get the group help
		var sGroupHelp = oData.sGroupHelp;
		// If the group help is empty
		if( sGroupHelp.isEmpty() ){
			// Get the group tip
			sGroupHelp = oData.sGroupTip;
		}
 
		// Create a group box
		var wGroupBox = new DzGroupBox( oData.wGroupParent );
		wGroupBox.title = oData.sGroupTitle + oData.sGroupRequired + " :";
		wGroupBox.toolTip = oData.sGroupTip;
		wGroupBox.whatsThis = oData.sGroupWhatsThis
			.arg( sGroupTitle )
			.arg( sGroupHelp );
		wGroupBox.enabled = oData.bGroupEnabled;
 
		// Create a grid layout for the group
		var lytGroup = new DzGridLayout( wGroupBox );
		lytGroup.margin = s_nMargin;
		lytGroup.spacing = s_nMargin;
 
		// Get the number of options
		var nOptions = oData.aOptions.length;
 
		// Pre-size lists of widgets
		var aLabels = new Array( nOptions );
		var aWidgets = new Array( nOptions );
 
		// Declare working variables
		var oOption;
		var oWidgets;
 
		// Iterate over the options to create
		for( var i = 0; i < nOptions; i += 1 ){
			// Get the 'current' option
			oOption = oData.aOptions[i];
 
			// Create a labled browse edit for the option
			oWidgets = createLabeledAssetBrowseEdit(
					wGroupBox, lytGroup, i, oOption.sLabel,
					s_oAppSettings.getStringValue( oOption.sKey ),
					oOption.sDefault );
 
			// Setup tool tips
			oWidgets.wLabel.toolTip = oOption.sTip;
			oWidgets.wComboEdit.toolTip = oOption.sTip;
			oWidgets.wBrowseBtn.toolTip = oOption.sBrowse;
 
			// Setup What's This?
			oWidgets.wLabel.whatsThis = wGroupBox.whatsThis;
			oWidgets.wComboEdit.whatsThis = wGroupBox.whatsThis;
			oWidgets.wBrowseBtn.whatsThis = wGroupBox.whatsThis;
 
			// Capture the label
			aLabels[i] = oWidgets.wLabel;
 
			// Capture the DzComboEdit
			aWidgets[i] = {
				"wComboEdit": oWidgets.wComboEdit,
				"sKey": oOption.sKey,
				"sDefault": oOption.sDefault
			};
		}
 
		// Return an object with the widgets
		return {
			"wGroupBox": wGroupBox,
			"aLabels": aLabels,
			"aWidgets": aWidgets
		};
	};
 
	/*********************************************************************/
	// void : A function for responding to a "None" item in a DzComboEdit being selected;
	// 'this' is expected to be the combo edit associated with the item
	function handleComboEditNoneItemChanged()
	{
		// Convert the arguments object into an array
		var aArguments = [].slice.call( arguments );
 
		// If the value passed in is not the "None" item
		if( aArguments[0] != text( "None" ) ){
			// We are done...
			return;
		}
 
		// Clear the text
		this.clearText();
	};
 
	/*********************************************************************/
	// void : A function for adding a DzComboEdit menu item that causes
	// something other than populating with the item text to occur
	function addComboEditFunctionItem( wComboEdit, nIndex, sItem, bSeparator, funcCallback )
	{
		// Declare working variable
		var aItems;
 
		// Initialize
		var nIdx = nIndex;
 
		// If the index passed in is less than the 'default' position
		if( nIndex < -1 ){
			// Get the list of current items
			aItems = wComboEdit.items();
 
			// Update our index to the 'last' position
			nIdx = aItems.length;
		}
 
		// If a separator is desired 
		if( bSeparator ){
			// Add the item and a separator to the list
			wComboEdit.insertItems( nIdx, [ sItem, "-" ] );
		// If a separator is not desired
		} else {
			// Insert the item
			wComboEdit.insertItem( nIdx, sItem );
		}
 
		// Connect the itemChanged signal to the callback function,
		// with the DzComboEdit as the 'this' object
		connect( wComboEdit, "itemChanged(const QString&)", wComboEdit, funcCallback );
	};
 
	/*********************************************************************/
	// void : A function for setting the minimum width on a set of labels
	function setLabelMinimumWidths( aLabels, nWidth )
	{
		// Declare working variable
		var nCurWidth;
 
		// Initialize
		var nMinWidth = 0;
 
		// If a width is specified
		if( nWidth ){
			// Use the specified width
			nMinWidth = nWidth;
		// If a width is not specified
		} else {
			// Iterate over the labels
			for( var i = 0, n = aLabels.length; i < n; i += 1 ){
				// Get the width of the current label
				nCurWidth = aLabels[i].width;
				// If the current width is larger than the captured width
				if( nCurWidth > nMinWidth ){
					// Update the captured width
					nMinWidth = nCurWidth;
				}
			}
		}
 
		// Iterate over the labels
		for( var i = 0, n = aLabels.length; i < n; i += 1 ){
			// Set the minimum width
			aLabels[i].minWidth = nMinWidth;
		}
	};
 
	/*********************************************************************/
	// void : A function for setting or removing a DzAppSettings key depending
	// on whether a value matches (case-insensitive) a default value
	function setOrRemoveStringSetting( sKey, sValue, sDefault )
	{
		// If the value is the same as the default
		if( sValue.toLowerCase() == sDefault.toLowerCase() ){
			// Remove the setting
			s_oAppSettings.removeKey( sKey );
		// If the value is not the same as the default
		} else {
			// Set the setting
			s_oAppSettings.setStringValue( sKey, sValue );
		}
	};
 
	/*********************************************************************/
	// void : A function for constructing/displaying a dialog
	function doDialog()
	{
		// Define plugin/key names
		var sFaceTransfer = "Face Transfer";
		var sFaceTransfer2 = "Face Transfer 2";
 
		// Define Scene Subset/Character Preset key names
		var sDefMaleKey = "DefaultMale";
		var sDefFemaleKey = "DefaultFemale";
 
		// Define Post Process key name
		var sPostProcessKey = "PostProcess";
 
		// Define common strings
		var sGenesisVer = "Genesis %1";
 
		var sLoadTitle = text( "%1 Load" ).arg( sGenesisVer );
		var sMaterialTitle = text( "%1 Material" ).arg( sGenesisVer );
		var sPostProcessTitle = text( "Post Process" );
		var sRegRequired = text( " (registration required)" );
		var sTrialRequired = text( " (trial required)" );
 
		var sMale = text( "Male" );
		var sFemale = text( "Female" );
		var sScript = text( "Script" );
 
		var sLoadAsset = "Scene Subset or Charater Preset";
		var sMatAsset = "Material(s) Preset";
		var sPostProcessScript = "Post Process script";
 
		var sLoadTip = text( "The %1 to load if no %2 figure is selected" ).arg( sLoadAsset ).arg( sGenesisVer );
		var sMaterialTip = text( "The %1 to apply to a %2 figure" ).arg( sMatAsset ).arg( sGenesisVer );
		var sPostProcessTip = text( "The script to execute when saving a %1 figure" ).arg( sGenesisVer );
		var sBrowseTip = text( "Click to browse for a %1 %2" );
 
		var sWhatsThis = "<b>%1</b><br/><br/>%2";
		var sPostProcessHelp = text( "This script is executed during a save operation - " +
			"after materials are applied, but before transfer data is removed and the " +
			"(optional) Scene Subset is saved.<br/><br/>" +
			"A global transient variable named <i>Figure</i>, which refers to the figure that is loaded/selected, " +
			"is provided to the script in the global context at runtime." );
 
 
		// Create a basic dialog
		var wDlg = new DzBasicDialog();
		var sDialogTitle = text( "%1 Assets" ).arg( sFaceTransfer );
		var sDialogHelp = text( "This dialog provides access to application " +
			"settings used by %1 and %2" )
			.arg( sFaceTransfer )
			.arg( sFaceTransfer2 );
		wDlg.caption = sDialogTitle;
		wDlg.toolTip = sDialogHelp;
		wDlg.whatsThis = sWhatsThis.arg( sDialogTitle ).arg( sDialogHelp );
 
		// Get the wrapped widget
		var oDlgWgt = wDlg.getWidget();
 
		// Strip the space for a settings key
		var sKey = wDlg.caption.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 tab widget
		var wTabWidget = new DzTabWidget( wDlg );
		wDlg.addWidget( wTabWidget );
 
		// Initialize
		var aLabels = [];
		var aFtRegOptions = [];
		var aFtTrialOptions = [];
		var bFtOverrideEnabled = false;
 
		var oData;
		var oOption;
 
		// -----------------------------------
		// ---------- Face Transfer ----------
		// -----------------------------------
 
		// Face Transfer (1) is not available for macOS; it does not make sense
		// to display options that will ultimately have no impact
		if( App.platform() == DzApp.Windows ){
			// Get whether or not overrides are enabled
			bFtOverrideEnabled = isFeatureEnabled( sFaceTransfer, false );
 
			// Define common strings
			var sFtRegRequired = !bFtOverrideEnabled ? sRegRequired : "";
			var sG8or81 = 8 + " or " + 8.1;
 
			// Navigate down a level, to settings for "Face Transfer"
			s_oAppSettings.pushPath( sFaceTransfer );
 
			// Create a widget for the "Face Transfer" page
			var wFaceTransferPage = new DzWidget( wTabWidget );
			wFaceTransferPage.toolTip = text( "This page provides access to " +
				"application settings for %1" ).arg( sFaceTransfer );
 
			// Create a layout for the "Face Transfer" page
			var lytFtPage = new DzVBoxLayout( wFaceTransferPage );
			lytFtPage.margin = s_nMargin;
 
			// ----- Genesis 8 Load
 
			// Define the configuration data for creating the group
			oData = {};
			oData.nGenesisVer = 8;
			oData.sGenesisVer = sGenesisVer.arg( oData.nGenesisVer );
			oData.wGroupParent = wFaceTransferPage;
			oData.sGroupTitle = sLoadTitle.arg( oData.nGenesisVer );
			oData.sGroupTip = sLoadTip.arg( sG8or81 );
			oData.sGroupWhatsThis = sWhatsThis;
			oData.sGroupHelp = "";
			oData.sGroupRequired = sFtRegRequired;
			oData.bGroupEnabled = bFtOverrideEnabled;
			oData.aOptions = [
				{
					"sLabel": sFemale,
					"sKey": sDefFemaleKey,
					"sDefault": "/data/Daz 3D/Genesis 8 Female Starter Essentials/Utilities/Genesis 8 Female With Basic Wear.duf",
					"sTip": sLoadTip.arg( sG8or81 + " " + sFemale ),
					"sBrowse": sBrowseTip.arg( sGenesisVer.arg( sG8or81 ) + " " + sFemale ).arg( sLoadAsset )
				},
				{
					"sLabel": sMale,
					"sKey": sDefMaleKey,
					"sDefault": "/data/Daz 3D/Genesis 8 Male Starter Essentials/Utilities/Genesis 8 Male With Basic Wear.duf",
					"sTip": sLoadTip.arg( sG8or81 + " " + sMale ),
					"sBrowse": sBrowseTip.arg( sGenesisVer.arg( sG8or81 ) + " " + sMale ).arg( sLoadAsset )
				}
			];
 
			// Create the group
			oOption = createLabeledAssetGroup( oData );
			// Add the group to the page layout
			lytFtPage.addWidget( oOption.wGroupBox );
			// Capture the labels
			aLabels = aLabels.concat( oOption.aLabels );
			// Capture the options
			aFtRegOptions = aFtRegOptions.concat( oOption.aWidgets );
 
			// ----- Genesis 8 Material
 
			// Define the configuration data for creating the group
			oData = {};
			oData.nGenesisVer = 8;
			oData.sGenesisVer = sGenesisVer.arg( oData.nGenesisVer );
			oData.wGroupParent = wFaceTransferPage;
			oData.sGroupTitle = sMaterialTitle.arg( oData.nGenesisVer );
			oData.sGroupTip = sMaterialTip.arg( oData.nGenesisVer );
			oData.sGroupWhatsThis = sWhatsThis;
			oData.sGroupHelp = "";
			oData.sGroupRequired = sFtRegRequired;
			oData.bGroupEnabled = bFtOverrideEnabled;
			oData.aOptions = [
				{
					"sLabel": sFemale,
					"sKey": "Genesis8FemaleMaterial",
					"sDefault": "/data/DAZ 3D/Genesis 8/Female/Tools/Presets/Genesis 8 Female Simple MAT.duf",
					"sTip": sMaterialTip.arg( oData.nGenesisVer + " " + sFemale ),
					"sBrowse": sBrowseTip.arg( sGenesisVer.arg( oData.nGenesisVer ) + " " + sFemale ).arg( sMatAsset )
				},
				{
					"sLabel": sMale,
					"sKey": "Genesis8MaleMaterial",
					"sDefault": "/data/DAZ 3D/Genesis 8/Male/Tools/Presets/Genesis 8 Male Simple MAT.duf",
					"sTip": sMaterialTip.arg( oData.nGenesisVer + " " + sMale ),
					"sBrowse": sBrowseTip.arg( sGenesisVer.arg( oData.nGenesisVer ) + " " + sMale ).arg( sMatAsset )
				}
			];
 
			// Create the group
			oOption = createLabeledAssetGroup( oData );
			// Add the group to the page layout
			lytFtPage.addWidget( oOption.wGroupBox );
			// Capture the labels
			aLabels = aLabels.concat( oOption.aLabels );
			// Capture the options
			aFtRegOptions = aFtRegOptions.concat( oOption.aWidgets );
 
			// ----- Genesis 8.1 Material
 
			// Define the configuration data for creating the group
			oData = {};
			oData.nGenesisVer = 8.1;
			oData.sGenesisVer = sGenesisVer.arg( oData.nGenesisVer );
			oData.wGroupParent = wFaceTransferPage;
			oData.sGroupTitle = sMaterialTitle.arg( oData.nGenesisVer );
			oData.sGroupTip = sMaterialTip.arg( oData.nGenesisVer );
			oData.sGroupWhatsThis = sWhatsThis;
			oData.sGroupHelp = "";
			oData.sGroupRequired = sFtRegRequired;
			oData.bGroupEnabled = bFtOverrideEnabled;
			oData.aOptions = [
				{
					"sLabel": sFemale,
					"sKey": "Genesis8_1FemaleMaterial",
					"sDefault": "/data/DAZ 3D/Genesis 8/Female 8_1/Tools/Presets/Genesis 8.1 Female Simple MAT.duf",
					"sTip": sMaterialTip.arg( oData.nGenesisVer + " " + sFemale ),
					"sBrowse": sBrowseTip.arg( sGenesisVer.arg( oData.nGenesisVer ) + " " + sFemale ).arg( sMatAsset )
				},
				{
					"sLabel": sMale,
					"sKey": "Genesis8_1MaleMaterial",
					"sDefault": "/data/DAZ 3D/Genesis 8/Male 8_1/Tools/Presets/Genesis 8.1 Male Simple MAT.duf",
					"sTip": sMaterialTip.arg( oData.nGenesisVer + " " + sMale ),
					"sBrowse": sBrowseTip.arg( sGenesisVer.arg( oData.nGenesisVer ) + " " + sMale ).arg( sMatAsset )
				}
			];
 
			// Create the group
			oOption = createLabeledAssetGroup( oData );
			// Add the group to the page layout
			lytFtPage.addWidget( oOption.wGroupBox );
			// Capture the labels
			aLabels = aLabels.concat( oOption.aLabels );
			// Capture the options
			aFtRegOptions = aFtRegOptions.concat( oOption.aWidgets );
 
			// ----- Post Process
 
			// Get whether or not overrides are enabled
			var bFtPostProcessEnabled = isFeatureEnabled( sFaceTransfer, true );
			var sFtTrialRequired = !bFtPostProcessEnabled ? sTrialRequired : "";
 
			// Define the configuration data for creating the group
			oData = {};
			oData.nGenesisVer = 8;
			oData.sGenesisVer = sGenesisVer.arg( oData.nGenesisVer );
			oData.wGroupParent = wFaceTransferPage;
			oData.sGroupTitle = sPostProcessTitle;
			oData.sGroupTip = sPostProcessTip.arg( oData.nGenesisVer );
			oData.sGroupWhatsThis = sWhatsThis;
			oData.sGroupHelp = sPostProcessHelp;
			oData.sGroupRequired = sFtTrialRequired;
			oData.bGroupEnabled = bFtPostProcessEnabled;
			oData.aOptions = [
				{
					"sLabel": sScript,
					"sKey": sPostProcessKey,
					"sDefault": "",
					"sTip": sPostProcessTip.arg( oData.nGenesisVer ),
					"sBrowse": sBrowseTip.arg( sGenesisVer.arg( oData.nGenesisVer ) ).arg( sPostProcessScript )
				}
			];
 
			// Create the group
			oOption = createLabeledAssetGroup( oData );
			// Add the group to the page layout
			lytFtPage.addWidget( oOption.wGroupBox );
			// Capture the labels
			aLabels = aLabels.concat( oOption.aLabels );
			// Capture the options
			aFtTrialOptions = aFtTrialOptions.concat( oOption.aWidgets );
 
			// Iterate over the Face Transfer trial options
			for( var i = 0, n = aFtTrialOptions.length; i < n; i += 1 ){
				// Get the 'current' option
				oOption = aFtTrialOptions[i];
				// Add a "None" (clear) item to the menu
				addComboEditFunctionItem( oOption.wComboEdit, -1, text( "None" ), true, handleComboEditNoneItemChanged );
			}
 
			// Add stretch to keep widget layouts tight
			lytFtPage.addStretch( 1 );
 
			// Add the page to the tab widget
			wTabWidget.addTab( wFaceTransferPage, sFaceTransfer );
 
			// Navigate up a level, to the root of the application settings
			s_oAppSettings.popPath();
		}
 
 
		// -------------------------------------
		// ---------- Face Transfer 2 ----------
		// -------------------------------------
 
		// Get whether or not overrides are enabled
		var bFt2OverrideEnabled = isFeatureEnabled( sFaceTransfer2, false );
		var sFt2RegRequired = !bFt2OverrideEnabled ? sRegRequired : "";
 
		// Initialize
		var aFt2RegOptions = [];
		var aFt2TrialOptions = [];
 
		// Navigate down a level, to settings for "Face Transfer 2"
		s_oAppSettings.pushPath( sFaceTransfer2 );
 
		// Create a widget for the "Face Transfer 2" page
		var wFaceTransfer2Page = new DzWidget( wTabWidget );
		wFaceTransfer2Page.toolTip = text( "This page provides access to " +
			"application settings for %1" ).arg( sFaceTransfer2 );
 
		// Create a layout for the "Face Transfer 2" page
		var lytFt2Page = new DzVBoxLayout( wFaceTransfer2Page );
		lytFt2Page.margin = s_nMargin;
 
		// ----- Genesis 9 Load
 
		// Define the configuration data for creating the group
		oData = {};
		oData.nGenesisVer = 9;
		oData.sGenesisVer = sGenesisVer.arg( oData.nGenesisVer );
		oData.wGroupParent = wFaceTransfer2Page;
		oData.sGroupTitle = sLoadTitle.arg( oData.nGenesisVer );
		oData.sGroupTip = sLoadTip.arg( oData.nGenesisVer );
		oData.sGroupWhatsThis = sWhatsThis;
		oData.sGroupHelp = "";
		oData.sGroupRequired = sFt2RegRequired;
		oData.bGroupEnabled = bFt2OverrideEnabled;
		oData.aOptions = [
			{
				"sLabel": sFemale,
				"sKey": sDefFemaleKey,
				"sDefault": "/data/Daz 3D/Genesis 9 Starter Essentials/Utilities/Genesis 9 Female With Basic Wear.duf",
				"sTip": sLoadTip.arg( oData.nGenesisVer + " " + sFemale ),
				"sBrowse": sBrowseTip.arg( sGenesisVer.arg( oData.nGenesisVer ) + " " + sFemale ).arg( sLoadAsset )
			},
			{
				"sLabel": sMale,
				"sKey": sDefMaleKey,
				"sDefault": "/data/Daz 3D/Genesis 9 Starter Essentials/Utilities/Genesis 9 Male With Basic Wear.duf",
				"sTip": sLoadTip.arg( oData.nGenesisVer + " " + sMale ),
				"sBrowse": sBrowseTip.arg( sGenesisVer.arg( oData.nGenesisVer ) + " " + sMale ).arg( sLoadAsset )
			}
		];
 
		// Create the group
		oOption = createLabeledAssetGroup( oData );
		// Add the group to the page layout
		lytFt2Page.addWidget( oOption.wGroupBox );
		// Capture the labels
		aLabels = aLabels.concat( oOption.aLabels );
		// Capture the options
		aFt2RegOptions = aFtRegOptions.concat( oOption.aWidgets );
 
		// ----- Genesis 9 Material
 
		// Define the configuration data for creating the group
		oData = {};
		oData.nGenesisVer = 9;
		oData.sGenesisVer = sGenesisVer.arg( oData.nGenesisVer );
		oData.wGroupParent = wFaceTransfer2Page;
		oData.sGroupTitle = sMaterialTitle.arg( oData.nGenesisVer );
		oData.sGroupTip = sMaterialTip.arg( oData.nGenesisVer );
		oData.sGroupWhatsThis = sWhatsThis;
		oData.sGroupHelp = "";
		oData.sGroupRequired = sFt2RegRequired;
		oData.bGroupEnabled = bFt2OverrideEnabled;
		oData.aOptions = [
			{
				"sLabel": sFemale,
				"sKey": "Genesis9FemaleMaterial",
				"sDefault": "/data/DAZ 3D/Genesis 9/Base/Tools/Presets/Genesis 9 Female Simple MAT.duf",
				"sTip": sMaterialTip.arg( oData.nGenesisVer + " " + sFemale ),
				"sBrowse": sBrowseTip.arg( sGenesisVer.arg( oData.nGenesisVer ) + " " + sFemale ).arg( sMatAsset )
			},
			{
				"sLabel": sMale,
				"sKey": "Genesis9MaleMaterial",
				"sDefault": "/data/DAZ 3D/Genesis 9/Base/Tools/Presets/Genesis 9 Male Simple MAT.duf",
				"sTip": sMaterialTip.arg( oData.nGenesisVer + " " + sMale ),
				"sBrowse": sBrowseTip.arg( sGenesisVer.arg( oData.nGenesisVer ) + " " + sMale ).arg( sMatAsset )
			}
		];
 
		// Create the group
		oOption = createLabeledAssetGroup( oData );
		// Add the group to the page layout
		lytFt2Page.addWidget( oOption.wGroupBox );
		// Capture the labels
		aLabels = aLabels.concat( oOption.aLabels );
		// Capture the options
		aFt2RegOptions = aFt2RegOptions.concat( oOption.aWidgets );
 
		// ----- Post Process
 
		// Get whether or not overrides are enabled
		var bFt2PostProcessEnabled = isFeatureEnabled( sFaceTransfer2, true );
		var sFt2TrialRequired = !bFt2PostProcessEnabled ? sTrialRequired : "";
 
		// Define the configuration data for creating the group
		oData = {};
		oData.nGenesisVer = 9;
		oData.sGenesisVer = sGenesisVer.arg( oData.nGenesisVer );
		oData.wGroupParent = wFaceTransfer2Page;
		oData.sGroupTitle = sPostProcessTitle;
		oData.sGroupTip = sPostProcessTip.arg( oData.nGenesisVer );
		oData.sGroupWhatsThis = sWhatsThis;
		oData.sGroupHelp = sPostProcessHelp;
		oData.sGroupRequired = sFt2TrialRequired;
		oData.bGroupEnabled = bFt2PostProcessEnabled;
		oData.aOptions = [
			{
				"sLabel": sScript,
				"sKey": sPostProcessKey,
				"sDefault": "",
				"sTip": sPostProcessTip.arg( oData.nGenesisVer ),
				"sBrowse": sBrowseTip.arg( sGenesisVer.arg( oData.nGenesisVer ) ).arg( sPostProcessScript )
			}
		];
 
		// Create the group
		oOption = createLabeledAssetGroup( oData );
		// Add the group to the page layout
		lytFt2Page.addWidget( oOption.wGroupBox );
		// Capture the labels
		aLabels = aLabels.concat( oOption.aLabels );
		// Capture the options
		aFt2TrialOptions = aFt2TrialOptions.concat( oOption.aWidgets );
 
		// Iterate over the Face Transfer 2 trial options
		for( var i = 0, n = aFt2TrialOptions.length; i < n; i += 1 ){
			// Get the 'current' option
			oOption = aFt2TrialOptions[i];
			// Add a "None" (clear) item to the menu
			addComboEditFunctionItem( oOption.wComboEdit, -1, text( "None" ), true, handleComboEditNoneItemChanged );
		}
 
		// Add stretch to keep widget layouts tight
		lytFt2Page.addStretch( 1 );
 
		// Add the page to the tab widget
		wTabWidget.addTab( wFaceTransfer2Page, sFaceTransfer2 );
 
		// Navigate up a level, to the root of the application settings
		s_oAppSettings.popPath();
 
		//--------------------------------------------
 
		// Align all of the labels
		setLabelMinimumWidths( aLabels, 40 );
 
		// Get the minimum height of the dialog
		var sizeHint = oDlgWgt.minimumSizeHint;
		var nHeight = sizeHint.height;
 
		// Set the fixed height of the dialog to the minimum
		wDlg.setFixedHeight( nHeight );
 
		// Display the dialog
		if( wDlg.exec() ){
			// Navigate down a level, to settings for "Face Transfer"
			s_oAppSettings.pushPath( sFaceTransfer );
 
			// If overrides are enabled for Genesis 8
			if( bFtOverrideEnabled ){
				// Iterate over the Face Transfer registered options
				for( var i = 0, n = aFtRegOptions.length; i < n; i += 1 ){
					// Get the 'current' option
					oOption = aFtRegOptions[i];
					// Set or remove the setting depending on whether the value is the default
					setOrRemoveStringSetting( oOption.sKey, oOption.wComboEdit.text, oOption.sDefault );
				}
			}
 
			// If the post processing override is enabled for Genesis 8/8.1
			if( bFtPostProcessEnabled ){
				// Iterate over the Face Transfer trial options
				for( var i = 0, n = aFtTrialOptions.length; i < n; i += 1 ){
					// Get the 'current' option
					oOption = aFtTrialOptions[i];
					// Set or remove the setting depending on whether the value is the default
					setOrRemoveStringSetting( oOption.sKey, oOption.wComboEdit.text, oOption.sDefault );
				}
			}
 
			// Navigate up a level, to the root of the application settings
			s_oAppSettings.popPath();
 
			// Navigate down a level, to settings for "Face Transfer 2"
			s_oAppSettings.pushPath( sFaceTransfer2 );
 
			// If overrides are enabled for Genesis 9
			if( bFt2OverrideEnabled ){
				// Iterate over the Face Transfer 2 registered options
				for( var i = 0, n = aFt2RegOptions.length; i < n; i += 1 ){
					// Get the 'current' option
					oOption = aFt2RegOptions[i];
					// Set or remove the setting depending on whether the value is the default
					setOrRemoveStringSetting( oOption.sKey, oOption.wComboEdit.text, oOption.sDefault );
				}
			}
 
			// If the post processing override is enabled for Genesis 9
			if( bFt2PostProcessEnabled ){
				// Iterate over the Face Transfer 2 trial options
				for( var i = 0, n = aFt2TrialOptions.length; i < n; i += 1 ){
					// Get the 'current' option
					oOption = aFt2TrialOptions[i];
					// Set or remove the setting depending on whether the value is the default
					setOrRemoveStringSetting( oOption.sKey, oOption.wComboEdit.text, oOption.sDefault );
				}
			}
 
			// Navigate up a level, to the root of the application settings
			s_oAppSettings.popPath();
		}
	};
 
	/*********************************************************************/
	// Display the dialog
	doDialog();
 
// Finalize the function and invoke
})();