1
2 '''
3 A module for building pyNuke jobs.
4
5 These jobs use a persistent Nuke instance on the farm; a Dynamic Allocation jobtype.
6 '''
7
8
9
10
11
12 import sys
13 import os
14
15 try:
16 import qb
17 except:
18 if 'QBDIR' in os.environ:
19 QBDIR = os.environ['QBDIR']
20 else:
21 if os.name == 'posix':
22 if os.uname()[0] == 'Darwin':
23 QBDIR = '/Applications/pfx/qube'
24 else:
25 QBDIR = '/usr/local/pfx/qube'
26
27 sys.path.append('%s/api/python' % QBDIR)
28 import qb
29
30 try:
31 from qb import frontend
32 from qb.frontend import pythonFrontEnd as pythonFrontEnd
33 except ImportError:
34 from AppUI import frontend
35 from AppUI.frontend import pythonFrontEnd as pythonFrontEnd
36
37
39 - def __init__(self, nukeExe, nukeArgs=None, dev=False):
40 '''
41 Function to initialize members of the class
42
43 @param nukeArgs: Optional arguments to use when starting the Nuke python session.
44 @type nukeArgs: C{dict}
45
46 @param dev: Set the 'dev' value for the Qube job, enables debug output on the farm.
47 @type dev: bool
48 '''
49 super(NukePythonJob, self).__init__(dev=dev)
50
51 self['kind'] = 'nuke'
52 self['prototype'] = 'pyNuke'
53
54 pkg = {
55 'pyExecutable': nukeExe,
56 'nukeArgs': nukeArgs,
57 'smartAgenda': 1
58 }
59 self['package'].update(pkg)
60
61 self['package']['regex_errors'] = '\n'.join(
62 ['License failure',
63 'No license for product',
64 'FOUNDRY LICENSE ERROR REPORT',
65 'Can\'t read .* No such file or directory'])
66
67 self['package']['regex_highlights'] = '\n'.join(
68 ['^Nuke \d+\.\d+.*',
69 '^Nuke thread count: \d+',
70 '^Licence expires on.*'])
71
72 self.addJobCmds('''print "Nuke version: %s " % nuke.env['NukeVersionString']''')
73 self['agenda'] = []
74
75
77 - def __init__(self, nukeExe, frameRange, scriptPath, writeNodes=None, nukeArgs=None, jobName='', jobLabel='', dev=False):
78 '''
79 Function to initialize members of the class
80
81 @param frameRange: A frame range string. 1-3,6-10x2 expands to [1,2,3,6,8,10]
82 @type frameRange: C{str}
83
84 @param scriptPath: The full disk path to a nuke script.
85 @type scriptPath: C{str}
86
87 @param writeNodes: A list of nuke Write node names; if a string is passed, it will be recast as a list.
88 @type writeNodes: C{list}
89
90 @param nukeArgs: Optional arguments to use when starting the Nuke python session.
91 @type nukeArgs: C{dict}
92
93 @param jobName: Optional qube job name
94 @type jobName: C{str}
95
96 @param jobLabel: Optional qube job label
97 @type jobLabel: C{str}
98
99 @param dev: Set the 'dev' value for the Qube job, enables debug output on the farm.
100 @type dev: bool
101 '''
102 super(NukeRenderJob, self).__init__(nukeExe, nukeArgs=nukeArgs, dev=dev)
103
104 self.frameRange = frameRange
105 self['package']['frameRange'] = self.frameRange
106 self['package']['smartAgenda'] = True
107
108 self.scriptPath = scriptPath
109 self['package']['scriptPath'] = self.scriptPath
110
111 (self.scriptName, scriptExt) = os.path.splitext(os.path.basename(self.scriptPath))
112 if jobName:
113 self['name'] = jobName
114 else:
115 self['name'] = 'Nuke render %s%s' % (self.scriptName, scriptExt)
116
117 if jobLabel:
118 self['label'] = jobLabel
119
120 if not writeNodes:
121 writeNodes = []
122 elif isinstance(writeNodes, str):
123 writeNodes = [writeNodes]
124 self.writeNodes = sorted(writeNodes)
125 self['package']['writeNodes'] = self.writeNodes
126
127 if nukeArgs:
128 self['package']['nukeArgs'] = ' '.join(['%s %s' % (x[0], x[1]) for x in nukeArgs.items()])
129
130 self['package']['regex_outputPaths'] = 'Writing (.*) took [0-9.\-]+ seconds'
131
132 self.addJobCmds('import nukescripts')
133 self.addJobCmds(self.getQubeFileNameFilterCmds())
134
135 nuke_script = self.scriptPath
136 if not nuke_script.startswith('"'):
137 nuke_script = '"%s"' % nuke_script
138 self.addJobCmds('nuke.scriptOpen(%s)' % nuke_script)
139
140 if not self.writeNodes:
141 self.addJobCmds('''allWriteNodes = sorted([x.name() for x in nuke.allNodes() if x.Class()=='Write'])''')
142
143 self['agenda'] = self._buildAgenda()
144
146 '''
147 Build the command string that will create a Nuke fileNameFilter function that will fetch and
148 iterate over the worker's worker_path_map dictionary.
149
150 @return: the necessary Nuke python commands to initialize Nuke's internal path mapping
151 @rtype: C{list}
152 '''
153 cmds = []
154
155 cmds.append('''
156 #=================================================
157 # Setting up path translation FilenameFilter
158 #=================================================
159 def qubeWorkerFilenameFilter(filename):
160 import qb
161 xlatedPath = qb.convertpath(filename, qb.workerpathmap())
162 return xlatedPath
163
164 nuke.addFilenameFilter(qubeWorkerFilenameFilter)
165 #=================================================
166 # Done...
167 #================================================= ''' )
168 return cmds
169
171 '''
172 Create an agenda for the job, complete with per-frame python commands.
173
174 @return: The populated work agenda for the job
175 @rtype: C{dict}
176 '''
177 agenda = []
178 for frame in [x['name'] for x in qb.genframes(self.frameRange)]:
179 if self.writeNodes:
180 for node in self.writeNodes:
181 cmds = []
182 work = {'name': '%s@%04d' % (node, int(frame)),
183 'package': {'commands': cmds}
184 }
185
186 fmt = {'n': node, 'f': frame}
187 cmds.append('''writeNode = nuke.toNode('%(n)s')''' % fmt)
188 cmds.append('''print "Rendering Write node: %(n)s"''' % fmt)
189 cmds.append('''filename = nukescripts.replaceHashes( writeNode['file'].value() ) % nuke.frame()''')
190 cmds.append('''pathTranslated = qubeWorkerFilenameFilter(filename)''')
191 cmds.append('''if filename != pathTranslated: print "\\n\\t%s\\n\\t-> %s\\n" % (filename, pathTranslated)''')
192 cmds.append('resultBool = nuke.execute("%(n)s", %(f)s, %(f)s, 1)' % fmt)
193 cmds.append('''if resultBool == False: raise Exception, "Failure encountered processing node '%(n)s at frame%(f)s'"''' % fmt)
194 cmds.append('sys.stdout.flush()')
195
196 agenda.append(work)
197 else:
198 cmds = []
199 work = {'name': '%s' % frame,
200 'package': {'commands': cmds}
201 }
202
203 fmt = {'f': frame}
204 cmds.append('''
205 for node in allWriteNodes:
206 print 'Rendering Write node: %%s' %% node
207 resultBool = nuke.execute('%%s' %% node, %(f)s, %(f)s, 1)
208 if resultBool == False:
209 raise Exception, "Failure encountered processing node '%%s at frame%(f)s' %% node"
210 sys.stdout.flush()
211 ''' % fmt)
212 agenda.append(work)
213
214 return agenda
215
216
217 if __name__ == '__main__':
218 '''
219 A "hello world", meant to show how to build and submit a Nuke dynamic allocation job using the pythonChildJob framework.
220 '''
221
222
223
224 jobList = []
225
226 nukeExe = '/Applications/Nuke8.0v4/Nuke8.0v4.app/Contents/MacOS/Nuke8.0v4'
227 nukeExe = '//NEWTON/Pypeline/bin/current/all/nuke8v6_.bat'
228 nukeScript = '//eru/py_dev/qube_test/nuke/nuke_render.nk'
229 nukeArgs = {}
230
231 jobA = NukeRenderJob(nukeExe, frameRange='1-10', scriptPath=nukeScript, writeNodes=[], nukeArgs=nukeArgs)
232 jobA['cpus'] = 4
233 jobA['max_cpus'] = 0
234 jobA['name'] += ' 1 node'
235 jobA['reservations'] = 'host.processors=1+5'
236 jobList.append(jobA)
237
238 nukeScript = '/Users/jburk/test/nuke/test_2nodes.nk'
239 jobB = NukeRenderJob(nukeExe, frameRange='1-20', scriptPath=nukeScript, nukeArgs=nukeArgs)
240 jobB['cpus'] = 4
241 jobB['name'] += ' 2 nodes'
242
243
244 if '--arc' in sys.argv:
245 arcSize = qb.archivejob('job.qja', jobA, format=qb.QB_API_BINARY)
246 print 'job.qja: %s bytes' % arcSize
247 else:
248 for j in qb.submit(jobList):
249 print 'submitted: %(id)s' % j
250
251 sys.exit()
252