├── example.png ├── README.md └── DeformationTransfer.py /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwanao731/DeformationTransferForMaya/HEAD/example.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deformation Transfer for Maya 2 | 3 | This is a plugin to do "Deformation Transfer" on Maya. 4 | 5 | ![](example.png) 6 | 7 | ## Requirement 8 | You need to install numpy and scipy for your Maya environemnt. 9 | 10 | ## Reference 11 | 12 | Deformation Transfer for Triangle Meshes, SIGGRAPH 2004 [Paper](http://people.csail.mit.edu/sumner/research/deftransfer/) 13 | 14 | # Contact 15 | Naoya Iwamoto 16 | iwanao731@gmail.com 17 | -------------------------------------------------------------------------------- /DeformationTransfer.py: -------------------------------------------------------------------------------- 1 | # 2 | """ 3 | DeformationTransfer 4 | "Deformation Transfer" for Maya 2018 5 | 6 | Reference: 7 | - Deformation Transfer, SIGGRAPH 2005 8 | 9 | """ 10 | 11 | __author__ = "Naoya Iwamoto " 12 | __status__ = "beta release" 13 | __version_ = "0.1" 14 | __date__ = "15 Feb 2019" 15 | 16 | import sys 17 | import maya.api.OpenMaya as om 18 | import maya.cmds as cmds 19 | import maya.mel 20 | import numpy as np 21 | import scipy.linalg as linalg 22 | 23 | def maya_useNewAPI(): 24 | pass 25 | 26 | def initializePlugin(plugin): 27 | fnPlugin = om.MFnPlugin(plugin, vendor = 'Euclid Lab.', version = '0.1') 28 | try: 29 | fnPlugin.registerCommand(DeformationTransferCmd.commandName, DeformationTransferCmd.creator) 30 | except: 31 | sys.stderr.write('Failed to register command: {0}\n'.format(DeformationTransferCmd.commandName)) 32 | raise 33 | 34 | cmds.setParent('MayaWindow') 35 | if not cmds.menu('EuclidLab', query=True, exists=True): 36 | cmds.menu('EuclidLab', label='EuclidLab') 37 | cmds.setParent('EuclidLab', menu=True) 38 | if not cmds.menu('Deformation Transfer', query=True, exists=True): 39 | cmds.menuItem('Deformation Transfer', label='Deformation Transfer', tearOff=True, command='import maya.mel;maya.mel.eval("DeformationTransferBuild")') 40 | 41 | def uninitializePlugin(plugin): 42 | fnPlugin = om.MFnPlugin(plugin) 43 | try: 44 | fnPlugin.deregisterCommand(DeformationTransferCmd.commandName) 45 | except: 46 | sys.stderr.write('Failed to unregister command: {0}\n'.format(DeformationTransferCmd.commandName)) 47 | raise 48 | cmds.deleteUI('MayaWindow|EuclidLab|Deformation Transfer', menuItem=True) 49 | 50 | class DeformationTransferCmd(om.MPxCommand): 51 | commandName = 'DeformationTransferBuild' 52 | 53 | def __init__(self): 54 | om.MPxCommand.__init__(self) 55 | 56 | @staticmethod 57 | def creator(): 58 | return DeformationTransferCmd() 59 | 60 | def doIt(self, args): 61 | print '---------------------' 62 | print 'Deformation Transfer' 63 | print '---------------------' 64 | DeformationTransfer() 65 | 66 | def getSelectMesh(): 67 | slist = om.MGlobal.getActiveSelectionList() 68 | itsl = om.MItSelectionList(slist) 69 | meshPaths = [] 70 | while not itsl.isDone(): 71 | dagPath = itsl.getDagPath() 72 | itsl.next() 73 | if dagPath is None: 74 | continue 75 | apiType = dagPath.apiType() 76 | if apiType != om.MFn.kTransform: 77 | continue 78 | for c in xrange(dagPath.childCount()): 79 | child = dagPath.child(c) 80 | if child.apiType() != om.MFn.kMesh: 81 | continue 82 | path = dagPath.getAPathTo(child) 83 | mesh = om.MFnMesh(path) 84 | if not mesh.findPlug('intermediateObject', True).asBool(): 85 | meshPaths.append(path) 86 | break 87 | return meshPaths 88 | 89 | def setMatrixCol(mat, vec, iCol): 90 | mat[0][iCol] = vec[0] 91 | mat[1][iCol] = vec[1] 92 | mat[2][iCol] = vec[2] 93 | 94 | def computeMatrixA(meshPath): 95 | mesh = om.MFnMesh(meshPath) 96 | #numVertices, vertexList = mesh.getVertices() 97 | 98 | #print "print numVertices : ", mesh.numVertices 99 | 100 | # get triangle information 101 | ___, indices = mesh.getTriangles() 102 | 103 | neighbor = [] 104 | offset = len(neighbor) 105 | neighbor = neighbor + [set() for v in xrange(mesh.numVertices)] 106 | 107 | MatA = np.zeros((len(indices), mesh.numVertices)) 108 | 109 | for triID in xrange(len(indices) / 3): 110 | i0 = indices[triID * 3 + 0] + offset 111 | i1 = indices[triID * 3 + 1] + offset 112 | i2 = indices[triID * 3 + 2] + offset 113 | 114 | e0 = mesh.getPoint(i1) - mesh.getPoint(i0); 115 | e1 = mesh.getPoint(i2) - mesh.getPoint(i0); 116 | 117 | # Construct Va 118 | Va = np.zeros((3, 2)); 119 | setMatrixCol(Va, e0, 0); 120 | setMatrixCol(Va, e1, 1); 121 | 122 | # QR Decomposition (TBD) 123 | Q, R = np.linalg.qr(Va) 124 | 125 | invRQT = np.dot(np.linalg.inv(R), Q.transpose()) 126 | 127 | # i0 128 | MatA[triID * 3 + 0][i0] = - invRQT[0][0] - invRQT[1][0] 129 | MatA[triID * 3 + 1][i0] = - invRQT[0][1] - invRQT[1][1] 130 | MatA[triID * 3 + 2][i0] = - invRQT[0][2] - invRQT[1][2] 131 | 132 | # i1 133 | MatA[triID * 3 + 0][i1] = invRQT[0][0] 134 | MatA[triID * 3 + 1][i1] = invRQT[0][1] 135 | MatA[triID * 3 + 2][i1] = invRQT[0][2] 136 | 137 | # i2 138 | MatA[triID * 3 + 0][i2] = invRQT[1][0] 139 | MatA[triID * 3 + 1][i2] = invRQT[1][1] 140 | MatA[triID * 3 + 2][i2] = invRQT[1][2] 141 | 142 | return MatA 143 | 144 | def calcNormal(v1, v2, v3): 145 | return np.cross((v2-v1),(v3-v1)) 146 | 147 | def setTriEdgeMatrix(mesh, i1, i2, i3): 148 | 149 | matV = np.zeros((3,3)) 150 | 151 | v1 = mesh.getPoint(i1); 152 | v2 = mesh.getPoint(i2); 153 | v3 = mesh.getPoint(i3); 154 | 155 | e1 = v2 - v1; 156 | e2 = v3 - v1; 157 | e3 = calcNormal(v1, v2, v3); 158 | 159 | setMatrixCol(matV, e1, 0); 160 | setMatrixCol(matV, e2, 1); 161 | setMatrixCol(matV, e3, 2); 162 | 163 | return matV 164 | 165 | def setMatrixBlock(matF, matBlock, irow, icol): 166 | 167 | rows, cols = matBlock.shape 168 | 169 | for r in range(rows): 170 | for c in range(cols): 171 | matF[irow+r][icol+c] = matBlock[r][c] 172 | 173 | def computeMatrixF(srcRefMeshPath, srcDefMeshPath): 174 | 175 | src_ref_mesh = om.MFnMesh(srcRefMeshPath) 176 | src_def_mesh = om.MFnMesh(srcDefMeshPath) 177 | 178 | #numVertices, src_vertexList = src_ref_mesh.getVertices() 179 | 180 | # get triangle information 181 | ___, indices = src_ref_mesh.getTriangles() 182 | 183 | numTriangle = len(indices)/3 184 | 185 | # loop triangle 186 | neighbor = [] 187 | offset = len(neighbor) 188 | neighbor = neighbor + [set() for v in xrange(src_ref_mesh.numVertices)] 189 | 190 | # set MatF 191 | matF = np.zeros((numTriangle*3, 3)) 192 | 193 | for triID in xrange(numTriangle): 194 | 195 | i0 = indices[triID * 3 + 0] + offset 196 | i1 = indices[triID * 3 + 1] + offset 197 | i2 = indices[triID * 3 + 2] + offset 198 | 199 | # 3 x 3 matrix 200 | Va = setTriEdgeMatrix(src_ref_mesh, i0, i1, i2) 201 | Vb = setTriEdgeMatrix(src_def_mesh, i0, i1, i2) 202 | 203 | # QR Decomposition 204 | Q, R = np.linalg.qr(Va) 205 | 206 | invRQT = np.dot(np.linalg.inv(R), Q.transpose()) 207 | 208 | Sa = np.dot(Vb, invRQT) 209 | SaT = Sa.transpose() 210 | 211 | setMatrixBlock(matF, SaT, triID*3, 0) 212 | 213 | return matF 214 | 215 | def DeformationTransfer(): 216 | 217 | meshPaths = getSelectMesh() 218 | 219 | src_ref_mesh_path = meshPaths[0] 220 | src_def_mesh_path = meshPaths[1] 221 | trg_ref_mesh_path = meshPaths[2] 222 | trg_def_mesh_path = meshPaths[3] 223 | 224 | # set target reference model 225 | print "analysis target reference model" 226 | MatA = computeMatrixA(trg_ref_mesh_path) 227 | print "MatA : ", MatA.shape 228 | 229 | MatAt = MatA.transpose() 230 | print "MatAt : ", MatAt.shape 231 | 232 | LU = linalg.lu_factor(np.dot(MatAt, MatA)) # LU decompose 233 | 234 | # transfer to target model 235 | print "analysis transfer of source ref and def" 236 | matF = computeMatrixF(src_ref_mesh_path, src_def_mesh_path) # deformation gradient for source model 237 | 238 | print "matF : ", matF.shape 239 | 240 | # solve deformation transfer 241 | UtS = np.dot(MatAt, matF) 242 | 243 | print "UtS : ", UtS.shape 244 | 245 | trg_def_x = linalg.lu_solve(LU, UtS) 246 | 247 | print "trg_def_x : ", trg_def_x.shape 248 | 249 | # set result to target def model 250 | trg_def_mesh = om.MFnMesh(trg_def_mesh_path) 251 | for i in xrange(trg_def_mesh.numVertices): 252 | pos = trg_def_mesh.getPoint(i) 253 | pos[0] = trg_def_x[i][0] 254 | pos[1] = trg_def_x[i][1] 255 | pos[2] = trg_def_x[i][2] 256 | trg_def_mesh.setPoint(i, pos) 257 | 258 | print "done" 259 | 260 | --------------------------------------------------------------------------------