Notifications
Clear all

[Closed] How do you speed up your scripts?

I really like maxscript, but I’m a terribly inefficient coder. I need a lot of help on getting my scripts running fast enough so they actually work quicker than doing it by hand. I have one script that I plan to go through and redesign, but its a bit of too large an endeavour right now: ffd2Quad. Before I get to that, I wanted to fix some smaller, more manageable scripts. So this weekend I wrote up a script to create a valid patch from faces the user has selected. while it runs, it takes a long time to do the sorting. I’d really appreciate suggestions or links to resources on how to improve the speed.

nixxed this version. see below
4 Replies
1 Reply
(@denist)
Joined: 1 year ago

Posts: 0

the code might be much easier, cleaner, faster… but before doing an optimization could you explain me a little better what you want to do… can you show couple pictures showing the things ‘before – and – after’ script?
i want to be sure that the code needs an optimization only.

my idea was to have a script that converts the user face selections into ‘legal’ quad patches so they could use a patch deform modifier to wrap geometry around a model. I just tried to rerun the script I supplied and I realized there was a typo in it. Sorry about that, that is egg on my face. I attached an screengrab of what the script does, but like I mentioned–it doesn’t do it quickly.

  A general overview of what I want from the script:
 I select some faces on an object. I run the macro. I pick the geometry to wrap. A patch is created from the faces and an instance of the wrapping geometry is placed on the patch with a patch_deform.
 
 My strategy:
 detach faces
 find corners
 find longest edges between corners
 create shape from edge, grow ring, (repeat, repeat, repeat)
 shape--> cross section-->surface
 
 I don't think I designed this well, because it takes exponentially longer the more faces involved in the process. But also on  a more generalized level, I'm having a very hard time switching from thinking about how to write scripts from the algorithm level of  whats fastest in max, instead of the macro level of a 3d modeler's workflow. I was wondering if anyone had advice about the differences.
fn getCorners obj = (
   	(
   	select obj
   	subobjectLevel = 3
   	modPanel.setCurrentObject $.baseObject
   	actionMan.executeAction 0 "40021"  -- Selection: Select All
   	max select all
   	obj.EditablePoly.ConvertSelection #Border #Vertex
   	subobjectLevel = 1
   	obj.EditablePoly.ShrinkSelection ()
   
   	)
   	corners = polyop.getVertselection obj
   )
   
   --gracias to DenisT (once again)
   fn addShape sp1 sp2 =( if iskindof sp1 SplineShape and iskindof sp2 SplineShape do
    (
   	 convertToSplineShape sp1 -- to make it undoable
   	 convertToSplineShape sp2
   	 addAndWeld sp1 sp2 -1
   	 sp1
    )
    )
    
   
   fn getVertsInOrder obj corners = 
   (
   	--first off get the out side edges and columns
   	
   	firstrow = #{}
   	firstcol = #{}
   	for i = 2 to corners.count do 
   	(
   		verts = #{}
   		append verts (corners[1])
   		append verts (corners[i])
   		obj.EditablePoly.SetSelection #Vertex verts
   		macros.run "PolyTools" "StepLoop"
   
   		--this the hold up (?)
   		if ((firstrow as array).count  < ((polyop.getvertselection obj) as array).count) then (
   			firstcol = copy firstrow
   			firstrow = (polyop.getvertselection obj)
   		) else if ((firstcol as array).count == 0) then firstcol = (polyop.getvertselection obj)
   	)
   	
   	obj.EditablePoly.SetSelection #Vertex firstrow
   	obj.EditablePoly.ConvertSelection #Vertex #Edge
   	subobjectLevel = 2
   	obj.EditablePoly.ShrinkSelection ()
   	
   	--at this point  the first edge is selected
   	processededges = #{}
   	newedges = (polyop.getEdgeselection obj)
   	contours = #()
   	shapeX = SplineShape pos:obj.pivot name:"contour"
   	completeredraw()
   	contournames = #()
   	for  i= 1 to firstcol.count  do (
   		
   		newedges = (polyop.getEdgeselection obj) - processededges
   		polyop.setEdgeSelection obj newedges
   		shapename = uniquename("contour")
   		polyop.createShape obj newedges name:shapename
   		append contournames shapename
   		addShape shapeX  (getnodebyname shapename)
   		processededges = processededges + (polyop.getEdgeselection obj)
   		
   		macros.run "PolyTools" "GrowRing"
   	)
   	
   	--need to make sure each  spline is facing the right direction
   	convertToSplineShape shapex 
   	select shapex
   	macros.run "Modifier Stack" "SubObject_3"
   	for i =1 to numSplines shapex do (
   		
   		firstknot = getKnotPoint shapex i 1
   		
   		rev = false
   		for h in firstcol do (
   			if firstknot == polyop.getvert obj h then (
   				rev = true
   			)	
   		)
   		if rev == true then (
   			reverse shapex i
   			updateShape shapex
   		)
   			rev = false
   	)
   	
   	addmodifier shapex (crosssection())
   	addmodifier shapex (surface())
   -- 	addmodifier shapex (Turn_to_Patch())
   )
   
   fn createPatchfromQuadCluster base source: = (
   maxOps.cloneNodes base cloneType:#copy newNodes:&nnl
   select nnl
   mainBase = $
   subobjectLevel = 4
   	if ( (polyop.getfaceselection mainbase as array).count < 4 ) then (
   		messagebox "not enough face selected"
   		return error
   	)
   max select invert
   max delete
   
   getVertsInOrder mainBase (( getCorners mainBase) as array )
   
   if source != unsupplied then (
   		maxOps.cloneNodes source cloneType:#reference newNodes:&nnl
   		ref = nnl
   		ref.pos = mainBase.pos
   		spd = SpacePatchDeform()
   		addmodifier ref spd
   		select ref
   	) -- "There is no way to specify the patch to deform to using MAXScript. " ---FFUUUUUUUUUUUUUUUUUUUUUUUUU
   	delete mainBase
   )
   
   fn patchSmuggler = (
   	base = $
   	--sourcey = pickobject()
   	with redraw off (
   		undo off
   		(
   		createPatchfromQuadCluster base --source:sourcey
   		)
   	)
   )
   
   patchSmuggler()

