Skip to content

Instantly share code, notes, and snippets.

hdlx/RootsAndIvy.py

Last active February 28, 2024 15:30
Show Gist options
• Save hdlx/5ff8d58c8f8e1d22945cfee3475817c4 to your computer and use it in GitHub Desktop.
This file contains 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
 #F. Hiba, C. Mendes, B. Lefebvre, P. Hubert import maya.api.OpenMaya as om import maya.cmds as cmds from math import radians, degrees, sin, cos from random import gauss, random, uniform, shuffle from copy import copy #4 utilities function related to transformations #give the quaternion corresponding to the rottion from vector 1 to vector 2 def getQuaternion (vector1=om.MVector (0,0,0), vector2=om.MVector (0,0,0)): dot = vector1^vector2 #get axis dot = dot.normalize() # normalize axis angle = vector1.angle(vector2) #get angle (in rad) quaternion = om.MQuaternion (dot[0]*sin(angle/2),dot[1]*sin(angle/2),dot[2]*sin(angle/2),cos(angle/2)) #convert. to quaternion return quaternion #convert axis + angle to quaternion def toQuaternion (axe=om.MVector (0,1,0), angle=0): angle = radians(angle) quaternion = om.MQuaternion (axe[0]*sin(angle/2),axe[1]*sin(angle/2),axe[2]*sin(angle/2),cos(angle/2)) return quaternion def getMatrix(MVectorx,MVectory,MVectorz,MVectorPos): matrix = om.MMatrix([ MVectorx.x,MVectorx.y,MVectorx.z,0, MVectory.x,MVectory.y,MVectory.z,0, MVectorz.x,MVectorz.y,MVectorz.z,0, MVectorPos.x,MVectorPos.y,MVectorPos.z,1 ]) return matrix #return closest point on the mesh and the corresponding normal. Result as [mVector,mVector] def getPointNormal (mMesh, point): point = om.MPoint(point) result = mMesh.getClosestPointAndNormal(point, om.MSpace.kWorld) res0 = om.MVector(result[0]) result = [res0,result[1]] return result def getClosestVertex(mayaMesh,mVector): selectionList = om.MSelectionList() selectionList.add(mayaMesh) dPath = selectionList.getDagPath(0) mMesh = om.MFnMesh(dPath) ID = mMesh.getClosestPoint(om.MPoint(mVector), space=om.MSpace.kWorld)[1] #getting closest face ID list = cmds.ls( cmds.polyListComponentConversion (mayaMesh+'.f['+str(ID)+']',ff=True,tv=True),flatten=True)#face's vertices list #setting vertex [0] as the closest one d = mVector-om.MVector(cmds.xform(list[0], t=True, ws=True, q=True)) smallerDist2 = d.x*d.x+d.y*d.y+d.z*d.z #using distance squared to compare distance closest = list[0] #iterating from vertex [1] for i in range(1,len(list)) : d = mVector-om.MVector(cmds.xform(list[i], t=True, ws=True, q=True)) d2 = d.x*d.x+d.y*d.y+d.z*d.z if d2 1: for i in range(1, len(self.list)): if random() < var.closeRate : self.listClose.append(copy(self.list[i])) self.delIndex.append(i) if len(self.delIndex) > 0: for i in sorted (self.delIndex, reverse=True) : del (self.list[i]) for branch in self.list: branch1 = Branch() branch1.setup(branch=branch,iterations = var.iterations+(var.iterations-1)*var.iterationsVar*uniform(-1,1)) geo1 = copy(branch1.makeGeo(branch=branch)) self.templist.append(branch1) cmds.refresh() if random() < var.divRate: branch2 = Branch() branch2.setup(branch = branch,iterations=var.iterations+(var.iterations-1)*var.iterationsVar*uniform(-1,1)) geo2 = copy(branch2.makeGeo(branch=branch)) myMesh.add(geo2) self.templist.append(branch2) cmds.refresh() myMesh.add(geo1) self.list = copy(self.templist) for branch in self.listClose: branch1 = Branch() branch1.setup(branch=branch,iterations=var.iterations+(var.iterations-1)*var.iterationsVar*uniform(-1,1),close=True) geo = copy(branch1.makeGeo(branch=branch, close=True)) myMesh.add(geo) cmds.refresh() def close(self): for branch in self.list: branch1 = Branch() branch1.setup(branch=branch,iterations=var.iterations+(var.iterations-1)*var.iterationsVar*uniform(-1,1),close=True) geo = copy(branch1.makeGeo(branch=branch, close=True)) myMesh.add(geo) cmds.refresh() #variables class. Is used to create a single object representing all the variables. class Var: def __init__(self): self.scaleFactor = 1 self.radius=0.5 self.penetration = 0 self.distance=self.radius*(1-self.penetration) self.startAngle = 0 self.angleAmplitude = 30 self.angleAmplitudeDiv = 45 self.iterations = 50 self.iterationsVar = 30 self.divRate = 0.3 self.closeRate = 0.2 self.loopNumber = 1 self.leafScaleVar = 0.2 self.leafRot1Var = 15 self.leavesDensity = 0.4 self.firstIteration = True #updates value based on sliders. Called by slider. def update(self): self.scaleFactor = cmds.floatSliderGrp(scaleFactor, v=True, query = True) self.radius = cmds.floatSliderGrp(radius, v=True, query = True) self.penetration = cmds.floatSliderGrp(penetration, v=True, query = True) self.distance = self.radius*(1-self.penetration) self.startAngle = cmds.intSliderGrp(startAngle, v=True, query = True) self.angleAmplitude = cmds.floatSliderGrp(angleAmplitude, v=True, query = True) self.angleAmplitudeDiv = cmds.floatSliderGrp(angleAmplitudeDiv, v=True, query = True) self.iterations = cmds.intSliderGrp(iterations, v=True, query = True) self.iterationsVar = cmds.floatSliderGrp(iterationsVar, v=True, query = True) self.divRate = cmds.floatSliderGrp(divRate, v=True, query = True) self.closeRate = cmds.floatSliderGrp(closeRate, v=True, query = True) self.loopNumber = cmds.intSliderGrp(loopNumber, v=True, query=True) self.leavesScaleVar = cmds.floatSliderGrp(leavesScaleVar, v=True, query=True) self.leavesRot1Var = cmds.intSliderGrp(leavesRot1Var, v=True, query=True) self.leavesDensity = cmds.floatSliderGrp(leavesDensity, v=True, query=True) self.leavesPlacementIntMin = cmds.intSliderGrp (leavesPlacementIntMin, v=True, query=True) self.leavesPlacementIntMax = cmds.intSliderGrp (leavesPlacementIntMax, v=True, query=True) #leaves class is used to store a vector list and to place leaves along these vectors. class Leaves: def __init__(self): self.vectorList = [] def setMesh(self, mayamesh): cmds.xform(mayamesh, piv=(0,0,0)) cmds.makeIdentity (mayamesh, apply=True) self.mesh = mayamesh def append (self,vector): self.vectorList.append(vector) def reset (self): if hasattr(self,'grp'): if cmds.objExists (self.grp): print 'del' cmds.delete (self.grp) self.grp = cmds.group (em=True, n='Leaves') '''cmds.parent (self.grp,grp.grp)''' def placeGeo(self): for vector in self.vectorList: if random() < var.leavesDensity: z = vector.mVector.normal() x = vector.mNormal y = z^x x = y^z pos = vector.pos mat1 = getMatrix(x, y, z, pos) mat2 = getMatrix(x, -y, z, pos) leaf = cmds.duplicate (self.mesh) loc = cmds.spaceLocator () cmds.parent(leaf,loc) if uniform (0, 1)>0.5: cmds.xform (loc, matrix=mat1 )#place on the branch else: cmds.xform (loc, matrix=mat2 )#place on the branch cmds.move(0.9*var.radius,0,0,leaf,os=True,r=True)#place on the surface placementAngle = uniform (var.leavesPlacementIntMin,var.leavesPlacementIntMax) cmds.rotate (0, 0, -placementAngle, loc, os=True, r=True, fo=True)#place at various angle around Z. Zero angle = leaf orientation follows normal. cmds.rotate(gauss(0, var.leavesRot1Var),gauss(0, var.leavesRot1Var),0, leaf, os=True, r=True, fo=True) #random rotation s=gauss(1, var.leavesScaleVar) cmds.scale(s, s, s, leaf, r=True) cmds.parent(leaf,world=True) cmds.parent(leaf,self.grp) cmds.delete (loc) cmds.refresh() #FONCTIONS CALL BY BUTTONS #update vector with new variables and set it relative to locator. Use to tweak the first vector's parameters or to display it after reset. def updateVector (vector): var.update() vector.firstSetup(locator=myLocator, mMesh=myMesh.mMesh) vector.startRotation(mMesh=myMesh.mMesh, angle=var.startAngle) if hasattr(branch0, 'bezier'): if cmds.objExists (branch0.bezier): cmds.delete (branch0.bezier) branch0.firstSetup(vector0) if hasattr(branch0, 'geo'): if cmds.objExists (branch0.geo): cmds.delete (branch0.geo) branch0.makeGeo(first=True) branchInv.firstSetupInv(branch0) branchlist.initialize(branch0,branchInv)#put first branch in branchlist def reset(): grp.reset() myMesh.reset() leaves.reset() vector0.__init__() branch0 = Branch() branchInv = Branch() branchlist = Branchlist() updateVector(vector0) leaves.vectorList = [] def resetButton(): var.firstIteration = True try: reset() except: print 'Nothing to reset !' def setLocatorButton (): #launched when setlocator button is pressed. myLocator.setup (cmds.ls(sl=True)[0]) #point locator via custom locator class updateVector(vector0) #update to display vector cmds.viewFit(cmds.ls(sl=True)[0]) def setMeshButton(): try: mesh = cmds.ls(sl=True)[0] grp.reset() myMesh.setmMesh(mesh) except : print 'please select a mesh object !' def iterateButton(): try: if var.firstIteration == True : reset() branchlist.iterate(var.loopNumber) except: print 'set mesh and location first !' def finishButton(): try: branchlist.close() cmds.delete(myMesh.refMesh) except: print 'No generation to terminate !' def combineGeo(): try: geo = grp.unite() grp.reset() var.firstIteration=True cmds.select(geo) except: print 'No geo to combine !' def setLeafButton(): leaves.setMesh(cmds.ls(sl=True)[0]) def placeLeavesButton(): leaves.reset() leaves.placeGeo() #Objects created at launch to easy variable access. var = Var() myMesh = Mesh() vector0 = Vector() myLocator = Locator() branch0 = Branch() branchInv = Branch() branchlist = Branchlist() grp = Grp() leaves = Leaves() #WINDOW if (cmds.window('RootsAndIvy', exists=True)): cmds.deleteUI('RootsAndIvy', window=True ) myWindow = cmds.window('RootsAndIvy', iconName='mw' ) if (cmds.windowPref( myWindow, exists=True )): cmds.windowPref( myWindow, remove=True ) #MENU menuBarLayout = cmds.menuBarLayout() cmds.menu(label='File') cmds.menuItem(label='Reset') cmds.menu(label='Help', helpMenu=True) cmds.menuItem( label='About...') #MAIN LAYOUT mainColumn = cmds.columnLayout(adjustableColumn=True, w=500, co=['both',5]) #FRAME 1 frame1 = cmds.frameLayout (label='Initial setup', mw=5, parent=mainColumn) f1r = cmds.rowLayout(numberOfColumns=2, parent=frame1, rat=[1,'top',0], adj=2) frame11 = cmds.frameLayout (label='Mesh setup', mw=5, mh=2, parent=f1r, bgs=True) frame12 = cmds.frameLayout (label='Standard division setup', mw=5, parent=f1r, bgs=True) cmds.rowColumnLayout(numberOfColumns=2, parent=frame11, rs=[[2,5]], cs=[2,5], cal=[1,'right']) cmds.text(label='Reference Mesh') cmds.button( label='Set mesh', command=('setMeshButton()')) cmds.text(label='') cmds.button(label='Create locator', command=("cmds.spaceLocator(n='StartLocator')") ) cmds.text(label='Start position') cmds.button(label='Set position', command=('setLocatorButton()') ) cmds.rowLayout(numberOfColumns=2, parent=frame12) #FRAME 2 frame2 = cmds.frameLayout (label=" Generation's setup",mw=5,parent=mainColumn,bgs=True) scaleFactor = cmds.floatSliderGrp(label = 'Scale factor (length)\n Affects precision',field=True,minValue=0.0, maxValue=0.5, fieldMinValue=0.0, fieldMaxValue=5, step=0.01, value=0.1,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'updateVector(vector0)',dc= 'updateVector(vector0)',parent=frame12,ad3=1 ) startAngle = cmds.intSliderGrp(label = 'Angle',field=True,minValue=0.0, maxValue=360, fieldMinValue=0.0, fieldMaxValue=360, step=1, value=0,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'updateVector(vector0)',dc= 'updateVector(vector0)' ,parent=frame12,ad3=1) radius = cmds.floatSliderGrp(label = 'Radius',field=True,minValue=0.0, maxValue=0.5, fieldMinValue=0.0, fieldMaxValue=2, step=0.05, value=0.05,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'updateVector(vector0)',dc= 'updateVector(vector0)',parent=frame12,ad3=1 ) penetration = cmds.floatSliderGrp(label = 'Penetration',field=True,minValue=0.0, maxValue=1, fieldMinValue=0.0, fieldMaxValue=1, step=0.05, value=0.05,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'updateVector(vector0)',dc= 'updateVector(vector0)',parent=frame12,ad3=1 ) iterations = cmds.intSliderGrp(label='Branches average length', field=True,minValue=1, maxValue=100, fieldMinValue=1, fieldMaxValue=100, step=1, value=20,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()' ,ad3=1,co3=[0,0,30],ct3=('right','center','right')) iterationsVar = cmds.floatSliderGrp(label='Length variation amplitude', field=True,minValue=0, maxValue=1, fieldMinValue=0, fieldMaxValue=1, step=0.05, value=0.3,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()',ad3=1,co3=[0,0,30],ct3=('right','center','right') ) angleAmplitude = cmds.floatSliderGrp(label='Angle randomness variance',field=True,minValue=0.0, maxValue=45, fieldMinValue=0.0, fieldMaxValue=90, step=1, value=15,columnWidth3 = (100,50,50),columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()',ad3=1,co3=[0,0,30],ct3=('right','center','right')) angleAmplitudeDiv = cmds.floatSliderGrp(label='Angle randomness variance\nat divisions',field=True,minValue=0.0, maxValue=45, fieldMinValue=0.0, fieldMaxValue=90, step=1, value=25,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()',ad3=1,co3=[0,0,30],ct3=('right','center','right')) divRate = cmds.floatSliderGrp(label='Divisions rate', field=True, minValue=0, maxValue=1, fieldMinValue=0, fieldMaxValue=1, step=0.01, value=0.30,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()' ,ad3=1,co3=[0,0,30],ct3=('right','center','right')) closeRate = cmds.floatSliderGrp(label='Closing rate', field=True, minValue=0, maxValue=1, fieldMinValue=0, fieldMaxValue=1, step=0.01, value=0.20,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()',ad3=1,co3=[0,0,30],ct3=('right','center','right') ) cmds.text (label='A high (division rate/closing rate) ratio will lead to exponential generation !') #FRAME 3 frame3 = cmds.frameLayout(label='Create geometry',mw=5,parent=mainColumn) frame31 = cmds.frameLayout(label='Main structure',mw=5,parent=frame3,bgs=True) #ROWCOLUMN with 2 column f31r = cmds.rowColumnLayout(numberOfColumns=2,rs=[[2,5]],cs=[2,5],cal=[1,'right']) #1 loopNumber = cmds.intSliderGrp(label = 'Number of \n iteration\'s \nloop',field=True,minValue=1, maxValue=10, fieldMinValue=1, fieldMaxValue=50, step=1, value=1,columnWidth3 = (100,50,50), ad3=1,columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()', parent=f31r ) #2 cmds.button(label='Iterate', command=('iterateButton()')) #3 cmds.rowLayout(numberOfColumns=2,parent=f31r) cmds.text (label='Reset current generation') cmds.button( label='reset', command=('resetButton()') ) #4 cmds.rowLayout(numberOfColumns=2, parent=f31r) cmds.text(label='Terminate\n (Plug extremities)') cmds.button(label='Terminate', command=('finishButton()') ) #FRAME4 frame32 = cmds.frameLayout(label='Leaves',mw=5,parent=frame3,bgs=True) cmds.text(label='Leaf primitive orientation must be : \n UP = global Y, leaf direction = global Z, stem basis = 0,0,0') f32r=cmds.rowLayout(numberOfColumns=2,ad2=2,ct2=('right','right')) f32rc=cmds.columnLayout() f32rr=cmds.rowLayout(numberOfColumns=2) cmds.text (label = 'Set leave primitive',parent=f32rr) cmds.button( label='Set Mesh', command=('setLeafButton()'),parent=f32rr) leavesDensity= cmds.floatSliderGrp(label = 'Density',field=True,minValue=0, maxValue=1, fieldMinValue=0, fieldMaxValue=1, step=0.01, value=0.4,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()',ad3=1,co3=[0,0,30],ct3=('right','center','right'),parent=f32rc ) leavesScaleVar= cmds.floatSliderGrp(label = 'Scale\n variance',field=True,minValue=0, maxValue=1, fieldMinValue=0, fieldMaxValue=1, step=0.01, value=0.3,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()',ad3=1,co3=[0,0,30],ct3=('right','center','right'),parent=f32rc ) leavesRot1Var= cmds.intSliderGrp(label = 'Rotation\n variance',field=True,minValue=0, maxValue=30, fieldMinValue=0, fieldMaxValue=45, step=1, value=15,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()',ad3=1,co3=[0,0,30],ct3=('right','center','right'),parent=f32rc ) cmds.columnLayout (parent=f32r) cmds.text (label = 'Positionning interval (0 = normal, 90 = lateral segment) : ') leavesPlacementIntMin = cmds.intSliderGrp(label = 'min',field=True,minValue=0, maxValue=90, fieldMinValue=0, fieldMaxValue=90, step=1, value=45,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()',ad3=1,co3=[0,0,30],ct3=('right','center','right')) leavesPlacementIntMax = cmds.intSliderGrp(label = 'max',field=True,minValue=0, maxValue=90, fieldMinValue=0, fieldMaxValue=90, step=1, value=85,columnWidth3 = (100,50,50), columnAlign3=('right','center','center'),cc = 'var.update()',dc= 'var.update()',ad3=1,co3=[0,0,30],ct3=('right','center','right') ) cmds.rowColumnLayout(numberOfColumns=2,rs=[[2,5]],cs=[2,5],cal=[1,'right']) cmds.button(label='Place leaves', command=('placeLeavesButton()')) cmds.button(label='Reset', command=('leaves.reset()')) #FRAME 5 frame5=cmds.frameLayout (label='Utility', mw=5, parent=mainColumn, bgs=True) cmds.rowLayout(numberOfColumns=2) cmds.text (label='Combine geometry') cmds.button(label='Combine geo', command=('combineGeo()') ) var.update() cmds.showWindow('RootsAndIvy')
to join this conversation on GitHub. Already have an account? Sign in to comment