Last active
March 7, 2026 22:57
-
-
Save StevenGoehrig/2f45cb8d0464dbb833359e27fb9729a7 to your computer and use it in GitHub Desktop.
Eye Socket Component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from maya import cmds | |
| import pymel.core as pm | |
| from PySide6 import QtCore, QtGui, QtWidgets | |
| from maya.app.general.mayaMixin import MayaQWidgetDockableMixin | |
| from mgear.core import curve, primitive, icon, transform, vector | |
| from functools import partial | |
| from custom import curve_tool, joint_tool, orientation_tool | |
| def get_vertex_loop_from_selection(): #TODO move to it's own script | |
| selection = cmds.ls(sl=True, fl=True) | |
| return str(selection) | |
| if selection: | |
| vertex_list = "" | |
| separator = "" | |
| for vertex in selection: | |
| if vertex_list: | |
| separator = "," | |
| vertex_list = vertex_list + separator + str(vertex) | |
| if not vertex_list: | |
| pm.displayWarning("Please select first the vertex loop.") | |
| elif len(vertex_list.split(",")) < 4: | |
| pm.displayWarning("The minimun vertex count is 4") | |
| return vertex_list | |
| else: | |
| pm.displayWarning("Please select first the vertex loop.") | |
| class EyeSocketComponent(): | |
| def __init__(self, vertex_loop, inner_vertex, name_prefix, side, ctl_suffix, eye_joint, parent_node, head_joint): | |
| self.vertex_loop = vertex_loop | |
| self.inner_vertex = inner_vertex | |
| self.name_prefix = name_prefix | |
| self.side = side | |
| self.ctl_suffix = ctl_suffix | |
| self.eye_joint = eye_joint | |
| self.parent_node = parent_node | |
| self.head_joint = head_joint | |
| self.ctl_locs, self.control_num, self.bindCurve, self.ctlCurve, self.cvNum, self.socket_root, self.socketCrv_root, self.socketRope_root = self.create_guides() | |
| def setName(self, name, idx=None): | |
| namesList = [self.name_prefix, self.side, name] | |
| if idx is not None: | |
| namesList[1] = self.side + str(idx) | |
| name = "_".join(namesList) | |
| return name | |
| def create_guides(self): | |
| vertex_list = [] | |
| for vertex in self.vertex_loop.split(','): | |
| vertex = vertex.replace("'", "") | |
| vertex_list.append(vertex.strip()) | |
| vertex_list[0] = vertex_list[0].replace('[', '', 1) | |
| vertex_list[-1] = vertex_list[-1].replace(']', '', 1) | |
| #Create Root Nodes | |
| socket_root = primitive.addTransform(None, self.setName('socket_root')) | |
| socketCrv_root = primitive.addTransform(socket_root, self.setName('socketCrv_root')) | |
| socketRope_root = primitive.addTransform(socket_root, self.setName('socketRope_root')) | |
| #Create Bind Curve | |
| bindCurve = curve_tool.createCurve(vertex_list, self.setName('bindCurve'), start=self.inner_vertex, parent=socketCrv_root, per=True) | |
| control_num = 12 | |
| cvNum = (control_num * 3) + 3 #add 3 because it's a periodic curve and 3 of the CVs are hidden | |
| ctlCurve = curve_tool.createCurveFromCurve(bindCurve, | |
| self.setName('ctlCurve'), | |
| nbPoints=cvNum, | |
| parent=socketCrv_root) | |
| ctlCVs = ctlCurve.getCVs(space="world") | |
| ctlCVs = ctlCVs[:-3] # Remove the last 3 elements since it's a periodic curve | |
| ctl_locs = [] | |
| for i, cv in enumerate(ctlCVs): | |
| if i % 3 == 0: | |
| ctlNum = int(i / 3) | |
| loc = cmds.spaceLocator(n=f'ctl_position_{ctlNum}') | |
| ctl_locs.append(loc) | |
| cmds.xform(loc, t=cv) | |
| # Hide curves and prevent double transformations | |
| cmds.setAttr(f'{socketCrv_root}.visibility', 0) | |
| cmds.setAttr(f'{socketRope_root}.visibility', 0) | |
| cmds.setAttr(f'{socketCrv_root}.inheritsTransform', 0) | |
| cmds.setAttr(f'{socketRope_root}.inheritsTransform', 0) | |
| return ctl_locs, control_num, bindCurve, ctlCurve, cvNum, socket_root, socketCrv_root, socketRope_root | |
| def build_rig(self): | |
| #Create Up Vector Curves | |
| bindCVs = self.bindCurve.getCVs(space="world") | |
| fn_bindCurve = curve_tool.getFnCurve(self.bindCurve) | |
| bind_numCVs = fn_bindCurve.numCVs - 3 #subtract 3 because it's a periodic curve and 3 of the CVs are hiddenw | |
| bindUpvCurve = curve_tool.createCurveFromCurve(self.bindCurve, | |
| self.setName('bindUpvCurve'), | |
| nbPoints=bind_numCVs + 3, | |
| parent=self.socketCrv_root) | |
| ctlUpvCurve = curve_tool.createCurveFromCurve(self.bindCurve, | |
| self.setName('ctlUpvCurve'), | |
| nbPoints=self.cvNum, | |
| parent=self.socketCrv_root) | |
| ctl_pos = [] | |
| for loc in self.ctl_locs: | |
| pos = cmds.xform(loc, q=True, worldSpace=True, t=True) | |
| print(pos) | |
| ctl_pos.append(pos) | |
| bsCurve = curve_tool.createBSCurve(ctl_pos, | |
| self.setName('bsCurve'), | |
| parent=self.socketCrv_root) | |
| #Get the direction of the parent joint | |
| parentOrientation = orientation_tool.getOrientation(self.eye_joint, 'vector') | |
| parentZUp = parentOrientation[2] | |
| #Offset upv curve in the direction of the parent joint | |
| for upvCurve in [bindUpvCurve, ctlUpvCurve]: | |
| upvCvs = upvCurve.getCVs(space="world") | |
| fn_upvCurve = curve_tool.getFnCurve(upvCurve) | |
| upv_numCVs = fn_upvCurve.numCVs | |
| for i, cv in enumerate(upvCvs): | |
| offset = [cv[0] + parentZUp[0], cv[1] + parentZUp[1], cv[2] + parentZUp[2]] | |
| #subtract 3 because it's a periodic curve and 3 of the CVs are hidden | |
| if i < upv_numCVs-3: | |
| upvCurve.setCV(i, offset, space='world') | |
| #Create Joints | |
| joints = [] | |
| for i, cv in enumerate(bindCVs): | |
| if i < bind_numCVs: | |
| oTransUpV = pm.PyNode( | |
| pm.createNode( | |
| "transform", | |
| n=self.setName("socketRopeUpv", idx=i), | |
| p=self.socketRope_root, | |
| ss=True)) | |
| oTrans = pm.PyNode( | |
| pm.createNode( | |
| "transform", | |
| n=self.setName("socketRope", idx=i), | |
| p=self.socketRope_root, | |
| ss=True)) | |
| oParam, oLength = curve.getCurveParamAtPosition(self.bindCurve, bindCVs[i]) | |
| uLength = curve.findLenghtFromParam(self.bindCurve, oParam) | |
| u = oParam | |
| cnsUpv = curve_tool.pathCns( | |
| oTransUpV, bindUpvCurve, cnsType=True, u=u, tangent=False) | |
| cns = curve_tool.pathCns( | |
| oTrans, self.bindCurve, cnsType=True, u=u, tangent=False) | |
| cns.setAttr("worldUpType", 1) | |
| cns.setAttr("frontAxis", 0) | |
| cns.setAttr("upAxis", 1) | |
| pm.connectAttr(oTransUpV.attr("worldMatrix[0]"), | |
| cns.attr("worldUpMatrix")) | |
| jnt = joint_tool.addJntVanilla(oTrans, noReplace=True) | |
| joints.append(jnt) | |
| # Controls lists | |
| controls = [] | |
| ctlVec = [] | |
| bsNpo = [] | |
| ctlNpo = [] | |
| parentCtls = [] | |
| # controls options | |
| axis_list = ["sx", "sy", "sz", "ro"] | |
| ctlOptions = [["innerMid", "sphere", 14, .03], | |
| ["innerUp", "sphere", 14, .03], | |
| ["upperIn", "sphere", 14, .03], | |
| ["upperMid", "sphere", 14, .03], | |
| ["upperOut", "sphere", 14, .03], | |
| ["outerUp", "sphere", 14, .03], | |
| ["outerMid", "sphere", 14, .03], | |
| ["outerLow", "sphere", 14, .03], | |
| ["lowerOut", "sphere", 14, .03], | |
| ["lowerMid", "sphere", 14, .03], | |
| ["lowerIn", "sphere", 14, .03], | |
| ["innerLow", "sphere", 14, .03]] | |
| if self.side == "R": | |
| r_ctlOptions = ctlOptions | |
| r_ctlOptions.reverse() | |
| firstCtl = r_ctlOptions.pop(-1) | |
| r_ctlOptions.insert(0, firstCtl) | |
| ctlOptions = r_ctlOptions | |
| params = ["tx", "ty", "tz", "rx", "ry", "rz"] | |
| # Create Controls | |
| zOffset = 0 #TODO: fix this | |
| if self.side == "R": | |
| zOffset = zOffset * -1 | |
| iconSize = 40 | |
| for i, pos in enumerate(ctl_pos): | |
| t = transform.getTransformFromPos(pos) | |
| t = transform.setMatrixPosition( | |
| transform.getTransform(pm.PyNode(self.eye_joint)), pos | |
| ) | |
| temp = primitive.addTransform( | |
| self.socket_root, self.setName("temp"), t | |
| ) | |
| temprz = temp.rz.get() | |
| temp.rz.set(temprz+zOffset) | |
| t = transform.getTransform(temp) | |
| pm.delete(temp) | |
| oName = ctlOptions[i][0] | |
| o_icon = ctlOptions[i][1] | |
| color = ctlOptions[i][2] | |
| wd = ctlOptions[i][3] | |
| npo = primitive.addTransform(self.socket_root, | |
| self.setName("%s_npo" % oName, self.side), | |
| t) | |
| ctlNpo.append(npo) | |
| bsCurve_npo = primitive.addTransform(npo, | |
| self.setName("bsCurve_%s_npo" % oName, self.side), | |
| t) | |
| bsNpo.append(bsCurve_npo) | |
| ctl = icon.create(bsCurve_npo, | |
| self.setName("%s_%s" % (oName, self.ctl_suffix), self.side), | |
| t, | |
| icon=o_icon, | |
| w=wd * iconSize, | |
| d=wd * iconSize, | |
| color=color) | |
| controls.append(ctl) | |
| upv = primitive.addTransform(ctl, self.setName("%s_upv" % oName, self.side), t) | |
| upv.attr("tx").set(parentZUp[0]) | |
| upv.attr("ty").set(parentZUp[1]) | |
| upv.attr("tz").set(parentZUp[2]) | |
| ctlVec.append(upv) | |
| # # Parent Controls | |
| if i == (self.control_num/4) or i == (self.control_num/4)*3: | |
| if i == (self.control_num/4): | |
| parentName = 'upper' | |
| else: | |
| parentName = 'lower' | |
| ctl_npo = primitive.addTransform(self.socket_root, | |
| self.setName("%s_npo" % parentName, self.side), | |
| t) | |
| ctl = icon.create(ctl_npo, | |
| self.setName("%s_%s" % (parentName, self.ctl_suffix), self.side), | |
| t, | |
| icon=o_icon, | |
| w=wd * iconSize*3, | |
| d=wd * iconSize*3, | |
| color=color) | |
| parentCtls.append(ctl) | |
| # Connecting control bsNPOs with bsCurve | |
| bsCVs = bsCurve.getCVs(space="world") | |
| fn_bsCurve = curve_tool.getFnCurve(bsCurve) | |
| bs_numCVs = fn_bsCurve.numCVs | |
| for i, cv in enumerate(bindCVs): | |
| if i < bs_numCVs - 1: | |
| oParam, oLength = curve.getCurveParamAtPosition(bsCurve, bsCVs[i]) | |
| uLength = curve.findLenghtFromParam(bsCurve, oParam) | |
| u = oParam | |
| cns = curve_tool.pathCns( | |
| pm.PyNode(bsNpo[i]), bsCurve, cnsType=True, u=u, tangent=False, rot=False) | |
| cns.setAttr("worldUpType", 1) | |
| cns.setAttr("frontAxis", 0) | |
| cns.setAttr("upAxis", 1) | |
| transFromMat = cmds.shadingNode('translationFromMatrix', au=True) | |
| plusMAv = cmds.shadingNode('plusMinusAverage', au=True) | |
| cmds.connectAttr(f'{bsNpo[i]}.parentInverseMatrix', f'{transFromMat}.input') | |
| cmds.connectAttr(f'{transFromMat}.output', f'{plusMAv}.input3D[0]') | |
| cmds.connectAttr(f'{cns}.allCoordinates', f'{plusMAv}.input3D[1]') | |
| cmds.connectAttr(f'{plusMAv}.output3D', f'{bsNpo[i]}.translate', f=True) | |
| # Connecting control crvs with controls | |
| ctlCurveCvs = self.ctlCurve.getCVs(space="world") | |
| for i, item in enumerate(controls): #CHANGES BEGIN | |
| node = pm.createNode("decomposeMatrix") | |
| pm.connectAttr(item + ".worldMatrix[0]", node + ".inputMatrix") | |
| pm.connectAttr( | |
| node + ".outputTranslate", self.ctlCurve + ".controlPoints[%s]" % (i * 3) | |
| ) | |
| pm.connectAttr( | |
| node + ".outputTranslate", self.ctlCurve + ".controlPoints[%s]" % (i * 3 + 1) | |
| ) | |
| pm.connectAttr( | |
| node + ".outputTranslate", self.ctlCurve + ".controlPoints[%s]" % (i * 3 + 2) | |
| ) | |
| # #Up socket up vectors | |
| for i, item in enumerate(ctlVec): | |
| node = pm.createNode("decomposeMatrix") | |
| pm.connectAttr(item + ".worldMatrix[0]", node + ".inputMatrix") | |
| pm.connectAttr( | |
| node + ".outputTranslate", ctlUpvCurve + ".controlPoints[%s]" % (i * 3) | |
| ) | |
| pm.connectAttr( | |
| node + ".outputTranslate", ctlUpvCurve + ".controlPoints[%s]" % (i * 3 + 1) | |
| ) | |
| pm.connectAttr( | |
| node + ".outputTranslate", ctlUpvCurve + ".controlPoints[%s]" % (i * 3 + 2) | |
| ) | |
| # Connect joint curve to controls | |
| cmds.wire(self.bindCurve, w=self.ctlCurve, dds=[0,10]) | |
| cmds.wire(bindUpvCurve, w=ctlUpvCurve, dds=[0,10]) | |
| cmds.parentConstraint(self.head_joint, bsCurve, mo=True) | |
| for npo in ctlNpo: | |
| cmds.setAttr(f'{npo}.inheritsTransform', 0) | |
| for npo in bsNpo: | |
| cmds.orientConstraint(self.head_joint, npo, mo=True) | |
| for joint in joints: | |
| cmds.parent(joint, self.head_joint) | |
| cmds.parent(self.socket_root, self.parent_node) | |
| # for loc in self.ctl_locs: | |
| # cmds.delete(loc) | |
| class EyeSocketRiggerUI(MayaQWidgetDockableMixin, QtWidgets.QDialog): #TODO move to it's own script | |
| def __init__(self, parent=None): | |
| super(EyeSocketRiggerUI, self).__init__(parent) | |
| self.setWindowTitle("Eye Autorigger") | |
| self.build_ui() | |
| self.component = None | |
| def build_ui(self): | |
| self.create_controls() | |
| self.create_layout() | |
| self.create_connections() | |
| def create_controls(self): | |
| # Geometry input control | |
| self.geometryInput_group = QtWidgets.QGroupBox("Geometry Input") | |
| self.vertex_loop_label = QtWidgets.QLabel("Vertex Loop:") | |
| self.vertex_loop = QtWidgets.QLineEdit() | |
| self.vertex_loop_button = QtWidgets.QPushButton("<<") | |
| self.inner_vertex_label = QtWidgets.QLabel("Inner Corner Vertex:") | |
| self.inner_vertex = QtWidgets.QLineEdit() | |
| self.inner_vertex_button = QtWidgets.QPushButton("<<") | |
| # Name prefix input control | |
| self.prefix_group = QtWidgets.QGroupBox("Name Prefix") | |
| self.name_prefix = QtWidgets.QLineEdit() | |
| self.name_prefix.setText("socket") | |
| # Side infix input control | |
| self.side_group = QtWidgets.QGroupBox("Side") | |
| self.side = QtWidgets.QLineEdit() | |
| self.side.setText("L") | |
| # Control suffix input control | |
| self.suffix_group = QtWidgets.QGroupBox("Control Name Suffix") | |
| self.ctl_suffix = QtWidgets.QLineEdit() | |
| self.ctl_suffix.setText("anim") | |
| # Joint input control | |
| self.joints_group = QtWidgets.QGroupBox("Joint Parent") | |
| self.joint_parent = QtWidgets.QLineEdit() | |
| self.joint_parent.setText("eye_L_eye_jnt") | |
| self.joint_parent_button = QtWidgets.QPushButton("<<") | |
| # Options input controls | |
| self.options_group = QtWidgets.QGroupBox("Rig Parent") | |
| self.parent_node = QtWidgets.QLineEdit() | |
| self.parent_node.setText("head_M_00_jnt_customGrp") | |
| self.parent_button = QtWidgets.QPushButton("<<") | |
| self.head_joint = QtWidgets.QLineEdit() | |
| self.head_joint.setText("head_M_00_jnt") | |
| self.head_button = QtWidgets.QPushButton("<<") | |
| # Build buttons | |
| self.guides_button = QtWidgets.QPushButton("Build Control Guides") | |
| self.rig_button = QtWidgets.QPushButton("Create Controls") | |
| def create_layout(self): | |
| # Vertex Loop Layout | |
| vertex_loop_layout = QtWidgets.QHBoxLayout() | |
| vertex_loop_layout.addWidget(self.vertex_loop_label) | |
| vertex_loop_layout.addWidget(self.vertex_loop) | |
| vertex_loop_layout.addWidget(self.vertex_loop_button) | |
| # Inner Vertex Layout | |
| inner_vertex_layout = QtWidgets.QHBoxLayout() | |
| inner_vertex_layout.addWidget(self.inner_vertex_label) | |
| inner_vertex_layout.addWidget(self.inner_vertex) | |
| inner_vertex_layout.addWidget(self.inner_vertex_button) | |
| # Geometry Input Layout | |
| geometryInput_layout = QtWidgets.QVBoxLayout() | |
| geometryInput_layout.addLayout(vertex_loop_layout) | |
| geometryInput_layout.addLayout(inner_vertex_layout) | |
| self.geometryInput_group.setLayout(geometryInput_layout) | |
| #Joints Layout | |
| joint_parent_layout = QtWidgets.QHBoxLayout() | |
| joint_parent_layout.addWidget(self.joint_parent) | |
| joint_parent_layout.addWidget(self.joint_parent_button) | |
| joints_layout = QtWidgets.QVBoxLayout() | |
| joints_layout.addLayout(joint_parent_layout) | |
| self.joints_group.setLayout(joints_layout) | |
| # Options Layout | |
| parent_layout = QtWidgets.QHBoxLayout() | |
| parent_layout.addWidget(self.parent_node) | |
| parent_layout.addWidget(self.parent_button) | |
| head_layout = QtWidgets.QHBoxLayout() | |
| head_layout.addWidget(self.head_joint) | |
| head_layout.addWidget(self.head_button) | |
| options_layout = QtWidgets.QVBoxLayout() | |
| options_layout.addLayout(parent_layout) | |
| options_layout.addLayout(head_layout) | |
| self.options_group.setLayout(options_layout) | |
| # Name prefix | |
| name_prefix_layout = QtWidgets.QHBoxLayout() | |
| name_prefix_layout.addWidget(self.name_prefix) | |
| self.prefix_group.setLayout(name_prefix_layout) | |
| # Side | |
| side_layout = QtWidgets.QHBoxLayout() | |
| side_layout.addWidget(self.side) | |
| self.side_group.setLayout(side_layout) | |
| # Control Name Extension | |
| controlExtension_layout = QtWidgets.QHBoxLayout() | |
| controlExtension_layout.addWidget(self.ctl_suffix) | |
| self.suffix_group.setLayout(controlExtension_layout) | |
| # Main Layout | |
| main_layout = QtWidgets.QVBoxLayout() | |
| main_layout.addWidget(self.prefix_group) | |
| main_layout.addWidget(self.side_group) | |
| main_layout.addWidget(self.suffix_group) | |
| main_layout.addWidget(self.geometryInput_group) | |
| main_layout.addWidget(self.options_group) | |
| main_layout.addWidget(self.joints_group) | |
| main_layout.addWidget(self.guides_button) | |
| main_layout.addWidget(self.rig_button) | |
| self.setLayout(main_layout) | |
| def create_connections(self): | |
| self.vertex_loop_button.clicked.connect( | |
| partial(self.populate_edge_loop, self.vertex_loop) | |
| ) | |
| self.inner_vertex_button.clicked.connect( | |
| partial(self.populate_element, self.inner_vertex, "vertex") | |
| ) | |
| self.parent_button.clicked.connect( | |
| partial(self.populate_element, self.parent_node) | |
| ) | |
| self.head_button.clicked.connect( | |
| partial(self.populate_element, self.head_joint) | |
| ) | |
| self.joint_parent_button.clicked.connect( | |
| partial(self.populate_element, self.joint_parent, "joint") | |
| ) | |
| self.guides_button.clicked.connect(self.save_component) | |
| self.rig_button.clicked.connect(self.rig_component) | |
| def save_component(self): | |
| self.component = EyeSocketComponent( | |
| self.vertex_loop.text(), | |
| self.inner_vertex.text(), | |
| self.name_prefix.text(), | |
| self.side.text(), | |
| self.ctl_suffix.text(), | |
| self.joint_parent.text(), | |
| self.parent_node.text(), | |
| self.head_joint.text() | |
| ) | |
| def rig_component(self): | |
| self.component.build_rig() | |
| def populate_edge_loop(self, lineEdit): | |
| lineEdit.setText(get_vertex_loop_from_selection()) | |
| def populate_element(self, lEdit, oType="transform"): | |
| if oType == "joint": | |
| oTypeInst = pm.nodetypes.Joint | |
| elif oType == "vertex": | |
| oTypeInst = pm.MeshVertex | |
| else: | |
| oTypeInst = pm.nodetypes.Transform | |
| oSel = pm.selected() | |
| if oSel: | |
| if isinstance(oSel[0], oTypeInst): | |
| lEdit.setText(oSel[0].name()) | |
| else: | |
| pm.displayWarning( | |
| "The selected element is not a valid %s" % oType) | |
| else: | |
| pm.displayWarning("Please select first one %s." % oType) | |
| # ============================================================== | |
| def main(): | |
| autorigger = EyeSocketRiggerUI() | |
| autorigger.show(dockable=True) | |
| print("Opening Eye Autorigger") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import maya.cmds as cmds | |
| import maya.api.OpenMaya as om2 | |
| import pymel.core as pm | |
| from mgear.core import curve | |
| import json | |
| import sys | |
| def getDagPath(obj): | |
| if not isinstance(obj, str): | |
| objName = cmds.ls(obj)[0] | |
| sel = om2.MSelectionList() | |
| sel.add(objName) | |
| dag_path = sel.getDagPath(0) | |
| return dag_path | |
| def getFnCurve(obj): | |
| dag_path = getDagPath(obj) | |
| fn_curve = om2.MFnNurbsCurve(dag_path) | |
| return fn_curve | |
| def mayaSelRange(vals): | |
| """Convert maya cmds.ls() component selection list into indices | |
| Arguments: | |
| vals (list): A list of components like what you get out of cmds.ls(sl=True) | |
| Returns: | |
| list: A list of integer indices | |
| """ | |
| out = [] | |
| for val in vals: | |
| nn = val.split('[')[1][:-1].split(':') | |
| nn = list(map(int, nn)) | |
| out.extend(range(nn[0], nn[-1] + 1)) | |
| return out | |
| def buildNeighborDict(vertColl): | |
| """ Parse vertices into a dictionary of neighbors, limited to the original vertex set | |
| Arguments: | |
| vertColl (list): A list of verts like what you get out of cmds.ls(sl=True) | |
| Returns: | |
| dict: A dictionary formatted like {vertIndex: [neighborVertIdx, ...], ...} | |
| """ | |
| # Get the object name | |
| obj = vertColl[0] | |
| objName = obj.split('.')[0] | |
| verts = set(mayaSelRange(vertColl)) | |
| neighborDict = {} | |
| for v in verts: | |
| vname = f'{objName}.vtx[{v}]' | |
| edges = cmds.polyListComponentConversion(vname, fromVertex=True, toEdge=True) | |
| neighbors = cmds.polyListComponentConversion( | |
| edges, fromEdge=True, toVertex=True | |
| ) | |
| neighbors = set(mayaSelRange(neighbors)) | |
| neighbors.remove(v) | |
| neighborDict[v] = list(neighbors & verts) | |
| return neighborDict, objName | |
| def sortLoops(neighborDict): | |
| """Sort vertex loop neighbors into individual loops | |
| Arguments: | |
| neighborDict (dict): A dictionary formatted like {vertIndex: [neighborVertIdx, ...], ...} | |
| Returns: | |
| list of lists: A list of lists containing ordered vertex loops. | |
| Only if the loop is closed, the first and last element will be the same. | |
| """ | |
| neighborDict = dict(neighborDict) # work on a copy of the dict so I don't destroy the original | |
| loops = [] | |
| # If it makes it more than 1000 times through this code, something is probably wrong | |
| # This way I don't get stuck in an infinite loop like I could with while(neighborDict) | |
| for _ in range(1000): | |
| if not neighborDict: | |
| break | |
| vertLoop = [list(neighborDict.keys())[0]] | |
| vertLoop.append(neighborDict[vertLoop[-1]][0]) | |
| # Loop over this twice: Once forward, and once backward | |
| # This handles loops that don't connect back to themselves | |
| for i in range(2): | |
| vertLoop = vertLoop[::-1] | |
| while vertLoop[0] != vertLoop[-1]: | |
| nextNeighbors = neighborDict[vertLoop[-1]] | |
| if len(nextNeighbors) == 1: | |
| break | |
| elif nextNeighbors[0] == vertLoop[-2]: | |
| vertLoop.append(nextNeighbors[1]) | |
| else: | |
| vertLoop.append(nextNeighbors[0]) | |
| # Remove vertices I've already seen from the dict | |
| # Don't remove the same vert twice if the first and last items are the same | |
| start = 0 | |
| if vertLoop[0] == vertLoop[-1]: | |
| start = 1 | |
| for v in vertLoop[start:]: | |
| del neighborDict[v] | |
| loops.append(vertLoop) | |
| else: | |
| raise RuntimeError("You made it through 1000 loops, and you still aren't done? Something must be wrong") | |
| return loops | |
| def getMinZVert(loop): | |
| minZ = None | |
| minZVert = None | |
| for vert in loop: | |
| pos = vert.getPosition(space="world") | |
| if minZ is None or pos[2] < minZ: | |
| minZ = pos[2] | |
| minZVert = vert | |
| return minZVert | |
| def zSortLoop(loop): | |
| loop = loop[:-1] | |
| minZVert = getMinZVert(loop) | |
| index = loop.index(minZVert) | |
| zSortedLoop = loop[index:] + loop[:index] + [minZVert] | |
| return zSortedLoop | |
| def vertSortLoop(loop, per, first_vert): | |
| if per: | |
| loop = loop[:-1] | |
| print(loop) | |
| index = loop.index(pm.PyNode(first_vert)) | |
| if per: | |
| vSortedLoop = loop[index:] + loop[:index] + [pm.PyNode(first_vert)] | |
| else: | |
| vSortedLoop = loop[index:] + loop[:index] | |
| return vSortedLoop | |
| def fillVertexNames(objName, sorted_loop): | |
| sorted_named_loop = [] | |
| for vertex in sorted_loop: | |
| sorted_named_loop.append(f'{objName}.vtx[{vertex}]') | |
| return sorted_named_loop | |
| def getVertexPositions(vertex_loop, per, start='z'): | |
| print(vertex_loop) | |
| neighborDict, objName = buildNeighborDict(vertex_loop) | |
| sorted_loop = sortLoops(neighborDict)[0] | |
| sorted_named_loop = fillVertexNames(objName, sorted_loop) | |
| vertex_nodes = [pm.PyNode(vertex) for vertex in sorted_named_loop] | |
| if start == 'z': | |
| reSortedLoop = zSortLoop(vertex_nodes) | |
| else: | |
| print(vertex_nodes) | |
| reSortedLoop = vertSortLoop(vertex_nodes, per, start) | |
| print(reSortedLoop) | |
| vertex_positions = [vertex_node.getPosition(space="world") for vertex_node in reSortedLoop] | |
| return (vertex_positions) | |
| def createCurve(selection, name, start='z', parent=None, per=False): | |
| vertex_positions = getVertexPositions(selection, per, start) | |
| print(vertex_positions) | |
| if per: | |
| if vertex_positions[0] == vertex_positions[-1]: | |
| vertex_positions = vertex_positions[:-1] | |
| new_curve = curve.addCurve(parent, name, vertex_positions, degree=3, close=per) | |
| return new_curve | |
| def createCurveFromCurve(srcCrv, name, nbPoints, parent=None, per=True): | |
| """Create a curve from a curve | |
| Arguments: | |
| srcCrv (curve): The source curve. | |
| name (str): The new curve name. | |
| nbPoints (int): Number of control points for the new curve. | |
| parent (dagNode): Parent of the new curve. | |
| Returns: | |
| dagNode: The newly created curve. | |
| """ | |
| param = getParamPositionsOnCurve(srcCrv, nbPoints) | |
| crv = curve.addCurve(parent, name, param, close=per, degree=3) | |
| return crv | |
| def createBSCurve(cvPositions, name, parent=None, per=False): | |
| """Create a curve from a curve | |
| Arguments: | |
| srcCrv (curve): The source curve. | |
| name (str): The new curve name. | |
| nbPoints (int): Number of control points for the new curve. | |
| parent (dagNode): Parent of the new curve. | |
| Returns: | |
| dagNode: The newly created curve. | |
| """ | |
| # srcCurveCVs = srcCrv.getCVs(space="world") | |
| # cvPositions = [] | |
| # for i, cv in enumerate(srcCurveCVs): | |
| # if i % 3 == 0: | |
| # cvPositions.append(cv) | |
| # if per: | |
| # cvPositions = cvPositions[:-1] | |
| crv = curve.addCurve(parent, name, cvPositions, close=per, degree=1) | |
| return crv | |
| def getParamPositionsOnCurve(srcCrv, nbPoints): | |
| """get param position on curve | |
| Arguments: | |
| srcCrv (curve): The source curve. | |
| nbPoints (int): Number of points to return. | |
| Returns: | |
| tuple: world positions. | |
| """ | |
| fn_curve = getFnCurve(srcCrv) | |
| length = fn_curve.length() | |
| parL = fn_curve.findParamFromLength(length) | |
| param = [] | |
| increment = parL / (nbPoints - 1) | |
| p = 0.0 | |
| for x in range(nbPoints): | |
| # we need to check that the param value never exceed the parL | |
| if p > parL: | |
| p = parL | |
| if fn_curve.isParamOnCurve(p): | |
| pos = fn_curve.getPointAtParam(p, space=om2.MSpace.kWorld) | |
| param.append(pos) | |
| p += increment | |
| return param | |
| def pathCns(obj, curve, cnsType=False, u=0, tangent=False, rot=True): | |
| """ | |
| Apply a path constraint or curve constraint. | |
| Arguments: | |
| obj (dagNode): Constrained object. | |
| curve (Nurbscurve): Constraining Curve. | |
| cnsType (int): 0 for Path Constraint, 1 for Curve | |
| Constraint (Parametric). | |
| u (float): Position of the object on the curve (from 0 to 100 for path | |
| constraint, from 0 to 1 for Curve cns). | |
| tangent (bool): Keep tangent orientation option. | |
| Returns: | |
| pyNode: The newly created constraint. | |
| """ | |
| node = pm.PyNode(pm.createNode("motionPath")) | |
| node.setAttr("uValue", u) | |
| node.setAttr("fractionMode", not cnsType) | |
| node.setAttr("follow", tangent) | |
| curve_shape = pm.listRelatives(curve, shapes=True)[0] | |
| pm.connectAttr(curve_shape.attr("worldSpace"), node.attr("geometryPath")) | |
| pm.connectAttr(node.attr("allCoordinates"), obj.attr("translate")) | |
| if rot == True: | |
| pm.connectAttr(node.attr("rotate"), obj.attr("rotate")) | |
| pm.connectAttr(node.attr("rotateOrder"), obj.attr("rotateOrder")) | |
| pm.connectAttr(node.attr("message"), obj.attr("specifiedManipLocation")) | |
| return node |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import pymel.core as pm | |
| import maya.cmds as cmds | |
| from mgear.core import primitive, transform | |
| import maya.api.OpenMaya as om2 | |
| def addJntVanilla( | |
| obj=False, parent=False, noReplace=False, grp=None, jntName=None, *args | |
| ): | |
| """Create one joint for each selected object. | |
| Args: | |
| obj (bool or dagNode, optional): The object to drive the new | |
| joint. If False will use the current selection. | |
| parent (bool or dagNode, optional): The parent for the joint. | |
| If False will try to parent to jnt_org. If jnt_org doesn't | |
| exist will parent the joint under the obj | |
| noReplace (bool, optional): If True will add the extension | |
| "_jnt" to the new joint name | |
| grp (pyNode or None, optional): The set to add the new joint. | |
| If none will use "rig_deformers_grp" | |
| *args: Maya's dummy | |
| Returns: | |
| pyNode: The New created joint. | |
| """ | |
| if not obj: | |
| oSel = pm.selected() | |
| else: | |
| oSel = [obj] | |
| for obj in oSel: | |
| if not parent: | |
| try: | |
| oParent = pm.PyNode("jnt_org") | |
| except TypeError: | |
| oParent = obj | |
| else: | |
| oParent = parent | |
| if not jntName: | |
| if noReplace: | |
| jntName = "_".join(obj.name().split("_")) + "_jnt" | |
| else: | |
| jntName = "_".join(obj.name().split("_")[:-1]) + "_jnt" | |
| mtx = om2.MMatrix(cmds.xform(obj.name(), q=True, worldSpace=True, m=True)) | |
| t_mtx = om2.MTransformationMatrix(mtx) | |
| jnt = primitive.addJoint(oParent, jntName, t_mtx) | |
| if grp: | |
| grp.add(jnt) | |
| else: | |
| try: | |
| defSet = pm.PyNode("rig_deformers_grp") | |
| pm.sets(defSet, add=jnt) | |
| except TypeError: | |
| pm.sets(n="rig_deformers_grp") | |
| defSet = pm.PyNode("rig_deformers_grp") | |
| pm.sets(defSet, add=jnt) | |
| jnt.setAttr("segmentScaleCompensate", False) | |
| jnt.setAttr("jointOrient", 0, 0, 0) | |
| pm.parentConstraint(obj, jnt) | |
| return jnt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import pymel.core as pm | |
| def getOrientation(obj, returnType='point'): | |
| ''' | |
| Get an objects orientation. | |
| args: | |
| obj (str)(obj) = The object to get the orientation of. | |
| returnType (str) = The desired returned value type. (valid: 'point', 'vector')(default: 'point') | |
| returns: | |
| (tuple) | |
| ''' | |
| obj = pm.ls(obj)[0] | |
| world_matrix = pm.xform(obj, q=True, m=True, ws=True) | |
| rAxis = pm.getAttr(obj.rotateAxis) | |
| if any((rAxis[0], rAxis[1], rAxis[2])): | |
| print('# Warning: {} has a modified .rotateAxis of {} which is included in the result. #'.format(obj, rAxis)) | |
| if returnType is 'vector': | |
| from maya.api.OpenMaya import MVector | |
| result = ( | |
| MVector(world_matrix[0:3]), | |
| MVector(world_matrix[4:7]), | |
| MVector(world_matrix[8:11]) | |
| ) | |
| else: | |
| result = ( | |
| world_matrix[0:3], | |
| world_matrix[4:7], | |
| world_matrix[8:11] | |
| ) | |
| return result |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment