def _create_version(self): existing = ProductVersion.list( ptask_version=self._ptask_version.spec, product=self._product.spec, ) if len(existing) == 1: self._product_version = existing.pop() self._product_version.update(release_note=self._note) if self.interactive: print "\nProduct version exists: " + \ Style.bright + self._product_version.spec + Style.reset else: try: self._product_version = ProductVersion.create( ptask_version=self._ptask_version.spec, product=self._product.spec, release_note=self._note, creator=current_username(), ) except ProductVersionError as e: raise ActionError("Unable to create product version: " + str(e)) else: if self.interactive: print "\nCreated product version: " + \ Style.bright + self._product_version.spec + Style.reset
def _create_product(self): existing = Product.list( name=self._name, category=self._category, ptask=self._ptask.spec, ) if len(existing) == 1: self._product = existing.pop() self._product.update(description=self._description) if self.interactive: print "\nBase product exists: " + \ Style.bright + self._product.spec + Style.reset else: try: self._product = Product.create( ptask=self._ptask.spec, name=self._name, category=self._category, description=self._description, creator=current_username(), ) except ProductError as e: raise ActionError("Unable to create product: " + str(e)) else: if self.interactive: print "\nCreated base product: " + \ Style.bright + self._product.spec + Style.reset
def _create_representation(self): existing = ProductRepresentation.list( product_version=self._product_version.spec, resolution=self._resolution, representation_type=self._file_type, ) if len(existing) == 1: self._product_repr = existing.pop() if self.interactive: print "\nProduct representation exists: " + \ Style.bright + self._product_repr.spec + Style.reset else: try: self._product_repr = ProductRepresentation.create( product_version=self._product_version.spec, resolution=self._resolution, representation_type=self._file_type, creation_location=current_location_code(), creator=current_username(), ) except ProductRepresentationError as e: raise ActionError( "Unable to create product representation: " + str(e)) else: if self.interactive: print "\nCreated product representation: " + \ Style.bright + self._product_repr.spec + Style.reset
def _create_version(self): new_version = self.ptask.next_version_number location_code = current_location_code() try: self._new_ptask_version = PTaskVersion.create( current_username(), "in progress...".format(n=new_version), location_code, ptask_spec=self.ptask.spec, number=new_version, parent_spec=self.source_version.spec, ) except PTaskVersionError as e: raise ActionError("Failed to create ptask version: " + str(e)) else: print "\nNew version successfully created in the database." # ---- provision a version directory in the ptask area try: self.ptask.area.provision( self.ptask.area.dir(version=new_version, verify=False) ) except PTaskAreaError as e: raise ActionError("Unable to provision version directory.") else: print "\nSuccessfully provisioned directory for version: " + \ str(new_version)
def setup_cl_args(cls, parser): parser.add_argument( "username", nargs="?", default=current_username(), help="Print info for the supplied username." )
def queue_submit_cmd(command, queue_name, output_file=None, id_extra=None): """Create and submit a shell script with the given command.""" ptask_area = PTaskArea.current() ptask_area.provision(QUEUE) script_dir = ptask_area.dir(dir_name=QUEUE) now = datetime.datetime.now() if not id_extra: id_extra = now.strftime("%f") unique_id = "{u}_{t}_{s}_{e}".format( u=current_username(), t=now.strftime("%Y_%m_%d_%H_%M_%S"), s=ptask_area.spec.replace('=', '_'), e=id_extra, ) script_name = unique_id + '.sh' log_name = unique_id + '.log' script_path = os.path.join(script_dir, script_name) log_path = os.path.join(script_dir, log_name) with open(script_path, "w") as script_file: script_file.write("#!/bin/bash\n") script_file.write(command + "\n") script_file.write("chmod 660 " + output_file + "\n") os.chmod(script_path, 0770) # ---- submit to the queue from cheesyq import DPACheesyQ, DPADataLibrary, DPACheesyQTasks data_lib = DPADataLibrary.DjangoLibrary(None) render_task = DPACheesyQ.RenderTask() render_task.taskid = unique_id render_task.logFileName = log_path render_task.outputFileName = output_file data_lib.set(render_task.taskid, render_task) render_task.addTask(script_path) os.system("cqresubmittask {qn} {tid}".format( qn=queue_name, tid=render_task.taskid )) print "Submitted task: " + str(render_task.taskid)
def get_unique_id(area_spec="", id_extra=None, dt=None): if not dt: dt = datetime.datetime.now() if id_extra: id_extra = "_" + id_extra else: id_extra = "" return "{u}_{t}_{s}{e}".format( u=current_username(), t=dt.strftime("%Y_%m_%d_%H_%M_%S"), s=area_spec.replace('=', '_'), e=id_extra, )
def _get_path(self, ptask, version=None, latest_version=None, directory=None, location_override=None): cur_loc_code = current_location_code() if version: version_num = version.number else: if not latest_version: latest_version = ptask.latest_version version = latest_version version_num = None if location_override: loc_code = location_override.code else: loc_code = version.location_code if loc_code == cur_loc_code: try: path = ptask.area.dir( version=version_num, dir_name=directory, ) except PTaskAreaError: raise ActionError( "Path for 's' does not exist.".format(s=ptask.spec), ) path += "/" else: location = version.location if not location.host: raise ActionError( "Unable to sync with location '{l}'. Unknown host.".\ format(l=location.name) ) path = ptask.area.dir( version=version_num, dir_name=directory, root=location.filesystem_root, verify=False, ) path = current_username() + "@" + location.host + ":" + path + "/" return path
def _render_to_product(self): # add the version note for the product render_node = self.session.nuke.toNode(self._node_to_render) render_node['product_ver_note'].setValue(self._version_note) # ---- progress dialog num_ops = 6 cur_op = 0 progress_dialog = QtGui.QProgressDialog( "Product render...", "", cur_op, num_ops, self) progress_dialog.setWindowTitle("Dark Knight is busy...") progress_dialog.setAutoReset(False) progress_dialog.setLabelText("Preparing nuke file for rendering...") progress_dialog.show() # ensure the product has been created progress_dialog.setLabelText("Creating product...") product_repr = create_product_before_render(node=render_node) product_repr_area = product_repr.area cur_op += 1 progress_dialog.setValue(cur_op) # get timestamp for all the tasks being submitted now = datetime.datetime.now() ptask_area = PTaskArea.current() ptask = PTask.get(ptask_area.spec) if ptask_area.version: ptask_version = ptask.version(ptask_area.version) else: ptask_version = ptask.latest_version ptask_dir = ptask_area.dir() ver_dir = ptask_area.dir(version=ptask_version.number) nuke_file = self.session.nuke.root().name() nuke_file = nuke_file.replace(ptask_dir, ver_dir) file_base = os.path.splitext(os.path.split(nuke_file)[1])[0] # ---- sync current work area to version snapshot to render from progress_dialog.setLabelText("Sync'ing the latest work...") try: self.session.save() self._sync_latest() except Exception as e: self._show_error("Unable to save & sync the latest work: " + str(e)) self.setEnabled(True) progress_dialog.close() return cur_op += 1 progress_dialog.setValue(cur_op) # make sure queue directory exists progress_dialog.setLabelText("Provisioning the queue directory...") try: product_repr_area.provision('queue') except Exception as e: raise DarkKnightError( "Unable to create queue scripts directory: " + str(e)) cur_op += 1 progress_dialog.setValue(cur_op) queue_dir = product_repr_area.dir(dir_name='queue') tasks_info_file = os.path.join(queue_dir, 'tasks_info.cfg') tasks_info_config = Config() progress_dialog.setLabelText("Building the queue script...") # dpaset command to run dpaset_cmd = 'eval "`dpa env ptask {pt}@{vn}`"'.format( pt=ptask.spec, vn=ptask_version.number) frange_str = str(self._frange).replace("-", "_").replace(":", "_") script_path = os.path.join(queue_dir, "{pn}.{fr}.sh".format(pn=render_node['product_name'].value(), fr=frange_str)) render_cmd = "nuke --cont -f -F {fs}-{fe}x{step} -X {rn} -V 2 -x {nf}".\ format( fs=self._frange.start, fe=self._frange.end, step=self._frange.step, rn=self._node_to_render, nf=nuke_file, ) with open(script_path, "w") as script_file: script_file.write("#!/bin/bash\n\n") # XXX these should happen automatically in the queue... script_file.write("source /DPA/wookie/dpa/bash/startup.bash\n") script_file.write("pipeup\n\n") script_file.write("# set the ptask version to render\n") script_file.write(dpaset_cmd + "\n\n") script_file.write("# render!\n") script_file.write(render_cmd + "\n\n") os.chmod(script_path, 0770) cur_op += 1 progress_dialog.setValue(cur_op) task_id = get_unique_id(product_repr_area.spec, dt=now) task_id += "_" + frange_str tasks_info_config.add('task_id', task_id) out_file = self.session.nuke.filename(render_node, self.session.nuke.REPLACE) if not self._debug_mode: progress_dialog.setLabelText("Submitting to the queue...") create_queue_task(self._render_queue, script_path, task_id, output_file=out_file, submit=True, log_path=script_path + '.log') tasks_info_config.write(tasks_info_file) os.chmod(tasks_info_file, 0660) cur_op += 1 progress_dialog.setValue(cur_op) if not self._debug_mode: progress_dialog.setLabelText("Sending submission report...") # send msg... msg_title = "Queue submission report: " + \ now.strftime("%Y/%m/%d %H:%M:%S") msg_body = "Submitted the following task for " + \ ptask.spec + ":\n\n" msg_body += " Product representation: " + product_repr.spec + "\n" msg_body += " Description: " + self._version_note + "\n" msg_body += " Render queue: " + self._render_queue + "\n" msg_body += " Frames: " + str(self._frange) + "\n" msg_body += " Task ID: " + task_id + "\n" msg_body += " Scripts directory: " + queue_dir + "\n" msg_body += "\n" dk_config = ptask.area.config(DK_CONFIG_PATH, composite_ancestors=True, composite_method="append") recipients = dk_config.get('notify', []) recipients.append(current_username()) recipients = emails_from_unames(recipients) notification = Notification(msg_title, msg_body, recipients, sender=User.current().email) notification.send_email() cur_op += 1 progress_dialog.setValue(cur_op) progress_dialog.close()
def _render_to_product(self): # get render node reference render_node = self.session.hou.node(self._node_to_render) # ---- progress dialog num_ops = 8 cur_op = 0 progress_dialog = QtGui.QProgressDialog( "Product render...", "", cur_op, num_ops, self) progress_dialog.setWindowTitle("Dark Knight is busy...") progress_dialog.setAutoReset(False) progress_dialog.setLabelText("Preparing nuke file for rendering...") progress_dialog.show() ######################################### # ensure the product has been created ######################################### progress_dialog.setLabelText("Creating product...") if not render_node.type().name()=='ifd' or not self._version_note: raise Exception("The supplied node is not a WriteProduct node.") print "Creating product for node... " + str(render_node) ptask_area = PTaskArea.current() ptask = PTask.get(ptask_area.spec) if ptask_area.version: ptask_version = ptask.version(ptask_area.version) else: ptask_version = ptask.latest_version category = 'imgseq' file_type = 'exr' product_name = render_node.name() product_desc = render_node.name() + " mantra render" product_ver_note = self._version_note camera_node = self.session.hou.node(render_node.evalParm('camera')) if not camera_node: raise Exception("Camera specified is not valid.") width = camera_node.evalParm("resx") height = camera_node.evalParm("resy") resolution = "%sx%s" % (width, height) create_action_cls = ActionRegistry().get_action('create', 'product') if not create_action_cls: raise Exception("Unable to find product creation action.") create_action = create_action_cls( product=product_name, ptask=ptask.spec, version=ptask_version.number, category=category, description=product_desc, file_type=file_type, resolution=resolution, note=product_ver_note, ) try: create_action() except ActionError as e: raise Exception("Unable to create product: " + str(e)) # provision the ifd directory try: create_action.product_repr.area.provision('ifd') except Exception as e: raise Exception( "Unable to create ifd file directory: " + str(e)) ifd_dir = os.path.join(create_action.product_repr.area.path, 'ifd', product_name + '.$F4.ifd') out_path = os.path.join(create_action.product_repr.area.path, product_name + '.$F4.' + file_type) # by default, the mantra frame range has an expression on frame numbers render_node.parm('f1').deleteAllKeyframes() render_node.parm('f2').deleteAllKeyframes() # set frange render_node.parm('trange').set(1) render_node.parm('f1').set(self._frange.start) render_node.parm('f2').set(self._frange.end) render_node.parm('f3').set(self._frange.step) # set output render_node.parm('soho_outputmode').set(1) render_node.parm('soho_diskfile').set(ifd_dir) render_node.parm('soho_diskfile').disable(0) render_node.parm('vm_picture').set(out_path) render_node.parm('soho_mkpath').set(1) product_repr = create_action.product_repr product_repr_area = product_repr.area cur_op += 1 progress_dialog.setValue(cur_op) ######################################### # create ifd files ######################################### progress_dialog.setLabelText("Generating ifd files...") render_node.parm('execute').pressButton() ifd_file_list = glob.glob( os.path.join( create_action.product_repr.area.path, 'ifd', '*.ifd') ) for ifd_file in ifd_file_list: os.chmod(ifd_file, 0770) cur_op += 1 progress_dialog.setValue(cur_op) ######################################### # sync current work area to version snapshot to render from ######################################### progress_dialog.setLabelText("Sync'ing the latest work...") try: self.session.save() self._sync_latest() except Exception as e: self._show_error("Unable to save & sync the latest work: " + str(e)) self.setEnabled(True) progress_dialog.close() return cur_op += 1 progress_dialog.setValue(cur_op) ######################################### # ensure queue directory exists ######################################### progress_dialog.setLabelText("Provisioning the queue directory...") try: product_repr_area.provision('queue') except Exception as e: raise DarkKnightError( "Unable to create queue scripts directory: " + str(e)) cur_op += 1 progress_dialog.setValue(cur_op) out_dir = product_repr_area.path ifd_dir = product_repr_area.dir(dir_name='ifd') queue_dir = product_repr_area.dir(dir_name='queue') tasks_info_file = os.path.join(queue_dir, 'tasks_info.cfg') tasks_info_config = Config() cur_op += 1 progress_dialog.setValue(cur_op) ######################################### # buidling queue scripts ######################################### progress_dialog.setLabelText("Building the queue script...") # # dpaset command to run dpaset_cmd = 'eval "`dpa env ptask {pt}@{vn}`"'.format( pt=ptask.spec, vn=ptask_version.number) # write out queue shell scripts frame_scripts = [] for frame in self._frame_list: frame_padded = str(frame).zfill(4) ifd_file = os.path.join(ifd_dir, "{pn}.{fn}.ifd".format(pn=product_name, fn=frame_padded)) script_path = os.path.join(queue_dir, "{pn}.{fn}.sh".format(pn=product_name, fn=frame_padded)) out_file = os.path.join(out_dir, "{pn}.{fn}.{ft}".format(pn=product_name, fn=frame_padded, ft=file_type) ) render_cmd = "/opt/hfs14/bin/mantra -f {ifd} -V 2a".\ format( ifd=ifd_file ) with open(script_path, "w") as script_file: script_file.write("#!/bin/bash\n\n") # XXX these should happen automatically in the queue... script_file.write("source /DPA/wookie/dpa/bash/startup.bash\n") script_file.write("pipeup\n\n") script_file.write("# set the ptask version to render\n") script_file.write(dpaset_cmd + "\n\n") script_file.write("# render!\n") script_file.write(render_cmd + "\n\n") frame_scripts.append((frame_padded, script_path, out_file)) os.chmod(script_path, 0770) cur_op += 1 progress_dialog.setValue(cur_op) ################################################ # submit to the queue ################################################ now = datetime.datetime.now() task_id_base = get_unique_id(product_repr_area.spec, dt=now) frame_tasks = [] # create frame tasks for (frame, frame_script, out_file) in frame_scripts: progress_dialog.setLabelText( "Submitting frame: " + frame_script) task_id = task_id_base + "_" + frame if not self._debug_mode: # create tasks, don't actually submit yet create_queue_task(self._render_queue, frame_script, task_id, output_file=out_file, submit=False, log_path=frame_script + '.log') frame_tasks.append((frame, task_id)) # # resubmit frame-by-frame because # group submit seems to be occasionally # having problems. os.system("cqresubmittask {qn} {tid}".format( qn=self._render_queue, tid=task_id)) cur_op += 1 progress_dialog.setValue(cur_op) ################################################ # task info stuff, allows task ids to # be retrieved with product spec ################################################ progress_dialog.setLabelText("Creating task info file...") tasks_info_file = os.path.join(queue_dir, 'tasks_info.cfg') tasks_info_config = Config() tasks_info_config.add('base_id', task_id_base) frame_info = Config() for (frame, task_id) in frame_tasks: frame_info.add(str(frame), task_id) tasks_info_config.add('frame_ids', frame_info) tasks_info_config.write(tasks_info_file) os.chmod(tasks_info_file, 0660) cur_op += 1 progress_dialog.setValue(cur_op) ################################################ # email report ################################################ if not self._debug_mode: # send msg... msg_title = "Queue submission report: " + \ now.strftime("%Y/%m/%d %H:%M:%S") msg_body = "Submitted the following tasks for " + \ ptask.spec + ":\n\n" msg_body += " Description: " + self._version_note + "\n" msg_body += " Resolution: " + resolution + "\n" msg_body += " Render queue: " + self._render_queue + "\n" msg_body += " Frames: " + str(self._frange) + "\n" msg_body += " Ifd directory: " + ifd_dir + "\n" msg_body += "\n" msg_body += " Base task ID: " + task_id_base + "\n" msg_body += " Product representation: " + \ product_repr.spec + "\n" msg_body += " Scripts directory: " + queue_dir + "\n" msg_body += "\n" dk_config = ptask.area.config(DK_CONFIG_PATH, composite_ancestors=True, composite_method="append") recipients = dk_config.get('notify', []) recipients.append(current_username()) recipients = emails_from_unames(recipients) notification = Notification(msg_title, msg_body, recipients, sender=User.current().email) notification.send_email() print recipients cur_op += 1 progress_dialog.setValue(cur_op) progress_dialog.close()
def _product_render_arnold(self): # get timestamp for all the tasks being submitted now = datetime.datetime.now() render_layers = self._get_render_layers() # figure out the total number of operations to perform for the progress num_ops = 1 + len(render_layers) * len(self._frame_list) # layer > frame num_ops += len(self._frame_list) # frame submission if self._remove_scenes: num_ops += 1 if self._generate_scenes: num_ops += 1 cur_op = 0 progress_dialog = QtGui.QProgressDialog( "Product render...", "", cur_op, num_ops, self) progress_dialog.setWindowTitle("Dark Knight is busy...") progress_dialog.setAutoReset(False) progress_dialog.setLabelText("Preparing maya file for rendering...") progress_dialog.show() ptask = self._cur_ptask ptask_version = self._cur_ptask.version(self._version) ptask_dir = self._cur_ptask.area.dir() ver_dir = ptask.area.dir(version=self._version) # need to get the maya file in the version directory maya_file = self.session.cmds.file(q=True, sceneName=True) maya_file = maya_file.replace(ptask_dir, ver_dir) file_base = os.path.splitext(os.path.split(maya_file)[1])[0] self.session.cmds.setAttr('defaultResolution.width', self._resolution.width) self.session.cmds.setAttr('defaultResolution.height', self._resolution.height) # set the output file naming convention to name.#.ext self.session.cmds.setAttr("defaultRenderGlobals.animation", True) self.session.cmds.setAttr("defaultRenderGlobals.putFrameBeforeExt", True) self.session.cmds.setAttr("defaultRenderGlobals.outFormatControl", False) # set all other cameras to not be renderable (this seems to work) cam_shape_list = self.session.cmds.ls(cameras=True) for cam_shape in cam_shape_list: cam_name = str( self.session.cmds.listRelatives(cam_shape, parent=True)[0]) if cam_name == self._camera: self.session.cmds.setAttr(cam_shape + ".renderable", 1) else: self.session.cmds.setAttr(cam_shape + ".renderable", 0) # ---- sync current work area to version snapshot to render from cur_project = self.session.cmds.workspace(query=True, rootDirectory=True) ver_project = cur_project.replace(ptask_dir, ver_dir) progress_dialog.setLabelText("Sync'ing work to current version...") try: self.session.save() self._sync_latest() except Exception as e: self._show_error("Unable to save & sync the latest work: " + str(e)) self.setEnabled(True) progress_dialog.close() return cur_op += 1 progress_dialog.setValue(cur_op) create_action_cls = ActionRegistry().get_action('create', 'product') if not create_action_cls: progress_dialog.close() raise DarkKnightError("Unable to find product creation action.") # ---- clean up ASSs scene_dir = os.path.join(ver_project, 'arnold', file_base, 'ass') if self._remove_scenes: progress_dialog.setLabelText("Removing ass files...") if os.path.isdir(scene_dir): try: shutil.rmtree(scene_dir) except Exception as e: progress_dialog.close() raise DarkKnightError("Unable to clean up ass files: " + str(e)) cur_op += 1 progress_dialog.setValue(cur_op) # ---- get a list of warnings to ignore #prman_config = ptask.area.config(PRMAN_CONFIG_PATH, # composite_ancestors=True, composite_method="override") #prman_warnings = " ".join( # ["-woff " + w for w in prman_config.get('woff', [])]) # ---- construct scripts for the queue render_summary = [] for render_layer in render_layers: progress_dialog.setLabelText( "Creating product for layer: {rl}...".format(rl=render_layer)) # ensure product exists for each render layer create_action = create_action_cls( product=render_layer, ptask=ptask_version.ptask_spec, version=ptask_version.number, category='imgseq', description=render_layer + " render layer", file_type=self._file_type, resolution=self._res_str, note=self._version_note, ) try: create_action() except ActionError as e: progress_dialog.close() raise DarkKnightError("Unable to create product: " + str(e)) product_repr = create_action.product_repr product_repr_area = product_repr.area progress_dialog.setLabelText( "Provisioning 'queue' directory in product...") # make sure queue directory exists try: product_repr_area.provision('queue') except Exception as e: progress_dialog.close() raise DarkKnightError( "Unable to create queue scripts directory: " + str(e)) queue_dir = product_repr_area.dir(dir_name='queue') tasks_info_file = os.path.join(queue_dir, 'tasks_info.cfg') tasks_info_config = Config() # dpaset command to run dpaset_cmd = 'eval "`dpa env ptask {pt}@{vn}`"'.format( pt=ptask.spec, vn=ptask_version.number) # set group permissions on project dir, recursively os.system("chmod g+rw {pd} -R".format(pd=ver_project)) # figure out the render layer if render_layer == 'masterLayer': layer_index = self.session.cmds.getAttr("defaultRenderLayer.rlid") else: layer_index = self.session.cmds.getAttr(render_layer + ".rlid") frame_scripts = [] for frame in self._frame_list: frame_padded = str(frame).zfill(4) progress_dialog.setLabelText( "Building render shell script for {rl} frame {f}".format( rl=render_layer, f=frame_padded)) script_path = os.path.join(queue_dir, "{rl}.{fn}.sh".format(rl=render_layer, fn=frame_padded)) out_dir = product_repr_area.dir() out_file = os.path.join(out_dir, "{rl}.{fn}.{ft}".\ format(rl=render_layer, fn=frame_padded, ft=self._file_type)) simple_scene = "{proj}arnold/{fb}/ass/{fb}.{fn}.ass".format( proj=ver_project, fb=file_base, fn=frame_padded) layer_scene = "{proj}arnold/{fb}/ass/{fb}_{rl}.{fn}.ass".\ format(proj=ver_project, fb=file_base, fn=frame_padded, rl=render_layer) render_cmd = "/opt/solidangle/arnold-maya2016/bin/kick -dw -v 6 -i $ASS_PATH " render_cmd += "-l /opt/solidangle/arnold-maya2016/shaders " render_cmd += "-l /opt/solidangle/arnold-maya2016/procedurals " render_cmd += "-o {od} ".format(od=out_file) #render_cmd += "-f {rl} ".format(rl=render_layer) #render_cmd += "-p {proj} ".format(proj=ver_project) #render_cmd += "--prman '-t:0 -cwd \"{proj}\" {warn}' ".\dd # format(proj=ver_project, warn=prman_warnings) with open(script_path, "w") as script_file: script_file.write("#!/bin/bash\n\n") # XXX these should happen automatically in the queue... script_file.write("source /DPA/wookie/dpa/bash/startup.bash\n") script_file.write("pipeup\n") # 'kick' command has to be added to $PATH # Create env variable for Arnold License server script_file.write("export [email protected]\n\n") script_file.write("# set the ptask version to render\n") script_file.write(dpaset_cmd + "\n") script_file.write("cd " + ver_project + "\n\n") # Add necessary paths to the environment for XGen script_file.write("export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/autodesk/maya2016/plug-ins/xgen/lib:/usr/autodesk/maya2016/lib:/opt/solidangle/arnold-maya2016/bin\n\n") # the logic for determining which ass will be generated is # unclear at this point. So we'll build a conditional script_file.write("if [[ -f {lr} ]]; then\n".format(lr=layer_scene)) script_file.write(" export ASS_PATH={lr}\n".format(lr=layer_scene)) script_file.write("else\n") script_file.write(" export ASS_PATH={sr}\n".format(sr=simple_scene)) script_file.write("fi\n") script_file.write("# render!\n") script_file.write(render_cmd + "\n\n") script_file.write("chmod 660 {of}\n\n".format( of=os.path.join(out_dir, render_layer + "*." + self._file_type))) os.chmod(script_path, 0770) frame_scripts.append((frame_padded, script_path, out_file)) cur_op += 1 progress_dialog.setValue(cur_op) frame_tasks = [] task_id_base = get_unique_id(product_repr_area.spec, dt=now) tasks_info_config.add('base_id', task_id_base) if self._generate_scenes: frame_queue = 'hold' else: frame_queue = self._render_queue # create frame tasks for (frame, frame_script, out_file) in frame_scripts: progress_dialog.setLabelText( "Submitting frame: " + frame_script) task_id = task_id_base + "_" + frame if not self._debug_mode: # create tasks, don't actually submit yet create_queue_task(frame_queue, frame_script, task_id, output_file=out_file, submit=False, log_path=frame_script + '.log') frame_tasks.append((frame, task_id)) # # resubmit frame-by-frame because # group submit seems to be occasionally # having problems. os.system("cqresubmittask {qn} {tid}".format( qn=frame_queue, tid=task_id)) cur_op += 1 progress_dialog.setValue(cur_op) frame_info = Config() for (frame, task_id) in frame_tasks: frame_info.add(str(frame), task_id) tasks_info_config.add('frame_ids', frame_info) # resubmit all at once (workaround for slow individual submissions) # # This approach seems to be having problems with the server # communications. Switch to frame-by-frame resubmit because # that has worked where this fails #os.system("cqresubmittask {qn} {tid}".format( # qn=frame_queue, tid=task_id_base)) if self._generate_scenes: progress_dialog.setLabelText("Creating ass generation script...") script_path = os.path.join(queue_dir, "{rl}_assgen.sh".format(rl=render_layer)) with open(script_path, "w") as script_file: script_file.write("#!/bin/bash\n\n") # XXX these should happen automatically in the queue... script_file.write("source /DPA/wookie/dpa/bash/startup.bash\n") script_file.write("pipeup\n\n") script_file.write("# set the ptask version to render\n") script_file.write(dpaset_cmd + "\n") script_file.write("cd " + ver_project + "\n\n") script_file.write("# generate the ass files...\n") current_render_layer = render_layer if render_layer == 'masterLayer': current_render_layer = "defaultRenderLayer" switch_render_layer_cmd = "editRenderLayerGlobals " switch_render_layer_cmd += "-currentRenderLayer \"{rl}\"".\ format(rl=current_render_layer) arnold_export_cmd = "arnoldExportAss -f \"{ad}/{fb}_{rl}.ass\" ".\ format(ad=scene_dir, fb=file_base, rl=render_layer) arnold_export_cmd += "-startFrame {sf} -endFrame {ef} -frameStep 1 ".\ format(li=layer_index, sf=self._frange.start, ef=self._frange.end) arnold_export_cmd += "-mask 255 -lightLinks 1 -shadowLinks 1 -cam {cam} ".\ format(cam=self._camera) arnold_export_cmd += "-expandProcedurals " maya_batch_cmd = 'maya2016 -batch -proj "{proj}" '.format( proj=ver_project) maya_batch_cmd += '-command \'{srlc}; {ar}\' '.\ format(srlc=switch_render_layer_cmd, ar=arnold_export_cmd) maya_batch_cmd += '-file "{mf}"'.format(mf=maya_file) script_file.write(maya_batch_cmd + "\n") script_file.write( "\n# make sure project dir has group permissions\n") script_file.write( "chmod g+rw {pd} -R\n\n".format(pd=ver_project)) # submit the frames to render script_file.write("# Submit frames after ass gen \n") for (frame, frame_task) in frame_tasks: script_file.write("cqmovetask {qn} {tid}\n".format( qn=self._render_queue, tid=frame_task)) # changed to move group #script_file.write("cqmovetask {qn} {tid}\n".format( #qn=self._render_queue, tid=task_id_base)) os.chmod(script_path, 0770) # submit the scenegen script progress_dialog.setLabelText( "Submitting ass gen: " + script_path) task_id = task_id_base + "_asses" tasks_info_config.add('assgen_id', task_id) if not self._debug_mode: create_queue_task(self._scenegen_queue, script_path, task_id, output_file=scene_dir, submit=True, log_path=script_path + '.log') cur_op += 1 progress_dialog.setValue(cur_op) cur_op += 1 progress_dialog.setValue(cur_op) progress_dialog.close() render_summary.append( (render_layer, task_id_base, product_repr, queue_dir)) # For now, disable wrangling tickets. bsddb is causing problems # - zshore, 2015-10-23 # if not self._debug_mode: # # ---- dpa specific queue stuff # from cheesyq import DPAWrangler # # create wrangling ticket # wrangle = DPAWrangler.WrangleRecord(task_id_base) # wrangle.frames = self._frame_list # db = DPAWrangler.GetWranglingDB() # db.set(wrangle.baseId, wrangle) # DPAWrangler.AssignWranglerTask("none", task_id_base) wranglecmd = 'cqcreatewrangleitem ' + task_id_base + ' ' for f in self._frame_list: wranglecmd = wranglecmd + str(f) + ' ' print wranglecmd os.system(wranglecmd) tasks_info_config.write(tasks_info_file) os.chmod(tasks_info_file, 0660) if not self._debug_mode: # send msg... msg_title = "Queue submission report: " + \ now.strftime("%Y/%m/%d %H:%M:%S") msg_body = "Submitted the following tasks for " + \ ptask.spec + ":\n\n" msg_body += " Description: " + self._version_note + "\n" msg_body += " Resolution: " + self._res_str + "\n" msg_body += " File type: " + self._file_type + "\n" msg_body += " Camera: " + self._camera + "\n" if self._generate_scenes: msg_body += " Ass gen queue: " + self._scenegen_queue + "\n" msg_body += " Render queue: " + self._render_queue + "\n" msg_body += " Frames: " + str(self._frange) + "\n" msg_body += " Ass directory: " + scene_dir + "\n" msg_body += "\n" for (layer, task_id_base, product_repr, queue_dir) in render_summary: msg_body += " Render layer: " + layer + "\n" msg_body += " Base task ID: " + task_id_base + "\n" msg_body += " Product representation: " + \ product_repr.spec + "\n" msg_body += " Scripts directory: " + queue_dir + "\n" msg_body += "\n" dk_config = ptask.area.config(DK_CONFIG_PATH, composite_ancestors=True, composite_method="append") recipients = dk_config.get('notify', []) recipients.append(current_username()) recipients = emails_from_unames(recipients) notification = Notification(msg_title, msg_body, recipients, sender=User.current().email) notification.send_email()
def setup_cl_args(cls, parser): # ptask spec (name, parent) - can be relative to current ptask parser.add_argument( "spec", action=ParsePTaskSpecArg, help="The spec representing the ptask to create.", ) # ptask type parser.add_argument( "-t", "--type", dest="ptask_type", default=None, help="The type of ptask to create.", metavar="type" ) # description parser.add_argument( "-d", "--description", default=None, help="A description of the ptask being created.", metavar='"description"' ) # creator parser.add_argument( "-c", "--creator", default=current_username(), help="The creator of this ptask.", metavar="username" ) # start_date parser.add_argument( "-b", "--begins", action=ParseDateArg, dest="start_date", help="The start date of the new ptask.", metavar="YYYY-MM-DD", ) # due_date parser.add_argument( "-e", "--ends", action=ParseDateArg, dest="due_date", help="The due date of the new ptask.", metavar="YYYY-MM-DD", ) # source parser.add_argument( "-s", "--source", action=ParsePTaskSpecArg, help="The ptask to use when creating this ptask.", metavar="source_spec", ) # force - hidden option to force creation without prompting parser.add_argument( "-f", "--force", action="store_true", default=False, help="Force creation without prompting for values.", )
def _product_render_arnold(self): # get timestamp for all the tasks being submitted now = datetime.datetime.now() render_layers = self._get_render_layers() # figure out the total number of operations to perform for the progress num_ops = 1 + len(render_layers) * len(self._frame_list) # layer > frame num_ops += len(self._frame_list) # frame submission if self._remove_scenes: num_ops += 1 if self._generate_scenes: num_ops += 1 cur_op = 0 progress_dialog = QtGui.QProgressDialog( "Product render...", "", cur_op, num_ops, self) progress_dialog.setWindowTitle("Dark Knight is busy...") progress_dialog.setAutoReset(False) progress_dialog.setLabelText("Preparing maya file for rendering...") progress_dialog.show() ptask = self._cur_ptask ptask_version = self._cur_ptask.version(self._version) ptask_dir = self._cur_ptask.area.dir() ver_dir = ptask.area.dir(version=self._version) # need to get the maya file in the version directory maya_file = self.session.cmds.file(q=True, sceneName=True) maya_file = maya_file.replace(ptask_dir, ver_dir) file_base = os.path.splitext(os.path.split(maya_file)[1])[0] self.session.cmds.setAttr('defaultResolution.width', self._resolution.width) self.session.cmds.setAttr('defaultResolution.height', self._resolution.height) # set the output file naming convention to name.#.ext self.session.cmds.setAttr("defaultRenderGlobals.animation", True) self.session.cmds.setAttr("defaultRenderGlobals.putFrameBeforeExt", True) self.session.cmds.setAttr("defaultRenderGlobals.outFormatControl", False) # set all other cameras to not be renderable (this seems to work) cam_shape_list = self.session.cmds.ls(cameras=True) for cam_shape in cam_shape_list: cam_name = str( self.session.cmds.listRelatives(cam_shape, parent=True)[0]) if cam_name == self._camera: self.session.cmds.setAttr(cam_shape + ".renderable", 1) else: self.session.cmds.setAttr(cam_shape + ".renderable", 0) # ---- sync current work area to version snapshot to render from cur_project = self.session.cmds.workspace(query=True, rootDirectory=True) ver_project = cur_project.replace(ptask_dir, ver_dir) progress_dialog.setLabelText("Sync'ing work to current version...") try: self.session.save() self._sync_latest() except Exception as e: self._show_error("Unable to save & sync the latest work: " + str(e)) self.setEnabled(True) progress_dialog.close() return cur_op += 1 progress_dialog.setValue(cur_op) create_action_cls = ActionRegistry().get_action('create', 'product') if not create_action_cls: progress_dialog.close() raise DarkKnightError("Unable to find product creation action.") # ---- clean up ASSs scene_dir = os.path.join(ver_project, 'arnold', file_base, 'ass') if self._remove_scenes: progress_dialog.setLabelText("Removing ass files...") if os.path.isdir(scene_dir): try: shutil.rmtree(scene_dir) except Exception as e: progress_dialog.close() raise DarkKnightError("Unable to clean up ass files: " + str(e)) cur_op += 1 progress_dialog.setValue(cur_op) # ---- get a list of warnings to ignore #prman_config = ptask.area.config(PRMAN_CONFIG_PATH, # composite_ancestors=True, composite_method="override") #prman_warnings = " ".join( # ["-woff " + w for w in prman_config.get('woff', [])]) # ---- construct scripts for the queue render_summary = [] for render_layer in render_layers: progress_dialog.setLabelText( "Creating product for layer: {rl}...".format(rl=render_layer)) # ensure product exists for each render layer create_action = create_action_cls( product=render_layer, ptask=ptask_version.ptask_spec, version=ptask_version.number, category='imgseq', description=render_layer + " render layer", file_type=self._file_type, resolution=self._res_str, note=self._version_note, ) try: create_action() except ActionError as e: progress_dialog.close() raise DarkKnightError("Unable to create product: " + str(e)) product_repr = create_action.product_repr product_repr_area = product_repr.area progress_dialog.setLabelText( "Provisioning 'queue' directory in product...") # make sure queue directory exists try: product_repr_area.provision('queue') except Exception as e: progress_dialog.close() raise DarkKnightError( "Unable to create queue scripts directory: " + str(e)) queue_dir = product_repr_area.dir(dir_name='queue') tasks_info_file = os.path.join(queue_dir, 'tasks_info.cfg') tasks_info_config = Config() # dpaset command to run dpaset_cmd = 'eval "`dpa env ptask {pt}@{vn}`"'.format( pt=ptask.spec, vn=ptask_version.number) # set group permissions on project dir, recursively os.system("chmod g+rw {pd} -R".format(pd=ver_project)) # figure out the render layer if render_layer == 'masterLayer': layer_index = self.session.cmds.getAttr("defaultRenderLayer.rlid") else: layer_index = self.session.cmds.getAttr(render_layer + ".rlid") frame_scripts = [] for frame in self._frame_list: frame_padded = str(frame).zfill(4) progress_dialog.setLabelText( "Building render shell script for {rl} frame {f}".format( rl=render_layer, f=frame_padded)) script_path = os.path.join(queue_dir, "{rl}.{fn}.sh".format(rl=render_layer, fn=frame_padded)) out_dir = product_repr_area.dir() out_file = os.path.join(out_dir, "{rl}.{fn}.{ft}".\ format(rl=render_layer, fn=frame_padded, ft=self._file_type)) simple_scene = "{proj}arnold/{fb}/ass/{fb}.{fn}.ass".format( proj=ver_project, fb=file_base, fn=frame_padded) layer_scene = "{proj}arnold/{fb}/ass/{fb}_{rl}.{fn}.ass".\ format(proj=ver_project, fb=file_base, fn=frame_padded, rl=render_layer) render_cmd = "/opt/solidangle/arnold-maya2014/bin/kick -dw -v 0 -i $ASS_PATH " render_cmd += "-l /opt/solidangle/arnold-maya2014/shaders " render_cmd += "-o {od} ".format(od=out_file) #render_cmd += "-f {rl} ".format(rl=render_layer) #render_cmd += "-p {proj} ".format(proj=ver_project) #render_cmd += "--prman '-t:0 -cwd \"{proj}\" {warn}' ".\dd # format(proj=ver_project, warn=prman_warnings) with open(script_path, "w") as script_file: script_file.write("#!/bin/bash\n\n") # XXX these should happen automatically in the queue... script_file.write("source /DPA/wookie/dpa/bash/startup.bash\n") script_file.write("pipeup\n") # 'kick' command has to be added to $PATH # Create env variable for Arnold License server script_file.write("export [email protected]\n\n") script_file.write("# set the ptask version to render\n") script_file.write(dpaset_cmd + "\n") script_file.write("cd " + ver_project + "\n\n") # the logic for determining which ass will be generated is # unclear at this point. So we'll build a conditional script_file.write("if [[ -f {lr} ]]; then\n".format(lr=layer_scene)) script_file.write(" export ASS_PATH={lr}\n".format(lr=layer_scene)) script_file.write("else\n") script_file.write(" export ASS_PATH={sr}\n".format(sr=simple_scene)) script_file.write("fi\n") script_file.write("# render!\n") script_file.write(render_cmd + "\n\n") script_file.write("chmod 660 {of}\n\n".format( of=os.path.join(out_dir, render_layer + "*." + self._file_type))) os.chmod(script_path, 0770) frame_scripts.append((frame_padded, script_path, out_file)) cur_op += 1 progress_dialog.setValue(cur_op) frame_tasks = [] task_id_base = get_unique_id(product_repr_area.spec, dt=now) tasks_info_config.add('base_id', task_id_base) if self._generate_scenes: frame_queue = 'hold' else: frame_queue = self._render_queue # create frame tasks for (frame, frame_script, out_file) in frame_scripts: progress_dialog.setLabelText( "Submitting frame: " + frame_script) task_id = task_id_base + "_" + frame if not self._debug_mode: # create tasks, don't actually submit yet create_queue_task(frame_queue, frame_script, task_id, output_file=out_file, submit=False, log_path=frame_script + '.log') frame_tasks.append((frame, task_id)) # # resubmit frame-by-frame because # group submit seems to be occasionally # having problems. os.system("cqresubmittask {qn} {tid}".format( qn=frame_queue, tid=task_id)) cur_op += 1 progress_dialog.setValue(cur_op) frame_info = Config() for (frame, task_id) in frame_tasks: frame_info.add(str(frame), task_id) tasks_info_config.add('frame_ids', frame_info) # resubmit all at once (workaround for slow individual submissions) # # This approach seems to be having problems with the server # communications. Switch to frame-by-frame resubmit because # that has worked where this fails #os.system("cqresubmittask {qn} {tid}".format( # qn=frame_queue, tid=task_id_base)) if self._generate_scenes: progress_dialog.setLabelText("Creating ass generation script...") script_path = os.path.join(queue_dir, "{rl}_assgen.sh".format(rl=render_layer)) with open(script_path, "w") as script_file: script_file.write("#!/bin/bash\n\n") # XXX these should happen automatically in the queue... script_file.write("source /DPA/wookie/dpa/bash/startup.bash\n") script_file.write("pipeup\n\n") script_file.write("# set the ptask version to render\n") script_file.write(dpaset_cmd + "\n") script_file.write("cd " + ver_project + "\n\n") script_file.write("# generate the ass files...\n") current_render_layer = render_layer if render_layer == 'masterLayer': current_render_layer = "defaultRenderLayer" switch_render_layer_cmd = "editRenderLayerGlobals " switch_render_layer_cmd += "-currentRenderLayer \"{rl}\"".\ format(rl=current_render_layer) arnold_export_cmd = "arnoldExportAss -f \"{ad}/{fb}_{rl}.ass\" ".\ format(ad=scene_dir, fb=file_base, rl=render_layer) arnold_export_cmd += "-startFrame {sf} -endFrame {ef} -frameStep 1 ".\ format(li=layer_index, sf=self._frange.start, ef=self._frange.end) arnold_export_cmd += "-mask 255 -lightLinks 1 -shadowLinks 1 -cam {cam}".\ format(cam=self._camera) maya_batch_cmd = 'maya -batch -proj "{proj}" '.format( proj=ver_project) maya_batch_cmd += '-command \'{srlc}; {ar}\' '.\ format(srlc=switch_render_layer_cmd, ar=arnold_export_cmd) maya_batch_cmd += '-file "{mf}"'.format(mf=maya_file) script_file.write(maya_batch_cmd + "\n") script_file.write( "\n# make sure project dir has group permissions\n") script_file.write( "chmod g+rw {pd} -R\n\n".format(pd=ver_project)) # submit the frames to render script_file.write("# Submit frames after ass gen \n") for (frame, frame_task) in frame_tasks: script_file.write("cqmovetask {qn} {tid}\n".format( qn=self._render_queue, tid=frame_task)) # changed to move group #script_file.write("cqmovetask {qn} {tid}\n".format( #qn=self._render_queue, tid=task_id_base)) os.chmod(script_path, 0770) # submit the scenegen script progress_dialog.setLabelText( "Submitting ass gen: " + script_path) task_id = task_id_base + "_asses" tasks_info_config.add('assgen_id', task_id) if not self._debug_mode: create_queue_task(self._scenegen_queue, script_path, task_id, output_file=scene_dir, submit=True, log_path=script_path + '.log') cur_op += 1 progress_dialog.setValue(cur_op) cur_op += 1 progress_dialog.setValue(cur_op) progress_dialog.close() render_summary.append( (render_layer, task_id_base, product_repr, queue_dir)) # For now, disable wrangling tickets. bsddb is causing problems # - zshore, 2015-10-23 # if not self._debug_mode: # # ---- dpa specific queue stuff # from cheesyq import DPAWrangler # # create wrangling ticket # wrangle = DPAWrangler.WrangleRecord(task_id_base) # wrangle.frames = self._frame_list # db = DPAWrangler.GetWranglingDB() # db.set(wrangle.baseId, wrangle) # DPAWrangler.AssignWranglerTask("none", task_id_base) tasks_info_config.write(tasks_info_file) os.chmod(tasks_info_file, 0660) if not self._debug_mode: # send msg... msg_title = "Queue submission report: " + \ now.strftime("%Y/%m/%d %H:%M:%S") msg_body = "Submitted the following tasks for " + \ ptask.spec + ":\n\n" msg_body += " Description: " + self._version_note + "\n" msg_body += " Resolution: " + self._res_str + "\n" msg_body += " File type: " + self._file_type + "\n" msg_body += " Camera: " + self._camera + "\n" if self._generate_scenes: msg_body += " Ass gen queue: " + self._scenegen_queue + "\n" msg_body += " Render queue: " + self._render_queue + "\n" msg_body += " Frames: " + str(self._frange) + "\n" msg_body += " Ass directory: " + scene_dir + "\n" msg_body += "\n" for (layer, task_id_base, product_repr, queue_dir) in render_summary: msg_body += " Render layer: " + layer + "\n" msg_body += " Base task ID: " + task_id_base + "\n" msg_body += " Product representation: " + \ product_repr.spec + "\n" msg_body += " Scripts directory: " + queue_dir + "\n" msg_body += "\n" dk_config = ptask.area.config(DK_CONFIG_PATH, composite_ancestors=True, composite_method="append") recipients = dk_config.get('notify', []) recipients.append(current_username()) recipients = emails_from_unames(recipients) notification = Notification(msg_title, msg_body, recipients, sender=User.current().email) notification.send_email()
def setup_cl_args(cls, parser): parser.add_argument("username", nargs="?", default=current_username(), help="Print info for the supplied username.")
def setup_cl_args(cls, parser): # ptask spec (name, parent) - can be relative to current ptask parser.add_argument( "spec", action=ParsePTaskSpecArg, help="The spec representing the ptask to create.", ) # ptask type parser.add_argument("-t", "--type", dest="ptask_type", default=None, help="The type of ptask to create.", metavar="type") # description parser.add_argument("-d", "--description", default=None, help="A description of the ptask being created.", metavar='"description"') # creator parser.add_argument("-c", "--creator", default=current_username(), help="The creator of this ptask.", metavar="username") # start_date parser.add_argument( "-b", "--begins", action=ParseDateArg, dest="start_date", help="The start date of the new ptask.", metavar="YYYY-MM-DD", ) # due_date parser.add_argument( "-e", "--ends", action=ParseDateArg, dest="due_date", help="The due date of the new ptask.", metavar="YYYY-MM-DD", ) # source parser.add_argument( "-s", "--source", action=ParsePTaskSpecArg, help="The ptask to use when creating this ptask.", metavar="source_spec", ) # force - hidden option to force creation without prompting parser.add_argument( "-f", "--force", action="store_true", default=False, help="Force creation without prompting for values.", )
class Logger(object): # ------------------------------------------------------------------------ # Public class attributes: # ------------------------------------------------------------------------ # the available levels for logging levels = ['NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] # log to the shared log directory, or a tempdirectory if undefined log_dir = DpaVars.share_logs(default=tempfile.gettempdir()).get() # the default logging level log_level = "WARNING" # ------------------------------------------------------------------------ # Private class attributes: # ------------------------------------------------------------------------ # ---- setup consistent stdout handling for all dpa code # formatting for all logging to stdout _stdout_formatter = logging.Formatter( fmt="%(name)s %(levelname)s: %(message)s") # handler for all stdout. Anything logged at a level matching or above # should go to stdout _stdout_handler = logging.StreamHandler() _stdout_handler.setFormatter(_stdout_formatter) _stdout_handler.setLevel(_log_level_from_name(log_level)) # format all log messages _logfile_formatter = logging.Formatter( fmt="[%(asctime)s] {un}@{ma} %(name)s %(levelname)s: %(message)s".\ format(ma=platform.node(), un=current_username()), datefmt="%Y/%m/%d-%H:%M:%S", ) # attach the handler to the root logger. _root_logger = logging.getLogger() _root_logger.addHandler(_stdout_handler) # keep track of processed logger names _logger_cache = dict() # ------------------------------------------------------------------------ # Class methods: # ------------------------------------------------------------------------ @classmethod def get(cls, name=None): if not name: name = "dpa" # make sure everything is namespaced with dpa. if not name.startswith("dpa"): name = ".".join(["dpa", name]) if not name in cls._logger_cache.keys(): # create the custom logger wrapper # log to a file based on the logger's name log_file = name + ".log" log_path = os.path.join(cls.log_dir, log_file) # ensure path exists with proper permissions first if not os.path.exists(log_path): _create_log_file(log_path) # create a handler to output to a log file, set the level to INFO logfile_handler = logging.FileHandler(filename=log_path) logfile_handler.setLevel(_log_level_from_name('INFO')) logfile_handler.setFormatter(cls._logfile_formatter) # get the named logger, set have it process all messages, and # have it send output to the file handler. note the level is # set on the handler. logger = logging.getLogger(name) logger.setLevel(_log_level_from_name('DEBUG')) logger.addHandler(logfile_handler) cls._logger_cache[name] = logger return cls._logger_cache[name] # ------------------------------------------------------------------------ @classmethod def set_level(cls, level): """Set the level for the root logger's stdout.""" cls.log_level = level cls._stdout_handler.setLevel(_log_level_from_name(level))