Speeding up a script.


#1

My current script works, but it’s really slow on high res models. Is there any way to improve it?

To test the script, create a plane, put a greeble on it, with “select tops” then put edit poly on top, switch to face mode then run the script. It should assign each ‘greeble building’ a different material ID between 20 and 80 and the rooftop and rooftop sections should be a different ID.

I’m attempting to recreate this, adapt and build on it to make it even better and make a really nice procedural City… http://www.mattepainting.org/board/viewtopic.php?f=25&t=4272

 theObject = modPanel.getCurrentObject()
  thePolys = ( theObject.GetSelection #Face node:selection[1] ) as array
 print "count" + thepolys.count as string
 for  i = 1 to thePolys.count do
 (
 --i = 1
 	--select i
 	a = theObject.SetSelection #Face #{thePolys[i]} -- node:selection[1]
 	b = theObject.GetFaceMaterial thePolys[i]
 	print b
 	If b == 1 do
 	(
 	--	Growselection()
 		
 		max select none
 		$.Edit_Poly.Select #Face #{thePolys[i]}
 		theObject.ButtonOp #GrowSelection
 		
 		--choose ranomd building texture
 		r = Random 20 100
 		r = r - 1
 		$.modifiers[#Edit_Poly].SetOperation #SetMaterial
 		$.modifiers[#Edit_Poly].MaterialIDtoSet = r
 		$.modifiers[#Edit_Poly].Commit ()
 		
 	--	facematerialID = r
 		max select none
 		$.Edit_Poly.Select #Face #{thePolys[i]}
 		t = random 5 10
 		$.modifiers[#Edit_Poly].SetOperation #SetMaterial
 		$.modifiers[#Edit_Poly].MaterialIDtoSet = t
 		$.modifiers[#Edit_Poly].Commit ()		
 		
 	--	face.arraynumber.materialID = t		
 	)
 	if b == 2 do (
 	
 --		Growselection()
 	print "no"
 max select none
 		$.Edit_Poly.Select #Face #{thePolys[i]}
 		theObject.ButtonOp #GrowSelection
 		
 		--choose ranomd building texture
 		r = Random 2 4
 		r = r - 1
 		$.modifiers[#Edit_Poly].SetOperation #SetMaterial
 		$.modifiers[#Edit_Poly].MaterialIDtoSet = r
 		$.modifiers[#Edit_Poly].Commit ()
 		
 	--	facematerialID = r
 		max select none
 		$.Edit_Poly.Select #Face #{thePolys[i]}
 		t = random 5 10
 		$.modifiers[#Edit_Poly].SetOperation #SetMaterial
 		$.modifiers[#Edit_Poly].MaterialIDtoSet = t
 		$.modifiers[#Edit_Poly].Commit ()	
 	)
 
 )
 
 

#2

And for those interested and who want to test it, create a multisub material in slot 3 and set it to 100 materials, then run this script.



for i = 20 to 100 do
(
					obj_mat = meditMaterials[3].materialList[i]
					
					new_mat = StandardMaterial()
					new_mat.name = "Building" + i as string
					
					--new_mat.opacitymap = obj_mat.texmap_opacity
					r = 160
					g = random 92 133
					b = random 50 140
					new_mat.diffuse = color r g b
					--new_mat.selfIllumAmount = 100
					meditMaterials[2].materialList[i] = new_mat
					
)

#3

a few things that should help…

  • disable scene redraw while your script runs; that sped things up tons here.
  • check if ‘max select none’ is slower than ‘$.Edit_Poly.Select #Face #{1…$.numfaces} select:false’
  • cache this (i.e. assign to a variable before all the loops): ‘$.modifiers[#Edit_Poly]’
  • similarly, you’re also using ‘$.Edit_Poly’; that should be the same thing, no?
  • ‘If b == 1 do’ and ‘if b == 2 do (’ should be a ‘if b == 1 then ( … ); else if b == 2 then ( … )’ (skips one check, won’t have much impact)
  • ‘r = Random 2 4; r = r - 1’ ? why not go straight with ‘r = random 1 3’? Or at least ‘r -= 1’.

#4

Suprisingly little differences, still takes about 35seconds to process the script with just 522 polys selected (off 2830). I’m sure it doesn’t help that you have to have the edit poly modifier open and in face mode.

My machine is pretty fast… not machine problem…


#5

Hi,

Try removing the print commands (especially from inside the loop).
Prints can slow things down quite a bit.

Cheers,
o


#6

looks like .setOperation is one of the slowest parts; seeing as you’re never actually changing operation modes, try moving it outside of the loops, before the “for i = 1 to etc”, and remove the duplicates (so you only set the operational mode once).

Can’t help but think there’s got to be some epoly/whatever bits to change material IDs as well that might be faster. You basically only need the UI for the #GrowSelection?

Edit: just a quick benchy bit… on a teapot with 12800 ‘faces’, 100 of which selected…
Original: 12538ms
Removing the duplicate .setOperation: 6579ms (about half, imagine that)
Moving the .setOperation outside the loops: 1862ms


#7

Yeah at the moment and to set the materialiD, yes, anyone know how to code it or something similar… eg…

Select the element that the face is part of…


#8

well, getting the ‘element’ for a given face is as simple as checking all the edges of that face, see if it’s shared by another face - if it is, add that face and do the same check (minus the edge you already covered) for that face. Keep going until there’s no more new faces.
But that might be slower than what growSelection does - haven’t the foggiest.

One other thing may be to not set the material ID piecewise, but instead only collect the faces and their material IDs in (bit)arrays in the current loop. Then loop over one of the arrays (presumably the material ID one, as there’s less of those), select the faces that go with the given material ID, then apply that material ID to all of those faces in one go with the .MaterialIDtoSet and .Commit() combo.


#9

how about this?

$.modifiers[#Edit_Poly].ConvertSelection #Face #Element

edit: ok I see you want to keep the modifier panel shut.

There’s:

[b]$.modifiers[#Edit_Poly].getElementsUsingFace

…[/b]that should do the trick, althought it wants 3 arguments and I can’t work out what they are.


#10

Here is the needed info for getElementsUsingFace, which appears to be missing from the Maxscript Docs:

<void>getElementsUsingFace <&bitArray>elementSet <&bitArray>faceSet <&bitArray>fenceSet node:<node>
getElementsUsingFace - no automatic redraw after invoked
elementSet is In and Out parameter
faceSet is In and Out parameter
fenceSet is In and Out parameter
node default value: undefined
Or use the polyop method which is documented:
polyop.getElementsUsingFace <Poly poly> <facelist> fence:<fence_facelist>

Bits in the result bitarray are set for all faces in the same “element”, or connected component, with faces in <facelist>.

If <fence_facelist> is specified, those faces will be considered “walls” for this processing and will not be evaluated. That is, if face i is specified in <facelist>, and a ring of faces around it is specified in <fence_facelist>, the algorithm will stop at that ring.

-Eric


#11

polyOp may not work, as it expects an Editable_Poly result, rather than any ol’ geometry object with an Edit Poly modifier?

eyes the missing documentation bits
Doing a CGtalk search for ‘missing documentation’ is… informative :slight_smile:


#12

Fine be that way, atleast it gives a little more info on what you need to supply.

-Eric


#13

How about this:
Instead of going with the edit_poly modifier, creating a snapshot and converting it to editable poly object, this way you don’t have to deal with selecting a few faces, assigning the material ids to them, selecting other faces etc.
Try this code:


(
	fn getElements obj faces:#All =
	(
		local objClass = classOf obj
		local func
		case objClass of (
			Editable_Poly: fn func obj face = polyOp.getElementsUsingFace obj face
			PolyMeshObject: fn func obj face = polyOp.getElementsUsingFace obj face
			Editable_Mesh: fn func obj face = meshOp.getElementsUsingFace obj face
			Edit_Poly: fn func obj face = (local elem = #{}; obj.getElementsUsingFace elem #{face} #{}; elem)
			default: undefined
		)
		local elements = #()
		local usedFaces = #{}
		
		local numFaces = case objClass of (
			Editable_Poly: obj.numFaces
			Editable_Mesh: obj.numFaces
			PolyMeshObject: obj.numFaces 
			Edit_Poly: obj.getNumFaces()
			default: 0
		)
		if faces == #All then
			faces = #{1..numFaces}

		for f in faces where not usedFaces[f] do (
			local elem = func obj f
			append elements elem
			usedFaces += elem
		)
		elements
	)


	fn assignRandomMatID obj =
	(
		with redraw off (
			local newMesh = snapshot obj
			convertToPoly newMesh
			
			local roofIDRange = [81,99]
			local buildingIDRange = [20,80]
			
			local sel = polyOp.getFaceSelection newMesh
			local elements = getElements newMesh faces:sel
			
			for elem in elements do (
				polyOp.setFaceMatID newMesh elem (random buildingIDRange[1] buildingIDRange[2])
			)
			
			for f in sel do (
				polyOp.setFaceMatID newMesh f (random roofIDRange[1] roofIDRange[2])
			)
		)
		newMesh
	)
	
--	gc()
	 t = timeStamp()
	assignRandomMatID $
	 format "took: % ms.
" (timeStamp() - t)
)

I didn’t test you code, but from my own tests it was way faster than tests I did with edit poly modifier. On my machine (not a very strong one) it took 22 secs. to assign random material ids to a plane with 20x20 segments, which translates into 49,430 polys after the greeble and edit poly modifier.
To run my script, create a plane, add greeble and check “select tops” and run the script. No need to add an edit poly modifier.

cheers,
o


#14

er?

That it does :slight_smile: Thanks!


#15

Thanks Ofer_z! That works well, very fast, took 1000000ms on my machine for a 300,000 poly object! :slight_smile:

Unfortunetly I’ve got to slow down the script as need to add one bit…

If the Id of the top face is 2 then the element needs to be given a different range of ID as I want the top bits of greeble to like aircon/roof objects, so can’t have them having the same ID as the main buildings.

Going to attempt to code this in myself now, but i’m just trying to disect your code! Looks very efficient and a bit over my head at the moment!

Script leaks memory like a siv though! Used 14.5gb Page file usage and the gc() didn’t fix this at the end of the script, had to restart max.


#16

Have you tried switching undo off? Can make things much faster too…


#17

Rorschach is right, the script is not exactly leaking memory, it’s max’s undo buffer that gets filled. Turning off the undo buffer solves this and increases the speed.


(
	fn getElements obj faces:#All =
	(
		local objClass = classOf obj
		local func
		case objClass of (
			Editable_Poly: fn func obj face = polyOp.getElementsUsingFace obj face
			PolyMeshObject: fn func obj face = polyOp.getElementsUsingFace obj face
			Editable_Mesh: fn func obj face = meshOp.getElementsUsingFace obj face
			Edit_Poly: fn func obj face = (local elem = #{}; obj.getElementsUsingFace elem #{face} #{}; elem)
			default: undefined
		)
		local elements = #()
		local usedFaces = #{}
		
		local numFaces = case objClass of (
			Editable_Poly: obj.numFaces
			Editable_Mesh: obj.numFaces
			PolyMeshObject: obj.numFaces 
			Edit_Poly: obj.getNumFaces()
			default: 0
		)
		if faces == #All then
			faces = #{1..numFaces}

		for f in faces where not usedFaces[f] do (
			local elem = func obj f
			append elements elem
			usedFaces += elem
		)
		elements
	)


	fn assignRandomMatID obj =
	(
		with undo off with redraw off (
			local newMesh = snapshot obj
			convertToPoly newMesh
			
			local roofIDRange = [81,99]
			local buildingIDRange = [20,80]
			
			local sel = polyOp.getFaceSelection newMesh
			local elements = getElements newMesh faces:sel
			
			for elem in elements do (
				polyOp.setFaceMatID newMesh elem (random buildingIDRange[1] buildingIDRange[2])
			)
			
			for f in sel do (
				polyOp.setFaceMatID newMesh f (random roofIDRange[1] roofIDRange[2])
			)
		)
		newMesh
	)
	
--	gc()
	 t = timeStamp()
	assignRandomMatID $
	 format "took: % ms.
" (timeStamp() - t)
)

I’ll try to upload a version with some comments later when I have some time, so it’ll be easier to understand what I did there.

Cheers,
o


#18

I’ve added some comments, hopefully it will make it a bit more readable.


(
	-- This function returns and array of bitArrays, each holding the
	-- face IDs of the element.
	-- If faces is supplied with a bitArray, only the face IDs in that
	-- bitArray are checked for their elements.
	fn getElements obj faces:#All =
	(
		-- Check the obj class
		local objClass = classOf obj
		-- Create a temp function (func) that given the object and a face ID returns
		-- the element face IDs.
		-- Depending on the class of obj, use the right method to get the element faces.
		local func
		case objClass of (
			Editable_Poly: fn func obj face = polyOp.getElementsUsingFace obj face
			PolyMeshObject: fn func obj face = polyOp.getElementsUsingFace obj face
			Editable_Mesh: fn func obj face = meshOp.getElementsUsingFace obj face
			Edit_Poly: fn func obj face = (local elem = #{}; obj.getElementsUsingFace elem #{face} #{}; elem)
			default: undefined
		)
		
		-- Initialize some variables
		local elements = #() 	-- The array of the elements
		local usedFaces = #{} 	-- A bitArray to hold all the face IDs that were either checked or were part of elements of other checked faces.
		
		-- Get the number of faces in obj (use the right method based on the class of obj).
		local numFaces = case objClass of (
			Editable_Poly: obj.numFaces
			Editable_Mesh: obj.numFaces
			PolyMeshObject: obj.numFaces 
			Edit_Poly: obj.getNumFaces()
			default: 0
		)
		-- If faces is not a bitArray, make it into a bitArray with all the faces in obj.
		if faces == #All then
			faces = #{1..numFaces}
		
		-- Loop through all the faces in the faces variable.
		-- If the current face ID is already in usedFaces, skip it.
		for f in faces where not usedFaces[f] do (
			-- Using the temp function func, get the face IDs of the 
			-- element the current face is in.
			local elem = func obj f
			-- Append the element's face IDs to the elements array.
			append elements elem
			-- Add the face IDs in the element to the usedFaces bitArray.
			usedFaces += elem
		)
		-- Return the elements array.
		elements
	)


	-- This function creates a snapshot of the given object (the greeble object),
	-- it then assigns random material IDs to the different elements, and different
	-- material IDs to the roofs (the object's selected faces).
	-- Finally, it returns the newly created object (the snapshot).
	fn assignRandomMatID obj =
	(
		-- Turn off the undo buffer and scene redraw to save memory and increase speed.
		with undo off with redraw off (
			-- Create a snapshot of the original object.
			local newMesh = snapshot obj
			-- Convert the new snapshot into an editable_poly (editable_poly is much faster than
			-- editable_mesh when it comes to a lot of geometry related scripted operations)
			convertToPoly newMesh
			
			-- Set local varialbes to define the ID ranges for the roofs and buildings.
			local roofIDRange = [81,99]
			local buildingIDRange = [20,80]
			
			-- Get the current face selection from the new snapshot (this will be the same as
			-- the current face selection on the original object.)
			local sel = polyOp.getFaceSelection newMesh
			-- Get newMesh's elements (the buildings). Supply the faces parameter with 
			-- the current selection to speed up finding elements.
			local elements = getElements newMesh faces:sel
			
			-- Loop through the elements, and assign a random material ID
			-- to all the faces in that element.
			for elem in elements do (
				polyOp.setFaceMatID newMesh elem (random buildingIDRange[1] buildingIDRange[2])
			)
			
			-- Loop through the selected faces, and assign a random material ID
			-- to it.
			for f in sel do (
				polyOp.setFaceMatID newMesh f (random roofIDRange[1] roofIDRange[2])
			)
		)
		-- Return the snapshot mesh.
		newMesh
	)
	
--	gc()
	 t = timeStamp()
	assignRandomMatID $
	 format "took: % ms.
" (timeStamp() - t)
)

Cheers,
o


#19

This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.