Deadline, Houdini, Octane, Pipeline, Python, Redshift, Scripting

Redshift & Octane on Deadline via Husk Prt.2


Please check out the first part: 

https://www.enoni.de/wp/karma-on-deadline-via-husk/

Read to the lines where the scripts enhancements at the Deadline repository starts.

These first steps are necessary.

Lets take your favourite renderer to USD land and skip the part of making *.rs or *.orbx archives and render standalone via Husk.

Here is a small update on specific Python scripts to trigger specific render delegates. The tricky part is, that we have to pipe the ENV VARS through Deadline, so Husk will find specific locations to load the right renderer and versions – especially you use custom starting prodcedures. Therefore i just read the HOUDINI_PACKAGE_DIR var and send this with the submission script at the current Houdini session. In case you dont use the Houdini package .json system you have the modify the scripts in other ways.

First step: edit the SubmitRenderJob_husk function :

*\DeadlineRepository10\submission\Houdini\Main\SubmitHoudiniToDeadlineFunctions.py

def SubmitRenderJob_husk( node, jobProperties, dependencies ):
    if jobProperties.get("usdjob"):
        if jobProperties.get("usdjob") == 1:
            assemblyJobIds = []
            jobName = jobProperties.get( "jobname", "Untitled" )
            jobName = "%s - %s"%(jobName, node.path())
            
            subInfo = json.loads( hou.getenv("Deadline_Submission_Info") )
            homeDir = subInfo["UserHomeDir"]

            jobInfoFile = os.path.join(homeDir, "temp", "houdini_submit_info.job")
            ## job file ##
            with open( jobInfoFile, "w" ) as fileHandle:
                fileHandle.write( "Plugin=HuskStandalone\n" )
                fileHandle.write( "Name=%s\n" % jobName )
                fileHandle.write( "Comment=%s\n" % jobProperties.get( "comment", "" ) )
                fileHandle.write( "Department=%s\n" % jobProperties.get( "department", "" ) )
                fileHandle.write( "Pool=%s\n" % jobProperties.get( "pool", "None" ) )
                fileHandle.write( "SecondaryPool=%s\n" % jobProperties.get( "secondarypool", "" ) )
                fileHandle.write( "Group=%s\n" % jobProperties.get( "group", "None" ) )
                fileHandle.write( "Priority=%s\n" % jobProperties.get( "priority", 50 ) )
                fileHandle.write( "TaskTimeoutMinutes=%s\n" % jobProperties.get( "tasktimeout", 0 ) )
                fileHandle.write( "EnableAutoTimeout=%s\n" % jobProperties.get( "autotimeout", False ) )
                fileHandle.write( "ConcurrentTasks=%s\n" % jobProperties.get( "concurrent", 1 ) )
                fileHandle.write( "MachineLimit=%s\n" % jobProperties.get( "machinelimit", 0 ) )
                fileHandle.write( "LimitConcurrentTasksToNumberOfCpus=%s\n" % jobProperties.get( "slavelimit", False ) )
                fileHandle.write( "LimitGroups=%s\n" % jobProperties.get( "limits", 0 ) )
                fileHandle.write( "JobDependencies=%s\n" % dependencies )
                fileHandle.write( "OnJobComplete=%s\n" % jobProperties.get( "onjobcomplete", "Nothing" ) )
                fileHandle.write( "Frames=%s\n" % GetFrameList( node, jobProperties) )
                fileHandle.write( "ChunkSize=%s\n" % jobProperties.get( "framespertask", 1 ) )


            pluginInfoFile = os.path.join( homeDir, "temp", "houdini_plugin_info.job")
            with open( pluginInfoFile, "w" ) as fileHandle:
                fileHandle.write( "SceneFile=%s\n" % hou.parm(node.path() + "/lopoutput").eval() )
                fileHandle.write( "LogLevel=%s\n" % jobProperties.get( "usdloglevel", 2 ) )
                fileHandle.write( "HouVersion=%s\n" % jobProperties.get( "ns_pipe_hou_version", "Nothing" ) )
                fileHandle.write( "OutImage=%s\n" % jobProperties.get( "ns_pipe_image_out", "" ) )
                fileHandle.write( "RenderDelegate=%s\n" % jobProperties.get( "ns_delegate", "" ) )
                fileHandle.write( "HouPackageLocation=%s\n" % jobProperties.get( "ns_package", "" ) )


            arguments = [ jobInfoFile, pluginInfoFile ]

            jobResult = CallDeadlineCommand( arguments )
            jobId = GetJobIdFromSubmission( jobResult )
            assemblyJobIds.append( jobId )

            print("---------------------------------------------------")
            print("\n".join( [ line.strip() for line in jobResult.split("\n") if line.strip() ] ) )
            print("---------------------------------------------------")

        else:
            return
    else:
        print("ns_Pipe> Found no usdjob property")

HouVersion, OutImage, RenderDelegate and HouPackageLocation are the new jobProperties i added. The values (ns_pipe_hou_version, ns_pipe_image_out, ns_delegate, ns_package) came from the Houdini submission script.

Next edit: build the right argument for Husk:

*\DeadlineRepository10\custom\plugins\HuskStandalone\HuskStandalone.py

from System import *
from System.Diagnostics import *
from System.IO import *

import os

from Deadline.Plugins import *
from Deadline.Scripting import *

from pathlib import Path

def GetDeadlinePlugin():
    return HuskStandalone()

def CleanupDeadlinePlugin(deadlinePlugin):
    deadlinePlugin.Cleanup()

