Notifications
Clear all

[Closed] Detach Specific Parts Of Object

Here is a different approach in a similar line of thought as the previous ones… poor

It’s a little faster, and although it works with the test model, it is more sensitive to fail.

If it works, one way to make it faster for large models, could be by improving the “get elements” part. There is a code somewhere on this forum to do it.

(
	gc()
	st = timestamp(); sh = heapfree
	
	fn GetAveragePos mesh verts:#{} =
	(
		avg = [0,0,0]
		for j in verts do avg += getvert mesh j
		avg /= verts.numberset
	)
	
	with undo off
	(
		/* GET MAIN PIPE */
		tmesh = snapshotasmesh $
		openedges = meshop.getopenedges tmesh
		openverts = meshop.getvertsusingedge tmesh openedges
		meshop.weldvertsbythreshold tmesh openverts 0.0001
		update tmesh
		
		allFaces = #{1..tmesh.numfaces}
		largestArea  = 0
		mainPipeFaces = #{}
		for j in allFaces do
		(
			element = meshop.getelementsusingface tmesh j
			area = meshop.getfacearea tmesh element
			if area > largestArea do
			(
				largestArea = area
				mainPipeFaces = element
			)
			for i in element do deleteitem allFaces i
		)

		free tmesh
		/* GET MAIN PIPE */

		tmesh = snapshotasmesh $
		
		mainElements = #()
		addElementsFaces = #{1..tmesh.numfaces} - mainPipeFaces
		
		for j in mainPipeFaces do
		(
			element = meshop.getelementsusingface tmesh j
			verts = meshop.getvertsusingface tmesh element
			avgPos = GetAveragePos tmesh verts:verts
			
			append mainElements #(element, avgPos)
			for i in element do deleteitem mainPipeFaces i
		)

		addElements = #()
		for j in addElementsFaces do
		(
			element = meshop.getelementsusingface tmesh j
			verts = meshop.getvertsusingface tmesh element
			avgPos = GetAveragePos tmesh verts:verts
			
			append addElements #(element, avgPos)
			for i in element do deleteitem addElementsFaces i
		)
		
		for j in addElements do
		(
			pos = j[2]
			dist = 1E9
			found = 0
			for i = 1 to mainElements.count do
			(
				d = distance pos mainElements[i][2] 
				if d < dist do
				(
					dist = d
					found = i
				)
			)
			join mainElements[found][1] j[1]
		)
		
		for j in mainElements do
		(
			verts = meshop.getvertsusingface tmesh j[1]
			polys = polyop.getfacesusingvert $ verts
		
			element = polyop.getelementsusingface $ polys
			polyop.detachFaces $ element delete:false asNode:true
		)

		free tmesh
		gc light:on
		format "ELEMENTS: %
" mainElements.count
	)
	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
)

I’ve been looking into the Splitting/Slicing process. If you wouldn’t want to use ProBooleans, another solution could be the following one.

You can change the radius of the virtual collision sphere, or you could use a box as a collision test if that geometry suits better your needs.

I tested it with the Snake file, and it worked well.

(
	node   = $Snake
	radius = 25.0
	
	planes = for j in $plane* collect #(ray j.pos j.dir, #{})
	
	for j = 1 to node.numverts do
	(
		vpos = polyop.getvert node j
		for i in planes where distance vpos i[1].pos < radius do i[2][j] = true
	)
	
	for j in planes do polyop.slice node (polyop.getfacesusingvert node j[2]) j[1]
	
	polyop.splitedges node (polyop.getedgeselection node)
)

Hey Jorge, your last code (not poly slice method) works 99% which is great.
Let me explain why I can not use poly slice method. Imagine facade wall with few windows, doors, ornamens and some additional asets all attached as one object. Now I need to cut this wall to make modular objects which can be combined as a puzzle and export later to game engine. So objects will probably have average triangle count. Optimization of code is not main thing here.

I prepared new test file (see attachment) for testing your code. This is the screenshot of the scene

Also I make another version of “cutMesh” function using simple boolean operation but not works in this case (first commented function in the code) so I stay with proboolean version. I use max2014 for testing.
Just open the file and run the code and you’ll see your method as first from above. A small element
is not detached and crossing over other element. Model ei. result in middle is only cutted version.
You can run only commented fn to see what is the problem.