there is something wrong in your concept. i can’t say what exactly. but it looks heavy.
how is about to use the FFD_Box modifier applied to the face level?

PS. Patch : GeometryClass is very old. there is no really good interface to work with it via mxs. i remember one tool that i wrote a while ago… everything about patches was a workaround, hacks, and tricks.

okay I solved my problem. huge headache for a tiny mistake, mixed my bitArray and array methods

for i = 1 to firstcol.count do
should be
for i = 1 to (firstcol as array).count do

heres how the script ended up looking. I ashamed of sloppy it is, but at least its runnning

fn getCorners obj = (
 	(
 	select obj
 	subobjectLevel = 3
 	modPanel.setCurrentObject $.baseObject
 	actionMan.executeAction 0 "40021"  -- Selection: Select All
 	max select all
 	obj.EditablePoly.ConvertSelection #Border #Vertex
 	subobjectLevel = 1
 	obj.EditablePoly.ShrinkSelection ()
 
 	)
 	corners = polyop.getVertselection obj
 )
 
 --gracias to DenisT (once again)
 fn addShape sp1 sp2 =( if iskindof sp1 SplineShape and iskindof sp2 SplineShape do
  (
 	 convertToSplineShape sp1 -- to make it undoable
 	 convertToSplineShape sp2
 	 addAndWeld sp1 sp2 -1
 	 sp1
  )
  )
  
 
 fn getVertsInOrder obj corners = 
 (
 	--first off get the out side edges and columns
 	
 	firstrow = #{}
 	firstcol = #{}
 	for i = 2 to corners.count do 
 	(
 		verts = #{}
 		append verts (corners[1])
 		append verts (corners[i])
 		obj.EditablePoly.SetSelection #Vertex verts
 		macros.run "PolyTools" "StepLoop"
 
 		--this the hold up (?)
 		if ((firstrow as array).count  < ((polyop.getvertselection obj) as array).count) then (
 			firstcol = copy firstrow
 			firstrow = (polyop.getvertselection obj)
 		) else if ((firstcol as array).count == 0) then firstcol = (polyop.getvertselection obj)
 	)
 	
 	obj.EditablePoly.SetSelection #Vertex firstrow
 	obj.EditablePoly.ConvertSelection #Vertex #Edge
 	subobjectLevel = 2
 	obj.EditablePoly.ShrinkSelection ()
 	
 	--at this point  the first edge is selected
 	processededges = #{}
 	newedges = (polyop.getEdgeselection obj)
 	contours = #()
 	shapeX = SplineShape pos:obj.pivot name:"contour"
 	completeredraw()
 	contournames = #()
 	for  i = 1 to (firstcol as array).count  do (
 		
 		newedges = (polyop.getEdgeselection obj) - processededges
 		polyop.setEdgeSelection obj newedges
 		shapename = uniquename("contour")
 		polyop.createShape obj newedges name:shapename
 		append contournames shapename
 		addShape shapeX  (getnodebyname shapename)
 		processededges = processededges + (polyop.getEdgeselection obj)
 		
 		macros.run "PolyTools" "GrowRing"
 	)
 	
 	--need to make sure each  spline is facing the right direction
 	convertToSplineShape shapex 
 	select shapex
 	macros.run "Modifier Stack" "SubObject_3"
 	for i =1 to numSplines shapex do (
 		
 		firstknot = getKnotPoint shapex i 1
 		
 		rev = false
 		for h in firstcol do (
 			if firstknot == polyop.getvert obj h then (
 				rev = true
 			)	
 		)
 		if rev == true then (
 			reverse shapex i
 			updateShape shapex
 		)
 			rev = false
 	)
 	
 	addmodifier shapex (crosssection())
 	surf = Surface()
 	surf.steps = 0
 	addmodifier shapex (surf)
 -- 	addmodifier shapex (Turn_to_Patch())
 )
 
 fn createPatchfromQuadCluster base source: = (
 maxOps.cloneNodes base cloneType:#copy newNodes:&nnl
 select nnl
 mainBase = $
 subobjectLevel = 4
 	if ( (polyop.getfaceselection mainbase as array).count < 4 ) then (
 		messagebox "not enough face selected"
 		return error
 	)
 max select invert
 max delete
 
 getVertsInOrder mainBase (( getCorners mainBase) as array )
 
 if source != unsupplied then (
 		maxOps.cloneNodes source cloneType:#reference newNodes:&nnl
 		ref = nnl
 		ref.pos = mainBase.pos
 		spd = SpacePatchDeform()
 		addmodifier ref spd
 		select ref
 	) -- "There is no way to specify the patch to deform to using MAXScript. " ---FFUUUUUUUUUUUUUUUUUUUUUUUUU
 	delete mainBase
 )
 
 fn patchSmuggler = (
 	base = $
 	--sourcey = pickobject()
 	with redraw off (
 		undo off
 		(
 		createPatchfromQuadCluster base --source:sourcey
 		)
 	)
 )
 
 -- patchSmuggler()
 
 -- Hugger Script
 /**
 hugs an object around a user select quad cluster
 relies on PatchSmuggler.Ms and FFDtoFace_1129 for some functions
 
 **
 1)grab quad cluster and sort corners/rows/columne --> fn sortClusterObj()
 */
 
 --this dialog never shows up. how to force a rollout ot update?
 rollout waitRollout "Hugger 1.0" width:144 height:88
 (
 	label lbl1 "Please Wait" pos:[14,0] width:118 height:19
 	
 )
 
 fltrllout = newrolloutfloater "HUGGER" 144 50
 addrollout waitRollout fltrllout
 updateToolbarButtons()
 -- fltrllout.size = [ 144, 50]
 
 patchSmuggler()
 basePlate = $
 
 columns = basePlate.numsplines
 rows = numknots baseplate / columns
 macros.run "Modifier Stack" "Convert_to_Poly"
 
 waitRollout.lbl1.caption = "Pick the Hugger Object"
 HuggerObj = pickobject()
 select huggerobj
 fbox = FFDBox ()
 addmodifier HuggerObj (fbox)
 setDimensions HuggerObj.modifiers[#FFD_box__4x4x4] [rows,columns,2]
 animateAll fbox
 
 --refer to this: fn ffdSetWorldPos obj ffdmod i pos
 --refer to this: getVertNormal obj index smoothGroup: =
 
 theMaster = fbox[#master]
 theCount = theMaster.numsubs
 -- theCount = fbox.numsubs
 -- for i = 1 to theCount do print theMaster[i]
 height = huggerObj.max[3]
 
 
 
 
 fn rotateSelectedPolyFaces theObj theAngle thePivot: =
 (
 	local theSel = polyOp.getFaceSelection theObj --# get the selected faces
 	local theVerts = polyOp.getVertsUsingFace theObj theSel --# convert to vertex selection
 	local theMatrix = theAngle as matrix3 --# convert the quaternion to a matrix
 	local thePivotMatrix = if thePivot == unsupplied then 
 		matrix3 1  --# if no pivot node is provided, use identity matrix - this will result in rotation about world origin
 	else 
 		thePivot.transform --# otherwise use the transformation matrix of the supplied node
 	--# calculate the transformation matrix - get the vertex out of world space into the space of the pivot node,
 	--# multiply by the rotation matrix and convert back to world space
 	local theTM =  inverse thePivotMatrix * theMatrix *  thePivotMatrix 
 	for v in theVerts do  --# for every vertex, get the position, transform by the matrix and set back as new position
 		polyOp.setVert theObj v ((polyOp.getVert theObj v)* theTM)
 	redrawViews() --# force a viewport update
 )
 
 
 	addmodifier basePlate (Normalmodifier())
 	select huggerobj
 	modPanel.setCurrentObject $.baseObject
 	if classof huggerobj == editable_poly then ( 
 		subobjectLevel = 4
 		max select all
 	)
 	modPanel.setCurrentObject $.modifiers[1]
 	
 		
 		--the ffd move code
 fn AlignFFD = (
 	for i = 1 to theCount/2 do (
 		ffdSetWorldPos HuggerObj fbox i (polyop.getvert baseplate i)
 	)
 	for i = (theCount/2 +1) to theCount do (
 		ffdSetWorldPos HuggerObj fbox i ((polyop.getvert baseplate (i-(theCount/2))) + height*(getVertNormal baseplate (i-(theCount/2)))) --this is terrible math
 	)
 )
 
 
 AlignFFD()
 when geometry HuggerObj changes id:#huggercalback do ( -- do  I need to delete this callback later?
 	AlignFFD()
 )
 
 -- delete baseplate
 	
 
 
  rollout huggerrllout "Hugger 1.0" width:144 height:88
  (
 	 button rotateClockWise_btn "Turn Clockwise" pos:[8,8] width:128 height:16
 	 button CounterClockwise_btn "Counter Clockwise" pos:[8,24] width:128 height:16
 --	  button horizontal_btn "Flip Horizontal" pos:[8,40] width:144 height:16 -- figur out the transform matrix stuff later
 --	  button vertical_btn "Flip Vertaically" pos:[8,56] width:144 height:16
 	 button commit_btn "Commit" pos:[8,56] width:128 height:24
 	 button normal_btn "Flip Normal" pos:[8,40] width:128 height:16
 	
 	 on huggerrllout close do
 	 (
 			  deleteAllChangeHandlers id:#huggercalback 
 		 )
 	 on rotateClockWise_btn pressed do
 	 (
 	 with redraw off(
 	 rot = eulerangles 0 0 90
 	 fbox.enabled = off
 	 rotateSelectedPolyFaces HuggerObj (quat 90 [0,0,1]) thePivot:HuggerObj
 	 fbox.enabled = on
 	 
 	 )
 	 
 	 completeredraw()
 	 AlignFFD()
 		 )
 	 on CounterClockwise_btn pressed do
 	 (
 	 with redraw off(
 	 fbox.enabled = off
 	 rot = eulerangles 0 0 90
 	 rotateSelectedPolyFaces HuggerObj (quat 90 [0,0,-1]) thePivot:HuggerObj
 	 fbox.enabled = on
 	 )
 	 
 	 completeredraw()
 	 AlignFFD()
 		 )
 	 on commit_btn pressed do
 	 (
 	 convertto  HuggerObj editable_poly
 	 delete baseplate
 	 closeRolloutFloater fltrllout
 		 )
 	 on normal_btn pressed do
 	 (
 	 if baseplate.modifiers[#Normal].flip then(
 		 baseplate.modifiers[#Normal].flip = off
 	 )
 	 else ( 
 		 baseplate.modifiers[#Normal].flip = on
 		 )
 	 AlignFFD()
 	 )
  )
 
 -- fltrllout = newrolloutfloater "HUGGER" 140 310
 removerollout waitRollout fltrllout
  fltrllout.size = [ 144, 120]
 addrollout huggerrllout fltrllout