class HuskStandalone(DeadlinePlugin):
    # functions inside a class must be indented in python - DT
    def __init__( self ):
        self.InitializeProcessCallback += self.InitializeProcess
        self.RenderExecutableCallback += self.RenderExecutable # get the renderExecutable Location
        self.RenderArgumentCallback += self.RenderArgument # get the arguments to go after the EXE


    def Cleanup( self ):
        del self.InitializeProcessCallback
        del self.RenderExecutableCallback
        del self.RenderArgumentCallback

    def InitializeProcess( self ):
        self.SingleFramesOnly=True
        self.StdoutHandling=True
        self.PopupHandling=False

        self.AddStdoutHandlerCallback("USD ERROR(.*)").HandleCallback += self.HandleStdoutError # detect this error
        self.AddStdoutHandlerCallback( r"ALF_PROGRESS ([0-9]+(?=%))" ).HandleCallback += self.HandleStdoutProgress

    # get path to the executable
    def RenderExecutable(self):
        return self.GetConfigEntry( "USD_RenderExecutable" ).replace("<ns_Pipe>", "Houdini " + self.GetPluginInfoEntry("HouVersion"))

    # get the settings that go after the filename in the render command, 3Delight only has simple options.
    def RenderArgument( self ):
        if self.GetPluginInfoEntry("RenderDelegate") == "Redshift":
            os.environ["HOUDINI_PACKAGE_DIR"] = self.GetPluginInfoEntry("HouPackageLocation") # set delegate location

            # construct fileName
            #this will only support 1 frame per task

            usdFile = self.GetPluginInfoEntry("SceneFile")
            usdFile = RepositoryUtils.CheckPathMapping( usdFile )
            usdFile = usdFile.replace( "\\", "/" )

            usdPaddingLength = FrameUtils.GetPaddingSizeFromFilename( usdFile )

            frameNumber = self.GetStartFrame() # check this 2021 USD

            argument = ""
            
            argument += " --renderer HdRedshiftRendererPlugin"

            argument += " --usd-input " + usdFile

            argument += " --verbose a{}".format(self.GetPluginInfoEntry("LogLevel"))  # alfred style output and full verbosity

            argument += " --frame {}".format(frameNumber)

            argument += " --frame-count 1" # only render 1 frame per task

            ## renderer handled in job file. ##
            
            if self.GetPluginInfoEntry("OutImage") != "":
                if not os.path.isdir(os.path.dirname(self.GetPluginInfoEntry("OutImage"))):
                    os.makedirs(os.path.dirname(self.GetPluginInfoEntry("OutImage")))

                output_path = os.path.dirname(self.GetPluginInfoEntry("OutImage"))
                out_image_path_parts = self.GetPluginInfoEntry("OutImage").split("/")
                image_comp_parts = out_image_path_parts[-1].split(".")
                image_name = image_comp_parts[0]
                padded_frame_number = StringUtils.ToZeroPaddedString(frameNumber, len(image_comp_parts[-2]))
                image_format = image_comp_parts[-1]

                argument += " --output {0}/{1}.{2}.{3}".format(output_path, image_name, padded_frame_number, image_format)
            else:
                # fallback
                outputPath = os.path.dirname(usdFile).split('/') #[:-4] We are now going to site the composite USD in the project root.
                outputPath.append("render")
                outputPath = os.path.abspath(os.path.join(*outputPath))
            
                if not os.path.isdir(outputPath):
                    os.mkdir(outputPath)

                filename = Path(usdFile).name
                filename = Path(filename).with_suffix("")
                
                paddedFrameNumber = StringUtils.ToZeroPaddedString(frameNumber, 4)
                
                argument += " -o {0}/{1}.{2}.exr".format(outputPath, filename, paddedFrameNumber)

            argument += " --make-output-path"

            # argument += " --exrmode 0" ## legacy exr mode for fusion cryptomattes ##

            # argument += " --ocio 1"

            self.LogInfo( "Rendering USD file: " + usdFile )

        elif self.GetPluginInfoEntry("RenderDelegate") == "Octane":
            os.environ["HOUDINI_PACKAGE_DIR"] = self.GetPluginInfoEntry("HouPackageLocation") # set delegate location

            # construct fileName
            #this will only support 1 frame per task

            usdFile = self.GetPluginInfoEntry("SceneFile")
            usdFile = RepositoryUtils.CheckPathMapping( usdFile )
            usdFile = usdFile.replace( "\\", "/" )

            usdPaddingLength = FrameUtils.GetPaddingSizeFromFilename( usdFile )

            frameNumber = self.GetStartFrame() # check this 2021 USD

            argument = ""
            
            argument += " --renderer HdOctaneRendererPlugin"

            argument += " --usd-input " + usdFile

            argument += " --verbose a{}".format(self.GetPluginInfoEntry("LogLevel"))  # alfred style output and full verbosity

            argument += " --frame {}".format(frameNumber)

            argument += " --frame-count 1" # only render 1 frame per task

            ## renderer handled in job file. ##
            
            if self.GetPluginInfoEntry("OutImage") != "":
                if not os.path.isdir(os.path.dirname(self.GetPluginInfoEntry("OutImage"))):
                    os.makedirs(os.path.dirname(self.GetPluginInfoEntry("OutImage")))

                output_path = os.path.dirname(self.GetPluginInfoEntry("OutImage"))
                out_image_path_parts = self.GetPluginInfoEntry("OutImage").split("/")
                image_comp_parts = out_image_path_parts[-1].split(".")
                image_name = image_comp_parts[0]
                padded_frame_number = StringUtils.ToZeroPaddedString(frameNumber, len(image_comp_parts[-2]))
                image_format = image_comp_parts[-1]

                argument += " --output {0}/{1}.{2}.{3}".format(output_path, image_name, padded_frame_number, image_format)
            else:
                # fallback
                outputPath = os.path.dirname(usdFile).split('/') #[:-4] We are now going to site the composite USD in the project root.
                outputPath.append("render")
                outputPath = os.path.abspath(os.path.join(*outputPath))
            
                if not os.path.isdir(outputPath):
                    os.mkdir(outputPath)

                filename = Path(usdFile).name
                filename = Path(filename).with_suffix("")
                
                paddedFrameNumber = StringUtils.ToZeroPaddedString(frameNumber, 4)
                
                argument += " -o {0}/{1}.{2}.exr".format(outputPath, filename, paddedFrameNumber)

            argument += " --make-output-path"

            # argument += " --exrmode 0" ## legacy exr mode for fusion cryptomattes ##

            # argument += " --ocio 1"

            self.LogInfo( "Rendering USD file: " + usdFile )
        
        elif self.GetPluginInfoEntry("RenderDelegate") == "Karma":
            # construct fileName
            #this will only support 1 frame per task

            usdFile = self.GetPluginInfoEntry("SceneFile")
            usdFile = RepositoryUtils.CheckPathMapping( usdFile )
            usdFile = usdFile.replace( "\\", "/" )

            usdPaddingLength = FrameUtils.GetPaddingSizeFromFilename( usdFile )

            frameNumber = self.GetStartFrame() # check this 2021 USD

            argument = ""

            argument += " --renderer BRAY_HdKarma"

            argument += " --usd-input " + usdFile

            argument += " --verbose a{}".format(self.GetPluginInfoEntry("LogLevel"))  # alfred style output and full verbosity

            argument += " --frame {}".format(frameNumber)

            argument += " --frame-count 1" # only render 1 frame per task

            ## renderer handled in job file. ##
            
            if self.GetPluginInfoEntry("OutImage") != "":
                output_path = os.path.dirname(self.GetPluginInfoEntry("OutImage"))
                out_image_path_parts = self.GetPluginInfoEntry("OutImage").split("/")
                image_comp_parts = out_image_path_parts[-1].split(".")
                image_name = image_comp_parts[0]
                padded_frame_number = StringUtils.ToZeroPaddedString(frameNumber, len(image_comp_parts[-2]))
                image_format = image_comp_parts[-1]

                argument += " -o {0}/{1}.{2}.{3}".format(output_path, image_name, padded_frame_number, image_format)
            else:
                # fallback
                outputPath = os.path.dirname(usdFile).split('/') #[:-4] We are now going to site the composite USD in the project root.
                outputPath.append("render")
                outputPath = os.path.abspath(os.path.join(*outputPath))
            
                if not os.path.isdir(outputPath):
                    os.mkdir(outputPath)

                filename = Path(usdFile).name
                filename = Path(filename).with_suffix("")
                
                paddedFrameNumber = StringUtils.ToZeroPaddedString(frameNumber, 4)
                
                argument += " -o {0}/{1}.{2}.exr".format(outputPath, filename, paddedFrameNumber)

            argument += " --make-output-path"
            
            argument += " --exrmode 1" ## legacy exr mode for fusion cryptomattes ##

            # argument += " --ocio 1"

            self.LogInfo( "Rendering USD file: " + usdFile )

        elif self.GetPluginInfoEntry("RenderDelegate") == "KarmaXPU":
            # construct fileName
            #this will only support 1 frame per task

            usdFile = self.GetPluginInfoEntry("SceneFile")
            usdFile = RepositoryUtils.CheckPathMapping( usdFile )
            usdFile = usdFile.replace( "\\", "/" )

            usdPaddingLength = FrameUtils.GetPaddingSizeFromFilename( usdFile )

            frameNumber = self.GetStartFrame() # check this 2021 USD

            argument = ""

            argument += " --renderer BRAY_HdKarmaXPU"

            argument += " --usd-input " + usdFile

            argument += " --verbose a{}".format(self.GetPluginInfoEntry("LogLevel"))  # alfred style output and full verbosity

            argument += " --frame {}".format(frameNumber)

            argument += " --frame-count 1" # only render 1 frame per task

            ## renderer handled in job file. ##
            
            if self.GetPluginInfoEntry("OutImage") != "":
                output_path = os.path.dirname(self.GetPluginInfoEntry("OutImage"))
                out_image_path_parts = self.GetPluginInfoEntry("OutImage").split("/")
                image_comp_parts = out_image_path_parts[-1].split(".")
                image_name = image_comp_parts[0]
                padded_frame_number = StringUtils.ToZeroPaddedString(frameNumber, len(image_comp_parts[-2]))
                image_format = image_comp_parts[-1]

                argument += " -o {0}/{1}.{2}.{3}".format(output_path, image_name, padded_frame_number, image_format)
            else:
                # fallback
                outputPath = os.path.dirname(usdFile).split('/') #[:-4] We are now going to site the composite USD in the project root.
                outputPath.append("render")
                outputPath = os.path.abspath(os.path.join(*outputPath))
            
                if not os.path.isdir(outputPath):
                    os.mkdir(outputPath)

                filename = Path(usdFile).name
                filename = Path(filename).with_suffix("")
                
                paddedFrameNumber = StringUtils.ToZeroPaddedString(frameNumber, 4)
                
                argument += " -o {0}/{1}.{2}.exr".format(outputPath, filename, paddedFrameNumber)

            argument += " --make-output-path"
            
            argument += " --exrmode 1" ## legacy exr mode for fusion cryptomattes ##

            # argument += " --ocio 1"

            self.LogInfo( "Rendering USD file: " + usdFile )

        return argument

    # just incase we want to implement progress at some point
    def HandleStdoutProgress(self):
        self.SetStatusMessage(self.GetRegexMatch(0))
        self.SetProgress(float(self.GetRegexMatch(1)))

    # what to do when an error is detected.
    def HandleStdoutError(self):
        self.FailRender(self.GetRegexMatch(0))

