Module modoCmdrangeBackEnd
[hide private]
[frames] | no frames]

Source Code for Module modoCmdrangeBackEnd

  1  '''
 
  2      A backend that writes out a modo command file for an agenda item, then redirects that command
 
  3      file to the modo_cl executable:
 
  4          
 
  5          modo_cl < commands.txt
 
  6  ''' 
  7  #======================================
 
  8  #  $Revision: #5 $
 
  9  #  $Change: 16972 $
 
 10  #======================================
 
 11  
 
 12  
 
 13  import os 
 14  import sys 
 15  import re 
 16  import time  
 17  import pprint 
 18  import logging 
 19  import tempfile 
 20  
 
 21  import qb.backend.commandBackEnd 
 22  import qb.backend.utils as backendUtils 
 23  import qb.utils 
 24  
 
 25  
 
 26  MODO_CMD_TEMPLATE = '''
 
 27  log.toConsole true
 
 28  log.toConsoleRolling true
 
 29  scene.open "%(scene)s" normal
 
 30  pref.value render.threads %(renderThreads)s
 
 31  pref.value render.useNetwork False
 
 32  pref.value lanservices.sendAssetsForNetworkJob false
 
 33  @ChangeRenderFrameRange.pl %(fStart)s %(fEnd)s %(fStep)s
 
 34  %(renderDirScript)s %(renderOutputDir)s
 
 35  render.animation %(renderOutput)s %(imageFormat)s {*}
 
 36  app.quit 
 
 37  ''' 
 38  
 
