def update_user_attributes(source, target): """ Updates the target shape attributes with the given source shape content :param source: maya shape node :type source: str :param target: maya shape node :type target: str .. note:: This method loops twice on the use attributes. One time to add the missing attributes and the second to set their value. This allows avoiding issues when dealing with child attributes. """ # get user defined attributes user_attributes = cmds.listAttr(source, userDefined=True) if not user_attributes: return logger.debug("Updating user attributes on {}".format(target)) # loop on user defined attributes if any to ---> addAttr for attr in user_attributes: # adds attribute on shape add_attribute(source, target, attr) # loop on user defined attributes if any to ---> setAttr for attr in user_attributes: # updates the attribute values update_attribute(source, target, attr)
def update_shape(source, target): """ Connect the shape output from source to the input shape on target :param source: maya shape node :type source: str :param target: maya shape node :type target: str """ # clean uvs on mesh nodes clean_uvs_sets(target) # get attributes names attributes = get_shape_type_attributes(source) logger.debug("Updating shape: {} using --> {}".format(target, source)) # updates the shape cmds.connectAttr("{}.{}".format(source, attributes["output"]), "{}.{}".format(target, attributes["input"]), force=True) # forces shape evaluation to achieve the update cmds.dgeval("{}.{}".format(target, attributes["output"])) # finish shape update cmds.disconnectAttr("{}.{}".format(source, attributes["output"]), "{}.{}".format(target, attributes["input"]))
def wrapper_function(*args, **kwars): if not cmds.about(batch=True): # get panels views model_panels = cmds.getPanel(type="modelPanel") logger.debug("Isolating views") # isolate views for model_panel in model_panels: cmds.isolateSelect(model_panel, state=True) # clear isolated panel set isolate_set = cmds.isolateSelect(model_panel, query=True, viewObjects=True) if isolate_set: cmds.sets(clear=isolate_set) # forces view refresh cmds.refresh() # runs decorated function function_exec = function(*args, **kwars) return function_exec
def update_transformed_shape(source, target, hold_transform): """ Updates the target shape with the given source shape content :param source: maya shape node :type source: str :param target: maya shape node :type target: str :param hold_transform: keeps the transform node position values :type hold_transform: bool """ deform_origin = get_shape_orig(target) if deform_origin: return logger.debug("Transformed shape found: {}".format(target)) # maintain transform on target if hold_transform: update_shape(source, target) # update target transform else: update_transform(source, target)
def wrapper_function(*args, **kwars): # runs decorated function function_exec = function(*args, **kwars) if not cmds.about(batch=True): logger.debug("Focusing Flex window: {}".format(FLEX_UI_NAME)) # sets focus on Flex window cmds.setFocus(FLEX_UI_NAME) return function_exec
def wrapper_function(*args, **kwars): # runs decorated function function_exec = function(*args, **kwars) # get panels and set isolation state to off if not cmds.about(batch=True): logger.debug("Showing views") model_panels = cmds.getPanel(type="modelPanel") for model_panel in model_panels: cmds.isolateSelect(model_panel, state=False) return function_exec
def wrapper_function(*args, **kwars): # gets current time t1 = time.time() # runs decorated function function_exec = function(*args, **kwars) # gets new time t2 = time.time() - t1 logger.debug('function: {} took {}'.format(function.__name__, t2)) return function_exec
def wrapper_function(*args, **kwars): # gets selection sel = cmds.ls(selection=True) logger.debug("Holding selection: {}".format(sel)) # runs decorated function function_exec = function(*args, **kwars) # selects back cmds.select(sel, replace=True) return function_exec
def create_wrap(source, target, intermediate=None): """ Creates a wrap deformer on the target by using source as driver :param source: the maya source node :type source: str :param target: the maya target node :type target: str :param intermediate: the intermediate shape to use on the warp node :type intermediate: str :return: wrap node :rtype: str """ logger.debug("Creating wrap deformer for {} using {}".format( target, source)) # creates the deformer target_type = cmds.objectType(target) wrap_node = cmds.deformer(target, type="wrap", name="flex_warp")[0] cmds.setAttr("{}.exclusiveBind".format(wrap_node), 1) cmds.setAttr("{}.autoWeightThreshold".format(wrap_node), 1) cmds.setAttr("{}.dropoff[0]".format(wrap_node), 4.0) # sets settings for nurbs type shapes if target_type == "nurbsSurface" or target_type == "nurbsCurve": cmds.setAttr("{}.nurbsSamples[0]".format(wrap_node), 10) # sets settings for mesh type shapes else: cmds.setAttr("{}.inflType[0]".format(wrap_node), 2) # gets attributes types for the given target attrs = get_shape_type_attributes(target) # filters intermediate shape intermediate_shape = filter_shape_orig(source, intermediate) # connects the wrap node to the source cmds.connectAttr("{}.{}".format(intermediate_shape, attrs["output_world"]), "{}.basePoints[0]".format(wrap_node), force=True) cmds.connectAttr("{}.{}".format(source, attrs["output"]), "{}.driverPoints[0]".format(wrap_node), force=True) cmds.connectAttr("{}.worldMatrix[0]".format(target), "{}.geomMatrix".format(wrap_node), force=True) return wrap_node
def update_deformed_shape(source, target, mismatching_topology=True): """ Updates the target shape with the given source shape content :param source: maya shape node :type source: str :param target: maya shape node :type target: str :param mismatching_topology: ignore or not mismatching topologies :type mismatching_topology: bool """ # gets orig shape deform_origin = get_shape_orig(target) # returns as target is not a deformed shape if not deform_origin: return logger.debug("Deformed shape found: {}".format(target)) # returns if source and target shapes don't match if not is_matching_type(source, target): logger.warning( "{} and {} don't have same shape type. passing...".format( source, target)) return # returns if vertices count isn't equal and mismatching isn't requested if not mismatching_topology and not is_matching_count(source, target): logger.warning("{} and {} don't have same shape vertices count." "passing...".format(source, target)) return deform_origin = deform_origin[0] # updates map1 name copy_map1_name(source, deform_origin) # updates on mismatching topology if mismatching_topology and not is_matching_count(source, target): update_deformed_mismatching_shape(source, target, deform_origin) return # update the shape update_shape(source, deform_origin) # update uvs set on target update_uvs_sets(target)
def update_deformed_mismatching_shape(source, target, shape_orig): """ Updates the target shape with the given source shape content :param source: maya shape node :type source: str :param target: maya shape node :type target: str :param shape_orig: shape orig on the target shape :type shape_orig: str """ logger.debug("Running update deformed mismatched shapes") # gets all deformers on the target shape (supported by flex) deformers = get_deformers(target) if len(deformers["skinCluster"]) > 1: logger.warning( "Dual skinning is yet not supported. {} will be used".format( deformers["skinCluster"][0])) # Turns all deformers envelope off set_deformer_state(deformers, False) # creates deformers backups bs_nodes, skin_nodes, cluster_nodes = create_deformers_backups( source, target, shape_orig, deformers) # updates target shape update_shape(source, shape_orig) # updates skinning nodes update_skincluster_node(skin_nodes, deformers["skinCluster"]) # updates blendshapes nodes update_blendshapes_nodes(bs_nodes, deformers["blendShape"]) # update cluster nodes update_clusters_nodes(target, cluster_nodes) # updates uv sets on target shape update_uvs_sets(target) # Turns all deformers envelope ON set_deformer_state(deformers, True) # deletes backups delete_transform_from_nodes(set(bs_nodes).union(skin_nodes))
def analyze_groups(source, target): """ Analyze the shapes found inside the source and target groups :param source: maya transform node :type source: str :param target: maya transform node :type target: str """ logger.debug( "Analysing the following groups - source: {} - target: {}".format( source, target)) # gets the matching shapes matching_shapes = get_matching_shapes_from_group(source, target) # gets mismatching shape types mismatched_types = [ x for x in matching_shapes if not is_matching_type(x, matching_shapes[x]) ] # gets mismatching shape vertices count mismatched_count = [ x for x in matching_shapes if not is_matching_count(x, matching_shapes[x]) ] # gets mismatching shape bounding box mismatched_bbox = [ x for x in matching_shapes if not is_matching_bouding_box(x, matching_shapes[x]) ] logger.info("-" * 90) logger.info("Mismatch shapes types: {}".format(mismatched_types)) logger.info("Mismatch vertices shapes: {}".format(mismatched_count)) logger.info("Mismatch volume shapes: {}".format(mismatched_bbox)) logger.warning("-" * 90) logger.warning("Source missing shapes: {}".format( get_missing_shapes_from_group(source, target))) logger.warning("Target missing shapes: {}".format( get_missing_shapes_from_group(target, source))) logger.warning("-" * 90) return matching_shapes, mismatched_types, mismatched_count, mismatched_bbox
def update_plugin_attributes(source, target): """ Updates all maya plugin defined attributes :param source: maya shape node :type source: str :param target: maya shape node :type target: str """ source_attrs = cmds.listAttr(source, fromPlugin=True) or [] taget_attrs = cmds.listAttr(target, fromPlugin=True) or [] logger.debug("Updating plugin attributes on {}".format(target)) for attribute in source_attrs: if attribute in taget_attrs: update_attribute(source, target, attribute)
def create_duplicate(shape, duplicate_name): """ Creates a shape node duplicate :param shape: the shape node to duplicate :type shape: str :param name: the name for the duplicate :type name: str :return: the duplicated shape node :rtype: str """ logger.debug("Creating shape duplicate for {}".format(shape)) shape_holder = cmds.createNode(cmds.objectType(shape), name="{}Shape".format(duplicate_name)) cmds.rename(shape_holder, "{}".format(shape_holder)) update_shape(shape, shape_holder) return shape_holder
def set_deformer_state(deformers, enable): """ Set envelope attribute to one on the given deformers dictionary :param deformers: dict containing the deformers set by type :type deformers: type :param enable: on or off state for the given deformers :type enable: bool """ logger.debug("Setting deformers {} envelop enable to: {}" .format(deformers, enable)) # Loop of the deformer dict and set state for deformer_type in deformers: for i in deformers[deformer_type] or []: if enable: set_deformer_on(i) continue set_deformer_off(i)
def copy_map1_name(source, target): """ Copies the name of the uvSet at index zero (map1) to match it :param source: maya shape node :type source: str :param target: maya shape node :type target: str """ if not is_matching_type(source, target): return source_uv_name = cmds.getAttr("{}.uvSet[0].uvSetName".format(source)) try: cmds.setAttr("{}.uvSet[0].uvSetName".format(target), source_uv_name, type="string") except RuntimeError: logger.debug("{} doesn't not have uvs, skipping udpate map1 name" .format(target)) return
def __init__(self, parent=None): """ Creates all the user interface widgets :param parent: the parent widget for the Flex dialog widget :type parent: PySide2.QtWidgets """ super(FlexAnalyzeDialog, self).__init__(parent=parent) logger.debug("Analyze widget initialised") # sets window rules self.setObjectName(FLEX_ANALYZE_NAME) self.setWindowTitle("mGear: Flex analyze shapes") self.setMinimumWidth(500) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) # creates the icons self.green_icon = QtGui.QIcon() image = QtGui.QPixmap("{}/green.png".format(get_resources_path())) self.green_icon.addPixmap(image) self.red_icon = QtGui.QIcon() image = QtGui.QPixmap("{}/red.png".format(get_resources_path())) self.red_icon.addPixmap(image) self.yellow_icon = QtGui.QIcon() image = QtGui.QPixmap("{}/yellow.png".format(get_resources_path())) self.yellow_icon.addPixmap(image) # creates layout layout = QtWidgets.QVBoxLayout() layout.setMargin(0) # setup table self.setLayout(layout) self.__create_table() # add widgets layout.addWidget(self.table_widget)
def __init__(self, parent=None): """ Creates all the user interface widgets :param parent: the parent widget for the Flex dialog widget :type parent: PySide2.QtWidgets """ super(FlexDialog, self).__init__(parent=parent) logger.debug("Flex widget initialised") # sets window rules self.setObjectName(FLEX_UI_NAME) self.setWindowTitle("mGear: Flex (rig updater)") self.setStyleSheet(self.__style_sheet()) self.setMinimumWidth(350) self.setMinimumHeight(100) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) # creates widgets self.layout_widgets() self.models_groups_widgets() self.options_widgets() self.run_widgets()
def update_transform(source, target): """ Updates the transform node on target This method creates a duplicate of the transform node on source and uses is as the new parent transform for the target shape :param source: maya shape node :type source: str :param target: maya shape node :type target: str """ logger.debug("Updating transform node on {} from {}".format( target, source)) # create duplicate of the source transform holder = cmds.duplicate(source, parentOnly=True, name="mgear_flex_holder")[0] # adds the target shape duplicate into the holder transform node cmds.parent(target, holder, add=True, shape=True) # unlock locked attributes on holder transform node for attr in cmds.listAttr(holder, locked=True) or []: cmds.setAttr("{}.{}".format(holder, attr), lock=False) # updates the shape update_shape(source, target) # parents new shape under the correct place target_parent = get_parent(target)[0] target_parent_parent = get_parent(target_parent)[0] cmds.parent(holder, target_parent_parent) cmds.delete(target_parent) cmds.rename(holder, "{}".format(target_parent.split("|")[-1].split(":")[-1]))
def clean_uvs_sets(shape): """ Deletes all uv sets besides map1 This is used to be able to update target shapes with whatever the source shape has. This is only relevant for mesh shape types. :param shape: The Maya shape node :type shape: string """ # check if shape is not a mesh type node if cmds.objectType(shape) != "mesh": return logger.debug("Cleaning uv sets on {}".format(shape)) # gets uvs indices uvs_idx = cmds.getAttr("{}.uvSet".format(shape), multiIndices=True) # deletes the extra indices for idx in uvs_idx: if idx: cmds.setAttr("{}.uvSet[{}]".format(shape, idx), lock=False) cmds.removeMultiInstance("{}.uvSet[{}]".format(shape, idx))
def create_blendshapes_backup(source, target, nodes): """ Creates an updated backup for the given blendshapes nodes on source .. important:: This method does not work as the other source/target type of methods in flex. The source is the current geometry before topology update containing the blendshape nodes. We use it in order to create a wrap to the newer target geometry topology. :param source: current shape node :type source: str :param target: new shape node :type target: str :return: backup blendshape nodes :rtype: list """ logger.debug("Creating blendshapes backup") # gets simpler shape name shape_name = get_prefix_less_name(target) # get attributes types attrs = get_shape_type_attributes(target) # creates source duplicate intermediate = get_shape_orig(source)[0] source_duplicate = create_duplicate(intermediate, "{}_flex_bs_sourceShape" .format(shape_name)) # first loops to create a clean copy of the blendshape nodes nodes_copy = [] for node in nodes: duplicate = copy_blendshape_node(node, source_duplicate) if duplicate: nodes_copy.append(duplicate) # creates wrapped target shape warp_target = create_duplicate(target, "{}_flex_bs_warpShape" .format(shape_name)) # wraps the duplicate to the source create_wrap(source_duplicate, warp_target) # creates blendshape target shape target_duplicate = create_duplicate(target, "{}_flex_bs_targetShape" .format(shape_name)) return_nodes = [] # loops on the blendshape nodes for node in nodes_copy: # creates transfer blendshape transfer_node = cmds.deformer(target_duplicate, type="blendShape", name="flex_transfer_{}".format(node))[0] # get blendshape targets indices. We skip verification because at this # stage the copied blendshapes nodes will always have targets targets_idx = cmds.getAttr("{}.weight".format(node), multiIndices=True) # loop on blendshape targets indices for idx in targets_idx or []: # input target group attribute attr_name = (BLENDSHAPE_TARGET.format(node, idx)) # blendshape target name target_name = cmds.aliasAttr("{}.weight[{}]".format(node, idx), query=True) # loop on actual targets and in-between targets for target in cmds.getAttr(attr_name, multiIndices=True): # gets and sets the blendshape weight value weight = float((target - 5000) / 1000.0) cmds.setAttr("{}.weight[{}]".format(node, idx), weight) # geometry target attribute geometry_target_attr = "{}[{}].inputGeomTarget".format( attr_name, target) shape_target = geometry_target_attr.replace( geometry_target_attr.split(".")[0], transfer_node) # updates the target cmds.connectAttr("{}.{}".format(warp_target, attrs["output"]), shape_target, force=True) cmds.disconnectAttr("{}.{}" .format(warp_target, attrs["output"]), shape_target) cmds.setAttr("{}.weight[{}]".format(node, idx), 0) cmds.setAttr("{}.weight[{}]".format(transfer_node, idx), 0) if target_name: cmds.aliasAttr(target_name, "{}.weight[{}]".format( transfer_node, idx)) # adds blendshape node to nodes to return return_nodes.append("{}".format(transfer_node)) # deletes backup process shapes cmds.delete(cmds.listRelatives(source_duplicate, parent=True), cmds.listRelatives(warp_target, parent=True)) # forces refresh cmds.refresh() return return_nodes
def copy_blendshape_node(node, target): """ Copies the given blendshape node into the given target shape :param node: blendshape node :type node: str :param target: target shape node :type target: str :return: copied blenshape node :rtype: str """ logger.debug("Copying blendshape node {} to {}".format(node, target)) # get blendshape targets indices targets_idx = cmds.getAttr("{}.weight".format(node), multiIndices=True) # skip node if no targets where found if not targets_idx: return # list for ignored targets (when they are live connected) ignore = [] # creates blendshape deformer node on target node_copy = cmds.deformer(target, type="blendShape", name="flex_copy_{}" .format(node))[0] # loop on blendshape targets indices for idx in targets_idx: # input target group attribute attr_name = (BLENDSHAPE_TARGET.format(node, idx)) # blendshape target name target_name = cmds.aliasAttr("{}.weight[{}]".format(node, idx), query=True) # checks for empty target if not cmds.getAttr(attr_name, multiIndices=True): continue # loop on actual targets and in-between targets for target in cmds.getAttr(attr_name, multiIndices=True): # target attribute name target_attr = "{}[{}]".format(attr_name, target) # checks for incoming connections on the geometry target if cmds.listConnections("{}.inputGeomTarget".format(target_attr), destination=False): logger.warning("{} can't be updated because it is a live " "target".format(target_name)) ignore.append(idx) continue # updates node copy target destination = target_attr.replace(target_attr.split(".")[0], node_copy) cmds.connectAttr(target_attr, destination, force=True) cmds.disconnectAttr(target_attr, destination) # skips updating target name if this was a life target if idx in ignore: continue # forces the weight attribute to be shown on the blendshape node cmds.setAttr("{}.weight[{}]".format(node_copy, idx), 0) # updates blendshape node attribute name if target_name: cmds.aliasAttr(target_name, "{}.weight[{}]" .format(node_copy, idx)) # gets targets on copied node to see if there is any node with zero target idx = cmds.getAttr("{}.weight".format(node_copy), multiIndices=True) if not idx: cmds.delete(node_copy) return return node_copy
def update_rig(source, target, options): """ Updates all shapes from the given source group to the target group :param source: maya transform node :type source: str :param target: maya transform node :type target: str :param options: update options :type options: dict """ # gets the matching shapes matching_shapes = get_matching_shapes_from_group(source, target) logger.info("-" * 90) logger.info("Matching shapes: {}".format(matching_shapes)) logger.info("-" * 90) for shape in matching_shapes: logger.debug("-" * 90) logger.debug("Updating: {}".format(matching_shapes[shape])) if options["deformed"]: update_deformed_shape(shape, matching_shapes[shape], options["mismatched_topologies"]) if options["transformed"]: update_transformed_shape(shape, matching_shapes[shape], options["hold_transform_values"]) if options["user_attributes"]: update_user_attributes(shape, matching_shapes[shape]) if options["object_display"]: logger.debug("Updating object display attributes on {}".format( matching_shapes[shape])) update_maya_attributes(shape, matching_shapes[shape], OBJECT_DISPLAY_ATTRIBUTES) if options["component_display"]: logger.debug("Updating component display attributes on {}".format( matching_shapes[shape])) update_maya_attributes(shape, matching_shapes[shape], COMPONENT_DISPLAY_ATTRIBUTES) if options["render_attributes"]: logger.debug("Updating render attributes on {}".format( matching_shapes[shape])) update_maya_attributes(shape, matching_shapes[shape], RENDER_STATS_ATTRIBUTES) if options["plugin_attributes"]: update_plugin_attributes(shape, matching_shapes[shape]) logger.info("-" * 90) logger.info("Source missing shapes: {}".format( get_missing_shapes_from_group(source, target))) logger.info("Target missing shapes: {}".format( get_missing_shapes_from_group(target, source))) logger.info("-" * 90)