Here i added some code in the RenderExecutable and RenderArgument function. Just compare it with the original script.

You can see, for the Octane & Redshift delegate i set the ENV variable “HOUDINI_PACKAGE_DIR”, so Husk is loading the same version i used in the setup file or Houdini session.

I added a new argument for Husk as well:

argument += " --exrmode 0"

This is for Fusion Cryptomattes .exr files. Otherwise the Cryptos wont working.

NOTE:

Check out other arguments to pass through, here: https://www.sidefx.com/docs/houdini/ref/utils/husk.html

For example to set OCIO color transform with this line:

argument += " --ocio 1"

Create a custom Houdini Submitter Script for USD-ROPs.

You can just create a Houdini shelf tool or build an entry in the OPmenu.xml to trigger the script.

Create a Shelf Tool and paste the Python Code
Or entries in OPmenu.xml

OPmenu.xml code:

<!--LOP LEVEL MENU-->
<!--############################################################################################################################-->


           <expression>
node = kwargs.get("node", None)
if node is None:
    return False
return node.type().category().name() in ["usdrender_rop", "usd_rop"]
        </expression>
        <subMenu id="ns_version_menu_lop">
            <label>[ ns_Pipe ]</label>
            <context>


            </context>
            <expression>
node = kwargs.get("node", None)
if node is None:
    return False
