Announcements

Between mid-October and November, the content on AREA will be relocated to the Autodesk Community M&E Hub and the Autodesk Community Gallery. Learn more HERE.

Convert a scripted modifier to not be a modifier

Convert a scripted modifier to not be a modifier

voller
Contributor Contributor
1,547 Views
15 Replies
Message 1 of 16

Convert a scripted modifier to not be a modifier

voller
Contributor
Contributor

I found this old script that selects the border of all smoothing groups in an Editable Mesh, in a modifier, that are only visible if you add an Edit Mesh or a Mesh Select on top of the modifier. For the life of me I can't figure out how to change it so that it will only run when I load the script, or as a shortcut, so that it ends with the edges selected on an Editable Mesh or Edit Mesh object that is currently selected. I don't need it as a modifier as I want to add the code to be part of another script.

 

plugin simpleMeshMod SGsToEdgeSel
	name:"SGs to EdgeSel"
	classID:#(0x15d25e23, 0x19ee8ef5)
(
	fn faceFromEdge edge = (edge - 1) / 3 + 1

	on modifyMesh do
	(
		local edges = mesh.edges as bitarray
		local edgeSel = #{}

		for edge in edges do
		(
			local reverseEdge = (meshop.getEdgesReverseEdge mesh edge as array)[1]
			if reverseEdge != undefined and
			   bit.and (getFaceSmoothGroup mesh (faceFromEdge edge)) \
			           (getFaceSmoothGroup mesh (faceFromEdge reverseEdge)) == 0 do
			(
				append edgeSel edge
				edges[reverseEdge] = off
			)
		)

		mesh.selectedEdges = edgeSel
	)
)



 This is what I changed it to. I get no errors, but nothing gets selected either. 

obj = selection[1]

(
	fn faceFromEdge edge = (edge - 1) / 3 + 1

if (classOf obj == Editable_Mesh) do
	(
		local edges = mesh.edges as bitarray
		local edgeSel = #{}

		for edge in edges do
		(
			local reverseEdge = (meshop.getEdgesReverseEdge mesh edge as array)[1]
			if reverseEdge != undefined and
			   bit.and (getFaceSmoothGroup mesh (faceFromEdge edge)) \
			           (getFaceSmoothGroup mesh (faceFromEdge reverseEdge)) == 0 do
			(
				append edgeSel edge
				edges[reverseEdge] = off
			)
		)

		mesh.selectedEdges = edgeSel
	)
)

 

0 Likes
Accepted solutions (3)
1,548 Views
15 Replies
Replies (15)
Message 2 of 16

denisT.MaxDoctor
Advisor
Advisor

does anyone still edit and modify geometry objects with Editable Mesh?
Maybe it will be more useful to find boundaries of poly object's smoothing groups?

0 Likes
Message 3 of 16

voller
Contributor
Contributor

Imported CAD is the reason I'm using Editable Mesh. When you have a large detailed model, where you want to be able to select the original trimmed surface area of what was represented in the original BREP, in a single click for applying material ID's or for unwrapping purposes. Instead of using by angle which does not respect the original trimmed surface borders where the material ID would change, but there is not sharp edge to define it. I can't select just a fillet as it blends in with surfaces on both sides using By Angle.

 

Think of how the nPower's Translater importer Power Edge Mesh feature works. It makes the mesh much easier to work with for unwrapping, viewport performance, and just plain visibility since you don't see all of the unnecessary edges filling up the screen, it appears just as it would in the CAD software. I still use that plugin, but they haven't been fixing the bugs that have been showing up in later versions of Max, so you end up with a few objects that you have to reimport with the built in importer. But when you convert to mesh, it makes all of the hidden edges visible. So I'm making a script that takes that imported object and makes it appear just as the nPower Translator would.

0 Likes
Message 4 of 16

voller
Contributor
Contributor

Here's a small example. Power Edge Mesh on the left, default 3ds Max import on the right when converted to or imported as a mesh. When you get into far more complicated objects, the one on the left is far easier to work with. I'm taking what is on the right, preserving the explicit normals, and hiding the unnecessary edges. The proof of concept works using that scripted Modifier to make only the trimmed surface edges visible. It was one of the requested features for their Body Object when converted to a mesh, but was never implimented.
nPower-left_Default-right2.jpg

0 Likes
Message 5 of 16

