237 lines
8.0 KiB
Python
237 lines
8.0 KiB
Python
|
import maya.cmds as cmds
|
||
|
import maya.mel as mel
|
||
|
import maya.OpenMaya as om
|
||
|
import maya.OpenMayaAnim as oma
|
||
|
import collections
|
||
|
|
||
|
util = om.MScriptUtil()
|
||
|
util.createFromInt(0)
|
||
|
|
||
|
dummy = {}
|
||
|
dummy['mob'] = om.MObject()
|
||
|
dummy['intPtr'] = util.asIntPtr()
|
||
|
dummy['omp'] = om.MPoint()
|
||
|
|
||
|
class MDagPath(om.MDagPath):
|
||
|
def __init__(self, node=None):
|
||
|
super(MDagPath, self).__init__()
|
||
|
if node is not None:
|
||
|
sel = om.MSelectionList()
|
||
|
sel.add(node)
|
||
|
sel.getDagPath(0, self, dummy['mob'])
|
||
|
|
||
|
def __str__(self):
|
||
|
return self.fullPathName()
|
||
|
|
||
|
class MeshData(object):
|
||
|
def __init__(self, mfnMesh):
|
||
|
self.mesh = mfnMesh
|
||
|
self.regist()
|
||
|
|
||
|
def minmax(self, minA, maxA, pos):
|
||
|
for i in range(3):
|
||
|
if minA[i] > pos[i]:
|
||
|
minA[i] = pos[i]
|
||
|
if maxA[i] <= pos[i]:
|
||
|
maxA[i] = pos[i]
|
||
|
|
||
|
def createBlock(self, minA, maxA):
|
||
|
functions = []
|
||
|
split = 8
|
||
|
for i in range(3):
|
||
|
reach = (maxA[i] - minA[i]) / split
|
||
|
code = "lambda x: "
|
||
|
for j in range(split - 1):
|
||
|
characters = {'min': minA[i], 'reach': reach, 'i': j}
|
||
|
if j == 0:
|
||
|
code += "(((%(min)s - 10000000 <= x < (%(min)s + %(reach)s * (%(i)s+2)))) * (%(i)s+2) or " % characters
|
||
|
elif j == split - 2:
|
||
|
code += "(((%(min)s + %(reach)s * (%(i)s+1)) <= x < (%(min)s + 100000000))) * (%(i)s+2) or 0) - 1" % characters
|
||
|
else:
|
||
|
code += "(((%(min)s + %(reach)s * (%(i)s+1)) <= x < (%(min)s + %(reach)s * (%(i)s+2)))) * (%(i)s+2) or " % characters
|
||
|
functions.append(eval(code))
|
||
|
getBlockKeyFunc = lambda x, y, z: (functions[0](x), functions[1](y), functions[2](z))
|
||
|
|
||
|
indexDict = collections.defaultdict(list)
|
||
|
for x in range(1, split):
|
||
|
reachX = (maxA[0] - minA[0]) / split
|
||
|
for y in range(1, split):
|
||
|
reachY = (maxA[1] - minA[1]) / split
|
||
|
for z in range(1, split):
|
||
|
reachZ = (maxA[2] - minA[2]) / split
|
||
|
# Use a tuple of coordinates instead of MPoint
|
||
|
p = (reachX * x + minA[0], reachY * y + minA[1], reachZ * z + minA[2])
|
||
|
a = indexDict[p]
|
||
|
for xx in range(2):
|
||
|
for yy in range(2):
|
||
|
for zz in range(2):
|
||
|
a.append((x - xx, y - yy, z - zz))
|
||
|
return getBlockKeyFunc, indexDict
|
||
|
|
||
|
def getPoints(self):
|
||
|
posA = om.MPointArray()
|
||
|
self.mesh.getPoints(posA, om.MSpace.kWorld)
|
||
|
return posA
|
||
|
|
||
|
def regist(self):
|
||
|
posA = self.getPoints()
|
||
|
length = posA.length()
|
||
|
self.posA = posA
|
||
|
|
||
|
minXYZ = [100000, 100000, 100000]
|
||
|
maxXYZ = [-100000, -100000, -100000]
|
||
|
for i in range(length):
|
||
|
pos = posA[i]
|
||
|
self.minmax(minXYZ, maxXYZ, pos)
|
||
|
|
||
|
getBlockKeyFunc, indexDict = self.createBlock(minXYZ, maxXYZ)
|
||
|
pointsDict = collections.defaultdict(list)
|
||
|
for i in range(length):
|
||
|
pos = posA[i]
|
||
|
key = getBlockKeyFunc(pos[0], pos[1], pos[2])
|
||
|
pointsDict[key].append((pos, i))
|
||
|
|
||
|
for points, keys in indexDict.items():
|
||
|
if not any(pointsDict[k] for k in keys):
|
||
|
indexDict[points] = False
|
||
|
|
||
|
self.point_boxIndex = indexDict
|
||
|
self.boxIndex_points = pointsDict
|
||
|
|
||
|
def getColosetVertexId(self, point):
|
||
|
minLength = 10000000000
|
||
|
colosetBox = None
|
||
|
|
||
|
# Convert back to MPoint for distance calculation
|
||
|
for k, v in self.point_boxIndex.items():
|
||
|
if v:
|
||
|
k_point = om.MPoint(k[0], k[1], k[2]) # Convert tuple back to MPoint
|
||
|
length = k_point.distanceTo(point)
|
||
|
if length < minLength:
|
||
|
minLength = length
|
||
|
colosetBox = k
|
||
|
|
||
|
if colosetBox is None:
|
||
|
return -1
|
||
|
|
||
|
minLength = 10000000000
|
||
|
result = -1
|
||
|
|
||
|
for k in self.point_boxIndex[colosetBox]:
|
||
|
points = self.boxIndex_points[k]
|
||
|
|
||
|
for _point, idx in points:
|
||
|
length = _point.distanceTo(point)
|
||
|
if length < minLength:
|
||
|
minLength = length
|
||
|
result = idx
|
||
|
|
||
|
return result
|
||
|
|
||
|
class LatticeData(MeshData):
|
||
|
def __init__(self, mfnLattice):
|
||
|
super(LatticeData, self).__init__(mfnLattice)
|
||
|
|
||
|
def getPoints(self):
|
||
|
mfnLattice = self.mesh
|
||
|
divisions = cmds.lattice(mfnLattice.fullPathName(), q=True, divisions=True)
|
||
|
points = om.MPointArray()
|
||
|
for u in range(divisions[2]):
|
||
|
for t in range(divisions[1]):
|
||
|
for s in range(divisions[0]):
|
||
|
trans = cmds.pointPosition(mfnLattice.fullPathName() + '.pt[%s][%s][%s]' % (s, t, u), world=True)
|
||
|
points.append(om.MPoint(trans[0], trans[1], trans[2]))
|
||
|
|
||
|
return points
|
||
|
|
||
|
def progress(**kwargs):
|
||
|
if cmds.about(b=True):
|
||
|
return False
|
||
|
if kwargs.get('maxValue', False) is not False and kwargs['maxValue'] == 0:
|
||
|
return False
|
||
|
cmds.progressWindow(**kwargs)
|
||
|
|
||
|
def isLttice(targetMesh):
|
||
|
try:
|
||
|
oma.MFnLattice(MDagPath(targetMesh))
|
||
|
return True
|
||
|
except:
|
||
|
return False
|
||
|
|
||
|
def applySnapOnClosetVertex(targetMesh, moveVertexList):
|
||
|
if isLttice(targetMesh):
|
||
|
target = oma.MFnLattice(MDagPath(targetMesh))
|
||
|
tgtMesh = LatticeData(target)
|
||
|
else:
|
||
|
target = om.MFnMesh(MDagPath(targetMesh))
|
||
|
tgtMesh = MeshData(target)
|
||
|
tgtPoints = tgtMesh.getPoints()
|
||
|
|
||
|
moveNode = moveVertexList[0].split('.')[0]
|
||
|
if isLttice(moveNode):
|
||
|
compName = 'pt'
|
||
|
movTarget = oma.MFnLattice(MDagPath(moveNode))
|
||
|
movMesh = LatticeData(movTarget)
|
||
|
else:
|
||
|
compName = 'vtx'
|
||
|
movTarget = om.MFnMesh(MDagPath(moveNode))
|
||
|
movMesh = MeshData(movTarget)
|
||
|
movPoints = movMesh.getPoints()
|
||
|
|
||
|
length = movPoints.length()
|
||
|
|
||
|
vtx = cmds.ls(moveVertexList, fl=True)
|
||
|
if compName == 'pt':
|
||
|
divisions = cmds.lattice(moveNode, divisions=True, q=True)
|
||
|
vertexIndex = [
|
||
|
(int(v.split('[')[-3].split(']')[0]),
|
||
|
int(v.split('[')[-2].split(']')[0]),
|
||
|
int(v.split('[')[-1].split(']')[0]))
|
||
|
for v in vtx
|
||
|
]
|
||
|
vertexIndex = tuple((s + divisions[0] * t + (divisions[0] * divisions[1]) * u) for s, t, u in vertexIndex)
|
||
|
else:
|
||
|
vertexIndex = tuple(int(v.split('[')[-1].split(']')[0]) for v in vtx)
|
||
|
|
||
|
progress(title='rsSnapOnClosestVertex', progress=0, maxValue=length, status='', isInterruptable=True)
|
||
|
for i in range(length):
|
||
|
if i in vertexIndex:
|
||
|
if i % 50 == 0:
|
||
|
progress(e=True, progress=i, status=f'{i}/{length}')
|
||
|
num = tgtMesh.getColosetVertexId(movPoints[i])
|
||
|
point = tgtPoints[num]
|
||
|
cmds.move(point.x, point.y, point.z, f'{moveNode}.{compName}[{i}]', a=True, ws=True)
|
||
|
progress(endProgress=1)
|
||
|
|
||
|
def cmd():
|
||
|
try:
|
||
|
companyName = cmds.displayString('rsCompany', q=True, value=True)
|
||
|
if companyName == 'square-enix':
|
||
|
pass
|
||
|
else:
|
||
|
mel.eval('RsDccTpc ("rsSnapOnClosestVertex", "2015/10", "kimutoru@rstool", "cmd", "功能描述","");')
|
||
|
except Exception as e:
|
||
|
print(f"Error: {e}")
|
||
|
|
||
|
cmds.undoInfo(ock=True)
|
||
|
sel = cmds.ls(sl=True)
|
||
|
if len(sel) > 1:
|
||
|
if isLttice(sel[0].split('.')[0]):
|
||
|
vtx = cmds.ls('*.pt[*][*][*]', sl=True, fl=True)
|
||
|
if not vtx:
|
||
|
moveNode = sel[0].split('.')[0]
|
||
|
vtx = cmds.ls(moveNode + '.pt[*]')
|
||
|
else:
|
||
|
vtx = cmds.ls('*.vtx[*]', sl=True, fl=True)
|
||
|
if not vtx:
|
||
|
moveNode = sel[0].split('.')[0]
|
||
|
vtx = cmds.ls(moveNode + '.vtx[*]')
|
||
|
|
||
|
applySnapOnClosetVertex(sel[-1], vtx)
|
||
|
else:
|
||
|
cmds.error('[rsSnapOnClosestVertex] 请至少选择两个对象,一个是目标对象,另一个是要移动的顶点列表。')
|
||
|
cmds.undoInfo(cck=True)
|
||
|
|
||
|
# if __name__ == "__main__":
|
||
|
# cmd()
|