def asset_results(self, value): chosen_asset = value[0] project = Project() self.frame_range = qd.input("Enter frame range (as numeric input) or leave blank if none:") if self.frame_range is None or self.frame_range == u'': self.frame_range = 1 self.frame_range = str(self.frame_range) if not self.frame_range.isdigit(): qd.error("Invalid frame range input. Setting to 1.") self.body = project.get_body(chosen_asset) self.body.set_frame_range(self.frame_range) department_list = [] asset_type = self.body.get_type() if str(asset_type) == 'prop': department_list = self.body.prop_export_departments() self.department_results(department_list) elif str(asset_type) == 'character': department_list = self.body.char_export_departments() elif str(asset_type) == 'set': department_list = self.body.set_export_departments() elif str(asset_type) == 'shot': department_list = self.body.shot_export_departments() self.item_gui = sfl.SelectFromList(l=department_list, multiple_selection=True, parent=maya_main_window(), title="Select department(s) for this export: ") self.item_gui.submitted.connect(self.department_results)
def dcc_geo(self, parent, asset_name, already_tabbed_in_node=None, excluded_departments=[], character=False, mode=UpdateModes.CLEAN): # Set up the body/elements and check if it's an asset. body = Project().get_body(asset_name) if not body.is_asset(): qd.error("Must be an asset.") return None # Set up the nodes, name geo node = already_tabbed_in_node if already_tabbed_in_node else parent.createNode( "dcc_geo") if character: node.setName("geo") else: try: node.setName(asset_name.title()) except: node.setName(asset_name.title() + "_1", unique_name=True) # Set the asset_name data tag data = node.parm("data").evalAsJSONMap() data["asset_name"] = asset_name node.parm("data").set(data) # Set the contents to the nodes that belong to the asset self.update_contents_geo(node, asset_name, excluded_departments, mode) return node
def tool_results(self, value): tool_name = value[0] source = os.path.join(Environment().get_hda_dir(), str(tool_name) + ".hda") hou.hda.installFile(source) obj = hou.node("/obj") try: hda = obj.createNode(tool_name) except: try: out = hou.node("/out") hda = out.createNode(tool_name) except Exception as e: qd.error("Could not find the correct context for tool: " + str(tool_name), details=str(e)) return definition = hou.hdaDefinition(hda.type().category(), hda.type().name(), source) definition.setPreferred(True) hda.allowEditingOfContents() try: hda.setName(tool_name) except: print( str(tool_name) + " cloned but could not be renamed correctly.") layout_object_level_nodes()
def exportSelected(self, selection, destination, tag=None, startFrame=1, endFrame=1, disregardNoTags=False): endFrame = self.frame_range abcFiles = [] # for node in selection: # abcFilePath = os.path.join(destination, str(node) + '.abc') # print("abc file path 1: ", abcFilePath) abcFilePath = os.path.join(destination, self.element.get_long_name() + '.abc') try: command = self.buildTaggedAlembicCommand(abcFilePath, tag, startFrame, endFrame) except: qd.error('Alembic export failed.') print('Export Alembic command: ', command) pm.Mel.eval(command) abcFiles.append(abcFilePath) return abcFiles
def publish_src_node_to_department(self, src, node, department, user, comment): if os.path.exists(src): try: #save node definition--this is the same as the Save Node Type menu option. Just to make sure I remember how this works - We are getting the definition of the selected hda and calling the function on it passing in the selected hda. We are not calling the function on the selected hda. node.type().definition().updateFromNode(node) except hou.OperationFailed, e: qd.error( 'There was a problem publishing the HDA to the pipeline.\n' ) print(str(e)) return element = self.body.get_element(department, Element.DEFAULT_NAME) dst = self.publish_element(element, user, src, comment) print("dst: ", dst) try: hou.hda.installFile(dst) definition = hou.hdaDefinition(node.type().category(), node.type().name(), dst) definition.setPreferred(True) node.allowEditingOfContents() except Exception as e: qd.error("Publish failed for " + str(department), details=str(e))
def tab_in(self, parent, asset_name, already_tabbed_in_node=None, excluded_departments=[]): print "Creating node for {0}".format(asset_name) body = Project().get_body(asset_name) if body is None or not body.is_asset(): qd.error( "Pipeline error: This asset either doesn't exist or isn't an asset." ) return if body.get_type() == AssetType.CHARACTER: return self.dcc_character(parent, asset_name, already_tabbed_in_node, excluded_departments) elif body.get_type() == AssetType.PROP: return self.dcc_geo(parent, asset_name, already_tabbed_in_node, excluded_departments) elif body.get_type() == AssetType.SET: return self.dcc_set(parent, asset_name, already_tabbed_in_node) else: qd.error( "Pipeline error: this asset isn't a character, prop or set.") return
def results(self, value): type = value[0] name = self.name # determine if asset was created or not. created = True if name is None or type is None: created = False if created: project = Project() body = project.create_asset(name, asset_type=type) if body == None: # print a message about failure/duplicate qd.error("Asset with name " + name + " already exists in pipeline.") else: assembler = Assembler() assembler.create_hda(name, body=body) qd.info("Asset created successfully.", "Success") else: qd.error("Asset creation failed.")
def non_gui_publish_go(selectedHDA=None, comment=None): if selectedHDA != None: non_gui_publish_hda(selectedHDA, comment) else: qd.error('Please select a single node') return
def dcc_character(self, parent, asset_name, already_tabbed_in_node=None, excluded_departments=[], mode=UpdateModes.CLEAN, shot=None): # Set up the body/elements and make sure it's a character body = Project().get_body(asset_name) if not body.is_asset() or not body.get_type() == AssetType.CHARACTER: qd.error("Must be a character.") return None # If there's an already tabbed in node, set it to that node node = already_tabbed_in_node if already_tabbed_in_node else parent.createNode( "dcc_character") try: node.setName(asset_name.title()) except: node.setName(asset_name.title() + "_1", unique_name=True) node.parm("asset_name").set(asset_name) # Set the asset_name data tag data = node.parm("data").evalAsJSONMap() data["asset_name"] = asset_name node.parm("data").set(data) # Set the contents to the character's nodes self.update_contents_character(node, asset_name, excluded_departments, mode, shot) return node
def publish( self, selectedHDA=None ): #, departments=[Department.HDA, Department.ASSEMBLY, Department.MODIFY, Department.MATERIAL, Department.HAIR, Department.CLOTH]): project = Project() self.selectedHDA = selectedHDA if self.selectedHDA is None: self.selectedHDA = get_selected_node() if self.selectedHDA is None: return if self.selectedHDA.type().definition() is not None: self.src = self.selectedHDA.type().definition().libraryFilePath() if self.node_name: self.asset_results([self.node_name]) return asset_list = project.list_props_and_actors() self.item_gui = sfl.SelectFromList( l=asset_list, parent=houdini_main_window(), title="Select an asset to publish to") self.item_gui.submitted.connect(self.asset_results) else: qd.error('The selected node is not a digital asset') return
def results(self, values): selection = str(values[0]) shot = Project().get_body(selection) comp_element = shot.get_element(Department.COMP) self.publishes = comp_element.list_publishes() os.environ["DCC_NUKE_ASSET_NAME"] = selection if not self.publishes: # has not been imported. Import it first. shot_importer = importer.NukeImporter() shot_importer.shot_results([selection]) return else: # get the latest publish username = Environment().get_current_username() try: filepath = comp_element.checkout(username) except: filepath = comp_element.get_last_publish()[3] if os.path.exists(filepath): qd.info("Opening file, please wait.") nuke.scriptOpen(filepath) else: qd.error("Couldn't find the file.")
def rollback_element(self, node, department, name): self.node = node self.department = department project = Project() body = project.get_body(name) element = body.get_element(department) self.publishes = element.list_publishes() print("publishes: ", self.publishes) if not self.publishes: qd.error("There have been no publishes for this department. Rollback failed.") return # make the list a list of strings, not tuples self.sanitized_publish_list = [] for publish in self.publishes: path = publish[3] file_ext = path.split('.')[-1] if not file_ext == "hda" and not file_ext =="hdanc": continue label = publish[0] + " " + publish[1] + " " + publish[2] self.sanitized_publish_list.append(label) self.item_gui = sfl.SelectFromList(l=self.sanitized_publish_list, parent=houdini_main_window(), title="Select publish to clone") self.item_gui.submitted.connect(self.publish_selection_results)
def export(self, element, selection=None, startFrame=None, endFrame=None): project = Project() bodyName = element.get_parent() body = project.get_body(bodyName) abcFilePath = element.get_cache_dir() self.element = element if startFrame is None: startFrame = pm.playbackOptions(q=True, animationStartTime=True) if endFrame is None: endFrame = pm.playbackOptions(q=True, animationEndTime=True) if body.is_shot(): startFrame -= 1 endFrame = int(endFrame) endFrame += 1 endFrame = str(endFrame) files = self.exportReferences(abcFilePath, tag='DCC_Alembic_Export_Flag', startFrame=startFrame, endFrame=endFrame) if self.cameras: files.extend(self.export_cameras(body, startFrame, endFrame)) elif body.is_asset(): if body.get_type() == AssetType.SET: files = self.exportReferences(abcFilePath) else: files = self.exportAll(abcFilePath, tag='DCC_Alembic_Export_Flag', element=element) elif body.is_crowd_cycle(): files = self.exportAll(abcFilePath, tag='DCC_Alembic_Export_Flag', startFrame=startFrame, endFrame=endFrame, element=element) if not files: #Maybe this is a bad distinction but None is if it was canceled or something and empty is if it went but there weren't any alembics if files is None: return qd.error( 'No alembics were exported. Make sure the top-level group is tagged.' ) return for abcFile in files: os.system('chmod 774 ' + abcFile) exported_asset_names = "" for file in files: asset_file_name = str(file).rpartition('/')[2] exported_asset_names += asset_file_name + '\n' qd.info("Alembics exported successfully: " + '\n' + exported_asset_names) return files
def results(self, value): type = value[0] name = self.name # determine if asset was created or not. created = True if name is None or type is None: created = False if created: project = Project() body = project.create_asset(name, asset_type=type) if body == None: # print a message about failure/duplicate qd.error("Asset with name " + name + " already exists in pipeline.") elif self.type == AssetType.SHOT: qd.info("Asset created successfully.", "Success") else: if self.type == AssetType.SET: # create whole_set.json setPath = os.path.join(Project().get_assets_dir(), str(name), "model", "main", "cache", "") exporter = JSONExporter() exporter.createWholeSetJSON(setPath) assembler = Assembler() assembler.create_hda(name, body=body) qd.info("Asset created successfully.", "Success") else: qd.error("Asset creation failed.")
def check_unsaved_changes(): unsaved_changes = mc.file(q=True, modified=True) if unsaved_changes: response = qd.yes_or_no("Would you like to publish the current asset before you proceed?", title="Unsaved changes detected", details="(Press No if you just created a new scene or opened Maya.)") if response is True: # instead of saving, publish. scene = mc.file(q=True, sceneName=True) dir_path = scene.split("assets/") try: asset_path = dir_path[1].split("/") except: # scene path is stored in the user directory instead of assets. We can't get the asset name, so they must publish manually. qd.error("Publish failed. Please publish manually before cloning the new asset.") return asset_name = asset_path[0] try: department = asset_path[1].split("/")[0] print("department " + department) except: department = None if department: print("department found") else: qd.warning("Skipping changes to " + str(asset_name)) return publisher = Publisher(quick_publish=True, export=False) publisher.non_gui_publish(asset_name, department)
def checkFileName(name): from pipe.gui import quick_dialogs as qd if not re.match('^[a-zA-Z][a-zA-Z0-9.]*', name): qd.error("AssetName can't start with a number or symbol!\nAlso, AssetName can only have letters, numbers and \'.\'\'s") return False first_char_to_lower = lambda s: s[:1].lower() + s[1:] if s else '' name = first_char_to_lower(name) if name.find('_') != -1: qd.error("AssetName can't have underscore!") return False if name.find('/') != -1: qd.error("AssetName can't have backslash!") return False if name.find('!') != -1: qd.error("AssetName can't have Exclamation point!") return False if name.find('|') != -1: qd.error("AssetName can't have pipe (|)!") return False return True
def tab_into_correct_place(self, inside, node, department): # If the node belongs inside a DCC Character, do the following if department in self.dcc_character_departments: # Hair and Cloth assets should be connected to geo. If it doesn't exist, throw an error. geo = inside.node("geo") if geo is None: qd.error( "There should be a geo network. Something went wrong.") return # Attach the Hair or Cloth asset to the geo network. node.setInput(0, geo) # If the node belongs inside a DCC Geo, do the following else: # Shot_modeling and geo are our way of knowing where to insert nodes. If either of them is null, throw an error. geo = inside.node("geo") shot_modeling = inside.node("shot_modeling") if shot_modeling is None or geo is None: qd.error( "There should be a shot_modeling and geo network. Something went wrong." ) return None # If we're inserting a modify node, do the following if department == Department.MODIFY: # If there is a material node, put the modify node in between material and geo. material = inside.node("material") if material is not None: node.setInput(0, geo) material.setInput(0, node) # Else, stick it between geo and shot_modeling. else: node.setInput(0, geo) shot_modeling.setInput(0, node) # If we're inserting a material node, do the following elif department == Department.MATERIAL: # If there is a modify node, put the material node in between modify and shot_modeling. modify = inside.node("modify") if modify is not None: node.setInput(0, modify) shot_modeling.setInput(0, node) # Else, stick it between geo and shot_modeling. else: node.setInput(0, geo) shot_modeling.setInput(0, node) inside.layoutChildren() return node
def generateGeometry(self, path='',element=None): ''' Function for generating geometry for Maya files. Creates the following output formats: .obj @return: True if all files were created successfully False if some files were not created @post: Missing filenames are printed out to both the Maya terminal as well as presented in a Maya confirm dialog. ''' path = os.path.dirname(mc.file(q=True, sceneName=True)) if not os.path.exists (os.path.join(path, 'cache')): os.makedirs(os.path.join(path, 'cache')) ABCPATH = os.path.join(path, 'cache', 'abcFiles') if os.path.exists(ABCPATH): shutil.rmtree(ABCPATH) filePath = mc.file(q=True, sceneName=True) fileDir = os.path.dirname(filePath) abcFilePath = self.getElementCacheDirectory(fileDir, element) if abcFilePath is None: return False selection = mc.ls(geometry=True, visible=True) selection_long = mc.ls(geometry=True, visible=True, long=True) project = Project() if element is None: checkout = project.get_checkout(path) if checkout is None: qd.error('There was a problem exporting the alembic to the correct location. Checkout the asset again and try one more time.') return None body = project.get_body(checkout.get_body_name()) element = body.get_element(checkout.get_department_name(), checkout.get_element_name()) else: body = project.get_body(element.get_parent()) # We decided to try exporting all the geo into one alembic file instead of many. This is the line that does many # abcs = abcExport(selection_long, ABCPATH) # if body.is_asset(): # if body.get_type() == AssetType.SET: # abcs = self.abcExportLoadedReferences(ABCPATH) # else: # abcs = self.abcExportAll(element.get_long_name(), ABCPATH) # else: abcs = self.abcExportAll(element.get_long_name(), ABCPATH) if not len(self.checkFiles(abcs)) == 0: return False return True
def publish_element(self, element, user, src, comment="None"): dst = element.publish(user.get_username(), src, comment) #Ensure file has correct permissions try: os.chmod(dst, 0660) except: qd.error("Error setting file permissions.") return dst
def check_body(self, body): # Check if this body is an asset. If not, return error. body = body if not body.is_asset(): qd.error("Must be an asset of type PROP, CHARACTER or SET.") return None type = body.get_type() return type
def commit_conversions(self): # Find all boxes that have nodes that were made by the conversion script boxes = [] for item in hou.selectedItems(): if not isinstance(item, hou.NetworkBox): continue # If the box doesn't have two nodes in it, it's definitely not ours nodes = item.nodes() if len(nodes) != 2: continue # If neither is named _new and/or neither is named _old, it's not one of ours if not "_new" in nodes[0].name() and not "_new" in nodes[1].name(): continue if not "_old" in nodes[0].name() and not "_old" in nodes[1].name(): continue # If the assets are not named the same, it's not one of ours print nodes[0].name()[:-4] print nodes[1].name()[:-4] if nodes[0].name()[:-4] != nodes[1].name()[:-4]: continue # If it passed the tests, add it to the list of network boxes we can work with boxes.append(item) print boxes # Don't go on unless there's a valid network box if len(boxes) < 1: qd.error( "There aren't any network boxes created by the conversion script." ) return for box in boxes: old_node = next( (node for node in box.nodes() if "_old" in node.name()), None) new_node = next( (node for node in box.nodes() if "_new" in node.name()), None) old_hda = old_node.type().definition() old_hda.setIcon(Environment().get_project_dir() + '/pipe/tools/_resources/1.png') publish.non_gui_publish_go(old_node, "Converted to V2") for child in new_node.allSubChildren(): if "_material" in child.type().name( ) or "_modify" in child.type().name(): publish.non_gui_publish_go(child, "Converted from V1")
def getElementCacheDirectory(self, path, element=None): if element is None: project = Project() checkout = project.get_checkout(path) if checkout is None: qd.error('There was a problem exporting the alembic to the correct location. Checkout the asset again and try one more time.') return None body = project.get_body(checkout.get_body_name()) element = body.get_element(checkout.get_department_name(), checkout.get_element_name()) return element.get_cache_dir()
def publish_hda(self): project = Project() environment = Environment() user = environment.get_user() selectedHDA = self.selectedHDA src = self.src body = self.body asset_type = body.get_type() inside = selectedHDA.node("inside") modify = inside.node("modify") material = inside.node("material") hair = inside.node("hair") cloth = inside.node("cloth") if asset_type == AssetType.CHARACTER: geo = inside.node("geo") geo_inside = geo.node("inside") modify = geo_inside.node("modify") material = geo_inside.node("material") departments_to_publish = [] if not modify is None: departments_to_publish.append("modify") if not material is None: departments_to_publish.append("material") if not hair is None: departments_to_publish.append("hair") if not cloth is None: departments_to_publish.append("cloth") if body is None: qd.error("Asset not found in pipe.") return comment = "publish by " + str(user.get_username( )) + " in departments " + str(departments_to_publish) for department in departments_to_publish: inside = self.get_inside_node(asset_type, department, selectedHDA) node = inside.node(department) src = node.type().definition().libraryFilePath() self.publish_src_node_to_department(src, node, department, user, comment) success_message = "Success! Published to " + str( departments_to_publish) self.print_success_message(success_message) return "published to " + str(departments_to_publish)
def get_selected_node(): nodes = hou.selectedNodes() if len(nodes) == 1: selectedHDA = nodes[0] elif len(nodes) > 1: qd.error('Too many nodes selected. Please select only one node.') return None else: qd.error('No nodes selected. Please select a node.') return None return selectedHDA
def update_contents_geo(self, node, asset_name, excluded_departments=[], mode=UpdateModes.SMART, shot=None): # Set up the body/elements and make sure it's not an actor. Just do some simple error checking. body = Project().get_body(asset_name) if body is None: qd.error("Asset doesn't exist.") return None if not body.is_asset() or body.get_type( ) == AssetType.SET or "dcc_geo" not in node.type().name(): qd.error("Must be a prop or actor.") return None # Get interior nodes importnode = node.node("import") inside = node.node("inside") # Set the asset_name and reload if node.parm("asset_name").evalAsString() != asset_name: node.parm("asset_name").set(asset_name) importnode.parm("reload").pressButton() # Tab in each content HDA based on department for department in self.dcc_geo_departments: # If the department is not excluded, tab-in/update the content node like normal if department not in excluded_departments: self.update_content_node( node, inside, asset_name, department, mode, inherit_parameters=department == Department.MODIFY) # If the department is excluded, we should delete it. elif mode == UpdateModes.CLEAN: self.destroy_if_there(inside, department) inside.layoutChildren() # If this prop is being animated, set parms accordingly if shot is not None: node.parm("space").set("anim") node.parm("asset_department").set("rig") node.parm("shot").set(shot) return node
def create_body(self): name = qd.input("What's the name of this asset?") # determine if asset was created or not. created = True if name is None: created = False if created: qd.info("Asset created successfully (but not really, yet).", "Success") else: qd.error("Asset creation failed.")
def assemble_hda_instance(self, asset_name, department, inside): # Tab an instance of this new HDA into the asset you are working on try: hda_instance = inside.createNode(asset_name + "_" + department) print('created hda instance for ' + asset_name + ' in ' + department) except Exception as e: qd.error("HDA Creation Error. " + asset_name + "_" + department + " must not exist.") hda_instance.setName(department) self.tab_into_correct_place(inside, hda_instance, department) hda_instance.allowEditingOfContents() hda_instance.setSelected(True, clear_all_selected=True) return hda_instance
def subnet_type(self, asset_name): body = Project().get_body(asset_name) if body is None or not body.is_asset(): qd.error( "Pipeline error: This asset either doesn't exist or isn't an asset." ) return if body.get_type() == AssetType.ACTOR: return "dcc_character" elif body.get_type() == AssetType.PROP: return "dcc_geo" elif body.get_type() == AssetType.SET: return "dcc_set" else: qd.error("Pipeline error: this asset isn't an actor, prop or set.") return
def create_hda(self, asset_name, body=None, department_paths=None, already_tabbed_in_node=None): if body is None: body = self.body type = self.check_body(body) if type is None: qd.error("Invalid body type specified.") return None # Tab in the parent asset that will hold this checked out HDA node = already_tabbed_in_node if already_tabbed_in_node else self.tab_in( hou.node("/obj"), asset_name) #, excluded_departments=[department]) if type == AssetType.SET: return node, self.assemble_set(node) departments = self.get_departments(type) created_instances = [] for department in departments: element = self.get_hda_element(body, department, asset_name) checkout_file = self.get_checkout_file(element) if department_paths: content_hda_filepath = department_paths[department] else: content_hda_filepath = None # CREATE NEW HDA DEFINITION self.create_new_hda_definition(element, asset_name, department, checkout_file, content_hda_filepath) # get the "inside" node definied in otls/dcc_inside.hda inside = self.get_inside_node(type, department, node) # Tab an instance of this new HDA into the asset you are working on hda_instance = self.assemble_hda_instance(asset_name, department, inside) created_instances.append(hda_instance) return node, created_instances
def results(self, value): print("Final value: ", value[0]) filename = value[0] project = Project() body = project.get_body(filename) self.body = body type = body.get_type() element = self.get_element_option(type, body) if self.quick: latest = element.get_last_publish() if not latest: qd.error("There have been no publishes in this department.") return else: selected_scene_file = latest[3] self.open_scene_file(selected_scene_file) return if element is None: qd.warning("Nothing was cloned.") return self.publishes = element.list_publishes() print("publishes: ", self.publishes) if not self.publishes: qd.error( "There have been no publishes in this department. Maybe you meant model?" ) return # make the list a list of strings, not tuples self.sanitized_publish_list = [] for publish in self.publishes: path = publish[3] file_ext = path.split('.')[-1] if not file_ext == "mb": continue label = publish[0] + " " + publish[1] + " " + publish[2] self.sanitized_publish_list.append(label) self.item_gui = sfl.SelectFromList(l=self.sanitized_publish_list, parent=maya_main_window(), title="Select publish to clone") self.item_gui.submitted.connect(self.publish_selection_results)