From 1dcddc9b4e2ab6da6a628f24ad0ebe04e555d8e8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 25 Feb 2021 18:49:43 +0100 Subject: [PATCH] Deadline in Pype 3 - added new implementation of Event listener Added missed custom plugins --- vendor/deadline/custom/events/Pype/Pype.param | 37 + vendor/deadline/custom/events/Pype/Pype.py | 264 + .../custom/plugins/GlobalJobPreLoad.py | 76 + .../custom/plugins/MayaPype/AssetTools.mel | 911 +++ .../plugins/MayaPype/BifrostMemusage.mel | 67 + .../MayaPype/DeadlineMayaBatchFunctions.py | 239 + .../plugins/MayaPype/MayaBatchUtils.mel | 217 + .../custom/plugins/MayaPype/MayaPype.ico | Bin 0 -> 76854 bytes .../custom/plugins/MayaPype/MayaPype.options | 1073 ++++ .../custom/plugins/MayaPype/MayaPype.param | 154 + .../custom/plugins/MayaPype/MayaPype.py | 5002 +++++++++++++++++ .../PypeTileAssembler/PypeTileAssembler.ico | Bin 0 -> 110294 bytes .../PypeTileAssembler.options | 35 + .../PypeTileAssembler/PypeTileAssembler.param | 17 + .../PypeTileAssembler/PypeTileAssembler.py | 372 ++ vendor/deadline/readme.md | 3 + 16 files changed, 8467 insertions(+) create mode 100644 vendor/deadline/custom/events/Pype/Pype.param create mode 100644 vendor/deadline/custom/events/Pype/Pype.py create mode 100644 vendor/deadline/custom/plugins/GlobalJobPreLoad.py create mode 100644 vendor/deadline/custom/plugins/MayaPype/AssetTools.mel create mode 100644 vendor/deadline/custom/plugins/MayaPype/BifrostMemusage.mel create mode 100644 vendor/deadline/custom/plugins/MayaPype/DeadlineMayaBatchFunctions.py create mode 100644 vendor/deadline/custom/plugins/MayaPype/MayaBatchUtils.mel create mode 100644 vendor/deadline/custom/plugins/MayaPype/MayaPype.ico create mode 100644 vendor/deadline/custom/plugins/MayaPype/MayaPype.options create mode 100644 vendor/deadline/custom/plugins/MayaPype/MayaPype.param create mode 100644 vendor/deadline/custom/plugins/MayaPype/MayaPype.py create mode 100644 vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.ico create mode 100644 vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.options create mode 100644 vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.param create mode 100644 vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.py create mode 100644 vendor/deadline/readme.md diff --git a/vendor/deadline/custom/events/Pype/Pype.param b/vendor/deadline/custom/events/Pype/Pype.param new file mode 100644 index 0000000000..1452163bdd --- /dev/null +++ b/vendor/deadline/custom/events/Pype/Pype.param @@ -0,0 +1,37 @@ +[State] +Type=Enum +Items=Global Enabled;Opt-In;Disabled +Category=Options +CategoryOrder=0 +CategoryIndex=0 +Label=State +Default=Global Enabled +Description=How this event plug-in should respond to events. If Global, all jobs and slaves will trigger the events for this plugin. If Opt-In, jobs and slaves can choose to trigger the events for this plugin. If Disabled, no events are triggered for this plugin. + +[PythonSearchPaths] +Type=MultiLineMultiFolder +Label=Additional Python Search Paths +Category=Options +CategoryOrder=0 +CategoryIndex=1 +Default= +Description=The list of paths to append to the PYTHONPATH environment variable. This allows the Python job to find custom modules in non-standard locations. + +[LoggingLevel] +Type=Enum +Label=Logging Level +Category=Options +CategoryOrder=0 +CategoryIndex=2 +Items=DEBUG;INFO;WARNING;ERROR +Default=DEBUG +Description=Logging level where printing will start. + +[PypeExecutable] +Type=MultiLineMultiFolder +Label=Path to Pype executable dir +Category=Job Plugins +CategoryOrder=1 +CategoryIndex=1 +Default= +Description= \ No newline at end of file diff --git a/vendor/deadline/custom/events/Pype/Pype.py b/vendor/deadline/custom/events/Pype/Pype.py new file mode 100644 index 0000000000..2b2904b229 --- /dev/null +++ b/vendor/deadline/custom/events/Pype/Pype.py @@ -0,0 +1,264 @@ +import os +import sys +import logging +import json +import subprocess +import platform +import os +import tempfile +import time + +import Deadline.Events +import Deadline.Scripting + +from System import Environment + +def GetDeadlineEventListener(): + return PypeEventListener() + + +def CleanupDeadlineEventListener(eventListener): + eventListener.Cleanup() + + +class PypeEventListener(Deadline.Events.DeadlineEventListener): + """ + Called on every Deadline plugin event, used for injecting Pype + environment variables into rendering process. + + Expects that job already contains env vars: + AVALON_PROJECT + AVALON_ASSET + AVALON_TASK + AVALON_APP_NAME + Without these only global environment would be pulled from Pype + + Configure 'Path to Pype executable dir' in Deadlines + 'Tools > Configure Events > pype ' + Only directory path is needed. + + """ + ALREADY_INJECTED = False + + def __init__(self): + self.OnJobSubmittedCallback += self.OnJobSubmitted + self.OnJobStartedCallback += self.OnJobStarted + self.OnJobFinishedCallback += self.OnJobFinished + self.OnJobRequeuedCallback += self.OnJobRequeued + self.OnJobFailedCallback += self.OnJobFailed + self.OnJobSuspendedCallback += self.OnJobSuspended + self.OnJobResumedCallback += self.OnJobResumed + self.OnJobPendedCallback += self.OnJobPended + self.OnJobReleasedCallback += self.OnJobReleased + self.OnJobDeletedCallback += self.OnJobDeleted + self.OnJobErrorCallback += self.OnJobError + self.OnJobPurgedCallback += self.OnJobPurged + + self.OnHouseCleaningCallback += self.OnHouseCleaning + self.OnRepositoryRepairCallback += self.OnRepositoryRepair + + self.OnSlaveStartedCallback += self.OnSlaveStarted + self.OnSlaveStoppedCallback += self.OnSlaveStopped + self.OnSlaveIdleCallback += self.OnSlaveIdle + self.OnSlaveRenderingCallback += self.OnSlaveRendering + self.OnSlaveStartingJobCallback += self.OnSlaveStartingJob + self.OnSlaveStalledCallback += self.OnSlaveStalled + + self.OnIdleShutdownCallback += self.OnIdleShutdown + self.OnMachineStartupCallback += self.OnMachineStartup + self.OnThermalShutdownCallback += self.OnThermalShutdown + self.OnMachineRestartCallback += self.OnMachineRestart + + self.ALREADY_INJECTED = False + + + def Cleanup(self): + del self.OnJobSubmittedCallback + del self.OnJobStartedCallback + del self.OnJobFinishedCallback + del self.OnJobRequeuedCallback + del self.OnJobFailedCallback + del self.OnJobSuspendedCallback + del self.OnJobResumedCallback + del self.OnJobPendedCallback + del self.OnJobReleasedCallback + del self.OnJobDeletedCallback + del self.OnJobErrorCallback + del self.OnJobPurgedCallback + + del self.OnHouseCleaningCallback + del self.OnRepositoryRepairCallback + + del self.OnSlaveStartedCallback + del self.OnSlaveStoppedCallback + del self.OnSlaveIdleCallback + del self.OnSlaveRenderingCallback + del self.OnSlaveStartingJobCallback + del self.OnSlaveStalledCallback + + del self.OnIdleShutdownCallback + del self.OnMachineStartupCallback + del self.OnThermalShutdownCallback + del self.OnMachineRestartCallback + + def inject_pype_environment(self, job, additonalData={}): + + if self.ALREADY_INJECTED: + self.LogInfo("Environment injected previously") + return + + # adding python search paths + paths = self.GetConfigEntryWithDefault("PythonSearchPaths", "").strip() + paths = paths.split(";") + + for path in paths: + self.LogInfo("Extending sys.path with: " + str(path)) + sys.path.append(path) + + self.LogInfo("inject_pype_environment start") + try: + if platform.system().lower() == "linux": + pype_command = "pype_console.sh" + elif platform.system().lower() == "windows": + pype_command = "pype_console.exe" + + pype_root = self.GetConfigEntryWithDefault("PypeExecutable", "").strip() + + pype_app = os.path.join(pype_root , pype_command) + if not os.path.exists(pype_app): + raise RuntimeError("App '{}' doesn't exist. Fix it in Tools > Configure Events > pype".format(pype_app)) + + # tempfile.TemporaryFile cannot be used because of locking + export_url = os.path.join(tempfile.gettempdir(), + time.strftime('%Y%m%d%H%M%S'), + 'env.json') # add HHMMSS + delete later + self.LogInfo("export_url {}".format(export_url)) + + additional_args = {} + additional_args['project'] = job.GetJobEnvironmentKeyValue('AVALON_PROJECT') + additional_args['asset'] = job.GetJobEnvironmentKeyValue('AVALON_ASSET') + additional_args['task'] = job.GetJobEnvironmentKeyValue('AVALON_TASK') + additional_args['app'] = job.GetJobEnvironmentKeyValue('AVALON_APP_NAME') + self.LogInfo("args::{}".format(additional_args)) + + args = [ + pype_app, + 'extractenvironments', + export_url + ] + if all(additional_args.values()): + for key, value in additional_args.items(): + args.append("--{}".format(key)) + args.append(value) + + self.LogInfo("args::{}".format(args)) + + exit_code = subprocess.call(args, shell=True) + if exit_code != 0: + raise RuntimeError("Publishing failed") + + with open(export_url) as fp: + contents = json.load(fp) + self.LogInfo("contents::{}".format(contents)) + for key, value in contents.items(): + job.SetJobEnvironmentKeyValue(key, value) + + Deadline.Scripting.RepositoryUtils.SaveJob(job) # IMPORTANT + self.ALREADY_INJECTED = True + + os.remove(export_url) + + self.LogInfo("inject_pype_environment end") + except Exception as error: + import traceback + self.LogInfo(traceback.format_exc()) + self.LogInfo("inject_pype_environment failed") + Deadline.Scripting.RepositoryUtils.FailJob(job) + raise + + def updateFtrackStatus(self, job, statusName, createIfMissing=False): + "Updates version status on ftrack" + pass + + def OnJobSubmitted(self, job): + self.LogInfo("OnJobSubmitted LOGGING") + # for 1st time submit + self.inject_pype_environment(job) + self.updateFtrackStatus(job, "Render Queued") + + def OnJobStarted(self, job): + self.LogInfo("OnJobStarted") + # inject_pype_environment shouldnt be here, too late already + self.updateFtrackStatus(job, "Rendering") + + def OnJobFinished(self, job): + self.updateFtrackStatus(job, "Artist Review") + + def OnJobRequeued(self, job): + self.LogInfo("OnJobRequeued LOGGING") + self.inject_pype_environment(job) + + def OnJobFailed(self, job): + pass + + def OnJobSuspended(self, job): + self.LogInfo("OnJobSuspended LOGGING") + self.updateFtrackStatus(job, "Render Queued") + + def OnJobResumed(self, job): + self.LogInfo("OnJobResumed LOGGING") + self.updateFtrackStatus(job, "Rendering") + + def OnJobPended(self, job): + self.LogInfo("OnJobPended LOGGING") + + def OnJobReleased(self, job): + pass + + def OnJobDeleted(self, job): + pass + + def OnJobError(self, job, task, report): + self.LogInfo("OnJobError LOGGING") + data = {"task": task, "report": report} + + def OnJobPurged(self, job): + pass + + def OnHouseCleaning(self): + pass + + def OnRepositoryRepair(self, job): + pass + + def OnSlaveStarted(self, job): + self.LogInfo("OnSlaveStarted LOGGING") + + def OnSlaveStopped(self, job): + pass + + def OnSlaveIdle(self, job): + pass + + def OnSlaveRendering(self, host_name, job): + self.LogInfo("OnSlaveRendering LOGGING") + + def OnSlaveStartingJob(self, host_name, job): + self.LogInfo("OnSlaveStartingJob LOGGING") + # inject params must be here for Resubmits + self.inject_pype_environment(job) + + def OnSlaveStalled(self, job): + pass + + def OnIdleShutdown(self, job): + pass + + def OnMachineStartup(self, job): + pass + + def OnThermalShutdown(self, job): + pass + + def OnMachineRestart(self, job): + pass diff --git a/vendor/deadline/custom/plugins/GlobalJobPreLoad.py b/vendor/deadline/custom/plugins/GlobalJobPreLoad.py new file mode 100644 index 0000000000..016fd51b11 --- /dev/null +++ b/vendor/deadline/custom/plugins/GlobalJobPreLoad.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +"""Remap pype path and PYPE_METADATA_PATH.""" +import platform +from Deadline.Scripting import RepositoryUtils + + +def pype_command_line(executable, arguments, workingDirectory): + """Remap paths in comand line argument string. + + Using Deadline rempper it will remap all path found in command-line. + + Args: + executable (str): path to executable + arguments (str): arguments passed to executable + workingDirectory (str): working directory path + + Returns: + Tuple(executable, arguments, workingDirectory) + + """ + print("-" * 40) + print("executable: {}".format(executable)) + print("arguments: {}".format(arguments)) + print("workingDirectory: {}".format(workingDirectory)) + print("-" * 40) + print("Remapping arguments ...") + arguments = RepositoryUtils.CheckPathMapping(arguments) + print("* {}".format(arguments)) + print("-" * 40) + return executable, arguments, workingDirectory + + +def pype(deadlinePlugin): + """Remaps `PYPE_METADATA_FILE` and `PYPE_PYTHON_EXE` environment vars. + + `PYPE_METADATA_FILE` is used on farm to point to rendered data. This path + originates on platform from which this job was published. To be able to + publish on different platform, this path needs to be remapped. + + `PYPE_PYTHON_EXE` can be used to specify custom location of python + interpreter to use for Pype. This is remappeda also if present even + though it probably doesn't make much sense. + + Arguments: + deadlinePlugin: Deadline job plugin passed by Deadline + + """ + job = deadlinePlugin.GetJob() + pype_metadata = job.GetJobEnvironmentKeyValue("PYPE_METADATA_FILE") + pype_python = job.GetJobEnvironmentKeyValue("PYPE_PYTHON_EXE") + # test if it is pype publish job. + if pype_metadata: + pype_metadata = RepositoryUtils.CheckPathMapping(pype_metadata) + if platform.system().lower() == "linux": + pype_metadata = pype_metadata.replace("\\", "/") + + print("- remapping PYPE_METADATA_FILE: {}".format(pype_metadata)) + job.SetJobEnvironmentKeyValue("PYPE_METADATA_FILE", pype_metadata) + deadlinePlugin.SetProcessEnvironmentVariable( + "PYPE_METADATA_FILE", pype_metadata) + + if pype_python: + pype_python = RepositoryUtils.CheckPathMapping(pype_python) + if platform.system().lower() == "linux": + pype_python = pype_python.replace("\\", "/") + + print("- remapping PYPE_PYTHON_EXE: {}".format(pype_python)) + job.SetJobEnvironmentKeyValue("PYPE_PYTHON_EXE", pype_python) + deadlinePlugin.SetProcessEnvironmentVariable( + "PYPE_PYTHON_EXE", pype_python) + + deadlinePlugin.ModifyCommandLineCallback += pype_command_line + + +def __main__(deadlinePlugin): + pype(deadlinePlugin) diff --git a/vendor/deadline/custom/plugins/MayaPype/AssetTools.mel b/vendor/deadline/custom/plugins/MayaPype/AssetTools.mel new file mode 100644 index 0000000000..dca9894aa0 --- /dev/null +++ b/vendor/deadline/custom/plugins/MayaPype/AssetTools.mel @@ -0,0 +1,911 @@ +/* +Copyright 2017 Thinkbox Software +All Rights Reserved + +This file has the following public functions. Examples of how to use them are below: +get_network_assets +get_filename_attributes_from_scene +get_source_file_paths_from_attr +get_files_to_copy +write_asset_introspection_file_transfer_pairs +gather_source_file_copies +change_to_relative_directory +write_repathing_mel_script_file +repath_assets +find_asset_paths +do_local_asset_caching + +Use #1 - Local Asset Caching: +This code is used in production for our "Local Asset Caching". In this case, everything is run on the slave-side, it copies the files, then repaths them. +You can use it directly on the slave-side by calling do_local_asset_caching. +In this case, this file must be executed on the slave-side. + +User #2 - Local hot folder synching and remote asset repathing: +This file also contains all the functions needed to do scene introspection and hot folder copying. +This is an example of what we'd do on the submission-side if you wanted to copy all the files into a hot-folder, followed +by creating a script that should run on the slave-side-end which will repath them to the correct place. +It was written for Rodeo FX, but was never put into production. +NOTE: In this case, this MEL script must execute on the SUBMITTER side. +So, if we want to use this code for both 'local asset caching' and this hot-folder thing, we have to have this AssetTools.mel file both on the slave and the submitter. + +//Example submitter function: +proc function_to_run_on_submission_for_scene_introspection_and_hot_folder_syncing() { + + //This function creates a file called "asset_repathing_script.mel" which must be run on the slave-side to do the repathing. + + string $attrNames[0]; + string $cacheFileObjectNames[0]; + string $yetiAttrNames[0]; + string $sourceFiles[0]; + string $destFiles[0]; + string $gatheredSourceFiles[0]; + + get_filename_attributes_from_scene( $attrNames, $cacheFileObjectNames, $yetiAttrNames ); + + // Only use if you want to filter out files that are already local + string $networkPrefixes[] = { "//", "/", "X:", "x:", "Y:", "y:", "Z:", "z:" }; + get_network_assets( $attrNames, "basic", $networkPrefixes ); + get_network_assets( $cacheFileObjectNames, "cacheFile", $networkPrefixes ); + get_network_assets( $yetiAttrNames, "yeti", $networkPrefixes ); + + get_files_to_copy( "C:/temp", $attrNames, $cacheFileObjectNames, $yetiAttrNames, $sourceFiles, $destFiles ); + gather_source_file_copies("C:/temp", $sourceFiles, $gatheredSourceFiles ); + change_to_relative_directory( $gatheredSourceFiles, "C:/temp"); + + write_asset_introspection_file_transfer_pairs( "C:/temp/maya_asset_pairs.txt", $gatheredSourceFiles, $destFiles ); + repath_assets( "C:/temp", $attrNames, $cacheFileObjectNames, $yetiAttrNames ); + write_repathing_mel_script_file( "C:/temp/asset_repathing_script.mel", "C:/temp", $attrNames, $cacheFileObjectNames, $yetiAttrNames ); +} + +*/ + +///////////////////////////////INTERNAL FUNCTIONS//////////////////////////////////// + +proc int is_windows_path( string $v ) { + //Starts with "//" (windows network) + //Starts with "[driveletter]:/" (windows filesystem) + //Add to this as needed. + $v = fromNativePath( $v ); + if( size($v) < 4 ) //min size of four + return false; + $matchVal = `match ".*\n.*" $v`; //no newlines. important to add because of embedded mel in string attributes. + if( $matchVal != "" ) + return false; + if( startsWith( $v, "//" ) && !startsWith( $v, "///" ) ) //double slash starting. good. + return true; + $matchVal = `match "^[a-zA-Z]:/.*" $v`; //windows drive letter style. good. absolute paths only. + if( $matchVal != "" ) + return true; + return false; +} + +proc int is_posix_path( string $v ) { + //Starts with "/" (linux filesystem) + //Add to this as needed. + $v = fromNativePath( $v ); + if( size($v) < 2 ) //min size of two + return false; + $matchVal = `match ".*\n.*" $v`; //no newlines. important to add because of embedded mel in string attributes. + if( $matchVal != "" ) + return false; + if( startsWith( $v, "/" ) && !startsWith( $v, "//" ) ) //must start with single slash. only absolute paths supported (can't have a double slash. that's windows) + return true; + return false; +} + +proc string to_native_path( string $v ) { + //This is used to swap slashes on windows. + //There is already a MEL script function called toNativePath, but swaps to the filename types of the current OS. We want to do it based on the actual style of path instead. + //NOTE: MEL's "fromNativePath" ALWAYS converts everything to forward slashes, but "toNativePath" does it based on the current OS. + if( is_windows_path( $v ) ) + while( $v != ( $v = `substitute "/" $v "\\"` ) ); //swap to backslashes + return $v; +} + +proc int looks_like_filepath( string $attrVal ) { + //check all the cases for absolute filenames: + if( is_windows_path( $attrVal ) ) + return true; + if( is_posix_path( $attrVal ) ) + return true; + return false; +} + +proc string get_asset_path( string $attrName ) { + //Get a filename out of a string attribute + //Normally this is just the value of the attribute. + //HOWEVER there are special exceptions that we may need to handle here. + //An example might be filenames that use "####" in place of the current frame number or something. An example is Krakatoa objects. However, this isn't handled yet. + + //Normal case, just use the string attribute itself. Most things will end up here. + $attrVal = `getAttr $attrName`; //should return string. + + //Just exclude the empty string case right away + if( $attrVal == "" ) + return ""; + + //This swaps backslashes to forward slashes + $attrVal = fromNativePath( $attrVal ); + + if( looks_like_filepath( $attrVal ) ) + return $attrVal; + + return ""; +} + +proc int is_attribute_a_string( string $nodeAndAttrName ) { + //for some reason, some attributes produce a "The value for the attribute could not be retrieved" when trying to query them. If that's the case, then just skip it. I don't think this'll be an issue. + if( catchQuiet( `getAttr -type $nodeAndAttrName` ) ) { + return false; + } + + $attrType = `getAttr -type $nodeAndAttrName`; + if( $attrType == "string" ) { + return true; + } + return false; +} + +proc int is_multi_attribute_a_string( string $nodeName, string $attrNameComponents[] ) { + //Test whether this multi-attribute holds strings + //Without this test prior to recursion, we'll be recursively iterating through a lot of useless non-string attributes. For example, mesh vertices. + //For example, recurse to the end of "tweak1.plist[0].controlPoints[0].xValue" and determine if "xValue" is a string attribute. + + $multiAttrName = $nodeName; + for( $i=0; $i= 2 ) { + + //multi-attribute case. must use recursion to get the attributes. + if( is_multi_attribute_a_string( $nodeName, $attrNameComponents ) ) { + get_multi_attrs_recursive( $outAttrs, $nodeName, $attrNameComponents, 0 ); //will append to $outAttrs + } + + } else { + + //Normal (non-multi) attribute case + //Example: "file1.fileTextureName" + string $fullNodeAndAttrName = $nodeName + "." + $attrName; + + if( is_attribute_a_string( $fullNodeAndAttrName ) ) { + $outAttrs[ size($outAttrs) ] = $fullNodeAndAttrName; //append to $outAttrs + } + + } + } + + return $outAttrs; +} + +proc string[] get_all_string_attributes_from_scene() { + + //The values in the outgoing array look like "nodeName.attrName" or "nodeName.multiAttrName[index].attrName", etc. + + string $allStringAttrs[0]; + + $allSceneNodes = `ls`; + for( $i=0; $i*"` || `gmatch $filenameValue "**"` || check_if_file_exists( $filenameValue, $stringAttr ) ) { + $outAttrNames[ size($outAttrNames) ] = $stringAttr; //append + } + } +} + +proc get_all_filename_attributes_from_cacheFile_objects( string $outAttrNames[] ) { + //Nodes of type cacheFiles are intentionally skipped over in the attribute introspection code. + //That is because they present a special case where there are two files (.mcx, .xml) that are derived from a base directory and base name. + //I'm not 100% sure if it's always a .mcx file that it needs, but in our current case, it is. There always appears to be a .xml file too. + + string $allCacheNodeNames[] = `ls -type "cacheFile"`; + for( $i=0; $i /repath_root/C/win/path/myfile.ext + // 2) //netdrive/win/path/myfile.ext > /repath_root/netdrive/win/path/myfile.ext + // 3) /posix/path/myfile.ext > /repath_root/posix/path/myfile.ext + + //Potential trouble: + //Some characters that are valid in Windows may not be valid in Posix paths. I'm just assuming here that all the characters are allowable in both systems. + //Maybe need to do some additional mapping here to make it work. + + if( !looks_like_filepath( $repathRoot ) ) { + print( "ERROR: Could not repath to here because it is not a valid absolute path: '" + $repathRoot + "'\n" ); + return ""; + } + + $repathRoot = fromNativePath( $repathRoot ); + if( substring( $repathRoot, size($repathRoot), size($repathRoot) ) == "/" ) //strip trailing slash + $repathRoot = substring( $repathRoot, 1, size($repathRoot)-1 ); + + $outPath = ""; + if( is_windows_path( $sourcePath ) ) { + + $winPath = ""; + if( startsWith( $sourcePath, "//" ) ) { + $winPath = substring( $sourcePath, 3, size($sourcePath) ); //strip double-slash + } else { + //remove the ":", so it looks like "/repath_root/C/previous_path/filename.ext" + $winPath = toupper( substring( $sourcePath, 1, 1 ) ) + substring( $sourcePath, 3, size($sourcePath) ); + } + $outPath = $repathRoot + "/" + $winPath; + + } else { //is_posix_path() will return true here, because was checked earlier to be one or the other. + $outPath = $repathRoot + $sourcePath; + } + return $outPath; +} + +proc int is_local_file( string $path, string $networkPrefixes[] ) { + int $isLocal = true; + + for( $i=0; $i $deleteTimerInSec ) + { + string $assetName = `substring $manifest 1 (size($manifest)-$manifestSize_CONST)`; + print( "Deleting asset file " + $assetName + " because it is more than " + $deleteTimer + " days old\n" ); + sysFile -delete $assetName; + sysFile -delete $manifest; + } + } +} + +proc delete_assets( string $cacheDir, int $deleteTimer ) { + + string $manifests[] = `getFileList -folder $cacheDir -fs "*.manifest"`; + for( $i = 0; $i < size($manifests); $i++ ) + { + $manifests[$i] = $cacheDir + "/" + $manifests[$i]; + } + string $folders[] = `getFileList -folder $cacheDir`; + for( $folder in $folders ) + { + string $currFolder = $cacheDir + "/" + $folder; + delete_assets( $currFolder, $deleteTimer ); + } + inspect_manifests( $manifests, $deleteTimer ); +} + +////////////////////// EXTERNAL FUNCTIONS TO BE USED FOR HOT-FOLDER SYNCHING AND REPATH, AND LOCAL ASSET CACHING ////////////////// + +// Using $networkPrefixes we filter out all assets that don't have a network prefix so that we only mess with network files +global proc get_network_assets( string $outAttrNames[], string $type, string $networkPrefixes[] ) { + string $filenameValue; + + for( $i=size($outAttrNames)-1; $i>=0; $i-- ) { + if( $type == "cacheFile" ) { + string $filenames[] = get_cacheFile_paths( $outAttrNames[$i] ); + // Only need to check 1 path, as they have the same folder + $filenameValue = $filenames[0]; + } else if( $type == "yeti" ) { + $filenameValue = get_yeti_path( $outAttrNames[$i] ); + } else { // basic + $filenameValue = get_asset_path( $outAttrNames[$i] ); + } + + if( is_local_file( $filenameValue, $networkPrefixes ) ) { + stringArrayRemoveAtIndex( $i, $outAttrNames ); + } + } +} + +global proc get_filename_attributes_from_scene( string $outAttrNames[], string $outCacheFileObjectNames[], string $outYetiAttrNames[] ) { + clear( $outAttrNames ); + clear( $outCacheFileObjectNames ); + clear( $outYetiAttrNames ); + + //First get name/value pairs from string attributes in the scene + //This will be the majority of all assets. Special case attributes to follow. + get_all_filename_attributes_from_scene( $outAttrNames ); + + //Special case alert: Nodes of type "cacheFile" need to be special caseed because they combine two attributes to make a path. + get_all_filename_attributes_from_cacheFile_objects( $outCacheFileObjectNames ); + + //Special case the Yeti nodes because you have to get attributes differently. + if( `pluginInfo -query -loaded "pgYetiMaya"` ){ + get_all_filename_attributes_from_yeti_objects( $outYetiAttrNames ); + } +} + +global proc get_source_file_paths_from_attr( string $outAttrNames[], string $outCacheFileObjectNames[], string $outYetiAttrNames[], string $outSourceFiles[] ){ + python( "import maya.app.general.fileTexturePathResolver" ); + + // Build list of files + for( $i=0; $i 0 ) { + $outputSourceDestPairsFilename = fromNativePath( $outputSourceDestPairsFilename ); //Sanitize + $fileId = `fopen $outputSourceDestPairsFilename "w"`; + + for( $i=0; $i" + $dest + "\n" ); + fprint $fileId $fileLineStr; + print( $fileLineStr ); //For debug + } + + fclose $fileId; + } +} + +global proc gather_source_file_copies( string $gatherDirectoryRoot, string $inOrigSourceFiles[], string $outNewSourceFiles[] ) { + + //NOTE: On error (couldn't copy for some reason), an error message is printed, and the original path to the file is returned. + //We might need to change this behaviour to handle errors better/differently. + + //Sanitize inputs + clear( $outNewSourceFiles ); + $gatherDirectoryRoot = fromNativePath( $gatherDirectoryRoot ); + if( substring( $gatherDirectoryRoot, size($gatherDirectoryRoot), size($gatherDirectoryRoot) ) != "/" ) //add trailing slash + $gatherDirectoryRoot += "/"; + + for( $i=0; $i*"` || `gmatch $translatedAttrFiles[$i] "**"`|| `filetest -f $translatedAttrFiles[$i]` ) { + $fileLineStr = "catch( `setAttr -type \"string\" \"" + $attrNames[$i] + "\" \"" + $translatedAttrFiles[$i] + "\"` );\n"; + fprint $fileId $fileLineStr; + } + } + } + + // repath cacheFile directories + for( $i=0; $i*"` || `gmatch $translatedAttrFiles[$i] "**"`|| `filetest -f $translatedAttrFiles[$i]` ) { + catch( `setAttr -type "string" $attrNames[$i] $translatedAttrFiles[$i]` ); + } + } + } + + // repath cacheFile directories + for( $i=0; $i" $cachePaths; + delete_assets( $cachePaths[0], $deleteTimer ); +} + +string $dl_ignorable_asset_attributes[] = {"rmanGlobals.imageOutputDir","rmanGlobals.ribOutputDir"}; +proc int is_ignorable_attribute( string $attr ) +{ + global string $dl_ignorable_asset_attributes[]; + return `stringArrayContains $attr $dl_ignorable_asset_attributes`; +} + +global proc string[] find_asset_paths() +{ + filePathEditor -refresh; + string $file_paths[]; + string $dirs[] = `filePathEditor -q -listDirectories ""`; + for( $dir in $dirs ) + { + string $files[] = `filePathEditor -q -withAttribute -listFiles $dir`; + int $i; + for( $i = 0; $i Render Options. +// +// Also handy to put in a per-frame expression. + +global float $ELAPSED; + +global proc BifrostMemUsage() { + + // Initialize python. + global float $ELAPSED; + python( "import time" ); + float $time = python( "'%.02f' % (time.time()-float("+$ELAPSED+"))" ); + print( "[bifrostMemUsage] ELAPSED since last eval: "+$time+" seconds.\n" ); + $ELAPSED = python( "time.time()" ); + + // Get data. + string $date_time = `date`; + float $frame = `currentTime -q`; + string $free = `memory -mb -freeMemory`; + string $used = `memory -mb -heapMemory`; + + // Make Mb amounts nicer. + python( "import locale" ); + python( "locale.setlocale(locale.LC_ALL, '')" ); + $free = python( "format( "+int($free)+", 'n' ).decode(locale.getpreferredencoding())" ); + + $used = python( "format( "+int($used)+", 'n' ).decode(locale.getpreferredencoding())" ); + string $mem_output = "[bifrostMemUsage] FRAME "+$frame+" ("+$date_time+")\n"; + $mem_output += "[bifrostMemUsage] USED: "+$used+" Mb\n"; + $mem_output += "[bifrostMemUsage] FREE: "+$free+" Mb\n"; + string $containers[] = `ls -type bifrostContainer`; + + for( $container in $containers ) { + + $mem_output += " >>> "+$container+" <<<\n"; + if( `objExists ($container+".masterVoxelSize")` ) { + float $mvs = `getAttr ($container+".masterVoxelSize")`; + $mem_output += " MVS: "+$mvs+"\n" ; + } // end if + + // Get associated Bifrost object. + string $bifrost_objs[] = `listConnections -shapes on -type bifrostShape $container`; + string $bifrost_obj = $bifrost_objs[0]; + string $num_parts = `getAttr( $bifrost_obj+".outNumParticles")`; + string $num_voxels = `getAttr( $bifrost_obj+".outNumVoxels")`; + + $num_parts = python( "format( "+int($num_parts)+", 'n' ).decode(locale.getpreferredencoding())" ); + $num_voxels = python( "format( "+int($num_voxels)+", 'n' ).decode(locale.getpreferredencoding())" ); + $mem_output += " Particles: "+$num_parts+"\n" ; + $mem_output += " Voxels: "+$num_voxels+"\n" ; + + } + + $mem_output += "\n"; + + // Finally, print out. + print( $mem_output ); + +} // end bifrostMemUsage \ No newline at end of file diff --git a/vendor/deadline/custom/plugins/MayaPype/DeadlineMayaBatchFunctions.py b/vendor/deadline/custom/plugins/MayaPype/DeadlineMayaBatchFunctions.py new file mode 100644 index 0000000000..7f30deda31 --- /dev/null +++ b/vendor/deadline/custom/plugins/MayaPype/DeadlineMayaBatchFunctions.py @@ -0,0 +1,239 @@ +from __future__ import print_function + +import json +import os +import re +import subprocess + +import maya.cmds +import maya.mel + +# The version that Redshift fixed the render layer render setup override locking issue +# Prior versions will need to use the workaround in the unlockRenderSetupOverrides function +REDSHIFT_RENDER_SETUP_FIX_VERSION = (2, 5, 64) + +def getCurrentRenderLayer(): + return maya.cmds.editRenderLayerGlobals( query=True, currentRenderLayer=True ) + +# A method mimicing the built-in mel function: 'renderLayerDisplayName', but first tries to see if it exists +def getRenderLayerDisplayName( layer_name ): + if maya.mel.eval( 'exists renderLayerDisplayName' ): + layer_name = maya.mel.eval( 'renderLayerDisplayName ' + layer_name ) + else: + # renderLayerDisplayName doesn't exist, so we try to do it ourselves + if layer_name == 'masterLayer': + return layer_name + + if maya.cmds.objExists(layer_name) and maya.cmds.nodeType( layer_name ) == 'renderLayer': + # Display name for default render layer + if maya.cmds.getAttr( layer_name + '.identification' ) == 0: + return 'masterLayer' + + # If Render Setup is used the corresponding Render Setup layer name should be used instead of the legacy render layer name. + result = maya.cmds.listConnections( layer_name + '.msg', type='renderSetupLayer' ) + if result: + return result[0] + + return layer_name + +# remove_override_json_string is a json string consisting of a node as a key, with a list of attributes we want to unlock as the value +# ie. remove_override_json_string = '{ "defaultRenderGlobals": [ "animation", "startFrame", "endFrame" ] }' +def unlockRenderSetupOverrides( remove_overrides_json_string ): + try: + # Ensure we're in a version that HAS render setups + import maya.app.renderSetup.model.renderSetup as renderSetup + except ImportError: + return + + # Ensure that the scene is actively using render setups and not the legacy layers + if not maya.mel.eval( 'exists mayaHasRenderSetup' ) or not maya.mel.eval( 'mayaHasRenderSetup();' ): + return + + # If the version of Redshift has the bug fix, bypass the overrides + if not redshiftRequiresWorkaround(): + return + + remove_overrides = json.loads( remove_overrides_json_string ) + + render_setup = renderSetup.instance() + layers = render_setup.getRenderLayers() + layers_to_unlock = [ layer for layer in layers if layer.name() != 'defaultRenderLayer' ] + + for render_layer in layers_to_unlock: + print('Disabling Render Setup Overrides in "%s"' % render_layer.name()) + for collection in render_layer.getCollections(): + if type(collection) == maya.app.renderSetup.model.collection.RenderSettingsCollection: + for override in collection.getOverrides(): + if override.targetNodeName() in remove_overrides and override.attributeName() in remove_overrides[ override.targetNodeName() ]: + print( ' Disabling Override: %s.%s' % ( override.targetNodeName(), override.attributeName() ) ) + override.setSelfEnabled( False ) + +def redshiftRequiresWorkaround(): + # Get the version of Redshift + redshiftVersion = maya.cmds.pluginInfo( 'redshift4maya', query=True, version=True ) + redshiftVersion = tuple( int(version) for version in redshiftVersion.split('.') ) + # Check if the Redshift version is prior to the bug fix + return redshiftVersion < REDSHIFT_RENDER_SETUP_FIX_VERSION + + +def performArnoldPathmapping( startFrame, endFrame, tempLocation=None ): + """ + Performs pathmapping on all arnold standin files that are need for the current task + :param startFrame: Start frame of the task + :param endFrame: End frame of the task + :param tempLocation: The temporary location where all pathmapped files will be copied to. Only needs to be provided the first time this function is called. + :return: Nothing + """ + if tempLocation: + performArnoldPathmapping.tempLocation = tempLocation + else: + if not performArnoldPathmapping.tempLocation: + raise ValueError( "The first call made to performArnoldPathmapping must provided a tempLocation" ) + + #a simple regex for finding frame numbers + frameRE = re.compile( r'#+' ) + + # Define a function that will be used when looping to replace padding with a 0 padded string. + def __replaceHashesWithZeroPaddedFrame( frameNum, origFileName ): + return frameRE.sub( lambda matchObj: str( frameNum ).zfill( len(matchObj.group(0)) ), origFileName ) + + standInObjects = maya.cmds.ls( type="aiStandIn" ) + for standIn in standInObjects: + try: + # If we have already seen this node before then grab the settings that we need + origDir, origFileName = performArnoldPathmapping.originalProperties[ standIn ] + except KeyError: + # If we have not seen this node before then store it's original path and update the path in the node to where we will be pathmapping the file. + standinFile = maya.cmds.getAttr( standIn + ".dso" ) + + if not standinFile or os.path.splitext( standinFile )[ 1 ].lower() != ".ass": + # If the standinFile isn't set or isn't .ass file then we cannot pathmap it. + continue + + origDir, origFileName = os.path.split( standinFile ) + standinTempLocation = os.path.join( performArnoldPathmapping.tempLocation, standIn ) + + maya.cmds.setAttr( "%s.dso" % standIn, os.path.join( standinTempLocation, origFileName ), type="string" ) + #Create the Temp directory the first time we see a new standin + if not os.path.isdir( standinTempLocation ): + os.makedirs( standinTempLocation ) + + performArnoldPathmapping.originalProperties[ standIn ] = (origDir, origFileName) + + for frame in range( startFrame, endFrame + 1 ): + # evaluate the frame that the node is using (Normally it will be the same as the scene but it can be different) + evalFrame = maya.cmds.getAttr( "%s.frameNumber" % standIn, time=frame ) + fileNameWithFrame = __replaceHashesWithZeroPaddedFrame( evalFrame, origFileName ) + + # If we have already mapped this file then continue. + if not ( standIn, fileNameWithFrame ) in performArnoldPathmapping.mappedFiles: + #Perform pathmapping + runPathmappingOnFile( + os.path.join( origDir, fileNameWithFrame ), + os.path.join( performArnoldPathmapping.tempLocation, standIn, fileNameWithFrame ) + ) + performArnoldPathmapping.mappedFiles.add( ( standIn, fileNameWithFrame ) ) + +performArnoldPathmapping.tempLocation = "" +#State property which contains mappings of standin objects to their original fileproperties +performArnoldPathmapping.originalProperties = {} +#State property which contains unique identifier for each file that we have already mapped in the form of ( standin, filename ) +performArnoldPathmapping.mappedFiles=set() + + +def runPathmappingOnFile( originalLocation, pathmappedLocation ): + print( 'Running PathMapping on "%s" and copying to "%s"' % (originalLocation, pathmappedLocation) ) + arguments = [ "-CheckPathMappingInFile", originalLocation, pathmappedLocation ] + print( CallDeadlineCommand( arguments ) ) + +def GetDeadlineCommand(): + deadlineBin = "" + try: + deadlineBin = os.environ['DEADLINE_PATH'] + except KeyError: + #if the error is a key error it means that DEADLINE_PATH is not set. however Deadline command may be in the PATH or on OSX it could be in the file /Users/Shared/Thinkbox/DEADLINE_PATH + pass + + # On OSX, we look for the DEADLINE_PATH file if the environment variable does not exist. + if deadlineBin == "" and os.path.exists( "/Users/Shared/Thinkbox/DEADLINE_PATH" ): + with open( "/Users/Shared/Thinkbox/DEADLINE_PATH" ) as f: + deadlineBin = f.read().strip() + + deadlineCommand = os.path.join(deadlineBin, "deadlinecommand") + + return deadlineCommand + +def CallDeadlineCommand(arguments, hideWindow=True): + deadlineCommand = GetDeadlineCommand() + startupinfo = None + creationflags = 0 + if os.name == 'nt': + if hideWindow: + # Python 2.6 has subprocess.STARTF_USESHOWWINDOW, and Python 2.7 has subprocess._subprocess.STARTF_USESHOWWINDOW, so check for both. + if hasattr( subprocess, '_subprocess' ) and hasattr( subprocess._subprocess, 'STARTF_USESHOWWINDOW' ): + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW + elif hasattr( subprocess, 'STARTF_USESHOWWINDOW' ): + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + else: + # still show top-level windows, but don't show a console window + CREATE_NO_WINDOW = 0x08000000 #MSDN process creation flag + creationflags = CREATE_NO_WINDOW + + arguments.insert( 0, deadlineCommand ) + + # Specifying PIPE for all handles to workaround a Python bug on Windows. The unused handles are then closed immediatley afterwards. + proc = subprocess.Popen(arguments, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo, creationflags=creationflags) + output, errors = proc.communicate() + + return output + +def OutputPluginVersions(): + print("================== PLUGINS ===================\n") + plugins = sorted(maya.cmds.pluginInfo(query=True, listPlugins=True), key=lambda p: p.lower()) + for plugin in plugins: + version = maya.cmds.pluginInfo(plugin, query=True, version=True) + print("%s (v%s)" % (plugin, version)) + print("==============================================\n") + +def ForceLoadPlugins(): + """ + Force load an explicit set of plug-ins with known issues. There are bugs in Maya where these plug-ins are not + automatically loaded when required in a scene. + + When a scene contains an Alembic reference node (backed by an external .abc file), Maya does not embed "requires" + statements into the scene to indicate that the "AbcImport" and "fbxmaya" plug-ins are dependencies of the scene. + This can be changed for the current Maya session with the following MEL commands: + + pluginInfo -edit -writeRequires AbcImport + pluginInfo -edit -writeRequires fbxmaya + + However, there is a secondary bug where the "requires" statements are inserted in the scene after already trying to + load the references. + + Our work-around is to force loading of these plug-ins always before loading the job scene. Both plugins ship with + Maya and are fairly lightweight in size. + """ + + PLUGINS_TO_LOAD = ( + 'AbcImport', # For Maya 2017 on Windows this is 5MB and takes 15 ms to load + 'fbxmaya' # For Maya 2017 on Windows this is 12MB and takes 141ms to load + ) + + for plugin in PLUGINS_TO_LOAD: + plugin_loaded = maya.cmds.pluginInfo(plugin, query=True, loaded=True) + if not plugin_loaded: + try: + print( "Loading %s..." % plugin, end="" ) + maya.cmds.loadPlugin( plugin ) + except RuntimeError as e: + # Maya raises this exception when it cannot find the plugin. The message is formatted as: + # + # Plug-in, "pluginName", was not found on MAYA_PLUG_IN_PATH + # + # This seems reasonable enough to forward on to the user. The try-except only serves the purpose of + # continuing to attempt additional plug-ins. This is a best-effort work-around. + print( 'Error: %s' % e) + else: + print( "ok" ) \ No newline at end of file diff --git a/vendor/deadline/custom/plugins/MayaPype/MayaBatchUtils.mel b/vendor/deadline/custom/plugins/MayaPype/MayaBatchUtils.mel new file mode 100644 index 0000000000..b3fa1c5553 --- /dev/null +++ b/vendor/deadline/custom/plugins/MayaPype/MayaBatchUtils.mel @@ -0,0 +1,217 @@ +proc string deadlineMapIndividualFile( string $origFile, int $checkExistence ) +{ + if( $origFile == "" ) + { + return $origFile; + } + + string $mappedFile = `dirmap -cd $origFile`; + if( $mappedFile != $origFile && $checkExistence ) + { + if( catchQuiet(` python( "import maya.app.general.fileTexturePathResolver" )` ) ) + { + print( "Unable to Import FileTexturePathResolver ignoring file existence check for pathmapping.\n" ); + return $mappedFile; + } + + string $resolvedFiles[] = `python( "maya.app.general.fileTexturePathResolver.findAllFilesForPattern('" + $mappedFile + "', None)" )`; + if( size( $resolvedFiles ) == 0 ) + { + print( "Failed to Pathmap " + $origFile+ " no files exist that match the following mapped filename " + $mappedFile + "\n" ); + $mappedFile = $origFile; + } + } + + return $mappedFile; +} + +//this function is used to remap all file paths of certain node type that include tokens. It is currently being used to fix an issue with the dirmap command. +global proc remapNodeFilePathsWithTokens( string $nodeType, string $pathAttr, int $checkExistence ) +{ + string $fileNodes[] = `ls -type $nodeType`; + + for( $fileNode in $fileNodes ) + { + string $fileAttr = $fileNode + "." + $pathAttr; + string $curFile = `getAttr $fileAttr`; + string $mappedFile = `deadlineMapIndividualFile $curFile $checkExistence`; + if( $mappedFile != $curFile ) + { + print( "Changing "+$fileAttr+ " from " + $curFile + " to " + $mappedFile+"\n" ); + setAttr -type "string" $fileAttr $mappedFile; + } + } +} + +global proc mapOpenColorIOFile( int $enable ) +{ + string $origConfigPath = ""; + + // Ensure that the colorManagementPrefs command exists, last confirmed version is 2015 since docs aren't available before then + if( catchQuiet( $origConfigPath = `colorManagementPrefs -q -configFilePath` ) ) + { + return; + } + + string $mappedConfigPath = `deadlineMapIndividualFile $origConfigPath false`; + if( $mappedConfigPath != $origConfigPath ) + { + print( "Changing OCIO Config File from " + $origConfigPath + " to " + $mappedConfigPath+"\n" ); + colorManagementPrefs -e -configFilePath $mappedConfigPath; + colorManagementPrefs -e -cmConfigFileEnabled $enable; + } + +} + +proc string evaluateXgenVariable(string $variable) +{ + // Computes the expanded XGen variable. + + // This does not work for description or object/module level variables. + + // xg.expandFilepath and its arguments are listed in the following header file: + // + // \plug-ins\xgen\include\XGen\XgExternalAPI.h + // + // NOTE: This is not documented in XGen's python API docs, but is exposed via the xgenm.XgExternalAPI module + return python("xg.expandFilepath('" + $variable + "', '')"); +} + +proc string[] evaluateXGenVariables() +{ + // Computes an array of all XGen variables that need to be expanded for path mapping XGen attributes + + // This function evaluates the following XGen path variables: + // - ${HOME} + // - ${XGEN_ROOT} + // - ${XGEN_LOCATION} + // - ${PROJECT} + + // The function returns a one-dimensional array (MEL only supports 1D arrays) containing the mapping of the + // variable to its evaluated value. Each odd-indexed entry is a string containing the unexpanded variable. Each + // subsequent even-indexed entry contains the expanded value of the variable. + + // Example: + // > print(evaluateXGenVariables()); + // {"${HOME}", "C:/Users/usiskin/Documents", "${XGEN_ROOT}", "C:/Assets/Maya/xgen", ... } + + // Array of XGen variables to be expanded + string $xgenVariables[] = { + "${HOME}", + "${XGEN_ROOT}", + "${XGEN_LOCATION}", + "${PROJECT}" + }; + + // Resulting array that will contains interleaved xgen variables and their evaluated values + string $xgenValues[] = {}; + + // Temporary working variables + string $xgenVariable; + string $xgenValue; + int $i = 0; + + // Evaluate all XGen variables + for( $xgenVariable in $xgenVariables ) + { + $xgenValue = evaluateXgenVariable( $xgenVariable ); + stringArrayInsertAtIndex($i++, $xgenValues, $xgenVariable); + stringArrayInsertAtIndex($i++, $xgenValues, $xgenValue); + } + + return $xgenValues; +} + +proc mapXgenAttributes(string $palette, string $description, string $object, string $deadlineMappings[], string $xgenVarMap[] ) +{ + int $i = 0; + + string $attributes[] = python("xg.attrs( \"" + $palette + "\", \"" + $description + "\", \"" + $object + "\" )"); + + for ($attribute in $attributes) + { + string $attrVal = python("xg.getAttr( \"" + $attribute + "\",\"" + $palette + "\", \"" + $description + "\", \"" + $object + "\" )"); + + // Replace slashes so that they are all forward slashes. Maya (and XGen) expect only forward slashes in paths, + // regardless of platform. + $attrVal = substituteAllString($attrVal, "\\", "/"); + + // Make a copy of the attribute value that we will use to expand XGen variables and run path mapping on. + string $newAttrVal = $attrVal; + + // Substitue the XGen collection variables with their evaluated values (if we were able to evaluate them) + for( $i = 0; $i < size($xgenVarMap); $i += 2 ) + { + string $xgenVariable = $xgenVarMap[$i]; + string $xgenValue = $xgenVarMap[$i + 1]; + if ( size( $xgenValue ) > 0 ) + { + $newAttrVal = substituteAllString($newAttrVal, $xgenVariable, $xgenValue); + } + } + + // Run the path mapping string substitutions + for( $i = 0; $i < size($deadlineMappings); $i += 2 ) + { + $newAttrVal = substituteAllString($newAttrVal, $deadlineMappings[ $i ], $deadlineMappings[ $i + 1 ] ); + } + + if ( $newAttrVal != $attrVal ) + { + // Escape quotes in the attribute values so that they can be used in a python statement + string $escapedNewAttrVal = substituteAllString($newAttrVal, "\"", "\\\""); + + string $command = "xg.setAttr( \"" + $attribute + "\",\"" + $escapedNewAttrVal + "\",\"" + $palette + "\", \"" + $description + "\", \"" + $object + "\" )"; + python($command); + + // Build full attribute path + string $fullAttribute = $palette + "."; + if( size($description) ) + { + $fullAttribute += $description + "."; + } + if( size($object) ) + { + $fullAttribute += $object + "."; + } + $fullAttribute += $attribute; + + print ( "Changing '" + $fullAttribute + "' from '" + $attrVal + "' To '" + $newAttrVal + "'\n" ); + } + } +} + +global proc mapXGen( string $deadlineMappings[] ) +{ + catchQuiet ( python("import xgenm as xg") ); + string $palettes[]; + + clear $palettes; + catchQuiet ( $palettes = python("xg.palettes()") ); + if ( `size $palettes` == 0 ) + return; + + string $descriptions[]; + string $objects[]; + + // Evaluate all XGen variables required for path mapping + string $xgenVarMap[] = evaluateXGenVariables(); + + for ( $palette in $palettes ) + { + mapXgenAttributes($palette, "", "", $deadlineMappings, $xgenVarMap); + clear $descriptions; + $descriptions = python("xg.descriptions( \"" + $palette + "\" )"); + for ( $description in $descriptions ) + { + mapXgenAttributes($palette, $description, "", $deadlineMappings, $xgenVarMap); + clear $objects; + $objects = python("xg.objects( \"" + $palette + "\", \"" + $description + "\" )"); + $objects = stringArrayCatenate( $objects, `python("xg.fxModules( \"" + $palette + "\", \"" + $description + "\" )")` ); + for ( $object in $objects ) + { + mapXgenAttributes($palette, $description, $object, $deadlineMappings, $xgenVarMap); + } + } + } +} \ No newline at end of file diff --git a/vendor/deadline/custom/plugins/MayaPype/MayaPype.ico b/vendor/deadline/custom/plugins/MayaPype/MayaPype.ico new file mode 100644 index 0000000000000000000000000000000000000000..6d3a7bfa38c26197720b7474a52423f5d85494f2 GIT binary patch literal 76854 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 literal 0 HcmV?d00001 diff --git a/vendor/deadline/custom/plugins/MayaPype/MayaPype.options b/vendor/deadline/custom/plugins/MayaPype/MayaPype.options new file mode 100644 index 0000000000..bb5c9b032e --- /dev/null +++ b/vendor/deadline/custom/plugins/MayaPype/MayaPype.options @@ -0,0 +1,1073 @@ +[SceneFile] +Type=filename +Label=Scene Filename +Category=Scene File +Index=0 +Description=The scene filename as it exists on the network. +Required=false +DisableIfBlank=true + +[Renderer] +Type=label +Label=Renderer +Category=Maya Info +Index=0 +Description=The Maya renderer to use for rendering. +Required=false +DisableIfBlank=true + +[Version] +Type=label +Label=Version +Category=Maya Info +Index=1 +Description=The version of Maya to use to render. +Required=false +DisableIfBlank=true + +[Build] +Type=enum +Values=None;32bit;64bit +Label=Build To Force +Category=Maya Info +Index=2 +Description=The build of Maya to force. +Required=false +DisableIfBlank=true + +[StrictErrorChecking] +Type=boolean +Label=Enable Strict Error Checking +Category=Maya Info +Index=3 +Description=If true, checks for errors printed by the Maya renderer. Some of these errors are not fatal, so this option can be turned off to ignore them. +Required=true +DisableIfBlank=false +Default=true + +[StartupScript] +Type=filename +Label=Startup Script +Category=Maya Info +Index=4 +Description=Maya will source the specified script file on startup. +Required=false +DisableIfBlank=true + +[KrakatoaFinalPassDensity] +Type=float +Minimum=0.0 +Maximum=10.0 +Label=Final Pass Density +Category=Krakatoa Options +Index=0 +Description=The final pass density for particles. +Required=false +DisableIfBlank=true + +[KrakatoaFinalPassDensityExponent] +Type=integer +Minimum=-32 +Maximum=32 +Label=Final Pass Density Exponent +Category=Krakatoa Options +Index=1 +Description=The exponent for the final pass density. +Required=false +DisableIfBlank=true + +[KrakatoaUseLightingPassDensity] +Type=boolean +Label=Use Lighting Pass Density +Category=Krakatoa Options +Index=2 +Description=If true use the Lighting Pass Density. +Required=false +DisableIfBlank=true + +[KrakatoaLightingPassDensity] +Type=float +Minimum=0.0 +Maximum=10.0 +Label=Lighting Pass Density +Category=Krakatoa Options +Index=3 +Description=The Lighting pass density for particles. +Required=false +DisableIfBlank=true + +[KrakatoaLightingPassDensityExponent] +Type=integer +Minimum=-32 +Maximum=32 +Label=Lighting Pass exponent +Category=Krakatoa Options +Index=4 +Description=The exponent for the Lighting pass density. +Required=false +DisableIfBlank=true + +[KrakatoaUseEmissionStrength] +Type=boolean +Label=Use Emission Strength +Category=Krakatoa Options +Index=5 +Description=If true use the Lighting Pass Density. +Required=false +DisableIfBlank=true + +[KrakatoaEmissionStrength] +Type=float +Minimum=0.0 +Maximum=10.0 +Label=Emission Strength +Category=Krakatoa Options +Index=6 +Description=The emission strength of particles. +Required=false +DisableIfBlank=true + +[KrakatoaEmissionStrengthExponent] +Type=integer +Minimum=-32 +Maximum=32 +Label=Emission Strength Exponent +Category=Krakatoa Options +Index=7 +Description=The exponent for the Emission strength. +Required=false +DisableIfBlank=true + +[KrakatoaUseEmission] +Type=boolean +Label=Use Emission +Category=Krakatoa Options +Index=8 +Description=If true use Emission. +Required=false +DisableIfBlank=true + +[KrakatoaUseAbsorption] +Type=boolean +Label=Use Absorption +Category=Krakatoa Options +Index=9 +Description=If true use Absorption. +Required=false +DisableIfBlank=true + +[KrakatoaEnableMotionBlur] +Type=boolean +Label=Enable Motion Blur +Category=Krakatoa Options +Index=10 +Description=If true motion will be blurred. +Required=false +DisableIfBlank=true + +[KrakatoaMotionBlurParticleSegments] +Type=integer +Minimum=1 +Maximum=64 +Label=Motion Blur Particle Segments +Category=Krakatoa Options +Index=11 +Description=Number of segments in the motion blur. +Required=false +DisableIfBlank=true + +[KrakatoaJitteredMotionBlur] +Type=boolean +Label=Jittered Motion Blur +Category=Krakatoa Options +Index=12 +Description=jittered smoothing it out. +Required=false +DisableIfBlank=true + +[KrakatoaShutterAngle] +Type=float +Minimum=0.0 +Maximum=360.0 +Label=Shutter Angle +Category=Krakatoa Options +Index=13 +Description=The angle of the shutter. +Required=false +DisableIfBlank=true + +[KrakatoaEnableDOF] +Type=boolean +Label=Enable Depth of Field +Category=Krakatoa Options +Index=14 +Description=If true depth of field will be enabled. +Required=false +DisableIfBlank=true + +[KrakatoaSampleRateDOF] +Type=float +Minimum=0.01 +Maximum=10.0 +Label=Depth of Field Sample Rate +Category=Krakatoa Options +Index=15 +Description=the sample rate of the depth of field. +Required=false +DisableIfBlank=true + +[KrakatoaEnableMatteObjects] +Type=boolean +Label=Enable Matte Objects +Category=Krakatoa Options +Index=16 +Description=If true ??? +Required=false +DisableIfBlank=true + +[KrakatoaRenderingMethod] +Type=enum +Values=Particles;Voxels; +Label=Rendering Method +Category=Krakatoa Options +Index=25 +Description=The method of rendering to be used. +Required=false +DisableIfBlank=true + +[KrakatoaVoxelSize] +Type=float +Minimum=0.001 +Maximum=100.0 +Label=Voxel Size +Category=Krakatoa Options +Index=26 +Description=the size of the voxels. +Required=false +DisableIfBlank=true + +[KrakatoaVoxelFilterRadius] +Type=integer +Minimum=1 +Maximum=30 +Label=Voxel Filter Radius +Category=Krakatoa Options +Index=27 +Description=The Voxel Filter Radius. +Required=false +DisableIfBlank=true + +[KrakatoaForceEXROutput] +Type=boolean +Label=Force Output to Exr +Category=Krakatoa Options +Index=28 +Description=If true then output should be exr. +Required=false +DisableIfBlank=true + +[OctaneMaxSamples] +Type=integer +Minimum=1 +Maximum=64000 +Label=Max Samples +Category=Octane Options +Index=0 +Description=The maximum number of samples to use for rendering. +Required=false +DisableIfBlank=true + +[RedshiftVerbose] +Type=enum +Values=0;1;2 +Label=Redshift Verbose +Category=Redshift Options +Index=0 +Description=The level of verbosity when rendering with Redshift. +Required=false +DisableIfBlank=true + +[IRayUseCpus] +Type=boolean +Label=Use CPUs +Category=Iray Options +Index=0 +Description=If enabled, then CPUs will be used in addition to GPUs for rendering. +Required=false +DisableIfBlank=true + +[IRayCPULoad] +Type=float +Label=CPU Load +Category=Iray Options +Minimum=0.0 +DecimalPlaces=1 +Index=1 +Description=The number of CPUs that Iray can use. +Required=false +DisableIfBlank=true + +[IRayMaxSamples] +Type=integer +Label=Max Samples +Category=Iray Options +Minimum=1 +Maximum=4096 +Index=2 +Description=The Maximum number of samples the Render can take. +Required=false +DisableIfBlank=true + +[MayaToArnoldVersion] +Type=Label +Label=Maya To Arnold Major Version +Category=Arnold Options +Index=0 +Description=The major version of the Maya To Arnold (mtoa) plugin installed. +Required=false +DisableIfBlank=true + +[ArnoldVerbose] +Type=enum +Values=0;1;2;3 +Label=Arnold Verbosity +Category=Arnold Options +Index=1 +Description=The level of verbosity when rendering with Arnold. +Required=false +DisableIfBlank=true + +[MentalRayVerbose] +Type=enum +Values=;No Messages;Fatal Messages Only;Error Messages;Warning Messages;Info Messages;Progress Messages;Detailed Messages (Debug) +Label=Mental Ray Verbose +Category=Mental Ray Options +Index=0 +Description=The level of verbosity when rendering with Mental Ray. +Required=false +DisableIfBlank=true + +[AutoMemoryLimit] +Type=boolean +Label=Auto Memory Limit +Category=Mental Ray Options +Index=3 +Description=If checked, Mental Ray will automatically detect the optimal memory limit when rendering. +Required=false +DisableIfBlank=true + +[MemoryLimit] +Type=integer +Minimum=0 +Maximum=2147483647 +Label=Memory Limit +Category=Mental Ray Options +Index=4 +Description=Soft limit (in MB) for the memory used by Mental Ray (specify 0 for unlimited memory). +Required=false +DisableIfBlank=true + +[VRayAutoMemoryEnabled] +Type=boolean +Label=Enable Auto Memory Limit Detection +Category=VRay Options +Index=0 +Description=If checked, Deadline will automatically detect the dynamic memory limit for VRay prior to rendering. +Required=false +DisableIfBlank=true + +[VRayAutoMemoryBuffer] +Type=integer +Minimum=0 +Maximum=2147483647 +Label=Memory Limit Buffer (MB) +Category=VRay Options +Index=1 +Description=Deadline subtracts this value from the system's unused memory to determine the dynamic memory limit for VRay. +Required=false +DisableIfBlank=true + +[GPUsPerTask] +Type=integer +Minimum=0 +Maximum=16 +Label=GPUs Per Task +Category=GPU Options +Index=0 +Description=The number of GPUs to use per task. If set to 0, the default number of GPUs will be used. +Required=false +DisableIfBlank=true + +[GPUsSelectDevices] +Type=string +Label=Select GPU Devices +Category=GPU Options +Index=1 +Description=A comma separated list of the GPU devices to use specified by device Id. 'GPUs Per Task' will be ignored. +Required=false +DisableIfBlank=true + +[ImageWidth] +Type=integer +Minimum=1 +Label=Image Width +Category=Output Resolution +Index=0 +Description=The width of the image in pixels. +Required=false +DisableIfBlank=true + +[ImageHeight] +Type=integer +Minimum=1 +Label=Image Height +Category=Output Resolution +Index=1 +Description=The height of the image in pixels. +Required=false +DisableIfBlank=true + +[OutputFilePath] +Type=folder +Label=Output File Path +Category=Output Image File Options +Index=0 +Description=The directory in which output will be sent (must be an existing directory). +Required=false +DisableIfBlank=true + +[OutputFilePrefix] +Type=string +Label=Output File Prefix +Category=Output Image File Options +Index=1 +Description=The output filename prefix. +Required=false +DisableIfBlank=true + +[LocalRendering] +Type=boolean +Label=Enable Local Rendering +Category=Output Image File Options +Index=2 +Description=If this option is set to true, the Workers will render locally, and then copy the images to the network output directory on completion. +Required=false +DisableIfBlank=true + +[SkipExistingFrames] +Type=boolean +Label=Skip Existing Frames +Category=Output Image File Options +Index=3 +Description=If this option is set to true, Maya will skip rendering existing frames. +Required=false +DisableIfBlank=true + +[Animation] +Type=boolean +Label=Animation +Category=Render Options +Index=0 +Description=If off, Deadline will not pass any animation options to the Maya renderer. +Required=false +DisableIfBlank=true + +[MotionBlur] +Type=boolean +Label=Motion Blur +Category=Render Options +Index=1 +Description=Turns motion blur on or off. +Required=false +DisableIfBlank=true + +[FrameNumberOffset] +Type=integer +Label=Frame Number Offset +Category=Render Options +Index=2 +Description=Uses Maya's frame renumbering option to offset the frames that are rendered. +Required=false +DisableIfBlank=true + +[RenderHalfFrames] +Type=boolean +Label=Render Half Frames +Category=Render Options +Index=3 +Description=If checked, frames will be split into two using a step of 0.5. +Required=false +DisableIfBlank=true + +[MaxProcessors] +Type=integer +Label=Threads +Category=Render Options +Index=4 +Description=The number of processors to use. +Required=false +DisableIfBlank=true + +[Camera] +Type=enum +Label=Camera +Category=Render Options +Index=5 +Description=Choose which camera to use in the drop-down list. +Required=false +DisableIfBlank=true + +[AntiAliasing] +Type=enum +Values=; ;low;medium;high;highest +Label=Anti-Aliasing +Category=Render Options +Index=6 +Description=The level of edge antialiasing to use. +Required=false +DisableIfBlank=true + +[ProjectPath] +Type=folder +Label=Project Path +Category=Render Options +Index=7 +Description=The path to the Maya project folder. +Required=false +DisableIfBlank=true + +[UseLegacyRenderLayers] +Type=Boolean +Label=Use Legacy Render Layers +Category=Render Options +Index=8 +Description=As of Maya 2016.5, Autodesk has added a new render layer system (render setup) that is incompatible with the older version (legacy). This value must be the same value as in the file to render or it will fail. +Required=false +DisableIfBlank=true + +[RenderSetupIncludeLights] +Type=Boolean +Label=Render Setup - Include All Lights +Category=Render Options +Index=9 +Description=If enabled, all lights in the scene will automatically be added to every render setup. This is a machine level setting in Maya and we match that behaviour by pulling from the submitting machine. +Required=false +DisableIfBlank=true + +[UseLocalAssetCaching] +Type=boolean +Label=Use Local Asset Caching +Category=Render Options +Index=10 +Description=If enabled, Workers for this job will attempt to copy all network assets to their local cache and remap those assets in the scene file to point to the local cache (file structure is preserved). +Required=false +DisableIfBlank=true + +[EnableOpenColorIO] +Type=boolean +Label=Enable Open Color IO +Category=Render Options +Index=11 +Description=If enabled, the 'Use OCIO Configuration' setting will be enabled in Maya if the Open Color IO file was path mapped. +Required=false +DisableIfBlank=true + +[MaxwellRenderTime] +Type=float +Minimum=0.500 +Maximum=14400.000 +DecimalPlaces=3 +Label=Render Time (minutes) +Category=Maxwell Options +Index=0 +Description=Per-frame render time in minutes. +Required=false +DisableIfBlank=true + +[MaxwellSamplingLevel] +Type=integer +Minimum=0 +Maximum=50 +Label=Sampling Level +Category=Maxwell Options +Index=1 +Description=Maximum sampling level. +Required=false +DisableIfBlank=true + +[MaxwellResumeRender] +Type=boolean +Label=Resume Rendering From MXI File +Category=Maxwell Options +Index=2 +Description=If enabled, the Maxwell will try to resume from the previous render if the MXI file exists. +Required=false +DisableIfBlank=true + +[MentalRayExportfile] +Type=filenamesave +Label=MI File +Category=Mental Ray Export Options +Index=0 +Description=The filename of the exported Mental Ray files. +Required=false +DisableIfBlank=true + +[MentalRayExportBinary] +Type=boolean +Label=Export Binary MI File +Category=Mental Ray Export Options +Index=1 +Description=Whether or not the exported Mental Ray files should be in binary format. +Required=false +DisableIfBlank=true + +[MentalRayExportTabStop] +Type=integer +Minimum=0 +Maximum=100 +Label=ASCII Tabulator Size +Category=Mental Ray Export Options +Index=2 +Description=The tabulator size to use in ASCII Mental Ray files. +Required=false +DisableIfBlank=true + +[MentalRayExportPerFrame] +Type=enum +Values=0;1;2;3 +Label=Animation Export Option +Category=Mental Ray Export Options +Index=3 +Description=0: single file (name.ext), 1: one file per frame (name.ext.#), 2: one file per frame (name.#.ext), 3: one file per frame (name.#). +Required=false +DisableIfBlank=true + +[MentalRayExportPadFrame] +Type=integer +Minimum=0 +Maximum=9 +Label=MI File Pad Size +Category=Mental Ray Export Options +Index=4 +Description=The number of digits used for the MI File filename number. +Required=false +DisableIfBlank=true + +[MentalRayExportFragment] +Type=boolean +Label=Export Scene Fragment +Category=Mental Ray Export Options +Index=5 +Description=Whether or not to export a scene fragment. +Required=false +DisableIfBlank=true + +[MentalRayExportFragmentMaterials] +Type=boolean +Label=Export Materials +Category=Mental Ray Export Options +Index=6 +Description=Whether or not to export materials (if Export Scene Fragment is enabled). +Required=false +DisableIfBlank=true + +[MentalRayExportFragmentShaders] +Type=boolean +Label=Export All Incoming Shaders +Category=Mental Ray Export Options +Index=7 +Description=Whether or not to export all incoming shaders (if Export Scene Fragment is enabled). +Required=false +DisableIfBlank=true + +[MentalRayExportFragmentChildDag] +Type=boolean +Label=Export Entire Child DAG +Category=Mental Ray Export Options +Index=8 +Description=Whether or not to export entire child DAG (if Export Scene Fragment is enabled). +Required=false +DisableIfBlank=true + +[MentalRayExportPassContributionMaps] +Type=boolean +Label=Export Pass Conribution Maps +Category=Mental Ray Export Options +Index=9 +Description=Whether or not to export pass contribution maps. +Required=false +DisableIfBlank=true + +[MentalRayExportPassUserData] +Type=boolean +Label=Export Pass User Data +Category=Mental Ray Export Options +Index=10 +Description=Whether or not to export pass user data. +Required=false +DisableIfBlank=true + +[RIBDirectory] +Type=folder +Label=RIB Directory +Category=Renderman Export Options +Index=0 +Description=The directory in which exported scenes will be sent to. Works with Renderman tokens. +Required=false +DisableIfBlank=true + +[RIBPrefix] +Type=string +Label=RIB File Prefix +Category=Renderman Export Options +Index=1 +Description=The file prefix for the exported scene files. Works with Renderman tokens. +Required=false +DisableIfBlank=true + +[BifrostJob] +Type=boolean +Category=Bifrost Options +Label=Runs a Bifrost export job +Index=0 +Description=Whether a Bifrost Export job is running. +Required=false +DisableIfBlank=true + +[BifrostCompressionFormat] +Type=enum +Values=0;1;2 +Label=Bifrost Compression Format +Category=Bifrost Options +Index=1 +Description=Simple, Float or Quantization. +Required=false +DisableIfBlank=true + +[AlembicJob] +Type=boolean +Category=Alembic Options +Label=Runs an Alembic export job +Index=0 +Description=Whether an Alembic export job is running. +Required=false +DisableIfBlank=true + +[AlembicJob] +Type=boolean +Category=Alembic Options +Label=Runs an Alembic export job +Index=0 +Description=Whether an Alembic export job is running. +Required=false +DisableIfBlank=true + +[Verbose] +Type=boolean +Category=Alembic Options +Label=Enable Verbose +Index=1 +Description=Print the current frame being evaluated. +Required=false +DisableIfBlank=true + +[NoNormals] +Type=boolean +Category=Alembic Options +Label=Enable NoNormals +Index=2 +Description=Whether normal data for Alembic poly meshes will be written. +Required=false +DisableIfBlank=true + +[RenderableOnly] +Type=boolean +Category=Alembic Options +Label=Enable RenderableOnly +Index=3 +Description=Whether Non-Renderable Hierarchy will be written out. +Required=false +DisableIfBlank=true + +[StripNamespaces] +Type=boolean +Category=Alembic Options +Label=Enable StripNamespaces +Index=4 +Description=Strip Namespace from node before writing. +Required=false +DisableIfBlank=true + +[UVWrite] +Type=boolean +Category=Alembic Options +Label=Enable UVWrite +Index=5 +Description=Whether UV Data will be written. +Required=false +DisableIfBlank=true + +[WriteColorSets] +Category=Alembic Options +Type=boolean +Label=Enable WriteColorSets +Index=6 +Description=Write all color sets on MFnMeshes. +Required=false +DisableIfBlank=true + +[WriteFaceSets] +Type=boolean +Category=Alembic Options +Label=Enable WriteFaceSets +Index=7 +Description=Write all face sets on MFnMeshes. +Required=false +DisableIfBlank=true + +[WholeFrameGeo] +Type=boolean +Category=Alembic Options +Label=Enable WholeFrameGeo +Index=8 +Description=Whether geometry data will only be written on whole frames. +Required=false +DisableIfBlank=true + +[WorldSpace] +Type=boolean +Category=Alembic Options +Label=Enable WorldSpace +Index=9 +Description=Any root nodes will be stored in world space. +Required=false +DisableIfBlank=true + +[FilterEulerRotations] +Type=boolean +Category=Alembic Options +Label=Enable Euler Filtering +Index=10 +Description=Whether to apply Euler filter in rotation sampling. +Required=false +DisableIfBlank=true + +[WriteCreases] +Type=boolean +Category=Alembic Options +Label=Enable WriteCreases +Index=11 +Description=Whether crease info will be stored in the Alembic file. +Required=false +DisableIfBlank=true + +[WriteVisibility] +Type=boolean +Category=Alembic Options +Label=Enable WriteVisiblity +Index=12 +Description=Whether visibility state will be stored in the Alembic file. +Required=false +DisableIfBlank=true + +[AlembicFormatOption] +Type=enum +Values=HDF;Ogawa +Label=Alembic Cache Format +Category=Alembic Options +Index=13 +Description=The Cache format to use, HDF or Ogawa. +Required=false +DisableIfBlank=true + +[AlembicSelection] +Type=string +Label=Selected Items for Alembic Cache +Category=Alembic Options +Index=14 +Description=The collection of all items to be exported to the Alembic format. Either "All" or a comma-separated list of selected items. +Required=false + +[OutputFile] +Type=filenamesave +Label=Alembic File +Category=Alembic Options +Index=15 +Description=The Alembic file to be written. +Required=false +DisableIfBlank=true + +[AlembicSubFrames] +Type=boolean +Category=Alembic Options +Label=Alembic SubFrames +Index=16 +Description=Whether or not subframes should be exported for Alembic files. +Required=false +DisableIfBlank=true + +[AlembicLowSubFrame] +Type=float +Minimum=-10.0 +Maximum=10.0 +Label=Low Sub Frame +Category=Alembic Options +Index=17 +Description=The Lower subframe to be exported. +Required=false +DisableIfBlank=true + +[AlembicHighSubFrame] +Type=float +Minimum=-10.0 +Maximum=10.0 +Label=High Sub Frame +Category=Alembic Options +Index=18 +Description=The Lower subframe to be exported. +Required=false +DisableIfBlank=true + +[AlembicAttributes] +Type=string +Label=Attributes +Category=Alembic Options +Index=14 +Description=A comma separated list of attributes to be exported from the geometry. +Required=false + +[AlembicAttributePrefix] +Type=string +Label=Attribute Prefixes +Category=Alembic Options +Index=14 +Description=A comma separated list of attribute prefixes to be exported from the geometry. +Required=false + +[ScriptJob] +Type=boolean +Category=Maya Script Job Options +Label=Run a Maya script job +Index=0 +Description=Whether a Maya Script job is running. +Required=false +DisableIfBlank=true + +[ScriptFilename] +Type=filename +Label=Script Filename +Category=Maya Script Job Options +Index=1 +Description=The script filename as it exists on the network. +Required=false +DisableIfBlank=true + +[GeometryCacheJob] +Type=boolean +Category=Geometry Caching Options +Label=Run a Geometry caching job +Index=0 +Description=Whether a Geometry caching job is running. +Required=false +DisableIfBlank=true + +[OneFilePerFrame] +Type=boolean +Label=One File Per Frame +Index=1 +Description=File distribution: One file per frame or only one file. +Required=false +DisableIfBlank=true + +[SavePointsAs] +Type=enum +Values=Double;Float +Label=Save Points As +Category=Geometry Caching Options +Index=2 +Description=Save points as doubles or floats. +Required=false +DisableIfBlank=true + +[SavePointsIn] +Type=enum +Values=Local;World +Label=Save Points In +Category=Geometry Caching Options +Index=3 +Description=Export in local or world space. +Required=false +DisableIfBlank=true + +[OneFilePerGeometry] +Type=boolean +Label=One File Per Geometry +Category=Geometry Caching Options +Index=4 +Description=Write one Cache per Geometry. +Required=false +DisableIfBlank=true + +[CacheFormat] +Type=enum +Values=mcc;mcx +Label=Cache Format +Index=5 +Description=Use mcc or mcx caching format. +Required=false +DisableIfBlank=true + +[GeoCacheFileName] +Type=filename +Label=Geometry Cache Filename +Category=Geometry Caching Options +Index=6 +Description=The cache filename as it exists on the network. +Required=false +DisableIfBlank=true + +[SelectedGeometry] +Type=string +Label=Selected Geometry in Scene +Category=Geometry Caching Options +Index=7 +Description=A comma separated list of the Geometry selected by the user, or "All" to signal all Geometry. +Required=false +DisableIfBlank=true + +[FluidCacheJob] +Type=boolean +Category=Fluid Caching Options +Label=Run a Fluid caching job +Index=0 +Description=Whether a Fluid caching job is running. +Required=false +DisableIfBlank=true + +[OneFilePerFluid] +Type=boolean +Label=One File Per Fluid +Category=Fluid Caching Options +Index=2 +Description=Write one Cache per Fluid. +Required=false +DisableIfBlank=true + +[FluidCacheFileName] +Type=filename +Label=Fluid Cache Filename +Category=Fluid Caching Options +Index=3 +Description=The cache filename as it exists on the network. +Required=false +DisableIfBlank=true + +[SelectedFluids] +Type=string +Label=Selected Fluids in Scene +Category=Fluid Caching Options +Index=6 +Description=A comma separated list of the Fluids selected by the user, or "All" to signal all Fluids. +Required=false +DisableIfBlank=true \ No newline at end of file diff --git a/vendor/deadline/custom/plugins/MayaPype/MayaPype.param b/vendor/deadline/custom/plugins/MayaPype/MayaPype.param new file mode 100644 index 0000000000..5e5b4d64ca --- /dev/null +++ b/vendor/deadline/custom/plugins/MayaPype/MayaPype.param @@ -0,0 +1,154 @@ +[About] +Type=label +Label=About +Category=About Plugin +CategoryOrder=-1 +Index=0 +Default=Pype Maya Batch Wrapper Plugin for Deadline +Description=Not configurable + +[ConcurrentTasks] +Type=label +Label=ConcurrentTasks +Category=About Plugin +CategoryOrder=-1 +Index=0 +Default=True +Description=Not configurable + +[PypeWrapper] +Type=multilinemultifilename +Category=Pype Wrapper Script +CategoryOrder=0 +Index=15 +Label=Pype Wrapper Script locations +Default=P:\pype\launchers\pype-wrapper\MayaPype.bat;/mnt/pipeline/pype/pype-wrapper/MayaPype.sh;/Volumes/pipeline/pype/pype-wrapper/MayaPype.zsh +Description=The path to the Pype Wrapper script. Enter alternative paths on separate lines. + +[MaxwellInteractiveSlaves] +Type=slavelist +Category=Maxwell For Maya (version 2 and later) +CategoryOrder=1 +Index=0 +Label=Workers To Use Interactive License +Default= +Description=A list of Workers that should use an interactive Maxwell license instead of a render license. Use a , to separate multiple Worker names, for example: worker001,worker002,worker003 + +[EnablePathMapping] +Type=boolean +Category=Path Mapping For Scene Files (For Mixed Farms) +CategoryOrder=2 +CategoryIndex=0 +Label=Enable Path Mapping +Default=true +Description=If enabled, path mapping will be performed on the contents of the Maya scene file. + +[PathMappingMode] +Type=enum +Values=Use dirmap Command;Text Replacement (.ma files only) +Category=Path Mapping For Scene Files (For Mixed Farms) +CategoryOrder=2 +CategoryIndex=1 +Label=Path Mapping Mode +Default=Use dirmap Command +Description=The first option uses Maya's 'dirmap' command to map paths when the scene is loaded, which works on .ma and.mb files. The second option creates a local copy of the .ma file, and uses text replacement on the file to map paths. + +[XGenPathMapping] +Type=boolean +Category=Path Mapping For XGenFiles +CategoryOrder=2 +CategoryIndex=2 +Label=Enable XGen Path Mapping +Default=true +Description=If enabled, path mapping will be performed on the contents of XGen files. + +[SuppressWarnings] +Type=boolean +Category=Logging +CategoryOrder=3 +CategoryIndex=0 +Label=Suppress Warning Messages +Default=false +Description=If enabled, warning messages printed out by Maya will not be included in the render log. + +[SilenceSceneLoadErrors] +Type=boolean +Category=Logging +CategoryOrder=3 +CategoryIndex=1 +Label=Silence Scene Load Errors +Default=false +Description=If enabled, errors that occur when loading a scene file are silenced by Maya and will not be included in the render log. + +[WriteScriptToLog] +Type=boolean +Category=Logging +CategoryOrder=3 +CategoryIndex=2 +Label=Log Script Contents to Render Log +Default=false +Description=If enabled, or if an error occurs, the full script that Deadline is passing to Maya will be written to the render log. This functionality is useful for debugging purposes, and providing additional information to support. + +[LimitThreadsToCPUAffinity] +Type=boolean +Category=CPU Affinity +CategoryOrder=4 +CategoryIndex=0 +Label=Limit Threads to CPU Affinity +Default=false + +[AbortOnArnoldLicenseFail] +Type=boolean +Category=Arnold Options +CategoryOrder=5 +CategoryIndex=0 +Label=Abort On Arnold License Fail +Default=true +Description=If enabled, the render will fail if Arnold cannot get a license. If disabled, Arnold will will render with a watermark if it cannot get a license (Only applies when Arnold is the Renderer). + +[RemoteAssetPaths] +Type=multilinestring +Category=Local Asset Caching +CategoryOrder=6 +CategoryIndex=0 +Label=Remote Asset Path +Default=//;X:;Y:;Z: +Description=Assets whose paths begin with these paths will be copied to the Worker's local asset cache directory and be remapped in the scene file. + +[SlaveLACDirectoryWindows] +Type=folder +Category=Local Asset Caching +CategoryOrder=6 +CategoryIndex=1 +Label=Worker LAC Directory (Windows) +Default= +Description=Windows Worker's local storage location for cached assets (accepts environment variables). If blank, defaults to Deadline's temp folder on the Worker. ie. %temp%/Thinkbox/DeadlineX/temp/MayaCache + +[SlaveLACDirectoryOSX] +Type=folder +Category=Local Asset Caching +CategoryOrder=6 +CategoryIndex=2 +Label=Worker LAC Directory (OSX) +Default= +Description=OSX Worker's local storage location for cached assets (accepts environment variables). If blank, defaults to Deadline's temp folder on the Worker. ie. /Users/home/[user]/.local/share/Thinkbox/DeadlineX/temp/MayaCache + +[SlaveLACDirectoryLinux] +Type=folder +Category=Local Asset Caching +CategoryOrder=6 +CategoryIndex=3 +Label=Worker LAC Directory (Linux) +Default= +Description=Linux Worker's local storage location for cached assets (accepts environment variables). If blank, defaults to Deadline's temp folder on the Worker. ie. /home/[user]/.local/share/Thinkbox/DeadlineX/temp/MayaCache + +[SlaveLACDaysToDelete] +Type=integer +Category=Local Asset Caching +CategoryOrder=6 +CategoryIndex=4 +Label=Days until Cache Delete +Minimum=0 +Maximum=99 +Default=5 +Description=Cache files will be deleted once this many days has been reached in between accesses. diff --git a/vendor/deadline/custom/plugins/MayaPype/MayaPype.py b/vendor/deadline/custom/plugins/MayaPype/MayaPype.py new file mode 100644 index 0000000000..969d66fa2a --- /dev/null +++ b/vendor/deadline/custom/plugins/MayaPype/MayaPype.py @@ -0,0 +1,5002 @@ +import io +import json +import os +import re +from collections import defaultdict, namedtuple + +from Deadline.Plugins import * +from Deadline.Scripting import * +from FranticX.Processes import * +from System import * +from System.Diagnostics import * +from System.IO import * +from System.Text import * +from six import string_types + + +###################################################################### +## This is the function that Deadline calls to get an instance of the +## main DeadlinePlugin class. +###################################################################### +def GetDeadlinePlugin(): + return MayaBatchPlugin() + + +def CleanupDeadlinePlugin(deadlinePlugin): + deadlinePlugin.Cleanup() + + +###################################################################### +## This is the main DeadlinePlugin class for the MayaBatch plugin. +###################################################################### +class MayaBatchPlugin(DeadlinePlugin): + ProcessName = "MayaBatch" + Process = None + + Version = 0 + Build = "none" + Renderer = "mayasoftware" + EnablePathMapping = False + DirmapPathMapping = False + XGenPathMapping = False + EnableOpenColorIO = False + + StartFrame = "" + EndFrame = "" + ByFrame = "" + RenumberFrameStart = "" + FirstTask = True + Animation = True + + SceneFile = "" + ProjectPath = "" + StartupScriptPath = "" + RenderDirectory = "" + CurrentRenderDirectory = "" + LocalRendering = False + ImagePrefix = "" + + TempThreadDirectory = "" + + Camera = "" + Width = "" + Height = "" + AspectRatio = "" + + AntiAliasing = "" + MotionBlur = "" + Threads = "" + Verbosity = "" + + RenderLayer = "" + UsingRenderLayers = False + + Left = "" + Right = "" + Top = "" + Bottom = "" + + ScriptJob = False + ScriptFilename = "" + + BifrostJob = False + BifrostCompressionFormat = "0" + + AlembicJob = False + AlembicVerbose = False + AlembicFormat = "Ogawa" + AlembicFile = "" + AlembicKeys = "" + AlembicArgs = "" + AlembicSelection = "All" + + GeometryCacheJob = False + GeoFilePerFrame = False + GeoCacheFileName = "" + OneFilePerGeometry = "" + PointsAsDblorFlt = "" + PointsInLocalOrWorld = "" + GeoCacheFormat = "" + SelectedGeometry = "" + + FluidCacheJob = False + FluidFilePerFrame = "" + FluidCacheFileName = "" + OneFilePerFluid = "" + FluidCacheFormat = "" + SelectedFluids = "" + + RegionRendering = False + SingleRegionJob = False + SingleRegionFrame = 0 + SingleRegionIndex = "" + + # Krakatoa Variables + KrakatoaJobFileContainsKrakatoaParameters = True + KrakatoaFinalPassDensity = "" + KrakatoaFinalPassDensityExponent = "" + KrakatoaUseLightingPassDensity = "0" + KrakatoaLightingPassDensity = "" + KrakatoaLightingPassDensityExponent = "" + KrakatoaUseEmissionStrength = "0" + KrakatoaEmissionStrength = "" + KrakatoaEmissionStrength = "" + KrakatoaUseEmission = "0" + KrakatoaUseAbsorption = "0" + KrakatoaEnableMotionBlur = "0" + KrakatoaMotionBlurParticleSegments = "" + KrakatoaJitteredMotionBlur = "0" + KrakatoaShutterAngle = "" + KrakatoaEnableDOF = "" + KrakatoaSampleRateDOF = "" + KrakatoaEnableMatteObjects = "" + KrakatoaRenderingMethod = "" + KrakatoaVoxelSize = "0.5" + KrakatoaVoxelFilterRadius = "1" + KrakatoaForceEXROutput = "1" + + # This list contains all of the known error messages that we want to fail on if strict error checking is enabled. + # Each entry is either: + # * a string - we will fail if the line contains the string(case sensitive) + # * an iterable containing strings - we will fail if the line contains all of the strings in the iterable. + fatal_strict_errors = [ + "Render failed", + "could not get a license", + "Could not obtain a license", + "This scene does not have any renderable cameras", + "Warning: The post-processing failed while attempting to rename file", + "Error: Failed to open IFF file for reading", + "Error: An exception has occurred, rendering aborted.", + "Cannot open project", + "Could not open file.", + "Error reading file. :", + "Error: Scene was not loaded properly, please check the scene name", + "Error: Graphics card capabilities are insufficient for rendering.", + "Not enough storage is available to process this command.", + "Error: (Mayatomr) : mental ray has stopped with errors, see the log", + "Warning: (Mayatomr.Scene) : no render camera found, final scene will be incomplete and can't be rendered", + "mental ray: out of memory", + "The specified module could not be found.", + "Error: (Mayatomr.Export) : mental ray startup failed with errors", + "Number of arguments on call to preLayerScript does not match number of parameters in procedure definition.", + "Error: rman Fatal:", + "rman Error:", + "Error: There was a fatal error rendering the scene.", + "Could not read V-Ray environment variable", + "can't create file (No such file or directory)", + "Fatal Error:", + "Error writing render region to raw image file.", + "Error: OctaneRender is not activated!", + "Error: R12001", + "ERROR pgLicenseCheck", + ("Error: Camera", "does not exist"), + ( + "Error: The attribute", + "was locked in a referenced file, and cannot be unlocked.", + ), + ("Error: Cannot find file ", " for source statement."), + ("error 101003:", "can't create file"), + "Failed to save file", + ("Error: ", "XGen: Failed to find"), + ] + + def __init__(self): + self.InitializeProcessCallback += self.InitializeProcess + self.StartJobCallback += self.StartJob + self.RenderTasksCallback += self.RenderTasks + self.EndJobCallback += self.EndJob + + self.scriptContents = "" + self.isScriptLogged = False + + def Cleanup(self): + del self.InitializeProcessCallback + del self.StartJobCallback + del self.RenderTasksCallback + del self.EndJobCallback + + if self.Process: + self.Process.Cleanup() + del self.Process + + ## Called by Deadline to initialize the process. + def InitializeProcess(self): + # Set the plugin specific settings. + self.SingleFramesOnly = False + self.PluginType = PluginType.Advanced + + def StartJob(self): + self.Version = StringUtils.ParseLeadingNumber( + self.GetPluginInfoEntry("Version") + ) + self.Version = ( + int(self.Version * 10) / 10.0 + ) # we only want one decimal place + if not str(self.Version).endswith(".5"): + self.Version = ( + int(self.Version) * 1.0 + ) # If it's not a *.5 Version, then we want to ignore the decimal value + + self.Build = self.GetPluginInfoEntryWithDefault( + "Build", "None" + ).lower() + + self.LogInfo("Rendering with Maya Version " + str(self.Version)) + + self.EnablePathMapping = self.GetBooleanConfigEntryWithDefault( + "EnablePathMapping", False + ) + if self.EnablePathMapping: + self.DirmapPathMapping = ( + self.GetConfigEntryWithDefault( + "PathMappingMode", "Use dirmap Command" + ) + == "Use dirmap Command" + ) + self.XGenPathMapping = self.GetBooleanConfigEntryWithDefault( + "XGenPathMapping", False + ) + self.EnableOpenColorIO = self.GetBooleanPluginInfoEntryWithDefault( + "EnableOpenColorIO", False + ) + + sceneFilename = ( + self.GetPluginInfoEntryWithDefault( + "SceneFile", self.GetDataFilename() + ) + .strip() + .replace("\\", "/") + ) + sceneFilename = RepositoryUtils.CheckPathMapping( + sceneFilename + ).replace("\\", "/") + if ( + SystemUtils.IsRunningOnWindows() + and sceneFilename.startswith("/") + and not sceneFilename.startswith("//") + ): + sceneFilename = "/" + sceneFilename + + self.TempThreadDirectory = self.CreateTempDirectory( + "thread" + str(self.GetThreadNumber()) + ) + + # We can only do path mapping on .ma files (they're ascii files) + if ( + self.EnablePathMapping + and not self.DirmapPathMapping + and os.path.splitext(sceneFilename)[1].lower() == ".ma" + ): + self.LogInfo("Performing path mapping on ma scene file") + + tempSceneFileName = Path.GetFileName(sceneFilename) + self.SceneFile = os.path.join( + self.TempThreadDirectory, tempSceneFileName + ) + + RepositoryUtils.CheckPathMappingInFileAndReplace( + sceneFilename, self.SceneFile, ("\\", '/"'), ("/", '\\"') + ) + if SystemUtils.IsRunningOnLinux() or SystemUtils.IsRunningOnMac(): + os.chmod(self.SceneFile, os.stat(sceneFilename).st_mode) + + else: + self.SceneFile = sceneFilename + + self.SceneFile = PathUtils.ToPlatformIndependentPath(self.SceneFile) + + # ~ # These options are passed to maya batch when it starts up. + # ~ self.SceneFile = self.GetPluginInfoEntryWithDefault( "SceneFile", self.GetDataFilename() ).strip().replace( "\\", "/" ) + # ~ self.SceneFile = RepositoryUtils.CheckPathMapping( self.SceneFile ).replace( "\\", "/" ) + # ~ if SystemUtils.IsRunningOnWindows() and self.SceneFile.startswith( "/" ) and not self.SceneFile.startswith( "//" ): + # ~ self.SceneFile = "/" + self.SceneFile + + self.ProjectPath = ( + self.GetPluginInfoEntryWithDefault("ProjectPath", "") + .strip() + .replace("\\", "/") + ) + self.ProjectPath = RepositoryUtils.CheckPathMapping( + self.ProjectPath + ).replace("\\", "/") + if ( + SystemUtils.IsRunningOnWindows() + and self.ProjectPath.startswith("/") + and not self.ProjectPath.startswith("//") + ): + self.ProjectPath = "/" + self.ProjectPath + + self.StartupScriptPath = ( + self.GetPluginInfoEntryWithDefault("StartupScript", "") + .strip() + .replace("\\", "/") + ) + self.StartupScriptPath = RepositoryUtils.CheckPathMapping( + self.StartupScriptPath + ).replace("\\", "/") + if ( + SystemUtils.IsRunningOnWindows() + and self.StartupScriptPath.startswith("/") + and not self.StartupScriptPath.startswith("//") + ): + self.StartupScriptPath = "/" + self.StartupScriptPath + + if self.StartupScriptPath: + if not os.path.isfile(self.StartupScriptPath): + self.FailRender( + 'Startup script "%s" does not exist.' + % self.StartupScriptPath + ) + + # Set up the maya batch process. + self.Renderer = self.GetPluginInfoEntryWithDefault( + "Renderer", "mayaSoftware" + ).lower() + self.Process = MayaBatchProcess( + self, + self.Version, + self.Build, + self.SceneFile, + self.ProjectPath, + self.StartupScriptPath, + self.Renderer, + self.EnablePathMapping and self.DirmapPathMapping, + ) + + self.SetEnvironmentAndLogInfo("MAYA_DEBUG_ENABLE_CRASH_REPORTING", "0") + self.SetEnvironmentAndLogInfo( + "MAYA_DISABLE_CIP", + "1", + description="ADSK Customer Involvement Program", + ) + self.SetEnvironmentAndLogInfo( + "MAYA_DISABLE_CER", + "1", + description="ADSK Customer Error Reporting", + ) + self.SetEnvironmentAndLogInfo( + "MAYA_DISABLE_CLIC_IPM", + "1", + description="ADSK In Product Messaging", + ) + self.SetEnvironmentAndLogInfo("MAYA_OPENCL_IGNORE_DRIVER_VERSION", "1") + self.SetEnvironmentAndLogInfo( + "MAYA_VP2_DEVICE_OVERRIDE", "VirtualDeviceDx11" + ) + + self.SetEnvironmentAndLogInfo( + "MAYA_RENDER_SETUP_INCLUDE_ALL_LIGHTS", + str( + int( + self.GetBooleanPluginInfoEntryWithDefault( + "RenderSetupIncludeLights", True + ) + ) + ), + ) + + if self.Renderer == "redshift": + # Flag which tells Redshift to kill Maya if Redshift runs into an internal error instead of hanging + self.SetEnvironmentAndLogInfo( + "REDSHIFT_FORCEQUITONINTERNALERROR", "1" + ) + + self.SetRedshiftPathmappingEnv() + + if self.Version >= 2016.5: + useLegacyRenderLayers = int( + self.GetBooleanPluginInfoEntryWithDefault( + "UseLegacyRenderLayers", False + ) + ) + self.SetEnvironmentAndLogInfo( + "MAYA_ENABLE_LEGACY_RENDER_LAYERS", str(useLegacyRenderLayers) + ) + + # If on the Mac, set some environment variables (these are normally set by MayaENV.sh when running the Maya Terminal). + if SystemUtils.IsRunningOnMac(): + mayaExecutable = self.Process.RenderExecutable() + mayaBinFolder = Path.GetDirectoryName(mayaExecutable) + usrAwComBin = "/usr/aw/COM/bin" + usrAwComEtc = "/usr/aw/COM/etc" + + self.LogInfo( + "Adding " + + mayaBinFolder + + " to PATH environment variable for this session" + ) + self.LogInfo( + "Adding " + + usrAwComBin + + " to PATH environment variable for this session" + ) + self.LogInfo( + "Adding " + + usrAwComEtc + + " to PATH environment variable for this session" + ) + + path = Environment.GetEnvironmentVariable("PATH") + if path: + path = ( + mayaBinFolder + + ":" + + usrAwComBin + + ":" + + usrAwComEtc + + ":" + + path + ) + self.SetProcessEnvironmentVariable("PATH", path) + else: + self.SetProcessEnvironmentVariable( + "PATH", + mayaBinFolder + ":" + usrAwComBin + ":" + usrAwComEtc, + ) + + mayaLocation = Path.GetDirectoryName(mayaBinFolder) + self.SetEnvironmentAndLogInfo("MAYA_LOCATION", mayaLocation) + + mayaMacOSFolder = Path.Combine(mayaLocation, "MacOS") + self.LogInfo( + "Adding " + + mayaMacOSFolder + + " to DYLD_LIBRARY_PATH environment variable for this session" + ) + libraryPath = Environment.GetEnvironmentVariable( + "DYLD_LIBRARY_PATH" + ) + if libraryPath: + libraryPath = mayaMacOSFolder + ":" + libraryPath + self.SetProcessEnvironmentVariable( + "DYLD_LIBRARY_PATH", libraryPath + ) + else: + self.SetProcessEnvironmentVariable( + "DYLD_LIBRARY_PATH", mayaMacOSFolder + ) + + mayaFrameworksFolder = Path.Combine(mayaLocation, "Frameworks") + self.LogInfo( + "Adding " + + mayaFrameworksFolder + + " to DYLD_FRAMEWORK_PATH environment variable for this session" + ) + frameworkPath = Environment.GetEnvironmentVariable( + "DYLD_FRAMEWORK_PATH" + ) + if frameworkPath: + frameworkPath = mayaFrameworksFolder + ":" + frameworkPath + self.SetProcessEnvironmentVariable( + "DYLD_FRAMEWORK_PATH", frameworkPath + ) + else: + self.SetProcessEnvironmentVariable( + "DYLD_FRAMEWORK_PATH", mayaFrameworksFolder + ) + + mayaPythonVersionsFolder = Path.Combine( + mayaFrameworksFolder, "Python.framework/Versions" + ) + + pythonVersion = "2.7" + mayaPythonVersionFolder = Path.Combine( + mayaPythonVersionsFolder, pythonVersion + ) + if not Directory.Exists(mayaPythonVersionFolder): + pythonVersion = "2.6" + mayaPythonVersionFolder = Path.Combine( + mayaPythonVersionsFolder, pythonVersion + ) + + if Directory.Exists(mayaPythonVersionFolder): + self.SetEnvironmentAndLogInfo( + "PYTHONHOME", mayaPythonVersionFolder + ) + + elif SystemUtils.IsRunningOnWindows(): + mayaExecutable = self.Process.RenderExecutable() + mayaBinFolder = Path.GetDirectoryName(mayaExecutable) + mayaLocation = Path.GetDirectoryName(mayaBinFolder) + mayaPythonFolder = Path.Combine(mayaLocation, "Python") + if Directory.Exists(mayaPythonFolder): + self.SetEnvironmentAndLogInfo("PYTHONHOME", mayaPythonFolder) + + # Start the maya process. + self.StartMonitoredManagedProcess(self.ProcessName, self.Process) + + def SetEnvironmentAndLogInfo(self, envVar, value, description=None): + """ + Sets an environment variable and prints a message to the log + :param envVar: The environment variable that is going to be set + :param value: The value that is the environment variable is being set to + :param description: An optional description of the environment variable that will be added to the log. + :return: Nothing + """ + self.LogInfo( + "Setting {0}{1} environment variable to {2} for this session".format( + envVar, + " ({0})".format(description) if description else "", + value, + ) + ) + self.SetProcessEnvironmentVariable(envVar, value) + + def WriteBatchScriptFile(self, scriptBuilder): + """ + Writes out a MEL/python script that will be executed inside of Mayas. + :param scriptBuilder: A string builder object containing the script to create. + :return: The file path of the newly created script file. + """ + self.scriptContents = scriptBuilder.ToString() + + if self.GetBooleanConfigEntryWithDefault("WriteScriptToLog", False): + self.WriteScriptToLog() + + # Create the temp script file. + outScriptFilename = Path.GetTempFileName() + if SystemUtils.IsRunningOnWindows(): + with open(outScriptFilename, "wb") as f: + f.write( + self.scriptContents.encode("mbcs") + ) # On Windows, write it out as binary single-bytes per string using the current character set. This ensures that the resulting mel file doesn't have unicode characters, because maya will not be able to decode those characters correctly. + else: + File.WriteAllText( + outScriptFilename, self.scriptContents + ) # Non-Windows, we can write it out like this and the unicode characters will be written, but Maya will be able to handle the unicode encoded file. + + if SystemUtils.IsRunningOnLinux() or SystemUtils.IsRunningOnMac(): + os.chmod( + outScriptFilename, os.stat(Path.GetTempFileName()).st_mode + ) + + return outScriptFilename + + def WriteScriptToLog(self, isError=False): + """ + Prints the batch script, that performs the rendering inside Maya, to the render log. This will either be triggered by a plugin + configuration option or by encountering a fatal error. + :param isError: Determines how we log the script contents. Either as info or as a warning. + :return: None + """ + if self.isScriptLogged or not self.scriptContents: + return + + # determine how we should log the script based on if it's an error or not + logFunction = self.LogWarning if isError else self.LogInfo + + if isError: + logFunction( + "Encountered an error, logging render script contents for debugging:" + ) + else: + logFunction("Logging render script contents:") + + logFunction( + "################################ SCRIPT LOG START ################################" + ) + logFunction(self.scriptContents.replace("\r", "")) + logFunction( + "################################# SCRIPT LOG END #################################" + ) + + self.isScriptLogged = True + + def RenderTasks(self): + self.LogInfo("Waiting until maya is ready to go") + + # isScriptLogged needs to be reset at the beginning of each task, in case WriteScriptToLog is turned on + self.isScriptLogged = False + + # Wait until maya batch is ready to go. + self.WaitForProcess() + self.Process.ResetFrameCount() + + self.ScriptJob = self.GetBooleanPluginInfoEntryWithDefault( + "ScriptJob", False + ) + self.BifrostJob = self.GetBooleanPluginInfoEntryWithDefault( + "BifrostJob", False + ) + self.AlembicJob = self.GetBooleanPluginInfoEntryWithDefault( + "AlembicJob", False + ) + self.GeometryCacheJob = self.GetBooleanPluginInfoEntryWithDefault( + "GeometryCacheJob", False + ) + self.FluidCacheJob = self.GetBooleanPluginInfoEntryWithDefault( + "FluidCacheJob", False + ) + self.Animation = self.GetBooleanPluginInfoEntryWithDefault( + "Animation", True + ) + self.StartFrame = str(self.GetStartFrame()) + self.EndFrame = str(self.GetEndFrame()) + + self.RenderDirectory = ( + self.GetPluginInfoEntryWithDefault("OutputFilePath", "") + .strip() + .replace("\\", "/") + ) + self.RenderDirectory = RepositoryUtils.CheckPathMapping( + self.RenderDirectory + ).replace("\\", "/") + if len(self.RenderDirectory) > 0 and self.RenderDirectory.endswith( + "/" + ): + self.RenderDirectory = self.RenderDirectory.rstrip("/\\") + if ( + SystemUtils.IsRunningOnWindows() + and len(self.RenderDirectory) > 0 + and self.RenderDirectory.startswith("/") + and not self.RenderDirectory.startswith("//") + ): + self.RenderDirectory = "/" + self.RenderDirectory + + self.LocalRendering = self.GetBooleanPluginInfoEntryWithDefault( + "LocalRendering", False + ) + + if self.FirstTask: + mayaBatchUtilsScriptFilename = os.path.join( + self.GetPluginDirectory(), "MayaBatchUtils.mel" + ) + self.LogInfo( + "Importing Maya Batch Utils melscript: " + + mayaBatchUtilsScriptFilename + ) + self.FlushMonitoredManagedProcessStdout(self.ProcessName) + self.WriteStdinToMonitoredManagedProcess( + self.ProcessName, + 'eval( "source \\"' + + mayaBatchUtilsScriptFilename.replace("\\", "/") + + '\\";" )', + ) + + # Wait until eval is complete + self.LogInfo( + "Waiting for Maya Batch Utils melscript to finish loading" + ) + self.WaitForProcess() + + self.LogInfo( + "Importing Maya Batch Functions python script: %s" + % os.path.join( + self.GetPluginDirectory(), "DeadlineMayaBatchFunctions.py" + ) + ) + importFunctions = 'python("import sys; sys.path.append(\'%s\')");catch( python("import DeadlineMayaBatchFunctions") );' % self.GetPluginDirectory().replace( + "\\", "/" + ) + self.WriteStdinToMonitoredManagedProcess( + self.ProcessName, importFunctions + ) + + # Wait until import is complete. + self.LogInfo( + "Waiting for Maya Batch Functions python script to finish importing" + ) + self.WaitForProcess() + + if self.ScriptJob: + self.LogInfo(">This is a MEL/Python Script Job") + job = self.GetJob() + + self.LogInfo("+Reading Plugin Info") + pluginInfo = {} + pluginInfoTempList = job.GetJobPluginInfoKeys() + for key in pluginInfoTempList: + pluginInfo[key] = job.GetJobPluginInfoKeyValue(key) + + self.LogInfo("+Reading Extra Info") + extraInfo = {} + extraInfo["ExtraInfo0"] = job.JobExtraInfo0 + extraInfo["ExtraInfo1"] = job.JobExtraInfo1 + extraInfo["ExtraInfo2"] = job.JobExtraInfo2 + extraInfo["ExtraInfo3"] = job.JobExtraInfo3 + extraInfo["ExtraInfo4"] = job.JobExtraInfo4 + extraInfo["ExtraInfo5"] = job.JobExtraInfo5 + extraInfo["ExtraInfo6"] = job.JobExtraInfo6 + extraInfo["ExtraInfo7"] = job.JobExtraInfo7 + extraInfo["ExtraInfo8"] = job.JobExtraInfo8 + extraInfo["ExtraInfo9"] = job.JobExtraInfo9 + for key in job.ExtraInfoDictionary.Keys: + extraInfo[key] = job.ExtraInfoDictionary[key] + + self.LogInfo( + "+Building up a melscript of defined global variables" + ) + # Build up a melscript to define global variables and run it + scriptBuilder = StringBuilder() + + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + 'string $sceneName = "' + + self.SceneFile.replace("\\", "/") + + '";' + ) + scriptBuilder.AppendLine( + 'print ("Loading scene: " + $sceneName + "\\n");' + ) + scriptBuilder.AppendLine() + + # If using dirmap for path mapping, need to load the scene now (it's not loaded via the command line in this case). + # Only do this for the first task the Worker has picked up for this job. + if ( + self.FirstTask + and self.EnablePathMapping + and self.DirmapPathMapping + ): # this check seems to be equivalent to self.DelayLoadScene + self.CreateDelayLoadSceneMelscript(scriptBuilder) + + # double check at this point to make sure the scene is loaded. it could have been loaded by the command line, or by MEL script if delayed loading is enabled. + scriptBuilder.AppendLine("string $checkScene = `file -q -sn`;") + scriptBuilder.AppendLine('if ($checkScene=="")') + scriptBuilder.AppendLine( + ' error ("Cannot load scene \\"" + $sceneName + "\\". Please check the scene path, then try opening the scene on the machine which ran this job to troubleshoot the problem.\\n");' + ) + scriptBuilder.AppendLine() + + # write deadline convenience functions + scriptBuilder.AppendLine( + "global proc string DeadlinePluginInfo (string $value){" + ) + scriptBuilder.AppendLine(" switch($value) {") + for key, value in pluginInfo.iteritems(): + scriptBuilder.AppendLine( + ' case "%s" : return "%s"; break;' + % ( + key.strip().replace("\\", "\\\\"), + value.strip().replace("\\", "\\\\"), + ) + ) + scriptBuilder.AppendLine(" }") + scriptBuilder.AppendLine(' return "";') + scriptBuilder.AppendLine("}") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + "global proc string DeadlineExtraInfo (string $value){" + ) + scriptBuilder.AppendLine(" switch($value) {") + for key, value in extraInfo.iteritems(): + scriptBuilder.AppendLine( + ' case "%s" : return "%s"; break;' + % (key.replace("\\", "\\\\"), value.replace("\\", "\\\\")) + ) + scriptBuilder.AppendLine(" }") + scriptBuilder.AppendLine(' return "";') + scriptBuilder.AppendLine("}") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine() + + self.LogInfo("+Building DeadlineValue") + + scriptBuilder.AppendLine( + "global proc string DeadlineValue (string $value){" + ) + scriptBuilder.AppendLine(" switch($value) {") + # Format: + # scriptBuilder.AppendLine(" case \\" : return \"" + + "\"; break;") + scriptBuilder.AppendLine( + ' case "TestValue" : return "This is a test"; break;' + ) + scriptBuilder.AppendLine( + ' case "DataFileName" : return "' + + self.SceneFile.replace("\\", "\\\\") + + '"; break;' + ) + scriptBuilder.AppendLine( + ' case "PluginDirectory" : return "' + + self.GetPluginDirectory().replace("\\", "\\\\") + + '"; break;' + ) + scriptBuilder.AppendLine( + ' case "JobsDataDirectory" : return "' + + self.GetJobsDataDirectory().replace("\\", "\\\\") + + '"; break;' + ) + scriptBuilder.AppendLine( + ' case "StartFrame" : return "' + + str(self.GetStartFrame()) + + '"; break;' + ) + scriptBuilder.AppendLine( + ' case "EndFrame" : return "' + + str(self.GetEndFrame()) + + '"; break;' + ) + scriptBuilder.AppendLine( + ' case "ThreadNumber" : return "' + + str(self.GetThreadNumber()) + + '"; break;' + ) + scriptBuilder.AppendLine(" }") + scriptBuilder.AppendLine(' return "";') + scriptBuilder.AppendLine("}") + scriptBuilder.AppendLine() + + # Create the temp script file. + globalScriptFilename = Path.GetTempFileName() + File.WriteAllText(globalScriptFilename, scriptBuilder.ToString()) + + if SystemUtils.IsRunningOnLinux() or SystemUtils.IsRunningOnMac(): + os.chmod( + globalScriptFilename, + os.stat(Path.GetTempFileName()).st_mode, + ) + + self.LogInfo( + "Executing defined global variables melscript: " + + globalScriptFilename + ) + + self.FlushMonitoredManagedProcessStdout(self.ProcessName) + self.WriteStdinToMonitoredManagedProcess( + self.ProcessName, + 'eval( "source \\"' + + globalScriptFilename.replace("\\", "/") + + '\\";" )', + ) + + # Wait until render is complete. + self.LogInfo("Waiting for global variables melscript to finish") + self.WaitForProcess() + + # This is script job, so we'll just execute the given script. + self.ScriptFilename = self.GetPluginInfoEntry("ScriptFilename") + if not Path.IsPathRooted(self.ScriptFilename): + self.ScriptFilename = Path.Combine( + self.GetJobsDataDirectory(), self.ScriptFilename + ) + + if not File.Exists(self.ScriptFilename): + self.FailRender( + "MEL/Python Script File is missing: %s" + % self.ScriptFilename + ) + + elif self.BifrostJob: + self.LogInfo(">This is a Bifrost Job") + + if self.LocalRendering: + self.CurrentRenderDirectory = self.CreateTempDirectory( + "mayaSimOutput" + ).replace("\\", "/") + self.LogInfo( + "Simulating to local drive, will copy files and folders to final location after simulation is complete" + ) + else: + self.CurrentRenderDirectory = self.RenderDirectory + self.LogInfo("Simulating to network drive") + + BifrostStatsScriptFilename = Path.Combine( + self.GetPluginDirectory(), "BifrostMemUsage.mel" + ) + self.LogInfo( + "Executing Bifrost stats melscript: " + + BifrostStatsScriptFilename + ) + self.FlushMonitoredManagedProcessStdout(self.ProcessName) + self.WriteStdinToMonitoredManagedProcess( + self.ProcessName, + 'eval( "source \\"' + + BifrostStatsScriptFilename.replace("\\", "/") + + '\\";" )', + ) + + self.LogInfo("Creating melscript to execute") + + # Create the script to execute. + # Version dependant work: We add the Format type for Bifrost 2016 and beyond. + scriptBuilder = StringBuilder() + scriptBuilder.AppendLine("// Starting Mel program") + scriptBuilder.AppendLine() + + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + 'string $sceneName = "' + + self.SceneFile.replace("\\", "/") + + '";' + ) + scriptBuilder.AppendLine( + 'print ("Loading scene: " + $sceneName + "\\n");' + ) + scriptBuilder.AppendLine() + # If using dirmap for path mapping, need to load the scene now (it's not loaded via the command line in this case). + # Only do this for the first task the Worker has picked up for this job. + if ( + self.FirstTask + and self.EnablePathMapping + and self.DirmapPathMapping + ): # this check seems to be equivalent to self.DelayLoadScene + self.CreateDelayLoadSceneMelscript(scriptBuilder) + + if self.Version == 2015: + scriptBuilder.AppendLine( + "proc simulateIt( int $start, int $end, string $outdir ) {" + ) + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + " // Make sure Bifrost plugin is loaded." + ) + scriptBuilder.AppendLine( + ' if( !`pluginInfo -q -l "BifrostMain"` ) {' + ) + scriptBuilder.AppendLine( + ' loadPlugin( "BifrostMain" );' + ) + scriptBuilder.AppendLine( + ' loadPlugin( "Bifrostshellnode" );' + ) + scriptBuilder.AppendLine( + ' loadPlugin( "Bifrostvisplugin" );' + ) + scriptBuilder.AppendLine(" } // end if") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + " // Create simple expression to run BifrostMemUsage() on each timestep." + ) + scriptBuilder.AppendLine( + ' expression -s "BifrostMemUsage()";' + ) + scriptBuilder.AppendLine() + scriptBuilder.AppendLine(" // Select all containers.") + scriptBuilder.AppendLine( + " string $containers[] = `ls -type bifrostContainer`;" + ) + scriptBuilder.AppendLine(" if( !size( $containers ) )") + scriptBuilder.AppendLine(" error( `pwd` );") + scriptBuilder.AppendLine(" // end if") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine(" // Report.") + scriptBuilder.AppendLine( + " for( $container in $containers )" + ) + scriptBuilder.AppendLine( + ' print( "// Found container: "+$container+".\\n" );' + ) + scriptBuilder.AppendLine(" // end for") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine(" // Select container(s).") + scriptBuilder.AppendLine(" select -r $containers;") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine(" // Run simulation.") + scriptBuilder.AppendLine(" doCreateBifrostCache 1 { ") + scriptBulider.AppendLine( + ' "0", // mode. 0 means use strict start and end frames provided.' + ) + scriptBuilder.AppendLine(" $start, // start frame") + scriptBuilder.AppendLine(" $end, // end frame") + scriptBuilder.AppendLine( + " $outdir, // name of cache directory" + ) + scriptBuilder.AppendLine( + ' "", // Base name of cache files. If blank, use the container name as cache file.' + ) + scriptBuilder.AppendLine(" };") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine("} // end BifrostBatchSim") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + "simulateIt( " + + self.StartFrame + + ", " + + self.EndFrame + + ', "' + + self.CurrentRenderDirectory + + '" );' + ) + + elif self.Version > 2015: + self.BifrostCompressionFormat = self.GetPluginInfoEntry( + "BifrostCompressionFormat" + ) + + scriptBuilder.AppendLine( + "proc simulateIt( int $start, int $end, string $outdir, int $format ) {" + ) + scriptBuilder.AppendLine() + if self.Version == 2016: + scriptBuilder.AppendLine( + " // Make sure Bifrost plugin is loaded." + ) + scriptBuilder.AppendLine( + ' if( !`pluginInfo -q -l "BifrostMain"` ) {' + ) + scriptBuilder.AppendLine( + ' loadPlugin( "BifrostMain" );' + ) + scriptBuilder.AppendLine(" } // end if") + + scriptBuilder.AppendLine( + ' if( !`pluginInfo -q -l "bifrostshellnode"` ) {' + ) + scriptBuilder.AppendLine( + ' loadPlugin( "bifrostshellnode" );' + ) + scriptBuilder.AppendLine(" } // end if") + scriptBuilder.AppendLine( + ' if( !`pluginInfo -q -l "bifrostvisplugin"` ) {' + ) + scriptBuilder.AppendLine( + ' loadPlugin( "bifrostvisplugin" );' + ) + scriptBuilder.AppendLine(" } // end if") + + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + " // Create simple expression to run BifrostMemUsage() on each timestep." + ) + scriptBuilder.AppendLine( + ' expression -s "BifrostMemUsage()";' + ) + scriptBuilder.AppendLine() + scriptBuilder.AppendLine(" // Select all containers.") + scriptBuilder.AppendLine( + " string $containers[] = `ls -type bifrostContainer`;" + ) + scriptBuilder.AppendLine(" if( !size( $containers ) )") + scriptBuilder.AppendLine(" error( `pwd` );") + scriptBuilder.AppendLine(" // end if") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine(" // Report.") + scriptBuilder.AppendLine( + " for( $container in $containers )" + ) + scriptBuilder.AppendLine( + ' print( "// Found container: "+$container+".\\n" );' + ) + scriptBuilder.AppendLine(" // end for") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine(" // Select container(s).") + scriptBuilder.AppendLine(" select -r $containers;") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine(" // Run simulation.") + scriptBuilder.AppendLine(" doCreateBifrostCache 2 { ") + scriptBuilder.AppendLine( + ' "0", // mode. 0 means use strict start and end frames provided.' + ) + scriptBuilder.AppendLine(" $start, // start frame") + scriptBuilder.AppendLine(" $end, // end frame") + scriptBuilder.AppendLine( + " $outdir, // name of cache directory" + ) + scriptBuilder.AppendLine( + ' "", // Base name of cache files. If blank, use the container name as cache file.' + ) + scriptBuilder.AppendLine( + ' "bif", // name of cache format' + ) + scriptBuilder.AppendLine( + " $format // index of cache compression format" + ) + scriptBuilder.AppendLine(" };") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine("} // end BifrostBatchSim") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + "simulateIt( " + + self.StartFrame + + ", " + + self.EndFrame + + ', "' + + self.CurrentRenderDirectory + + '", ' + + self.BifrostCompressionFormat + + " );" + ) + + # Version independant + self.ScriptFilename = self.WriteBatchScriptFile(scriptBuilder) + + elif self.AlembicJob: + self.LogInfo(">This is an Alembic Job") + self.AlembicFormat = self.GetPluginInfoEntry("AlembicFormatOption") + + self.AlembicFile = ( + self.RenderDirectory + + "/" + + self.GetPluginInfoEntry("OutputFile") + ) + + if not self.AlembicFile.endswith(".abc"): + self.AlembicFile += ".abc" + + # init keys + if self.GetBooleanPluginInfoEntryWithDefault("Verbose", False): + self.AlembicArgs += " -v " + + if self.GetBooleanPluginInfoEntryWithDefault("NoNormals", False): + self.AlembicKeys += " -nn " + if self.GetBooleanPluginInfoEntryWithDefault( + "RenderableOnly", False + ): + self.AlembicKeys += " -ro " + if self.GetBooleanPluginInfoEntryWithDefault( + "StripNamespaces", False + ): + self.AlembicKeys += " -sn " + if self.GetBooleanPluginInfoEntryWithDefault("UVWrite", False): + self.AlembicKeys += " -uv " + if self.GetBooleanPluginInfoEntryWithDefault( + "WriteColorSets", False + ): + self.AlembicKeys += " -wcs " + if self.GetBooleanPluginInfoEntryWithDefault( + "WriteFaceSets", False + ): + self.AlembicKeys += " -wfs " + if self.GetBooleanPluginInfoEntryWithDefault( + "WholeFrameGeo", False + ): + self.AlembicKeys += " -wfg " + if self.GetBooleanPluginInfoEntryWithDefault("WorldSpace", False): + self.AlembicKeys += " -ws " + if self.GetBooleanPluginInfoEntryWithDefault( + "FilterEulerRotations", False + ): + self.AlembicKeys += " -ef " + if self.GetBooleanPluginInfoEntryWithDefault( + "WriteCreases", False + ): + self.AlembicKeys += " -wc " + if self.GetBooleanPluginInfoEntryWithDefault( + "WriteVisibility", False + ): + self.AlembicKeys += " -wv " + + if self.GetBooleanPluginInfoEntryWithDefault( + "AlembicSubFrames", False + ): + self.AlembicKeys += " -frs %s " % str( + self.GetFloatPluginInfoEntryWithDefault( + "AlembicLowSubFrame", -0.2 + ) + ) + self.AlembicKeys += " -frs %s " % str( + self.GetFloatPluginInfoEntryWithDefault( + "AlembicHighSubFrame", 0.2 + ) + ) + + alembicAttributes = self.GetPluginInfoEntryWithDefault( + "AlembicAttributes", "" + ) + alembicAttributePrefix = self.GetPluginInfoEntryWithDefault( + "AlembicAttributePrefix", "" + ) + + if not alembicAttributes == "": + for attr in alembicAttributes.split(","): + self.AlembicKeys += " -attr %s " % attr.strip() + + if not alembicAttributePrefix == "": + for attr in alembicAttributePrefix.split(","): + self.AlembicKeys += " -attrPrefix %s " % attr.strip() + + # Roots for selected items go between -df and -f flags. + self.AlembicSelection = self.GetPluginInfoEntryWithDefault( + "AlembicSelection", "All" + ) + # -root |pPyramid1 -root |camera1 + alembicSelectArgs = " " + + if self.AlembicSelection != "All": + alembicSelectionList = self.AlembicSelection.split( + "," + ) # Map all the comma-separated selected scene members to a list + + for item in alembicSelectionList: + if item == "": + item = "|" + + alembicSelectArgs += " -root " + item + " " + + self.AlembicArgs += ( + ' -j "' + + self.AlembicKeys + + " -fr " + + self.StartFrame + + " " + + self.EndFrame + + " -df " + + self.AlembicFormat + + alembicSelectArgs + + ' -f \\"' + + self.AlembicFile + + '\\""' + ) + # Alembic workload + # Get all of the needed Alembic attributes, then build the -j args string. Pass that string to scriptBuilder + # Available right now: start frame, end frame, directory, filename + scriptBuilder = StringBuilder() + + scriptBuilder.AppendLine("// Starting Mel program") + scriptBuilder.AppendLine() + + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + 'string $sceneName = "' + + self.SceneFile.replace("\\", "/") + + '";' + ) + scriptBuilder.AppendLine( + 'print ("Loading scene: " + $sceneName + "\\n");' + ) + scriptBuilder.AppendLine() + + # If using dirmap for path mapping, need to load the scene now (it's not loaded via the command line in this case). + # Only do this for the first task the Worker has picked up for this job. + if ( + self.FirstTask + and self.EnablePathMapping + and self.DirmapPathMapping + ): # this check seems to be equivalent to self.DelayLoadScene + self.CreateDelayLoadSceneMelscript(scriptBuilder) + + scriptBuilder.AppendLine(' loadPlugin("AbcExport"); ') + scriptBuilder.AppendLine(" AbcExport " + self.AlembicArgs + ";") + + self.ScriptFilename = self.WriteBatchScriptFile(scriptBuilder) + + elif self.GeometryCacheJob: + self.LogInfo(">This is a Geometry Caching Job") + # First load geometry cache params + self.GeoFilePerFrame = self.GetBooleanPluginInfoEntryWithDefault( + "OneFilePerFrame", False + ) + self.GeoCacheFileName = self.GetPluginInfoEntry("GeoCacheFileName") + self.OneFilePerGeometry = self.GetPluginInfoEntry( + "OneFilePerGeometry" + ) + self.PointsAsDblorFlt = self.GetPluginInfoEntryWithDefault( + "SavePointsAs", "Float" + ) # Corresponds to 0 or 1 (dbl/flt) + self.PointsInLocalOrWorld = self.GetPluginInfoEntryWithDefault( + "SavePointsIn", "World" + ) # Corresponds to 0 or 1 (loc/wld) + self.GeoCacheFormat = self.GetPluginInfoEntry("CacheFormat") + if self.GeoCacheFormat != "mcc": + self.GeoCacheFormat = "mcx" + + self.SelectedGeometry = self.GetPluginInfoEntry( + "SelectedGeometry" + ).replace(",", " ") + + if self.SelectedGeometry == "All": + cacheSelectArgs = " `listTransforms -geometry`" + elif len(self.SelectedGeometry) > 0: + cacheSelectArgs = self.SelectedGeometry + else: + cacheSelectArgs = '; error("No Geometry Selected") ' + + if self.GeoFilePerFrame: + self.GeoFilePerFrame = "OneFilePerFrame" + else: + self.GeoFilePerFrame = "OneFile" + # To check if all selected: SelectedGeometry = "All" + # Pass in selected Geometry. On MEL side: put GUI together + scriptBuilder = StringBuilder() + scriptBuilder.AppendLine("// Starting Mel program") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + 'string $sceneName = "' + + self.SceneFile.replace("\\", "/") + + '";' + ) + scriptBuilder.AppendLine( + 'print ("Loading scene: " + $sceneName + "\\n");' + ) + scriptBuilder.AppendLine() + + # If using dirmap for path mapping, need to load the scene now (it's not loaded via the command line in this case). + # Only do this for the first task the Worker has picked up for this job. + if ( + self.FirstTask + and self.EnablePathMapping + and self.DirmapPathMapping + ): # this check seems to be equivalent to self.DelayLoadScene + self.CreateDelayLoadSceneMelscript(scriptBuilder) + + scriptBuilder.AppendLine( + ' string $geoArgs[] = { "0", // Time Range Mode ' + ) + scriptBuilder.AppendLine( + ' "' + self.StartFrame + '", // Start Frame ' + ) + scriptBuilder.AppendLine( + ' "' + self.EndFrame + '", // End Frame ' + ) + scriptBuilder.AppendLine( + ' "' + self.GeoFilePerFrame + '", // File Distribution ' + ) + scriptBuilder.AppendLine(' "0", // Refresh during cache ') + scriptBuilder.AppendLine( + ' "' + self.RenderDirectory + '", // Output Directory ' + ) + scriptBuilder.AppendLine( + ' "' + + self.OneFilePerGeometry + + '", // One Cache Per Geometry ' + ) + scriptBuilder.AppendLine( + ' "' + self.GeoCacheFileName + '", // Output File Name ' + ) + scriptBuilder.AppendLine(' "1", // Use name as prefix ') + scriptBuilder.AppendLine( + ' "add", // Action to Perform ' + ) # Is this one going to be customizable? + scriptBuilder.AppendLine(' "1", // Force Save ') + scriptBuilder.AppendLine(' "1", // Simulation Rate ') + scriptBuilder.AppendLine(' "1", // Sample Multiplier ') + scriptBuilder.AppendLine( + ' "1", // Inheret Replace Modifications ' + ) + scriptBuilder.AppendLine( + ' " ' + + str(int(self.PointsAsDblorFlt == "Float")) + + '", // Store points as Double (0) or Float (1) ' + ) + scriptBuilder.AppendLine( + ' "' + + self.GeoCacheFormat + + '", // File Format (mcc or mcx) ' + ) + scriptBuilder.AppendLine( + ' "' + + str(int(self.PointsInLocalOrWorld == "World")) + + '" }; // Points in Local (0) or World (1) ' + ) + scriptBuilder.AppendLine() + scriptBuilder.AppendLine(" //--------------------- ") + scriptBuilder.AppendLine(" // Selection Procedure: ") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + " select -r " + + cacheSelectArgs + + " ; // Grab the geometry selected earlier by the user, or throw an error if none are selected " + ) + scriptBuilder.AppendLine(" string $selection[] = `ls -sl`; ") + scriptBuilder.AppendLine(" for( $item in $selection ) ") + scriptBuilder.AppendLine( + ' print("Found Geometry: " + $item ); ' + ) + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + " doCreateGeometryCache(6, $geoArgs); // The actual workload " + ) + + self.ScriptFilename = self.WriteBatchScriptFile(scriptBuilder) + + elif self.FluidCacheJob: + self.LogInfo(">This is a Fluid Caching Job") + + self.FluidFilePerFrame = self.GetBooleanPluginInfoEntryWithDefault( + "OneFilePerFrame", False + ) + self.FluidCacheFileName = self.GetPluginInfoEntry( + "FluidCacheFileName" + ) + self.OneFilePerFluid = self.GetPluginInfoEntry("OneFilePerFluid") + self.FluidCacheFormat = self.GetPluginInfoEntry("CacheFormat") + if self.FluidCacheFormat != "mcc": + self.FluidCacheFormat = "mcx" + + self.SelectedFluids = self.GetPluginInfoEntry( + "SelectedFluids" + ).replace(",", " ") + + if self.SelectedFluids == "All": + cacheSelectArgs = ' `listTransforms "-type fluidShape"` ' + elif len(self.SelectedFluids) > 0: + cacheSelectArgs = self.SelectedFluids + else: + cacheSelectArgs = '; error("No Fluids Were Selected") ' + + if self.FluidFilePerFrame: + self.FluidFilePerFrame = "OneFilePerFrame" + else: + self.FluidFilePerFrame = "OneFile" + # To check if all selected: SelectedGeometry = "All" + # Pass in selected Geometry. On MEL side: put GUI together + scriptBuilder = StringBuilder() + scriptBuilder.AppendLine("// Starting Mel program") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + 'string $sceneName = "' + + self.SceneFile.replace("\\", "/") + + '";' + ) + scriptBuilder.AppendLine( + 'print ("Loading scene: " + $sceneName + "\\n");' + ) + + if ( + self.FirstTask + and self.EnablePathMapping + and self.DirmapPathMapping + ): # this check seems to be equivalent to self.DelayLoadScene + self.CreateDelayLoadSceneMelscript(scriptBuilder) + + scriptBuilder.AppendLine( + ' string $fluidArgs[] = { "0", // Time Range Mode ' + ) + scriptBuilder.AppendLine( + ' "' + self.StartFrame + '", // Start Frame ' + ) + scriptBuilder.AppendLine( + ' "' + self.EndFrame + '", // End Frame ' + ) + scriptBuilder.AppendLine( + ' "' + + self.FluidFilePerFrame + + '", // Distribution: 1 file or 1 file per frame' + ) + scriptBuilder.AppendLine(' "1", // Refresh during caching ') + scriptBuilder.AppendLine( + ' "' + self.RenderDirectory + '", // Output Path ' + ) + scriptBuilder.AppendLine( + ' "' + + self.OneFilePerFluid + + '", // One Cache Per Fluid ' + ) + scriptBuilder.AppendLine( + ' "' + + self.FluidCacheFileName + + '", // Output File Name ' + ) + scriptBuilder.AppendLine(' "1", // Use name as prefix ') + scriptBuilder.AppendLine(' "add", // Action to Perform ') + scriptBuilder.AppendLine(' "1", // Force Save ') + scriptBuilder.AppendLine(' "1", // Simulation Rate ') + scriptBuilder.AppendLine(' "1", // Sample Multiplier ') + scriptBuilder.AppendLine( + ' "1", // Inheret Replace Modifications ' + ) + scriptBuilder.AppendLine(' "0", // Double as Float ') + scriptBuilder.AppendLine( + ' "' + self.FluidCacheFormat + '", // mcc or mcx ' + ) + scriptBuilder.AppendLine(' "1", // Density ') + scriptBuilder.AppendLine(' "1", // Velocity ') + scriptBuilder.AppendLine(' "1", // Temperature ') + scriptBuilder.AppendLine(' "1", // Fuel ') + scriptBuilder.AppendLine(' "1", // Color ') + scriptBuilder.AppendLine(' "1", // Texture ') + scriptBuilder.AppendLine(' "1" }; // Falloff ') + scriptBuilder.AppendLine(" //--------------------- ") + scriptBuilder.AppendLine(" // Selection Procedure: ") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + " select -r " + + cacheSelectArgs + + " ; // Grab the Fluids selected earlier by the user, or throw an error if none are selected " + ) + scriptBuilder.AppendLine(" string $selection[] = `ls -sl`; ") + scriptBuilder.AppendLine(" for( $item in $selection ) ") + scriptBuilder.AppendLine( + ' print("Found Fluid: " + $item ); ' + ) + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + " doCreateFluidCache(5, $fluidArgs); // The actual work " + ) + + self.ScriptFilename = self.WriteBatchScriptFile(scriptBuilder) + + else: + self.LogInfo(">This is a Render Job") + + # Get render options so we can build the script to execute. + self.RegionRendering = self.GetBooleanPluginInfoEntryWithDefault( + "RegionRendering", False + ) + self.SingleRegionJob = self.IsTileJob() + self.SingleRegionFrame = str(self.GetStartFrame()) + self.SingleRegionIndex = self.GetCurrentTaskId() + + if self.RegionRendering and self.SingleRegionJob: + self.StartFrame = str(self.SingleRegionFrame) + self.EndFrame = str(self.SingleRegionFrame) + else: + self.StartFrame = str(self.GetStartFrame()) + self.EndFrame = str(self.GetEndFrame()) + + frameNumberOffset = self.GetIntegerPluginInfoEntryWithDefault( + "FrameNumberOffset", 0 + ) + allowsFrameRenumbering = not (self.Renderer == "vray") + if self.GetBooleanPluginInfoEntryWithDefault( + "RenderHalfFrames", False + ): + self.LogInfo("Rendering half frames") + if allowsFrameRenumbering: + self.ByFrame = "0.5" + self.RenumberFrameStart = str( + (self.GetStartFrame() * 2) + (frameNumberOffset * 2) + ) + self.EndFrame = ( + self.EndFrame + ".5" + ) # Need to add an extra 1/2 frame to the end of the end frame + else: + self.FailRender( + "Rendering Half Frames is not supported by this renderer." + ) + else: + self.ByFrame = "1" + if frameNumberOffset != 0: + if allowsFrameRenumbering: + self.RenumberFrameStart = str( + self.GetStartFrame() + frameNumberOffset + ) + else: + self.LogWarning( + "Renumbering Frames is not supported by this renderer." + ) + + self.LogInfo("Rendering with " + self.Renderer) + + if self.RegionRendering and self.SingleRegionJob: + self.ImagePrefix = self.GetPluginInfoEntryWithDefault( + "RegionPrefix" + self.SingleRegionIndex, "" + ).replace("\\", "/") + else: + self.ImagePrefix = ( + self.GetPluginInfoEntryWithDefault("OutputFilePrefix", "") + .strip() + .replace("\\", "/") + ) + + if ( + self.LocalRendering + and self.Renderer != "mentalrayexport" + and self.Renderer != "vrayexport" + and self.Renderer != "rendermanexport" + ): + if len(self.RenderDirectory) == 0: + self.LocalRendering = False + self.CurrentRenderDirectory = self.RenderDirectory + self.LogInfo( + "OutputFilePath was not specified in the plugin info file, rendering to network drive" + ) + else: + self.CurrentRenderDirectory = self.CreateTempDirectory( + "mayaOutput" + ).replace("\\", "/") + self.LogInfo( + "Rendering to local drive, will copy files and folders to final location after render is complete" + ) + else: + self.LocalRendering = False + self.CurrentRenderDirectory = self.RenderDirectory + self.LogInfo("Rendering to network drive") + + self.RenderLayer = self.GetPluginInfoEntryWithDefault( + "RenderLayer", "" + ).strip() + self.UsingRenderLayers = self.GetBooleanPluginInfoEntryWithDefault( + "UsingRenderLayers", False + ) + + self.Camera = self.GetPluginInfoEntryWithDefault( + "Camera", "" + ).strip() + self.Width = self.GetPluginInfoEntryWithDefault( + "ImageWidth", "" + ).strip() + self.Height = self.GetPluginInfoEntryWithDefault( + "ImageHeight", "" + ).strip() + self.Scale = self.GetPluginInfoEntryWithDefault( + "ImageScale", "" + ).strip() + self.AspectRatio = self.GetPluginInfoEntryWithDefault( + "AspectRatio", "" + ).strip() + self.SkipExistingFrames = self.GetBooleanPluginInfoEntryWithDefault( + "SkipExistingFrames", False + ) + + self.MotionBlur = ( + self.GetPluginInfoEntryWithDefault("MotionBlur", "") + .strip() + .lower() + ) + if self.MotionBlur == "true": + self.MotionBlur = "1" + elif self.MotionBlur == "false": + self.Motionblur = "0" + + self.AntiAliasing = self.GetPluginInfoEntryWithDefault( + "AntiAliasing", "" + ).strip() + self.Threads = self.GetThreadCount() + + # Get krakatoa information from .job file + if self.Renderer == "mayakrakatoa": + + # check if parameters exist in the .job file, indicating the job was initiated through the Maya interface. + try: + self.GetPluginInfoEntry("KrakatoaUseEmission") + except: + self.KrakatoaJobFileContainsKrakatoaParameters = False + + if self.KrakatoaJobFileContainsKrakatoaParameters: + self.KrakatoaFinalPassDensity = self.GetPluginInfoEntryWithDefault( + "KrakatoaFinalPassDensity", "7.0" + ).strip() + self.KrakatoaFinalPassDensityExponent = self.GetPluginInfoEntryWithDefault( + "KrakatoaFinalPassDensityExponent", "-1" + ).strip() + + if self.GetBooleanPluginInfoEntryWithDefault( + "KrakatoaUseLightingPassDensity", False + ): + self.KrakatoaUseLightingPassDensity = "1" + else: + self.KrakatoaUseLightingPassDensity = "0" + + self.KrakatoaLightingPassDensity = self.GetPluginInfoEntryWithDefault( + "KrakatoaLightingPassDensity", "1.0" + ).strip() + self.KrakatoaLightingPassDensityExponent = self.GetPluginInfoEntryWithDefault( + "KrakatoaLightingPassDensityExponent", "-1" + ).strip() + + if self.GetBooleanPluginInfoEntryWithDefault( + "KrakatoaUseEmissionStrength", False + ): + self.KrakatoaUseEmissionStrength = "1" + else: + self.KrakatoaUseEmissionStrength = "0" + + self.KrakatoaEmissionStrength = self.GetPluginInfoEntryWithDefault( + "KrakatoaEmissionStrength", "1.0" + ).strip() + self.KrakatoaEmissionStrengthExponent = self.GetPluginInfoEntryWithDefault( + "KrakatoaEmissionStrengthExponent", "-1" + ).strip() + + if self.GetBooleanPluginInfoEntryWithDefault( + "KrakatoaUseEmission", False + ): + self.KrakatoaUseEmission = "1" + else: + self.KrakatoaUseEmission = "0" + + if self.GetBooleanPluginInfoEntryWithDefault( + "KrakatoaUseAbsorption", False + ): + self.KrakatoaUseAbsorption = "1" + else: + self.KrakatoaUseAbsorption = "0" + + if self.GetBooleanPluginInfoEntryWithDefault( + "KrakatoaEnableMotionBlur", False + ): + self.KrakatoaEnableMotionBlur = "1" + else: + self.KrakatoaEnableMotionBlur = "0" + + self.KrakatoaMotionBlurParticleSegments = self.GetPluginInfoEntryWithDefault( + "KrakatoaMotionBlurParticleSegments", "2" + ).strip() + + if self.GetBooleanPluginInfoEntryWithDefault( + "KrakatoaJitteredMotionBlur", False + ): + self.KrakatoaJitteredMotionBlur = "1" + else: + self.KrakatoaJitteredMotionBlur = "0" + + self.KrakatoaShutterAngle = self.GetPluginInfoEntryWithDefault( + "KrakatoaShutterAngle", "180.0" + ).strip() + + if self.GetBooleanPluginInfoEntryWithDefault( + "KrakatoaEnableDOF", False + ): + self.KrakatoaEnableDOF = "1" + else: + self.KrakatoaEnableDOF = "0" + + self.KrakatoaSampleRateDOF = self.GetPluginInfoEntryWithDefault( + "KrakatoaSampleRateDOF", "0.1" + ).strip() + + if self.GetBooleanPluginInfoEntryWithDefault( + "KrakatoaEnableMatteObjects", False + ): + self.KrakatoaEnableMatteObjects = "1" + else: + self.KrakatoaEnableMatteObjects = "0" + + if self.GetPluginInfoEntryWithDefault( + "KrakatoaRenderingMethod", "Particles" + ).strip(): + self.KrakatoaRenderingMethod = "0" + else: + self.KrakatoaRenderingMethod = "1" + self.KrakatoaVoxelSize = self.GetPluginInfoEntryWithDefault( + "KrakatoaVoxelSize", "0.5" + ).strip() + self.KrakatoaVoxelFliterRadius = self.GetPluginInfoEntryWithDefault( + "KrakatoaVoxelFilterRadius", "1" + ).strip() + + if self.GetBooleanPluginInfoEntryWithDefault( + "KrakatoaForceEXROutput", True + ): + self.KrakatoaForceEXROutput = "1" + else: + self.KrakatoaForceEXROutput = "0" + + if self.RegionRendering: + if self.SingleRegionJob: + self.Left = self.GetPluginInfoEntryWithDefault( + "RegionLeft" + self.SingleRegionIndex, "0" + ) + self.Right = self.GetPluginInfoEntryWithDefault( + "RegionRight" + self.SingleRegionIndex, "0" + ) + self.Top = self.GetPluginInfoEntryWithDefault( + "RegionTop" + self.SingleRegionIndex, "0" + ) + self.Bottom = self.GetPluginInfoEntryWithDefault( + "RegionBottom" + self.SingleRegionIndex, "0" + ) + else: + self.Left = self.GetPluginInfoEntryWithDefault( + "RegionLeft", "0" + ).strip() + self.Right = self.GetPluginInfoEntryWithDefault( + "RegionRight", "0" + ).strip() + self.Top = self.GetPluginInfoEntryWithDefault( + "RegionTop", "0" + ).strip() + self.Bottom = self.GetPluginInfoEntryWithDefault( + "RegionBottom", "0" + ).strip() + self.LogInfo("Creating melscript to execute render") + + # Create the script to execute. + scriptBuilder = StringBuilder() + scriptBuilder.AppendLine("// Starting Mel program") + scriptBuilder.AppendLine() + + scriptBuilder.AppendLine() + scriptBuilder.AppendLine("proc renderIt(string $name) {") + scriptBuilder.AppendLine() + + scriptBuilder.AppendLine(self.GetInitializeCommand()) + + # local asset caching + if self.FirstTask and self.GetBooleanPluginInfoEntryWithDefault( + "UseLocalAssetCaching", False + ): + assetToolsScriptFilename = os.path.join( + self.GetPluginDirectory(), "AssetTools.mel" + ) + self.LogInfo( + "Importing scene asset introspection melscript: " + + assetToolsScriptFilename + ) + self.FlushMonitoredManagedProcessStdout(self.ProcessName) + self.WriteStdinToMonitoredManagedProcess( + self.ProcessName, + 'eval( "source \\"' + + assetToolsScriptFilename.replace("\\", "/") + + '\\";" )', + ) # The function to do LAC is in this external .mel script. + scriptBuilder.AppendLine(self.LocalAssetCachingCommand()) + + # If not rendering an animation, don't specify the animation parameters so that the output file isn't padded. + if self.Animation: + scriptBuilder.AppendLine(self.GetStartFrameCommand()) + scriptBuilder.AppendLine(self.GetEndFrameCommand()) + scriptBuilder.AppendLine(self.GetByFrameCommand()) + scriptBuilder.AppendLine(self.GetRenumberFrameStartCommand()) + + scriptBuilder.AppendLine(self.GetImagePrefixCommand()) + scriptBuilder.AppendLine(self.GetRenderDirectoryCommand()) + + scriptBuilder.AppendLine(self.GetCameraCommand()) + scriptBuilder.AppendLine(self.GetWidthCommand()) + scriptBuilder.AppendLine(self.GetHeightCommand()) + scriptBuilder.AppendLine(self.GetScaleCommand()) + scriptBuilder.AppendLine(self.GetResolutionCommand()) + # scriptBuilder.AppendLine( self.GetAspectRatioCommand() ) + scriptBuilder.AppendLine(self.GetSkipExistingFramesCommand()) + + scriptBuilder.AppendLine(self.GetAntiAliasingCommand()) + scriptBuilder.AppendLine(self.GetMotionBlurCommand()) + scriptBuilder.AppendLine(self.GetThreadsCommand()) + scriptBuilder.AppendLine(self.GetMemoryCommand()) + scriptBuilder.AppendLine(self.GetVerboseCommand()) + + scriptBuilder.AppendLine(self.GetRenderLayerCommand()) + scriptBuilder.AppendLine(self.GetRegionCommand()) + scriptBuilder.AppendLine(self.GetMiscCommands()) + + scriptBuilder.AppendLine(self.GetRenderCommand()) + + scriptBuilder.AppendLine() + scriptBuilder.AppendLine("}") + + scriptBuilder.AppendLine() + scriptBuilder.AppendLine("//") + scriptBuilder.AppendLine("// Main part") + scriptBuilder.AppendLine("//") + scriptBuilder.AppendLine("proc mainDeadlineRender() {") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + 'string $sceneName = "' + + self.SceneFile.replace("\\", "/") + + '";' + ) + scriptBuilder.AppendLine( + 'print ("Loading scene: " + $sceneName + "\\n");' + ) + scriptBuilder.AppendLine() + + # If using dirmap for path mapping, need to load the scene now (it's not loaded via the command line in this case). + # Only do this for the first task the Worker has picked up for this job. + if self.EnablePathMapping: + if self.FirstTask: + if ( + self.DirmapPathMapping + ): # this check seems to be equivalent to self.DelayLoadScene + self.CreateDelayLoadSceneMelscript(scriptBuilder) + + if self.XGenPathMapping: + mappings = [ + mappingPair + for mappingPair in RepositoryUtils.GetPathMappings() + if mappingPair[1] + ] + if len(mappings) > 0: + scriptBuilder.AppendLine() + scriptBuilder.AppendLine( + 'string $deadlineMappings[] = { "' + + '", "'.join( + mappingComponent.replace("\\", "/") + for mappingPair in mappings + for mappingComponent in mappingPair + ) + + '" };' + ) + scriptBuilder.AppendLine( + 'print("------[ XGen path mapping STARTED ]------\\n");' + ) + scriptBuilder.AppendLine( + "if( catch( mapXGen( $deadlineMappings ) ) ) {" + ) + scriptBuilder.AppendLine( + 'print("------[ XGen path mapping FAILED ]------\\n");' + ) + scriptBuilder.AppendLine("} else {") + scriptBuilder.AppendLine( + 'print("------[ XGen path mapping SUCCEEDED ]------\\n");' + ) + scriptBuilder.AppendLine("}") + scriptBuilder.AppendLine() + + if self.Renderer in ["arnold", "arnoldexport"]: + scriptBuilder.AppendLine( + "catch(python( \"DeadlineMayaBatchFunctions.performArnoldPathmapping( %s, %s, '%s')\" ) );" + % ( + self.StartFrame, + self.EndFrame, + self.TempThreadDirectory.replace("\\", "/"), + ) + ) + + # double check at this point to make sure the scene is loaded. it could have been loaded by the command line, or by MEL script if delayed loading is enabled. + scriptBuilder.AppendLine("string $checkScene = `file -q -sn`;") + scriptBuilder.AppendLine('if ($checkScene=="") {') + scriptBuilder.AppendLine( + 'error ("Cannot load scene \\"" + $sceneName + "\\". Please check the scene path, then try opening the scene on the machine which ran this job to troubleshoot the problem.\\n");' + ) + scriptBuilder.AppendLine( + "} else if (catch(`renderIt($sceneName)`)) {" + ) + scriptBuilder.AppendLine('error ("Render failed.\\n");') + scriptBuilder.AppendLine("} else {") + scriptBuilder.AppendLine('print ("Render completed.\\n");') + scriptBuilder.AppendLine("}") + scriptBuilder.AppendLine() + scriptBuilder.AppendLine("}") + + scriptBuilder.AppendLine("mainDeadlineRender();") + scriptBuilder.AppendLine("// Ending Mel program") + scriptBuilder.AppendLine() + + self.ScriptFilename = self.WriteBatchScriptFile(scriptBuilder) + + # Have maya batch execute the script. + self.LogInfo("Executing script: " + self.ScriptFilename) + self.FlushMonitoredManagedProcessStdout(self.ProcessName) + + # Execute either MEL or Python script based on file extension provided. + if os.path.splitext(self.ScriptFilename)[1].lower() == ".py": + self.WriteStdinToMonitoredManagedProcess( + self.ProcessName, + 'eval( "python( \\"execfile( \\\\\\"' + + self.ScriptFilename.replace("\\", "/") + + '\\\\\\" )\\" );" );', + ) + else: + self.WriteStdinToMonitoredManagedProcess( + self.ProcessName, + 'eval( "source \\"' + + self.ScriptFilename.replace("\\", "/") + + '\\";" )', + ) + + # Wait until render is complete. + self.LogInfo("Waiting for script to finish") + self.WaitForProcess() + + # If this is a regular job, do some post processing. + if not self.ScriptJob: + # Delete the temp script file. + os.remove(self.ScriptFilename) + + # If local rendering, move output to its final destination. + if self.LocalRendering and self.Renderer != "3delight": + self.LogInfo( + "Moving output files and folders from " + + self.CurrentRenderDirectory + + " to " + + self.RenderDirectory + ) + self.VerifyAndMoveDirectory( + self.CurrentRenderDirectory, + self.RenderDirectory, + False, + -1, + ) + + # We've now finished the first task on this Worker for this job. + self.FirstTask = False + + def SetRedshiftPathmappingEnv(self): + # "C:\MyTextures\" "\\MYSERVER01\Textures\" ... + redshiftMappingRE = re.compile(r"\"([^\"]*)\"\s+\"([^\"]*)\"") + + if self.Renderer == "redshift" and self.EnablePathMapping: + mappings = RepositoryUtils.GetPathMappings() + # Remove Mappings with no to path. + mappings = [ + mappingPair for mappingPair in mappings if mappingPair[1] + ] + + if len(mappings) == 0: + return + + self.LogInfo("Redshift Path Mapping...") + + oldRSMappingFileName = Environment.GetEnvironmentVariable( + "REDSHIFT_PATHOVERRIDE_FILE" + ) + if oldRSMappingFileName: + self.LogInfo( + '[REDSHIFT_PATHOVERRIDE_FILE]="%s"' % oldRSMappingFileName + ) + with io.open( + oldRSMappingFileName, mode="r", encoding="utf-8" + ) as oldRSMappingFile: + for line in oldRSMappingFile: + mappings.extend(redshiftMappingRE.findall(line)) + + oldRSMappingString = Environment.GetEnvironmentVariable( + "REDSHIFT_PATHOVERRIDE_STRING" + ) + if oldRSMappingString: + self.LogInfo( + '[REDSHIFT_PATHOVERRIDE_STRING]="%s"' % oldRSMappingString + ) + mappings.extend(redshiftMappingRE.findall(oldRSMappingString)) + + newRSMappingFileName = os.path.join( + self.TempThreadDirectory, "RSMapping.txt" + ) + with io.open( + newRSMappingFileName, mode="w", encoding="utf-8" + ) as newRSMappingFile: + for mappingPair in mappings: + self.LogInfo( + u'source: "%s" dest: "%s"' + % (mappingPair[0], mappingPair[1]) + ) + newRSMappingFile.write( + u'"%s" "%s"\n' % (mappingPair[0], mappingPair[1]) + ) + + self.SetEnvironmentAndLogInfo( + "REDSHIFT_PATHOVERRIDE_FILE", newRSMappingFileName + ) + + def _get_valid_mappings_for_dirmap(self): + """ + Pulls all Path mappings from Deadline and ensures all paths are valid for dirmap. + Validity is ensured by: + Changing all backslashes(\) in the output to slashes(/) + removing all paths that do not start with either / or a : + :return: a list of pairs of paths contain the From and To components for pathmapping + """ + # Confirms that paths start with either \,/ or : + valid_mapping_re = re.compile(r"^([a-z]:|[\\\/])", re.IGNORECASE) + + valid_mappings = [] + for mapping_pair in RepositoryUtils.GetPathMappings(): + # Confirm both the from and to path are valid + if all(map(valid_mapping_re.match, mapping_pair)): + valid_mappings.append( + map(lambda x: x.replace("\\", "/"), mapping_pair) + ) + + return valid_mappings + + def build_dirmap_commands(self): + """ + Builds a list of melscript commands needed to enable Dirmap pathmapping in Maya + :return: a list of melscript commands. + """ + + mappings = self._get_valid_mappings_for_dirmap() + + if not mappings: + self.LogInfo( + "No valid path mappings found. Skipping adding dirmap commands." + ) + return [] + + # Enable Dirmap + commands = ["dirmap -en true;"] + + for original, mapped in mappings: + self.LogInfo( + 'Adding dirmap command to map "{}" to "{}"'.format( + original, mapped + ) + ) + commands.append('dirmap -m "{}" "{}";'.format(original, mapped)) + + return commands + + def CreateDelayLoadSceneMelscript(self, scriptBuilder): + + # If using dirmap for path mapping, need to load the scene now (it's not loaded via the command line in this case). + # Only do this for the first task the Worker has picked up for this job. + # The below is only a sanity check, and should never fail. + if ( + not self.EnablePathMapping or not self.DirmapPathMapping + ): # this check seems to be equivalent to self.DelayLoadScene + raise Exception( + "Do not call CreateDelayedLoadingMelscript unless delayed loading is enabled." + ) + + scriptBuilder.AppendLine("\n".join(self.build_dirmap_commands())) + scriptBuilder.AppendLine() + + # Force load plug-ins that Maya does not properly auto-load when the scene depends on them + scriptBuilder.AppendLine( + 'catch( python( "DeadlineMayaBatchFunctions.ForceLoadPlugins()" ) );' + ) + + # Use "catchQuiet" to silence errors when loading the scene + useCatchQuiet = self.GetBooleanConfigEntryWithDefault( + "SilenceSceneLoadErrors", False + ) + + # this call right here actually loads the scene file. + # this is needed because in DelayLoadScene mode, this filename was not passed on the command line. + scriptBuilder.AppendLine( + "int $loadFailed = %s( `file -o $sceneName` );" + % ("catchQuiet" if useCatchQuiet else "catch") + ) + + scriptBuilder.AppendLine("if (!$loadFailed) {") + + # show loaded plugins + scriptBuilder.AppendLine( + 'catch( python( "DeadlineMayaBatchFunctions.OutputPluginVersions()" ) );' + ) + + # Need to execute the startup script now, after the scene has been loaded. + if self.StartupScriptPath != "": + scriptBuilder.AppendLine( + 'string $startupScriptName = "' + + self.StartupScriptPath.replace("\\", "/") + + '";' + ) + scriptBuilder.AppendLine( + 'print ("Executing startup script: " + $startupScriptName + "\\n");' + ) + scriptBuilder.AppendLine( + 'if( catch(eval( "source \\"" + $startupScriptName + "\\";" )) ){' + ) + scriptBuilder.AppendLine( + 'print ( "An error occurred during the Startup Script.\\n" );' + ) + scriptBuilder.AppendLine("}") + + scriptBuilder.AppendLine("}") + + # this call is currently a work around because Maya's dirmap is failing to pathmap any file paths that include tokens. + # When this is fixed in maya we can add a version check. + scriptBuilder.AppendLine( + 'catch( remapNodeFilePathsWithTokens( "file", "fileTextureName", true ) );' + ) + scriptBuilder.AppendLine( + 'catch( remapNodeFilePathsWithTokens( "aiStandIn", "dso", false ) );' + ) + # Dirmap does not map OpenColorIO Files so we have to handle this separately. + scriptBuilder.AppendLine( + "catch( mapOpenColorIOFile( " + + ("1" if self.EnableOpenColorIO else "0") + + " ) );" + ) + + def EndJob(self): + self.LogInfo("Ending Maya Job") + self.FlushMonitoredManagedProcessStdoutNoHandling(self.ProcessName) + self.LogInfo("Waiting for Maya to shut down") + self.ShutdownMonitoredManagedProcess(self.ProcessName) + self.LogInfo("Maya has shut down") + + if ( + self.EnablePathMapping + and not self.DirmapPathMapping + and os.path.splitext(self.SceneFile)[1].lower() == ".ma" + ): + os.remove(self.SceneFile) + + def WaitForProcess(self): + self.FlushMonitoredManagedProcessStdout(self.ProcessName) + self.WriteStdinToMonitoredManagedProcess( + self.ProcessName, self.Process.ReadyForInputCommand() + ) + while not self.Process.IsReadyForInput(): + self.VerifyMonitoredManagedProcess(self.ProcessName) + self.FlushMonitoredManagedProcessStdout(self.ProcessName) + + blockingDialogMessage = self.CheckForMonitoredManagedProcessPopups( + self.ProcessName + ) + if blockingDialogMessage: + self.FailRender(blockingDialogMessage) + + if self.IsCanceled(): + self.FailRender("Received cancel task command") + SystemUtils.Sleep(100) + self.Process.ResetReadyForInput() + + def unlockRedshiftRenderSetupOverrides(self): + renderSetupOverrides = defaultdict(list) + + if self.StartFrame or self.EndFrame: + renderSetupOverrides["defaultRenderGlobals"].append("animation") + if self.StartFrame: + renderSetupOverrides["defaultRenderGlobals"].append( + "startFrame" + ) + if self.EndFrame: + renderSetupOverrides["defaultRenderGlobals"].append("endFrame") + + if self.ByFrame: + renderSetupOverrides["defaultRenderGlobals"].append("byFrameStep") + + if self.RenumberFrameStart: + renderSetupOverrides["defaultRenderGlobals"].append( + "modifyExtension" + ) + renderSetupOverrides["defaultRenderGlobals"].append( + "startExtension" + ) + + if self.ImagePrefix: + renderSetupOverrides["defaultRenderGlobals"].append( + "imageFilePrefix" + ) + renderSetupOverrides["redshiftOptions"].append("imageFilePrefix") + + if self.Height: + renderSetupOverrides["defaultResolution"].append("height") + + if self.Width: + renderSetupOverrides["defaultResolution"].append("width") + + # Pass in renderSetupOverrides as a string + unlockOverrideString = 'string $renderSetupOverrides = "%s";\n' % json.dumps( + renderSetupOverrides + ).replace( + '"', '\\"' + ) + unlockOverrideString += 'catch( python( "DeadlineMayaBatchFunctions.unlockRenderSetupOverrides(\'" + $renderSetupOverrides + "\' )" ) );' + return unlockOverrideString + + def GetInitializeCommand(self): + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return 'string $rl=""; string $rp="";select defaultRenderGlobals; setAttr .renderAll 1; float $resize=-1.;' + elif self.Renderer == "mayasoftware": + return 'string $opt = ""; string $rl=""; string $rp=""; float $resize=-1.; select defaultRenderGlobals; setAttr .renderAll 1;' + elif self.Renderer == "mayahardware": + return 'float $resize=-1.; global string $hardwareRenderOptions = ""; string $rl=""; string $rp=""; select hardwareRenderGlobals;' + elif self.Renderer == "mayahardware2": + return 'float $resize=-1.; global string $ogsRenderOptions = ""; string $rl=""; string $rp=""; select hardwareRenderingGlobals;' + elif self.Renderer == "mayavector": + return 'if (!`pluginInfo -q -l VectorRender`) {loadPlugin VectorRender;} vrCreateGlobalsNode(); string $rl=""; string $rp=""; float $resize=-1.; select vectorRenderGlobals; setAttr defaultRenderGlobals.renderAll 1;' + elif self.Renderer == "mentalray": + return 'string $opt=""; string $rl=""; string $rp=""; int $renderThreads = 2; float $resize=-1.; miLoadMayatomr; miCreateDefaultNodes(); select defaultRenderGlobals; setAttr .renderAll 1;' + elif self.Renderer in ["renderman", "rendermanris", "rendermanexport"]: + return ( + 'if (!`pluginInfo -q -l RenderMan_for_Maya`) {loadPlugin RenderMan_for_Maya;} rmanCreateAllGlobalsNodes();string $rl=""; string $rp=""; string $spoolmode=""; int $chunksize=100; int $rib=' + + ("1" if self.Renderer == "rendermanexport" else "0") + + '; int $ui=0; string $renderer=""; string $globals="";' + ) + elif self.Renderer in ["renderman22", "renderman22export"]: + return """if (!`pluginInfo -q -l RenderMan_for_Maya.py`) {loadPlugin RenderMan_for_Maya.py;} + python("import rfm2.api.nodes"); + string $rl=""; + python("rfm2.api.nodes.rman_globals()"); + string $options="";""" + elif self.Renderer == "turtle": + return 'string $extraOptions=""; string $rl=""; string $rp=""; float $resize=-1.; ilrLoadTurtle; setAttr TurtleRenderGlobals.renderer 0;' + elif self.Renderer == "gelato": + return 'string $opt=""; string $rl=""; string $rp=""; float $resize=-1.; select defaultRenderGlobals;' + elif self.Renderer in ["arnold", "arnoldexport"]: + return 'string $opt=""; string $rl=""; string $rp=""; float $resize=-1.; loadPlugin -quiet mtoa;;' + elif self.Renderer in ["redshift", "redshiftexport"]: + # Redshift 1.2.95 changed the redshiftGetRedshiftOptionsNode function to accept a single parameter. So try to call it without the parameter first, and if an error occurs (ie: catchQuiet returns True), then try to call it with the parameter. + return ( + 'string $rl=""; string $rp=""; float $resize=-1.; loadPlugin -quiet redshift4maya; redshiftRegisterRenderer(); if( catchQuiet( eval( "redshiftGetRedshiftOptionsNode()" ) ) ) { eval( "redshiftGetRedshiftOptionsNode(true)" ); };\n' + 'string $redshiftVersion = `pluginInfo -q -version "redshift4maya"`;string $redshiftVersions[];$redshiftVersions = stringToStringArray($redshiftVersion, ".");float $redshiftMajorVersion = ( float )( $redshiftVersions[0]+"."+$redshiftVersions[1] );\n' + + self.unlockRedshiftRenderSetupOverrides() + ) + elif self.Renderer in ["vray", "vrayexport"]: + return 'string $opt=""; string $rl=""; string $rp=""; float $resize=-1.; vrayRegisterRenderer(); vrayCreateVRaySettingsNode(); select vraySettings;' + elif self.Renderer == "3delight": + return 'string $opt = ""; string $rl=""; string $rp=""; DRG_batchRenderInit(); string $render_pass = DRG_selectedRenderPass(); if ($render_pass != "") { select $render_pass;};' + elif self.Renderer == "mentalrayexport": + return 'source mentalrayBatchExportProcedure.mel; string $filename=""; string $rl=""; string $rp=""; string $opt=""; float $resize=-1.; int $perLayer=1; miLoadMayatomr; miCreateDefaultNodes(); select defaultRenderGlobals; setAttr .renderAll 1;' + elif self.Renderer == "finalrender": + return 'int $kpi = 0; int $rep = 1; int $amt = 0; int $irr=0; int $frr[4]; string $rl=""; string $rp=""; int $numCpu=0; string $dr=""; string $strHosts = ""; frLoad; copyCommonRenderGlobals(currentRenderer(), "finalRender"); lockNode -lock false defaultFinalRenderSettings; select defaultRenderGlobals; setAttr .renderAll 1;' + elif self.Renderer in ["maxwell", "maxwellexport"]: + return "if (!`pluginInfo -q -l maxwell`) {loadPlugin maxwell;} setAttr defaultRenderGlobals.renderAll 1;" + elif self.Renderer == "mayakrakatoa": + return 'if (!`pluginInfo -q -l MayaKrakatoa`) {loadPlugin MayaKrakatoa;} string $rl=""; string $rp=""; string $options=""; select defaultRenderGlobals; setAttr .renderAll 1; float $resize=-1.;' + elif self.Renderer == "octanerender": + return 'string $opt = ""; string $rl=""; string $rp=""; float $resize=-1.; int $interactive=0; if(!`pluginInfo -q -l "OctanePlugin"`) {loadPlugin "OctanePlugin";} select defaultRenderGlobals; setAttr .renderAll 1;' + elif self.Renderer == "causticvisualizer": + return 'string $opt = ""; string $rl=""; string $rp=""; float $resize=-1.; int $interactive=0; select defaultRenderGlobals; setAttr .renderAll 1;' + elif self.Renderer == "ifmirayphotoreal": + return 'string $rl=""; string $rp="";select defaultRenderGlobals; setAttr .renderAll 1; float $resize=-1.;' + else: + self.LogWarning( + "Renderer " + + self.Renderer + + " is currently unsupported, so falling back to generic render arguments" + ) + return 'string $rl=""; string $rp="";select defaultRenderGlobals; setAttr .renderAll 1; float $resize=-1.;' + + return "" + + def LocalAssetCachingCommand(self): + + cacheDirectoryConfigEntry = "SlaveLACDirectoryWindows" + if SystemUtils.IsRunningOnMac(): + cacheDirectoryConfigEntry = "SlaveLACDirectoryOSX" + elif SystemUtils.IsRunningOnLinux(): + cacheDirectoryConfigEntry = "SlaveLACDirectoryLinux" + + cacheDirectory = self.GetConfigEntryWithDefault( + cacheDirectoryConfigEntry, "" + ).strip() + if not cacheDirectory: + self.LogWarning( + "Disabling local asset caching: The local asset cache directory for this platform is not set. You must set this option in the MayaBatch repository plugin settings." + ) + return "" + + cacheDirectory = os.path.expandvars(cacheDirectory).replace("\\", "/") + if not os.path.exists(cacheDirectory): + try: + os.makedirs(cacheDirectory) + except OSError: + # ensure that another Worker hasn't created this directory while we tried to. + if not os.path.exists(cacheDirectory): + self.FailRender( + 'Failed to create local asset cache directory: "%s". You can modify this directory in the MayaBatch repository plugin settings.' + % cacheDirectory + ) + + networkPrefixes = self.GetConfigEntryWithDefault( + "RemoteAssetPaths", "//;X:;Y:;Z:" + ).replace("\\", "/") + daysToDelete = self.GetIntegerConfigEntryWithDefault( + "SlaveLACDaysToDelete", 5 + ) + + # This do_local_asset_caching function is defined in AssetTools.mel. + scriptString = ( + '\ndo_local_asset_caching( "' + + cacheDirectory + + '", "' + + networkPrefixes + + '", ' + + str(daysToDelete) + + " );\n" + ) + + return scriptString + + def GetStartFrameCommand(self): + if len(self.StartFrame) > 0: + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return ( + "removeRenderLayerAdjustmentAndUnlock .animation; setAttr .animation 1; removeRenderLayerAdjustmentAndUnlock .startFrame; setAttr .startFrame " + + self.StartFrame + + ";" + ) + elif ( + self.Renderer == "mayasoftware" + or self.Renderer == "mentalray" + or self.Renderer == "gelato" + or self.Renderer == "mentalrayexport" + or self.Renderer == "finalrender" + or self.Renderer == "mayakrakatoa" + or self.Renderer == "octanerender" + or self.Renderer == "causticvisualizer" + ): + return ( + "removeRenderLayerAdjustmentAndUnlock .animation; setAttr .animation 1; removeRenderLayerAdjustmentAndUnlock .startFrame; setAttr .startFrame " + + self.StartFrame + + ";" + ) + elif self.Renderer in { + "mayavector", + "mayahardware", + "mayahardware2", + "turtle", + "arnold", + "arnoldexport", + "redshift", + "redshiftexport", + "vray", + "vrayexport", + "ifmirayphotoreal", + "renderman22", + "renderman22export", + }: + return ( + "removeRenderLayerAdjustmentAndUnlock defaultRenderGlobals.animation; setAttr defaultRenderGlobals.animation 1; removeRenderLayerAdjustmentAndUnlock defaultRenderGlobals.startFrame; setAttr defaultRenderGlobals.startFrame " + + self.StartFrame + + ";" + ) + elif ( + self.Renderer == "renderman" + or self.Renderer == "rendermanris" + or self.Renderer == "rendermanexport" + ): + return ( + "setAttr -l 0 defaultRenderGlobals.animation; setAttr defaultRenderGlobals.animation 1; setAttr -l 0 defaultRenderGlobals.startFrame; setAttr defaultRenderGlobals.startFrame " + + self.StartFrame + + ";" + ) + elif self.Renderer == "3delight": + return ( + "removeRenderLayerAdjustmentAndUnlock .startFrame; catch(`setAttr .startFrame " + + self.StartFrame + + "`);" + ) + elif ( + self.Renderer == "maxwell" or self.Renderer == "maxwellexport" + ): + return ( + "maxwellUnlockAndSet defaultRenderGlobals.animation 1; maxwellUnlockAndSet defaultRenderGlobals.startFrame " + + self.StartFrame + + ";" + ) + else: + return ( + "removeRenderLayerAdjustmentAndUnlock .animation; setAttr .animation 1; removeRenderLayerAdjustmentAndUnlock .startFrame; setAttr .startFrame " + + self.StartFrame + + ";" + ) + return "" + + def GetEndFrameCommand(self): + if len(self.EndFrame) > 0: + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return ( + "removeRenderLayerAdjustmentAndUnlock .animation; setAttr .animation 1; removeRenderLayerAdjustmentAndUnlock .endFrame; setAttr .endFrame " + + self.EndFrame + + ";" + ) + elif ( + self.Renderer == "mayasoftware" + or self.Renderer == "mentalray" + or self.Renderer == "gelato" + or self.Renderer == "mentalrayexport" + or self.Renderer == "finalrender" + or self.Renderer == "mayakrakatoa" + or self.Renderer == "octanerender" + or self.Renderer == "causticvisualizer" + ): + return ( + "removeRenderLayerAdjustmentAndUnlock .animation; setAttr .animation 1; removeRenderLayerAdjustmentAndUnlock .endFrame; setAttr .endFrame " + + self.EndFrame + + ";" + ) + elif self.Renderer in { + "mayavector", + "mayahardware", + "mayahardware2", + "turtle", + "arnold", + "arnoldexport", + "redshift", + "redshiftexport", + "vray", + "vrayexport", + "ifmirayphotoreal", + "renderman22", + "renderman22export", + }: + return ( + "removeRenderLayerAdjustmentAndUnlock defaultRenderGlobals.animation; setAttr defaultRenderGlobals.animation 1; removeRenderLayerAdjustmentAndUnlock defaultRenderGlobals.endFrame; setAttr defaultRenderGlobals.endFrame " + + self.EndFrame + + ";" + ) + elif ( + self.Renderer == "renderman" + or self.Renderer == "rendermanris" + or self.Renderer == "rendermanexport" + ): + return ( + "setAttr -l 0 defaultRenderGlobals.animation; setAttr defaultRenderGlobals.animation 1; setAttr -l 0 defaultRenderGlobals.endFrame; setAttr defaultRenderGlobals.endFrame " + + self.EndFrame + + ";" + ) + elif self.Renderer == "3delight": + return ( + "removeRenderLayerAdjustmentAndUnlock .endFrame; catch(`setAttr .endFrame " + + self.EndFrame + + "`);" + ) + elif ( + self.Renderer == "maxwell" or self.Renderer == "maxwellexport" + ): + return ( + "maxwellUnlockAndSet defaultRenderGlobals.animation 1; maxwellUnlockAndSet defaultRenderGlobals.endFrame " + + self.EndFrame + + ";" + ) + else: + return ( + "removeRenderLayerAdjustmentAndUnlock .animation; setAttr .animation 1; removeRenderLayerAdjustmentAndUnlock .endFrame; setAttr .endFrame " + + self.EndFrame + + ";" + ) + return "" + + def GetByFrameCommand(self): + if len(self.ByFrame) > 0: + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return ( + "removeRenderLayerAdjustmentAndUnlock .byFrameStep; catch(`setAttr .byFrameStep " + + self.ByFrame + + "`);" + ) + elif ( + self.Renderer == "mayasoftware" + or self.Renderer == "mentalray" + or self.Renderer == "gelato" + or self.Renderer == "mentalrayexport" + or self.Renderer == "finalrender" + or self.Renderer == "mayakrakatoa" + or self.Renderer == "octanerender" + or self.Renderer == "causticvisualizer" + ): + return ( + "removeRenderLayerAdjustmentAndUnlock .byFrameStep; catch(`setAttr .byFrameStep " + + self.ByFrame + + "`);" + ) + elif self.Renderer in { + "mayavector", + "mayahardware", + "mayahardware2", + "renderman", + "rendermanris", + "rendermanexport", + "renderman22", + "renderman22export", + "turtle", + "maxwell", + "maxwellexport", + "arnold", + "arnoldexport", + "redshift", + "redshiftexport", + "ifmirayphotoreal", + }: + return ( + "removeRenderLayerAdjustmentAndUnlock defaultRenderGlobals.byFrameStep; catch(`setAttr defaultRenderGlobals.byFrameStep " + + self.ByFrame + + "`);" + ) + elif self.Renderer == "vray" or self.Renderer == "vrayexport": + # return 'setAttr "vraySettings.frameStep" ' + self.ByFrame + '; setAttr "vraySettings.animation" true;' + # return 'setAttr "defaultRenderGlobals.byFrameStep" ' + self.ByFrame + '; setAttr "defaultRenderGlobals.animation" true;;' + return ( + 'setAttr "defaultRenderGlobals.byFrameStep" ' + + self.ByFrame + + '; setAttr "defaultRenderGlobals.animation" true;; setAttr "vraySettings.frameStep" ' + + self.ByFrame + + '; setAttr "vraySettings.animation" true;' + ) + elif self.Renderer == "3delight": + return ( + "removeRenderLayerAdjustmentAndUnlock .increment; catch(`setAttr .increment " + + self.ByFrame + + "`);" + ) + else: + return ( + "removeRenderLayerAdjustmentAndUnlock .byFrameStep; catch(`setAttr .byFrameStep " + + self.ByFrame + + "`);" + ) + return "" + + def GetRenumberFrameStartCommand(self): + if len(self.RenumberFrameStart) > 0: + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return ( + "removeRenderLayerAdjustmentAndUnlock .modifyExtension; setAttr .modifyExtension 1; removeRenderLayerAdjustmentAndUnlock .startExtension; setAttr .startExtension " + + self.RenumberFrameStart + + ";" + ) + elif ( + self.Renderer == "mayasoftware" + or self.Renderer == "mentalray" + or self.Renderer == "gelato" + or self.Renderer == "mentalrayexport" + or self.Renderer == "finalrender" + or self.Renderer == "mayakrakatoa" + or self.Renderer == "octanerender" + or self.Renderer == "causticvisualizer" + ): + return ( + "removeRenderLayerAdjustmentAndUnlock .modifyExtension; setAttr .modifyExtension 1; removeRenderLayerAdjustmentAndUnlock .startExtension; setAttr .startExtension " + + self.RenumberFrameStart + + ";" + ) + elif ( + self.Renderer == "mayavector" + or self.Renderer == "mayahardware" + or self.Renderer == "mayahardware2" + or self.Renderer == "turtle" + or self.Renderer == "arnold" + or self.Renderer == "arnoldexport" + or self.Renderer == "redshift" + or self.Renderer == "redshiftExport" + or self.Renderer == "ifmirayphotoreal" + ): + return ( + "removeRenderLayerAdjustmentAndUnlock defaultRenderGlobals.modifyExtension; setAttr defaultRenderGlobals.modifyExtension 1; removeRenderLayerAdjustmentAndUnlock defaultRenderGlobals.startExtension; setAttr defaultRenderGlobals.startExtension " + + self.RenumberFrameStart + + ";" + ) + elif ( + self.Renderer == "renderman" or self.Renderer == "rendermanris" + ): + return ( + 'rmanSetGlobalAttr "renumber" 1; rmanSetGlobalAttr "renumberStart" ' + + self.RenumberFrameStart + + ";" + ) + if self.Renderer == "renderman22": + # As of Renderman 22.3 Renderman does not support Frame renumbering. + return "" + elif ( + self.Renderer == "maxwell" or self.Renderer == "maxwellexport" + ): + return ( + "maxwellUnlockAndSet defaultRenderGlobals.modifyExtension 1; maxwellUnlockAndSet defaultRenderGlobals.startExtension " + + self.RenumberFrameStart + + ";" + ) + else: + return ( + "removeRenderLayerAdjustmentAndUnlock .modifyExtension; setAttr .modifyExtension 1; removeRenderLayerAdjustmentAndUnlock .startExtension; setAttr .startExtension " + + self.RenumberFrameStart + + ";" + ) + return "" + + def GetImagePrefixCommand(self): + """ + This function is used to write a command modify the Image Prefix in maya. + """ + if len(self.ImagePrefix) > 0: + + # Mental Ray doesn't properly handle these tokens when writing the output of incremental saves (file version is in the name) + # Even though it shows it in the render settings that it's properly using the tokens. + if self.Renderer == "mentalray" and ( + "%s" in self.ImagePrefix or "" in self.ImagePrefix + ): + sceneName = os.path.splitext(os.path.basename(self.SceneFile))[ + 0 + ] + self.ImagePrefix = self.ImagePrefix.replace( + "%s", sceneName + ).replace("", sceneName) + + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return ( + 'removeRenderLayerAdjustmentAndUnlock .imageFilePrefix; catch(`setAttr -type "string" .imageFilePrefix "' + + self.ImagePrefix + + '"`);' + ) + elif ( + self.Renderer == "mayasoftware" + or self.Renderer == "mentalray" + or self.Renderer == "gelato" + or self.Renderer == "mentalrayexport" + or self.Renderer == "finalrender" + or self.Renderer == "mayakrakatoa" + or self.Renderer == "octanerender" + or self.Renderer == "causticvisualizer" + ): + return ( + 'removeRenderLayerAdjustmentAndUnlock .imageFilePrefix; catch(`setAttr -type "string" .imageFilePrefix "' + + self.ImagePrefix + + '"`);' + ) + elif ( + self.Renderer == "mayavector" + or self.Renderer == "mayahardware" + or self.Renderer == "mayahardware2" + or self.Renderer == "renderman" + or self.Renderer == "rendermanris" + or self.Renderer == "rendermanexport" + or self.Renderer == "turtle" + or self.Renderer == "maxwell" + or self.Renderer == "maxwellexport" + or self.Renderer == "arnold" + or self.Renderer == "arnoldexport" + or self.Renderer == "ifmirayphotoreal" + ): + return ( + 'removeRenderLayerAdjustmentAndUnlock defaultRenderGlobals.imageFilePrefix; catch(`setAttr -type "string" defaultRenderGlobals.imageFilePrefix "' + + self.ImagePrefix + + '"`);' + ) + elif ( + self.Renderer == "redshift" + or self.Renderer == "redshiftexport" + ): + redshiftData = [ + 'if( $redshiftMajorVersion >1.2 || ( $redshiftMajorVersion == 1.2 && (int)$redshiftVersions[2] > 81) ){ removeRenderLayerAdjustmentAndUnlock defaultRenderGlobals.imageFilePrefix; catch(`setAttr -type "string" defaultRenderGlobals.imageFilePrefix "' + + self.ImagePrefix + + '"`); }', + ' else { removeRenderLayerAdjustmentAndUnlock redshiftOptions.imageFilePrefix; catch(`setAttr -type "string" redshiftOptions.imageFilePrefix "' + + self.ImagePrefix + + '"`); }', + ] + return "".join(redshiftData) + elif self.Renderer in {"renderman22", "renderman22export"}: + # Renderman command line renderer does not use this method and instead uses "$options += " -imageFile ...". We are not using that method because it modifies both the Image Prefix and the Output Directory which we want to handle separately + # We instead modify the Global property which Renderman reads when rendering. + return ( + 'removeRenderLayerAdjustmentAndUnlock rmanGlobals.imageFileFormat; catch(`setAttr -type "string" rmanGlobals.imageFileFormat "' + + self.ImagePrefix + + '"`);' + ) + elif self.Renderer == "vray" or self.Renderer == "vrayexport": + return ( + 'setAttr -type "string" "vraySettings.fileNamePrefix" "' + + self.ImagePrefix + + '";' + ) + else: + return ( + 'removeRenderLayerAdjustmentAndUnlock .imageFilePrefix; catch(`setAttr -type "string" .imageFilePrefix "' + + self.ImagePrefix + + '"`);' + ) + return "" + + def GetRenderDirectoryCommand(self): + if len(self.RenderDirectory) > 0: + flags = "-fr" if (self.Version > 2012) else "-rt" + + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return ( + "workspace " + + flags + + ' "images" "' + + self.CurrentRenderDirectory + + '"; workspace ' + + flags + + ' "depth" "' + + self.CurrentRenderDirectory + + '";' + ) + elif ( + self.Renderer == "mayasoftware" + or self.Renderer == "mentalray" + or self.Renderer == "mayavector" + or self.Renderer == "mayahardware" + or self.Renderer == "mayahardware2" + or self.Renderer == "turtle" + or self.Renderer == "vray" + or self.Renderer == "vrayexport" + or self.Renderer == "mentalrayexport" + or self.Renderer == "finalrender" + or self.Renderer == "maxwell" + or self.Renderer == "maxwellexport" + or self.Renderer == "mayakrakatoa" + or self.Renderer == "octanerender" + or self.Renderer == "causticvisualizer" + or self.Renderer == "redshift" + or self.Renderer == "ifmirayphotoreal" + ): + return ( + "workspace " + + flags + + ' "images" "' + + self.CurrentRenderDirectory + + '"; workspace ' + + flags + + ' "depth" "' + + self.CurrentRenderDirectory + + '";' + ) + elif self.Renderer == "redshiftexport": + sceneName = ' `workspace -q -rd` + "redshift/" ' + if len(self.RenderLayer) > 0: # dir depends on layer + sceneName += ' + "' + self.RenderLayer + '/" ' + sceneName += " + basenameEx($name) " + if self.Animation: + sceneName += ' + ".####"' + sceneName += ' + ".rs"' + + renderDirBuilder = StringBuilder() + renderDirBuilder.AppendLine("$rp = " + sceneName + ";") + renderDirBuilder.AppendLine(" string $path = dirname($rp);") + renderDirBuilder.AppendLine(" if(! `filetest -d $path` ){") + renderDirBuilder.AppendLine(" sysFile -md $path; }") + return renderDirBuilder.ToString() + elif self.Renderer == "arnold" or self.Renderer == "arnoldexport": + return ( + "workspace " + + flags + + ' "images" "' + + self.CurrentRenderDirectory + + '";workspace ' + + flags + + ' "depth" "' + + self.CurrentRenderDirectory + + '";workspace -fileRule "images" "' + + self.CurrentRenderDirectory + + '";' + ) + elif ( + self.Renderer == "renderman" + or self.Renderer == "rendermanris" + or self.Renderer == "rendermanexport" + ): + return 'rmanSetImageDir "' + self.CurrentRenderDirectory + '";' + elif self.Renderer in {"renderman22", "renderman22export"}: + return ( + 'removeRenderLayerAdjustmentAndUnlock rmanGlobals.imageOutputDir; catch(`setAttr -type "string" rmanGlobals.imageOutputDir "' + + self.CurrentRenderDirectory + + '"`);' + ) + elif self.Renderer == "gelato": + return ( + "workspace " + + flags + + ' "images" "' + + self.CurrentRenderDirectory + + '";' + ) + else: + return ( + "workspace " + + flags + + ' "images" "' + + self.CurrentRenderDirectory + + '"; workspace ' + + flags + + ' "depth" "' + + self.CurrentRenderDirectory + + '";' + ) + return "" + + def GetCameraCommand(self): + if len(self.Camera) > 0: + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return ( + 'makeCameraRenderable("' + + self.Camera + + '"); if (`attributeExists batchCamera vraySettings` != 0) setAttr -type "string" "vraySettings.batchCamera" "' + + self.Camera + + '";' + ) + elif self.Renderer in { + "mayasoftware", + "mentalray", + "mayavector", + "mayahardware", + "mayahardware2", + "renderman", + "rendermanris", + "renderman22", + "turtle", + "gelato", + "mentalrayexport", + "finalrender", + "maxwell", + "maxwellexport", + "mayakrakatoa", + "arnold", + "arnoldexport", + "octanerender", + "causticvisualizer", + "redshift", + "redshiftexport", + "ifmirayphotoreal", + }: + return 'makeCameraRenderable("' + self.Camera + '");' + elif self.Renderer == "vray" or self.Renderer == "vrayexport": + return ( + 'makeCameraRenderable("' + + self.Camera + + '"); setAttr -type "string" "vraySettings.batchCamera" "' + + self.Camera + + '";' + ) + elif self.Renderer == "3delight": + return ( + 'string $render_pass = DRG_selectedRenderPass(); DRP_setCamera($render_pass, "' + + self.Camera + + '");' + ) + else: + return ( + 'makeCameraRenderable("' + + self.Camera + + '"); if (`attributeExists batchCamera vraySettings` != 0) setAttr -type "string" "vraySettings.batchCamera" "' + + self.Camera + + '";' + ) + return "" + + def GetWidthCommand(self): + if len(self.Width) > 0: + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return ( + "removeRenderLayerAdjustmentAndUnlock defaultResolution.width; catch(`setAttr defaultResolution.width " + + self.Width + + "`);" + ) + elif ( + self.Renderer == "mayasoftware" + or self.Renderer == "mentalray" + or self.Renderer == "mayavector" + or self.Renderer == "mayahardware" + or self.Renderer == "mayahardware2" + or self.Renderer == "turtle" + or self.Renderer == "gelato" + or self.Renderer == "mentalrayexport" + or self.Renderer == "finalrender" + or self.Renderer == "maxwell" + or self.Renderer == "maxwellexport" + or self.Renderer == "mayakrakatoa" + or self.Renderer == "arnold" + or self.Renderer == "arnoldexport" + or self.Renderer == "octanerender" + or self.Renderer == "causticvisualizer" + or self.Renderer == "redshift" + or self.Renderer == "redshiftexport" + or self.Renderer == "ifmirayphotoreal" + ): + return ( + "removeRenderLayerAdjustmentAndUnlock defaultResolution.width; catch(`setAttr defaultResolution.width " + + self.Width + + "`);" + ) + elif self.Renderer == "vray" or self.Renderer == "vrayexport": + return 'setAttr "vraySettings.width" ' + self.Width + ";" + elif self.Renderer == "3delight": + return "DRG_setResolutionX(" + self.Width + ");" + else: + return ( + "removeRenderLayerAdjustmentAndUnlock defaultResolution.width; catch(`setAttr defaultResolution.width " + + self.Width + + "`);" + ) + return "" + + def GetHeightCommand(self): + if len(self.Height) > 0: + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return ( + "removeRenderLayerAdjustmentAndUnlock defaultResolution.height; catch(`setAttr defaultResolution.height " + + self.Height + + "`);" + ) + elif ( + self.Renderer == "mayasoftware" + or self.Renderer == "mentalray" + or self.Renderer == "mayavector" + or self.Renderer == "mayahardware" + or self.Renderer == "mayahardware2" + or self.Renderer == "turtle" + or self.Renderer == "gelato" + or self.Renderer == "mentalrayexport" + or self.Renderer == "finalrender" + or self.Renderer == "maxwell" + or self.Renderer == "maxwellexport" + or self.Renderer == "mayakrakatoa" + or self.Renderer == "arnold" + or self.Renderer == "arnoldexport" + or self.Renderer == "octanerender" + or self.Renderer == "causticvisualizer" + or self.Renderer == "redshift" + or self.Renderer == "redshiftexport" + or self.Renderer == "ifmirayphotoreal" + ): + return ( + "removeRenderLayerAdjustmentAndUnlock defaultResolution.height; catch(`setAttr defaultResolution.height " + + self.Height + + "`);" + ) + elif self.Renderer == "vray" or self.Renderer == "vrayexport": + return 'setAttr "vraySettings.height" ' + self.Height + ";" + elif self.Renderer == "3delight": + return "DRG_setResolutionY(" + self.Height + ");" + else: + return ( + "removeRenderLayerAdjustmentAndUnlock defaultResolution.height; catch(`setAttr defaultResolution.height " + + self.Height + + "`);" + ) + return "" + + def GetScaleCommand(self): + if len(self.Scale) > 0: + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return "$resize=" + self.Scale + ";" + elif ( + self.Renderer != "maxwell" + and self.Renderer != "maxwellexport" + and self.Renderer != "finalrender" + and self.Renderer != "3delight" + ): + return "$resize=" + self.Scale + ";" + return "" + + def GetResolutionCommand(self): + if len(self.Width) > 0 and len(self.Height) > 0: + if ( + self.Renderer == "renderman" + or self.Renderer == "rendermanris" + or self.Renderer == "rendermanexport" + ): + return ( + 'rmanSetGlobalAttr "Format:resolution" "' + + self.Width + + " " + + self.Height + + '";' + ) + elif self.Renderer in {"renderman22", "renderman22export"}: + return '$options += " -resolution %s %s";' % ( + self.Width, + self.Height, + ) + return "" + + # ~ def GetAspectRatioCommand( self ): + # ~ if len( self.AspectRatio ) > 0: + # ~ if self.Renderer == "file" or ( self.UsingRenderLayers and len( self.RenderLayer ) == 0 ): + # ~ return 'removeRenderLayerAdjustmentAndUnlock defaultResolution.deviceAspectRatio; catch(`setAttr defaultResolution.deviceAspectRatio ' + self.AspectRatio + '`);' + # ~ elif self.Renderer == "mayasoftware" or self.Renderer == "gelato" or self.Renderer == "finalrender": + # ~ return 'removeRenderLayerAdjustmentAndUnlock defaultResolution.lockDeviceAspectRatio; setAttr defaultResolution.lockDeviceAspectRatio 1; removeRenderLayerAdjustmentAndUnlock defaultResolution.deviceAspectRatio; setAttr defaultResolution.deviceAspectRatio ' + self.AspectRatio + ';' + # ~ elif self.Renderer == "mayavector" or self.Renderer == "mayahardware" or self.Renderer == "mayahardware2": + # ~ return 'removeRenderLayerAdjustmentAndUnlock defaultResolution.pixelAspect; catch(`setAttr defaultResolution.pixelAspect ' + self.AspectRatio + '`);' + # ~ elif self.Renderer == "mentalray" or self.Renderer == "turtle" or self.Renderer == "mentalrayexport": + # ~ return 'removeRenderLayerAdjustmentAndUnlock defaultResolution.deviceAspectRatio; catch(`setAttr defaultResolution.deviceAspectRatio ' + self.AspectRatio + '`);' + # ~ else: + # ~ return 'removeRenderLayerAdjustmentAndUnlock defaultResolution.deviceAspectRatio; catch(`setAttr defaultResolution.deviceAspectRatio ' + self.AspectRatio + '`);' + # ~ return '' + + def GetSkipExistingFramesCommand(self): + if self.Version >= 2014 and self.SkipExistingFrames: + if self.Renderer == "mayasoftware" or self.Renderer == "mentalray": + return "removeRenderLayerAdjustmentAndUnlock .skipExistingFrames; catch(`setAttr .skipExistingFrames 1`);" + if ( + self.Renderer == "mayavector" + or self.Renderer == "mayahardware" + or self.Renderer == "mayahardware2" + ): + return "removeRenderLayerAdjustmentAndUnlock defaultRenderGlobals.skipExistingFrames; catch(`setAttr defaultRenderGlobals.skipExistingFrames 1`);" + return "" + + def GetRenderLayerCommand(self): + if len(self.RenderLayer) > 0: + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return '$rl="' + self.RenderLayer + '";' + elif self.Renderer in { + "mayasoftware", + "mentalray", + "mayavector", + "mayahardware", + "mayahardware2", + "renderman", + "rendermanris", + "turtle", + "gelato", + "vray", + "mentalrayexport", + "finalrender", + "mayakrakatoa", + "arnold", + "arnoldexport", + "octanerender", + "causticvisualizer", + "redshift", + "ifmirayphotoreal", + }: + return '$rl="' + self.RenderLayer + '";' + elif self.Renderer == "redshiftexport": + return "editRenderLayerGlobals -crl " + self.RenderLayer + ";" + elif self.Renderer == "3delight": + return ( + 'string $node = "' + + self.RenderLayer + + '"; if ($node == "") $node = "defaultRenderLayer"; DRG_connectRenderPassAttr("layerToRender", $node);' + ) + elif self.Renderer in {"maxwell", "maxwellexport"}: + return ( + "selectLayerMembers " + + self.RenderLayer + + "; setAttr defaultRenderGlobals.renderAll 0;" + ) + else: + return '$rl="' + self.RenderLayer + '";' + return "" + + def GetMotionBlurCommand(self): + if len(self.MotionBlur) > 0: + if self.Renderer == "mayasoftware" or self.Renderer == "gelato": + return ( + "removeRenderLayerAdjustmentAndUnlock .motionBlur; catch(`setAttr .motionBlur " + + self.MotionBlur + + "`);" + ) + elif ( + self.Renderer == "mayahardware" + or self.Renderer == "mayahardware2" + ): + return ( + "removeRenderLayerAdjustmentAndUnlock .enableMotionBlur; catch(`setAttr .enableMotionBlur " + + self.MotionBlur + + "`);" + ) + elif self.Renderer == "finalrender": + return ( + "removeRenderLayerAdjustmentAndUnlock defaultFinalRenderSettings.motionBlur; catch(`setAttr defaultFinalRenderSettings.motionBlur " + + self.MotionBlur + + "`);" + ) + elif self.Renderer == "ifmirayphotoreal": + return ( + "removeRenderLayerAdjustmentAndUnlock ifmGlobalsCommon.motionBlur; catch(`setAttr ifmGlobalsCommon.motionBlur " + + self.MotionBlur + + "`);" + ) + return "" + + def GetAntiAliasingCommand(self): + if len(self.AntiAliasing) > 0: + if self.Renderer == "mayasoftware": + antialiasing = self.AntiAliasing + if antialiasing == "low": + antialiasing = "3" + elif antialiasing == "medium": + antialiasing = "2" + elif antialiasing == "high": + antialiasing = "1" + elif antialiasing == "highest": + antialiasing = "0" + return ( + "removeRenderLayerAdjustmentAndUnlock defaultRenderQuality.edgeAntiAliasing; catch(`setAttr defaultRenderQuality.edgeAntiAliasing " + + antialiasing + + "`);" + ) + return "" + + def GetThreadsCommand(self): + if len(self.Threads) > 0: + if self.Renderer in ["mayasoftware"]: + return ( + "setAttr .numCpusToUse " + + self.Threads + + "; if(!`about -mac`) { threadCount -n " + + self.Threads + + "; };" + ) + elif self.Renderer in ["mentalray"]: + numThreads = int(self.Threads) + if numThreads > 0: + return ( + "global int $g_mrBatchRenderCmdOption_NumThreadOn = true; global int $g_mrBatchRenderCmdOption_NumThread = " + + self.Threads + + ";" + ) + else: + return "global int $g_mrBatchRenderCmdOption_NumThreadAutoOn = true; global int $g_mrBatchRenderCmdOption_NumThreadAuto = true;" + elif self.Renderer in ["vray"]: + return ( + 'setAttr "vraySettings.sys_max_threads" ' + + self.Threads + + ";" + ) + elif self.Renderer in [ + "renderman", + "rendermanris", + "rendermanexport", + ]: + return ( + 'rmanSetGlobalAttr "limits:threads" "' + + self.Threads + + '";' + ) + elif self.Renderer in {"renderman22", "renderman22export"}: + return '$options += " -numThreads %s";' % self.Threads + elif self.Renderer == "3delight": + return ( + "removeRenderLayerAdjustmentAndUnlock .numberOfCPUs; catch(`setAttr .numberOfCPUs " + + self.Threads + + "`);" + ) + elif self.Renderer in ["finalrender"]: + return "$numCpu = " + self.Threads + ";" + elif self.Renderer in ["maxwell", "maxwellexport"]: + return ( + "removeRenderLayerAdjustmentAndUnlock maxwellRenderOptions.numThreads; catch(`setAttr maxwellRenderOptions.numThreads " + + self.Threads + + "`);" + ) + elif self.Renderer in ["arnold", "arnoldexport"]: + numThreads = int(self.Threads) + if numThreads > 0: + return ( + "removeRenderLayerAdjustmentAndUnlock defaultArnoldRenderOptions.threads_autodetect; catch(`setAttr defaultArnoldRenderOptions.threads_autodetect 0`);removeRenderLayerAdjustmentAndUnlock defaultArnoldRenderOptions.threads;catch(`setAttr defaultArnoldRenderOptions.threads " + + self.Threads + + "`);" + ) + else: + return "removeRenderLayerAdjustmentAndUnlock defaultArnoldRenderOptions.threads_autodetect; catch(`setAttr defaultArnoldRenderOptions.threads_autodetect 1`);" + return "" + + def GetMemoryCommand(self): + if self.Renderer == "mentalray": + autoMemoryLimit = self.GetBooleanPluginInfoEntryWithDefault( + "AutoMemoryLimit", True + ) + if autoMemoryLimit: + return "global int $g_mrBatchRenderCmdOption_MemLimitAutoOn = true; global int $g_mrBatchRenderCmdOption_MemLimitAuto = true;" + else: + memoryLimit = self.GetIntegerPluginInfoEntryWithDefault( + "MemoryLimit", 0 + ) + if memoryLimit >= 0: + return ( + "global int $g_mrBatchRenderCmdOption_MemLimitOn = true; global int $g_mrBatchRenderCmdOption_MemLimit = " + + str(memoryLimit) + + ";" + ) + elif self.Renderer == "vray": + if self.GetBooleanPluginInfoEntryWithDefault( + "VRayAutoMemoryEnabled", False + ): + self.LogInfo("Auto memory detection for VRay enabled") + + # This value is already in MB. + autoMemoryBuffer = self.GetLongPluginInfoEntryWithDefault( + "VRayAutoMemoryBuffer", 500 + ) + self.LogInfo( + "Auto memory buffer is " + str(autoMemoryBuffer) + " MB" + ) + + # Convert this value to MB. + availableMemory = (SystemUtils.GetAvailableRam() / 1024) / 1024 + self.LogInfo( + "Available system memory is " + + str(availableMemory) + + " MB" + ) + + # Now calculate the limit we should pass to vray. + autoMemoryLimit = availableMemory - autoMemoryBuffer + self.LogInfo( + "Setting VRay dynamic memory limit to " + + str(autoMemoryLimit) + + " MB" + ) + + return ( + 'setAttr "vraySettings.sys_rayc_dynMemLimit" ' + + str(autoMemoryLimit) + + ";" + ) + else: + self.LogInfo("Auto memory detection for VRay disabled") + return "" + + def GetRegionCommand(self): + if self.RegionRendering: + if ( + len(self.Left) > 0 + and len(self.Right) > 0 + and len(self.Top) > 0 + and len(self.Bottom) > 0 + ): + if ( + self.Renderer == "mayasoftware" + or self.Renderer == "redshift" + ): + # setMayaSoftwareRegion is a shorthand to set the region attributes of the defaultRenderGlobals which both mayaSoftware and Redshift use + return ( + "setMayaSoftwareRegion(" + + self.Left + + "," + + self.Right + + "," + + self.Top + + "," + + self.Bottom + + ");" + ) + elif self.Renderer == "mentalray": + return ( + "setMentalRayRenderRegion(" + + self.Left + + "," + + self.Right + + "," + + self.Top + + "," + + self.Bottom + + ");" + ) + elif self.Renderer in { + "renderman", + "rendermanris", + "renderman22", + }: + if len(self.Width) > 0 and len(self.Height) > 0: + width = int(self.Width) + height = int(self.Height) + if width > 0 and height > 0: + leftPercent = float(self.Left) / float(width) + rightPercent = float(self.Right) / float(width) + topPercent = float(self.Top) / float(height) + bottomPercent = float(self.Bottom) / float(height) + if self.Renderer == "renderman22": + return ( + "setAttr rmanGlobals.opt_cropWindowEnable 1;setAttr rmanGlobals.opt_cropWindowTopLeft %s %s; setAttr rmanGlobals.opt_cropWindowBottomRight %s %s;" + % ( + leftPercent, + topPercent, + rightPercent, + bottomPercent, + ) + ) + else: + return ( + "rmanSetCropWindow " + + str(leftPercent) + + " " + + str(rightPercent) + + " " + + str(topPercent) + + " " + + str(bottomPercent) + + ";" + ) + + elif self.Renderer == "turtle": + return ( + '$extraOptions += "-region ' + + self.Left + + " " + + self.Top + + " " + + self.Right + + " " + + self.Bottom + + '";' + ) + elif self.Renderer == "vray": + # return 'global int $batchDoRegion; $batchDoRegion=1; setAttr defaultRenderGlobals.left ' + self.Left + '; setAttr defaultRenderGlobals.rght ' + self.Right + '; setAttr defaultRenderGlobals.bot ' + self.Bottom + '; setAttr defaultRenderGlobals.top ' + self.Top + ';' + return ( + "vraySetBatchDoRegion(" + + self.Left + + "," + + self.Right + + "," + + self.Top + + "," + + self.Bottom + + ");;" + ) + elif self.Renderer == "3delight": + if len(self.Width) > 0 and len(self.Height) > 0: + width = int(self.Width) + height = int(self.Height) + if width > 0 and height > 0: + leftPercent = float(self.Left) / float(width) + rightPercent = float(self.Right) / float(width) + topPercent = float(self.Top) / float(height) + bottomPercent = float(self.Bottom) / float(height) + regionBuilder = StringBuilder() + regionBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock .useCropWindow; catch(`setAttr .useCropWindow 1`);" + ) + regionBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock .cropMinX; catch(`setAttr .cropMinX " + + str(leftPercent) + + "`);" + ) + regionBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock .cropMinY; catch(`setAttr .cropMinY " + + str(topPercent) + + "`);" + ) + regionBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock .cropMaxX; catch(`setAttr .cropMaxX " + + str(rightPercent) + + "`);" + ) + regionBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock .cropMaxY; catch(`setAttr .cropMaxY " + + str(bottomPercent) + + "`);" + ) + return regionBuilder.ToString() + elif self.Renderer == "finalrender": + return ( + "$irr=1; $frr[0]=" + + self.Left + + "; $frr[1]=" + + self.Bottom + + "; $frr[2]=" + + self.Right + + "; $frr[3]=" + + self.Top + + ";" + ) + elif self.Renderer == "arnold": + return ( + "setAttr defaultArnoldRenderOptions.regionMinX " + + self.Left + + ";setAttr defaultArnoldRenderOptions.regionMaxX " + + self.Right + + ";setAttr defaultArnoldRenderOptions.regionMinY " + + self.Top + + ";setAttr defaultArnoldRenderOptions.regionMaxY " + + self.Bottom + + ";;" + ) + elif self.Renderer == "ifmirayphotoreal": + width = int(self.Width) + height = int(self.Height) + if width > 0 and height > 0: + leftPercent = float(self.Left) / float(width) + rightPercent = float(self.Right) / float(width) + topPercent = float(self.Top) / float(height) + bottomPercent = float(self.Bottom) / float(height) + + regionBuilder = StringBuilder() + regionBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock ifmGlobalsCommon.region; catch(`setAttr ifmGlobalsCommon.region 1`);" + ) + regionBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock ifmGlobalsCommon.regionLeft; catch(`setAttr ifmGlobalsCommon.regionLeft " + + str(leftPercent) + + "`);" + ) + regionBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock ifmGlobalsCommon.regionTop; catch(`setAttr ifmGlobalsCommon.regionTop " + + str(topPercent) + + "`);" + ) + regionBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock ifmGlobalsCommon.regionRight; catch(`setAttr ifmGlobalsCommon.regionRight " + + str(rightPercent) + + "`);" + ) + regionBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock ifmGlobalsCommon.regionBottom; catch(`setAttr ifmGlobalsCommon.regionBottom " + + str(bottomPercent) + + "`);" + ) + return regionBuilder.ToString() + return "" + + def GetVerboseCommand(self): + if self.Renderer == "mentalray": + self.Verbosity = self.GetPluginInfoEntryWithDefault( + "MentalRayVerbose", "Progress Messages" + ) + + verbosity = "5" + if self.Verbosity == "No Messages": + verbosity = "0" + elif self.Verbosity == "Fatal Messages Only": + verbosity = "1" + elif self.Verbosity == "Error Messages": + verbosity = "2" + elif self.Verbosity == "Warning Messages": + verbosity = "3" + elif self.Verbosity == "Info Messages": + verbosity = "4" + elif self.Verbosity == "Progress Messages": + verbosity = "5" + elif self.Verbosity == "Detailed Messages (Debug)": + verbosity = "6" + return ( + "global int $g_mrBatchRenderCmdOption_VerbosityOn = true; global int $g_mrBatchRenderCmdOption_Verbosity = " + + verbosity + + ";" + ) + elif self.Renderer == "finalrender": + return "setAttr defaultFinalRenderSettings.displayMessages on; setAttr defaultFinalRenderSettings.verboseLevel 2;" + elif self.Renderer == "arnold" or self.Renderer == "arnoldexport": + # Create a named tuple so the properties are easier to access later on. + ArnoldVerbosity = namedtuple("ArnoldVerbosity", ["default", "max"]) + # Set the default verbosity settings to match the newest version at this time + arnoldVerboseDict = defaultdict( + lambda: ArnoldVerbosity(default=2, max=3), + { + 1: ArnoldVerbosity(default=1, max=2), + 2: ArnoldVerbosity(default=1, max=2), + 3: ArnoldVerbosity(default=2, max=3), + }, + ) + + mtoaVersion = self.GetIntegerPluginInfoEntryWithDefault( + "MayaToArnoldVersion", 2 + ) + verboseSettings = arnoldVerboseDict[mtoaVersion] + verbosity = self.GetIntegerPluginInfoEntryWithDefault( + "ArnoldVerbose", verboseSettings.default + ) + # Arnold will error if the verbosity level is higher than the maximum + if verbosity > verboseSettings.max: + verbosity = verboseSettings.max + self.Verbosity = str(verbosity) + return ( + "removeRenderLayerAdjustmentAndUnlock defaultArnoldRenderOptions.log_verbosity; catch(`setAttr defaultArnoldRenderOptions.log_verbosity " + + self.Verbosity + + "`);" + ) + elif self.Renderer == "octanerender": + return "removeRenderLayerAdjustmentAndUnlock octaneSettings.Verbose; catch(`setAttr octaneSettings.Verbose true`);" + elif self.Renderer == "causticvisualizer": + return "removeRenderLayerAdjustmentAndUnlock CausticVisualizerBatchSettings.consoleMaxVerbosityLevel; catch(`setAttr CausticVisualizerBatchSettings.consoleMaxVerbosityLevel 4`);" + elif self.Renderer == "redshift": + verbosity = self.GetIntegerPluginInfoEntryWithDefault( + "RedshiftVerbose", 2 + ) + if verbosity > 2: + verbosity = 2 + return "setAttr redshiftOptions.logLevel " + str(verbosity) + ";" + elif self.Renderer == "vray" or self.Renderer == "vrayexport": + return 'setAttr "vraySettings.sys_progress_increment" 1;' + return "" + + def GetMiscCommands(self): + if self.Renderer == "maxwell" or self.Renderer == "maxwellexport": + cmdArguments = "" + + slaveFound = False + # thisSlave = Environment.MachineName.lower() + thisSlave = self.GetSlaveName().lower() + interactiveSlaves = self.GetConfigEntryWithDefault( + "MaxwellInteractiveSlaves", "" + ).split(",") + for slave in interactiveSlaves: + if slave.lower().strip() == thisSlave: + self.LogInfo( + "This Worker is in the Maxwell interactive license list - an interactive license for Maxwell will be used instead of a render license" + ) + slaveFound = True + break + if not slaveFound: + cmdArguments += " -node" + + if self.GetBooleanPluginInfoEntryWithDefault( + "MaxwellResumeRender", False + ): + cmdArguments += " -trytoresume" + + if len(cmdArguments) > 0: + return ( + 'setAttr -type "string" maxwellRenderOptions.cmdLine "' + + cmdArguments.strip() + + '";' + ) + + elif self.Renderer == "arnold": + cmdArguments = ( + 'removeRenderLayerAdjustmentAndUnlock defaultArnoldRenderOptions.abortOnLicenseFail; setAttr "defaultArnoldRenderOptions.abortOnLicenseFail" ' + + str( + int( + self.GetBooleanConfigEntryWithDefault( + "AbortOnArnoldLicenseFail", True + ) + ) + ) + + ";" + ) + cmdArguments += "removeRenderLayerAdjustmentAndUnlock defaultArnoldRenderOptions.renderType; catch(`setAttr defaultArnoldRenderOptions.renderType 0`);" + + return cmdArguments + elif self.Renderer == "renderman": + return '$renderer="renderMan"; $globals="renderManGlobals";' + + elif self.Renderer == "rendermanris": + return ( + '$renderer = "renderManRIS"; $globals = "renderManRISGlobals";' + ) + + elif self.Renderer == "rendermanexport": + if self.GetBooleanPluginInfoEntryWithDefault( + "RenderWithRis", False + ): + return '$renderer = "renderManRIS"; $globals = "renderManRISGlobals";' + else: + return '$renderer="renderMan"; $globals="renderManGlobals";' + elif self.Renderer == "renderman22export": + return r'$options += " -rib -ribFile \\\"%s/%s\\\""; ' % ( + self.GetPluginInfoEntry("RIBDirectory"), + self.GetPluginInfoEntry("RIBPrefix"), + ) + + elif self.Renderer == "redshift": + redshiftMiscArgs = [] + selectedGPUs = self.GetGpuOverrides() + if len(selectedGPUs) > 0: + gpus = ",".join(str(gpu) for gpu in selectedGPUs) + self.LogInfo( + "This Worker is overriding its GPU affinity, so the following GPUs will be used by RedShift: " + + gpus + ) + redshiftMiscArgs.append( + "redshiftSelectCudaDevices({" + gpus + "});" + ) + + if self.RegionRendering: + + renderElementIndex = 0 + while True: + renderElementNode = self.GetPluginInfoEntryWithDefault( + "RenderElementNodeName" + str(renderElementIndex), None + ) + if not renderElementNode: + break + + renderElementImagePrefixEntry = ( + "RenderElement" + + str(renderElementIndex) + + "RegionPrefix" + ) + if self.SingleRegionJob: + renderElementImagePrefixEntry += self.SingleRegionIndex + + renderElementImagePrefix = self.GetPluginInfoEntryWithDefault( + renderElementImagePrefixEntry, None + ) + redshiftMiscArgs.append( + "removeRenderLayerAdjustmentAndUnlock " + + renderElementNode + + '.filePrefix; setAttr -type "string" ' + + renderElementNode + + '.filePrefix "' + + renderElementImagePrefix + + '";' + ) + renderElementIndex += 1 + return "\n".join(redshiftMiscArgs) + + elif ( + self.Renderer == "mayakrakatoa" + and self.KrakatoaJobFileContainsKrakatoaParameters + ): + return ( + 'setAttr "MayaKrakatoaRenderSettings.finalPassDensity" ' + + self.KrakatoaFinalPassDensity + + ';\nsetAttr "MayaKrakatoaRenderSettings.finalPassDensityExponent" ' + + self.KrakatoaFinalPassDensityExponent + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.useLightingPassDensity" ' + + self.KrakatoaUseLightingPassDensity + + ';\nsetAttr "MayaKrakatoaRenderSettings.lightingPassDensity" ' + + self.KrakatoaLightingPassDensity + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.lightingPassDensityExponent" ' + + self.KrakatoaLightingPassDensityExponent + + ';\nsetAttr "MayaKrakatoaRenderSettings.useEmissionStrength" ' + + self.KrakatoaUseEmissionStrength + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.emissionStrength" ' + + self.KrakatoaEmissionStrength + + ';\nsetAttr "MayaKrakatoaRenderSettings.emissionStrengthExponent" ' + + self.KrakatoaEmissionStrengthExponent + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.useEmission" ' + + self.KrakatoaUseEmission + + ';\nsetAttr "MayaKrakatoaRenderSettings.useAbsorption" ' + + self.KrakatoaUseAbsorption + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.enableMotionBlur" ' + + self.KrakatoaEnableMotionBlur + + ';\nsetAttr "MayaKrakatoaRenderSettings.motionBlurParticleSegments" ' + + self.KrakatoaMotionBlurParticleSegments + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.jitteredMotionBlur" ' + + self.KrakatoaJitteredMotionBlur + + ';\nsetAttr "MayaKrakatoaRenderSettings.shutterAngle" ' + + self.KrakatoaShutterAngle + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.enableDOF" ' + + self.KrakatoaEnableDOF + + ';\nsetAttr "MayaKrakatoaRenderSettings.sampleRateDOF" ' + + self.KrakatoaSampleRateDOF + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.enableMatteObjects" ' + + self.KrakatoaEnableMatteObjects + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.renderingMethod" ' + + self.KrakatoaRenderingMethod + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.voxelSize" ' + + self.KrakatoaVoxelSize + + ';\nsetAttr "MayaKrakatoaRenderSettings.voxelFilterRadius" ' + + self.KrakatoaVoxelFilterRadius + + ';\n\ + setAttr "MayaKrakatoaRenderSettings.forceEXROutput" ' + + self.KrakatoaForceEXROutput + + ";\n" + ) + + elif self.Renderer == "octanerender": + miscBuilder = StringBuilder() + maxSamples = self.GetPluginInfoEntryWithDefault( + "OctaneMaxSamples", "" + ) + if maxSamples != "": + miscBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock octaneSettings.MaxSamples; catch(`setAttr octaneSettings.MaxSamples " + + maxSamples + + "`);" + ) + + selectedGPUs = self.GetGpuOverrides() + + if len(selectedGPUs) > 0: + miscBuilder.AppendLine("for( $r = 0;$r< 8;$r++ )") + miscBuilder.AppendLine("{") + miscBuilder.AppendLine(" setAttr octaneSettings.GPU[$r] 0;") + miscBuilder.AppendLine("};") + + selectedGPUs = [str(x) for x in selectedGPUs] + + for gpuId in selectedGPUs: + miscBuilder.AppendLine( + "setAttr octaneSettings.GPU[" + gpuId + "] 1;" + ) + + gpus = ",".join(selectedGPUs) + self.LogInfo( + "This Worker is overriding its GPU affinity, so the following GPUs will be used by Octane: " + + gpus + ) + + return miscBuilder.ToString() + + elif self.Renderer == "ifmirayphotoreal": + + # If the number of gpus per task is set, then need to calculate the gpus to use. + gpusPerTask = self.GetIntegerPluginInfoEntryWithDefault( + "GPUsPerTask", 0 + ) + gpusSelectDevices = self.GetPluginInfoEntryWithDefault( + "GPUsSelectDevices", "" + ) + useCpus = self.GetBooleanPluginInfoEntryWithDefault( + "IRayUseCpus", True + ) + cpuLoad = self.GetFloatPluginInfoEntryWithDefault("IRayCPULoad", 4) + maxSamples = self.GetIntegerPluginInfoEntryWithDefault( + "IRayMaxSamples", 1024 + ) + + miscBuilder = StringBuilder() + + selectedGPUs = self.GetGpuOverrides() + if len(selectedGPUs) > 0: + miscBuilder.AppendLine( + "$resourceCount = `ifmResource -count`;" + ) + miscBuilder.AppendLine("for( $r = 1;$r<$resourceCount;$r++ )") + miscBuilder.AppendLine("{") + miscBuilder.AppendLine(" ifmResource -d $r;") + miscBuilder.AppendLine("};") + + gpuList = [] + for gpuId in selectedGPUs: + gpuList.append(str(gpuId)) + miscBuilder.AppendLine( + "ifmResource -e " + str(gpuId + 1) + ";" + ) + + gpus = ",".join(gpuList) + self.LogInfo( + "This Worker is overriding its GPU affinity, so the following GPUs will be used by IRay: " + + gpus + ) + + if useCpus: + miscBuilder.AppendLine("ifmResource -e 0;") + miscBuilder.AppendLine( + "ifmResource -cl " + str(cpuLoad) + " 0;" + ) + self.LogInfo( + "Using CPUs for rendering with IRay, the following will be used for the CPU load: " + + str(cpuLoad) + ) + else: + miscBuilder.AppendLine("ifmResource -d 0;") + + miscBuilder.AppendLine( + "setAttr ifmGlobalsIrayPhotoreal.irayMaxSamples " + + str(maxSamples) + + ";" + ) + + return miscBuilder.ToString() + + elif ( + self.GetBooleanPluginInfoEntryWithDefault("Animation", True) + and self.Renderer == "vray" + ): + # New versions of Vray have a second frame list that use different commmands, so we'll just set it to the standard option + miscBuilder = StringBuilder() + miscBuilder.AppendLine( + 'if( `attributeExists "animType" vraySettings` )' + ) + miscBuilder.AppendLine("{") + miscBuilder.AppendLine(' setAttr "vraySettings.animType" 1;') + miscBuilder.AppendLine("}") + return miscBuilder.ToString() + + return "" + + def GetRenderCommand(self): + if self.Renderer == "file" or ( + self.UsingRenderLayers and len(self.RenderLayer) == 0 + ): + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "", "");' + elif self.Renderer == "mayasoftware": + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "mayaSoftware", $opt);' + elif self.Renderer == "mayahardware": + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "mayaHardware", $hardwareRenderOptions);' + elif self.Renderer == "mayahardware2": + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "mayaHardware2", $ogsRenderOptions);' + elif self.Renderer == "mayavector": + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "mayaVector", "");' + elif self.Renderer == "mentalray": + return 'setMayaSoftwareLayers($rl, $rp); miCreateMentalJobs(); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "mentalRay", $opt);' + elif ( + self.Renderer == "renderman" + or self.Renderer == "rendermanris" + or self.Renderer == "rendermanexport" + ): + return "setMayaSoftwareLayers($rl, $rp); setCurrentRenderer($renderer); renderManExecCmdlineRender($spoolmode, $chunksize, $rib, $ui);" + elif self.Renderer in {"renderman22", "renderman22export"}: + return 'setMayaSoftwareLayers($rl, ""); mayaBatchRenderProcedure(0, "", "", "renderman", $options);' + elif self.Renderer == "turtle": + return 'ilrSetRenderLayersAndPasses($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "turtle", $extraOptions);' + elif self.Renderer == "gelato": + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "gelato", $opt);' + elif self.Renderer == "arnold": + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize);mayaBatchRenderProcedure(0, "", "", "arnold", $opt);' + elif self.Renderer == "arnoldexport": + cmdArguments = ['string $exportString = "arnoldExportAss";'] + + if self.RenderLayer: + cmdArguments.append( + "editRenderLayerGlobals -currentRenderLayer $rl;" + ) + cmdArguments.append('setMayaSoftwareLayers($rl, "");') + + if self.Animation: + # if you specifiy the start and end frame when exporting the frame number is automatically added to the ass file. + cmdArguments.append( + '$exportString = $exportString + " -sf ' + + self.StartFrame + + " -ef " + + self.EndFrame + + '";' + ) + else: + # Set the current frame so the correct frame is exported but the frame number is not added to the filename + cmdArguments.append("currentTime -e " + self.StartFrame + ";") + + if self.GetBooleanPluginInfoEntryWithDefault( + "ArnoldExportCompressed", False + ): + cmdArguments.append('$exportString = $exportString + " -c";') + + cmdArguments.append( + 'if(`getAttr defaultArnoldRenderOptions.binaryAss` == 0){$exportString = $exportString + " -a";}' + ) + cmdArguments.append( + 'if(`getAttr defaultArnoldRenderOptions.outputAssBoundingBox`){$exportString = $exportString + " -bb";}' + ) + cmdArguments.append( + 'if(`getAttr defaultArnoldRenderOptions.expandProcedurals`){$exportString = $exportString + " -ep";}' + ) + cmdArguments.append( + 'if(`getAttr defaultArnoldRenderOptions.exportAllShadingGroups`){$exportString = $exportString + " -shg"; }' + ) + cmdArguments.append("eval $exportString;") + + return "\n".join(cmdArguments) + elif self.Renderer == "redshift": + # somewhere in between redshift 2.5.30 and 2.5.47, they changed from using redshiftOptions.imageFilePrefix to defaultRenderGlobals.imageFilePrefix + # they overwrote defaultRenderGlobals.imageFilePrefix with whatever was in redshiftOptions.imageFilePrefix when the scene was loaded, causing + # a different output path for the first frame rendered + # this fix was given to us by Nicolas from Redshift + redshiftRender = [ + "int $buildNumber = (int)$redshiftVersions[2];", + 'if ($redshiftMajorVersion == 2.5 && $buildNumber > 30 && $buildNumber < 47 && size(`getAttr "defaultRenderGlobals.imageFilePrefix"`) > 0) {', + 'setAttr -type "string" "redshiftOptions.imageFilePrefix" "";', + "}", + 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); redshiftBatchRender("");', + ] + return "".join(redshiftRender) + elif self.Renderer == "redshiftexport": + if self.Animation: + return ( + "currentTime " + + self.StartFrame + + "; rsProxy -fp $rp -s `getAttr defaultRenderGlobals.startFrame` -e `getAttr defaultRenderGlobals.endFrame` -b `getAttr defaultRenderGlobals.byFrameStep` ;" + ) + else: + return "rsProxy -fp $rp;" + elif self.Renderer == "vray": + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "vray", $opt);' + elif self.Renderer == "mayakrakatoa": + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "MayaKrakatoa", $options);' + elif self.Renderer == "3delight": + return 'setMayaSoftwareLayers($rl, $rp); mayaBatchRenderProcedure(0, "", "", "_3delight", $opt);' + elif self.Renderer == "vrayexport": + exportFile = RepositoryUtils.CheckPathMapping( + self.GetPluginInfoEntry("VRayExportFile") + ).replace("\\", "/") + + exportBuilder = StringBuilder() + exportBuilder.AppendLine( + 'setAttr vraySettings.vrscene_on 1; setAttr -type "string" vraySettings.vrscene_filename "' + + exportFile + + '";;' + ) + exportBuilder.AppendLine( + "setAttr vraySettings.vrscene_render_on 0;;" + ) + exportBuilder.AppendLine( + 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "vray", $opt);;' + ) + return exportBuilder.ToString() + elif self.Renderer == "mentalrayexport": + exportFile = RepositoryUtils.CheckPathMapping( + self.GetPluginInfoEntry("MentalRayExportfile") + ).replace("\\", "/") + + exportBuilder = StringBuilder() + exportBuilder.AppendLine('$filename = "' + exportFile + '";') + + if self.GetBooleanPluginInfoEntryWithDefault( + "MentalRayExportBinary", False + ): + exportBuilder.AppendLine('$opt +=" -binary ";') + else: + exportBuilder.AppendLine( + '$opt +=" -tabstop ' + + self.GetPluginInfoEntryWithDefault( + "MentalRayExportTabStop", "8" + ) + + '";' + ) + + perFrame = self.GetIntegerPluginInfoEntryWithDefault( + "MentalRayExportPerFrame", 2 + ) + exportBuilder.AppendLine( + '$opt +=" -perframe ' + str(perFrame) + '";' + ) + if perFrame != 0: + exportBuilder.AppendLine( + '$opt +=" -padframe ' + + self.GetPluginInfoEntryWithDefault( + "MentalRayExportPadFrame", "4" + ) + + '";' + ) + + if not self.GetBooleanPluginInfoEntryWithDefault( + "MentalRayExportPerLayer", False + ): + exportBuilder.AppendLine("$perLayer=0;") + + passContributionMaps = self.GetBooleanPluginInfoEntryWithDefault( + "MentalRayExportPassContributionMaps", False + ) + if passContributionMaps: + exportBuilder.AppendLine('$opt +=" -pcm";') + + passUserData = self.GetBooleanPluginInfoEntryWithDefault( + "MentalRayExportPassUserData", False + ) + if passUserData: + exportBuilder.AppendLine('$opt +=" -pud";') + + pathnames = self.GetPluginInfoEntryWithDefault( + "MentalRayExportPathNames", "" + ).strip() + if len(pathnames) > 0: + pathnames = pathnames.replace("1", "a") # absolute + pathnames = pathnames.replace("2", "r") # relative + pathnames = pathnames.replace("3", "n") # none + exportBuilder.AppendLine( + '$opt +=" -exportPathNames \\"' + pathnames + '\\"";' + ) + + if self.GetBooleanPluginInfoEntryWithDefault( + "MentalRayExportFragment", False + ): + exportBuilder.AppendLine('$opt +=" -fragmentExport ";') + if self.GetBooleanPluginInfoEntryWithDefault( + "MentalRayExportFragmentMaterials", False + ): + exportBuilder.AppendLine('$opt +=" -fragmentMaterials ";') + if self.GetBooleanPluginInfoEntryWithDefault( + "MentalRayExportFragmentShaders", False + ): + exportBuilder.AppendLine( + '$opt +=" -fragmentIncomingShdrs ";' + ) + if self.GetBooleanPluginInfoEntryWithDefault( + "MentalRayExportFragmentChildDag", False + ): + exportBuilder.AppendLine('$opt +=" -fragmentChildDag ";') + + filters = self.GetPluginInfoEntryWithDefault( + "MentalRayExportFilterString", "" + ).strip() + if len(filters) > 0: + exportBuilder.AppendLine( + '$opt +=" -exportFilterString ' + filters + '";' + ) + + exportBuilder.AppendLine( + "setMayaSoftwareLayers($rl, $rp); miCreateMentalJobs(); setImageSizePercent($resize); if ($perLayer) { mentalrayBatchExportProcedure($filename, $opt); } else { mentalrayBatchExportSingleFile($filename, $opt); };" + ) + + return exportBuilder.ToString() + elif self.Renderer == "finalrender": + return "frSetDRBatchHosts($strHosts); setMayaSoftwareLayers($rl, $rp); if (!$irr) finalRender -batch -kpi $kpi -rep $rep -amt $amt -n $numCpu; else finalRender -batch -kpi $kpi -rep $rep -amt $amt -n $numCpu -rr $frr[0] $frr[1] $frr[2] $frr[3];" + elif self.Renderer == "maxwell": + maxwellBuilder = StringBuilder() + + renderTime = self.GetPluginInfoEntryWithDefault( + "MaxwellRenderTime", "" + ) + if len(renderTime) > 0: + maxwellBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock maxwellRenderOptions.renderTime; catch(`setAttr maxwellRenderOptions.renderTime " + + renderTime + + "`);" + ) + + samplingLevel = self.GetPluginInfoEntryWithDefault( + "MaxwellSamplingLevel", "" + ) + if len(samplingLevel) > 0: + maxwellBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock maxwellRenderOptions.samplingLevel; catch(`setAttr maxwellRenderOptions.samplingLevel " + + samplingLevel + + "`);" + ) + + maxwellBuilder.AppendLine( + 'string $maxwellVersion = `pluginInfo -q -version "maxwell"`;' + ) + maxwellBuilder.AppendLine("string $maxwellVersionTokens[];") + maxwellBuilder.AppendLine( + 'tokenize($maxwellVersion, ".", $maxwellVersionTokens);' + ) + maxwellBuilder.AppendLine( + "int $majorVersion = $maxwellVersionTokens[0];" + ) + maxwellBuilder.AppendLine( + "int $minorVersion = $maxwellVersionTokens[1];" + ) + maxwellBuilder.AppendLine( + "if( $majorVersion > 2 || ( $majorVersion == 2 && $minorVersion >= 5 ) ) {" + ) + maxwellBuilder.AppendLine('maxwellBatchRender("");') + maxwellBuilder.AppendLine("} else {") + maxwellBuilder.AppendLine("maxwell -batchRender;") + maxwellBuilder.AppendLine("}") + + return maxwellBuilder.ToString() + elif self.Renderer == "maxwellexport": + maxwellBuilder = StringBuilder() + + maxwellBuilder.AppendLine( + "maxwellUnlockAndSet maxwellRenderOptions.persistentMXS 1;" + ) + + mxsFile = self.GetPluginInfoEntryWithDefault("MaxwellMXSFile", "") + mxsFile = RepositoryUtils.CheckPathMapping(mxsFile).replace( + "\\", "/" + ) + if len(mxsFile) > 0: + maxwellBuilder.AppendLine( + 'setAttr -type "string" maxwellRenderOptions.mxsPath "' + + mxsFile + + '";' + ) + + maxwellBuilder.AppendLine( + "removeRenderLayerAdjustmentAndUnlock maxwellRenderOptions.exportOnly;" + ) + maxwellBuilder.AppendLine( + "catch(`setAttr maxwellRenderOptions.exportOnly 1`);" + ) + + maxwellBuilder.AppendLine( + 'string $maxwellVersion = `pluginInfo -q -version "maxwell"`;' + ) + maxwellBuilder.AppendLine("string $maxwellVersionTokens[];") + maxwellBuilder.AppendLine( + 'tokenize($maxwellVersion, ".", $maxwellVersionTokens);' + ) + maxwellBuilder.AppendLine( + "int $majorVersion = $maxwellVersionTokens[0];" + ) + maxwellBuilder.AppendLine( + "int $minorVersion = $maxwellVersionTokens[1];" + ) + maxwellBuilder.AppendLine( + "if( $majorVersion > 2 || ( $majorVersion == 2 && $minorVersion >= 5 ) ) {" + ) + maxwellBuilder.AppendLine('maxwellBatchRender("");') + maxwellBuilder.AppendLine("} else {") + maxwellBuilder.AppendLine("maxwell -batchRender;") + maxwellBuilder.AppendLine("}") + + return maxwellBuilder.ToString() + elif self.Renderer == "octanerender": + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure($interactive, "", "", "OctaneRender", $opt);;' + elif self.Renderer == "causticvisualizer": + return 'setMayaSoftwareLayers($rl, $rp);r\n setImageSizePercent($resize);r\n mayaBatchRenderProcedure($interactive, "", "", "CausticVisualizer", $opt);;' + elif self.Renderer == "ifmirayphotoreal": + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "ifmIrayPhotoreal", "");' + else: + return 'setMayaSoftwareLayers($rl, $rp); setImageSizePercent($resize); mayaBatchRenderProcedure(0, "", "", "", "");' + + return "" + + def GetGpuOverrides(self): + resultGPUs = [] + + # If the number of gpus per task is set, then need to calculate the gpus to use. + gpusPerTask = self.GetIntegerPluginInfoEntryWithDefault( + "GPUsPerTask", 0 + ) + gpusSelectDevices = self.GetPluginInfoEntryWithDefault( + "GPUsSelectDevices", "" + ) + + if self.OverrideGpuAffinity(): + overrideGPUs = self.GpuAffinity() + if gpusPerTask == 0 and gpusSelectDevices != "": + gpus = gpusSelectDevices.split(",") + notFoundGPUs = [] + for gpu in gpus: + if int(gpu) in overrideGPUs: + resultGPUs.append(gpu) + else: + notFoundGPUs.append(gpu) + + if len(notFoundGPUs) > 0: + self.LogWarning( + "The Worker is overriding its GPU affinity and the following GPUs do not match the Workers affinity so they will not be used: " + + ",".join(notFoundGPUs) + ) + if len(resultGPUs) == 0: + self.FailRender( + "The Worker does not have affinity for any of the GPUs specified in the job." + ) + elif gpusPerTask > 0: + if gpusPerTask > len(overrideGPUs): + self.LogWarning( + "The Worker is overriding its GPU affinity and the Worker only has affinity for " + + str(len(overrideGPUs)) + + " Workers of the " + + str(gpusPerTask) + + " requested." + ) + resultGPUs = overrideGPUs + else: + resultGPUs = list(overrideGPUs)[:gpusPerTask] + else: + resultGPUs = overrideGPUs + elif gpusPerTask == 0 and gpusSelectDevices != "": + resultGPUs = gpusSelectDevices.split(",") + + elif gpusPerTask > 0: + gpuList = [] + for i in range( + (self.GetThreadNumber() * gpusPerTask), + (self.GetThreadNumber() * gpusPerTask) + gpusPerTask, + ): + gpuList.append(str(i)) + resultGPUs = gpuList + + resultGPUs = list(resultGPUs) + + return resultGPUs + + def GetThreadCount(self): + threads = self.GetIntegerPluginInfoEntryWithDefault("MaxProcessors", 0) + if self.OverrideCpuAffinity() and self.GetBooleanConfigEntryWithDefault( + "LimitThreadsToCPUAffinity", False + ): + affinity = len(self.CpuAffinity()) + if threads == 0: + threads = affinity + else: + threads = min(affinity, threads) + + return str(threads) + + def is_known_fatal_strict_error(self, line): + """ + Parse a line to determine if it contains a known fatal error. + :param line: A line of text which we want to check if it contains a known fatal error + :return: Whether or not the line contains a fatal error + """ + for known_error in self.fatal_strict_errors: + # fatal_errors contains a list of strings and non string iterables. + if isinstance(known_error, string_types): + if known_error in line: + # if known error is a string and is contained in the line then it is a fatal error. + return True + elif all(innerError in line for innerError in known_error): + # if known_error is a non string iterable which contains strings then it is fatal if everything in the iterable is in the line. + return True + + return False + + +class MayaBatchProcess(ManagedProcess): + deadlinePlugin = None + + Version = 0 + Build = "none" + SceneFile = "" + ProjectPath = "" + StartupScriptPath = "" + Renderer = "" + DelayLoadScene = False + + ReadyForInput = False + FinishedFrameCount = 0 + + PreviousFinishedFrame = "" + SkipNextFrame = False + + vrayRenderingImage = False + + CausticCurrentFrame = 0 + CausticTotalPasses = 0 + + def __init__( + self, + deadlinePlugin, + version, + build, + sceneFile, + projectPath, + startupScriptPath, + renderer, + delayLoadScene=False, + ): + self.deadlinePlugin = deadlinePlugin + + self.Version = version + self.Build = build + self.SceneFile = sceneFile + self.ProjectPath = projectPath + self.StartupScriptPath = startupScriptPath + self.Renderer = renderer + self.DelayLoadScene = delayLoadScene + + self.InitializeProcessCallback += self.InitializeProcess + self.RenderExecutableCallback += self.RenderExecutable + self.RenderArgumentCallback += self.RenderArgument + + self.initialFrame = None + + def Cleanup(self): + for stdoutHandler in self.StdoutHandlers: + del stdoutHandler.HandleCallback + + del self.InitializeProcessCallback + del self.RenderExecutableCallback + del self.RenderArgumentCallback + + def InitializeProcess(self): + self.ProcessPriority = ProcessPriorityClass.BelowNormal + self.UseProcessTree = True + self.PopupHandling = True + self.StdoutHandling = True + + # FumeFX initial values to support Task Render Status + self.FumeFXStartFrame = 0 + self.FumeFXEndFrame = 0 + self.FumeFXCurrFrame = 0 + self.FumeFXMemUsed = "0Mb" + self.FumeFXFrameTime = "00:00.00" + self.FumeFXEstTime = "00:00:00" + + # FumeFX STDout Handlers (requires min. FumeFX v3.5.3) + self.AddStdoutHandlerCallback( + ".*FumeFX: Starting simulation \(([-]?[0-9]+) - ([-]?[0-9]+)\).*" + ).HandleCallback += ( + self.HandleFumeFXProgress + ) # 0: STDOUT: "FumeFX: Starting simulation (-20 - 40)." + self.AddStdoutHandlerCallback( + ".*FumeFX: Frame: ([-]?[0-9]+)" + ).HandleCallback += ( + self.HandleFumeFXProgress + ) # 0: STDOUT: "FumeFX: Frame: -15" + self.AddStdoutHandlerCallback( + ".*FumeFX: Memory used: ([0-9]+[a-zA-Z]*)" + ).HandleCallback += ( + self.HandleFumeFXProgress + ) # 0: STDOUT: "FumeFX: Memory used: 86Mb" + self.AddStdoutHandlerCallback( + ".*FumeFX: Frame Time: ([0-9]+:[0-9]+\.[0-9]+)" + ).HandleCallback += ( + self.HandleFumeFXProgress + ) # 0: STDOUT: "FumeFX: Frame Time: 00:01.69" + self.AddStdoutHandlerCallback( + ".*FumeFX: Estimated Time: ([0-9]+:[0-9]+:[0-9]+)" + ).HandleCallback += ( + self.HandleFumeFXProgress + ) # 0: STDOUT: "FumeFX: Estimated Time: 00:00:18" + + # This indicates that maya batch is ready for input from us. + self.AddStdoutHandlerCallback( + r"READY FOR INPUT" + ).HandleCallback += self.HandleReadyForInput + + # Catch licensing errors. + self.AddStdoutHandlerCallback( + "FLEXlm error: .*" + ).HandleCallback += self.HandleFatalError + self.AddStdoutHandlerCallback( + "Maya: License was not obtained" + ).HandleCallback += self.HandleFatalError + + # Catch Startup Script Errors + self.AddStdoutHandlerCallback( + "An error occurred during the Startup Script." + ).HandleCallback += self.HandleFatalError + + # Catch Yeti Licensing Errors + self.AddStdoutHandlerCallback( + ".*ERROR pgLicenseCheck.*" + ).HandleCallback += self.HandleErrorMessage + + # Progress updates, works when rendering multiple frames per chunk. + self.AddStdoutHandlerCallback( + "Finished Rendering.*\\.([0-9]+)\\.[^\\.]+" + ).HandleCallback += self.HandleChunkedProgress1 + self.AddStdoutHandlerCallback( + ".*Finished Rendering.*" + ).HandleCallback += self.HandleChunkedProgress2 + + # CUDA errors piped as stdout. + self.AddStdoutHandlerCallback( + ".*CUDA_ERROR_UNKNOWN.*" + ).HandleCallback += self.HandleFatalError + self.AddStdoutHandlerCallback( + ".*Failed to init the CUDA driver API.*" + ).HandleCallback += self.HandleFatalError + self.AddStdoutHandlerCallback( + ".*The system does not support the required CUDA compute capabilities.*" + ).HandleCallback += self.HandleFatalError + + # Some status messages. + self.AddStdoutHandlerCallback( + "Constructing shading groups|Rendering current frame" + ).HandleCallback += self.HandleStatusMessage + + # Handle Redshift GPU affinity error + if self.Renderer == "redshift": + self.AddStdoutHandlerCallback( + r"redshiftSelectCudaDevices\.mel line \d+: Invalid device id: (\d+)$" + ).HandleCallback += self.HandleRedshiftGPUAffinityError + + # Error message handling. + self.AddStdoutHandlerCallback( + ".*Error: .*|.*Warning: .*" + ).HandleCallback += self.HandleErrorMessage + + # Mental Ray progress handling. + self.AddStdoutHandlerCallback( + "progr: +([0-9]+\\.[0-9]+)% +rendered" + ).HandleCallback += self.HandleMentalRayProgress + self.AddStdoutHandlerCallback( + "progr: +([0-9]+\\.[0-9]+)% +computing final gather points" + ).HandleCallback += self.HandleMentalRayGathering + self.AddStdoutHandlerCallback( + "progr: writing image file .* \\(frame ([0-9]+)\\)" + ).HandleCallback += self.HandleMentalRayWritingFrame + self.AddStdoutHandlerCallback( + "progr: +rendering finished" + ).HandleCallback += self.HandleMentalRayComplete + + self.AddStdoutHandlerCallback( + "\\[PROGRESS\\] Completed frame*" + ).HandleCallback += self.HandleProgressMessage2 + self.AddStdoutHandlerCallback( + ".*\\[PROGRESS\\] TURTLE rendering frame 100\\.00.*" + ).HandleCallback += self.HandleProgressMessage2 + self.AddStdoutHandlerCallback( + ".*Render complete.*" + ).HandleCallback += self.HandleProgressMessage2 + + self.AddStdoutHandlerCallback( + "\\[PROGRESS\\] Percentage of rendering done: (.*)" + ).HandleCallback += self.HandleProgressMessage3 + self.AddStdoutHandlerCallback( + ".*\\[PROGRESS\\] TURTLE rendering frame ([0-9]+\\.[0-9]+).*" + ).HandleCallback += self.HandleProgressMessage3 + self.AddStdoutHandlerCallback( + ".*RIMG : +([0-9]+)%" + ).HandleCallback += self.HandleProgressMessage3 + + if self.Renderer == "vray" or self.Renderer == "vrayexport": + self.CountRenderableCameras = self.deadlinePlugin.GetIntegerPluginInfoEntryWithDefault( + "CountRenderableCameras", 1 + ) + + # if each renderable camera is submitted as a separate job, then the number of renderable cameras is 1 per job and 'Camera' is nonempty + if ( + self.deadlinePlugin.GetPluginInfoEntryWithDefault("Camera", "") + != "" + or self.CountRenderableCameras < 1 + ): + self.CountRenderableCameras = 1 + + self.vrayRenderingImage = False + self.AddStdoutHandlerCallback( + "V-Ray error: .*" + ).HandleCallback += self.HandleFatalError + self.AddStdoutHandlerCallback( + "V-Ray: Building light cache*" + ).HandleCallback += self.HandleVrayMessage + self.AddStdoutHandlerCallback( + "V-Ray: Prepass ([0-9]+) of ([0-9]+)*" + ).HandleCallback += self.HandleVrayMessage + self.AddStdoutHandlerCallback( + "V-Ray: Rendering image*" + ).HandleCallback += self.HandleVrayMessage + self.AddStdoutHandlerCallback( + "V-Ray: +([0-9]+)%" + ).HandleCallback += self.HandleVrayProgress + self.AddStdoutHandlerCallback( + "V-Ray: +([0-9]+) %" + ).HandleCallback += self.HandleVrayProgress + self.AddStdoutHandlerCallback( + "([0-9]+) % completed" + ).HandleCallback += self.HandleVrayProgress + self.AddStdoutHandlerCallback( + "V-Ray: Total frame time" + ).HandleCallback += self.HandleVrayFrameComplete + + self.AddStdoutHandlerCallback( + "V-Ray: Updating frame at time ([0-9]+)" + ).HandleCallback += self.HandleVrayExportProgress + self.AddStdoutHandlerCallback( + "V-Ray: Render complete" + ).HandleCallback += self.HandleVrayExportComplete + self.AddStdoutHandlerCallback( + ".*V-Ray warning.* The file path for .* cannot be created.*" + ).HandleCallback += self.HandleFatalError + + if ( + self.Renderer == "renderman" + or self.Renderer == "rendermanris" + or self.Renderer == "rendermanexport" + ): + self.AddStdoutHandlerCallback( + "rfm Notice: Rendering .* at ([0-9]+)" + ).HandleCallback += self.HandleRendermanProgress + + # Catch 3Delight Errors + if self.Renderer == "3delight": + self.AddStdoutHandlerCallback( + ".*3DL ERROR .*" + ).HandleCallback += self.HandleFatalError + self.AddStdoutHandlerCallback( + r"\[\d+\.?\d* \d+\.?\d* \d+\.?\d*\]" + ).HandleCallback += self.HandlePointCloudOutput + + # Catch Arnold Errors + if self.Renderer == "arnold" or self.Renderer == "arnoldexport": + self.AddStdoutHandlerCallback( + r"Plug-in, \"mtoa\", was not found on MAYA_PLUG_IN_PATH" + ).HandleCallback += self.HandleFatalError + self.AddStdoutHandlerCallback( + r"\[mtoa\] Failed batch render" + ).HandleCallback += self.HandleFatalError + self.AddStdoutHandlerCallback( + "render done" + ).HandleCallback += self.HandleProgressMessage2 + + if self.Renderer == "octanerender": + self.AddStdoutHandlerCallback( + r"Octane: starting animation of frame" + ).HandleCallback += self.HandleOctaneStartFrame + self.AddStdoutHandlerCallback( + r"Octane: Refreshed image, ([0-9]+) samples per pixel of ([0-9]+)" + ).HandleCallback += self.HandleOctaneProgress + + if self.Renderer == "causticvisualizer": + self.AddStdoutHandlerCallback( + r"Executing frame ([0-9]+)" + ).HandleCallback += self.HandleCausticVisualizerCurrentFrame + self.AddStdoutHandlerCallback( + r"Rendering ([0-9]+) passes" + ).HandleCallback += self.HandleCausticVisualizerTotalPasses + self.AddStdoutHandlerCallback( + r"Rendered to pass ([0-9]+)" + ).HandleCallback += self.HandleCausticVisualizerCurrentPass + + if self.Renderer == "ifmirayphotoreal": + self.AddStdoutHandlerCallback( + r"Writing.*\\.\\.\\." + ).HandleCallback += self.HandleIRayEndFrame + self.AddStdoutHandlerCallback( + r".*Received update to ([0-9]+) iterations.*" + ).HandleCallback += self.HandleIRayProgressMessage + + if self.Renderer == "redshift": + self.AddStdoutHandlerCallback( + r"Frame rendering aborted." + ).HandleCallback += self.HandleFatalError + self.AddStdoutHandlerCallback( + r"Rendering was internally aborted" + ).HandleCallback += self.HandleFatalError + self.AddStdoutHandlerCallback( + r'Cannot find procedure "rsPreference"' + ).HandleCallback += self.HandleFatalError + self.AddStdoutHandlerCallback( + "Rendering frame \\d+ \\((\\d+)/(\\d+)\\)" + ).HandleCallback += self.HandleRedshiftNewFrameProgress + self.AddStdoutHandlerCallback( + "Block (\\d+)/(\\d+) .+ rendered" + ).HandleCallback += self.HandleRedshiftBlockRendered + + self.AddStdoutHandlerCallback( + "\\[PROGRESS\\] ([0-9]+) percent" + ).HandleCallback += self.HandleProgressMessage1 + self.AddStdoutHandlerCallback( + "([0-9]+)%" + ).HandleCallback += self.HandleProgressMessage1 + + # Set the popup ignorers. + self.AddPopupIgnorer(".*entry point.*") + self.AddPopupIgnorer(".*Entry Point.*") + + # Ignore Vray popup + self.AddPopupIgnorer(".*Render history settings.*") + self.AddPopupIgnorer(".*Render history note.*") + + # Handle QuickTime popup dialog + # "QuickTime does not support the current Display Setting. Please change it and restart this application." + self.AddPopupHandler("Unsupported Display", "OK") + self.AddPopupHandler("Nicht.*", "OK") + + ## Called by Deadline to get the render executable. + def RenderExecutable(self): + versionString = str(self.Version).replace(".", "_") + + mayaExecutable = "" + mayaExeList = self.deadlinePlugin.GetConfigEntry("PypeWrapper") + + if SystemUtils.IsRunningOnWindows(): + if self.Build == "32bit": + self.deadlinePlugin.LogInfo("Enforcing 32 bit build of Maya") + mayaExecutable = FileUtils.SearchFileListFor32Bit(mayaExeList) + if mayaExecutable == "": + self.deadlinePlugin.LogWarning( + "32 bit Maya " + + versionString + + ' render executable was not found in the semicolon separated list "' + + mayaExeList + + '". Checking for any executable that exists instead.' + ) + + elif self.Build == "64bit": + self.deadlinePlugin.LogInfo("Enforcing 64 bit build of Maya") + mayaExecutable = FileUtils.SearchFileListFor64Bit(mayaExeList) + if mayaExecutable == "": + self.deadlinePlugin.LogWarning( + "64 bit Maya " + + versionString + + ' render executable was not found in the semicolon separated list "' + + mayaExeList + + '". Checking for any executable that exists instead.' + ) + + if mayaExecutable == "": + self.deadlinePlugin.LogInfo("Not enforcing a build of Maya") + mayaExecutable = FileUtils.SearchFileList(mayaExeList) + if mayaExecutable == "": + self.deadlinePlugin.FailRender( + "Maya " + + versionString + + ' render executable was not found in the semicolon separated list "' + + mayaExeList + + '". The path to the render executable can be configured from the Plugin Configuration in the Deadline Monitor.' + ) + + return mayaExecutable + + ## Called by Deadline to get the render arguments. + def RenderArgument(self): + renderArguments = "-prompt" + + # If self.DelayLoadScene is True, that means we'll be loading the scene from the melscript code, instead of via initial command line. + if not self.DelayLoadScene: + renderArguments += ' -file "' + self.SceneFile + '"' + renderArguments += StringUtils.BlankIfEitherIsBlank( + ' -script "', + StringUtils.BlankIfEitherIsBlank(self.StartupScriptPath, '"'), + ) + + renderArguments += StringUtils.BlankIfEitherIsBlank( + ' -proj "', StringUtils.BlankIfEitherIsBlank(self.ProjectPath, '"') + ) + return renderArguments + + def HandleReadyForInput(self): + self.ReadyForInput = True + + def IsReadyForInput(self): + return self.ReadyForInput + + def ResetReadyForInput(self): + self.ReadyForInput = False + + def ReadyForInputCommand(self): + return 'print( "READY FOR INPUT\\n" );' + + def ResetFrameCount(self): + self.FinishedFrameCount = 0 + self.PreviousFinishedFrame = "" + self.SkipNextFrame = False + self.initialFrame = None + + def HandleFatalError(self): + self.deadlinePlugin.LogWarning(self.GetRegexMatch(0)) + self.deadlinePlugin.WriteScriptToLog(isError=True) + self.SuppressThisLine() + self.deadlinePlugin.FailRender(self.GetRegexMatch(0)) + + def HandlePointCloudOutput(self): + self.SuppressThisLine() + + def HandleChunkedProgress1(self): + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + if endFrame - startFrame + 1 != 0: + self.deadlinePlugin.SetProgress( + 100 + * (int(self.GetRegexMatch(1)) - startFrame + 1) + / (endFrame - startFrame + 1) + ) + + def HandleChunkedProgress2(self): + self.FinishedFrameCount += 1 + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + if endFrame - startFrame + 1 != 0: + self.deadlinePlugin.SetProgress( + 100 * (self.FinishedFrameCount / (endFrame - startFrame + 1)) + ) + + def HandleRedshiftNewFrameProgress(self): + self.FinishedFrameCount = float(self.GetRegexMatch(1)) - 1 + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + if endFrame - startFrame + 1 != 0: + progress = 100 * ( + self.FinishedFrameCount / (endFrame - startFrame + 1) + ) + self.deadlinePlugin.SetProgress(progress) + + def HandleRedshiftBlockRendered(self): + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + + completedBlockNumber = float(self.GetRegexMatch(1)) + totalBlockCount = float(self.GetRegexMatch(2)) + finishedFrames = completedBlockNumber / totalBlockCount + finishedFrames = finishedFrames + self.FinishedFrameCount + + if endFrame - startFrame + 1 != 0: + progress = 100 * (finishedFrames / (endFrame - startFrame + 1)) + self.deadlinePlugin.SetProgress(progress) + + def HandleRedshiftGPUAffinityError(self): + """ + Redshift emits a warning message on standard output when the MELscript procedure redshiftSelectCudaDevices() is + called with a GPU ordinal that is "invalid". Redshift then falls back to using all available GPUs. + + When a user configures GPU affinity, they likely don't want to use Redshift's fallback behavior - especially + since Redshift would then try to allocate the majority of the remaining GPUs VRAM. We catch this warning + message and fail the render with a more user-friendly message + """ + gpu_device_id = self.GetRegexMatch(1) + self.deadlinePlugin.FailRender( + "Redshift has detected that the selected GPU '%s' is invalid. Failing the task to prevent Redshift from using all GPUs." + % gpu_device_id + ) + + def HandleStatusMessage(self): + self.deadlinePlugin.SetStatusMessage(self.GetRegexMatch(0)) + + def HandleErrorMessage(self): + errorMessage = self.GetRegexMatch(0) + + # This message is always fatal, because it means the scene could not be loaded. + if errorMessage.find("Cannot load scene") != -1: + self.deadlinePlugin.LogWarning(self.GetRegexMatch(0)) + self.deadlinePlugin.WriteScriptToLog(isError=True) + self.SuppressThisLine() + self.deadlinePlugin.FailRender(errorMessage) + + if not self.deadlinePlugin.GetBooleanPluginInfoEntryWithDefault( + "StrictErrorChecking", True + ): + self.deadlinePlugin.LogWarning( + "Strict error checking off, ignoring the following error or warning." + ) + else: + + if self.deadlinePlugin.is_known_fatal_strict_error(errorMessage): + self.deadlinePlugin.LogWarning(self.GetRegexMatch(0)) + self.deadlinePlugin.WriteScriptToLog(isError=True) + self.SuppressThisLine() + self.deadlinePlugin.FailRender( + "Strict error checking on, caught the following error or warning.\n" + + errorMessage + + "\nIf this error message is unavoidable but not fatal, please email support@thinkboxsoftware.com with the error message, and disable the Maya job setting Strict Error Checking." + ) + else: + # Check if we're suppressing warnings. + if ( + self.deadlinePlugin.GetBooleanConfigEntryWithDefault( + "SuppressWarnings", False + ) + and errorMessage.find("Warning:") != -1 + ): + self.SuppressThisLine() + else: + # Only print this out if we're not suppressing warnings. + self.deadlinePlugin.LogWarning( + "Strict error checking on, ignoring the following unrecognized error or warning. If it is fatal, please email support@thinkboxsoftware.com with the error message." + ) + + def HandleProgressMessage1(self): + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + if endFrame - startFrame + 1 != 0: + self.deadlinePlugin.SetProgress( + (float(self.GetRegexMatch(1)) + self.FinishedFrameCount * 100) + / (endFrame - startFrame + 1) + ) + # self.SuppressThisLine() + + def HandleProgressMessage2(self): + if self.Renderer != "vray" and self.Renderer != "vrayexport": + self.FinishedFrameCount += 1 + + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + if endFrame - startFrame + 1 != 0: + self.deadlinePlugin.SetProgress( + (self.FinishedFrameCount * 100) + / (endFrame - startFrame + 1) + ) + + def HandleProgressMessage3(self): + self.deadlinePlugin.SetProgress(float(self.GetRegexMatch(1))) + # self.SuppressThisLine() + + def HandleVrayMessage(self): + progressStatus = None + errorMessage = self.GetRegexMatch(0) + if errorMessage.find("V-Ray: Building light cache") != -1: + progressStatus = "Building light cache." + elif errorMessage.find("V-Ray: Building global photon map") != -1: + progressStatus = self.GetRegexMatch(0) + elif errorMessage.find("V-Ray: Prepass") != -1: + progressStatus = self.GetRegexMatch(0) + elif errorMessage.find("V-Ray: Rendering image") != -1: + progressStatus = self.GetRegexMatch(0) + self.vrayRenderingImage = True + + if progressStatus is not None: + self.deadlinePlugin.SetStatusMessage(progressStatus) + + def HandleVrayProgress(self): + if self.vrayRenderingImage == True: + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + if endFrame - startFrame + 1 > 0: + self.deadlinePlugin.SetProgress( + ( + float(self.GetRegexMatch(1)) + + float(self.FinishedFrameCount) * 100 + ) + / ( + (endFrame - startFrame + 1) + * self.CountRenderableCameras + ) + ) + + def HandleVrayFrameComplete(self): + if self.vrayRenderingImage == True: + self.FinishedFrameCount += 1 + self.vrayRenderingImage = False + + def HandleVrayExportProgress(self): + if self.Renderer == "vrayexport": + self.FinishedFrameCount += 1 + + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + if endFrame - startFrame + 1 != 0: + self.deadlinePlugin.SetProgress( + ((self.FinishedFrameCount - 1) * 100) + / (endFrame - startFrame + 1) + ) + + def HandleVrayExportComplete(self): + if self.Renderer == "vrayexport": + self.deadlinePlugin.SetProgress(100) + + def HandleIRayEndFrame(self): + self.FinishedFrameCount += 1 + + def HandleIRayProgressMessage(self): + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + maxSamples = self.deadlinePlugin.GetIntegerPluginInfoEntryWithDefault( + "IRayMaxSamples", 1024 + ) + + if endFrame - startFrame + 1 != 0: + framePercentage = float(self.GetRegexMatch(1)) / float(maxSamples) + + self.deadlinePlugin.SetProgress( + ((framePercentage + (self.FinishedFrameCount)) * 100) + / (endFrame - startFrame + 1) + ) + + def HandleRendermanProgress(self): + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + totalFrames = endFrame - startFrame + 1 + + if totalFrames > 1: + currentFrame = float(self.GetRegexMatch(1)) + if self.initialFrame is None: + self.initialFrame = currentFrame + + normalizedFrame = currentFrame + if self.initialFrame > 1: + normalizedFrame = currentFrame - startFrame + 1 + + self.deadlinePlugin.SetProgress( + normalizedFrame * 100.0 / totalFrames + ) + + self.deadlinePlugin.SetStatusMessage(self.GetRegexMatch(0)) + + def HandleOctaneStartFrame(self): + self.FinishedFrameCount += 1 + + def HandleOctaneProgress(self): + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + if endFrame - startFrame + 1 != 0: + currSamples = float(self.GetRegexMatch(1)) + maxSampes = float(self.GetRegexMatch(2)) + self.deadlinePlugin.SetProgress( + ( + ((currSamples * 100.0) / maxSampes) + + (self.FinishedFrameCount - 1) * 100 + ) + / (endFrame - startFrame + 1) + ) + + self.deadlinePlugin.SetStatusMessage(self.GetRegexMatch(0)) + + def HandleCausticVisualizerCurrentFrame(self): + self.CausticCurrentFrame = int(self.GetRegexMatch(1)) + + def HandleCausticVisualizerTotalPasses(self): + self.CausticTotalPasses = int(self.GetRegexMatch(1)) + + def HandleCausticVisualizerCurrentPass(self): + if self.CausticTotalPasses > 0: + totalFrameCount = ( + self.deadlinePlugin.GetEndFrame() + - self.deadlinePlugin.GetStartFrame() + + 1 + ) + if totalFrameCount != 0: + causticCurrentPass = int(self.GetRegexMatch(1)) + currentFrameCount = ( + self.CausticCurrentFrame + - self.deadlinePlugin.GetStartFrame() + ) + self.deadlinePlugin.SetProgress( + ( + ((causticCurrentPass * 100) / self.CausticTotalPasses) + + (currentFrameCount * 100) + ) + / totalFrameCount + ) + + ######################################################################## + ## Mental Ray progress handling. + ######################################################################## + def HandleMentalRayProgress(self): + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + if endFrame - startFrame + 1 != 0: + self.deadlinePlugin.SetProgress( + (float(self.GetRegexMatch(1)) + self.FinishedFrameCount * 100) + / (endFrame - startFrame + 1) + ) + self.deadlinePlugin.SetStatusMessage(self.GetRegexMatch(0)) + # self.SuppressThisLine() + + def HandleMentalRayComplete(self): + if self.SkipNextFrame: + self.SkipNextFrame = False + else: + self.FinishedFrameCount += 1 + startFrame = self.deadlinePlugin.GetStartFrame() + endFrame = self.deadlinePlugin.GetEndFrame() + if endFrame - startFrame + 1 != 0: + self.deadlinePlugin.SetProgress( + (self.FinishedFrameCount * 100) + / (endFrame - startFrame + 1) + ) + + def HandleMentalRayGathering(self): + self.deadlinePlugin.SetStatusMessage(self.GetRegexMatch(0)) + # self.SuppressThisLine() + + def HandleMentalRayWritingFrame(self): + currFinishedFrame = self.GetRegexMatch(1) + if self.PreviousFinishedFrame == currFinishedFrame: + self.SkipNextFrame = True + else: + self.PreviousFinishedFrame = currFinishedFrame + + def HandleFumeFXProgress(self): + if re.match(r"FumeFX: Starting simulation ", self.GetRegexMatch(0)): + self.FumeFXStartFrame = self.GetRegexMatch(1) + self.FumeFXEndFrame = self.GetRegexMatch(2) + elif re.match(r"FumeFX: Frame: ", self.GetRegexMatch(0)): + self.FumeFXCurrFrame = self.GetRegexMatch(1) + denominator = ( + float(self.FumeFXEndFrame) - float(self.FumeFXStartFrame) + 1.0 + ) + progress = ( + ( + float(self.FumeFXCurrFrame) + - float(self.FumeFXStartFrame) + + 1.0 + ) + / denominator + ) * 100.0 + self.deadlinePlugin.SetProgress(progress) + msg = ( + "FumeFX: (" + + str(self.FumeFXCurrFrame) + + " to " + + str(self.FumeFXEndFrame) + + ") - Mem: " + + str(self.FumeFXMemUsed) + + " - LastTime: " + + str(self.FumeFXFrameTime) + + " - ETA: " + + str(self.FumeFXEstTime) + ) + self.deadlinePlugin.SetStatusMessage(msg) + elif re.match(r"FumeFX: Memory used:", self.GetRegexMatch(0)): + self.FumeFXMemUsed = self.GetRegexMatch(1) + elif re.match(r"FumeFX: Frame Time:", self.GetRegexMatch(0)): + self.FumeFXFrameTime = self.GetRegexMatch(1) + elif re.match(r"FumeFX: Estimated Time:", self.GetRegexMatch(0)): + self.FumeFXEstTime = self.GetRegexMatch(1) + else: + pass diff --git a/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.ico b/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.ico new file mode 100644 index 0000000000000000000000000000000000000000..70b2398730228cf3336731b39b75ba0a321610c6 GIT binary patch literal 110294 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 literal 0 HcmV?d00001 diff --git a/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.options b/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.options new file mode 100644 index 0000000000..dcfc0c6496 --- /dev/null +++ b/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.options @@ -0,0 +1,35 @@ +[OIIOToolPath] +Type=filename +Label=OIIO Tool location +Category=OIIO +Index=0 +Description=OIIO Tool executable to use. +Required=false +DisableIfBlank=true + +[OutputFile] +Type=filenamesave +Label=Output File +Category=Output +Index=0 +Description=The scene filename as it exists on the network +Required=false +DisableIfBlank=true + +[CleanupTiles] +Type=boolean +Category=Options +Index=0 +Label=Cleanup Tiles +Required=false +DisableIfBlank=true +Description=If enabled, the Pype Tile Assembler will cleanup all tiles after assembly. + +[Renderer] +Type=string +Label=Renderer +Category=Quicktime Info +Index=0 +Description=Renderer name +Required=false +DisableIfBlank=true diff --git a/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.param b/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.param new file mode 100644 index 0000000000..5312b8c14c --- /dev/null +++ b/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.param @@ -0,0 +1,17 @@ +[About] +Type=label +Label=About +Category=About Plugin +CategoryOrder=-1 +Index=0 +Default=Pype Tile Assembler Plugin for Deadline +Description=Not configurable + +[OIIOTool_RenderExecutable] +Type=multilinemultifilename +Label=OIIO Tool Executable +Category=Render Executables +CategoryOrder=0 +Default=C:\Program Files\OIIO\bin\oiiotool.exe;/usr/bin/oiiotool +Description=The path to the Open Image IO Tool executable file used for rendering. Enter alternative paths on separate lines. +W diff --git a/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.py b/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.py new file mode 100644 index 0000000000..dddc379273 --- /dev/null +++ b/vendor/deadline/custom/plugins/PypeTileAssembler/PypeTileAssembler.py @@ -0,0 +1,372 @@ +# -*- coding: utf-8 -*- +"""Tile Assembler Plugin using Open Image IO tool. + +Todo: + Currently we support only EXRs with their data window set. +""" +import os +import subprocess +from xml.dom import minidom + +from System.IO import Path + +from Deadline.Plugins import DeadlinePlugin +from Deadline.Scripting import ( + FileUtils, RepositoryUtils, SystemUtils) + + +INT_KEYS = { + "x", "y", "height", "width", "full_x", "full_y", + "full_width", "full_height", "full_depth", "full_z", + "tile_width", "tile_height", "tile_depth", "deep", "depth", + "nchannels", "z_channel", "alpha_channel", "subimages" +} +LIST_KEYS = { + "channelnames" +} + + +def GetDeadlinePlugin(): # noqa: N802 + """Helper.""" + return PypeTileAssembler() + + +def CleanupDeadlinePlugin(deadlinePlugin): # noqa: N802, N803 + """Helper.""" + deadlinePlugin.cleanup() + + +class PypeTileAssembler(DeadlinePlugin): + """Deadline plugin for assembling tiles using OIIO.""" + + def __init__(self): + """Init.""" + self.InitializeProcessCallback += self.initialize_process + self.RenderExecutableCallback += self.render_executable + self.RenderArgumentCallback += self.render_argument + self.PreRenderTasksCallback += self.pre_render_tasks + self.PostRenderTasksCallback += self.post_render_tasks + + def cleanup(self): + """Cleanup function.""" + for stdoutHandler in self.StdoutHandlers: + del stdoutHandler.HandleCallback + + del self.InitializeProcessCallback + del self.RenderExecutableCallback + del self.RenderArgumentCallback + del self.PreRenderTasksCallback + del self.PostRenderTasksCallback + + def initialize_process(self): + """Initialization.""" + self.SingleFramesOnly = True + self.StdoutHandling = True + self.renderer = self.GetPluginInfoEntryWithDefault( + "Renderer", "undefined") + self.AddStdoutHandlerCallback( + ".*Error.*").HandleCallback += self.handle_stdout_error + + def render_executable(self): + """Get render executable name. + + Get paths from plugin configuration, find executable and return it. + + Returns: + (str): Render executable. + + """ + oiiotool_exe_list = self.GetConfigEntry("OIIOTool_RenderExecutable") + oiiotool_exe = FileUtils.SearchFileList(oiiotool_exe_list) + + if oiiotool_exe == "": + self.FailRender(("No file found in the semicolon separated " + "list \"{}\". The path to the render executable " + "can be configured from the Plugin Configuration " + "in the Deadline Monitor.").format( + oiiotool_exe_list)) + + return oiiotool_exe + + def render_argument(self): + """Generate command line arguments for render executable. + + Returns: + (str): arguments to add to render executable. + + """ + # Read tile config file. This file is in compatible format with + # Draft Tile Assembler + data = {} + with open(self.config_file, "rU") as f: + for text in f: + # Parsing key-value pair and removing white-space + # around the entries + info = [x.strip() for x in text.split("=", 1)] + + if len(info) > 1: + try: + data[str(info[0])] = info[1] + except Exception as e: + # should never be called + self.FailRender( + "Cannot parse config file: {}".format(e)) + + # Get output file. We support only EXRs now. + output_file = data["ImageFileName"] + output_file = RepositoryUtils.CheckPathMapping(output_file) + output_file = self.process_path(output_file) + """ + _, ext = os.path.splitext(output_file) + if "exr" not in ext: + self.FailRender( + "[{}] Only EXR format is supported for now.".format(ext)) + """ + tile_info = [] + for tile in range(int(data["TileCount"])): + tile_info.append({ + "filepath": data["Tile{}".format(tile)], + "pos_x": int(data["Tile{}X".format(tile)]), + "pos_y": int(data["Tile{}Y".format(tile)]), + "height": int(data["Tile{}Height".format(tile)]), + "width": int(data["Tile{}Width".format(tile)]) + }) + + # FFMpeg doesn't support tile coordinates at the moment. + # arguments = self.tile_completer_ffmpeg_args( + # int(data["ImageWidth"]), int(data["ImageHeight"]), + # tile_info, output_file) + + arguments = self.tile_oiio_args( + int(data["ImageWidth"]), int(data["ImageHeight"]), + tile_info, output_file) + self.LogInfo( + "Using arguments: {}".format(" ".join(arguments))) + self.tiles = tile_info + return " ".join(arguments) + + def process_path(self, filepath): + """Handle slashes in file paths.""" + if SystemUtils.IsRunningOnWindows(): + filepath = filepath.replace("/", "\\") + if filepath.startswith("\\") and not filepath.startswith("\\\\"): + filepath = "\\" + filepath + else: + filepath = filepath.replace("\\", "/") + return filepath + + def pre_render_tasks(self): + """Load config file and do remapping.""" + self.LogInfo("Pype Tile Assembler starting...") + scene_filename = self.GetDataFilename() + + temp_scene_directory = self.CreateTempDirectory( + "thread" + str(self.GetThreadNumber())) + temp_scene_filename = Path.GetFileName(scene_filename) + self.config_file = Path.Combine( + temp_scene_directory, temp_scene_filename) + + if SystemUtils.IsRunningOnWindows(): + RepositoryUtils.CheckPathMappingInFileAndReplaceSeparator( + scene_filename, self.config_file, "/", "\\") + else: + RepositoryUtils.CheckPathMappingInFileAndReplaceSeparator( + scene_filename, self.config_file, "\\", "/") + os.chmod(self.config_file, os.stat(self.config_file).st_mode) + + def post_render_tasks(self): + """Cleanup tiles if required.""" + if self.GetBooleanPluginInfoEntryWithDefault("CleanupTiles", False): + self.LogInfo("Cleaning up Tiles...") + for tile in self.tiles: + try: + self.LogInfo("Deleting: {}".format(tile["filepath"])) + os.remove(tile["filepath"]) + # By this time we would have errored out + # if error on missing was enabled + except KeyError: + pass + except OSError: + self.LogInfo("Failed to delete: {}".format( + tile["filepath"])) + pass + + self.LogInfo("Pype Tile Assembler Job finished.") + + def handle_stdout_error(self): + """Handle errors in stdout.""" + self.FailRender(self.GetRegexMatch(0)) + + def tile_oiio_args( + self, output_width, output_height, tile_info, output_path): + """Generate oiio tool arguments for tile assembly. + + Args: + output_width (int): Width of output image. + output_height (int): Height of output image. + tiles_info (list): List of tile items, each item must be + dictionary with `filepath`, `pos_x` and `pos_y` keys + representing path to file and x, y coordinates on output + image where top-left point of tile item should start. + output_path (str): Path to file where should be output stored. + + Returns: + (list): oiio tools arguments. + + """ + args = [] + + # Create new image with output resolution, and with same type and + # channels as input + first_tile_path = tile_info[0]["filepath"] + first_tile_info = self.info_about_input(first_tile_path) + create_arg_template = "--create{} {}x{} {}" + + image_type = "" + image_format = first_tile_info.get("format") + if image_format: + image_type = ":type={}".format(image_format) + + create_arg = create_arg_template.format( + image_type, output_width, + output_height, first_tile_info["nchannels"] + ) + args.append(create_arg) + + for tile in tile_info: + path = tile["filepath"] + pos_x = tile["pos_x"] + tile_height = self.info_about_input(path)["height"] + if self.renderer == "vray": + pos_y = tile["pos_y"] + else: + pos_y = output_height - tile["pos_y"] - tile_height + + # Add input path and make sure inputs origin is 0, 0 + args.append(path) + args.append("--origin +0+0") + # Swap to have input as foreground + args.append("--swap") + # Paste foreground to background + args.append("--paste +{}+{}".format(pos_x, pos_y)) + + args.append("-o") + args.append(output_path) + + return args + + def tile_completer_ffmpeg_args( + self, output_width, output_height, tiles_info, output_path): + """Generate ffmpeg arguments for tile assembly. + + Expected inputs are tiled images. + + Args: + output_width (int): Width of output image. + output_height (int): Height of output image. + tiles_info (list): List of tile items, each item must be + dictionary with `filepath`, `pos_x` and `pos_y` keys + representing path to file and x, y coordinates on output + image where top-left point of tile item should start. + output_path (str): Path to file where should be output stored. + + Returns: + (list): ffmpeg arguments. + + """ + previous_name = "base" + ffmpeg_args = [] + filter_complex_strs = [] + + filter_complex_strs.append("nullsrc=size={}x{}[{}]".format( + output_width, output_height, previous_name + )) + + new_tiles_info = {} + for idx, tile_info in enumerate(tiles_info): + # Add input and store input index + filepath = tile_info["filepath"] + ffmpeg_args.append("-i \"{}\"".format(filepath.replace("\\", "/"))) + + # Prepare initial filter complex arguments + index_name = "input{}".format(idx) + filter_complex_strs.append( + "[{}]setpts=PTS-STARTPTS[{}]".format(idx, index_name) + ) + tile_info["index"] = idx + new_tiles_info[index_name] = tile_info + + # Set frames to 1 + ffmpeg_args.append("-frames 1") + + # Concatenation filter complex arguments + global_index = 1 + total_index = len(new_tiles_info) + for index_name, tile_info in new_tiles_info.items(): + item_str = ( + "[{previous_name}][{index_name}]overlay={pos_x}:{pos_y}" + ).format( + previous_name=previous_name, + index_name=index_name, + pos_x=tile_info["pos_x"], + pos_y=tile_info["pos_y"] + ) + new_previous = "tmp{}".format(global_index) + if global_index != total_index: + item_str += "[{}]".format(new_previous) + filter_complex_strs.append(item_str) + previous_name = new_previous + global_index += 1 + + joined_parts = ";".join(filter_complex_strs) + filter_complex_str = "-filter_complex \"{}\"".format(joined_parts) + + ffmpeg_args.append(filter_complex_str) + ffmpeg_args.append("-y") + ffmpeg_args.append("\"{}\"".format(output_path)) + + return ffmpeg_args + + def info_about_input(self, input_path): + args = [self.render_executable(), "--info:format=xml", input_path] + popen = subprocess.Popen( + " ".join(args), + shell=True, + stdout=subprocess.PIPE + ) + popen_output = popen.communicate()[0].replace(b"\r\n", b"") + + xmldoc = minidom.parseString(popen_output) + image_spec = None + for main_child in xmldoc.childNodes: + if main_child.nodeName.lower() == "imagespec": + image_spec = main_child + break + + info = {} + if not image_spec: + return info + + def child_check(node): + if len(node.childNodes) != 1: + self.FailRender(( + "Implementation BUG. Node {} has more children than 1" + ).format(node.nodeName)) + + for child in image_spec.childNodes: + if child.nodeName in LIST_KEYS: + values = [] + for node in child.childNodes: + child_check(node) + values.append(node.childNodes[0].nodeValue) + + info[child.nodeName] = values + + elif child.nodeName in INT_KEYS: + child_check(child) + info[child.nodeName] = int(child.childNodes[0].nodeValue) + + else: + child_check(child) + info[child.nodeName] = child.childNodes[0].nodeValue + return info diff --git a/vendor/deadline/readme.md b/vendor/deadline/readme.md new file mode 100644 index 0000000000..1c79c89b28 --- /dev/null +++ b/vendor/deadline/readme.md @@ -0,0 +1,3 @@ +## Pype Deadline repository overlay + + This directory is overlay for Deadline repository. It means that you can copy whole hierarchy to Deadline repository and it should work.