denisT.MaxDoctor
Advisor
Advisor
Accepted solution
fn getAdjacentMeshData mesh = 
(
	fn sortbyxy v1 v2 = 
	(
		if (d = v1.x - v2.x) == 0 then (v1.y - v2.y) else d
	)	
	
	edges = #()
	for f=1 to mesh.numfaces do
	(
		vv = getface mesh f
		vv = [vv[1],vv[2],vv[3],vv[1]]
		id = (f-1)*3
		sm = getFaceSmoothGroup mesh f

		for k=1 to 3 do
		(
			e = [vv[k], vv[k + 1], id + k, sm]
			if (e.x > e.y) do swap e.x e.y
			append edges e
		)
	)
	qsort edges sortbyxy
	edges
	
	borders = #{}
	k = 1
	while k < edges.count do
	(
		n = k+1
		if edges[k][1] == edges[n][1] and edges[k][2] == edges[n][2] then
		(
			if edges[k][4] != edges[n][4] do
			(
				append borders edges[k][3]
				append borders edges[n][3]
			)
			k += 2
		)
		else
		(
			append borders edges[k][3]
			k += 1
		)
	)
	
	mesh.selectededges = borders
)

--ee = getAdjacentMeshData $

 

the script you are using is very slow algorithmically...
try mine. I'm sure the code can be optimized, and maybe I'll do it later.

Message 6 of 16

denisT.MaxDoctor
Advisor
Advisor

the edges you are looking for are called "hard edges" and they include "open edges" as well. Edges that need to be made invisible are inversion of "hard".

0 Likes
Message 7 of 16

voller
Contributor
Contributor

Wow, that is crazy fast compared to the previous one! I appreciate that. I'll try and piece this all together now.

 

 

0 Likes
Message 8 of 16

voller
Contributor
Contributor

Is there an easy way to do that? Make them invisible that is, or visible if I make all edges invisible first before running that function. Appears that the mesh setEdgeVis requires the face index as well.

setEdgeVis <mesh> <face_index_integer> <edge_index_integer> <boolean>

 

0 Likes
Message 9 of 16

denisT.MaxDoctor
Advisor
Advisor
Accepted solution

something like this:

 

fn getAdjacentMeshData mesh vis:off sel:on = 
(
	fn sortbyxy v1 v2 = 
	(
		if (d = v1.x - v2.x) == 0 then (v1.y - v2.y) else d
	)	
	
	edges = #()
	for f=1 to mesh.numfaces do
	(
		vv = getface mesh f
		vv = [vv[1],vv[2],vv[3],vv[1]]
		id = (f-1)*3
		sm = getFaceSmoothGroup mesh f

		for k=1 to 3 do
		(
			e = [vv[k], vv[k + 1], id + k, sm]
			if (e.x > e.y) do swap e.x e.y
			append edges e
		)
	)
	qsort edges sortbyxy
	edges
	
	borders = #{}
	k = 1
	while k < edges.count do
	(
		n = k+1
		if edges[k][1] == edges[n][1] and edges[k][2] == edges[n][2] then
		(
			if edges[k][4] != edges[n][4] do
			(
				append borders edges[k][3]
				append borders edges[n][3]
			)
			k += 2
		)
		else
		(
			append borders edges[k][3]
			k += 1
		)
	)
	
	if vis do
	(
		for k=1 to edges.count do
		(
			edge = edges[k][3]
			face = (edge - 1) / 3 + 1
			index = (mod (edge - 1) 3) + 1
			setedgevis mesh face index borders[edge]
		)
		update mesh
	)
	if sel do mesh.selectededges = borders
		
	borders
)

--ee = getAdjacentMeshData $ vis:on sel:off
Message 10 of 16

voller
Contributor
Contributor

You are too good, this is so fast and elegant. I'll try to throw something large at it later and try to break it, but so far it works better than I could have imagined. I can't tell you how much this helps with this workflow.

 

Thank you so much!

0 Likes
Message 11 of 16

voller
Contributor
Contributor

I'm wondering if you could take a peak at what I have so far. If I have one item selected, it works great. It appears to work if I have multiple items selected too, except one crucial step is being ignored for some reason. Before running the code that you created (awesome, can't stress that enough), I apply a ProOptimizer modifier with the settings required to weld and preserve the normals, UVs, etc. Welding vertices seems to destroy the explicit normals, so I'm just falling back to this method... It fails to do poMod.Calculate = true only if there's more than one object selected. It seems to change all of the other settings that I have set, except for that one. I feel like I missed something crucial with my function, and it's not turning on calculate right before collapsing the stack, and right before collapses it into an Editable Mesh that your function then does its magic on. Is this just a possible limitation of ProOptimizer, or did I mess up something simple? Maybe a way to check if it is on before collapsing the stack?

