├── dependencies ├── init.py └── menu.py ├── render.bat ├── autoBuild.bat ├── README.md ├── autoBuild.py ├── maya_submit_node.py ├── nuke_submit_node.py └── render.py /dependencies/init.py: -------------------------------------------------------------------------------- 1 | nuke.pluginAddPath('./python') 2 | nuke.pluginAddPath('./icons') 3 | -------------------------------------------------------------------------------- /render.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM This script just sets python as a system variable for this particular session 4 | REM So that the python scripts can run if you get errors check the file paths 5 | 6 | cd C:\Python27\ 7 | 8 | python ./render.py 9 | 10 | echo Press the any key to exit 11 | pause > nul 12 | cls 13 | exit -------------------------------------------------------------------------------- /dependencies/menu.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import nuke 3 | import nuke_submit_node 4 | 5 | print 'Loading Lab Tools...' 6 | menubar = nuke.menu("Nuke") 7 | 8 | # Custom Lab Tools 9 | toolbar = nuke.toolbar("Nodes") 10 | m = toolbar.addMenu("hQueue", icon="hQueue.png") 11 | 12 | m.addCommand("Submit render", "nuke_submit_node.runGui()", icon="ICON.png") 13 | -------------------------------------------------------------------------------- /autoBuild.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM This script just sets python as a system variable for this particular session 4 | REM So that the python scripts can run if you get errors check the file paths 5 | 6 | "C:\Python27\python" %~f0\..\autoBuild.py 7 | "C:\Program Files\Nuke9.0v1\python" %~f0\..\autoBuild.py 8 | "C:\Program Files\Nuke9.0v2\python" %~f0\..\autoBuild.py 9 | "C:\Program Files\Nuke9.0v3\python" %~f0\..\autoBuild.py 10 | "C:\Program Files\Nuke9.0v4\python" %~f0\..\autoBuild.py 11 | "C:\Program Files\Nuke9.0v5\python" %~f0\..\autoBuild.py 12 | "C:\Program Files\Nuke9.0v6\python" %~f0\..\autoBuild.py 13 | "C:\Program Files\Nuke9.0v7\python" %~f0\..\autoBuild.py 14 | "C:\Program Files\Nuke9.0v8\python" %~f0\..\autoBuild.py 15 | "C:\Program Files\Nuke9.0v9\python" %~f0\..\autoBuild.py 16 | 17 | "C:\Program Files\Nuke10.0v1\python" %~f0\..\autoBuild.py 18 | "C:\Program Files\Nuke10.0v2\python" %~f0\..\autoBuild.py 19 | "C:\Program Files\Nuke10.0v3\python" %~f0\..\autoBuild.py 20 | "C:\Program Files\Nuke10.0v4\python" %~f0\..\autoBuild.py 21 | "C:\Program Files\Nuke10.0v5\python" %~f0\..\autoBuild.py 22 | 23 | echo Press the any key to exit 24 | pause > nul 25 | cls 26 | exit -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hqueue-renderScript 2 | A script designed to submit render jobs for Nuke and Maya using an installation of Houdini's render farm service, hQueue. 3 | 4 | # Introduction 5 | About a year ago I installed a version of Side FX's Houdini 14 Hqueue render farm job management system onto some of the networked computers of the University I was attending at the time. 6 | After using this for a few days, one of my peers offhandedly said that it would be useful if it could render Nuke and Maya scenes as well. 7 | This was all I needed to hear and within a week I had a basic working version and a week after that I had a fully fleshed out commandline python script that could submit Nuke, Maya and Arnold jobs to Hqueue on windows. 8 | 9 | # Where is it? 10 | Long story short the "fully fleshed out script" I wrote worked, but was terribly written. I've chosen to rewrite it from scratch instead of fixing the previous version. 11 | I'm also improving it as I rewrite it, adding a GUI starting with Nuke then moving onto Maya then Pyqt for a standalone GUI. As I get more and more features rewritten I'll start writing how-to instructions but usage should be fairly straightforward. 12 | 13 | # Why am I rewritting it? 14 | The people at SideFX have made the Hqueue program very useful, if you're already using Houdini it's free and with a little bit of effort it can be set up to use any operating system as nodes. 15 | 16 | # Links 17 | http://SideFX.com - This is SideFX's website, creators of Houdini, Houdini FX and the Hqueue render server program. -------------------------------------------------------------------------------- /autoBuild.py: -------------------------------------------------------------------------------- 1 | # Author: Josh Kelly 2 | 3 | # Import needed modules and components 4 | import sys 5 | import os 6 | import urllib2 7 | 8 | OSplatform = sys.platform 9 | if OSplatform.startswith("win"): 10 | OSplatform = "windows" 11 | nukeDir = "C:\Program Files" 12 | elif OSplatform.startswith("linux"): 13 | OSplatform = "linux" 14 | nukeDir = "/usr/local" 15 | elif OSplatform.startswith("darwin"): 16 | OSplatform = "macosx" 17 | nukeDir = "/Applications" 18 | macExtraFolders = "Contents/MacOS/" 19 | 20 | if OSplatform == "windows": 21 | pass 22 | elif OSplatform == "linux": 23 | if os.getuid() == 0: 24 | pass 25 | else: 26 | print "User is not root! Rerun with sudo!" 27 | quit() 28 | elif OSplatform == "macosx": 29 | if os.getuid() == 0: 30 | pass 31 | else: 32 | print "User is not root! Rerun with sudo!" 33 | quit() 34 | else: 35 | print "Unsupported platform" 36 | quit() 37 | 38 | folderStructure = { 39 | 'user': os.path.join('plugins', 'user'), 40 | 'python': os.path.join('plugins', os.path.join('user', 'python')), 41 | 'icons': os.path.join('plugins', os.path.join('user', 'icons')) 42 | } 43 | 44 | fileStructure = { 45 | 'init': os.path.join('plugins', os.path.join('user', 'init.py')), 46 | 'menu': os.path.join('plugins', os.path.join('user', 'menu.py')), 47 | 'nuke_submit_node': os.path.join('plugins', os.path.join('user', os.path.join('python', 'nuke_submit_node.py'))) 48 | } 49 | 50 | pluginComponents = { 51 | 'init': ['', 'https://raw.githubusercontent.com/SpaghettiBaguette/hqueue-renderScript/master/dependencies/init.py'], 52 | 'menu': ['', 'https://raw.githubusercontent.com/SpaghettiBaguette/hqueue-renderScript/master/dependencies/menu.py'], 53 | 'nuke_submit_node': ['', 'https://raw.githubusercontent.com/SpaghettiBaguette/hqueue-renderScript/master/nuke_submit_node.py'] 54 | } 55 | 56 | for i in pluginComponents.keys(): 57 | print pluginComponents[i][1] 58 | response = urllib2.urlopen(pluginComponents[i][1]) 59 | pluginComponents[i][0] = response.read() 60 | 61 | try: 62 | filePath = sys.argv[1] 63 | except: 64 | filePath = nukeDir 65 | 66 | nukeInstallDir = {} 67 | 68 | for i in os.listdir(filePath): 69 | if "Nuke" in i: 70 | nukeInstallDir[i] = os.path.join(filePath, i) 71 | else: 72 | pass 73 | 74 | print "########## Nuke Install Dir ##########" 75 | print '\n'.join(nukeInstallDir) 76 | print "######################################" 77 | 78 | def createFilesAndDir(pathValues, type): 79 | for i in nukeInstallDir.keys(): 80 | for x in pathValues.keys(): 81 | if type == 'folder': 82 | if os.path.isdir(os.path.join(nukeInstallDir[i], pathValues[x])): 83 | print os.path.join(nukeInstallDir[i], pathValues[x]), "exists!" 84 | else: 85 | print os.path.join(nukeInstallDir[i], pathValues[x]), "does not exist! Creating!" 86 | try: 87 | os.makedirs(os.path.join(nukeInstallDir[i], pathValues[x])) 88 | except: 89 | print "You're not an admin!" 90 | elif type == 'file': 91 | if os.path.isfile(os.path.join(nukeInstallDir[i], pathValues[x])): 92 | print os.path.join(nukeInstallDir[i], pathValues[x]), "exists!" 93 | pass 94 | else: 95 | print os.path.join(nukeInstallDir[i], pathValues[x]), "does not exist! Creating!" 96 | file = open(os.path.join(nukeInstallDir[i], pathValues[x]), 'w') 97 | file.write(pluginComponents[x][0]) 98 | file.close() 99 | else: 100 | raise ValueError('File type unsupported') 101 | 102 | createFilesAndDir(folderStructure, 'folder') 103 | createFilesAndDir(fileStructure, 'file') -------------------------------------------------------------------------------- /maya_submit_node.py: -------------------------------------------------------------------------------- 1 | # Author: Josh Kelly 2 | 3 | # Import needed modules and components 4 | import os 5 | import os.path 6 | import sys 7 | import xmlrpclib 8 | import getpass 9 | import json 10 | import posixpath 11 | import maya.cmds as cmds 12 | 13 | configLocation = os.path.join(os.environ['HOME'], ".hQueueConfig.dat") 14 | defaultServerAddress = 'localhost:5000' 15 | 16 | # This chunk of code is lifted from hqrop.py and rewritten as neccessary ####################################################################### 17 | def expandHQROOT(path, hq_server): 18 | """Return the given file path with instances of $HQROOT expanded 19 | out to the mount point for the HQueue shared folder root.""" 20 | # Get the HQueue root path. 21 | hq_root = getHQROOT(hq_server)[0] 22 | if hq_root is None: 23 | return path 24 | 25 | expanded_path = { 26 | 'windows': path.replace("$HQROOT", hq_root['windows']), 27 | 'linux': path.replace("$HQROOT", hq_root['linux']), 28 | 'macosx': path.replace("$HQROOT", hq_root['macosx']) 29 | } 30 | return expanded_path 31 | 32 | def getHQROOT(hq_server): 33 | """Query the HQueue server and return the mount point path 34 | to the HQueue shared folder root. 35 | Return None if the path cannot be retrieved from the server. 36 | """ 37 | # Identify this machine's platform. 38 | OSplatform = sys.platform 39 | if OSplatform.startswith("win"): 40 | OSplatform = "windows" 41 | elif OSplatform.startswith("linux"): 42 | OSplatform = "linux" 43 | elif OSplatform.startswith("darwin"): 44 | OSplatform = "macosx" 45 | 46 | # Connect to the HQueue server. 47 | s = hQServerConnect(hq_server) 48 | if s is None: 49 | return None 50 | 51 | try: 52 | # Get the HQ root for all platforms. 53 | hq_root = { 54 | 'windows': s.getHQRoot('windows'), 55 | 'linux': s.getHQRoot('linux'), 56 | 'macosx': s.getHQRoot('macosx') 57 | } 58 | except: 59 | print("Could not retrieve $HQROOT from '" + hq_server + "'.") 60 | return None 61 | 62 | return [hq_root, OSplatform] 63 | 64 | def hqServerProxySetup(hq_server): 65 | """Sets up a xmlrpclib server proxy to the given HQ server.""" 66 | if not hq_server.startswith("http://"): 67 | full_hq_server_path = "http://%s" % hq_server 68 | else: 69 | full_hq_server_path = hq_server 70 | 71 | return xmlrpclib.ServerProxy(full_hq_server_path, allow_none=True) 72 | 73 | def doesHQServerExists(hq_server): 74 | """Check that the given HQ server can be connected to. 75 | Returns True if the server exists and False if it does not. Furthermore, 76 | it will display an error message if it does not exists.""" 77 | server = hqServerProxySetup(hq_server) 78 | return hQServerPing(server, hq_server) 79 | 80 | def hQServerConnect(hq_server): 81 | """Connect to the HQueue server and return the proxy connection.""" 82 | s = hqServerProxySetup(hq_server) 83 | 84 | if hQServerPing(s, hq_server): 85 | return s 86 | else: 87 | return None 88 | 89 | def hQServerPing(server, hq_server): 90 | try: 91 | server.ping() 92 | return True 93 | except: 94 | print("Could not connect to '" + hq_server + "'.\n\n" 95 | + "Make sure that the HQueue server is running\n" 96 | + "or change the value of 'HQueue Server'.", 97 | TypeError("this is a type error")) 98 | 99 | return False 100 | 101 | def getClients(hq_server): 102 | """Return a list of all the clients registered on the HQueue server. 103 | Return None if the client list could not be retrieved from the server. 104 | """ 105 | s = hQServerConnect(hq_server) 106 | 107 | if s is None: 108 | return None 109 | 110 | try: 111 | client_ids = None 112 | attribs = ["id", "hostname"] 113 | clients = s.getClients(client_ids, attribs) 114 | except: 115 | print("Could not retrieve client list from '" + hq_server + "'.") 116 | return None 117 | 118 | return [client["hostname"] for client in clients] 119 | 120 | def getClientGroups(hq_server): 121 | """Return a list of all the client groups on the HQueue server. 122 | Return None if the client group list could not be retrieved from the server. 123 | """ 124 | s = hQServerConnect(hq_server) 125 | if s is None: 126 | return None 127 | 128 | try: 129 | client_groups = s.getClientGroups() 130 | except: 131 | print("Could not retrieve client group list from '" 132 | + hq_server + "'.") 133 | return None 134 | 135 | return client_groups 136 | 137 | def getBaseParameters(name, assigned_to, clientList, clientGroupList, installDir, serverAddress, priorityLevel): 138 | """Return a dictionary of the base parameters used in this nuke script""" 139 | parms = { 140 | "name": name, 141 | "assign_to": assigned_to, 142 | "clients": clientList, 143 | "client_groups": clientGroupList, 144 | "dirs_to_create": "", 145 | "environment": "", 146 | "hfs": installDir, 147 | "hq_server": serverAddress, 148 | "open_browser": "", 149 | "priority": priorityLevel, 150 | "hip_action": "", 151 | "autosave": "", 152 | "warn_unsaved_changes": "", 153 | "report_submitted_job_id": "", 154 | } 155 | 156 | addSubmittedByParm(parms) 157 | 158 | return parms 159 | 160 | def addSubmittedByParm(parms): 161 | """Adds who submits the job to the base parameters.""" 162 | try: 163 | parms["submittedBy"] = getpass.getuser() 164 | except (ImportError, KeyError): 165 | pass 166 | 167 | def buildContainingJobSpec(job_name, parms, child_jobs, 168 | apply_conditions_to_children=True): 169 | """Return a job spec that submits the child job and waits for it. 170 | The job containing the child job will not run any command. 171 | """ 172 | job = { 173 | "name": job_name, 174 | "priority": parms['priority'], 175 | # "environment": {"HQCOMMANDS": hutil.json.utf8Dumps(hq_cmds)}, 176 | "command": "", 177 | "children": child_jobs, 178 | # "emailTo": parms["emailTo"], 179 | # "emailReasons": parms["emailReasons"], 180 | } 181 | 182 | if "submittedBy" in parms: 183 | job["submittedBy"] = parms["submittedBy"] 184 | 185 | # Add job assignment conditions if any. 186 | conditions = {"clients": "host", "client_groups": "hostgroup"} 187 | for cond_type in conditions.keys(): 188 | job_cond_keyword = conditions[cond_type] 189 | if parms["assign_to"] == cond_type: 190 | # job[job_cond_keyword] = 'josh-laptop' 191 | job[job_cond_keyword] = parms[cond_type] 192 | if apply_conditions_to_children: 193 | for child_job in job["children"]: 194 | child_job[job_cond_keyword] = parms[cond_type] 195 | 196 | return job 197 | 198 | def buildOSCommands(MFS, startFrame, endFrame, fileName): 199 | commands = { 200 | # Example: nuke.exe -F 1-100 -x myscript.nk 201 | "linux": MFS['linux']+" -r "+cmds.optionMenuGrp('renderChoice', q=True, value=True)+" -s "+str(startFrame)+" -e "+str(endFrame)+" -b 1 -proj "+'"'+'/'.join(fileName['linux'].strip('"').split('/')[:-2])+'" '+fileName['linux'], 202 | "windows": MFS['windows']+" -r "+cmds.optionMenuGrp('renderChoice', q=True, value=True)+" -s "+str(startFrame)+" -e "+str(endFrame)+" -b 1 -proj "+'"'+'\\'.join(fileName['windows'].strip('"').split('\\')[:-2])+'" '+fileName['windows'], 203 | "macosx": MFS['macosx']+" -r "+cmds.optionMenuGrp('renderChoice', q=True, value=True)+" -s "+str(startFrame)+" -e "+str(endFrame)+" -b 1 -proj "+'"'+'/'.join(fileName['windows'].strip('"').split('/')[:-2])+'" '+fileName['macosx'] 204 | } 205 | 206 | return commands 207 | 208 | def buildChildJobs(jobName, OSCommands, priority): 209 | job_spec = { 210 | "name": jobName, 211 | "command": OSCommands, 212 | "priority": priority, 213 | "tags": '', 214 | # "maxHosts": 1, 215 | # "minHosts": 1, 216 | } 217 | return job_spec 218 | 219 | def sendJob(hq_server, main_job): 220 | s = hQServerConnect(hq_server) 221 | if s is None: 222 | return False 223 | 224 | # We do this here as we need a server connection 225 | #_setupEmailReasons(s, main_job) 226 | 227 | # If we're running as an HQueue job, make that job our parent job. 228 | try: 229 | ids = s.newjob(main_job) 230 | except Exception, e: 231 | print "Could not submit job:", main_job['name'], "to", hq_server 232 | return False 233 | 234 | return ids 235 | 236 | def getFrameWord(frames): 237 | if len(frames) == 1: 238 | return "Frame" 239 | else: 240 | return "Frames" 241 | 242 | ################################################################################################################################################################################################# 243 | #### CONFIG FUNCTIONS 244 | 245 | def retrieveConfigCache(): 246 | if os.path.isfile(configLocation): 247 | with open(configLocation, 'r') as f: 248 | config = json.load(f) 249 | return config['Server Address'] 250 | else: 251 | return defaultServerAddress 252 | 253 | def writeConfigCache(serverAddress): 254 | config = {'Server Address': serverAddress} 255 | with open(configLocation, 'w') as f: 256 | json.dump(config, f) 257 | return True 258 | 259 | def getMayaRenderers(): 260 | return cmds.renderer(q=1, namesOfAvailableRenderers=1),cmds.getAttr("defaultRenderGlobals.currentRenderer") 261 | 262 | ################################################################################################################################################################################################# 263 | 264 | ### C:/Program Files/Autodesk/Maya2011/devkit/plug-ins/scripted 265 | 266 | 267 | class createMyLayoutCls(object): 268 | def __init__(self, *args): 269 | pass 270 | 271 | def show(self): 272 | self.createMyLayout() 273 | 274 | def createMyLayout(self): 275 | 276 | # check to see if our window exists 277 | if cmds.window('hQueueSubmit', exists=True): 278 | cmds.deleteUI('hQueueSubmit') 279 | 280 | self.window = cmds.window('hQueueSubmit', widthHeight=(500, 600), title="hQueue Maya render submission panel", 281 | resizeToFitChildren=1) 282 | 283 | cmds.columnLayout('hQueueWindowLayout', adjustableColumn=True, columnAlign="center") #, rowSpacing=10) 284 | 285 | self.jobName = cmds.textFieldGrp('jobName', label="Job name: ", text="") 286 | 287 | self.serverAddress = cmds.textFieldButtonGrp('serverAddress', label="Server Address: ", 288 | text=retrieveConfigCache(), 289 | buttonLabel="Test the server Address", 290 | buttonCommand=self.serverAddressConnect) 291 | 292 | self.filePath = cmds.textFieldButtonGrp('filePath', label="File path: ", 293 | text=cmds.file(q=True, location=True), 294 | buttonLabel="Test the file path", 295 | buttonCommand=self.filePathCheck) 296 | 297 | self.installDirectoryChoicesKey = {'HQRoot install directory': '$HQROOT/maya_distros/$OS-Maya' + str(cmds.about(version=True)), 298 | 'Default install directory': os.environ['MAYA_LOCATION'], 299 | 'Custom install directory': ''} 300 | self.installDirectoryChoices = ['HQRoot install directory', 'Default install directory', 'Custom install directory'] 301 | self.installDirectoryCurrent = cmds.optionMenuGrp('installDirectoryCurrent', label="NFS Directory: ", 302 | changeCommand=self.installDirectoryChoicesChange) 303 | for i in self.installDirectoryChoices: 304 | cmds.menuItem(label=i) 305 | 306 | self.installDirectory = cmds.textFieldGrp('installDirectory', label="Maya Install Directory: ", 307 | text=self.installDirectoryChoicesKey[cmds.optionMenuGrp( 308 | self.installDirectoryCurrent, q=True, value=True)]) 309 | 310 | self.priorityLevels = ['1 (Lowest)', '2', '3', '4', '5 (Medium)', '6', '7', '8', '9', '10 (Highest)'] 311 | self.priority = cmds.optionMenuGrp('priority', label="Priority: ") 312 | for i in self.priorityLevels: 313 | cmds.menuItem(label=i) 314 | cmds.optionMenuGrp(self.priority, edit=True, select=5) 315 | 316 | self.clientSelectionTypes = {'Any Client': 'any', 'Selected Clients': 'clients', 'Clients from Listed Groups': 'client_groups'} 317 | self.clientTypes = ['Any Client', 'Selected Clients', 'Clients from Listed Groups'] 318 | self.assign_to = cmds.optionMenuGrp('assign_to', label="Assigned nodes: ", changeCommand=self.assignedToChange) 319 | for i in self.clientTypes: 320 | cmds.menuItem(label=i) 321 | 322 | self.clientGet = cmds.textFieldButtonGrp('clientGet', label="Clients: ", buttonLabel="Get client list", 323 | buttonCommand=self.getClientList, visible=False) 324 | 325 | self.clientGroupGet = cmds.textFieldButtonGrp('clientGroupGet', label="Client groups: ", 326 | buttonLabel="Get client groups", 327 | buttonCommand=self.getClientGroupList, visible=False) 328 | 329 | self.trackRange = cmds.textFieldGrp('trackRange', label="Track Range: ", text=str(int(cmds.getAttr('defaultRenderGlobals.startFrame'))) + '-' + str(int(cmds.getAttr('defaultRenderGlobals.endFrame')))) 330 | 331 | (self.renderOptions,self.currentRenderer) = getMayaRenderers() 332 | self.renderChoice = cmds.optionMenuGrp('renderChoice', label="Renderer: ") 333 | for i in self.renderOptions: 334 | cmds.menuItem(label=i) 335 | 336 | cmds.optionMenuGrp('renderChoice', edit=True, value=self.currentRenderer) 337 | 338 | self.submitJob = cmds.button(label="Submit job to farm", recomputeSize=True, command=self.submitJobToFarm) 339 | 340 | cmds.setParent(menu=True) 341 | 342 | cmds.showWindow(self.window) 343 | 344 | def assignedToChange(self, *args): 345 | if args[0] == 'Any Client': 346 | cmds.textFieldButtonGrp(self.clientGet, edit=True, visible=False) 347 | cmds.textFieldButtonGrp(self.clientGroupGet, edit=True, visible=False) 348 | elif args[0] == 'Selected Clients': 349 | cmds.textFieldButtonGrp(self.clientGroupGet, edit=True, visible=False) 350 | cmds.textFieldButtonGrp(self.clientGet, edit=True, visible=True) 351 | elif args[0] == 'Clients from Listed Groups': 352 | cmds.textFieldButtonGrp(self.clientGet, edit=True, visible=False) 353 | cmds.textFieldButtonGrp(self.clientGroupGet, edit=True, visible=True) 354 | else: 355 | print "Unknown type:", args[0] 356 | raise ValueError 357 | 358 | def getClientList(self, *args): 359 | # Get a response from the function of the button that was pressed 360 | self.clientResponse = getClients(cmds.textFieldButtonGrp(self.serverAddress, q=True, text=True)) 361 | 362 | self.clientListType = 'clientGet' 363 | 364 | self.cleanList = self.clientResponse 365 | 366 | # Call the function for popping up the popup 367 | self.popUpPanel(self) 368 | 369 | def getClientGroupList(self, *args): 370 | # Get a response from the function of the button that was pressed 371 | self.clientGroupResponse = getClientGroups(cmds.textFieldButtonGrp(self.serverAddress, q=True, text=True)) 372 | self.cleanList = [] 373 | 374 | self.clientListType = 'clientGroupGet' 375 | 376 | for i in range(0, len(self.clientGroupResponse)): 377 | self.cleanList.append(self.clientGroupResponse[i]['name']) 378 | 379 | # Call the function for popping up the popup 380 | self.popUpPanel(self) 381 | 382 | def popUpPanel(self, *args): 383 | # check to see if our window exists 384 | if cmds.window('clientList', exists=True): 385 | cmds.deleteUI('clientList') 386 | cmds.window('clientList', width=200, height=200, resizeToFitChildren=1) 387 | cmds.columnLayout(adjustableColumn=True, columnAlign="center") 388 | for i in self.cleanList: 389 | cmds.checkBox(i.replace('-', '')) 390 | cmds.button('acceptClientListButton', label='Accept', command=self.clientListAccept) 391 | cmds.popupMenu(alt=True, ctl=True) 392 | cmds.showWindow() 393 | 394 | def clientListAccept(self, *args): 395 | self.clientInterrumList = [] 396 | for i in self.cleanList: 397 | if cmds.checkBox(i.replace('-', ''), q=True, value=True) is True: 398 | self.clientInterrumList.append(i) 399 | cmds.textFieldButtonGrp(self.clientListType, edit=True, text=', '.join(self.clientInterrumList)) 400 | return True 401 | 402 | def cleanInstallEXE(self, unusableDir): 403 | if cmds.optionMenuGrp('installDirectoryCurrent', q=True, value=True) == "HQRoot install directory": 404 | usableDir = { 405 | 'linux': posixpath.join(posixpath.join(unusableDir.replace("$HQROOT", '"'+self.hqRoot['linux']).replace("$OS", 'linux'), 'bin'), 'Render"'), 406 | 'windows': posixpath.join(posixpath.join(unusableDir.replace("$HQROOT", '"'+self.hqRoot['windows']).replace("$OS", 'windows'), 'bin'), 'Render.exe"').replace('/', '\\'), 407 | 'macosx': posixpath.join(posixpath.join(unusableDir.replace("$HQROOT", '"'+self.hqRoot['macosx']).replace("$OS", 'macosx'), 'bin'), 'Render"') 408 | } 409 | elif cmds.optionMenuGrp('installDirectory', q=True, value=True) == "Default install directory": 410 | mayaDirName = 'maya' + str(cmds.about(version=True)) 411 | usableDir = { 412 | 'linux': posixpath.join(posixpath.join(posixpath.join('"/usr/autodesk', mayaDirName), 'bin'), 'Render"'), 413 | 'windows': posixpath.join(posixpath.join(posixpath.join('"C:\Program Files\Autodesk', mayaDirName), 'bin'), 'Render.exe"').replace('/', '\\'), 414 | 'macosx': posixpath.join(posixpath.join(posixpath.join('"/Applications/Autodesk/', mayaDirName), '/Maya.app/Contents/bin'), 'Render"') 415 | } 416 | return usableDir 417 | 418 | def installDirectoryChoicesChange(self, *args): 419 | cmds.textFieldGrp('installDirectory', edit=True, text=self.installDirectoryChoicesKey[args[0]]) 420 | 421 | def filePathCheck(self, *args): 422 | self.hQRootFilePathCheck() 423 | 424 | # Check if the file can be found at either of the possible file paths 425 | if os.path.isfile(self.fileResponse[self.platform].strip('"')) or os.path.isfile(self.fileResponse['hq'].strip('"')): 426 | # Set the background of filePath's text box to green if File found 427 | cmds.textFieldButtonGrp('filePath', edit=True, backgroundColor=(0, 0.8, 0)) 428 | self.filePathSuccess=True 429 | else: 430 | # Set the background of filePath's text box to red if File not found 431 | cmds.textFieldButtonGrp('filePath', edit=True, backgroundColor=(0.8, 0, 0)) 432 | self.filePathSuccess=None 433 | 434 | def hQRootFilePathCheck(self): 435 | (self.hqRoot, self.platform) = getHQROOT(cmds.textFieldButtonGrp(self.serverAddress, q=True, text=True)) 436 | self.filePathValue = cmds.textFieldButtonGrp(self.filePath, q=True, text=True) 437 | # See if the file path has $HQROOT in it 438 | if "$HQROOT" in self.filePathValue: 439 | # Set the platforms file value to the resolved path 440 | self.fileResponse = { 441 | 'windows': self.filePathValue.replace("$HQROOT", '"'+self.hqRoot['windows']).replace('/', '\\')+'"', 442 | 'macosx': self.filePathValue.replace("$HQROOT", '"'+self.hqRoot['macosx']).replace('\\', '/')+'"', 443 | 'linux': self.filePathValue.replace("$HQROOT", '"'+self.hqRoot['linux']).replace('\\', '/')+'"', 444 | 'hq': '"'+self.filePathValue+'"' 445 | } 446 | elif self.hqRoot['linux'] in self.filePathValue or self.hqRoot['macosx'] in self.filePathValue or self.hqRoot['windows'].replace('\\', '/') in self.filePathValue: 447 | self.fileResponse = { 448 | 'windows': self.filePathValue.replace(self.hqRoot[self.platform].replace('\\', '/'), '"'+self.hqRoot['windows']).replace('/', 449 | '\\')+'"', 450 | 'macosx': self.filePathValue.replace(self.hqRoot[self.platform].replace('\\', '/'), '"'+self.hqRoot['macosx']).replace('\\', 451 | '/')+'"', 452 | 'linux': self.filePathValue.replace(self.hqRoot[self.platform].replace('\\', '/'), '"'+self.hqRoot['linux']).replace('\\', 453 | '/')+'"', 454 | 'hq': self.filePathValue.replace(self.hqRoot['linux'], '"'+"$HQROOT").replace(self.hqRoot['macosx'], '"'+"$HQROOT").replace(self.hqRoot['windows'], '"'+"$HQROOT")+'"' 455 | } 456 | else: 457 | self.fileResponse = { 458 | 'windows': '"'+self.filePathValue.replace('/', '\\')+'"', 459 | 'macosx': '"'+self.filePathValue.replace('\\', '/')+'"', 460 | 'linux': '"'+self.filePathValue.replace('\\', '/')+'"', 461 | 'hq': '"'+self.filePathValue+'"' 462 | } 463 | 464 | def jobNameSet(self, jobName, FilePath): 465 | if jobName == "": 466 | return "Render -> NK: "+FilePath 467 | else: 468 | return jobName 469 | 470 | def finaliseClientList(self): 471 | self.clientGroupFullList = '' 472 | self.clientFullList = '' 473 | self.assigned_to_value = cmds.optionMenuGrp('assign_to', q=True, value=True) 474 | if self.assigned_to_value == "Any Client": 475 | self.assigned_to = self.clientSelectionTypes["Any Client"] 476 | elif self.assigned_to_value == "Selected Clients": 477 | self.assigned_to = self.clientSelectionTypes["Selected Clients"] 478 | # Create a string using the clientList names that can be sent with the job 479 | self.clientFullList = cmds.textFieldButtonGrp('clientGet', q=True, text=True) 480 | elif self.assigned_to_value == "Clients from Listed Groups": 481 | self.assigned_to = self.clientSelectionTypes["Clients from Listed Groups"] 482 | # Create a list using the clientList names to generate a list that can be sent with the job 483 | self.clientGroupFullList = cmds.textFieldButtonGrp('clientGroupGet', q=True, text=True) 484 | 485 | def finaliseJobSpecs(self): 486 | self.finaliseClientList() 487 | try: 488 | if self.hqRoot: 489 | pass 490 | except: 491 | self.filePathCheck() 492 | if self.filePathSuccess is True: 493 | return getBaseParameters(self.jobNameSet(cmds.textFieldGrp('jobName', q=True, text=True), self.fileResponse['hq']), self.assigned_to_value, 494 | self.clientFullList, self.clientGroupFullList, 495 | self.cleanInstallEXE(cmds.textFieldGrp('installDirectory', q=True, text=True)), cmds.textFieldButtonGrp('serverAddress', q=True, text=True), cmds.optionMenuGrp('priority', q=True, value=True)) 496 | else: 497 | raise ValueError("File Path not found") 498 | 499 | def submitJobToFarm(self, *args): 500 | self.parms = self.finaliseJobSpecs() 501 | self.childJobs = [] 502 | for i in range(int(cmds.textFieldGrp('trackRange', q=True, text=True).split('-')[0]), int(cmds.textFieldGrp('trackRange', q=True, text=True).split('-')[1]) + 1, 10): 503 | self.childJobs.append(buildChildJobs("Frame Range_" + str(i) + "-" + str(i + 9), 504 | buildOSCommands(self.parms['hfs'], i, i + 9, self.fileResponse), 505 | self.parms['priority'])) 506 | try: 507 | self.mainJob = buildContainingJobSpec(self.parms['name'], self.parms, self.childJobs) 508 | except: 509 | raise ValueError("Frame range is invalid") 510 | self.jobResponse = sendJob(self.parms['hq_server'], self.mainJob) 511 | if self.jobResponse: 512 | print "Job submission successful" 513 | else: 514 | print "Failed" 515 | 516 | def serverAddressWrite(self, *args): 517 | self.response = writeConfigCache(cmds.textFieldButtonGrp(self.serverAddress, q=True, text=True)) 518 | return self.response 519 | 520 | def serverAddressConnect(self, *args): 521 | self.response = doesHQServerExists(cmds.textFieldButtonGrp(self.serverAddress, q=True, text=True)) 522 | if self.response is True: 523 | self.serverAddressWrite() 524 | cmds.textFieldButtonGrp('serverAddress', edit=True, backgroundColor=(0, 0.8, 0)) 525 | else: 526 | cmds.textFieldButtonGrp('serverAddress', edit=True, backgroundColor=(0.8, 0, 0)) 527 | 528 | b_cls = createMyLayoutCls() 529 | b_cls.show() -------------------------------------------------------------------------------- /nuke_submit_node.py: -------------------------------------------------------------------------------- 1 | # Author: Josh Kelly 2 | 3 | # Import needed modules and components 4 | import os 5 | import os.path 6 | import sys 7 | import xmlrpclib 8 | import getpass 9 | import io 10 | import json 11 | import threading 12 | import posixpath 13 | import nuke 14 | import nukescripts 15 | 16 | configLocation = os.path.join(os.environ['HOME'], ".hQueueConfig.dat") 17 | defaultServerAddress = 'localhost:5000' 18 | 19 | # This chunk of code is lifted from hqrop.py and rewritten as neccessary ####################################################################### 20 | def expandHQROOT(path, hq_server): 21 | """Return the given file path with instances of $HQROOT expanded 22 | out to the mount point for the HQueue shared folder root.""" 23 | # Get the HQueue root path. 24 | hq_root = getHQROOT(hq_server)[0] 25 | if hq_root is None: 26 | return path 27 | 28 | expanded_path = { 29 | 'windows': path.replace("$HQROOT", hq_root['windows']), 30 | 'linux': path.replace("$HQROOT", hq_root['linux']), 31 | 'macosx': path.replace("$HQROOT", hq_root['macosx']) 32 | } 33 | return expanded_path 34 | 35 | def getHQROOT(hq_server): 36 | """Query the HQueue server and return the mount point path 37 | to the HQueue shared folder root. 38 | Return None if the path cannot be retrieved from the server. 39 | """ 40 | # Identify this machine's platform. 41 | OSplatform = sys.platform 42 | if OSplatform.startswith("win"): 43 | OSplatform = "windows" 44 | elif OSplatform.startswith("linux"): 45 | OSplatform = "linux" 46 | elif OSplatform.startswith("darwin"): 47 | OSplatform = "macosx" 48 | 49 | # Connect to the HQueue server. 50 | s = hQServerConnect(hq_server) 51 | if s is None: 52 | return None 53 | 54 | try: 55 | # Get the HQ root for all platforms. 56 | hq_root = { 57 | 'windows': s.getHQRoot('windows'), 58 | 'linux': s.getHQRoot('linux'), 59 | 'macosx': s.getHQRoot('macosx') 60 | } 61 | except: 62 | print("Could not retrieve $HQROOT from '" + hq_server + "'.") 63 | return None 64 | 65 | return [hq_root, OSplatform] 66 | 67 | def hqServerProxySetup(hq_server): 68 | """Sets up a xmlrpclib server proxy to the given HQ server.""" 69 | if not hq_server.startswith("http://"): 70 | full_hq_server_path = "http://%s" % hq_server 71 | else: 72 | full_hq_server_path = hq_server 73 | 74 | return xmlrpclib.ServerProxy(full_hq_server_path, allow_none=True) 75 | 76 | def doesHQServerExists(hq_server): 77 | """Check that the given HQ server can be connected to. 78 | Returns True if the server exists and False if it does not. Furthermore, 79 | it will display an error message if it does not exists.""" 80 | server = hqServerProxySetup(hq_server) 81 | return hQServerPing(server, hq_server) 82 | 83 | def hQServerConnect(hq_server): 84 | """Connect to the HQueue server and return the proxy connection.""" 85 | s = hqServerProxySetup(hq_server) 86 | 87 | if hQServerPing(s, hq_server): 88 | return s 89 | else: 90 | return None 91 | 92 | def hQServerPing(server, hq_server): 93 | try: 94 | server.ping() 95 | return True 96 | except: 97 | print("Could not connect to '" + hq_server + "'.\n\n" 98 | + "Make sure that the HQueue server is running\n" 99 | + "or change the value of 'HQueue Server'.", 100 | TypeError("this is a type error")) 101 | 102 | return False 103 | 104 | def getClients(hq_server): 105 | """Return a list of all the clients registered on the HQueue server. 106 | Return None if the client list could not be retrieved from the server. 107 | """ 108 | s = hQServerConnect(hq_server) 109 | 110 | if s is None: 111 | return None 112 | 113 | try: 114 | client_ids = None 115 | attribs = ["id", "hostname"] 116 | clients = s.getClients(client_ids, attribs) 117 | except: 118 | print("Could not retrieve client list from '" + hq_server + "'.") 119 | return None 120 | 121 | return [client["hostname"] for client in clients] 122 | 123 | def getClientGroups(hq_server): 124 | """Return a list of all the client groups on the HQueue server. 125 | Return None if the client group list could not be retrieved from the server. 126 | """ 127 | s = hQServerConnect(hq_server) 128 | if s is None: 129 | return None 130 | 131 | try: 132 | client_groups = s.getClientGroups() 133 | except: 134 | print("Could not retrieve client group list from '" 135 | + hq_server + "'.") 136 | return None 137 | 138 | return client_groups 139 | 140 | def getBaseParameters(name, assigned_to, clientList, clientGroupList, installDir, serverAddress, priorityLevel): 141 | """Return a dictionary of the base parameters used in this nuke script""" 142 | parms = { 143 | "name": name, 144 | "assign_to": assigned_to, 145 | "clients": clientList, 146 | "client_groups": clientGroupList, 147 | "dirs_to_create": "", 148 | "environment": "", 149 | "hfs": installDir, 150 | "hq_server": serverAddress, 151 | "open_browser": "", 152 | "priority": priorityLevel, 153 | "hip_action": "", 154 | "autosave": "", 155 | "warn_unsaved_changes": "", 156 | "report_submitted_job_id": "", 157 | } 158 | 159 | addSubmittedByParm(parms) 160 | 161 | return parms 162 | 163 | def addSubmittedByParm(parms): 164 | """Adds who submits the job to the base parameters.""" 165 | try: 166 | parms["submittedBy"] = getpass.getuser() 167 | except (ImportError, KeyError): 168 | pass 169 | 170 | def buildContainingJobSpec(job_name, parms, child_jobs, 171 | apply_conditions_to_children=True): 172 | """Return a job spec that submits the child job and waits for it. 173 | The job containing the child job will not run any command. 174 | """ 175 | job = { 176 | "name": job_name, 177 | "priority": parms['priority'], 178 | # "environment": {"HQCOMMANDS": hutil.json.utf8Dumps(hq_cmds)}, 179 | "command": "", 180 | "children": child_jobs, 181 | # "emailTo": parms["emailTo"], 182 | # "emailReasons": parms["emailReasons"], 183 | } 184 | 185 | if "submittedBy" in parms: 186 | job["submittedBy"] = parms["submittedBy"] 187 | 188 | # Add job assignment conditions if any. 189 | conditions = {"clients": "host", "client_groups": "hostgroup"} 190 | for cond_type in conditions.keys(): 191 | job_cond_keyword = conditions[cond_type] 192 | if parms["assign_to"] == cond_type: 193 | # job[job_cond_keyword] = 'josh-laptop' 194 | job[job_cond_keyword] = parms[cond_type] 195 | if apply_conditions_to_children: 196 | for child_job in job["children"]: 197 | child_job[job_cond_keyword] = parms[cond_type] 198 | 199 | return job 200 | 201 | def buildOSCommands(NFS, startFrame, endFrame, fileName): 202 | commands = { 203 | # Example: nuke.exe -F 1-100 -x myscript.nk 204 | "linux": NFS['linux']+" -F "+str(startFrame)+"-"+str(endFrame)+" -x "+fileName['linux'], 205 | "windows": NFS['windows']+" -F "+str(startFrame)+"-"+str(endFrame)+" -x "+fileName['windows'], 206 | "macosx": NFS['macosx']+" -F "+str(startFrame)+"-"+str(endFrame)+" -x "+fileName['macosx'], 207 | } 208 | 209 | return commands 210 | 211 | def buildChildJobs(jobName, OSCommands, priority): 212 | job_spec = { 213 | "name": jobName, 214 | "command": OSCommands, 215 | "priority": priority, 216 | "tags": '', 217 | # "maxHosts": 1, 218 | # "minHosts": 1, 219 | } 220 | return job_spec 221 | 222 | def sendJob(hq_server, main_job): 223 | s = hQServerConnect(hq_server) 224 | if s is None: 225 | return False 226 | 227 | # We do this here as we need a server connection 228 | #_setupEmailReasons(s, main_job) 229 | 230 | # If we're running as an HQueue job, make that job our parent job. 231 | try: 232 | ids = s.newjob(main_job) 233 | except Exception, e: 234 | print "Could not submit job:", main_job['name'], "to", hq_server 235 | return False 236 | 237 | return ids 238 | 239 | def getFrameWord(frames): 240 | if len(frames) == 1: 241 | return "Frame" 242 | else: 243 | return "Frames" 244 | 245 | ################################################################################################################################################################################################# 246 | #### CONFIG FUNCTIONS 247 | 248 | def retrieveConfigCache(): 249 | if os.path.isfile(configLocation): 250 | with open(configLocation, 'r') as f: 251 | config = json.load(f) 252 | return config['Server Address'] 253 | else: 254 | return defaultServerAddress 255 | 256 | def writeConfigCache(serverAddress): 257 | config = {'Server Address': serverAddress} 258 | with open(configLocation, 'w') as f: 259 | json.dump(config, f) 260 | 261 | ################################################################################################################################################################################################# 262 | #### SPLIT PATH FUNCTION 263 | 264 | def splitall(path): 265 | allparts = [] 266 | while 1: 267 | parts = os.path.split(path) 268 | if parts[0] == path: # sentinel for absolute paths 269 | allparts.insert(0, parts[0]) 270 | break 271 | elif parts[1] == path: # sentinel for relative paths 272 | allparts.insert(0, parts[1]) 273 | break 274 | else: 275 | path = parts[0] 276 | allparts.insert(0, parts[1]) 277 | return allparts 278 | 279 | ################################################################################################################################################################################################# 280 | 281 | # Run to open a window in Nuke 282 | class nukeWindow(nukescripts.PythonPanel): 283 | 284 | def __init__(self): 285 | # Init the panel with a name 286 | nukescripts.PythonPanel.__init__(self, "hQueue Nuke render submission panel") 287 | 288 | # Setup a text box for the job name 289 | self.jobName = nuke.String_Knob('jobName', 'Job Name: ', '') 290 | self.addKnob(self.jobName) 291 | 292 | # Setup a text box for the server address to be input into 293 | self.serverAddress = nuke.String_Knob('serverAddress', 'Server Address: ') 294 | self.serverAddress.setValue(retrieveConfigCache()) 295 | self.addKnob(self.serverAddress) 296 | 297 | # Setup a button to test the server address which will reveal the Connection Successful text 298 | self.addressTest = nuke.PyScript_Knob("addressTest", "Test the server address", "") 299 | self.addKnob(self.addressTest) 300 | 301 | # Create addressSuccessFlag flag that is hidden until the server is successfully pinged 302 | self.addressSuccessFlag = nuke.Text_Knob('addressSuccessFlag', '', 'Connection Successful') 303 | self.addressSuccessFlag.setFlag(nuke.STARTLINE) 304 | self.addressSuccessFlag.setVisible(False) 305 | self.addKnob(self.addressSuccessFlag) 306 | 307 | # Get the filepath from nuke and put it into a text box 308 | self.filePath = nuke.String_Knob('filePath', 'File Path: ', os.path.abspath(nuke.value("root.name"))) 309 | self.addKnob(self.filePath) 310 | 311 | # Create a button that will test the file path for an nuke script 312 | self.filePathCheck = nuke.PyScript_Knob("filePathCheck", "Test the File Path", "") 313 | self.addKnob(self.filePathCheck) 314 | 315 | # Create pathSuccessFlag flag that is hidden until the file path is verified 316 | self.pathSuccessFlag = nuke.Text_Knob('pathSuccessFlag', '', 'Connection Successful') 317 | self.pathSuccessFlag.setFlag(nuke.STARTLINE) 318 | self.pathSuccessFlag.setVisible(False) 319 | self.addKnob(self.pathSuccessFlag) 320 | 321 | # Setup a button to test the server address which will reveal the Connection Successful text 322 | self.installDirectoryChoicesKey = {'HQRoot install directory': '$HQROOT/nuke_distros/$OS-Nuke' + str(nuke.NUKE_VERSION_STRING), 323 | 'Default install directory': os.path.split(nuke.EXE_PATH)[0], 324 | 'Custom install directory': ''} 325 | self.installDirectoryChoices = ['HQRoot install directory', 'Default install directory', 'Custom install directory'] 326 | self.installDirectoryCurrent = nuke.Enumeration_Knob('installDirectoryCurrent', 'NFS Directory: ', self.installDirectoryChoices) 327 | self.addKnob(self.installDirectoryCurrent) 328 | 329 | # Setup a text box for the server address to be input into 330 | self.installDirectory = nuke.String_Knob('installDirectory', 'Nuke Install Directory: ') 331 | self.installDirectory.setValue('$HQROOT/nuke_distros/$OS-Nuke'+str(nuke.NUKE_VERSION_STRING)) 332 | self.addKnob(self.installDirectory) 333 | 334 | # Setup the Client selection box as a drop down menu 335 | self.priorityLevels = ['1 (Lowest)', '2', '3', '4', '5 (Medium)', '6', '7', '8', '9', '10 (Highest)'] 336 | self.priority = nuke.Enumeration_Knob('priority', 'Priority: ', self.priorityLevels) 337 | self.priority.setValue(self.priorityLevels[4]) 338 | self.addKnob(self.priority) 339 | 340 | # Setup the Client selection box as a drop down menu 341 | self.clientSelectionTypes = {'Any Client': 'any', 'Selected Clients': 'clients', 'Clients from Listed Groups': 'client_groups'} 342 | self.clientTypes = ['Any Client', 'Selected Clients', 'Clients from Listed Groups'] 343 | self.assign_to = nuke.Enumeration_Knob('nodes', 'Assigned nodes: ', self.clientTypes) 344 | self.addKnob(self.assign_to) 345 | 346 | # Setup the box that will display the chosen clients 347 | self.clientList = nuke.String_Knob('clientList', 'Clients: ', '') 348 | self.clientList.setFlag(nuke.STARTLINE) 349 | self.clientList.setVisible(False) 350 | self.addKnob(self.clientList) 351 | 352 | # Setup the get client list button, which will use hqrop functions 353 | self.clientGet = nuke.PyScript_Knob("clientGet", "Get client list", "") 354 | self.clientGet.setVisible(False) 355 | self.addKnob(self.clientGet) 356 | 357 | # Setup the get client groups button, which will use hqrop functions 358 | self.clientGroupGet = nuke.PyScript_Knob("clientGroupGet", "Get client groups", "") 359 | self.clientGroupGet.setVisible(False) 360 | self.addKnob(self.clientGroupGet) 361 | 362 | # Setup a frame range with the default frame range of the scene 363 | self.fRange = nuke.String_Knob('fRange', 'Track Range: ', '%s-%s' % (nuke.root().firstFrame(), nuke.root().lastFrame())) 364 | self.addKnob(self.fRange) 365 | 366 | # Setup a button to test the server address which will reveal the Connection Successful text 367 | self.submitJob = nuke.PyScript_Knob("submitJob", "Submit job to farm", "") 368 | self.submitJob.setFlag(nuke.STARTLINE) 369 | self.addKnob(self.submitJob) 370 | 371 | # Set the minimum size of the python panel 372 | self.setMinimumSize(500, 600) 373 | 374 | def knobChanged(self, knob): 375 | # When you press a button run the command attached to that button 376 | self.response = "" 377 | 378 | # Figure out which knob was changed 379 | if knob is self.addressTest: 380 | # Get a response from the function of the button that was pressed 381 | self.response = doesHQServerExists(self.serverAddress.value()) 382 | 383 | # If there is a response do thing 384 | if self.response == True: 385 | # Write out the server address to a a hidden file 386 | writeConfigCache(self.serverAddress.value()) 387 | # Set the value of addressSuccessFlag to green text Connection Successful 388 | self.addressSuccessFlag.setValue('Connection Successful') 389 | else: 390 | # Set the value of addressSuccessFlag to green text Connection failed 391 | self.addressSuccessFlag.setValue('Connection failed') 392 | 393 | # Set the address success text flag to visible 394 | self.addressSuccessFlag.setVisible(True) 395 | 396 | elif knob is self.filePathCheck: 397 | self.hQRootFilePathCheck()# Check if the file path exists 398 | 399 | if os.path.isfile(self.fileResponse[self.platform].strip('"')) or os.path.isfile(self.fileResponse['hq'].strip('"')): 400 | # Set the value of pathSuccessFlag to green text File found 401 | self.pathSuccessFlag.setValue('File found') 402 | else: 403 | # Set the value of pathSuccessFlag to green text File not found 404 | self.pathSuccessFlag.setValue('File not found') 405 | # Reveal the file result flag 406 | self.pathSuccessFlag.setVisible(True) 407 | 408 | # elif knob is self.installDirectoryCurrent: 409 | # self.installDirectory.setValue(nuke.EXE_PATH) 410 | 411 | elif knob is self.installDirectoryCurrent: 412 | self.installDirectory.setValue(self.installDirectoryChoicesKey[self.installDirectoryCurrent.value()]) 413 | 414 | elif knob is self.assign_to: 415 | if self.assign_to.value() == "Selected Clients": 416 | self.clientList.setVisible(True) 417 | self.clientGet.setVisible(True) 418 | self.clientGroupGet.setVisible(False) 419 | elif self.assign_to.value() == "Clients from Listed Groups": 420 | self.clientList.setVisible(True) 421 | self.clientGet.setVisible(False) 422 | self.clientGroupGet.setVisible(True) 423 | elif self.assign_to.value() == "Any Client": 424 | self.clientList.setVisible(False) 425 | self.clientGet.setVisible(False) 426 | self.clientGroupGet.setVisible(False) 427 | 428 | elif knob is self.clientGet: 429 | # Get a response from the function of the button that was pressed 430 | self.clientResponse = getClients(self.serverAddress.value()) 431 | self.cleanList = {} 432 | 433 | for i in range(0, len(self.clientResponse)): 434 | self.cleanList[self.clientResponse[i]] = self.clientResponse[i] 435 | 436 | # Call the function for popping up the popup 437 | self.clientInterrumList = self.popUpPanel(self.clientResponse) 438 | 439 | elif knob is self.clientGroupGet: 440 | # Get a response from the function of the button that was pressed 441 | self.clientGroupResponse = getClientGroups(self.serverAddress.value()) 442 | self.cleanList = {} 443 | 444 | for i in range(0, len(self.clientGroupResponse)): 445 | self.cleanList[self.clientGroupResponse[i]['name']] = self.clientGroupResponse[i] 446 | 447 | # Call the function for popping up the popup 448 | self.clientInterrumList = self.popUpPanel(self.clientGroupResponse) 449 | 450 | elif knob is self.submitJob: 451 | self.parms = self.finaliseJobSpecs() 452 | self.childJobs = [] 453 | for i in range(int(self.fRange.value().split('-')[0]), int(self.fRange.value().split('-')[1])+1, 10): 454 | self.childJobs.append(buildChildJobs("Frame Range_"+str(i)+"-"+str(i+9), buildOSCommands(self.parms['hfs'], i, i+9, self.fileResponse), self.parms['priority'])) 455 | try: 456 | self.mainJob = buildContainingJobSpec(self.parms['name'], self.parms, self.childJobs) 457 | except: 458 | raise ValueError("Frame range is invalid") 459 | self.jobResponse = sendJob(self.parms['hq_server'], self.mainJob) 460 | if self.jobResponse: 461 | print "Job submission successful" 462 | else: 463 | print "Failed" 464 | 465 | def hQRootFilePathCheck(self): 466 | (self.hqRoot, self.platform) = getHQROOT(self.serverAddress.value()) 467 | self.filePathValue = self.filePath.value() 468 | # See if the file path has $HQROOT in it 469 | if "$HQROOT" in self.filePathValue: 470 | # Set the platforms file value to the resolved path 471 | self.fileResponse = { 472 | 'windows': '"'+self.filePathValue.replace("$HQROOT", self.hqRoot['windows']).replace('/', '\\')+'"', 473 | 'macosx': '"'+self.filePathValue.replace("$HQROOT", self.hqRoot['macosx'])+'"', 474 | 'linux': '"'+self.filePathValue.replace("$HQROOT", self.hqRoot['linux'])+'"', 475 | 'hq': '"'+self.filePathValue+'"' 476 | } 477 | elif self.hqRoot['linux'] in self.filePathValue or self.hqRoot['macosx'] in self.filePathValue or \ 478 | self.hqRoot['windows'].replace('\\', '/') in self.filePathValue: 479 | self.fileResponse = { 480 | 'windows': self.filePathValue.replace(self.hqRoot[self.platform], '"'+self.hqRoot['windows']).replace('/', '\\')+'"', 481 | 'macosx': self.filePathValue.replace(self.hqRoot[self.platform], '"'+self.hqRoot['macosx'])+'"', 482 | 'linux': self.filePathValue.replace(self.hqRoot[self.platform], '"'+self.hqRoot['linux'])+'"', 483 | 'hq': self.filePathValue.replace(self.hqRoot['linux'], '"'+"$HQROOT").replace(self.hqRoot['macosx'], 484 | "$HQROOT").replace( 485 | self.hqRoot['windows'], "$HQROOT")+'"' 486 | } 487 | else: 488 | self.fileResponse = { 489 | 'windows': '"'+self.filePathValue.replace('/', '\\')+'"', 490 | 'macosx': '"'+self.filePathValue.replace('\\', '/')+'"', 491 | 'linux': '"'+self.filePathValue.replace('\\', '/')+'"', 492 | 'hq': '"'+self.filePathValue+'"' 493 | } 494 | 495 | def finaliseJobSpecs(self): 496 | self.finaliseClientList() 497 | try: 498 | if self.hqRoot: 499 | pass 500 | except: 501 | self.knobChanged(self.filePathCheck) 502 | return getBaseParameters(self.jobNameSet(self.jobName.value(), self.fileResponse['hq']), self.assigned_to, 503 | self.clientFullList, self.clientGroupFullList, 504 | self.cleanInstallEXE(self.installDirectory.value()), self.serverAddress.value(), 505 | self.priority.value()) 506 | 507 | def cleanInstallEXE(self, unusableDir): 508 | if self.installDirectoryCurrent.value() == "HQRoot install directory": 509 | usableDir = { 510 | 'linux': posixpath.join(unusableDir.replace("$HQROOT", '"'+self.hqRoot['linux']).replace("$OS", 'linux'), os.path.split(nuke.EXE_PATH)[1])+'"', 511 | 'windows': posixpath.join(unusableDir.replace("$HQROOT", '"'+self.hqRoot['windows']).replace("$OS", 'windows'), os.path.split(nuke.EXE_PATH)[1]+'.exe"').replace('/', '\\'), 512 | 'macosx': posixpath.join(unusableDir.replace("$HQROOT", '"'+self.hqRoot['macosx']).replace("$OS", 'macosx'), os.path.split(nuke.EXE_PATH)[1])+'"' 513 | } 514 | elif self.installDirectoryCurrent.value() == "Default install directory": 515 | nukeDirName = 'Nuke' + str(nuke.NUKE_VERSION_STRING) 516 | ### TODO: Figure out the Mac default install directory 517 | usableDir = { 518 | 'linux': posixpath.join(posixpath.join('"/usr/local', nukeDirName), os.path.split(nuke.EXE_PATH)[1])+'"', 519 | 'windows': posixpath.join(posixpath.join('"C:\Program Files', nukeDirName), os.path.split(nuke.EXE_PATH)[1] + '.exe"').replace('/', '\\'), 520 | 'macosx': posixpath.join(posixpath.join('"No idea', nukeDirName), os.path.split(nuke.EXE_PATH)[1])+'"' 521 | } 522 | return usableDir 523 | 524 | def jobNameSet(self, jobName, FilePath): 525 | if jobName == "": 526 | return "Render -> NK: "+FilePath 527 | else: 528 | return jobName 529 | 530 | def finaliseClientList(self): 531 | self.clientGroupFullList = '' 532 | self.clientFullList = '' 533 | if self.assign_to.value() == "Any Client": 534 | self.assigned_to = self.clientSelectionTypes["Any Client"] 535 | elif self.assign_to.value() == "Selected Clients": 536 | self.assigned_to = self.clientSelectionTypes["Selected Clients"] 537 | # Create a string using the clientList names that can be sent with the job 538 | self.clientFullList = self.clientList.value() 539 | elif self.assign_to.value() == "Clients from Listed Groups": 540 | self.assigned_to = self.clientSelectionTypes["Clients from Listed Groups"] 541 | # Create a list using the clientList names to generate a list that can be sent with the job 542 | self.clientGroupFullList = self.clientList.value() 543 | 544 | def popUpPanel(self, response): 545 | # If there is a response do thing 546 | if response: 547 | # reveal the client list and the client select button 548 | self.clientSelectPopUp = clientSelectionPanel() 549 | self.clientSelectPopUp.clientInterrumList.setValue(', '.join(self.cleanList.keys())) 550 | self.clientSelectPopUp.showModal() 551 | # set the value of clientList to the interrum string generated from the array for loop 552 | self.clientList.setValue(self.clientSelectPopUp.clientInterrumList.value()) 553 | 554 | class clientSelectionPanel(nukescripts.PythonPanel): 555 | def __init__(self): 556 | nukescripts.PythonPanel.__init__(self, 'Client select') 557 | 558 | # Setup a multiline client list that appears when clientGet is run 559 | self.clientInterrumList = nuke.Multiline_Eval_String_Knob('clientInterrumList', 'Client List: ') 560 | self.addKnob(self.clientInterrumList) 561 | 562 | # def knobChanged(self, knob): 563 | # if knob == "OK": 564 | # print "OK" 565 | # else: 566 | # print knob 567 | # print "KNOB ^^^" 568 | # return self.clientInterrumList 569 | 570 | #class nukeAdvancedWindow(nukescripts.PythonPanel): 571 | # def __init__(self): 572 | # # Init the panel with a name 573 | # nukescripts.PythonPanel.__init__(self, "hQueue Advanced Nuke render submission panel") 574 | 575 | ################################################################################################################################################################################################## 576 | ############################################ Main code 577 | ################################################################################################################################################################################################## 578 | 579 | def runGui(): 580 | currentWindow = nukeWindow() 581 | currentWindow.showModal() -------------------------------------------------------------------------------- /render.py: -------------------------------------------------------------------------------- 1 | ### Import needed modules and components 2 | import os 3 | import os.path 4 | import sys 5 | import xmlrpclib 6 | 7 | ### Window setup 8 | class programCheck: 9 | 10 | ### Initiate the job class by first building the window 11 | def __init__(self): 12 | self.gui = self.check() 13 | 14 | def check(self): 15 | ### Try and initiate in a Nuke window or a Maya window and error if neither are found. 16 | try: 17 | import nuke 18 | import nukescripts 19 | return(nukeWindow) 20 | except: 21 | try: 22 | import maya.cmds as cmds 23 | return(mayaWindow) 24 | except: 25 | ### Normally I'd run a raise RuntimeError but it's hard for non-programmers to debug so I'll print an error first then raise RuntimeError. 26 | print("No supported window manager available(Nuke, Maya)\n") 27 | raise RuntimeError("No supported window manager available(Nuke, Maya)\n") 28 | 29 | class hqRop(object): 30 | 31 | ### This chunk of code is lifted from hqrop.py and rewritten as neccessary ####################################################################### 32 | def submitJob(parms, submit_function): 33 | """Submits a job with the given parameters and function after checking to 34 | see if the project files need to be copied or not. 35 | submit_function will be passed parms 36 | """ 37 | if self.parms["hip_action"] == "copy_to_shared_folder": 38 | self.copyToSharedFolder(parms, submit_function) 39 | else: 40 | self.submit_function(self.parms) 41 | 42 | def getBaseParameters(hq_job_name, ): 43 | """Return a dictionary of the base parameters used by all HQueue ROPs.""" 44 | parms = { 45 | "name" : hq_job_name 46 | "assign_to" : hou.parm("hq_assign_to").evalAsString(), 47 | "clients": hou.ch("hq_clients").strip(), 48 | "client_groups" : hou.ch("hq_client_groups").strip(), 49 | "dirs_to_create": getDirectoriesToCreate(hou.pwd(), expand=False), 50 | "environment" : getEnvVariablesToSet(hou.pwd()), 51 | "hfs": getUnexpandedStringParmVal(hou.parm("hq_hfs")), 52 | "hq_server": hq_server 53 | "open_browser": hou.ch("hq_openbrowser"), 54 | "priority": hou.ch("hq_priority"), 55 | "hip_action": hou.ch("hq_hip_action"), 56 | "autosave": hou.ch("hq_autosave"), 57 | "warn_unsaved_changes": hou.ch("hq_warn_unsaved_changes"), 58 | "report_submitted_job_id": hou.ch("hq_report_submitted_job_id"), 59 | } 60 | 61 | addSubmittedByParm(parms) 62 | 63 | # Get the .hip file path. 64 | parms["hip_file"] = hou.hipFile.path() 65 | 66 | # Setup email parameters 67 | if hou.ch("hq_will_email"): 68 | # We remove the whitespace around each email entry 69 | parms["emailTo"] = ",".join([email.strip() for email in 70 | hou.ch("hq_emailTo").split(',')]) 71 | 72 | # The values added to parms for why emails should be sent are 73 | # place holders. 74 | # When jobSend is ran they are updated as we need a server connection 75 | # to add the values we want. 76 | email_reasons = [] 77 | 78 | if hou.ch("hq_email_on_start"): 79 | email_reasons.append("start") 80 | 81 | if hou.ch("hq_email_on_success"): 82 | email_reasons.append("success") 83 | 84 | if hou.ch("hq_email_on_failure"): 85 | email_reasons.append("failure") 86 | 87 | if hou.ch("hq_email_on_pause"): 88 | email_reasons.append("pause") 89 | 90 | if hou.ch("hq_email_on_resume"): 91 | email_reasons.append("resume") 92 | 93 | if hou.ch("hq_email_on_reschedule"): 94 | email_reasons.append("reschedule") 95 | 96 | if hou.ch("hq_email_on_priority_change"): 97 | email_reasons.append("priority change") 98 | 99 | parms["emailReasons"] = email_reasons 100 | else: 101 | parms["emailTo"] = "" 102 | # An empty list for emailReasons means no email will be sent 103 | parms["emailReasons"] = [] 104 | 105 | 106 | return parms 107 | 108 | def addSubmittedByParm(parms): 109 | """Adds who submits the job to the base parameters.""" 110 | try: 111 | parms["submittedBy"] = getpass.getuser() 112 | except (ImportError, KeyError): 113 | pass 114 | 115 | def setupEmailReasons(server_connection, job_spec): 116 | """Changes the placeholder string in the emailReasons part of the job 117 | spec with the actual string that will be sent. 118 | Gives a warning message if any of the options do not exist on the 119 | server side. 120 | """ 121 | placeholder_reasons = job_spec["emailReasons"] 122 | new_reasons = [] 123 | failure_messages = [] 124 | 125 | 126 | for reason in placeholder_reasons: 127 | try: 128 | if reason == "start": 129 | new_reasons.extend( 130 | server_connection.getStartedJobEventNames()) 131 | elif reason == "success": 132 | new_reasons.extend(server_connection.getSucceededStatusNames()) 133 | elif reason == "failure": 134 | new_reasons.extend(server_connection.getFailedJobStatusNames()) 135 | elif reason == "pause": 136 | new_reasons.extend(server_connection.getPausedJobStatusNames()) 137 | elif reason == "resume": 138 | new_reasons.extend(server_connection.getResumedEventNames()) 139 | elif reason == "reschedule": 140 | new_reasons.extend( 141 | server_connection.getRescheduledEventNames()) 142 | elif reason == "priority change": 143 | new_reasons.extend( 144 | server_connection.getPriorityChangedEventNames()) 145 | else: 146 | raise Exception("Did not recieve valid placeholder reason " + 147 | reason + ".") 148 | except xmlrpclib.Fault: 149 | failure_messages.append(string.capwords(reason)) 150 | 151 | 152 | if failure_messages: 153 | if hou.isUIAvailable(): 154 | base_failure_message = ("The server does not support sending " 155 | + "email for the following reasons:") 156 | failure_message = "\n".join(failure_messages) 157 | failure_message = "\n".join([base_failure_message, failure_message]) 158 | hou.ui.displayMessage(failure_message, 159 | severity = hou.severityType.Warning) 160 | 161 | 162 | job_spec["emailReasons"] = ",".join(new_reasons) 163 | 164 | def expandHQROOT(self, path, hq_server): 165 | """Return the given file path with instances of $HQROOT expanded 166 | out to the mount point for the HQueue shared folder root.""" 167 | # Get the HQueue root path. 168 | self.hq_root = self.getHQROOT(hq_server) 169 | if self.hq_root is None: 170 | return path 171 | 172 | expanded_path = path.replace("$HQROOT", self.hq_root) 173 | return expanded_path 174 | 175 | 176 | def getHQROOT(self, hq_server): 177 | """Query the HQueue server and return the mount point path 178 | to the HQueue shared folder root. 179 | Return None if the path cannot be retrieved from the server. 180 | """ 181 | # Identify this machine's platform. 182 | platform = sys.platform 183 | if platform.startswith("win"): 184 | platform = "windows" 185 | elif platform.startswith("linux"): 186 | platform = "linux" 187 | elif platform.startswith("darwin"): 188 | platform = "macosx" 189 | 190 | # Connect to the HQueue server. 191 | s = self.hQServerConnect(hq_server) 192 | if s is None: 193 | return None 194 | 195 | try: 196 | # Get the HQ root. 197 | self.hq_root = s.getHQRoot(platform) 198 | except: 199 | print("Could not retrieve $HQROOT from '" + hq_server + "'.") 200 | return None 201 | 202 | return self.hq_root 203 | 204 | def buildContainingJobSpec(job_name, hq_cmds, parms, child_job, 205 | apply_conditions_to_children=True): 206 | """Return a job spec that submits the child job and waits for it. 207 | The job containing the child job will not run any command. 208 | """ 209 | job = { 210 | "name": job_name, 211 | "priority": child_job["priority"], 212 | "environment": {"HQCOMMANDS": hutil.json.utf8Dumps(hq_cmds)}, 213 | "command": "", 214 | "children": [child_job], 215 | "emailTo": parms["emailTo"], 216 | "emailReasons": parms["emailReasons"], 217 | } 218 | 219 | if "submittedBy" in parms: 220 | job["submittedBy"] = parms["submittedBy"] 221 | 222 | # Add job assignment conditions if any. 223 | conditions = { "clients":"host", "client_groups":"hostgroup" } 224 | for cond_type in conditions.keys(): 225 | job_cond_keyword = conditions[cond_type] 226 | if parms["assign_to"] == cond_type: 227 | job[job_cond_keyword] = parms[cond_type] 228 | if apply_conditions_to_children: 229 | for child_job in job["children"]: 230 | child_job[job_cond_keyword] = parms[cond_type] 231 | 232 | return job 233 | 234 | 235 | def getHQueueCommands(remote_hfs, num_cpus=0): 236 | """Return the dictionary of commands to start hython, Python, and mantra. 237 | Return None if an error occurs when reading the commands 238 | from the HQueueCommands file. 239 | If `num_cpus` is greater than 0, then we add a -j option 240 | to each command so that the application is run with a maximum 241 | number of threads. 242 | """ 243 | 244 | self.hq_cmds = {} 245 | self.cmd_file = open(cmd_file_path, "r") 246 | self.cmd_name = None 247 | self.cmds = None 248 | self.continue_cmd = False 249 | for line in self.cmd_file: 250 | line = line.strip() 251 | 252 | # Check if we need to continue the current command. 253 | if self.continue_cmd: 254 | # Add line to current command. 255 | self.cmds = self.addLineToCommands(cmds, line) 256 | if line[-1] == "\\": 257 | self.continue_cmd = True 258 | else: 259 | self.cmds = self.finalizeCommands(cmd_name, cmds, remote_hfs, num_cpus) 260 | if self.cmds is None: 261 | return None 262 | self.hq_cmds[cmd_name] = self.cmds 263 | self.continue_cmd = False 264 | continue 265 | 266 | # Ignore comments and empty lines. 267 | if line.startswith("#") or line.strip() == "": 268 | continue 269 | 270 | # Ignore lines with no command assignment. 271 | eq_op_loc = line.find("=") 272 | if eq_op_loc < 0: 273 | continue 274 | 275 | # Start a new command. 276 | cmd_name = line[0:eq_op_loc].strip() 277 | cmds = None 278 | line = line[eq_op_loc+1:] 279 | 280 | # Add line to current command. 281 | cmds = addLineToCommands(cmds, line) 282 | if line[-1] == "\\": 283 | continue_cmd = True 284 | else: 285 | cmds = _finalizeCommands(cmd_name, cmds, remote_hfs, num_cpus) 286 | if cmds is None: 287 | return None 288 | hq_cmds[cmd_name] = cmds 289 | continue_cmd = False 290 | 291 | return hq_cmds 292 | 293 | def getHQueueCommands(remote_hfs, num_cpus=0): 294 | """Return the dictionary of commands to start hython, Python, and mantra. 295 | Return None if an error occurs when reading the commands 296 | from the HQueueCommands file. 297 | If `num_cpus` is greater than 0, then we add a -j option 298 | to each command so that the application is run with a maximum 299 | number of threads. 300 | """ 301 | import hou 302 | # HQueueCommands will exist in the Houdini path. 303 | cmd_file_path = hou.findFile( 304 | "soho/python%d.%d/HQueueCommands" % sys.version_info[:2]) 305 | 306 | hq_cmds = {} 307 | cmd_file = open(cmd_file_path, "r") 308 | cmd_name = None 309 | cmds = None 310 | continue_cmd = False 311 | for line in cmd_file: 312 | line = line.strip() 313 | 314 | # Check if we need to continue the current command. 315 | if continue_cmd: 316 | # Add line to current command. 317 | cmds = _addLineToCommands(cmds, line) 318 | if line[-1] == "\\": 319 | continue_cmd = True 320 | else: 321 | cmds = _finalizeCommands(cmd_name, cmds, remote_hfs, num_cpus) 322 | if cmds is None: 323 | return None 324 | hq_cmds[cmd_name] = cmds 325 | continue_cmd = False 326 | continue 327 | 328 | # Ignore comments and empty lines. 329 | if line.startswith("#") or line.strip() == "": 330 | continue 331 | 332 | # Ignore lines with no command assignment. 333 | eq_op_loc = line.find("=") 334 | if eq_op_loc < 0: 335 | continue 336 | 337 | # Start a new command. 338 | cmd_name = line[0:eq_op_loc].strip() 339 | cmds = None 340 | line = line[eq_op_loc+1:] 341 | 342 | # Add line to current command. 343 | cmds = _addLineToCommands(cmds, line) 344 | if line[-1] == "\\": 345 | continue_cmd = True 346 | else: 347 | cmds = _finalizeCommands(cmd_name, cmds, remote_hfs, num_cpus) 348 | if cmds is None: 349 | return None 350 | hq_cmds[cmd_name] = cmds 351 | continue_cmd = False 352 | 353 | return hq_cmds 354 | 355 | 356 | def addLineToCommands(cmds, line): 357 | """Adds the given line to the command string. 358 | This is a helper function for getHQueueCommands. 359 | """ 360 | line = line.strip() 361 | if line[-1] == "\\": 362 | line = line[:-1].strip() 363 | 364 | if cmds is None: 365 | cmds = line 366 | else: 367 | cmds = cmds + " " + line 368 | 369 | return cmds 370 | 371 | 372 | def finalizeCommands(cmd_name, cmds, remote_hfs, num_cpus): 373 | """Perform final touches to the given commands. 374 | This is a helper function for getHQueueCommands. 375 | """ 376 | if cmd_name.endswith("Windows"): 377 | remote_hfs = hutil.file.convertToWinPath(remote_hfs, var_notation="!") 378 | 379 | # Replace HFS instances. 380 | cmds = cmds.replace("%(HFS)s", remote_hfs) 381 | 382 | # Attempt to replace other variable instances with 383 | # environment variables. 384 | try: 385 | cmds = cmds % os.environ 386 | except KeyError, e: 387 | print("Use of undefined variable in " + cmd_name + ".", e) 388 | return None 389 | 390 | # Strip out wrapper quotes, if any. 391 | # TODO: Is this needed still? 392 | if (cmds[0] == "\"" and cmds[-1] == "\"") \ 393 | or (cmds[0] == "'" and cmds[-1] == "'"): 394 | cmds = cmds[1:] 395 | cmds = cmds[0:-1] 396 | 397 | # Add the -j option to hython and Mantra commands. 398 | if num_cpus > 0 and ( 399 | cmd_name.startswith("hython") or cmd_name.startswith("mantra")): 400 | cmds += " -j" + str(num_cpus) 401 | 402 | return cmds 403 | 404 | def sendJob(hq_server, main_job, open_browser, report_submitted_job_id): 405 | """Send the given job to the HQ server. 406 | If the ui is available, either display the HQ web interface or display the 407 | id of the submitted job depending on the value of `open_browser` and 408 | 'report_submitted_job_id'. 409 | """ 410 | import hou 411 | s = _connectToHQServer(hq_server) 412 | if s is None: 413 | return False 414 | 415 | # We do this here as we need a server connection 416 | setupEmailReasons(s, main_job) 417 | 418 | # If we're running as an HQueue job, make that job our parent job. 419 | try: 420 | ids = s.newjob(main_job, os.environ.get("JOBID")) 421 | except Exception, e: 422 | displayError("Could not submit job to '" + hq_server + "'.", e) 423 | return False 424 | 425 | # Don't open a browser window or try to display a popup message if we're 426 | # running from Houdini Batch. 427 | if not hou.isUIAvailable(): 428 | return True 429 | 430 | jobId = ids[0] 431 | if not open_browser and report_submitted_job_id: 432 | buttonindex = hou.ui.displayMessage("Your job has been submitted (Job %i)." % jobId, buttons=("Open HQueue", "Ok"), default_choice=1 ) 433 | 434 | if buttonindex == 0: 435 | open_browser = True 436 | 437 | if open_browser: 438 | url = "%(hq_server)s" % locals() 439 | webbrowser.open(url) 440 | 441 | return True 442 | 443 | def hqServerProxySetup(self, hq_server): 444 | """Sets up a xmlrpclib server proxy to the given HQ server.""" 445 | if not hq_server.startswith("http://"): 446 | full_hq_server_path = "http://%s" % hq_server 447 | else: 448 | full_hq_server_path = hq_server 449 | 450 | return xmlrpclib.ServerProxy(full_hq_server_path, allow_none=True) 451 | 452 | def doesHQServerExists(self, hq_server): 453 | """Check that the given HQ server can be connected to. 454 | Returns True if the server exists and False if it does not. Furthermore, 455 | it will display an error message if it does not exists.""" 456 | server = self.hqServerProxySetup(hq_server) 457 | return self.hQServerPing(server, hq_server) 458 | 459 | def hQServerConnect(self, hq_server): 460 | """Connect to the HQueue server and return the proxy connection.""" 461 | s = self.hqServerProxySetup(hq_server) 462 | 463 | if self.hQServerPing(s, hq_server): 464 | return s 465 | else: 466 | return None 467 | 468 | def hQServerPing(self, server, hq_server): 469 | try: 470 | server.ping() 471 | return True 472 | except: 473 | print("Could not connect to '" + hq_server + "'.\n\n" 474 | + "Make sure that the HQueue server is running\n" 475 | + "or change the value of 'HQueue Server'.", 476 | TypeError("this is a type error")) 477 | 478 | return False 479 | 480 | def getClients(self, hq_server): 481 | """Return a list of all the clients registered on the HQueue server. 482 | Return None if the client list could not be retrieved from the server. 483 | """ 484 | s = self.hQServerConnect(hq_server) 485 | 486 | if s is None: 487 | return None 488 | 489 | try: 490 | self.client_ids = None 491 | self.attribs = ["id", "hostname"] 492 | self.clients = s.getClients(self.client_ids, self.attribs) 493 | except: 494 | print("Could not retrieve client list from '" + hq_server + "'.") 495 | return None 496 | 497 | return [self.client["hostname"] for self.client in self.clients] 498 | 499 | def getClientGroups(self, hq_server): 500 | """Return a list of all the client groups on the HQueue server. 501 | Return None if the client group list could not be retrieved from the server. 502 | """ 503 | s = self.hQServerConnect(hq_server) 504 | if s is None: 505 | return None 506 | 507 | try: 508 | self.client_groups = s.getClientGroups() 509 | except: 510 | print("Could not retrieve client group list from '" 511 | + hq_server + "'.") 512 | return None 513 | 514 | return self.client_groups 515 | 516 | ################################################################################################################################################## 517 | 518 | ### Run to open a window in Nuke 519 | class nukeWindow(nukescripts.PythonPanel): 520 | 521 | ### Initialise the hqRop to be callable 522 | serverRop = hqRop() 523 | 524 | def __init__(self): 525 | ### Init the panel with a name 526 | nukescripts.PythonPanel.__init__(self, "hQueue Nuke render submission panel") 527 | ### Gets the absolute file path for the currently open Nuke script, if nothing open then defaults to install directory 528 | self.absoluteFilePath = os.path.abspath(nuke.value("root.name")) 529 | ### Setup a text box for the server address to be input into 530 | self.serverAddress = nuke.String_Knob('serverAddress', 'Server Address: ') 531 | self.addKnob(self.serverAddress) 532 | ### Setup a button to test the server address which will reveal the Connection Successful text 533 | self.addressTest = nuke.PyScript_Knob("addressTest", "Test the server address", "") 534 | self.addKnob(self.addressTest) 535 | ### Create addressSuccessFlag flag that is hidden until the server is successfully pinged 536 | self.addressSuccessFlag = nuke.Text_Knob('addressSuccessFlag', '', 'Connection Successful') 537 | self.addressSuccessFlag.setFlag(nuke.STARTLINE) 538 | self.addressSuccessFlag.setVisible(False) 539 | self.addKnob(self.addressSuccessFlag) 540 | ### Get the filepath from self.absoluteFilePath and put it into a text box 541 | self.filePath = nuke.String_Knob('filePath', 'File Path: ', self.absoluteFilePath) 542 | self.addKnob(self.filePath) 543 | ### Create a button that will test the file path for an nuke script 544 | self.filePathCheck = nuke.PyScript_Knob("filePathCheck", "Test the File Path", "") 545 | self.addKnob(self.filePathCheck) 546 | ### Create pathSuccessFlag flag that is hidden until the file path is verified 547 | self.pathSuccessFlag = nuke.Text_Knob('pathSuccessFlag', '', 'Connection Successful') 548 | self.pathSuccessFlag.setFlag(nuke.STARTLINE) 549 | self.pathSuccessFlag.setVisible(False) 550 | self.addKnob(self.pathSuccessFlag) 551 | ### Setup the get client list button, which will use hqrop functions 552 | self.clientGet = nuke.PyScript_Knob("clientGet", "Get client list", "") 553 | self.addKnob(self.clientGet) 554 | ### Setup the get client groups button, which will use hqrop functions 555 | self.clientGroupGet = nuke.PyScript_Knob("clientGroupGet", "Get client groups", "") 556 | self.addKnob(self.clientGroupGet) 557 | ### Setup a save client selection button, this hides the client list and itself 558 | self.clientSelect = nuke.PyScript_Knob("clientSelect", "Save client selection", "") 559 | self.clientSelect.setVisible(False) 560 | self.addKnob(self.clientSelect) 561 | ### Setup a multiline client list that appears when clientGet is run 562 | self.clientList = nuke.Multiline_Eval_String_Knob('clientList', 'Client List: ') 563 | self.clientList.setFlag(nuke.STARTLINE) 564 | self.clientList.setVisible(False) 565 | self.addKnob(self.clientList) 566 | ### Setup a frame range with the default frame range of the scene 567 | self.fRange = nuke.String_Knob('fRange', 'Track Range', '%s-%s' % (nuke.root().firstFrame(), nuke.root().lastFrame())) 568 | self.addKnob(self.fRange) 569 | 570 | ### Set the minimum size of the python panel 571 | self.setMinimumSize(500, 600) 572 | 573 | def knobChanged(self, knob): 574 | ### When you press a button run the command attached to that button 575 | self.response = "" 576 | ### Figure out which knob was changed 577 | if knob is self.filePathCheck: 578 | ### See if the file path has $HQROOT in it 579 | if "$HQROOT" in self.filePath.value(): 580 | ### Get a response from the function of the button that was pressed 581 | self.cleanPath = self.serverRop.expandHQROOT(self.filePath.value(), self.serverAddress.value()) 582 | self.response = self.filePath.value() 583 | ### Clean the file path before checking it exists 584 | if os.path.isfile(self.response): 585 | ### Set the value of pathSuccessFlag to green text Connection Successful 586 | self.pathSuccessFlag.setValue('File found') 587 | 588 | else: 589 | ### Set the value of pathSuccessFlag to green text Connection failed 590 | self.pathSuccessFlag.setValue('File not found') 591 | ### Set the address success text flag to visible 592 | self.pathSuccessFlag.setVisible(True) 593 | elif self.serverRop.getHQROOT(self.serverAddress.value()) in self.filePath.value(): 594 | self.hqroot = self.serverRop.getHQROOT(self.serverAddress.value()) 595 | self.response = self.filePath.value().replace(self.hqroot, "$HQROOT") 596 | ### Check whether file exists 597 | if os.path.isfile(self.filePath.value()): 598 | ### Set the value of pathSuccessFlag to green text Connection Successful 599 | self.pathSuccessFlag.setValue('File found') 600 | ### 601 | else: 602 | ### Set the value of pathSuccessFlag to green text Connection failed 603 | self.pathSuccessFlag.setValue('File not found') 604 | else: 605 | ### Check whether file exists 606 | if os.path.isfile(self.filePath.value()): 607 | ### Set the value of pathSuccessFlag to green text Connection Successful 608 | self.pathSuccessFlag.setValue('File found but not in shared folder, might not work for all OS') 609 | else: 610 | ### Set the value of pathSuccessFlag to green text Connection failed 611 | self.pathSuccessFlag.setValue('File not found') 612 | self.pathSuccessFlag.setVisible(True) 613 | ### Take the output of self.response and make it into a hqFilePath for submission to the server 614 | self.hqFilePath = self.response 615 | elif knob is self.addressTest: 616 | ### Get a response from the function of the button that was pressed 617 | self.response = self.serverRop.doesHQServerExists(self.serverAddress.value()) 618 | ### If there is a response do thing 619 | if self.response == True: 620 | ### Set the value of addressSuccessFlag to green text Connection Successful 621 | self.addressSuccessFlag.setValue('Connection Successful') 622 | else: 623 | ### Set the value of addressSuccessFlag to green text Connection failed 624 | self.addressSuccessFlag.setValue('Connection failed') 625 | ### Set the address success text flag to visible 626 | self.addressSuccessFlag.setVisible(True) 627 | elif knob is self.clientGet: 628 | ### Get a response from the function of the button that was pressed 629 | self.response = self.serverRop.getClients(self.serverAddress.value()) 630 | ### If there is a response do thing 631 | if self.response: 632 | ### reveal the client list and the client select button 633 | self.clientList.setVisible(True) 634 | self.clientSelect.setVisible(True) 635 | ### Generate a interrum string for future use 636 | self.clientGetInterrum = "" 637 | ### For loop to extract the computer names into a string with newlines 638 | for x in range(len(self.response)): 639 | self.clientGetInterrum+=str(self.response[x]+"\n") 640 | ### set the value of clientList to the interrum string generated from the array for loop and remove the last newline for neatness 641 | self.clientList.setValue(self.clientGetInterrum[:-1]) 642 | else: 643 | ### Just reveal the empty lists 644 | self.clientList.setVisible(True) 645 | self.clientSelect.setVisible(True) 646 | elif knob is self.clientGroupGet: 647 | ### Get a response from the function of the button that was pressed 648 | self.response = self.serverRop.getClientGroups(self.serverAddress.value()) 649 | ### If there is a response do thing 650 | if self.response: 651 | ### reveal the client list and the client select button 652 | self.clientList.setVisible(True) 653 | self.clientSelect.setVisible(True) 654 | ### Generate a interrum string for future use 655 | self.clientGetGroupInterrum = "" 656 | ### For loop to extract the computer names into a string with newlines 657 | for x in self.response: 658 | self.clientGetGroupInterrum+=str(x["name"]+"\n") 659 | ### set the value of clientList to the interrum string generated from the array for loop and remove the last newline for neatness 660 | self.clientList.setValue(self.clientGetGroupInterrum[:-1]) 661 | else: 662 | ### Just reveal the empty lists 663 | self.clientList.setVisible(True) 664 | self.clientSelect.setVisible(True) 665 | elif knob is self.clientSelect: 666 | ### Hide the client selection button and the client list 667 | self.clientSelect.setVisible(False) 668 | self.clientList.setVisible(False) 669 | self.clientSelectInterrum = self.clientList.value().replace("\n", " ") 670 | 671 | ### Run to open a window in Maya 672 | class mayaWindow(object): 673 | 674 | ### Create a window in the maya gui 675 | def __init__(self): 676 | print("Not currently supported") 677 | sys.exit 678 | 679 | class nativeWindow(object): 680 | 681 | ### Create a window in the native gui 682 | def __init__(self): 683 | print("Not currently supported") 684 | sys.exit 685 | 686 | ######################################################################################################################################################################################################## 687 | ############################################ Main code 688 | ######################################################################################################################################################################################################## 689 | 690 | ### Run the programCheck class method 691 | guiProgram = programCheck() 692 | 693 | ### Not sure if I want to just call guiProgram.gui().showModal() or assign it to a variable then call that. Will go with the assign variable for now 694 | ### Attempt to initiate the current window 695 | #guiProgram.gui().showModal() 696 | currentWindow = guiProgram.gui() 697 | currentWindow.showModal() --------------------------------------------------------------------------------