39 -class ModoCmdRangeBackEnd(qb.backend.commandBackEnd.CommandBackEnd):
40 ''' 41 A backend for a Modo using the cmdrange approach, starts Modo for every agenda item 42 ''' 43
44 - def __init__(self, job):
45 super(ModoCmdRangeBackEnd, self).__init__(job) 46 47 # submit the job with job['package']['dev'] = True to set the logging level to DEBUG 48 self.logging = logging.getLogger('%s' % self.__class__.__name__)
49
50 - def executeWork(self):
51 ''' 52 Now that we've got a job from the supervisor and have fired up the python 53 interpreter in the child process, it's time to ask the supervisor for an 54 agenda item to process. 55 56 Everything we need to know to get the work done is in either the job's or 57 the work's package dict. 58 ''' 59 if self.dev: 60 pp = pprint.PrettyPrinter(indent=2, width=1) 61 62 while True: 63 work_status = 1 64 65 backendUtils.bannerPrint('Requesting work', fhList=[sys.stdout, sys.stderr]) 66 work = qb.requestwork() 67 68 if self.dev: 69 print "WORK:" 70 pp.pprint(work) 71 72 # Deal with the minimal set of work statuses 73 if work['status'] == 'complete': 74 work_status = 0 75 break 76 elif work['status'] == 'pending': 77 # preempted -- bail out 78 print 'job %s has been preempted' % self.job['id'] 79 work_status = 0 80 qb.reportjob('pending') 81 break 82 elif work['status'] == 'blocked': 83 # blocked -- perhaps part of a dependency chain 84 print 'job %s has been blocked' % self.job['id'] 85 work_status = 0 86 qb.reportjob('blocked') 87 break 88 elif work['status'] == 'waiting': 89 # waiting -- rare, come back in QB_WAITING_TIMEOUT seconds 90 print 'job %s will be back in %s seconds' % (self.job['id'], self.QB_WAITING_TIMEOUT) 91 time.sleep(self.QB_WAITING_TIMEOUT) 92 continue 93 94 if not work['package']: 95 work['package'] = {} 96 97 #============================================================ 98 # Do the actual work 99 #============================================================ 100 backendUtils.bannerPrint('Processing work: %s' % work['name']) 101 102 frameRange = work['name'] 103 modoExec = self.job['package']['modoExec'] 104 105 cmdFile = self.createModoCmdFile(frameRange) 106 cmd = '"%s" -dbon:noconfig < "%s"' % (modoExec, cmdFile) 107 108 retCode = self.runCmd(work, cmd) 109 110 if work['status'] == 'failed': 111 backendUtils.flushPrint('ERROR: Processing work failed\n', fhList=[sys.stdout, sys.stderr]) 112 work_status = 1 113 else: 114 work_status = retCode 115 116 if not self.dev: 117 backendUtils.flushPrint('INFO: removing temporary Modo command file %s' % cmdFile) 118 try: 119 os.unlink(cmdFile) 120 except OSError: 121 pass 122 123 if work.get('resultpackage') is None: 124 work['resultpackage'] = {} 125 # ----------------------------------------------------------- 126 # set the work status, then report it back to the supervisor 127 # so that it can update the server-side agenda 128 # ----------------------------------------------------------- 129 if work_status != 0: 130 # either the work or the job instance itself has failed 131 work['status'] = 'failed' 132 elif self.outputPaths_required and len(work.get('resultpackage', {}).get('outputPaths', '')) == 0: 133 work['status'] = 'failed' 134 backendUtils.flushPrint('WARNING: no "regex_outputPaths" match was found, setting agenda item status to "failed".', fhList=[sys.stdout, sys.stderr]) 135 else: 136 # mark the work as complete, and reset both the failure counter and timer 137 work['status'] = 'complete' 138 139 # report the work status back to the supervisor 140 backendUtils.bannerPrint('Reporting work as %(status)s: %(name)s ' % work, fhList=[sys.stdout, sys.stderr]) 141 142 qb.reportwork(work)
143
144 - def createModoCmdFile(self, frameRange):
145 ''' 146 ''' 147 # ------------------------------------------------------- 148 # (possibly) split the frameRange into start,end,step 149 # ------------------------------------------------------- 150 fStep = 1 151 if not re.search('[-,]', frameRange): 152 # find the trivial single-frame case, with neither a '-' nor a ',' 153 fEnd = fStart = frameRange 154 155 elif frameRange.count('-') and not frameRange.count(','): 156 # next most-command case, a single frame range with an optional frameStep 157 (fStart, fEnd) = frameRange.split('-') 158 159 if fEnd.count('x'): 160 (fEnd, fStep) = fEnd.split('x') 161 else: 162 # a disjointed frame range containing a comma 163 fList = qb.rangesplit(frameRange) 164 fStart = fList[0] 165 fEnd = fList[-1] 166 167 # ------------------------------------------------------- 168 # build the multi-line cmdString 169 # ------------------------------------------------------- 170 sceneFile = qb.utils.translateQbConvertPathStrings(self.job['package']['sceneFile']) 171 172 # Is the if/else redundant with the default '' in the get statement? 173 if 'renderOutputDir' in self.job['package']: 174 renderOutputDir = qb.utils.translateQbConvertPathStrings(self.job['package'].get('renderOutputDir', '')) 175 else: 176 renderOutputDir = '' 177 178 renderThreads = self.job['package'].get('renderThreads', 0) 179 180 if renderThreads == 0: 181 renderThreads = 'auto' 182 183 # ------------------------------------------------------- 184 # renderOutputDir (if present) must end in a trailing slash 185 # ------------------------------------------------------- 186 renderDirScript = '' 187 if renderOutputDir: 188 renderDirScript = '@ChangeRenderOutputPaths.pl' 189 if not renderOutputDir.endswith('/'): 190 renderOutputDir += '/' 191 renderOutputDir = '"%s"' % renderOutputDir 192 193 # ------------------------------------------------------- 194 # Layered Images 195 # ------------------------------------------------------- 196 if 'renderOutputDirLayered' in self.job['package']: 197 renderOutputDirLayered = qb.utils.translateQbConvertPathStrings(self.job['package'].get('renderOutputDirLayered', '')) 198 else: 199 renderOutputDirLayered = '' 200 201 filenamePrefix = self.job['package'].get('filenamePrefix', '') 202 imageFormat = self.job['package'].get('imageFormat', '') 203 if filenamePrefix and imageFormat and renderOutputDirLayered: 204 renderOutput = '"%s"' % os.path.join(renderOutputDirLayered, filenamePrefix) 205 else: 206 renderOutput = '' 207 imageFormat = '' 208 209 modoCmds = MODO_CMD_TEMPLATE % { 210 'scene': sceneFile, 211 'fStart': fStart, 212 'fEnd': fEnd, 213 'fStep': fStep, 214 'renderOutputDir': renderOutputDir, 215 'renderThreads': renderThreads, 216 'renderDirScript': renderDirScript, 217 'renderOutput': renderOutput, 218 'imageFormat': imageFormat 219 } 220 221 # ------------------------------------------------------- 222 # write the cmdString to the cmdFile 223 # ------------------------------------------------------- 224 jobID = os.environ.get('QBJOBID',0) 225 subID = os.environ.get('QBSUBID',0) 226 cmdFilePath = tempfile.mkstemp(prefix='%s-%s' % (jobID, subID), text=True)[1] 227 print cmdFilePath 228 229 if self.dev: 230 print 'DEBUG:\tModo command script:' 231 print '%s' % ('-'*30,) 232 for ln in modoCmds.split('\n'): 233 print '\t%s' % ln 234 print '%s' % ('-'*30,) 235 236 fh=open(cmdFilePath, 'w') 237 fh.write(modoCmds) 238 fh.close() 239 240 return cmdFilePath
241 242 243 if __name__ == '__main__': 244 ''' 245 A "hello world", meant to show how to build and submit a pyCmdrange job. 246 ''' 247 import sys, os 248 import qb 249 250 jobList = [] 251 252 if '--dev' in sys.argv: 253 dev = True 254 else: 255 dev = False 256 257 jobA = { 258 'name': 'modo single frame', 259 'prototype': 'modoCmdrange', 260 'package': { 261 'modoExec': '/Applications/modo.app/Contents/MacOS/modo_cl', 262 'sceneFile': '/Users/jburk/test/modo/cube.lxo', 263 'renderOutputDir': '/Users/jburk/test/modo/render/' 264 } 265 } 266 jobA['agenda'] = qb.genframes('1-20x2') 267 #jobList.append(jobA) 268 269 jobB = { 270 'name': 'modo chunks', 271 'prototype': 'modoCmdrange', 272 'package': { 273 'modoExec': '/Applications/modo.app/Contents/MacOS/modo_cl', 274 'sceneFile': '/Users/jburk/test/modo/cube.lxo', 275 'renderOutputDir': '/Users/jburk/test/modo/render/' 276 } 277 } 278 jobB['agenda'] = qb.genchunks(5, '100-120x2') 279 #jobList.append(jobB) 280 281 jobC = { 282 'name': 'modo partition range', 283 'prototype': 'modoCmdrange', 284 'package': { 285 'modoExec': '/Applications/modo.app/Contents/MacOS/modo_cl', 286 'sceneFile': '/Users/jburk/test/modo/cube.lxo', 287 'renderOutputDir': '/Users/jburk/test/modo/render/', 288 'regex_highlights': 'numThreads:.*', 289 } 290 } 291 jobC['agenda'] = qb.genpartitions(1, '200-220') 292 #jobList.append(jobC) 293 294 jobD = { 295 'name': 'modo single frame layered', 296 'prototype': 'modoCmdrange', 297 'package': { 298 'modoExec': 'C:\\Program Files\\Luxology\\modo\\10.0v1\\modo_cl.exe', 299 'sceneFile': 'C:\\Users\Kevin\\Desktop\\qube_test.lxo', 300 'renderOutputDirLayered': 'C:\\Users\\Kevin\\Desktop', 301 'filenamePrefix':'test', 302 'imageFormat':'openexrlayers' 303 } 304 } 305 jobD['agenda'] = qb.genframes('1-20x2') 306 jobList.append(jobD) 307 308 309 if '--arc' in sys.argv: 310 arcFile = './job.qja' 311 arcSize = qb.archivejob(arcFile, jobA, qb.QB_API_BINARY) 312 print 'exported job. wrote: %s bytes to file: %s' % (arcSize, arcFile) 313 else: 314 for j in qb.submit(jobList): 315 print 'submitted: %(id)s' % j 316 317 sys.exit() 318