I attached the .max file I'm testing in with a simple imported BREP. If you run the script on a single object, you can select a vertex that is on one of the border edges and move it you'll see that it is welded. If you run the script on multiple objects, then go to one and select that same vertex location, it isn't welded. Which is typical for how 3ds Max changes body objects to meshes, you end up with an element for each trimmed surface.

try (closerolloutfloater MainFloater) catch()


fn weldVertsPreserveNormals obj =
(
	-- 		Weld vertices and preserve explicit normals
		
 -- Check if a ProOptimizer modifier already exists, if not then add one
	poMod = undefined
	
	for mod in obj.modifiers do 
	(
		if classOf mod == ProOptimizer then poMod = mod
	)
	if poMod == undefined do poMod = ProOptimizer()
		-- Add the ProOptimizer modifier to the object if it's not already applied
	if findItem obj.modifiers poMod == 0 do addModifier obj poMod

	-- Set Protect Borders, Keep Material, Texture, and UV Boundaries, Keep Normals as Protected, and Merge Vertices
	poMod.OptimizationMode = 1
	poMod.LockMat = true				
	poMod.LockUV = true
	poMod.keepTextures = true
	poMod.keepNormals = true
	poMod.normalMode = 1
	poMod.mergeVertices = true
	poMod.Calculate = true

 	maxOps.CollapseNodeTo obj 1 off
	
)

fn getAdjacentMeshData mesh vis:off sel:on = 
(
	fn sortbyxy v1 v2 = 
	(
		if (d = v1.x - v2.x) == 0 then (v1.y - v2.y) else d
	)	
	
	edges = #()
	for f=1 to mesh.numfaces do
	(
		vv = getface mesh f
		vv = [vv[1],vv[2],vv[3],vv[1]]
		id = (f-1)*3
		sm = getFaceSmoothGroup mesh f

		for k=1 to 3 do
		(
			e = [vv[k], vv[k + 1], id + k, sm]
			if (e.x > e.y) do swap e.x e.y
			append edges e
		)
	)
	qsort edges sortbyxy
	edges
	
	borders = #{}
	k = 1
	while k < edges.count do
	(
		n = k+1
		if edges[k][1] == edges[n][1] and edges[k][2] == edges[n][2] then
		(
			if edges[k][4] != edges[n][4] do
			(
				append borders edges[k][3]
				append borders edges[n][3]
			)
			k += 2
		)
		else
		(
			append borders edges[k][3]
			k += 1
		)
	)
	
	if vis do
	(
		for k=1 to edges.count do
		(
			edge = edges[k][3]
			face = (edge - 1) / 3 + 1
			index = (mod (edge - 1) 3) + 1
			setedgevis mesh face index borders[edge]
		)
		update mesh
	)
	if sel do mesh.selectededges = borders
		
	borders
)