-- SIMPLE BOOLEAN OPERATION (Fast Method)
-- slice plane need to cover whole poligon to be cutted
-- but in this cace we use multi polygonal slice plane
-----------------------------------------------------------------------------
/*fn cutMesh mesh nodes = with redraw off
(
	clearSelection()
	if GetCommandPanelTaskMode() != #create do SetCommandPanelTaskMode #create
	mesh = snapshot mesh ; mesh.selectedfaces = mesh.selectededges = #{}
	object = Editable_Mesh pos:[0,0,0] wirecolor:yellow name:"Cards"	
	for i in nodes do attach object (snapshotasmesh i)
	mesh - object ; node = converttopoly mesh ; delete object
	polyop.deleteFaces node node.selectedfaces delIsoVerts:on
	node.SelectEdgeLoop() ; node.wirecolor = yellow
	polyop.splitEdges node (polyOp.getedgeselection node)
	polyop.collapseDeadStructs node
	polyop.deleteIsoVerts node
	free nodes ; gc light:on ; node
)
obj = cutMesh $OBJ ($'CutPlanes*' as array)
move obj [0,0,100] ; select obj
SetCommandPanelTaskMode #modify
subobjectLevel = 2
*/
------------------------------------------------------------------
-- PRO BOOLEAN OPERATION (Precise method) *** works with max2017
	
