Differences

This shows you the differences between two versions of the page.

Link to this comparison view

muster:8.0:template_engine_python [2018/01/11 08:21] (current)
Line 1: Line 1:
 +====== Template engine (Python) ======
  
 +===== Introduction =====
 +
 +Muster 8 implements a brand new template engine. The template engine takes care of instruct Muster how to interact with external render engines or batch jobs. The new engine is entirely based on Python 3.3. Through the template engine you can:
 +
 +  * Configure external applications
 +  * Build command lines
 +  * Create submission dialogs
 +  * Declare special actions
 +  * Create complex presets dialogs
 +  * Define the behaviours of clients
 +  * Create custom execution environments
 +
 +The template engine expect a .py file for each template that should create a basic class derived from the standard MTemplateAPI.MTemplate class configured with custom capabilities.
 +
 +A minimal template requires at least the following elements:
 +
 +  * A class that inherit the base class MTemplateAPI.MTemplate
 +  * Configuration of the class default values like the template ID, the template name, the template logic (multiframe,​ image slicing, broadcast or target host)
 +  * Configuration of the template logics default values
 +  * Configuration of the template fields that will be mapped directly to a job attributes and will appear in the submission dialog
 +  * Configuration of the supported platforms (Win / Mac / Linux) specific values
 +  * Redefinition of the requires virtual functions that spawn the processes, build the command lines and check for the results
 +
 +After creating the template class, it must be installed into the global template manager singleton using :
 +
 +<code python>
 +MTemplateAPI.MManager.getUniqueClass().installPyTemplate(myTemplateClass)
 +</​code>​
 +
 +Each section may contain special values, stepping inside Muster default templates could be a good starting point in learning the template Python classes. ​
 +
 +===== Building a template =====
 +
 +We’ll step across building a template for Maya rendering to learn more:
 +The first check you’ve to do when building a new template, is taking a look at the command line required by the engine, it our case, the basic syntax maya expect is:
 +
 +<​code>​
 +Render –r render_type –proj project_directory –sf start_frame –ef end_frame –bf by_frame path_to_scene
 +</​code>​
 +
 +Keeping that in mind, let’s build the template using the internal software rendering:
 +
 +<code python>
 +'''​ Import Muster base modules '''​
 +import MClientAPI
 +import MTemplateAPI
 +
 +'''​ Get the template manager singleton '''​
 +manager = MTemplateAPI.MManager.getUniqueClass()
 +
 +'''​ Define a new class derived from MTemplateAPI.MTemplate base class , override required methods '''​
 +class MayaTemplate(MTemplateAPI.MTemplate):​
 +    def __init__(self):​
 +        MTemplateAPI.MTemplate.__init__(self)
 + self.setID(1)
 + self.setName("​Maya Sw")
 + self.setDescription("​Maya software render"​)
 + self.setTemplateLogic(MTemplateAPI.kTemplateMultiframe)
 + self.setDefaultPriority(1)
 + '''​ Multiframe template specific settings '''​
 + self.multiframe.setEnableFrameCheck(1)
 +</​code>​
 +
 +As you can see, the first section declares the basic template parameters, giving a template name, description and unique identifier. The logic is set to Multiframe that requires a <code python>​self.multiframe.setEnableFrameCheck(1)</​code>​ if you want to enable the frame check feature. This syntax shows how to access specific logics features.
 +
 +The next section configures the submission dialog either for Console and the web server:
 +
 +<code python>
 +mayaFile = MTemplateAPI.MTemplateItemFile("​MAYAFILENAME","​Maya scene file name","​Specifies the maya scene file to render","",​0,​1,​1,"​*.ma;​*.mb"​)
 +mayaProject = MTemplateAPI.MTemplateItemFolder("​MAYAPROJECTPATH","​Project directory","​Specifies the maya project path","",​0,​1,​1)
 +mayaRenderPath = MTemplateAPI.MTemplateItemFolder("​MAYADESTINATION","​Frames destination","​Specifies the frames destination","",​1,​1,​1)
 +mayaStartFrame = MTemplateAPI.MTemplateItemDouble("​MAYASTARTFRAME","​Start frame","​Specifies the starting frame for the rendering job",​1.0,​0,​0,​1)
 +mayaEndFrame = MTemplateAPI.MTemplateItemDouble("​MAYAENDFRAME","​End frame","​Specifies the ending frame for the rendering job",​1.0,​0,​0,​1)
 +mayaByFrame = MTemplateAPI.MTemplateItemDouble("​MAYABYFRAME","​By frame","​Specifies the frame step for the rendering job",​1.0,​0,​0,​1)
 +mayaNumberBy = MTemplateAPI.MTemplateItemInteger("​MAYANUMBERBY","​Number by","​Specifies the start number for frame numbering",​1,​0,​0,​1)
 +mayaStepBy = MTemplateAPI.MTemplateItemInteger("​MAYASTEPBY","​Step by","​Specifies the stepping for the frame numbering",​1,​0,​0,​1)
 +mayaDigits = MTemplateAPI.MTemplateItemInteger("​MAYADIGITS","​Digits","​Specifies the number of digits for the frame numbering",​1,​0,​0,​1)
 +mayaAbortRender = MTemplateAPI.MTemplateItemYesNo("​ABORTRENDER","​Abort on log","​Immediately abort the rendering if an error string is found during log output",​0,​0,​0,​1)
 +self.addSubmissionItem(mayaFile)
 +self.addSubmissionItem(mayaProject)
 +self.addSubmissionItem(mayaRenderPath)
 +self.addSubmissionItem(mayaStartFrame)
 +self.addSubmissionItem(mayaEndFrame)
 +self.addSubmissionItem(mayaByFrame)
 +self.addSubmissionItem(mayaNumberBy)
 +self.addSubmissionItem(mayaStepBy)
 +self.addSubmissionItem(mayaDigits)
 +self.addSubmissionItem(mayaAbortRender)
 +'''​ items mapping to Muster default tags '''​
 +self.addMapping("​MAYASTARTFRAME","​start_frame"​)
 +self.addMapping("​MAYAENDFRAME","​end_frame"​)
 +self.addMapping("​MAYABYFRAME","​by_frame"​)
 +self.addMapping("​MAYANUMBERBY","​number_by"​)
 +self.addMapping("​MAYASTEPBY","​step_by"​)
 +self.addMapping("​MAYAFILENAME","​job_file"​)
 +self.addMapping("​MAYAPROJECTPATH","​job_project"​)
 +self.addMapping("​MAYARENDERPATH","​output_folder"​)
 +</​code>​
 +
 +The previous code builds a set of fields object that are installed into the template using the **addSubmissionItem()** function call. Each object is a subclass of the MTemplateItem base class.
 +
 +The **addMapping()** function let you specify which field maps to the required Muster internal fields like the start frame, end frame and so on..
 +
 +<code python>
 +'''​ Windows support '''​
 +self.platformWindows.setPlatformEnabled(1)
 +self.platformWindows.setEnableErrorCheck(1)
 +self.platformWindows.setEnabledByDefault(1)
 +self.platformWindows.setFramesFloating(3)
 +self.platformWindows.setDetectionLogic(MTemplateAPI.kProcessChild)
 +self.platformWindows.setDetectionLogicProcessName("​Mayabatch.exe"​)
 +applicationPath = MTemplateAPI.MTemplateItemFile("​MAYAEXECUTABLE","​Maya batch render executable","​Path to Maya render.exe application batch render","​c:​\\program files\\Autodesk\\Maya 2010\\bin\\render.exe",​0,​0,​1,"​*.exe"​)
 +self.platformWindows.addClientConfigurationItem(applicationPath)
 +startingFolderPath = MTemplateAPI.MTemplateItemFolder("​MAYASTARTINGFOLDER","​Starting folder","​Specify the starting folder for the render process","​c:​\\program files\\Autodesk\\Maya 2010\\bin",​0,​0,​1)
 +self.platformWindows.addClientConfigurationItem(startingFolderPath)
 +processorUsage = MTemplateAPI.MTemplateItemCombo("​PROCUSAGE","​Processors usage","​Specify the number of processors to use for each render instance","​0",​0,​0,​1)
 +processorUsage.addItem("​All","​0"​)
 +processorUsage.addItem("​1","​1"​)
 +processorUsage.addItem("​2","​2"​)
 +processorUsage.addItem("​3","​3"​)
 +processorUsage.addItem("​4","​4"​)
 +self.platformWindows.addClientConfigurationItem(processorUsage)
 +</​code>​
 +
 +The previous code sample shows the engine declaration section for the Windows platform. ​
 +It not all sets platform specific attributes, but it also install a set of fields inside the platform itself. Those fields will be available into the client configuration dialog and can be accessed later to construct the command line as well as to provide additional behaviours.
 +
 +<code python>
 +def onBuildCommandLine(self,​ platform, job, chunk, clientTemplatePreferences,​instanceNum):​
 + renderCmd = "-r sw"
 + renderCmd += " -n " + clientTemplatePreferences['​PROCUSAGE'​]
 + renderCmd += " -proj \""​ + job.attributeGetString(manager.resolveMappingToJob(self.getID(),"​MAYAPROJECTPATH"​)) + "​\""​
 + if job.attributeIsEnabled(manager.resolveMappingToJob(self.getID(),"​MAYADESTINATION"​)):​
 + renderCmd += " -rd \""​ + job.attributeGetString(manager.resolveMappingToJob(self.getID(),"​MAYADESTINATION"​)) + "​\""​
 + renderCmd += " -s " + '​%.3f'​ % chunk.getStartFrame()
 + renderCmd += " -e " + '​%.3f'​ % chunk.getEndFrame()
 + renderCmd += " -b " + '​%.3f'​ % chunk.getByFrame()
 + renderCmd += " -rfs " + str(chunk.getFrameStart())
 + renderCmd += " -rfb " + str(chunk.getFrameStep())
 + renderCmd += " -pad " + job.attributeGetString("​MAYADIGITS"​)
 + renderCmd += " " + job.attributeGetString("​ADD_FLAGS"​)
 + renderCmd += " \""​ + job.attributeGetString(manager.resolveMappingToJob(self.getID(),"​MAYAFILENAME"​)) + "​\""​
 + return renderCmd
 +</​code>​
 +
 +The previous code sample shows how to override a class virtual method to provide a specific function. In this case, we need to tell Muster how to build the final command line.
 +
 +<code python>
 +def onGetApplicationPath(self,​job,​clientTemplatePreferences,​pathOut ):
 + pathOut.setString(clientTemplatePreferences['​MAYAEXECUTABLE'​])
 + return MTemplateAPI.MTemplateError()
 +
 +def onGetApplicationStartingFolder(self,​job,​clientTemplatePreferences,​pathOut):​
 + pathOut.setString(clientTemplatePreferences['​MAYASTARTINGFOLDER'​])
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +
 +In the previous code, we tell Muster how to find the command line and the starting folder for the batch job. In that case, we grab the values from the client configuration values using the clientTemplatePreferences supplied dictionary.
 +
 +<code python>
 +def onCheckLog(self,​job,​chunk,​clientTemplatePreferences,​log):​
 + if log.find("​Warning"​) != -1:
 + return MTemplateAPI.MTemplateError(2,"​Warning keyword found",​MTemplateAPI.MTemplateError.kTemplateErrorTypeWarning)
 + if log.find("​Error"​) != -1:
 + return MTemplateAPI.MTemplateError(1,"​Error keyword found",​MTemplateAPI.MTemplateError.kTemplateErrorTypeError)
 + if log.find("​Total Time For Render"​) == -1:
 + return MTemplateAPI.MTemplateError(3,"​Missing \"​Total Time For Render\"​ keyword. Batch render may have crashed",​MTemplateAPI.MTemplateError.kTemplateErrorTypeWarning)
 + if log.find("​Total Elapsed Time For Maya") == -1:
 + return MTemplateAPI.MTemplateError(3,"​Missing \"​Total Elapsed Time For Maya\" keyword. Batch render may have crashed",​MTemplateAPI.MTemplateError.kTemplateErrorTypeWarning)
 + if log.find("​completed."​) == -1:
 + return MTemplateAPI.MTemplateError(3,"​Missing \"​completed.\"​ keyword. Batch render may have crashed",​MTemplateAPI.MTemplateError.kTemplateErrorTypeWarning)
 + return MTemplateAPI.MTemplateError()
 +
 +def onCheckExitCode(self,​job,​chunk,​clientTemplatePreferences,​exitCode):​
 + if exitCode == 211:
 + return MTemplateAPI.MTemplateError(exitCode,"​Abnormal render termination",​MTemplateAPI.MTemplateError.kTemplateErrorTypeError)
 + if exitCode != 0:
 + return MTemplateAPI.MTemplateError(exitCode,​ "%d Exit code different from expected 0 value" % exitCode,​MTemplateAPI.MTemplateError.kTemplateErrorTypeWarning)
 +
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +
 +The previous virtual functions tell Muster how to process the batch job output log as well as the process exit code.
 +
 +<code python>
 +'''​ Create an instance of the template class and install it into the template manager context '''​
 +template = MayaTemplate()
 +manager.installPyTemplate(template)
 +</​code>​
 +
 +The last code creates an instance of the class and installs it into the template manager. ​
 +
 +Further information on the template syntax can be found looking into the API classes reference as well as into the existing templates.
 +
 +===== Class overrides descriptions =====
 +
 +Any template is able to override Muster default behaviours by redefining one or more virtual functions of the base class. This is a list of the available functions and their meaning:
 +
 +^Function name and parameters^Expected return variable^Description^
 +|onFieldChanged(string fieldName,​string fieldValue, ref MTemplateItemsFields fieldsToChange)|void|Invoked by Console when a template field value changes, useful to automate fills of different fields and/or invoke actions like scene parsing after the file selection|
 +|++example|
 +<code python>
 +def onFieldChanged(self, ​ fieldName, fieldValue, fieldsToChange):​
 + if fieldName == "​MAYAFILENAME":​
 + basePath = os.path.dirname(fieldValue)
 + if basePath.endswith("​scenes"​) and len(basePath) > 6:
 + separator = "/";​
 + if fieldValue.find("​\\"​) != -1: 
 + separator = "​\\"​
 + projectPath = basePath[0:​len(basePath)-7]
 + fieldsToChange.addEvent("​MAYAPROJECTPATH",​projectPath,​MTemplateAPI.MTemplateItemEvent.kTemplateItemFieldsActivationNone)
 + fieldsToChange.addEvent("​MAYADESTINATION",​projectPath + separator + "​images",​MTemplateAPI.MTemplateItemEvent.kTemplateItemFieldsActivationNone)
 +</​code>​
 +The previous code sample is used inside the Maya template to automatically fill the projects and image output path when a user selects the Maya scene.
 +++|||
 +|onBuildCommandLine(int platform, MJob job, MChunk chunk,ref stringMap clientTemplatePreferences,​ int instanceNum)|string|Invoked by the client to build the process command line|
 +|++example|
 +<code python>
 +def onBuildCommandLine(self,​ platform, job, chunk, clientTemplatePreferences,​instanceNum):​
 + renderCmd = "-r sw"
 + renderCmd += " -n " + clientTemplatePreferences['​PROCUSAGE'​]
 + renderCmd += " -proj \""​ + job.attributeGetString(manager.resolveMappingToJob(self.getID(),"​MAYAPROJECTPATH"​)) + "​\""​
 + if job.attributeIsEnabled(manager.resolveMappingToJob(self.getID(),"​MAYADESTINATION"​)):​
 + renderCmd += " -rd \""​ + job.attributeGetString(manager.resolveMappingToJob(self.getID(),"​MAYADESTINATION"​)) + "​\""​
 + renderCmd += " -s " + '​%.3f'​ % chunk.getStartFrame()
 + renderCmd += " -e " + '​%.3f'​ % chunk.getEndFrame()
 + renderCmd += " -b " + '​%.3f'​ % chunk.getByFrame()
 + renderCmd += " -rfs " + str(chunk.getFrameStart())
 + renderCmd += " -rfb " + str(chunk.getFrameStep())
 + renderCmd += " -pad " + job.attributeGetString("​MAYADIGITS"​)
 + renderCmd += " " + job.attributeGetString("​ADD_FLAGS"​)
 + renderCmd += " \""​ + job.attributeGetString(manager.resolveMappingToJob(self.getID(),"​MAYAFILENAME"​)) + "​\""​
 + return renderCmd
 +</​code>​
 +The previous code sample is used inside the Maya template to create the Mayabatch command line.
 +++|||
 +|onBuildAdditionalParameters(ref MPropertiesMap attributes)|string|Invoked by Console to build additional parameters for the command line|
 +|++example|
 +<code python>
 +def onBuildAdditionalParameters(self,​ attributes):​
 + renderCmd = ""​
 + if (attributes.isEnabled("​MM"​)):​
 + renderCmd += "-mm " + attributes.getString("​MM"​) + " "
 + return renderCmd
 +</​code>​
 +The previous code sample is used inside the Maya template to create additional parameters from the attributes previously declared.
 +++|||
 +|onGetApplicationPath(MJob job,ref stringMap clientTemplatePreferences,​ ref MStringRef pathOut )|MTemplateError |Invoked by the client to get the process path|
 +|++example|
 +<code python>
 +def onGetApplicationPath(self,​job,​clientTemplatePreferences,​pathOut ):
 + pathOut.setString(clientTemplatePreferences['​MAYAEXECUTABLE'​])
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +The previous code sets the Maya batch process grabbing the value from the MAYAEXECUTABLE field of the client preferences.
 +++|||
 +|onGetApplicationStartingFolder(MJob job,ref stringMap clientTemplatePreferences,​ ref MStringRef pathOut)| MTemplateError|Invoked by the client to get the process execution folder|
 +|++example|
 +<code python>
 +def onGetApplicationStartingFolder(self,​job,​clientTemplatePreferences,​pathOut):​
 + pathOut.setString(clientTemplatePreferences['​MAYASTARTINGFOLDER'​])
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +The previous code sets the Maya starting folder grabbing the value from the MAYASTARTINGFOLDER field of the client preferences.
 +++|||
 +|onBuildEnvironment(MJob job, MChunk chunk,ref stringMap clientTemplatePreferences,​ ref MEnvironment existingEnvironment)|MEnvironment|Invoked by the client to build an environment block for the process|
 +|++example|
 +<code python>
 +def onBuildEnvironment(self,​ job, chunk, clientTemplatePreferences,​ existingEnvironment):​
 + environment = existingEnvironment
 + platform = MClientAPI.GetPlatform()
 + if platform == MClientAPI.kPlatformOSX:​
 + environment.setValue("​DYLD_LIBRARY_PATH",""​)
 + elif platform == MClientAPI.kPlatformLinux:​
 + environment.setValue("​LD_LIBRARY_PATH",""​)
 + return environment
 +</​code>​
 +The previous code sample is used inside the Maya template. It grabs the running platform, and resets the library search paths to avoid clashing with the Muster QT distribution by setting new environmental variables.
 +++|||
 +|onCheckForSubframeAdvancingString(string line)|bool|Invoked by the client on each log line parsing to check for a frame completion string|
 +|++example|
 +<code python>
 +def onCheckForSubframeAdvancingString(self,​line):​
 + result = re.search("​.*(Finished Rendering).*",​ line)
 + if result != None:
 + return 1
 + return 0
 +</​code>​
 +The previous code sample is used inside the Maya template to check for the "​Finished rendering"​ log line to tell Muster that a frame has been completed.
 +++|||
 +|onDetectRunningProcess(MPid mainProcess,​ vector of MProcessSnapshot hostProcesses,​ ref stringMap clientTemplatePreferences,​ref MPid runningProcessPid)|kTemplateFunctionBehaviour (int constant)|Invoked by the client to detect the process effective running PID|
 +|onCheckForFramesPrefixString(string line, ref MStringRef prefixOut)|bool|Invoked by the client on each log line parsing to guess the frames path|
 +|++example|
 +<code python>
 +def onCheckForFramesPrefixString(self,​line,​ prefixOut):
 + result = re.search("​.*Finished Rendering\\s(.*\\.)(\\d+)\\.",​ line)
 + if result != None:
 + prefixOut.setString(result.group(1))
 + return 1
 + return 0
 +</​code>​
 +The previous code uses a regular expression to grab the frames prefix from the Maya log and report it back to Muster. Notice the usage of the MStringRef class to set back the value into the prefixOut variable.
 +++|||
 +|onCheckForSubframeProgress(string line, ref MStringRef progressOut)|bool|Invoked by the client on each log line parsing to guess the percentage of the current frames|
 +|++example|
 +<code python>
 +
 +</​code>​
 +The previous code sample is used inside the Maya template to create additional parameters from the attributes previously declared.
 +++|||
 +|onCheckLogLine(MMJob job,MChunk chunk,ref stringMap clientTemplatePreferences,​ string line)|MTemplateError|Invoked by the client for each log line, this is useful to abort the current render on certain log errors|
 +|++example|
 +<code python>
 +def onCheckLogLine(self,​job,​chunk,​clientTemplatePreferences,​line):​
 + abortRendering = job.attributeGetBool("​ABORTRENDER"​)
 + if abortRendering and line.find("​Error"​) != -1 :
 + error = MTemplateAPI.MTemplateError(1,"​Premature render termination. Error keyword found in the log",​MTemplateAPI.MTemplateError.kTemplateErrorTypeError)
 + error.addErrorAction(MTemplateAPI.MTemplateError.kTemplateErrorAbortRender);​
 + return error
 +
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +The previous code checks the Maya output line by line while rendering. If the ABORTRENDER field is enabled, it checks for an "​Error"​ string in the output, and aborts the render before its termination by setting an MTemplateError object according.
 +++|||
 +|onCheckLog(MJob job, MChunk chunk,ref stringMap clientTemplatePreferences,​ string log)|MTemplateError|Invoked by the client to check the full log at the end of the render|
 +|++example|
 +<code python>
 +def onCheckLog(self,​job,​chunk,​clientTemplatePreferences,​log):​
 + if log.find("​Warning"​) != -1:
 + return MTemplateAPI.MTemplateError(2,"​Warning keyword found",​MTemplateAPI.MTemplateError.kTemplateErrorTypeWarning)
 + if log.find("​Error"​) != -1:
 + return MTemplateAPI.MTemplateError(1,"​Error keyword found",​MTemplateAPI.MTemplateError.kTemplateErrorTypeError)
 + if log.find("​Total Time For Render"​) == -1:
 + return MTemplateAPI.MTemplateError(3,"​Missing \"​Total Time For Render\"​ keyword. Batch render may have crashed",​MTemplateAPI.MTemplateError.kTemplateErrorTypeWarning)
 + if log.find("​Total Elapsed Time For Maya") == -1:
 + return MTemplateAPI.MTemplateError(3,"​Missing \"​Total Elapsed Time For Maya\" keyword. Batch render may have crashed",​MTemplateAPI.MTemplateError.kTemplateErrorTypeWarning)
 + if log.find("​completed."​) == -1:
 + return MTemplateAPI.MTemplateError(3,"​Missing \"​completed.\"​ keyword. Batch render may have crashed",​MTemplateAPI.MTemplateError.kTemplateErrorTypeWarning)
 +
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +The previous code sample is used inside the Maya template to parse the output log and check for specific strings. It sets the MTemplateError object according.
 +++|||
 +|onCheckExitCode(MJob job,MChunk chunk,ref stringMap clientTemplatePreferences,​ int exitCode)|MTemplateError|Invoked by the client to check the process exit code|
 +|++example|
 +<code python>
 +def onCheckExitCode(self,​job,​chunk,​clientTemplatePreferences,​exitCode):​
 + if exitCode == 211:
 + return MTemplateAPI.MTemplateError(exitCode,"​Abnormal render termination",​MTemplateAPI.MTemplateError.kTemplateErrorTypeError)
 + if exitCode != 0:
 + return MTemplateAPI.MTemplateError(exitCode,​ "%d Exit code different from expected 0 value" % exitCode,​MTemplateAPI.MTemplateError.kTemplateErrorTypeWarning)
 +
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +The previous code sample is used inside the Maya template to check the batch process exit code.
 +++|||
 +|onApplicationFinder(ref MStringRef moduleRegExp,​ ref MStringRef&​ moduleTag)|int|Invoked by the Services control panel applet to configure automatic application finding|
 +|++example|
 +<code python>
 +def onApplicationFinder(self,​moduleRegExp,​moduleTag):​
 + platform = MClientAPI.GetPlatform()
 + '''​ Search maya.exe '''​
 + if platform == MClientAPI.kPlatformWindows:​
 + moduleRegExp.setString("​mayabatch.exe"​)
 + elif platform == MClientAPI.kPlatformOSX:​
 + moduleRegExp.setString("​MayaBatch"​)
 + elif platform == MClientAPI.kPlatformLinux:​
 + moduleRegExp.setString("​maya"​)
 + moduleTag.setString("​Maya"​)
 + return MTemplateAPI.kTemplateScanModule
 +</​code>​
 +The previous code sample is used inside the Maya template to instruct the Services Control panel applet on how to search for Maya inside the filesystem. It returns the MTemplateAPI.kTemplateScanModule to tell Muster that it must search for a specific module inside the file system. Alternatively,​ it can report kTemplateScanNone to avoid application scanning, or kTemplateScanClass to use the template onFindApplication() function.
 +++|||
 +|onFindApplication(ref MStringRef basePath,​ref stringMap clientTemplatePreferences)|bool|Invoked by the Services control panel applet to find a specific application directly from the template|
 +|++example|
 +<code python>
 +def onFindApplication(self,​ basePath, clientTemplatePreferences):​
 + basePath.setString("/​usr/​local/​bin/​myrender"​)
 + return 1
 +</​code>​
 +The previous code sample directly sets the path to the render engine without performing a file system scan.
 +++|||
 +|onModuleFound(ref MStringRef moduleExec,​ref MStringRef modulePath,​ref stringMap clientTemplatePreferences);​| bool|Invoked by the Services control panel applet when an application executable is found|
 +|++example|
 +<code python>
 +def onModuleFound(self,​moduleExec,​modulePath,​clientTemplatePreferences):​
 + platform = MClientAPI.GetPlatform()
 + basePath = modulePath.getString()
 + if platform == MClientAPI.kPlatformWindows:​
 + clientTemplatePreferences["​MAYAEXECUTABLE"​] = basePath + "​\\Render.exe"​
 + elif platform == MClientAPI.kPlatformOSX:​
 + clientTemplatePreferences["​MAYAEXECUTABLE"​] = basePath + "/​Render"​
 + elif platform == MClientAPI.kPlatformLinux:​
 + clientTemplatePreferences["​MAYAEXECUTABLE"​] = basePath + "/​Render"​
 +
 + clientTemplatePreferences["​MAYASTARTINGFOLDER"​] = basePath
 + return 1
 +</​code>​
 +The previous code sample is used inside the Maya template to setup the client preferences with the values reported back by the application filesystem scan.
 +++|||
 +|onGetSlicesInputFilename(MJob job,MChunk chunk)|string|Invoked by image slicing jobs to know each chunk slice file path|
 +|++example|
 +<code python>
 +def onGetSlicesInputFilename(self,​ job, chunk):
 + imgFile = job.attributeGetString("​output_folder"​)
 + imgFile += os.sep + "​Muster_sf_"​ + '​%d'​ % job.getJobId() + "​_"​ + '​%d'​ % chunk.getChunkId() + "​."​ + job.attributeGetString("​source_file_format"​) + '​.%d'​ % int(job.attributeGetString("​start_frame"​)) ​
 + return imgFile
 +</​code>​
 +The previous code sample is used inside the Maya template to tell Muster the slices filenames and path.
 +++|||
 +|onGetSlicesOutputFilename(MJob job)|string|Invoked by image slicing jobs to know the final assembled image name|
 +|++example|
 +<code python>
 +def onGetSlicesOutputFilename(self,​ job):
 + imgFile = job.attributeGetString("​output_folder"​)
 + imgFile += os.sep + "​final"​ + "​_"​ + '​%d'​ % job.getJobId() + "​."​ + job.attributeGetString("​destination_file_format"​)
 + return imgFile
 +
 +</​code>​
 +The previous code sample is used inside the Maya template to tell Muster the output assembled image filename and path.
 +++|||
 +|onGetSliceBoundaries(MJob job,MChunk chunk,​MTemplateSliceBoundaries boundaries)|void|Invoked by image slicing jobs to know how to expect slices and how to assemble them|
 +|++example|
 +<code python>
 +def onGetSliceBoundaries(self,​ job, chunk, refBoundaries):​
 + final_x_res = int(job.attributeGetString("​x_res"​))
 + final_y_res = int(job.attributeGetString("​y_res"​))
 + as_overlap = int(job.attributeGetString("​as_overlap"​))
 + num_slices = job.getPacketSize()
 + sliceWidth = int(final_x_res / num_slices);​
 + sliceNumber = chunk.getChunkId()
 + sliceEffectiveWidth = sliceWidth + as_overlap
 + if (sliceNumber == num_slices):​
 + sliceEffectiveWidth = final_x_res - (sliceWidth * (sliceNumber - 1))
 +
 + refBoundaries.setSliceFrameResolutionX(sliceEffectiveWidth);​
 + refBoundaries.setSliceFrameResolutionY(final_y_res);​
 + refBoundaries.setSliceSamplesOriginX(0);​
 + refBoundaries.setSliceSamplesOriginY(0);​
 + refBoundaries.setSliceWidth(sliceWidth);​
 + refBoundaries.setSliceHeight(final_y_res);​
 + refBoundaries.setFinalPositionX(sliceWidth * (sliceNumber-1));​
 + refBoundaries.setFinalPositionY(0);​
 +</​code>​
 +The previous code sample is used inside the Maya template to tell Muster how to expect the slices.
 +++|||
 +|onStartProcess(MJob job, int instanceNum,​ MPid& runningProcessPid))|void|Invoked by the client when a new process should be started|
 +|++example|
 +<code python>
 +def onStartProcess(self,​ job, instanceNum,​ runningProcessPid):​
 +</​code>​
 +...
 +++|||
 +|onTerminateProcess(MJob job, MPid process, int instanceNum))|void|Invoked by the client when a running process should be terminated|
 +|++example|
 +<code python>
 +def onTerminateProcess(self,​ job, process, instanceNum):​
 +</​code>​
 +...
 +++|||
 +|onChangeProcessPriority(MJob job, MPid process, int priority, int instanceNum))|void|Invoked by the client when the system priority of a process should be changed|
 +|++example|
 +<code python>
 +def onChangeProcessPriority(self,​ job, process, priority instanceNum):​
 +</​code>​
 +...
 +++|||
 +|onCheckProcessTermination(MJob job, MPid process, int instanceNum))|void|Invoked by the client to check if the process has been completed|
 +|++example|
 +<code python>
 +def onCheckProcessTermination(self,​ job, priority instanceNum):​
 +</​code>​
 +...
 +++|||
 +
 +
 +The following functions are also called from the global template (ID 0):
 +
 +
 +^Function name and parameters^Expected return variable^Description^
 +|onValidateJobSubmission(MJob job, ref MStringRef err)|bool|Used to validate a job submission|
 +|++example|
 +<code python>
 +def onValidateJobSubmission(self,​ job, err):
 + return 1
 +</​code>​
 +The function is invoked when a job requires validation before the submission. This is fired from the Console and/or mrtool before a job submission.
 +++|||
 +|onDeleteJob(MJob job)|void|Invoked when a job is deleted|
 +|++example|
 +<code python>
 +def onDeleteJob(self,​job):​
 + pass
 +</​code>​
 +The function is invoked when a job is deleted. This is fired from the Dispatcher, be as fast as possible to avoid stalling the networking thread.
 +++|||
 +|onSubmitJob(MJob job)|void|Invoked when a job is submitted|
 +|++example|
 +<code python>
 +def onSubmitJob(self,​ job):
 + pass
 +</​code>​
 +The function is invoked when a job is submitted in the queue. This is fired from the Dispatcher, be as fast as possible to avoid stalling the networking thread.
 +++|||
 +|onReinitJob(MJob job)|void|Invoked when a job is reinit|
 +|++example|
 +<code python>
 +def onReinitJob(self,​job):​
 + pass
 +</​code>​
 +The function is invoked when a job is reset. This is fired from the Dispatcher, be as fast as possible to avoid stalling the networking thread.
 +++|||
 +|onEditJob(MJob job)|void|Invoked when a job is edited|
 +|++example|
 +<code python>
 +def onEditJob(self,​job):​
 + pass
 +</​code>​
 +The function is invoked when a job is edited. This is fired from the Dispatcher, be as fast as possible to avoid stalling the networking thread.
 +++|||
 +|onJobPropertiesChanged(MJob job)|void|Invoked when a property of a job is changed|
 +|++example|
 +<code python>
 +def onJobPropertiesChanged(self,​job):​
 + pass
 +</​code>​
 +The function is invoked when any of the job parameter is changed live. This is fired from the Dispatcher, be as fast as possible to avoid stalling the networking thread.
 +++|||
 +|onJobStart(MJob job)|MTemplateError|Invoked when the job is started|
 +|++example|
 +<code python>
 +def onJobStart(self,​job):​
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +The function is invoked when the job is started. This is fired from the Dispatcher in a new thread, you can take your time inside the function.
 +++|||
 +|onJobEnd(MJob job)|MTemplateError|Invoked when a job is completed|
 +|++example|
 +<code python>
 +def onJobEnd(self,​job):​
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +The function is invoked when the job ends. This is fired form the Dispatcher in a new thread, you can take your time inside the function.
 +++|||
 +|onChunkStart(MJob job,MChunk chunk, ref stringMap clientTemplatePreferences,​int instanceId)|MTemplateError|Invoked when a chunk is started|
 +|++example|
 +<code python>
 +def onChunkStart(self,​ job, chunk,​clientTemplatePreferences,​instanceNum):​
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +The function is invoked when a chunk is started. This is fired from the Render client, you should be as fast as possible to avoid a rendering start timeout.
 +++|||
 +|onChunkEnd(MJob job,MChunk chunk,ref stringMap clientTemplatePreferences,​ int instanceId)|MTemplateError|Invoked when a chunk is completed|
 +|++example|
 +<code python>
 +def onChunkEnd(self,​ job, chunk,​clientTemplatePreferences,​instanceNum):​
 + return MTemplateAPI.MTemplateError()
 +</​code>​
 +The function is invoked when a chunk is started. This is fired from the Render client, you should be as fast as possible to avoid stalling the client.
 +++|||