return node.type().name() in ["usdrender_rop", "usd_rop"]
            </expression>
                    <subMenu id="ns_version_deadline_menu_lop">
                        <label>Deadline</label>
                        <context>

                        </context>
                        <scriptItem id="ns_deadline_submitter_husk">
                        <expression>
node = kwargs.get("node", None)
if node is None:
  return False
return node.type().name() in ["usdrender_rop", "usd_rop"]
                          </expression>
                          <label>Deadline Submitter Husk - Karma</label>
                          <scriptPath>$HSITE/scripts/python/ns_Pipe/ns_deadline_submitter_husk.py</scriptPath>
                        </scriptItem>

                        <scriptItem id="ns_deadline_submitter_husk_task">
                        <expression>
node = kwargs.get("node", None)
if node is None:
  return False
return node.type().name() in ["usdrender_rop", "usd_rop"]
                          </expression>
                          <label>Deadline Submitter Husk per Task - Karma</label>
                          <scriptPath>$HSITE/scripts/python/ns_Pipe/ns_deadline_submitter_husk_task.py</scriptPath>
                        </scriptItem>

                        <separatorItem/>

                        <scriptItem id="ns_deadline_submitter_husk_XPU">
                        <expression>
node = kwargs.get("node", None)
if node is None:
  return False
return node.type().name() in ["usdrender_rop", "usd_rop"]
                          </expression>
                          <label>Deadline Submitter Husk - KarmaXPU</label>
                          <scriptPath>$HSITE/scripts/python/ns_Pipe/ns_deadline_submitter_husk_xpu.py</scriptPath>
                        </scriptItem>

                        <scriptItem id="ns_deadline_submitter_husk_task_XPU">
                        <expression>
node = kwargs.get("node", None)
if node is None:
  return False
return node.type().name() in ["usdrender_rop", "usd_rop"]
                          </expression>
                          <label>Deadline Submitter Husk per Task - KarmaXPU</label>
                          <scriptPath>$HSITE/scripts/python/ns_Pipe/ns_deadline_submitter_husk_task_xpu.py</scriptPath>
                        </scriptItem>

                        <separatorItem/>

                        <scriptItem id="ns_deadline_submitter_husk_rs">
                        <expression>
node = kwargs.get("node", None)
if node is None:
  return False
return node.type().name() in ["usdrender_rop", "usd_rop"]
                          </expression>
                          <label>Deadline Submitter Husk - Redshift Delegate</label>
                          <scriptPath>$HSITE/scripts/python/ns_Pipe/ns_deadline_submitter_husk_rs.py</scriptPath>
                        </scriptItem>

                        <scriptItem id="ns_deadline_submitter_husk_task_rs">
                        <expression>
node = kwargs.get("node", None)
if node is None:
  return False
return node.type().name() in ["usdrender_rop", "usd_rop"]
                          </expression>
                          <label>Deadline Submitter Husk per Task - Redshift Delegate</label>
                          <scriptPath>$HSITE/scripts/python/ns_Pipe/ns_deadline_submitter_husk_task_rs.py</scriptPath>
                        </scriptItem>

                        <separatorItem/>

                        <scriptItem id="ns_deadline_submitter_husk_octane">
                        <expression>
node = kwargs.get("node", None)
if node is None:
  return False
return node.type().name() in ["usdrender_rop", "usd_rop"]
                          </expression>
                          <label>Deadline Submitter Husk - Octane Delegate</label>
                          <scriptPath>$HSITE/scripts/python/ns_Pipe/ns_deadline_submitter_husk_octane.py</scriptPath>
                        </scriptItem>

                        <scriptItem id="ns_deadline_submitter_husk_task_octane">
                        <expression>
node = kwargs.get("node", None)
if node is None:
  return False
return node.type().name() in ["usdrender_rop", "usd_rop"]
                          </expression>
                          <label>Deadline Submitter Husk per Task - Octane Delegate</label>
                          <scriptPath>$HSITE/scripts/python/ns_Pipe/ns_deadline_submitter_husk_task_octane.py</scriptPath>
                        </scriptItem>

                    </subMenu>

This will create a script, that has an input prompt where you can define how many frames Deadline will render per task and which priority is needed. You can, of course, build a high advanced submitter with a lot more inputs, choices and a fancy GUI.

Here for Octane you have to create a “husk_octane” pool on Deadline:

## Niclas Schlapmann - Freelance 3D Technical Artist
## www.enoni.de
## hello@enoni.de
## 07.04.2024
##################################### Imports ####################################
import hou
import os
import sys
import traceback
import json
import getpass
import time
from time import *
##################################################################################

