|
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
9
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
40 '''
41 A backend for a Modo using the cmdrange approach, starts Modo for every agenda item
42 '''
43
49
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
73 if work['status'] == 'complete':
74 work_status = 0
75 break
76 elif work['status'] == 'pending':
77
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
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
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
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
127
128
129 if work_status != 0:
130
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
137 work['status'] = 'complete'
138
139
140 backendUtils.bannerPrint('Reporting work as %(status)s: %(name)s ' % work, fhList=[sys.stdout, sys.stderr])
141
142 qb.reportwork(work)
143
145 '''
146 '''
147
148
149
150 fStep = 1
151 if not re.search('[-,]', frameRange):
152
153 fEnd = fStart = frameRange
154
155 elif frameRange.count('-') and not frameRange.count(','):
156
157 (fStart, fEnd) = frameRange.split('-')
158
159 if fEnd.count('x'):
160 (fEnd, fStep) = fEnd.split('x')
161 else:
162
163 fList = qb.rangesplit(frameRange)
164 fStart = fList[0]
165 fEnd = fList[-1]
166
167
168
169
170 sceneFile = qb.utils.translateQbConvertPathStrings(self.job['package']['sceneFile'])
171
172
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
185
186 renderDirScript = ''
187 if renderOutputDir:
188 renderDirScript = '@ChangeRenderOutputPaths.pl'
189 if not renderOutputDir.endswith('/'):
190 renderOutputDir += '/'
191 renderOutputDir = '"%s"' % renderOutputDir
192
193
194
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
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
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
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
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