fn cutMesh mesh nodes = with redraw off
(
	
	clearSelection() ; hwnd = undefined
	if GetCommandPanelTaskMode() != #modify do SetCommandPanelTaskMode #modify
	node = ProBoolean ; mesh = snapshot mesh
	object = Editable_Mesh pos:[0,0,0] wirecolor:yellow name:"Cards"	
	for i in nodes do attach object (snapshotasmesh i) ; nodes = #() --; update object
	node.createBooleanObject mesh undefined 4 2 0 ; select mesh
	node.SetImprint mesh on
	node.SetPlanarEdgeRemoval mesh 2
	local fixStrings = if (maxVersion())[1]/1000 >= 19 then #("CustButton","Change Operation") else #("RollupPanelTitle","Parameters")
	-- press "Change operation" button to refresh current operation
	for c in (windows.getChildrenHWND #max) while hwnd == undefined where c[4] == fixStrings[1] and c[5] == fixStrings[2] do 
	(
		hwnd = UIAccessor.GetParentWindow c[1]
	)
	if hwnd != undefined do
	(
		node.SetOperandSel mesh 0 on
		if (bt = windows.getChildHWND hwnd "Change Operation") != undefined do UIAccessor.PressButton bt[1]
	)
	node.SetBoolOp mesh 2
	node.SetOperandB mesh object 2 0
	SetCommandPanelTaskMode #modify
	node = converttopoly mesh
	node.wirecolor = black
	polyop.splitEdges node (polyOp.getedgeselection node)
	polyop.collapseDeadStructs node
	polyop.deleteIsoVerts node
	free nodes ; gc light:on ; node
)
obj = cutMesh $OBJ ($'CutPlanes*' as array)
move obj [0,0,100]
SetCommandPanelTaskMode #modify
subobjectLevel = 2
	
	--PolyTools Detach formula
-----------------------------------------------------------------------------------
(
	gc() ; obj = $
	st = timestamp(); sh = heapfree

	fn GetAveragePos mesh verts:#{} =
	(
		avg = [0,0,0]
		for j in verts do avg += getvert mesh j
		avg /= verts.numberset
	)

	with undo off
	(
		/* GET MAIN PIPE */
		tmesh = snapshotasmesh obj
		openedges = meshop.getopenedges tmesh
		openverts = meshop.getvertsusingedge tmesh openedges
		meshop.weldvertsbythreshold tmesh openverts 0.0001
		update tmesh
		
		local getElementsUsingFaceMesh = meshop.getelementsusingface
		local getElementsUsingFacePoly = polyop.getelementsusingface
		local getFaceArea = meshop.getfacearea
		local getVertsUsingFace = meshop.getvertsusingface
		local getFacesUsingVert = polyop.getfacesusingvert
		local detachFaces = polyop.detachFaces
		
		allFaces = #{1..tmesh.numfaces}
		largestArea  = 0
		mainPipeFaces = #{}
		for j in allFaces do
		(
			element = getElementsUsingFaceMesh tmesh j
			area = getFaceArea tmesh element
			if area > largestArea do
			(
				largestArea = area
				mainPipeFaces = element
			)
			for i in element do deleteitem allFaces i
		)

		free tmesh
		/* GET MAIN PIPE */

		tmesh = snapshotasmesh obj
		
		mainElements = #()
		addElementsFaces = #{1..tmesh.numfaces} - mainPipeFaces
		
		for j in mainPipeFaces do
		(
			element = getElementsUsingFaceMesh tmesh j
			verts = getVertsUsingFace tmesh element
			avgPos = GetAveragePos tmesh verts:verts
			append mainElements #(element, avgPos)
			for i in element do deleteitem mainPipeFaces i
		)

		addElements = #()
		for j in addElementsFaces do
		(
			element = getElementsUsingFaceMesh tmesh j
			verts = getVertsUsingFace tmesh element
			avgPos = GetAveragePos tmesh verts:verts
			
			append addElements #(element, avgPos)
			for i in element do deleteitem addElementsFaces i
		)
		for j in addElements do
		(
			pos = j[2]
			dist = 1E9
			found = 0
			for i = 1 to mainElements.count do
			(
				d = distance pos mainElements[i][2] 
				if d < dist do
				(
					dist = d
					found = i
				)
			)
			join mainElements[found][1] j[1]
		)
		
		for j in mainElements do
		(
			verts = getVertsUsingFace tmesh j[1]
			polys = getFacesUsingVert obj verts
		
			element = getElementsUsingFacePoly obj polys
			detachFaces obj element delete:false asNode:true name:(uniquename "CUTOBJ")
			objects[objects.count].wirecolor = random black white
		)

		free tmesh
		gc light:on
		select $'CUTOBJ*'
		move selection [0,0,100]	
		format "ELEMENTS: %
" mainElements.count
	)
	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
)

Thanks in advance!

2 Replies
(@polytools3d)
Joined: 10 months ago

Posts: 0

I see. Well, these latest models and the original model are black and white. None of the code I proposed will work with these models.

Same as what Denis said, I don’t see a simple generic solution for all these different problems.

  1. Let the artist do the work.
  2. Create a tool like Probooleans, but much more complex, where you can specify any shape for the cuts and then find a way to break the elements apart.

The second method might be a nightmare to develop, but I guess it could be done. I don’t know how much of these work you need to do, but my feeling is that developing such a tool will take longer than what it will take to the artists to do the job manually. Unless this needs to be done millions of time.

(@gazybara)
Joined: 10 months ago

Posts: 0

Yup. Artists will do the job manually faster for 100 models probaly . After that start nightmares:)

Solved problem with ProBoolean in max2017. I edited previous post.
This is weird. On the places where is complex cut, above code doing great job but on simple not.

there is no and can’t be good universal solution for searching and automatically finding all pieces for all cut parts.

my suggestion is –

because anyway an artist has to spend time making the geometry and setting cutting planes (objects) it’s not a big deal and time consuming to bind some vertices which in case of changing topology have to be in the same part (group of elements). after that the cutting and detaching process is trivial.
the binding shouldn’t take more than 5% of artists time.

3 Replies
(@gazybara)
Joined: 10 months ago

Posts: 0

I agree. Universal solution not exists and I like to spend time to solve problems in special cases where mxs can help and do it by hand. But for the easy cases why would speed up the whole process?

Another idea.
#1 collect and detach all elements detach that share selected edges (group A)
#2 collect and detach all other elements (group B)
#3 add push modifier with lover value on group B
#4 find which element in group B is collide with element in group A
#5 set value of push modifier to zero and attach with corresponding element

(@denist)
Joined: 10 months ago

Posts: 0

what a cut plane doesn’t really slice any face?
what if ‘free’ elements from B are pretty far from any element from A?
what if B is inside A?

there are many ‘ifs’ that can make a task very specific.
i don’t see a problem when an artist spends 7 hours making mesh and cut planes, and 10 minutes to bind pieces. You only need to make a friendly binding tool… which can use any “smart” searching mecanics