user = getpass.getuser()
lt = localtime()
year, month, day, hour, minute, sec = lt[0:6]
date = str(year)[2:4] + "-" + str(month).zfill(2) + "-" + str(day).zfill(2) + " - " + str(hour).zfill(2) + ":" + str(minute).zfill(2) + ":" + str(sec).zfill(2)

def deadline_submitter_husk_task_octane():
    renderNodes = hou.selectedNodes()
    
    if not renderNodes:
        return
    
    if renderNodes[0].type().name() not in ["usdrender_rop", "usd_rop"]:
        hou.ui.displayMessage("Select a proper USD-ROP or USDRender-ROP")
        return
    
    for renderNode in renderNodes:
        jobname = hou.getenv("HIPNAME")
        pool = "husk_octane"
        secondarypool = "husk_octane"
        comment = "submitted by <" + user + "> " + date
        department = "enoni.de"
        
        if renderNode.evalParm(renderNode.path() + "/trange") >= 1:
            framelist = str(int(renderNode.evalParm(renderNode.path() + "/f1"))) + "-" + str(int(renderNode.evalParm(renderNode.path() + "/f2")))
        else:
            framelist = str(int(hou.frame())) + "-" + str(int(hou.frame()))

        framecount = int(renderNode.evalParm(renderNode.path() + "/f2")) - int(renderNode.evalParm(renderNode.path() + "/f1")) + 1
        frame_input_count = hou.ui.readInput("Frames per task:", buttons=("OK", "Cancel"), initial_contents=str(framecount))

        if frame_input_count[0] == 1:
            return
        
        print("ns_Pipe> Render ROP: " + renderNode.name())
        
        choice = hou.ui.displayMessage("Deadline Priority?", buttons=("Extra Low(1)", "Low(25)", "Mid(50)", "High(99)", "Abort"))
        
        if choice == 0:
            prio = 1
        elif choice == 1:
            prio = 25
        elif choice == 2:
            prio = 50
        elif choice == 3:
            prio = 99
        else:
            return
        
        ## create prop dictionary ##
        jobProperties = {
            'batch': False,
            'jobname': jobname,
            'comment': comment,
            'department': department,
            'pool': pool,
            'secondarypool': secondarypool,
            'group': 'none',
            'priority': prio,
            'tasktimeout': 0,
            'autotimeout': 0,
            'concurrent': 1,
            'machinelimit': 0,
            'slavelimit': 1,
            'limits': '',
            'onjobcomplete': 'Nothing',
            'jobsuspended': 0,
            'shouldprecache': 1,
            'isblacklist': 0,
            'machinelist': '',
            'overrideframes': 1,
            'framelist': framelist,
            'framespertask': int(frame_input_count[1]),
            'bits': '64bit',
            'submitscene': 0,
            'isframedependent': 0,
            'gpuopenclenable': 0,
            'gpuspertask': 0,
            'gpudevices': '',
            'ignoreinputs': 0,
            'separateWedgeJobs': 0,
            
            'mantrajob': 0,
            'mantrapool': pool,
            'mantrasecondarypool': secondarypool,
            'mantragroup': 'none',
            'mantrapriority': prio,
            'mantratasktimeout': 0,
            'mantraautotimeout': 0,
            'mantraconcurrent': 1,
            'mantramachinelimit': 0,
            'mantraslavelimit': 1,
            'mantralimits': '',
            'mantraonjobcomplete': 'Nothing',
            'mantraisblacklist': 0,
            'mantramachinelist': '',
            'mantrathreads': 0,
            'mantralocalexport': 0,
            
            'arnoldjob': 1,
            'arnoldpool': pool,
            'arnoldsecondarypool': secondarypool,
            'arnoldgroup': 'none',
            'arnoldpriority': prio,
            'arnoldtasktimeout': 0,
            'arnoldautotimeout': 0,
            'arnoldconcurrent': 1,
            'arnoldmachinelimit': 0,
            'arnoldslavelimit': 1,
            'arnoldonjobcomplete': 'Nothing',
            'arnoldlimits': '',
            'arnoldisblacklist': 0,
            'arnoldmachinelist': '',
            'arnoldthreads': 0,
            'arnoldlocalexport': 1,
            
            'rendermanjob': 0,
            'rendermanpool': pool,
            'rendermansecondarypool': secondarypool,
            'rendermangroup': 'none',
            'rendermanpriority': prio,
            'rendermantasktimeout': 0,
            'rendermanconcurrent': 1,
            'rendermanmachinelimit': 0,
            'rendermanlimits': '',
            'rendermanonjobcomplete': 'Nothing',
            'rendermanisblacklist': 0,
            'rendermanmachinelist': '',
            'rendermanthreads': 0,
            'rendermanarguments': '',
            'rendermanlocalexport': 0,
            
            'redshiftjob': 0,
            'redshiftpool': pool,
            'redshiftsecondarypool': secondarypool,
            'redshiftgroup': 'none',
            'redshiftpriority': prio,
            'redshifttasktimeout': 0,
            'redshiftautotimeout': 0,
            'redshiftconcurrent': 1,
            'redshiftmachinelimit': 0,
            'redshiftslavelimit': 1,
            'redshiftlimits': '',
            'redshiftonjobcomplete': 'Nothing',
            'redshiftisblacklist': 0,
            'redshiftmachinelist': '',
            'redshiftarguments': '',
            'redshiftlocalexport': 0,
            
            'usdjob': 1,
            'usdpool': pool,
            'usdsecondarypool': secondarypool,
            'usdgroup': 'none',
            'usdpriority': prio,
            'usdtasktimeout': 0,
            'usdautotimeout': 0,
            'usdconcurrent': 1,
            'usdmachinelimit': 0,
            'usdslavelimit': 1,
            'usdlimits': '',
            'usdonjobcomplete': 'Nothing',
            'usdisblacklist': 0,
            'usdmachinelist': '',
            'usdarguments': '',
            'usdlocalexport': 1,
            'usdloglevel': 2,
    
            'vrayjob': 0,
            'vraypool': pool,
            'vraysecondarypool': secondarypool,
            'vraygroup': 'none',
            'vraypriority': prio,
            'vraytasktimeout': 0,
            'vrayautotimeout': 0,
            'vrayconcurrent': 1,
            'vraymachinelimit': 0,
            'vrayslavelimit': 1,
            'vraylimits': '',
            'vrayonjobcomplete': 'Nothing',
            'vrayisblacklist': 0,
            'vraymachinelist': '',
            'vraythreads': 0,
            'vrayarguments': '',
            'vraylocalexport': 0,
            
            'tilesenabled': 0,
            'tilesinx': 3,
            'tilesiny': 3,
            'tilessingleframeenabled': 1,
            'tilessingleframe': 1,
            'jigsawenabled': 1,
            'jigsawregioncount': 0,
            'jigsawregions': [],
            'submitdependentassembly': 1,
            'backgroundoption': 'Blank Image',
            'backgroundimage': '',
            'erroronmissingtiles': '1',
            'erroronmissingbackground': '0',
            'cleanuptiles': '1'
        }
        
        ## write USD from USD-ROP ##
        usd_file_path = renderNode.evalParm(renderNode.path() + "/lopoutput")
        if os.path.isfile(usd_file_path):
            if hou.ui.displayMessage("USD render file already exist. Override", buttons=("Yes", "Abort")) == 0:
                renderNode.parm("execute").pressButton()
            else:
                return
        else:
            renderNode.parm("execute").pressButton()

        tmp_version = ""
        
        ## submit to Deadline ##
        flag = 0
        
        ## imports and sys pathes for deadline ##
        try:
            from CallDeadlineCommand import CallDeadlineCommand
        except ImportError:
            path = ""
            print("The CallDeadlineCommand.py script could not be found in the Houdini installation. Please make sure that the Deadline Client has been installed on this machine.\n")
            hou.ui.displayMessage("The CallDeadlineCommand.py script could not be found in the Houdini installation. Please make sure that the Deadline Client has been installed on this machine.", title="Submit Houdini To Deadline")
        else:
            path = CallDeadlineCommand(["-GetRepositoryPath", "submission/Houdini/Main"]).strip()
        
        if path:
            path = path.replace("\\", "/")
            
            # Add the path to the system path
            if path not in sys.path:
                print("Appending \"" + path + "\" to system path to import SubmitHoudiniToDeadline module")
                sys.path.append(path)
            else:
                pass
            
            # Import the script and call the main() function
            try:
                import SubmitHoudiniToDeadline
            except:
                print(traceback.format_exc())
                print("The SubmitHoudiniToDeadline.py script could not be found in the Deadline Repository. Please make sure that the Deadline Client has been installed on this machine, that the Deadline Client bin folder is set in the DEADLINE_PATH environment variable, and that the Deadline Client has been configured to point to a valid Repository.")
        else:
            print("The SubmitHoudiniToDeadline.py script could not be found in the Deadline Repository. Please make sure that the Deadline Client has been installed on this machine, that the Deadline Client bin folder is set in the DEADLINE_PATH environment variable, and that the Deadline Client has been configured to point to a valid Repository.")
        
        ## get deadline info ##
        print("Grabbing submitter info...")
        try:
            output = json.loads(CallDeadlineCommand(["-prettyJSON", "-GetSubmissionInfo", "Pools", "Groups", "MaxPriority", "TaskLimit", "UserHomeDir", "RepoDir:submission/Houdini/Main", "RepoDir:submission/Integration/Main", "RepoDirNoCustom:draft",
                                                     "RepoDirNoCustom:submission/Jigsaw", ]))
        except:
            print("Unable to get submitter info from Deadline:\n\n" + traceback.format_exc())
            raise
        
        if output["ok"]:
            submissionInfo = output["result"]
            hou.putenv("Deadline_Submission_Info", json.dumps(submissionInfo))
        else:
            print("DeadlineCommand returned a bad result and was unable to grab the submitter info.\n\n" + output["result"])
            raise Exception(output["result"])
        
        ## submit render job ##
        try:
            import SubmitHoudiniToDeadlineFunctions as SHTDFunctions
            flag = 1
        except Exception as e:
            print(e)
            hou.ui.displayMessage("ns_Pipe> Library import failure. Make sure you have a proper Deadline installation.")
        
        if flag:
            try:
                jobProperties.update({"ns_delegate" : "Octane"})
                jobProperties.update({"ns_package" : hou.getenv("HOUDINI_PACKAGE_DIR")})
                jobProperties.update({"ns_pipe_hou_version": tmp_version})
                jobProperties.update({"ns_pipe_image_out" : hou.parm(renderNode.path() + "/spare_output_image").eval()})
                jobIds = SHTDFunctions.SubmitRenderJob_husk(renderNode, jobProperties, "")
            except Exception as e:
                print(e)
                hou.ui.displayMessage("ns_Pipe> Can`t submitting to Deadline Repository.")

