##-------------------------------------------------------------------------- ## ## ScriptName : Instant Tie ## Author : Joe Wu ## URL : https://www.youtube.com/@Im3dJoe ## LastUpdate : 2025/03/23 ## : easy way to create tie and rope ## Version : 1.0 First version for public test ## ## Other Note : test in maya 2023.3 windows ## ## Install : copy and paste script into a python tab in maya script editor ## use ctrl + drag to adjust droop ##-------------------------------------------------------------------------- import maya.cmds as mc import maya.OpenMaya as om import maya.OpenMayaUI as omui import maya.mel as mel from maya.OpenMaya import MGlobal import math import maya.api.OpenMaya as oma import types, fnmatch, sys, re, inspect, math, string, random, base64 import random def euclidean_distance(pt1, pt2): return math.sqrt((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2 + (pt1[2]-pt2[2])**2) def get_pivot(mesh): mc.CenterPivot(mesh) pivot = mc.xform(mesh, q=True, ws=True, piv=True) return pivot[:3] def cluster_pivots(mesh_list, k=2, iterations=10): points = [(mesh, get_pivot(mesh)) for mesh in mesh_list] if len(points) < k: return {0: points} centroids = [p[1] for p in random.sample(points, k)] for it in range(iterations): clusters = {i: [] for i in range(k)} for mesh, pt in points: distances = [euclidean_distance(pt, centroids[j]) for j in range(k)] clusters[distances.index(min(distances))].append((mesh, pt)) new_centroids = [] for i in range(k): pts = [pt for (mesh, pt) in clusters[i]] if pts: new_centroids.append([sum(c)/len(pts) for c in zip(*pts)]) else: new_centroids.append(centroids[i]) if all(euclidean_distance(new_centroids[i], centroids[i]) < 1e-6 for i in range(k)): break centroids = new_centroids return clusters def average_position(points): n = len(points) return [sum(p[i] for p in points)/n for i in range(3)] def checkFaceAngle(faceName): shapeNode = mc.listRelatives(faceName, fullPath=True , parent=True ) transformNode = mc.listRelatives(shapeNode[0], fullPath=True , parent=True ) obj_matrix = oma.MMatrix(mc.xform(transformNode, query=True, worldSpace=True, matrix=True)) face_normals_text = mc.polyInfo(faceName, faceNormals=True)[0] face_normals = [float(digit) for digit in re.findall(r'-?\d*\.\d*', face_normals_text)] v = oma.MVector(face_normals) * obj_matrix upvector = oma.MVector (0,1,0) getHitNormal = v quat = oma.MQuaternion(upvector, getHitNormal) quatAsEuler = oma.MEulerRotation() quatAsEuler = quat.asEulerRotation() rx, ry, rz = math.degrees(quatAsEuler.x), math.degrees(quatAsEuler.y), math.degrees(quatAsEuler.z) return rx, ry, rz def tieFiz(): cleanList = ('unitMesh', 'tieRefPlan*','intersectionPlan*','cutLin*','cutLineFa*','tempLoftPlan*','shrinkPlan*','MeshGrou*','pfxToon*','meshIntersectionSurface*','nurbsToPoly*') for c in cleanList: if mc.objExists(c): mc.delete(c) mc.button('ropeOnButton', e=1, en= 1) mc.button('ropeOffButton', e=1, en= 0) mc.floatSliderGrp('rtRopeTwist', e=1 ,en=0 , v=0) def get_longest_bbox_length(mesh): bbox = mc.exactWorldBoundingBox(mesh) width = bbox[3] - bbox[0] height = bbox[4] - bbox[1] depth = bbox[5] - bbox[2] return max(width, height, depth) def jwPolyUniteMerge(newName): mergeList = mc.ls(sl=1,fl=1, l=1, type = "transform") parent = mc.listRelatives(mergeList[0], parent=True, fullPath=True) transformNodeList = mc.listRelatives(mergeList[0], ad=True, f=True, type="transform") or [] joinArrays = transformNodeList + mergeList shapeNodeList = mc.listRelatives(joinArrays, s=True, f=True) or [] polyCubeName = mc.polyCube(w=0.0001, h=0.0001, d=0.0001, sx=1, sy=1, sz=1)[0] newNameMerge = mc.rename(newName) nodeName = mc.createNode("polyUnite") mc.rename(newName+ 'shapeMergeNode') for i, shape in enumerate(shapeNodeList): checkTransVis = mc.listRelatives(shape, p=True, f=True, type="transform") if not checkTransVis: continue checkVis = mc.getAttr(checkTransVis[0] + ".visibility") if checkVis == 1: mc.connectAttr(shape + ".outMesh", (newName + "shapeMergeNode.inputPoly[" + str(i) + "]"), f=True) mc.connectAttr(shape + ".worldMatrix[0]", (newName +"shapeMergeNode.inputMat[" + str(i) + "]"), f=True) mc.connectAttr((newName + "shapeMergeNode.output"), (newName + ".inMesh"), f=True) def jwCreateRefPlane(): view = omui.M3dView.active3dView() cam = om.MDagPath() view.getCamera(cam) camPath = cam.fullPathName() cameraTrans = mc.listRelatives(camPath,type='transform',p=True) ratio = mc.getAttr('defaultResolution.deviceAspectRatio') focalLengths = mc.getAttr(cameraTrans[0]+'.focalLength') nearClipPlane = mc.getAttr(cameraTrans[0]+'.nearClipPlane') mc.polyPlane(w = ratio, h=1 ,sx= 40, sy= 20, ax =(0,0,1), cuv= 2, ch=1) mc.rename('tieRefPlane') mc.parentConstraint( cameraTrans[0],('tieRefPlane'),weight =1) mc.delete(constraints=True) mc.parent('tieRefPlane',cameraTrans[0]) mc.setAttr("tieRefPlane.translate",0,0,0) mc.setAttr(('tieRefPlane.translateZ'), (-1.05*nearClipPlane)) head3d = mc.pointPosition(( 'tieRefPlane.vtx[0]') ) tail3d = mc.pointPosition(( 'tieRefPlane.vtx[860]') ) head2D = worldSpaceToImageSpace(cameraTrans[0], (head3d[0],head3d[1],head3d[2])) tail2d = worldSpaceToImageSpace(cameraTrans[0], (tail3d[0],tail3d[1],tail3d[2])) distanceX = tail2d[0] - head2D[0] distanceY = tail2d[1] - head2D[1] resWidth,resHeight = screenRes() mc.setAttr('tieRefPlane.scaleX', (resWidth/distanceX*2)) mc.setAttr('tieRefPlane.scaleY', (resHeight/distanceY*2)) mc.setAttr('tieRefPlane.visibility',0) def getRefPlanePos(SX,SY): if mc.objExists('tieRefPlane'): global CurrentCam pos = om.MPoint() dir = om.MVector() hitpoint = om.MFloatPoint() omui.M3dView().active3dView().viewToWorld(int(SX), int(SY), pos, dir) pos2 = om.MFloatPoint(pos.x, pos.y, pos.z) cameraPosition = mc.xform(CurrentCam,q=1,ws=1,rp=1) checkHit = 0 finalMesh = [] finalX = [] finalY = [] finalZ = [] shortDistance = 10000000000 distanceBetween = 1000000000 checkList = [] checkList.append('tieRefPlane') mesh = checkList[0] selectionList = om.MSelectionList() selectionList.add(mesh) dagPath = om.MDagPath() selectionList.getDagPath(0, dagPath) fnMesh = om.MFnMesh(dagPath) hitFace = om.MScriptUtil() hitFace.createFromInt(0) hitFacePtr = hitFace.asIntPtr() intersection = fnMesh.closestIntersection( om.MFloatPoint(pos2), om.MFloatVector(dir), None, None, False, oma.MSpace.kWorld, 99999, False, None, hitpoint, None, hitFacePtr, None, None, None) if intersection: x = hitpoint.x y = hitpoint.y z = hitpoint.z distanceBetween = math.sqrt( ((float(cameraPosition[0]) - x)**2) + ((float(cameraPosition[1]) - y)**2) + ((float(cameraPosition[2]) - z)**2)) if distanceBetween < shortDistance: shortDistance = distanceBetween finalMesh = mesh finalX = x finalY = y finalZ = z hitFace = om.MScriptUtil(hitFacePtr).asInt() return finalX, finalY, finalZ ,finalMesh ,hitFace mc.refresh(cv=True,f=True) def screenRes(): global lastPanelActive lastPanelActive =[] windowUnder = mc.getPanel(withFocus=True) if 'modelPanel' not in windowUnder: windowUnder = lastPanelActive if 'modelPanel' not in windowUnder: windowUnder = 'modelPanel4' viewNow = omui.M3dView() omui.M3dView.getM3dViewFromModelEditor(windowUnder, viewNow) screenW = omui.M3dView.portWidth(viewNow) screenH = omui.M3dView.portHeight(viewNow) return screenW,screenH def worldSpaceToImageSpace(cameraName, worldPoint): resWidth,resHeight = screenRes() selList = om.MSelectionList() selList.add(cameraName) dagPath = om.MDagPath() selList.getDagPath(0,dagPath) dagPath.extendToShape() camInvMtx = dagPath.inclusiveMatrix().inverse() fnCam = om.MFnCamera(dagPath) mFloatMtx = fnCam.projectionMatrix() projMtx = om.MMatrix(mFloatMtx.matrix) mPoint = om.MPoint(worldPoint[0],worldPoint[1],worldPoint[2]) * camInvMtx * projMtx; x = (mPoint[0] / mPoint[3] / 2 + .5) * resWidth y = (mPoint[1] / mPoint[3] / 2 + .5) * resHeight return [x,y] def create_shrink_wrap(mesh, target, projMethod, **kwargs): parameters = [ ("projection", projMethod), ("closestIfNoIntersection", 1), ("reverse", 0), ("bidirectional", 1), ("boundingBoxCenter", 1), ("axisReference", 1), ("alongX", 0), ("alongY", 0), ("alongZ", 1), ("offset", 0), ("targetInflation", 0), ("targetSmoothLevel", 0), ("falloff", 0), ("falloffIterations", 1), ("shapePreservationEnable", 0), ("shapePreservationSteps", 1) ] target_shapes = mc.listRelatives(target, f=True, shapes=True, type="mesh", ni=True) if not target_shapes: raise ValueError("The target supplied is not a mesh") target_shape = target_shapes[0] shrink_wrap = mc.deformer(mesh, type="shrinkWrap")[0] for parameter, default in parameters: mc.setAttr( shrink_wrap + "." + parameter, kwargs.get(parameter, default)) connections = [ ("worldMesh", "targetGeom"), ("continuity", "continuity"), ("smoothUVs", "smoothUVs"), ("keepBorder", "keepBorder"), ("boundaryRule", "boundaryRule"), ("keepHardEdge", "keepHardEdge"), ("propagateEdgeHardness", "propagateEdgeHardness"), ("keepMapBorders", "keepMapBorders") ] for out_plug, in_plug in connections: mc.connectAttr( target_shape + "." + out_plug, shrink_wrap + "." + in_plug) return shrink_wrap def get_closest_point_from_camera(mesh): global CurrentCam cam_position = mc.xform(CurrentCam, query=True, worldSpace=True, translation=True) vertices = mc.ls(f"{mesh}.vtx[*]", flatten=True) closest_point = None min_distance = float('inf') for vertex in vertices: vertex_position = mc.xform(vertex, query=True, worldSpace=True, translation=True) dist_sq = ((cam_position[0] - vertex_position[0]) ** 2 + (cam_position[1] - vertex_position[1]) ** 2 + (cam_position[2] - vertex_position[2]) ** 2) distance = math.sqrt(dist_sq) if distance < min_distance: min_distance = distance closest_point = vertex_position return closest_point, min_distance def get_farest_point_from_camera(mesh): global CurrentCam cam_pos = mc.xform(CurrentCam, query=True, worldSpace=True, translation=True) vertices = mc.ls(f"{mesh}.vtx[*]", flatten=True) farthest_point = None max_distance = float('-inf') for v in vertices: v_pos = mc.xform(v, query=True, worldSpace=True, translation=True) dist = math.sqrt((cam_pos[0] - v_pos[0])**2 + (cam_pos[1] - v_pos[1])**2 + (cam_pos[2] - v_pos[2])**2) if dist > max_distance: max_distance = dist farthest_point = v_pos return farthest_point, max_distance def get_curve_pivot_distance(curve): global CurrentCam cam_position = mc.xform(CurrentCam, query=True, worldSpace=True, translation=True) pivot = mc.xform(curve, query=True, worldSpace=True, rp=True) distance = math.sqrt((cam_position[0] - pivot[0])**2 + (cam_position[1] - pivot[1])**2 + (cam_position[2] - pivot[2])**2) return pivot, distance def connectSweepNodeToUI(): global sweepNode global cableSize mc.floatSliderGrp('rtSize', e=1, v = cableSize, min = (0.1*cableSize), max = (5*cableSize)) mc.connectControl('rtSize', (sweepNode[0] + ".scaleProfileX")) mc.connectControl('rtPrecision', (sweepNode[0] + ".interpolationPrecision")) mc.connectControl('rtRopeTwist', (sweepNode[0] + ".twist")) mc.floatSliderGrp('rtRopeTwist', e=1 ,en=0 , v=0) def ropeModeOff(): global sweepNode mc.setAttr(sweepNode[0]+'.patternEnable', 0) mc.setAttr(sweepNode[0]+'.twist',0) mc.button('ropeOnButton', e=1, en= 1) mc.button('ropeOffButton', e=1, en= 0) mc.floatSliderGrp('rtRopeTwist', e=1 ,en=0 , v=0) def ropeMode(): global sweepNode mc.setAttr(sweepNode[0]+'.patternEnable', 1) mc.setAttr(sweepNode[0]+'.patternNumberOfElements', 4) mc.setAttr(sweepNode[0]+'.interpolationMode', 1) mc.setAttr(sweepNode[0]+'.interpolationSteps', 100) mc.setAttr(sweepNode[0]+'.interpolationOptimize', 0) mc.setAttr(sweepNode[0]+'.patternScaleElementsX', 1) mc.setAttr(sweepNode[0]+'.twist', 10) mc.button('ropeOnButton', e=1, en= 0) mc.button('ropeOffButton', e=1, en= 1) mc.floatSliderGrp('rtRopeTwist', e=1 ,en=1) def createCable(cableSize): createType = mc.radioButtonGrp('rtType',q=1, sl=1) precision = mc.floatSliderGrp('rtPrecision', q=1, v = 1) global currentCurve curveSel = mc.ls(sl=1,fl=1) if mc.objExists('tieMeshGr*') == 0: mc.CreateEmptyGroup() mc.rename('tieMeshGrp') if mc.objExists('tieCurveGr*') == 0: mc.CreateEmptyGroup() mc.rename('tieCurveGrp') mc.parent(curveSel, 'tieCurveGrp') mc.rename('tieCurve_1') curveSel = mc.ls(sl=1,fl=1) currentCurve = curveSel[0] indexNumber = curveSel[0].split('_')[-1] global sweepNode nextCurveName = '' if sweepNode == '' or mc.objExists(sweepNode[0])== 0: mc.sweepMeshFromCurve(oneNodePerCurve=0) sweepNode = mc.ls(selection=True, flatten=True) mc.setAttr(sweepNode[0]+'.interpolationOptimize', 1) mc.setAttr(sweepNode[0]+'.interpolationPrecision',precision) mc.setAttr(sweepNode[0]+'.scaleProfileX', cableSize) mc.rename('sweep1','tieMesh_1') nextCurveName = ('tieMesh_1') else: curveShapes = mc.listRelatives(curveSel[0], shapes=True, fullPath=True) mc.connectAttr((curveShapes[0] + ".worldSpace[0]"), (sweepNode[0] + ".inCurveArray[" + str(indexNumber) + "]"), force=True) mc.createNode("mesh", name="sweepShape" + str(indexNumber)) mc.connectAttr((sweepNode[0] + ".outMeshArray[" + str(indexNumber) + "]"),("sweepShape" + str(indexNumber) + ".inMesh"), force=True) mc.sets(('sweep' + str(indexNumber)), edit=True, forceElement="initialShadingGroup") mc.rename(('sweep' + str(indexNumber)),('tieMesh_' + str(indexNumber))) curveShapes = mc.listRelatives(('|tieMesh_' + str(indexNumber)), shapes=True, fullPath=True) nextCurveName = curveShapes[0] if '|' in nextCurveName: selMesh = nextCurveName.split('|')[1] mc.parent(selMesh ,'tieMeshGrp') else: mc.parent(('|' + nextCurveName) ,'tieMeshGrp') stateName = mc.ls(sl=1,fl=1)[0] plugs = mc.listConnections('unitMeshshapeMergeNode.inputPoly', plugs=True, source=True) or [] next_index = len(plugs) mc.connectAttr(stateName + ".outMesh", ("unitMeshshapeMergeNode.inputPoly[" + str(next_index+1) + "]"), f=True) mc.connectAttr(stateName + ".worldMatrix[0]", ("unitMeshshapeMergeNode.inputMat[" + str(next_index+1) + "]"), f=True) connectSweepNodeToUI() mc.select(cl=1) def createTie(): global currentCurveType currentCurveType = 1 global cableSize mc.CenterPivot() mc.duplicate() mc.rename('cutLineFar') curveList, distanceO = get_curve_pivot_distance("cutLine") global CurrentCam source_pivot = mc.xform(CurrentCam, query=True, worldSpace=True, rotatePivot=True) mc.xform('cutLine', worldSpace=True, pivots=source_pivot) mc.xform('cutLineFar', worldSpace=True, pivots=source_pivot) farest_vertex, distanceF = get_farest_point_from_camera("unitMesh") closest_vertex, distanceC = get_closest_point_from_camera("unitMesh") scaleVC = distanceC / distanceO*0.95 scaleVF = distanceF / distanceO*1.05 mc.setAttr("cutLine.scale", scaleVC,scaleVC,scaleVC) mc.setAttr("cutLineFar.scale", scaleVF,scaleVF,scaleVF) mc.nurbsToPolygonsPref(polyType=1 , format=2, uType=3, uNumber=1, vType=3, vNumber=1) loft_surface = mc.loft('cutLineFar', 'cutLine',ch=True, u=True,c=False,ar=True,d=1,ss=1,rn=False,po=1,rsn=True) rx, ry, rz = checkFaceAngle(loft_surface[0]+'.f[0]') mc.rename('tempLoftPlane') result = mc.polySmooth(mth=0, dv=1, c=1, kb=0, ksb=0, khe=0, kt=1, kmb=1, suv=1, sl=1, dpe=1, ps=0.1, ro=1) mc.select('tempLoftPlane','unitMesh') mel.eval('assignNewPfxToon') getToon =mc.ls(sl=1) gettoonTrans = mc.listRelatives(getToon,p=1) toonShape = mc.listRelatives(gettoonTrans,c=1) mc.setAttr((toonShape[0]+'.borderLines'), 0) mc.setAttr((toonShape[0]+'.intersectionLines'), 1) mc.setAttr((toonShape[0]+'.creaseLines'), 0) mc.setAttr((toonShape[0]+'.profileLines'), 0) mc.setAttr((toonShape[0]+'.degree'), 1) mc.setAttr((toonShape[0]+'.selfIntersect'), 0) mc.setAttr((toonShape[0]+'.lineWidth'), 0.001) mc.select(gettoonTrans) nurbsGroups = mel.eval('createPfxOutCurves("%s", 1)' % gettoonTrans[0]) shapes = mc.listRelatives(nurbsGroups[0], ad=True, typ = 'transform') mc.nurbsToPolygonsPref(polyType=1 , format=2, uType=3, uNumber=1, vType=3, vNumber=1) convertList = [] for u in shapes: result = mc.nurbsToPoly(u, mnd=1,ch=0,f=2,pt=True,pc=200,chr=0.1,ft=0.01,mel=0.001,d=0.1,ut=3,un=1,vt=3,vn=1,uch=False,ucr=False,cht=0.2,es=0,ntr=0,mrt=0,uss=True) convertList.append(result[0]) mc.select(convertList) jwPolyUniteMerge('intersectionPlane') mc.CenterPivot('intersectionPlane') mc.delete('intersectionPlane', ch=1) shrinkPlane = mc.polyPlane(width=1, height=1, subdivisionsX=10, subdivisionsY=10, axis=(0, 1, 0), createUVs=2, constructionHistory=0) mc.rename('shrinkPlane') mc.select('intersectionPlane', add=1) mc.MatchTranslation() mc.setAttr("shrinkPlane.rotate", rx, ry, rz) mc.setAttr("shrinkPlane.scale", 100000,100000,100000) mc.makeIdentity(apply=True, translate=1, rotate=1, scale=1, normal=0, pn=True) mc.select('shrinkPlane') mc.ConvertSelectionToEdgePerimeter() mc.InvertSelection() mc.delete() shrink_wrap = create_shrink_wrap('shrinkPlane','intersectionPlane', 4) mc.delete('shrinkPlane',ch=1) mc.polyMergeVertex('shrinkPlane', d=0.001, am=1, ch=0) mc.ConvertSelectionToEdgePerimeter() mc.polyToCurve(form=2, degree=1, conformToSmoothMeshPreview=1) mc.delete(ch=1) mc.rename('tieCurve_1') cleanList = ('tieRefPlan*','intersectionPlan*','cutLin*','cutLineFa*','tempLoftPlan*','shrinkPlan*','MeshGrou*','pfxToon*','meshIntersectionSurface*','nurbsToPoly*') for c in cleanList: if mc.objExists(c): mc.delete(c) createCable(cableSize) def jwDrawLine(): cleanList = ('tieRefPlan*','intersectionPlan*','cutLin*','cutLineFa*','tempLoftPlan*','shrinkPlan*','MeshGrou*','pfxToon*','meshIntersectionSurface*','nurbsToPoly*') for c in cleanList: if mc.objExists(c): mc.delete(c) global currentCurve currentCurve = '' global CurrentCam mc.select(cl=True) view = omui.M3dView.active3dView() cam = om.MDagPath() view.getCamera(cam) camPath = cam.fullPathName() cameraTrans = mc.listRelatives(camPath,type='transform',p=True) CurrentCam = cameraTrans[0] global ctx ctx = 'Click2dTo3dCtx' if mc.draggerContext(ctx, exists=True): mc.deleteUI(ctx) mc.draggerContext(ctx, pressCommand = onPressPlace, rc = offPressPlace, dragCommand = onDragPlace, name=ctx, cursor='crossHair',undoMode='step') mc.setToolTo(ctx) def onPressPlace(): global currentNode global currentCurve global yMove global screenX,screenY global CurrentCam global ctx currentNode = 0 modifiers = mc.getModifiers() if (modifiers == 4): currentNode = 1 else: if mc.objExists('gravPoint_1'): clusterNode = mc.listConnections('gravPoint_1', d =1 ) connected = mc.listConnections(clusterNode[0] + ".outputGeometry", plugs=True) curveNode = connected[0].split('.')[0] mc.delete(curveNode,ch=True) if mc.objExists('tieRefPlane') == 0: jwCreateRefPlane() mc.curve(d= 1, p=[(0, 0, 0),(1,0,0)], k = [0,1]) mc.rename('cutLine') shapes = mc.listRelatives('cutLine',shapes=True) vpX, vpY, _ = mc.draggerContext(ctx, query=True, anchorPoint=True) screenX = vpX screenY = vpY wx,wy,wz,hitmesh,hitFace= getRefPlanePos(screenX,screenY) mc.move(wx,wy,wz,(shapes[0]+'.cv[0]'), ws=True, a=True) def offPressPlace(): rtTypeGet = mc.radioButtonGrp('rtType', q=1, sl=1) autoNode = mc.checkBox('autoNodeButton',q= 1,v=1) global currentCurve global currentNode modifiers = mc.getModifiers() if (modifiers == 4): currentNode = 1 updateNodesEnd() else: if mc.objExists('gravPoint_1') == 0 : if rtTypeGet == 1: createTie() if autoNode == 1: createNodeTieMesh() else: createRope() if currentNode == 0: if autoNode == 1: createNodeMesh() updateNodesEnd() mc.refresh(cv=True,f=True) cleanList = ('tieRefPlan*','intersectionPlan*','cutLin*','cutLineFa*','tempLoftPlan*','shrinkPlan*','MeshGrou*','pfxToon*','meshIntersectionSurface*','nurbsToPoly*') for c in cleanList: if mc.objExists(c): mc.delete(c) def onDragPlace(): global cableSize global currentCurve global yMove global screenX,screenY global currentNode global currentCurveType vpX, vpY, _ = mc.draggerContext(ctx, query=True, dragPoint=True) modifiers = mc.getModifiers() if (modifiers == 4): if currentCurveType == 2: currentNode = 1 if screenX > vpX: yMove = (yMove + (cableSize * 0.5) ) else: yMove = (yMove - (cableSize * 0.5) ) screenX = vpX if mc.objExists(currentCurve): position = mc.pointPosition((currentCurve + '.cv[1]'), world=True) mc.move( position[0], yMove, position[2], (currentCurve + '.cv[1]'), absolute=True ) position = mc.pointPosition((currentCurve + '.cv[2]'), world=True) mc.move( position[0], yMove, position[2], (currentCurve + '.cv[2]'), absolute=True ) else: screenX = vpX screenY = vpY result = getRefPlanePos(screenX, screenY) if (result is not None and hasattr(result, '__len__') and len(result) == 5): wx, wy, wz, hitmesh, hitFace = result shapes = mc.listRelatives('cutLine',shapes=True) mc.move(wx,wy,wz,('cutLine.cv[1]'), ws=True, a=True) mc.refresh(cv=True,f=True) def compute_bezier_control_points(p0, p3, factor1=1/3.0, factor2=2/3.0): p1 = [ p0[i] + (p3[i]-p0[i]) * factor1 for i in range(3) ] p2 = [ p0[i] + (p3[i]-p0[i]) * factor2 for i in range(3) ] return [p0, p1, p2, p3] def lower_middle_points(curve_name, slider_value): pos0 = mc.pointPosition(curve_name + ".cv[0]", world=True) pos3 = mc.pointPosition(curve_name + ".cv[3]", world=True) lowest_y = min(pos0[1], pos3[1]) random_offset = slider_value + random.uniform(-slider_value * 0.1, slider_value * 0.1) random_offset = abs(random_offset) new_y = lowest_y - random_offset for cv_index in [1, 2]: pos = mc.pointPosition(curve_name + ".cv[%d]" % cv_index, world=True) new_pos = [pos[0], new_y, pos[2]] mc.xform(curve_name + ".cv[%d]" % cv_index, ws=True, t=new_pos) def create_line_from_clusters(clusters): global yMove group0 = clusters.get(0, []) group1 = clusters.get(1, []) if not group0 or not group1: cleanList = ('tieRefPlan*','intersectionPlan*','cutLin*','cutLineFa*','tempLoftPlan*','shrinkPlan*','MeshGrou*','pfxToon*','meshIntersectionSurface*','nurbsToPoly*') for c in cleanList: if mc.objExists(c): mc.delete(c) else: pts0 = [pivot for (mesh, pivot) in group0] pts1 = [pivot for (mesh, pivot) in group1] avg0 = average_position(pts0) avg1 = average_position(pts1) pts = compute_bezier_control_points(avg0, avg1) knots = [0, 0, 0, 1, 1, 1] curve_name = mc.curve(p=pts, d=3, k=knots, bezier=True,name = 'ropeCurve_1') randState = mc.floatSliderGrp('rtDropRandom', q=1, v = 1) pos0 = mc.pointPosition(curve_name + ".cv[0]", world=True) pos3 = mc.pointPosition(curve_name + ".cv[3]", world=True) lowest_y = min(pos0[1], pos3[1]) random_offset = randState + random.uniform(-randState * 0.1, randState * 0.1) random_offset = abs(random_offset) new_y = lowest_y - random_offset for cv_index in [1, 2]: pos = mc.pointPosition(curve_name + ".cv[%d]" % cv_index, world=True) if randState > 0: new_pos = [pos[0], new_y, pos[2]] mc.xform(curve_name + ".cv[%d]" % cv_index, ws=True, t=new_pos) yMove = new_y def createRope(): cleanList = ('tieRefPlan*','intersectionPlan*','cutLin*','cutLineFa*','tempLoftPlan*','shrinkPlan*','MeshGrou*','pfxToon*','meshIntersectionSurface*','nurbsToPoly*') global cableSize global yMove yMove = 0 global currentCurveType currentCurveType = 2 if mc.objExists('cutLine'): mc.select('cutLine') mc.CenterPivot() mc.duplicate() mc.rename('cutLineFar') curveList, distanceO = get_curve_pivot_distance("cutLine") global CurrentCam source_pivot = mc.xform(CurrentCam, query=True, worldSpace=True, rotatePivot=True) mc.xform('cutLine', worldSpace=True, pivots=source_pivot) mc.xform('cutLineFar', worldSpace=True, pivots=source_pivot) farest_vertex, distanceF = get_farest_point_from_camera("unitMesh") closest_vertex, distanceC = get_closest_point_from_camera("unitMesh") scaleVC = distanceC / distanceO*0.95 scaleVF = distanceF / distanceO*1.05 mc.setAttr("cutLine.scale", scaleVC,scaleVC,scaleVC) mc.setAttr("cutLineFar.scale", scaleVF,scaleVF,scaleVF) mc.nurbsToPolygonsPref(polyType=1 , format=2, uType=3, uNumber=1, vType=3, vNumber=1) loft_surface = mc.loft('cutLineFar', 'cutLine',ch=True, u=True,c=False,ar=True,d=1,ss=1,rn=False,po=1,rsn=True) rx, ry, rz = checkFaceAngle(loft_surface[0]+'.f[0]') mc.rename('tempLoftPlane') result = mc.polySmooth(mth=0, dv=1, c=1, kb=0, ksb=0, khe=0, kt=1, kmb=1, suv=1, sl=1, dpe=1, ps=0.1, ro=1) mc.select('tempLoftPlane','unitMesh') mel.eval('assignNewPfxToon') getToon =mc.ls(sl=1) gettoonTrans = mc.listRelatives(getToon,p=1) toonShape = mc.listRelatives(gettoonTrans,c=1) mc.setAttr((toonShape[0]+'.borderLines'), 0) mc.setAttr((toonShape[0]+'.intersectionLines'), 1) mc.setAttr((toonShape[0]+'.creaseLines'), 0) mc.setAttr((toonShape[0]+'.profileLines'), 0) mc.setAttr((toonShape[0]+'.degree'), 1) mc.setAttr((toonShape[0]+'.selfIntersect'), 0) mc.setAttr((toonShape[0]+'.lineWidth'), 0.001) mc.select(gettoonTrans) nurbsGroups = mel.eval('createPfxOutCurves("%s", 1)' % gettoonTrans[0]) if mc.objExists(nurbsGroups[0]): shapes = mc.listRelatives(nurbsGroups[0], ad=True, typ = 'transform') or [] if shapes: mc.nurbsToPolygonsPref(polyType=1 , format=2, uType=3, uNumber=1, vType=3, vNumber=1) convertList = [] for u in shapes: result = mc.nurbsToPoly(u, mnd=1,ch=0,f=2,pt=True,pc=200,chr=0.1,ft=0.01,mel=0.001,d=0.1,ut=3,un=1,vt=3,vn=1,uch=False,ucr=False,cht=0.2,es=0,ntr=0,mrt=0,uss=True) convertList.append(result[0]) mc.select(convertList) mc.CenterPivot() clusters = cluster_pivots(convertList, k=2) curve = create_line_from_clusters(clusters) if mc.objExists('|ropeCurve_1'): mc.select('|ropeCurve_1') for c in cleanList: if mc.objExists(c): mc.delete(c) createCable(cableSize) currentNode = 1 else: for c in cleanList: if mc.objExists(c): mc.delete(c) mc.select(cl=1) def instantTieGo(): tieFiz() script_jobs = mc.scriptJob(listJobs=True) for job in script_jobs: if 'tieFiz' in job: job_id = int(job.split(":")[0]) mc.scriptJob(kill=job_id, force=True) global tieSelTarget global tieToolID global sweepNode global cableSize global sweepNodeB global currentCurveType currentCurveType = 1 sweepNodeB = '' cableSize = 0.1 sweepNode = '' tieSelTarget = [] tieSelTarget = mc.ls(sl=1,fl=1) filteredList = [] for obj in tieSelTarget: shapes = mc.listRelatives(obj, shapes=True) or [] if not any(mc.objectType(shape) in ['nurbsCurve', 'bezierCurve'] for shape in shapes): filteredList.append(obj) if filteredList: mc.select(filteredList) jwPolyUniteMerge('unitMesh') mc.setAttr("unitMesh.visibility",0) mc.setAttr("unitMesh.hiddenInOutliner",1) cableSize = get_longest_bbox_length('unitMesh') / 100 jwDrawLine() tieToolID = mc.scriptJob(runOnce=True, event=["ToolChanged", tieFiz]) def ropeRandSwitch(): state = mc.radioButtonGrp('rtType',q=1, sl=1) if state == 1: mc.floatSliderGrp('rtDropRandom', e=1 , en=0) mc.floatSliderGrp('rtPrecision', e =1, v = 50) else: mc.floatSliderGrp('rtDropRandom', e=1 , en=1) mc.floatSliderGrp('rtPrecision', e =1, v = 90) def updateNodesEnd(): global currentCurve getAllNodeList = mc.ls('nodeCurve_*',fl=1) if getAllNodeList: for g in getAllNodeList: parentCurve = mc.getAttr(g + '.sourceCurve') if currentCurve == parentCurve: posi = mc.getAttr(g + '.nodePosition') pos0 = [] pos1 = [] if posi == 'head': pos0 = mc.pointOnCurve(parentCurve, pr=0.01, p=True) pos1 = mc.pointOnCurve(parentCurve, pr=0.02, p=True) else: pos0 = mc.pointOnCurve(parentCurve, pr=0.99, p=True) pos1 = mc.pointOnCurve(parentCurve, pr=0.98, p=True) mc.xform(g, ws=True, t=pos0) direction = [pos1[i] - pos0[i] for i in range(3)] rot = mc.angleBetween(euler=True, v1=[1, 0, 0], v2=direction) mc.xform(g, ws=True, ro=rot) rotRand = random.uniform(0, 360) mc.setAttr(g+ '.rotateX',rotRand) def createNodeTieMesh(): global currentCurve global sweepNode global sweepNodeB global currentNode currentNode = 1 ccc = mc.listRelatives(currentCurve, shapes=True, fullPath=True) plugs = mc.listConnections(ccc[0]+ '.worldSpace[0]', plugs=True, source=True) or [] getSweepNode = plugs[0].split('.')[0] cableSize = mc.getAttr(getSweepNode + '.scaleProfileX') if mc.objExists('NodeMeshGr*') == 0: mc.CreateEmptyGroup() mc.rename('NodeMeshGrp') nodeGrp= createNodesTie(currentCurve) mc.parent(nodeGrp, 'NodeMeshGrp') nodeGrp = mc.ls(sl=1,fl=1) mc.addAttr(nodeGrp[0], ln="sourceCurve", dt="string") mc.setAttr(nodeGrp[0] + ".sourceCurve", "", type="string") mc.setAttr(nodeGrp[0] + ".sourceCurve", currentCurve, type="string") mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(nodeGrp[0] + ".scaleX"), force=True) mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(nodeGrp[0] + ".scaleY"), force=True) mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(nodeGrp[0] + ".scaleZ"), force=True) if sweepNodeB == '' or mc.objExists(sweepNodeB[0])== 0: mc.sweepMeshFromCurve(oneNodePerCurve=0) sweepNodeB = mc.ls(selection=True, flatten=True) mc.setAttr(sweepNodeB[0]+'.interpolationMode', 2) mc.setAttr(sweepNodeB[0]+'.interpolationSteps', 4) mc.setAttr(sweepNodeB[0]+'.scaleProfileX', cableSize) mc.rename('sweep1','nodeMesh_1') mc.parent('|nodeMesh_1', 'NodeMeshGrp') else: for c in nodeGrp: indexNumber = c.split('_')[-1] curveShapes = mc.listRelatives(c, shapes=True, fullPath=True) mc.connectAttr((curveShapes[0] + ".worldSpace[0]"), (sweepNodeB[0] + ".inCurveArray[" + str(indexNumber) + "]"), force=True) mc.createNode("mesh", name="sweepShape" + str(indexNumber)) mc.connectAttr((sweepNodeB[0] + ".outMeshArray[" + str(indexNumber) + "]"),("sweepShape" + str(indexNumber) + ".inMesh"), force=True) mc.sets(('sweep' + str(indexNumber)), edit=True, forceElement="initialShadingGroup") mc.rename( ('sweep' + str(indexNumber)) ,('nodeMesh_' + str(indexNumber))) mc.parent(('nodeMesh_' + str(indexNumber)) ,'NodeMeshGrp') mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(sweepNodeB[0] + ".scaleProfileX"),force=True) mc.select(cl=1) def createNodeMesh(): global currentCurve global sweepNode global sweepNodeB global currentNode currentNode = 1 ccc = mc.listRelatives(currentCurve, shapes=True, fullPath=True) plugs = mc.listConnections(ccc[0]+ '.worldSpace[0]', plugs=True, source=True) or [] getSweepNode = plugs[0].split('.')[0] cableSize = mc.getAttr(getSweepNode + '.scaleProfileX') if mc.objExists('NodeMeshGr*') == 0: mc.CreateEmptyGroup() mc.rename('NodeMeshGrp') nodeGrp= createNodesEnd(currentCurve) mc.parent(nodeGrp, 'NodeMeshGrp') nodeGrp = mc.ls(sl=1,fl=1) mc.addAttr(nodeGrp[0], ln="sourceCurve", dt="string") mc.setAttr(nodeGrp[0] + ".sourceCurve", "", type="string") mc.setAttr(nodeGrp[0] + ".sourceCurve", currentCurve, type="string") mc.addAttr(nodeGrp[1], ln="sourceCurve", dt="string") mc.setAttr(nodeGrp[1] + ".sourceCurve", "", type="string") mc.setAttr(nodeGrp[1] + ".sourceCurve", currentCurve, type="string") mc.addAttr(nodeGrp[0], ln="nodePosition", dt="string") mc.setAttr(nodeGrp[0] + ".nodePosition", "", type="string") mc.setAttr(nodeGrp[0] + ".nodePosition", 'head', type="string") mc.addAttr(nodeGrp[1], ln="nodePosition", dt="string") mc.setAttr(nodeGrp[1] + ".nodePosition", "", type="string") mc.setAttr(nodeGrp[1] + ".nodePosition", 'end', type="string") mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(nodeGrp[0] + ".scaleX"), force=True) mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(nodeGrp[0] + ".scaleY"), force=True) mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(nodeGrp[0] + ".scaleZ"), force=True) mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(nodeGrp[1] + ".scaleX"), force=True) mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(nodeGrp[1] + ".scaleY"), force=True) mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(nodeGrp[1] + ".scaleZ"), force=True) if sweepNodeB == '' or mc.objExists(sweepNodeB[0])== 0: mc.sweepMeshFromCurve(oneNodePerCurve=0) sweepNodeB = mc.ls(selection=True, flatten=True) mc.setAttr(sweepNodeB[0]+'.interpolationMode', 2) mc.setAttr(sweepNodeB[0]+'.interpolationSteps', 4) mc.setAttr(sweepNodeB[0]+'.scaleProfileX', cableSize) mc.rename('sweep1','nodeMesh_1') mc.parent('|nodeMesh_1', 'NodeMeshGrp') mc.rename('sweep2','nodeMesh_1') mc.parent('|nodeMesh_1', 'NodeMeshGrp') else: for c in nodeGrp: indexNumber = c.split('_')[-1] curveShapes = mc.listRelatives(c, shapes=True, fullPath=True) mc.connectAttr((curveShapes[0] + ".worldSpace[0]"), (sweepNodeB[0] + ".inCurveArray[" + str(indexNumber) + "]"), force=True) mc.createNode("mesh", name="sweepShape" + str(indexNumber)) mc.connectAttr((sweepNodeB[0] + ".outMeshArray[" + str(indexNumber) + "]"),("sweepShape" + str(indexNumber) + ".inMesh"), force=True) mc.sets(('sweep' + str(indexNumber)), edit=True, forceElement="initialShadingGroup") mc.rename( ('sweep' + str(indexNumber)) ,('nodeMesh_' + str(indexNumber))) mc.parent(('nodeMesh_' + str(indexNumber)) ,'NodeMeshGrp') #connectSweepNode mc.connectAttr((sweepNode[0] + ".scaleProfileX"),(sweepNodeB[0] + ".scaleProfileX"),force=True) mc.select(cl=1) def recreate_curve(original_curve): shapes = mc.listRelatives('nodeCurve_1', shapes=True, noIntermediate=True) curve_shape = shapes[0] degree = mc.getAttr(curve_shape + ".degree") cv_list = mc.ls('nodeCurve_1' + ".cv[*]", flatten=True) points = [mc.pointPosition(cv, world=True) for cv in cv_list] simplified_points = [[round(coord, 3) for coord in point] for point in points] def createTieCurve(): simplified_points = [[1.322, -0.373, 0.494], [2.125, 0.006, 0.969], [2.211, 0.705, 1.113], [2.264, 1.24, 0.61], [2.256, 1.511, -0.076], [2.241, 1.307, -0.879], [2.475, 0.496, -1.235], [2.67, -0.385, -1.097], [2.534, -1.166, -0.638], [2.011, -1.554, 0.226], [1.523, -1.545, 0.81], [1.09, -1.255, 1.045], [0.762, -0.726, 1.017], [0.614, -0.148, 0.749], [0.713, 0.348, 0.377], [1.267, 0.62, 0.078], [2.354, 0.526, -0.017]] new_curve = mc.curve(p=simplified_points, d=3, name="nodeCurve_1") return new_curve def createNodeCurve(): simplified_points =[[2.27, 2.2, -2.0], [2.67, 1.9, -1.3], [2.97, 1.1, 0.0], [2.77, 0.0, 1.6], [2.87, -1.1, 1.4], [2.77, -1.8, 0.7], [2.87, -1.7, -0.2], [3.07, -0.9, -0.8], [3.97, 0.2, -0.6], [4.27, 0.8, 0.5], [3.77, 1.2, 0.9], [3.17, 1.4, 1.1], [2.47, 1.4, 0.8], [2.07, 1.1, 0.3], [1.97, 0.9, -0.7], [1.97, 0.1, -1.3], [1.97, -0.7, -1.1], [1.77, -1.4, -0.8], [1.57, -1.8, -0.2], [1.47, -1.9, 0.6], [1.47, -1.3, 1.4], [1.47, -0.4, 1.8], [1.37, 0.7, 1.6], [1.17, 1.4, 0.9], [0.97, 1.5, 0.0], [0.77, 1.3, -0.8], [0.57, 0.6, -1.4], [0.27, -0.7, -1.6], [0.27, -1.7, -0.7], [0.17, -1.8, 0.3], [-0.13, -1.4, 0.9], [-0.23, -0.8, 1.5], [-0.23, 0.1, 1.6], [-0.33, 1.0, 1.1], [-0.53, 1.4, 0.5], [-0.63, 1.4, -0.2], [-0.53, 1.0, -0.7], [0.27, 0.4, -0.7], [2.07, -0.5, -0.7]] new_curve = mc.curve(p=simplified_points, d=3, name="nodeCurve_1") return new_curve def longest_neighbor_cv_distance(curve): cvs = mc.ls(curve + ".cv[*]", flatten=True) if not cvs or len(cvs) < 2: raise RuntimeError("Curve has less than 2 CVs: " + curve) max_dist = 0.0 longest_pair = (None, None) for i in range(len(cvs) - 1): pos1 = mc.pointPosition(cvs[i], world=True) pos2 = mc.pointPosition(cvs[i+1], world=True) dx = pos2[0] - pos1[0] dy = pos2[1] - pos1[1] dz = pos2[2] - pos1[2] dist = math.sqrt(dx*dx + dy*dy + dz*dz) if dist > max_dist: max_dist = dist longest_pair = (i, i+1) return longest_pair, max_dist def createNodesTie(curveName): newNodeNameA = createTieCurve() pair, distance = longest_neighbor_cv_distance(curveName) random_num = random.uniform(pair[0], pair[1]) pos0 = mc.pointOnCurve(curveName, pr= random_num, p=True) pos1 = mc.pointOnCurve(curveName, pr= (random_num+0.1), p=True) mc.xform(newNodeNameA, ws=True, t=pos0) direction = [pos1[i] - pos0[i] for i in range(3)] rot = mc.angleBetween(euler=True, v1=[1, 0, 0], v2=direction) mc.xform(newNodeNameA, ws=True, ro=rot) rotRand = random.uniform(0, 360) mc.setAttr(newNodeNameA+ '.rotateX',rotRand) return newNodeNameA def createNodesEnd(curveName): newNodeNameA = createNodeCurve() pos0 = mc.pointOnCurve(curveName, pr= 0.01, p=True) pos1 = mc.pointOnCurve(curveName, pr= 0.02, p=True) mc.xform(newNodeNameA, ws=True, t=pos0) direction = [pos1[i] - pos0[i] for i in range(3)] rot = mc.angleBetween(euler=True, v1=[1, 0, 0], v2=direction) mc.xform(newNodeNameA, ws=True, ro=rot) rotRand = random.uniform(0, 360) mc.setAttr(newNodeNameA+ '.rotateX',rotRand) newNodeNameB = createNodeCurve() pos0 = mc.pointOnCurve(curveName, pr=0.99, p=True) pos1 = mc.pointOnCurve(curveName, pr=0.98, p=True) mc.xform(newNodeNameB, ws=True, t=pos0) direction = [pos1[i] - pos0[i] for i in range(3)] rot = mc.angleBetween(euler=True, v1=[1, 0, 0], v2=direction) mc.xform(newNodeNameB, ws=True, ro=rot) rotRand = random.uniform(0, 360) mc.setAttr(newNodeNameB+ '.rotateX',rotRand) return newNodeNameA ,newNodeNameB def InstantTie(): if mc.window("InstantTieUI", exists=True): mc.deleteUI("InstantTieUI") mc.window("InstantTieUI", title="Instant Tie v1.0", widthHeight=(320, 130),mxb = False, mnb = False, s = 1 ) mc.columnLayout(adjustableColumn=True) mc.text(l=' ',h=3) mc.columnLayout() mc.columnLayout(adjustableColumn=True) mc.floatSliderGrp('rtSize', v = 1, min = 0.1, max = 5, s = 0.1, cw3 = [60,30,190] , label = "Thickness: " ,field =True,h=20) mc.floatSliderGrp('rtPrecision', v = 50, min = 0, max = 100, s = 1, cw3 = [60,30,190] , label = "Precision: " ,field =True,h=20) mc.floatSliderGrp('rtDropRandom', v = 0, min = 0, max = 10, s = 1, cw3 = [60,30,190] , label = "Droop: " ,field =True, en=0,h=20) mc.setParent( '..' ) mc.rowColumnLayout(nc= 4 ,cw=[(1,200),(2,50),(3,5),(4,30)]) mc.floatSliderGrp('rtRopeTwist', v = 0, min = 0, max = 20, fmx = 100, s = 0.1, cw3 = [60,30,190] , label = "Twist: " ,field =True,en=0) mc.button('ropeOnButton', label='twist', command=lambda *args: ropeMode()) mc.text(l=' ') mc.button('ropeOffButton',label='off', command=lambda *args: ropeModeOff(),en=0) mc.setParent( '..' ) mc.text(l=' ') mc.rowColumnLayout(nc= 4 ,cw=[(1,10),(2,170),(3,20),(4,85)]) mc.text(l=' ') mc.radioButtonGrp('rtType', nrb=2, sl=1, label='Type: ', labelArray2=['Tie', 'Rope'], cw = [(1,50),(2,50)],cc = lambda *args: ropeRandSwitch()) mc.text(l=' ') mc.checkBox('autoNodeButton', label= "Auto Node" ,v=1) mc.setParent( '..' ) mc.rowColumnLayout(nc= 2 ,cw=[(1,70),(2,170)]) mc.text(l=' ') mc.button(label='go', command=lambda *args: instantTieGo()) mc.text(l=' ') mc.showWindow("InstantTieUI") InstantTie()