Rollout Menu01 "Convert To T-Mesh"
(
	button convertTMesh "Convert to T-Mesh" pos:[2,6] width:100 height:20
	
	on convertTMesh pressed do
	(
		obj = selection[1]
		if selection.count == 0 then 
		(
			messagebox "Please select at least one mesh object to convert to T-Mesh"
		) 
		else
		(
			for obj in selection do
			(
				if (classOf obj == Editable_Mesh) or (classOf obj.modifiers[#Edit_Mesh] == Edit_Mesh) or (classOf obj == Editable_Poly) or (classOf obj.modifiers[#Edit_Poly] == Edit_Poly) or (classOf obj == Body_Object) then
				(
					(
						--Weld vertices and preserve explicit normals
						ff = weldVertsPreserveNormals obj
	
					)

					(
						--Make only smoothing group borders visible
						ee = getAdjacentMeshData obj vis:on sel:off
					)
				)
				else
				(
					messagebox "Not a valid mesh object"
				)
			)
		)

		
	)
)

Rollout Menu02 "How To Use"
(
	label typeH2U01 "1. Select a single mesh or body object that has properly assigned smoothing groups.
Each face should not have more than one smoothing group assigned, it may produce unexpected results. It will use the smoothing groups found in imported CAD or chamfered objects to create the T-Mesh border edges. Copy the object if needed, as this script will convert it to a T-Mesh.
If running the script on an instance, it will update all of them when the script is run on one object."
	width:260 height:135 align:#left

	label typeH2U02 "2. Click the Convert to T-Mesh button." align:#left
	label typeH2U03 "3. The selected mesh or body object should now be an Editable Mesh, with only the trimmed surface edges being visible as if it were a BREP.
As well as having easily selectable surfaces for applying material IDs or unwrapping, the vertices will be joined. Something that converting a body object to a mesh will not do." width:260 height:100 align:#left
)

Rollout Menu03 "About"
(
	label typeAbout01 "Convert To T-Mesh v0.25"
)



MainFloater = NewRolloutFloater "Convert To T-Mesh" 300 400
addRollout Menu01 Mainfloater
addRollout Menu02 Mainfloater
addRollout Menu03 Mainfloater

 

0 Likes
Message 12 of 16

denisT.MaxDoctor
Advisor
Advisor
Accepted solution

ProOptimizer works only in the Modifier Panel when the editing object (node) is current. So for multiple case we need to put all editing object to current editing mode:

 

 

try(destroydialog MeshConvertRollout) catch()
rollout MeshConvertRollout "Mesh Beautifier" width:191
(
	button convert_bt "Beautify Mesh" width:172 align:#center
	
	fn weldVertsPreserveNormals obj =
	(
		poMod = ProOptimizer()
		addModifier obj poMod

		-- Set Protect Borders, Keep Material, Texture, and UV Boundaries, Keep Normals as Protected, and Merge Vertices
		poMod.OptimizationMode = 1
		poMod.LockMat = true				
		poMod.LockUV = true
		poMod.keepTextures = true
		poMod.keepNormals = true
		poMod.normalMode = 1
		poMod.mergeVertices = true
		poMod.Calculate = true

		--maxOps.CollapseNodeTo obj 1 off
		converttomesh obj
	)

	fn getAdjacentMeshData mesh vis:on sel:on = 
	(
		fn sortByXY v1 v2 = 
		(
			if (d = v1.x - v2.x) == 0 then (v1.y - v2.y) else d
		)	
		
		edges = #()
		for f=1 to mesh.numfaces do
		(
			vv = getface mesh f as point4
			vv[4] = vv[1]
			id = (f-1)*3
			sm = getFaceSmoothGroup mesh f

			for k=1 to 3 do
			(
				e = [vv[k], vv[k + 1], id + k, sm]
				if (e.x > e.y) do swap e.x e.y
				append edges e
			)
		)
		qsort edges sortByXY
		--format ">> % %\n" edges.count (mesh.numfaces*3)
		
		borders = #{}
		borders.count = edges.count
		k = 1
		while k < edges.count do
		(
			n = k+1
			if edges[k][1] == edges[n][1] and edges[k][2] == edges[n][2] then
			(
				if edges[k][4] != edges[n][4] do
				(
					append borders edges[k][3]
					append borders edges[n][3]
				)
				k += 2
			)
			else
			(
				append borders edges[k][3]
				k += 1
			)
		)
		
		if vis do
		(
			for k=1 to edges.count do
			(
				edge = edges[k][3]
				face = (edge - 1) / 3 + 1
				index = (mod (edge - 1) 3) + 1
				setedgevis mesh face index borders[edge]
			)
			update mesh
		)
		if sel do mesh.selectededges = borders
			
		borders
	)

	on convert_bt pressed do undo "Beautify Mesh" on 
	(
		nodes = selection as array
		max modify mode
		for obj in nodes where canConvertTo obj Editable_Mesh do
		(
			modPanel.setCurrentObject obj 
			weldVertsPreserveNormals obj
			getAdjacentMeshData obj vis:on sel:on
		)
		max create mode
	)

	on MeshConvertRollout open do
	(
	)
)
createdialog MeshConvertRollout

 

 

select all nodes to "convert" and press the button

0 Likes
Message 13 of 16

denisT.MaxDoctor
Advisor
Advisor

the mod panel flicker looks ugly, but I already showed here how to disable MAX redraw to make the flicker less "stressful".

0 Likes
Message 14 of 16

voller
Contributor
Contributor

Ahh. That would explain why. I was able to trick it partially by putting in a check to see if poMod.Calculate != true and then setting it to true. Strangely, it worked for every object except for the first one in the selection. I so far tested it on a pretty dense mesh of 260k triangles, and it seemed to work fine. It did take a bit to do its thing, about 10-15 seconds or so. But that is much quicker than that modifier script I started with. 🙂

0 Likes
Message 15 of 16

denisT.MaxDoctor
Advisor
Advisor

@voller wrote:

 I so far tested it on a pretty dense mesh of 260k triangles, and it seemed to work fine. It did take a bit to do its thing, about 10-15 seconds or so...


could you post a dense mesh example, so I can do a test?

0 Likes
Message 16 of 16

voller
Contributor
Contributor

I'll send you a private message with a link. It's not a file I can share freely online.

0 Likes