deadline_submitter_husk_task_octane()

Here for Octane you have to create a “husk_redshift” pool on Deadline:

## Niclas Schlapmann - Freelance 3D Technical Artist
## www.enoni.de
## hello@enoni.de
## 07.04.2024
##################################### Imports ####################################
import hou
import os
import sys
import traceback
import json
import getpass
import time
from time import *
##################################################################################

user = getpass.getuser()
lt = localtime()
year, month, day, hour, minute, sec = lt[0:6]
date = str(year)[2:4] + "-" + str(month).zfill(2) + "-" + str(day).zfill(2) + " - " + str(hour).zfill(2) + ":" + str(minute).zfill(2) + ":" + str(sec).zfill(2)

def deadline_submitter_husk_task_rs():
    renderNodes = hou.selectedNodes()
    
    if not renderNodes:
        return
    
    if renderNodes[0].type().name() not in ["usdrender_rop", "usd_rop"]:
        hou.ui.displayMessage("Select a proper USD-ROP or USDRender-ROP")
        return
    
    for renderNode in renderNodes:
        jobname = hou.getenv("HIPNAME")
        pool = "husk_redshift"
        secondarypool = "husk_redshift"
        comment = "submitted by <" + user + "> " + date
        department = "enoni.de"
        
        if renderNode.evalParm(renderNode.path() + "/trange") >= 1:
            framelist = str(int(renderNode.evalParm(renderNode.path() + "/f1"))) + "-" + str(int(renderNode.evalParm(renderNode.path() + "/f2")))
        else:
            framelist = str(int(hou.frame())) + "-" + str(int(hou.frame()))

        framecount = int(renderNode.evalParm(renderNode.path() + "/f2")) - int(renderNode.evalParm(renderNode.path() + "/f1")) + 1
        frame_input_count = hou.ui.readInput("Frames per task:", buttons=("OK", "Cancel"), initial_contents=str(framecount))

        if frame_input_count[0] == 1:
            return
        
        print("ns_Pipe> Render ROP: " + renderNode.name())
        
        choice = hou.ui.displayMessage("Deadline Priority?", buttons=("Extra Low(1)", "Low(25)", "Mid(50)", "High(99)", "Abort"))
        
        if choice == 0:
            prio = 1
        elif choice == 1:
            prio = 25
        elif choice == 2:
            prio = 50
        elif choice == 3:
            prio = 99
        else:
            return
        
        ## create prop dictionary ##
        jobProperties = {
            'batch': False,
            'jobname': jobname,
            'comment': comment,
            'department': department,
            'pool': pool,
            'secondarypool': secondarypool,
            'group': 'none',
            'priority': prio,
            'tasktimeout': 0,
            'autotimeout': 0,
            'concurrent': 1,
            'machinelimit': 0,
            'slavelimit': 1,
            'limits': '',
            'onjobcomplete': 'Nothing',
            'jobsuspended': 0,
            'shouldprecache': 1,
            'isblacklist': 0,
            'machinelist': '',
            'overrideframes': 1,
            'framelist': framelist,
            'framespertask': int(frame_input_count[1]),
            'bits': '64bit',
            'submitscene': 0,
            'isframedependent': 0,
            'gpuopenclenable': 0,
            'gpuspertask': 0,
            'gpudevices': '',
            'ignoreinputs': 0,
            'separateWedgeJobs': 0,
            
            'mantrajob': 0,
            'mantrapool': pool,
            'mantrasecondarypool': secondarypool,
            'mantragroup': 'none',
            'mantrapriority': prio,
            'mantratasktimeout': 0,
            'mantraautotimeout': 0,
            'mantraconcurrent': 1,
            'mantramachinelimit': 0,
            'mantraslavelimit': 1,
            'mantralimits': '',
            'mantraonjobcomplete': 'Nothing',
            'mantraisblacklist': 0,
            'mantramachinelist': '',
            'mantrathreads': 0,
            'mantralocalexport': 0,
            
            'arnoldjob': 1,
            'arnoldpool': pool,
            'arnoldsecondarypool': secondarypool,
            'arnoldgroup': 'none',
            'arnoldpriority': prio,
            'arnoldtasktimeout': 0,
            'arnoldautotimeout': 0,
            'arnoldconcurrent': 1,
            'arnoldmachinelimit': 0,
            'arnoldslavelimit': 1,
            'arnoldonjobcomplete': 'Nothing',
            'arnoldlimits': '',
            'arnoldisblacklist': 0,
            'arnoldmachinelist': '',
            'arnoldthreads': 0,
            'arnoldlocalexport': 1,
            
            'rendermanjob': 0,
            'rendermanpool': pool,
            'rendermansecondarypool': secondarypool,
            'rendermangroup': 'none',
            'rendermanpriority': prio,
            'rendermantasktimeout': 0,
            'rendermanconcurrent': 1,
            'rendermanmachinelimit': 0,
            'rendermanlimits': '',
            'rendermanonjobcomplete': 'Nothing',
            'rendermanisblacklist': 0,
            'rendermanmachinelist': '',
            'rendermanthreads': 0,
            'rendermanarguments': '',
            'rendermanlocalexport': 0,
            
            'redshiftjob': 0,
            'redshiftpool': pool,
            'redshiftsecondarypool': secondarypool,
            'redshiftgroup': 'none',
            'redshiftpriority': prio,
            'redshifttasktimeout': 0,
            'redshiftautotimeout': 0,
            'redshiftconcurrent': 1,
            'redshiftmachinelimit': 0,
            'redshiftslavelimit': 1,
            'redshiftlimits': '',
            'redshiftonjobcomplete': 'Nothing',
            'redshiftisblacklist': 0,
            'redshiftmachinelist': '',
            'redshiftarguments': '',
            'redshiftlocalexport': 0,
            
            'usdjob': 1,
            'usdpool': pool,
            'usdsecondarypool': secondarypool,
            'usdgroup': 'none',
            'usdpriority': prio,
            'usdtasktimeout': 0,
            'usdautotimeout': 0,
            'usdconcurrent': 1,
            'usdmachinelimit': 0,
            'usdslavelimit': 1,
            'usdlimits': '',
            'usdonjobcomplete': 'Nothing',
            'usdisblacklist': 0,
            'usdmachinelist': '',
            'usdarguments': '',
            'usdlocalexport': 1,
            'usdloglevel': 2,
    
            'vrayjob': 0,
            'vraypool': pool,
            'vraysecondarypool': secondarypool,
            'vraygroup': 'none',
            'vraypriority': prio,
            'vraytasktimeout': 0,
            'vrayautotimeout': 0,
            'vrayconcurrent': 1,
            'vraymachinelimit': 0,
            'vrayslavelimit': 1,
            'vraylimits': '',
            'vrayonjobcomplete': 'Nothing',
            'vrayisblacklist': 0,
            'vraymachinelist': '',
            'vraythreads': 0,
            'vrayarguments': '',
            'vraylocalexport': 0,
            
            'tilesenabled': 0,
            'tilesinx': 3,
            'tilesiny': 3,
            'tilessingleframeenabled': 1,
            'tilessingleframe': 1,
            'jigsawenabled': 1,
            'jigsawregioncount': 0,
            'jigsawregions': [],
            'submitdependentassembly': 1,
            'backgroundoption': 'Blank Image',
            'backgroundimage': '',
            'erroronmissingtiles': '1',
            'erroronmissingbackground': '0',
            'cleanuptiles': '1'
        }
        
        ## write USD from USD-ROP ##
        usd_file_path = renderNode.evalParm(renderNode.path() + "/lopoutput")
        if os.path.isfile(usd_file_path):
            if hou.ui.displayMessage("USD render file already exist. Override", buttons=("Yes", "Abort")) == 0:
                renderNode.parm("execute").pressButton()
            else:
                return
        else:
            renderNode.parm("execute").pressButton()

        tmp_version = ""
        
        ## submit to Deadline ##
        flag = 0
        
        ## imports and sys pathes for deadline ##
        try:
            from CallDeadlineCommand import CallDeadlineCommand
        except ImportError:
            path = ""
            print("The CallDeadlineCommand.py script could not be found in the Houdini installation. Please make sure that the Deadline Client has been installed on this machine.\n")
            hou.ui.displayMessage("The CallDeadlineCommand.py script could not be found in the Houdini installation. Please make sure that the Deadline Client has been installed on this machine.", title="Submit Houdini To Deadline")
        else:
            path = CallDeadlineCommand(["-GetRepositoryPath", "submission/Houdini/Main"]).strip()
        
        if path:
            path = path.replace("\\", "/")
            
            # Add the path to the system path
            if path not in sys.path:
                print("Appending \"" + path + "\" to system path to import SubmitHoudiniToDeadline module")
                sys.path.append(path)
            else:
                pass
            
            # Import the script and call the main() function
            try:
                import SubmitHoudiniToDeadline
            except:
                print(traceback.format_exc())
                print("The SubmitHoudiniToDeadline.py script could not be found in the Deadline Repository. Please make sure that the Deadline Client has been installed on this machine, that the Deadline Client bin folder is set in the DEADLINE_PATH environment variable, and that the Deadline Client has been configured to point to a valid Repository.")
        else:
            print("The SubmitHoudiniToDeadline.py script could not be found in the Deadline Repository. Please make sure that the Deadline Client has been installed on this machine, that the Deadline Client bin folder is set in the DEADLINE_PATH environment variable, and that the Deadline Client has been configured to point to a valid Repository.")
        
        ## get deadline info ##
        print("Grabbing submitter info...")
        try:
            output = json.loads(CallDeadlineCommand(["-prettyJSON", "-GetSubmissionInfo", "Pools", "Groups", "MaxPriority", "TaskLimit", "UserHomeDir", "RepoDir:submission/Houdini/Main", "RepoDir:submission/Integration/Main", "RepoDirNoCustom:draft",
                                                     "RepoDirNoCustom:submission/Jigsaw", ]))
        except:
            print("Unable to get submitter info from Deadline:\n\n" + traceback.format_exc())
            raise
        
        if output["ok"]:
            submissionInfo = output["result"]
            hou.putenv("Deadline_Submission_Info", json.dumps(submissionInfo))
        else:
            print("DeadlineCommand returned a bad result and was unable to grab the submitter info.\n\n" + output["result"])
            raise Exception(output["result"])
        
        ## submit render job ##
        try:
            import SubmitHoudiniToDeadlineFunctions as SHTDFunctions
            flag = 1
        except Exception as e:
            print(e)
            hou.ui.displayMessage("ns_Pipe> Library import failure. Make sure you have a proper Deadline installation.")
        
        if flag:
            try:
                jobProperties.update({"ns_delegate" : "Redshift"})
                jobProperties.update({"ns_package" : hou.getenv("HOUDINI_PACKAGE_DIR")})
                jobProperties.update({"ns_pipe_hou_version": tmp_version})
                jobProperties.update({"ns_pipe_image_out" : hou.parm(renderNode.path() + "/spare_output_image").eval()})
                jobIds = SHTDFunctions.SubmitRenderJob_husk(renderNode, jobProperties, "")
            except Exception as e:
                print(e)
                hou.ui.displayMessage("ns_Pipe> Can`t submitting to Deadline Repository.")

deadline_submitter_husk_task_rs()

If you want to added Karma and KarmaX delegates as well, you have to change the “ns_delegate” value to “Karma” or “KarmaXPU”.

IMPORTANT:

You need on your USD-ROP a string field where the script find the path for the image output. The string var field has to be named “spare_output_image”. I just referencing the path from the Octane/Redshift Settings Node/LOP.

Custom String Field for ImageFile Output

When you now trigger the script with selected USD-ROP node, you are able to send USD Husk render jobs to Deadline. Modify it to your specific needs and build your own Deadline submitter.