├── hairTools ├── __init__.py └── hairTools.py ├── README.md ├── CHANGELOG └── LICENSE /hairTools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hairTools 2 | ========= 3 | Author: David Bokser 4 | E-Mail: me@davidbokser.com 5 | 6 | Website : http://www.davidbokser.com 7 | ------------------------------------------ 8 | 9 | A set of tools to help in creating and shaping hair curves in Maya 10 | 11 | Usage: 12 | 1. Download the hairTools zip. 13 | 14 | 2. Extract the contents to /Applications/Autodesk/maya2015/plug-ins. 15 | (Note: I'm showing the path of the plug-ins folder for a Mac OS X computer. You may need to locate and change the folder path if on Windows.) 16 | 17 | 3. Check inside your /Applications/Autodesk/maya2015/plug-ins folder. Make sure you have a folder called hairTools inside your plug-ins folder. 18 | 19 | 4. In Maya, open a Python console and enter this code: 20 | import sys 21 | sys.path.append( '/Applications/Autodesk/maya2015/plug-ins') 22 | 23 | 5. Execute the code you just typed in 24 | 25 | 6. Now type this in the Python console: 26 | import hairTools.hairTools as hairTools 27 | hairTools.hairballUI() 28 | 29 | 7. Execute the code you just typed in. You can now start using this nice tool. 30 | 31 | COPYRIGHT DAVID BOKSER 2010-2013. 32 | 33 | ================================================================ 34 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Version 1.5 2 | January 15th, 2012: 3 | - Added new functions 4 | trimFromBeginning(inputCurves, shortestLength) 5 | Cuts the inputCurves from the beginning, with shortestLength dictating the percentage from 6 | 0.0 to 1.0 of how short the shortest hair is. 7 | snapBaseToScalp(curves, scalp, mult=[.7, .4, .1]) 8 | Snaps the first CV on the given curves to the given scalp, with the mult value being 9 | the transition movement of the following 3 CVs 10 | pushCVOutFromScalp(cvs, scalp, pushMult = 1.5) 11 | Pushes given CVs away or toward the given scalp using the pushMult value 12 | pushCurveOutFromScalp(curves, scalp, pushMult = 1.5) 13 | Pushes given curves away or toward the given scalp using the pushMult value 14 | averageCV(amount=1.0) 15 | Similar to average vertex but works on CVs, takes an average of the CVs before and after and 16 | translates the selected CVs accordingly 17 | createInterpolatedCurve(curve1, curve2, v) 18 | Creates an inbetween curve given the 2 curves and a middle value 19 | createRandomInterpolatedCurves(curves, numCurves) 20 | Selects random pairs from the given curves and creates random interpolated curves. The 21 | number of curves that are created is dictated by numCurves, 22 | 23 | Version 1.12 24 | September 9th, 2010: 25 | - Added higher max twist value to 5 26 | 27 | 28 | Version 1.11 29 | September 7th, 2010: 30 | - Fixed bug that wouldn't create intermediate hull curves on negative twist values. 31 | 32 | 33 | Version 1.1 34 | September 6th, 2010: 35 | - Added ability to twist hair 36 | - Fixed a bug that errored out if no hairs were created. Now just gives a warning 37 | 38 | 39 | Version 1.02 40 | August 1st, 2010: 41 | - Added function to create single curve down the center of the geometry. 42 | 43 | 44 | Version 1.0 45 | July 14th, 2010: 46 | - Initial release 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | =============================================================================== 4 | 5 | hairTools is released under a BSD style license 6 | 7 | Copyright (c) 2013, David Bokser 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without modification, are 11 | permitted provided that the following conditions are met: 12 | 13 | * Redistributions of source code must retain the above copyright notice, 14 | this list of conditions and the following disclaimer. 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | * Neither the name of the David Bokser nor the names of its contributors may be used 19 | to endorse or promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY DAVID BOKSER "AS IS" AND ANY EXPRESS OR IMPLIED 23 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 24 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID BOKSER 25 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 28 | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 30 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | =============================================================================== -------------------------------------------------------------------------------- /hairTools/hairTools.py: -------------------------------------------------------------------------------- 1 | """ 2 | ------------------------------------------ 3 | hairTools.py 4 | Author: David Bokser 5 | email: me@davidbokser.com 6 | 7 | Website : http://www.davidbokser.com 8 | ------------------------------------------ 9 | 10 | Helps create multi-level hair curves along a poly mesh, with tools 11 | for styling and trimming. 12 | 13 | Usage: 14 | import hairTools.hairTools as hairTools 15 | hairTools.hairballUI() 16 | 17 | COPYRIGHT DAVID BOKSER 2010-2013. 18 | ================================================================ 19 | """ 20 | 21 | _version = '1.5' 22 | 23 | import maya.cmds as mc 24 | import maya.mel as mel 25 | import copy 26 | import random 27 | 28 | def hairballUI(): 29 | window = mc.window( title="Hairball v%s" % _version, iconName='hairball', widthHeight=(200, 55) ) 30 | 31 | mc.scrollLayout( 'scrollLayout' ) 32 | mc.columnLayout( adjustableColumn=True ) 33 | mc.button(label='Reload', command=''' 34 | import maya.cmds as mc 35 | import hairTools.hairTools as hairTools 36 | mc.deleteUI( "%s", window=True) 37 | hairTools.hairballUI() 38 | ''' % window) 39 | mc.frameLayout( label='Grow', labelAlign='center', borderStyle='etchedIn' ) 40 | mc.columnLayout( adjustableColumn=True ) 41 | densityField = mc.floatSliderGrp( label='Density', cw3=[80, 80, 120], precision=2, field=True, value = .4, fs=.01, minValue=.01, maxValue=1.0, fmx=200.0 ) 42 | layerField = mc.intSliderGrp( label='Layers', cw3=[80, 80, 120], field=True, value = 5, minValue=1, maxValue=15, fmx=200 ) 43 | twistField = mc.floatSliderGrp( label='Twist', cw3=[80, 80, 120], precision=2, field=True, value = 0, fs=.01, minValue=-1.0, maxValue=1.0, fmx=5.0, fmn=-5.0 ) 44 | mc.button( label='Cough it up!', command=''' 45 | import maya.cmds as mc 46 | import hairTools.hairTools as hairTools 47 | hairTools.makeHair(mc.ls(sl=True, fl=True), mc.floatSliderGrp("%s", q=True, value=True), mc.intSliderGrp("%s", q=True, v=True), mc.floatSliderGrp("%s", q=True, value=True)) 48 | ''' % (densityField, layerField, twistField)) 49 | mc.setParent( '..' ) 50 | mc.setParent( '..' ) 51 | 52 | mc.frameLayout( label='Groom', labelAlign='center', borderStyle='etchedIn' ) 53 | mc.columnLayout( adjustableColumn = True) 54 | rand1Field = mc.floatSliderGrp( label='Start', cw3=[80, 80, 120], precision=2, field=True, value = .1, fs=.1, minValue=0, maxValue=5.0, fmx=200.0 ) 55 | rand2Field = mc.floatSliderGrp( label='Middle', cw3=[80, 80, 120], precision=2, field=True, value = .4, fs=.1, minValue=0, maxValue=5.0, fmx=200.0 ) 56 | rand3Field = mc.floatSliderGrp( label='End', cw3=[80, 80, 120], precision=2, field=True, value = .6, fs=.1, minValue=0, maxValue=5.0, fmx=200.0 ) 57 | mc.button( label='Muss it up.', command=''' 58 | import maya.cmds as mc 59 | import hairTools.hairTools as hairTools 60 | hairTools.randomizeHair(mc.ls(sl=True, fl=True), [mc.floatSliderGrp("%s", q=True, value=True), mc.floatSliderGrp("%s", q=True, v=True), mc.floatSliderGrp("%s", q=True, v=True)]) 61 | ''' % (rand1Field, rand2Field, rand3Field)) 62 | mc.setParent( '..' ) 63 | mc.setParent( '..' ) 64 | 65 | mc.frameLayout( label='Trim', labelAlign='center', borderStyle='etchedIn' ) 66 | mc.columnLayout( adjustableColumn = True) 67 | minTrimField = mc.floatSliderGrp( label='Min Length', cw3=[80, 80, 120], precision=2, field=True, value = .3, fs=.1, minValue=0.1, maxValue=1.0 ) 68 | percentTrimField = mc.floatSliderGrp( label='Percent to trim', cw3=[80, 80, 120], precision=2, field=True, value = .5, fs=.1, minValue=0.1, maxValue=1.0 ) 69 | mc.button( label='A little off the top.', command=''' 70 | import maya.cmds as mc 71 | import hairTools.hairTools as hairTools 72 | hairTools.trimHair(mc.ls(sl=True), mc.floatSliderGrp("%s", q=True, value=True), mc.floatSliderGrp("%s", q=True, value=True)) 73 | ''' % (minTrimField, percentTrimField)) 74 | mc.setParent( '..' ) 75 | mc.setParent( '..' ) 76 | 77 | mc.showWindow( window ) 78 | 79 | mc.window(window, e=True, w=310, h=400) 80 | 81 | def makeHair(firstLoop, density, layers, twist=0.0): 82 | firstLoop = mc.ls(mc.polyListComponentConversion(firstLoop, fe=True, fv=True, tv=True), fl=True) 83 | 84 | # DO A LITTLE ERROR CHECKING TO SEE IF WE GOT WHAT WE NEED 85 | neighbor = getNeighboringEdgeloops(firstLoop) 86 | if len(neighbor) != len(firstLoop): 87 | mel.eval('warning "Selected edgeloop is not a border loop. Please select a border loop and try again."') 88 | return None 89 | 90 | # CREATE THE HULL CURVES 91 | if twist < 0: 92 | numIntermediates = round((twist*-1)/.1)-1 93 | else: 94 | numIntermediates = round(twist/.1)-1 95 | if numIntermediates < 0: 96 | numIntermediates = 0 97 | hullCurves = makeHullCurves(firstLoop, numIntermediates) 98 | 99 | twist /= numIntermediates + 1.0 100 | 101 | objName = firstLoop[0].split('.')[0] 102 | 103 | # CREATE ALL THE HAIR CURVES 104 | allHairCurves = [] 105 | for i in range(layers): 106 | for curve in hullCurves: 107 | s = (i+1)/(layers*1.0) 108 | mc.setAttr(curve+'.scale', s, s, s, type='double3') 109 | allHairCurves += makeHairCurves(hullCurves, density, twist) 110 | 111 | # DO SOME SPRING CLEANING 112 | mc.delete(hullCurves) 113 | for i in range(len(allHairCurves)): 114 | curveNum = str(i+1) 115 | allHairCurves[i] = mc.rename(allHairCurves[i], '%s_%sCRV' % (objName, curveNum)) 116 | 117 | if len(allHairCurves) > 0: 118 | hairGrp = mc.rename(mc.group(allHairCurves), objName + '_hairCurves') 119 | else: 120 | mel.eval('warning "No hair curves made. Perhaps Density value is too high."') 121 | 122 | def makeHullCurves(firstLoop, numIntermediates=0): 123 | verts = mc.ls(mc.polyListComponentConversion(firstLoop, fe=True, fv=True, tv=True), fl=True) 124 | 125 | edgeVerts = orderEdgeloopVerts(verts) 126 | firstVert = edgeVerts[0] 127 | dirVert = edgeVerts[1] 128 | 129 | obj = verts[0].split('.')[0] 130 | numObjVerts = mc.polyEvaluate(obj, vertex=True) 131 | 132 | numEdges = numObjVerts / len(edgeVerts) 133 | 134 | if numObjVerts%len(edgeVerts) != 0: 135 | mel.eval('warning("Number of verts in edge loops must be the same throughout mesh.")') 136 | return False 137 | 138 | usedVerts = [] 139 | edgeCurves = [] 140 | edgeLoops = [] 141 | usedVertOrder = [] 142 | for i in range(numEdges): 143 | # MAKE CURVES 144 | currentEdge = makeCurveFromVerts(edgeVerts)[0] 145 | if i != 0 and numIntermediates != 0: 146 | edgeCurves += makeIntermediateCurves(edgeCurves[-1], currentEdge, numIntermediates) 147 | edgeCurves.append(currentEdge) 148 | 149 | # KEEP TRACK OF USED VERTS 150 | for vert in edgeVerts: 151 | if vert not in usedVerts: 152 | usedVerts.append(vert) 153 | usedVertOrder.append(copy.copy(usedVerts)) 154 | 155 | # GET NEXT LOOP 156 | edgeLoops.append(edgeVerts) 157 | neighbors = mc.ls(getNeighboringEdgeloops(edgeVerts), fl=True) 158 | edgeVerts = [] 159 | for vert in neighbors: 160 | if vert not in usedVerts: 161 | edgeVerts.append(vert) 162 | if len(edgeVerts): 163 | firstVert = findCorrespondingVertInLoop(firstVert, edgeVerts) 164 | dirVert = findCorrespondingVertInLoop(dirVert, edgeVerts) 165 | edgeVerts = orderEdgeloopVerts(edgeVerts, start=firstVert, direction=dirVert) 166 | 167 | return edgeCurves 168 | 169 | def makeIntermediateCurves(curve1, curve2, numIntermediates=1, close=True): 170 | cShape1 = mc.listRelatives(curve1, shapes=True)[0] 171 | cShape2 = mc.listRelatives(curve2, shapes=True)[0] 172 | 173 | numCV1 = mc.getAttr(cShape1+'.spans') + mc.getAttr(cShape1+'.degree') 174 | numCV2 = mc.getAttr(cShape2+'.spans') + mc.getAttr(cShape2+'.degree') 175 | 176 | if numCV1 != numCV2: 177 | mel.eval('warning "Number of CVs between curves are not equal. Can\'t create intermediate curves"') 178 | return [] 179 | 180 | step = 1.0/(numIntermediates+1) 181 | allCurves = [] 182 | for p in range(1, int(numIntermediates)+1): 183 | points = [] 184 | for i in range(mc.getAttr(cShape1+'.spans')): 185 | p1 = mc.pointPosition('%s.cv[%i]' % (curve1,i)) 186 | p2 = mc.pointPosition('%s.cv[%i]' % (curve2,i)) 187 | v = (p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]) 188 | p3 = (p1[0]+(v[0]*step*p), p1[1]+(v[1]*step*p), p1[2]+(v[2]*step*p)) 189 | points.append(p3) 190 | allCurves += makeCurveFromPoints(points, close) 191 | allCurves[-1] = mc.rename(allCurves[-1], 'intCurve1') 192 | return allCurves 193 | 194 | def orderEdgeloopVerts(verts, start=None, direction=None): 195 | ''' 196 | Orders a list of verts in an edge loop. 197 | Assumes the verts are actually in an edge loop, 198 | otherwise will freeze Maya, so be WARNED!! 199 | ''' 200 | allEdgeVerts = copy.copy(verts) 201 | 202 | orderedVerts = [] 203 | if not start: 204 | start = verts.pop(0) 205 | else: 206 | if start in verts: 207 | verts.remove(start) 208 | else: 209 | mel.eval('warning("given start vert is not in edge verts, using default")') 210 | start = verts.pop(0) 211 | if direction and direction in verts: 212 | verts.remove(direction) 213 | else: 214 | adjacentVerts = mc.ls(mc.polyListComponentConversion(mc.polyListComponentConversion(start, fv=True, te=True), fe=True, tv=True),fl=True) 215 | for vert in adjacentVerts: 216 | if vert in verts: 217 | direction = vert 218 | verts.remove(direction) 219 | 220 | orderedVerts.append(start) 221 | orderedVerts.append(direction) 222 | 223 | while len(verts) > 1: 224 | adjacentVerts = mc.ls(mc.polyListComponentConversion(mc.polyListComponentConversion(orderedVerts[-1], fv=True, te=True), fe=True, tv=True), fl=True) 225 | for vert in adjacentVerts: 226 | if vert in verts: 227 | orderedVerts.append(vert) 228 | verts.remove(orderedVerts[-1]) 229 | 230 | orderedVerts.append(verts[0]) 231 | return orderedVerts 232 | 233 | def makeCurveFromVerts(verts, close=True): 234 | p = [] 235 | for vert in verts: 236 | p.append(mc.pointPosition(vert)) 237 | 238 | return makeCurveFromPoints(p, close) 239 | 240 | def makeCurveFromPoints(p, close=True): 241 | curve = mc.curve(p=p, d=3) 242 | if close: 243 | curve = mc.closeCurve(curve, ps=0, rpo=1, bb=0.5, bki=0, p=0.1) 244 | curve = mc.rebuildCurve(curve, rpo=1, rt=0, end=1, kr=0, kcp=1, kep=1, kt=0, s=4, d=3, tol=0.000129167) 245 | 246 | mc.xform(curve, centerPivots=True) 247 | 248 | return curve 249 | 250 | def getNeighboringEdgeloops(edgeLoop): 251 | ''' 252 | Get the neighboring edge loop. 253 | Takes in and returns verts, not edges 254 | ''' 255 | expandedVerts = mc.ls(mc.polyListComponentConversion(mc.polyListComponentConversion(edgeLoop, fv=True, te=True), fe=True, tv=True), fl=True) 256 | expandedEdgeVerts = mc.ls(edgeLoop, fl=True) 257 | 258 | for vert in expandedEdgeVerts: 259 | if vert in expandedVerts: 260 | expandedVerts.remove(vert) 261 | 262 | return mc.ls(expandedVerts, fl=True) 263 | 264 | def findCorrespondingVertInLoop(vert, edgeLoop): 265 | ''' 266 | Finds a vert on the edgeLoop whose edge is shared with the given vert 267 | ''' 268 | nearestVerts = mc.ls(mc.polyListComponentConversion(mc.polyListComponentConversion(vert, fv=True, te=True), fe=True, tv=True), fl=True) 269 | for vert in nearestVerts: 270 | if vert in edgeLoop: 271 | return vert 272 | 273 | return None 274 | 275 | def makeHairCurves(hullCurves, d, twist = 0.0): 276 | ''' 277 | Populate a hull with hair curves based on arclen of the biggest curve 278 | ''' 279 | largestArclen = 0 280 | for curve in hullCurves: 281 | arclen = mc.arclen(curve) 282 | if arclen > largestArclen: 283 | largestArclen = arclen 284 | 285 | numCurves = largestArclen / (d * 1.0) 286 | 287 | allCurves = [] 288 | for i in range(int(numCurves)): 289 | allCurves.append(makeHairCurve(hullCurves, i/numCurves, twist)) 290 | 291 | return allCurves 292 | 293 | def makeHairCurve(hullCurves, u, twist=0.0): 294 | ''' 295 | Create a curve through a series of hull curves by u parameter 296 | ''' 297 | p = [] 298 | i = 0 299 | for hull in hullCurves: 300 | p.append(mc.pointPosition('%s.u[%f]' % (hull, (u+(twist*i))%1.0 ) )) 301 | i+=1 302 | 303 | curve = mc.curve(p=p, d=3) 304 | curve = mc.rebuildCurve(curve, rpo=1, rt=0, end=1, kr=0, kcp=1, kep=1, kt=0, s=4, d=3, tol=0.000129167) 305 | 306 | mc.xform(curve, centerPivots=True) 307 | 308 | return curve 309 | 310 | def randomizeHair(curves, rMult = []): 311 | ''' 312 | random.randomizes the cvs on a set of selected curves. 313 | Takes in an array that will be multiplied against the random.random value 314 | so that the user has more control of random.randomization along the curve. 315 | ''' 316 | 317 | # FIND THE MAX NUMBER OF CVS 318 | longestCVCount = 0 319 | for curve in curves: 320 | numCV = mc.getAttr( curve+'.degree' ) + mc.getAttr( curve+'.spans' ) 321 | if numCV > longestCVCount: 322 | longestCVCount = numCV 323 | 324 | # GET MULT MODIFIER VALUES FOR EACH CV 325 | numMult = len(rMult)-1 326 | numCVSplit = longestCVCount / numMult 327 | cvMult = [] 328 | for i in range(longestCVCount): 329 | p = i/numCVSplit 330 | m = (i%numCVSplit)/(numCVSplit*1.0) 331 | try: 332 | dif = rMult[p+1] - rMult[p] 333 | except: 334 | dif = rMult[p] 335 | cvMult.append( (m*dif)+rMult[p] ) 336 | 337 | for curve in curves: 338 | numCV = mc.getAttr( curve+'.degree' ) + mc.getAttr( curve+'.spans' ) 339 | for i in range(numCV): 340 | rx = cvMult[i] * (random.random() - .5) 341 | ry = cvMult[i] * (random.random() - .5) 342 | rz = cvMult[i] * (random.random() - .5) 343 | mc.move(rx, ry, rz, '%s.cv[%i]' % (curve, i), r=True) 344 | 345 | def trimHair(curves, min, percent): 346 | ''' 347 | random.randomly trim hair curves for more variation in length 348 | ''' 349 | percentOfCurves = int(len(curves) * percent) 350 | for i in range(percentOfCurves): 351 | activeCurve = curves.pop(int(random.random()*len(curves))) 352 | r = (random.random() * (1.0 - min)) + min 353 | mc.delete(mc.detachCurve('%s.u[%f]' % (activeCurve, r), ch=False, cos=True, rpo=True)[0]) 354 | 355 | def createCenterCurve(): 356 | firstLoop = mc.ls(mc.polyListComponentConversion(fe=True, fv=True, tv=True), fl=True) 357 | 358 | # DO A LITTLE ERROR CHECKING TO SEE IF WE GOT WHAT WE NEED 359 | neighbor = getNeighboringEdgeloops(firstLoop) 360 | if len(neighbor) != len(firstLoop): 361 | mel.eval('warning "Selected edgeloop is not a border loop. Please select a border loop and try again."') 362 | return None 363 | 364 | # CREATE THE HULL CURVEs 365 | hullCurves = makeHullCurves(firstLoop) 366 | 367 | objName = firstLoop[0].split('.')[0] 368 | 369 | # CREATE ALL THE HAIR CURVES 370 | for curve in hullCurves: 371 | s = 0 372 | mc.setAttr(curve+'.scale', s, s, s, type='double3') 373 | hairCurve = makeHairCurve(hullCurves, .5) 374 | 375 | # DO SOME SPRING CLEANING 376 | mc.delete(hullCurves) 377 | hairCurve = mc.rename(hairCurve, '%s_CenterCRV' % objName) 378 | 379 | return hairCurve 380 | 381 | def trimFromBeginning(inputCurves, shortestLength): 382 | newCurves = [] 383 | for obj in inputCurves: 384 | parent = mc.listRelatives(obj, parent=True) 385 | r = random.random()*(1-shortestLength) 386 | obj = mc.rebuildCurve(obj, ch=0, rpo=1, rt=0, end=1, kr=0, kcp=1, kep=1, kt=0, s = 10, d = 3, tol = 0)[0] 387 | curves = mc.detachCurve( '%s.u[%f]' % (obj, r), ch=0, cos=True, rpo=1 ) 388 | mc.delete(curves[-1]) 389 | mc.rebuildCurve(curves[0], ch=1, rpo=1, rt=0, end=1, kr=0, kcp=0, kep=1, kt= 0, s = 0, d = 3, tol = 0) 390 | curves[0] = mc.rename(curves[0], obj) 391 | if parent: 392 | curves[0] = mc.parent(curves[0], parent)[0] 393 | 394 | newCurves.append(curves[0]) 395 | 396 | return newCurves 397 | 398 | def snapBaseToScalp(curves, scalp, mult=[.7, .4, .1]): 399 | import cgm.lib.distance as bbDistanceLib 400 | 401 | for obj in curves: 402 | currentPos = mc.pointPosition(obj+'.cv[0]') 403 | newPos = bbDistanceLib.returnClosestPointOnMeshInfoFromPos(currentPos, scalp)['position'] 404 | relPos = [newPos[0]-currentPos[0], newPos[1]-currentPos[1], newPos[2]-currentPos[2]] 405 | mc.move(newPos[0], newPos[1], newPos[2], obj+'.cv[0]', a=True) 406 | mc.move(relPos[0]*mult[0], relPos[1]*mult[0], relPos[2]*mult[0], obj+'.cv[1]', r=True) 407 | mc.move(relPos[0]*mult[1], relPos[1]*mult[1], relPos[2]*mult[1], obj+'.cv[2]', r=True) 408 | mc.move(relPos[0]*mult[2], relPos[1]*mult[2], relPos[2]*mult[2], obj+'.cv[3]', r=True) 409 | 410 | def pushCVOutFromScalp(cvs, scalp, pushMult = 1.5): 411 | import cgm.lib.distance as bbDistanceLib 412 | sel = mc.ls(sl=True) 413 | for obj in cvs: 414 | currentPos = mc.pointPosition(obj) 415 | newPos = bbDistanceLib.returnClosestPointOnMeshInfoFromPos(currentPos, scalp)['position'] 416 | relPos = [newPos[0]-currentPos[0], newPos[1]-currentPos[1], newPos[2]-currentPos[2]] 417 | mc.move(relPos[0]*pushMult, relPos[1]*pushMult, relPos[2]*pushMult, obj, r=True) 418 | mc.select(sel) 419 | 420 | def pushCurveOutFromScalp(curves, scalp, pushMult = 1.5): 421 | import cgm.lib.distance as bbDistanceLib 422 | sel = mc.ls(sl=True) 423 | 424 | for obj in curves: 425 | for shape in mc.listRelatives(obj,shapes=True,fullPath=True): 426 | cvList = (mc.ls([shape+'.cv[*]'],flatten=True)) 427 | 428 | for cv in cvList: 429 | currentPos = mc.pointPosition(cv) 430 | newPos = bbDistanceLib.returnClosestPointOnMeshInfoFromPos(currentPos, scalp)['position'] 431 | relPos = [newPos[0]-currentPos[0], newPos[1]-currentPos[1], newPos[2]-currentPos[2]] 432 | mc.move(relPos[0]*pushMult, relPos[1]*pushMult, relPos[2]*pushMult, cv, r=True) 433 | 434 | mc.select(sel) 435 | 436 | def averageCV(amount=1.0): 437 | for cv in mc.ls(sl=True,fl=True): 438 | num = int(cv.split('.cv[')[-1].split(']')[0]) 439 | baseObj = cv.split('.')[0] 440 | pos1 = mc.pointPosition('%s.cv[%i]' % (baseObj, num+1)) 441 | pos2 = mc.pointPosition('%s.cv[%i]' % (baseObj, num-1)) 442 | pos3 = mc.pointPosition('%s.cv[%i]' % (baseObj, num)) 443 | average = [(pos1[0]+pos2[0]+pos3[0])/3, (pos1[1]+pos2[1]+pos3[1])/3, (pos1[2]+pos2[2]+pos3[2])/3] 444 | relAvg = [average[0]-pos3[0], average[1]-pos3[1], average[2]-pos3[2]] 445 | mc.move(relAvg[0]*amount, relAvg[1]*amount, relAvg[2]*amount, cv, r=True) 446 | 447 | def createInterpolatedCurve(curve1, curve2, v): 448 | interpolatedCurve = mc.duplicate(curve1, rr=True, rc=True)[0] 449 | 450 | for shape in mc.listRelatives(curve2,shapes=True,fullPath=True): 451 | cvList = (mc.ls([shape+'.cv[*]'],flatten=True)) 452 | 453 | mc.rebuildCurve(interpolatedCurve, ch=0, rpo=1, rt= 0, end = 1, kr = 0, kcp = 0, kep = 1, kt = 0, s = len(cvList)-3, d = 3, tol = 0) 454 | for i in range(len(cvList)): 455 | pos1 = mc.pointPosition('%s.cv[%i]' % (interpolatedCurve,i)) 456 | pos2 = mc.pointPosition('%s.cv[%i]' % (curve2,i)) 457 | newPos = ((pos2[0]-pos1[0])*v+pos1[0], (pos2[1]-pos1[1])*v+pos1[1], (pos2[2]-pos1[2])*v+pos1[2]) 458 | mc.move(newPos[0], newPos[1], newPos[2], '%s.cv[%i]' % (interpolatedCurve,i), a=True) 459 | 460 | return interpolatedCurve 461 | 462 | def createRandomInterpolatedCurves(curves, numCurves): 463 | newCurves = [] 464 | for i in range(numCurves): 465 | curve1, curve2 = random.sample(curves,2) 466 | newCurve = createInterpolatedCurve(curve1, curve2, random.uniform(.3, .7)) 467 | newCurves.append(newCurve) 468 | 469 | return newCurves 470 | 471 | ''' 472 | import hairTools.hairTools as hairTools 473 | hairTools.trimFromBeginning(mc.ls(sl=True), .2) 474 | hairTools.createRandomInterpolatedCurves(mc.ls(sl=True), len(mc.ls(sl=True))*3) 475 | hairTools.snapBaseToScalp(mc.ls(sl=True), scalp) 476 | ''' --------------------------------------------------------------------------------