(@gazybara)
Joined: 10 months ago

Posts: 0

#what a cut plane doesn’t really slice any face?
Then nothing. We always starts if some edges are selected and are border edges.

#what if ‘free’ elements from B are pretty far from any element from A
Probaby will stay detached and later attach manually

#what if B is inside A
it will not happen, but if it happens then same thing as 2nd question but manually will be deleted

I changed the GetAveragePos() function to use faces centers and it seems to work now with the triangle model. However, I would bet it will fail with others.

(
	gc() ; obj = $
	st = timestamp(); sh = heapfree

	fn GetAveragePos mesh faces:#{} =
	(
		avg = [0,0,0]
		for j in faces do avg += meshop.getfacecenter mesh j
		avg /= faces.numberset
	)

	with undo off
	(
		/* GET MAIN PIPE */
		tmesh = snapshotasmesh obj
		openedges = meshop.getopenedges tmesh
		openverts = meshop.getvertsusingedge tmesh openedges
		meshop.weldvertsbythreshold tmesh openverts 0.0001
		update tmesh
		
		local getElementsUsingFaceMesh = meshop.getelementsusingface
		local getElementsUsingFacePoly = polyop.getelementsusingface
		local getFaceArea = meshop.getfacearea
		local getVertsUsingFace = meshop.getvertsusingface
		local getFacesUsingVert = polyop.getfacesusingvert
		local detachFaces = polyop.detachFaces
		
		allFaces = #{1..tmesh.numfaces}
		largestArea  = 0
		mainPipeFaces = #{}
		for j in allFaces do
		(
			element = getElementsUsingFaceMesh tmesh j
			area = getFaceArea tmesh element
			if area > largestArea do
			(
				largestArea = area
				mainPipeFaces = element
			)
			for i in element do deleteitem allFaces i
		)

		free tmesh
		/* GET MAIN PIPE */

		tmesh = snapshotasmesh obj
		
		mainElements = #()
		addElementsFaces = #{1..tmesh.numfaces} - mainPipeFaces
		
		for j in mainPipeFaces do
		(
			element = getElementsUsingFaceMesh tmesh j
			--verts = getVertsUsingFace tmesh element
			--avgPos = GetAveragePos tmesh verts:verts
			avgPos = GetAveragePos tmesh faces:element
			append mainElements #(element, avgPos)
			for i in element do deleteitem mainPipeFaces i
		)

		addElements = #()
		for j in addElementsFaces do
		(
			element = getElementsUsingFaceMesh tmesh j
			--verts = getVertsUsingFace tmesh element
			--avgPos = GetAveragePos tmesh verts:verts
			avgPos = GetAveragePos tmesh faces:element
			
			append addElements #(element, avgPos)
			for i in element do deleteitem addElementsFaces i
		)
		
		for j in addElements do
		(
			pos = j[2]
			dist = 1E9
			found = 0
			for i = 1 to mainElements.count do
			(
				d = distance pos mainElements[i][2]
				if d < dist do
				(
					dist = d
					found = i
				)
			)
			join mainElements[found][1] j[1]
		)
		
		for j in mainElements do
		(
			verts = getVertsUsingFace tmesh j[1]
			polys = getFacesUsingVert obj verts
		
			element = getElementsUsingFacePoly obj polys
			detachFaces obj element delete:false asNode:true name:(uniquename "CUTOBJ")
			objects[objects.count].wirecolor = random black white
		)
		
		($CUTOBJ*).pos.z += 100
		select ($CUTOBJ*)

		free tmesh
		gc light:on
		format "ELEMENTS: %
" mainElements.count
	)
	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
)

It works now. This last example represents one of many problematic cases which is annoying to do it by hand. Large number of small element, selecting, attaching, detachind, UV, mat ID, SmoothingGroups etc. At least we have code that will speed up the process for this particular task.

Thank you Jorge. I really appreciate all your work and suggestions here.
Of course this also applies to DenisT.

BTW sorry for my english

according to Jorge’s code it’s better to find main element before splitting edges.
we can split after that.

but welding after splitting might make a mesh not exactly the same than before split.

Yes. I saw that and remove split and weld fn’s. Detach fn will split geometry after all

Page 3 / 3