UVW Seem with GW


#1

Someone on scriptspot wants to have the ability to toggle on and off the UV seems of an object.

http://www.scriptspot.com/forums/3ds-max/scripts-wanted/wanted-script-to-display-uv-seam-on-mesh-without-having-to-put-an-unwrap-modfiier#comment-23060

I figured I would take a gander at the situation and see what I could come up with.

I’ve only spent a few minutes so far on it but I’m going to go with the GW.Polyline route. I guess you could also do splines with scripted controllers on the knots but that gets to be very taxing on the computer.

This script just does lines for all verts. Next I’ll add in the verts based on the UVW.


delete objects
myBox = box width:10 height:10 length:10 pos:[0,0,50] wirecolor:blue
convertToPoly myBox

unRegisterRedrawViewsCallback showObjectNames
fn showObjectNames = if (gw.wTransPoint [0,0,0]) != undefined do
(
	gw.setTransform (Matrix3 1)
 	gw.setColor #line green
-- 	gw.polyLine #([5,5,5],[10,10,10],[50,50,50]) true
	
	for o in objects where classof o == Editable_Poly do
	(
		vertsPts = for i = 1 to o.numVerts collect polyop.getVert o i
		gw.polyLine vertsPts true
	)	

	-- Update the viewports 
	gw.enlargeUpdateRect #whole
	gw.updateScreen()
)
registerRedrawViewsCallback showObjectNames
forceCompleteRedraw()


#2

good luck…


#3

You seem to be insinuating that gw is not the way go to on this one? then what would you suggest?


#4

try first to check how long takes to find all uvw (map) seam edges for… let’s say 10,000 faces mesh.


#5

Try to draw 1000 GW lines and see the viewport performance.


#6

Yeah GW doesn’t see to great when it comes to high volumes. That is a bummer.

The rough of splines could work but may become taxing depending on the functionality…

hmmm


#7
   If you don’t need to update the model position or topology, then you could gather the vertices positions outside the callback, which will give the callback a huge boost.
   
   Could also use a trigger on mouse up/down if position or topology change and renew the array of points.
   
   However, as Denis said, the biggest drop in performance is going to be in getting the uvw open edges.
   
   Unfortunately Max does not provide a function for that, so you need to do some dirty work to get them. Not really hard, but painfully slow.
   
   Once you get the open edges they would need either to be converted to a sorted array of point (per cluster I guess) or you could draw one line at time, which also adds some performance drop.
   
   In this example, 60,000 faces, I do not see any mayor performance drop, but it is completely different than gathering and drawing just the uvw open edges.

   delete objects
  myBox = box width:10 height:10 length:10 lengthsegs:100 widthsegs:100 heightsegs:100 pos:[0,0,50] wirecolor:red
  convertToPoly myBox
   
  unRegisterRedrawViewsCallback showObjectNames
  gw.setTransform (Matrix3 1)
  vertsPts = for i = 1 to myBox.numVerts collect polyop.getVert myBox i
  
  fn showObjectNames =
  (
  	gw.setColor #line green
  	gw.polyLine vertsPts true
  )
  registerRedrawViewsCallback showObjectNames
  forceCompleteRedraw()
    

#8

Here is a script that might be useful. Haven’t tested too much, but it seems to work, although it’s very slow.

There is room for optimization and improvements so I hope someone can get their hands on it. Meanwhile I’ll try to work on it a little more.

Hope someone find it useful.


 (
 
 	unRegisterRedrawViewsCallback cbDrawOpenEdges
 	
 	global RO_DISPLAY_UV_OE
 	global cbDrawOpenEdges
 	global cbSelectionChanged
 	
 	local vertexPos = #()
 	local uvChannel
 	local edgesColor = [0,255,0]
 
 	fn cbDrawOpenEdges = (
 		gw.setTransform (Matrix3 1)
 		gw.setColor #line edgesColor
 		for j in vertexPos do gw.polyLine j false
 	)
 
 	fn cbSelectionChanged mForce:false = (
 
 		unRegisterRedrawViewsCallback cbDrawOpenEdges
 		
 		if ($ != undefined) do (
 			
 			setwaitcursor()
 
 			st = timeStamp()
 
 			obj = snapshotasmesh selection[1]
 			allChannels = #()
 			
 			for j = 1 to meshop.getNumMaps obj do (
 				if (meshop.getMapSupport obj j == true) do append allChannels j
 			)
 			
 			RO_DISPLAY_UV_OE.ddl_channel.items = for j in allChannels collect j as string
 			
 			if (mForce == false) do (
 				uvChannel = allChannels[1]
 				RO_DISPLAY_UV_OE.ddl_channel.selection = 1
 			)
 
 			numTFaces = meshop.getNumMapFaces obj uvChannel
 			numTVerts = meshop.getNumMapVerts obj uvChannel
 			
 			facesTVertsIdx = for j = 1 to numTFaces collect (meshop.getMapFace obj uvChannel j)
 			
 			emesh = trimesh()
 			emesh.numverts = numTVerts
 			emesh.numfaces = numTFaces
 			
 			for j = 1 to numTFaces do (
 				objFaceTVerts = facesTVertsIdx[j]
 				setFace emesh j objFaceTVerts.x objFaceTVerts.y objFaceTVerts.z
 			)
 			
 			meshOpenEdges = meshop.getOpenEdges emesh
 			sharedFaces = meshop.getFacesUsingEdge emesh meshOpenEdges
 
 			vertexPos = #()
 			foundEdges = #()
 			
 			for j in sharedFaces do (
 				objFaceVerts = getFace obj j
 				
 				edge1 = j*3 - 2
 				edge2 = j*3 - 1
 				edge3 = j*3
 
 				edges = #{edge1, edge2, edge3} * meshOpenEdges
 					
 				if (finditem edges edge1) > 0 do (
 					v1Idx = objFaceVerts.x
 					v2Idx = objFaceVerts.y
 					if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
 						v1 = getVert obj v1Idx
 						v2 = getVert obj v2Idx
 						append foundEdges [v2Idx, v1Idx]
 						append vertexPos #(v1, v2)
 					)
 				)
 				if (finditem edges edge2) > 0 do (
 					v1Idx = objFaceVerts.y
 					v2Idx = objFaceVerts.z
 					if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
 						v1 = getVert obj v1Idx
 						v2 = getVert obj v2Idx
 						append foundEdges [v2Idx, v1Idx]
 						append vertexPos #(v1, v2)
 					)
 				)
 				if (finditem edges edge3) > 0 do (
 					v1Idx = objFaceVerts.z
 					v2Idx = objFaceVerts.x
 					if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
 						v1 = getVert obj v1Idx
 						v2 = getVert obj v2Idx
 						append foundEdges [v2Idx, v1Idx]
 						append vertexPos #(v1, v2)
 					)
 				)
 					
 			)
 
 			format "Found %	UV Open Edges in %s
" vertexPos.count ((timeStamp() - st) / 1000.0)
 
 			registerRedrawViewsCallback cbDrawOpenEdges
 
 			delete obj
 			delete emesh
 			
 			gc light:true
 			
 			setArrowCursor()
 			
 		)
 		
 		forceCompleteRedraw()
 		
 	)
 
 	
 	try(destroyDialog RO_DISPLAY_UV_OE) catch()
 	rollout RO_DISPLAY_UV_OE "UV Open Edges" width:160 height:164
 	(
 		checkbutton bt_enable "Display UV Open Edges" pos:[8,16] width:144 height:32
 		dropDownList ddl_channel "UV Channel:" pos:[8,56] width:144 height:40 enabled:false
 		button bt_update "Update" pos:[8,128] width:144 height:28 enabled:false
 
 		fn destroy = (
 			callbacks.removeScripts #selectionSetChanged
 			unRegisterRedrawViewsCallback cbDrawOpenEdges
 			forceCompleteRedraw()
 		)
 		
 		on RO_DISPLAY_UV_OE close do destroy()
 		
 		on bt_enable changed arg do
 		(
 			bt_update.enabled = arg
 			ddl_channel.enabled = arg
 			destroy()
 			
 			if (arg == true) do (
 				callbacks.addscript #selectionSetChanged "cbSelectionChanged()"
 				cbSelectionChanged()
 			)
 		)
 		
 		on bt_update pressed do cbSelectionChanged()
 		
 		on ddl_channel selected arg do (
 			uvChannel = (ddl_channel.selected as integer)
 			cbSelectionChanged mForce:true
 		)
 
 	)
 
 	createDialog RO_DISPLAY_UV_OE
 	
 )
 

#9

Have you looked into the XViewChecker Interface? It supports edge display and can have custom coded tools applied.

-Eric


#10

Nicely done. That is a pretty nice script you have there Rodríguez


#11

Not deeply. I just gave it a quick overview but couldn’t find a ready to use function to display the uv open edges, so I went the gw direction.

I thought it couldn’t be customized, but now that you mention it I’ll see what I can do.


#12

Thanks John,

I wish I could get it working at least four times faster for getting the edges, but I burned a few neurons already, have to recover them before a new attempt.

But I do hope some smart guys here and at ScriptSpot can tackle it.


#13

There are limits in what you can do, but you can create scripted functions and execute them through the XViewChecker interface.

-Eric


#14

Here is an improved version that should work in Max 9+.

Finding the UV open Edges:
90,000 polygons 12,000 open edges around 3X faster
20,000 polygons 6,000 open edges around 2X faster

Displaying the edges is still slow, but although it is drawing 10 times more edges, I haven’t noticed any mayor drop in performance. The biggest impact is because it is drawing one set of edges at a time.

(
 
 	unRegisterRedrawViewsCallback cbDrawOpenEdges
 	
 	global RO_DISPLAY_UV_OE
 	global cbDrawOpenEdges
 	global cbSelectionChanged
 	
 	local vertexPos   = #()
 	local uvChannel   = 1
 	local edgesColor  = [0,255,0]
 	local edgeOffset  = 0.03
 	local forceRedraw = false
 
 	fn cbDrawOpenEdges = (
 		gw.setTransform (Matrix3 1)
 		gw.setColor #line edgesColor
 		for j in vertexPos do gw.polyLine j true
 
 		-- Needed in some Max versions or used dirvers
 		if forceRedraw do (
 			gw.enlargeUpdateRect #whole
 			gw.updateScreen()
 		)
 	)
 
 	fn cbSelectionChanged mForce:false = (
 
 		unRegisterRedrawViewsCallback cbDrawOpenEdges
 		
 		if ($ != undefined) do (
 			
 			setwaitcursor()
 
 			st = timeStamp()
 
 			obj = snapshotasmesh selection[1]
 
 			allChannels = for j = 1 to meshop.getNumMaps obj where (meshop.getMapSupport obj j) collect j
 			
 			RO_DISPLAY_UV_OE.ddl_channel.items = for j in allChannels collect j as string
 			
 			if (mForce == false) do (
 				uvChannel = allChannels[1]
 				RO_DISPLAY_UV_OE.ddl_channel.selection = 1
 			)
 
 			numTFaces = meshop.getNumMapFaces obj uvChannel
 			numTVerts = meshop.getNumMapVerts obj uvChannel
 			
 			facesTVertsIdx = for j = 1 to numTFaces collect (meshop.getMapFace obj uvChannel j)
 			
 			emesh = trimesh()
 			setMesh emesh numverts:numTVerts numfaces:numTFaces
 			setMesh emesh faces:facesTVertsIdx
 			
 			objOpenEdges = meshop.getOpenEdges obj
 			meshOpenEdges = (meshop.getOpenEdges emesh) - objOpenEdges
 
 			vertexPos = #()
 						
 			sharedFaces = meshop.getFacesUsingEdge emesh meshOpenEdges
 			foundEdges = #()
 			
 			for j in sharedFaces do (
 				
 				objFaceVerts = getFace obj j
 				
 				edge1 = j*3 - 2
 				edge2 = j*3 - 1
 				edge3 = j*3
 				
 				n1 = (getFaceNormal obj j) * edgeOffset
 
 				if (finditem meshOpenEdges edge1) > 0 do (
 					v1Idx = objFaceVerts.x
 					v2Idx = objFaceVerts.y
 					if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
 						v1 = getVert obj v1Idx
 						v2 = getVert obj v2Idx
 						n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
 						append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
 						append foundEdges [v2Idx, v1Idx]
 					)
 				)
 				if (finditem meshOpenEdges edge2) > 0 do (
 					v1Idx = objFaceVerts.y
 					v2Idx = objFaceVerts.z
 					if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
 						v1 = getVert obj v1Idx
 						v2 = getVert obj v2Idx
 						n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
 						append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
 						append foundEdges [v2Idx, v1Idx]
 					)
 				)
 				if (finditem meshOpenEdges edge3) > 0 do (
 					v1Idx = objFaceVerts.z
 					v2Idx = objFaceVerts.x
 					if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
 						v1 = getVert obj v1Idx
 						v2 = getVert obj v2Idx
 						n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
 						append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
 						append foundEdges [v2Idx, v1Idx]
 					)
 				)
 					
 			)
 			
 			sharedFaces = meshop.getFacesUsingEdge obj objOpenEdges
 			
 			for j in sharedFaces do (
 				
 				objFaceVerts = getFace obj j
 				
 				edge1 = j*3 - 2
 				edge2 = j*3 - 1
 				edge3 = j*3
 				
 				n1 = (getFaceNormal obj j) * edgeOffset
 
 				if (finditem objOpenEdges edge1) > 0 do (
 					v1Idx = objFaceVerts.x
 					v2Idx = objFaceVerts.y
 					v1 = getVert obj v1Idx
 					v2 = getVert obj v2Idx
 					n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
 					append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
 				)
 				if (finditem objOpenEdges edge2) > 0 do (
 					v1Idx = objFaceVerts.y
 					v2Idx = objFaceVerts.z
 					v1 = getVert obj v1Idx
 					v2 = getVert obj v2Idx
 					n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
 					append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
 				)
 				if (finditem objOpenEdges edge3) > 0 do (
 					v1Idx = objFaceVerts.z
 					v2Idx = objFaceVerts.x
 					v1 = getVert obj v1Idx
 					v2 = getVert obj v2Idx
 					n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
 					append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
 				)
 					
 			)
 
 			format "Found %	UV Open Edges in %s
" vertexPos.count ((timeStamp() - st) / 1000.0)
 
 			registerRedrawViewsCallback cbDrawOpenEdges
 
 			delete obj
 			delete emesh
 			
 			gc light:true
 			
 			setArrowCursor()
 			
 		)
 		
 		forceCompleteRedraw()
 		
 	)
 
 	
 	try(destroyDialog RO_DISPLAY_UV_OE) catch()
 	rollout RO_DISPLAY_UV_OE "UV Open Edges" width:160 height:198
 	(
 		checkbutton bt_enable "Display UV Open Edges" pos:[8,8] width:144 height:32
 		dropdownList ddl_channel "UV Channel:" pos:[8,72] width:120 height:40 enabled:false
 		colorPicker cp1 "" pos:[131,90] width:21 height:21 enabled:true color:edgesColor modal:false
 		checkbox chk_redraw "Force Redraw" pos:[8,46] width:100 height:16 enabled:false
 		spinner spn_width "Edge Width:" pos:[8,120] width:120 height:16 range:[1,100,4] type:#integer fieldwidth:50
 		button bt_update "Update" pos:[8,160] width:144 height:28 enabled:false
 
 		fn destroy = (
 			callbacks.removeScripts #selectionSetChanged
 			unRegisterRedrawViewsCallback cbDrawOpenEdges
 			forceCompleteRedraw()
 		)
 		
 		on RO_DISPLAY_UV_OE close do destroy()
 		
 		on bt_enable changed arg do
 		(
 			bt_update.enabled = arg
 			ddl_channel.enabled = arg
 			chk_redraw.enabled = arg
 			destroy()
 			
 			if (arg == true) do (
 				callbacks.addscript #selectionSetChanged "cbSelectionChanged()"
 				cbSelectionChanged()
 			)
 		)
 		
 		on bt_update pressed do cbSelectionChanged()
 		
 		on ddl_channel selected arg do (
 			uvChannel = (ddl_channel.selected as integer)
 			cbSelectionChanged mForce:true
 		)
 		
 		on cp1 changed arg do edgesColor = arg
 			
 		on spn_width changed arg do edgeOffset = (arg-1)/100.0
 		
 		on chk_redraw changed arg do forceRedraw = arg
 
 	)
 
 	createDialog RO_DISPLAY_UV_OE
 	
 )

#15

it’s VERY smart!


#16

after optimizing your algorithm overall i made the performance 2.5 times faster and memory use 2 times less.
also this little trick:


	 fn cbDrawOpenEdges = (
		local polyLine = gw.polyLine
		 gw.setTransform (Matrix3 1)
		 gw.setColor #line edgesColor
		 for j in vertexPos do polyLine j true
 
		 -- Needed in some Max versions or used dirvers
		 if forceRedraw do (
			 gw.enlargeUpdateRect #whole
			 gw.updateScreen()
		 )
	 )

… stops memory leaking during gw polylines drawing


#17

It’s a pleasure to know you could get it working that fast.

The only way I can get that speed is by not removing the duplicated edges, but then I get double edges for each internal edge, so displaying them drops the performance.

I can’t wait to see your sorting algorithm.

Thanks for the caching function tip, the difference that one line can make always amazes me.

#18
When the obvious is not so obvious.

This optimization runs an average of 2.5 times faster and uses half the memory than previous version.

I feel this could still be optimized and that a different approach would lead to better results, especially for the drawing routine. But for what it is, I think it works pretty well.

“getFaceNormal” was a big surprise.


  (
  
  	unRegisterRedrawViewsCallback cbDrawOpenEdges
  	
  	global RO_DISPLAY_UV_OE
  	global cbDrawOpenEdges
  	global cbSelectionChanged
  	
  	local vertexPos   = #()
  	local uvChannel   = 1
  	local edgesColor  = [0,255,0]
  	local edgeOffset  = 0.03
  	local forceRedraw = false
  	local polyLine	= gw.polyLine
  	
  	fn cbDrawOpenEdges = (
  		
  		gw.setTransform (Matrix3 1)
  		gw.setColor #line edgesColor
  		
  		for j in vertexPos do polyLine j true
  
  		-- Needed in some Max versions or used dirvers
  		if forceRedraw do (
  			gw.enlargeUpdateRect #whole
  			gw.updateScreen()
  		)
  	)
  
  	fn cbSelectionChanged mForce:false = (
  
  		unRegisterRedrawViewsCallback cbDrawOpenEdges
  		
  		if ($ != undefined) do (
  			
  			setwaitcursor()
  
  			st = timeStamp()
  				
  			cGetMapFace = meshop.getMapFace
  
  			obj = snapshotasmesh selection[1]
  
  			allChannels = for j = 1 to meshop.getNumMaps obj where (meshop.getMapSupport obj j) collect j
  			
  			RO_DISPLAY_UV_OE.ddl_channel.items = for j in allChannels collect j as string
  			
  			if (mForce == false) do (
  				uvChannel = allChannels[1]
  				RO_DISPLAY_UV_OE.ddl_channel.selection = 1
  			)
  
  			numTFaces = meshop.getNumMapFaces obj uvChannel
  			numTVerts = meshop.getNumMapVerts obj uvChannel
  			
  			emesh = trimesh()
  			setMesh emesh numverts:numTVerts numfaces:numTFaces
  			setMesh emesh faces:(for j = 1 to numTFaces collect (cGetMapFace obj uvChannel j))
  			
  			objOpenEdges  = meshop.getOpenEdges obj
  			meshOpenEdges = (meshop.getOpenEdges emesh) - objOpenEdges
  			delete emesh
  						
  			sharedFaces = meshop.getFacesUsingEdge obj meshOpenEdges
  			foundEdges = #()
  			vertexPos  = #()
  			
  			for j in sharedFaces do (
  				
  				objFaceVerts = getFace obj j
  				
  				edge1 = j*3 - 2
  				edge2 = j*3 - 1
  				edge3 = j*3
  				
  				v1Idx = objFaceVerts.x
  				v2Idx = objFaceVerts.y
  				v3Idx = objFaceVerts.z
  				
  				v1 = getVert obj v1Idx
  				v2 = getVert obj v2Idx
  				v3 = getVert obj v3Idx
  
  				n1 = (normalize (cross (v1 - v2) (v2 - v3))) * edgeOffset
  
  				if (finditem meshOpenEdges edge1) > 0 do (
  					found = findItem foundEdges [v1Idx, v2Idx]
  					if (found == 0) then (
  						n2 = (normalize (cross n1 (v1-v2))) * edgeOffset
  						append vertexPos #(v1+n1+n2, v2+n1+n2, v1-n1-n2, v2-n1-n2, v1-n1+n2, v2-n1+n2, v1+n1-n2, v2+n1-n2)
  						append foundEdges [v2Idx, v1Idx]
  					)else(
  						deleteitem foundEdges found
  					)
  				)
  				if (finditem meshOpenEdges edge2) > 0 do (
  					found = findItem foundEdges [v2Idx, v3Idx]
  					if (found == 0) then (
  						n2 = (normalize (cross n1 (v2-v3))) * edgeOffset
  						append vertexPos #(v2+n1+n2, v3+n1+n2, v2-n1-n2, v3-n1-n2, v2-n1+n2, v3-n1+n2, v2+n1-n2, v3+n1-n2)
  						append foundEdges [v3Idx, v2Idx]
  					)else(
  						deleteitem foundEdges found
  					)
  				)
  				if (finditem meshOpenEdges edge3) > 0 do (
  					found = findItem foundEdges [v3Idx, v1Idx]
  					if (found == 0) then (
  						n2 = (normalize (cross n1 (v3-v1))) * edgeOffset
  						append vertexPos #(v3+n1+n2, v1+n1+n2, v3-n1-n2, v1-n1-n2, v3-n1+n2, v1-n1+n2, v3+n1-n2, v1+n1-n2)
  						append foundEdges [v1Idx, v3Idx]
  					)else(
  						deleteitem foundEdges found
  					)
  				)
  					
  			)
  			
  			sharedFaces = meshop.getFacesUsingEdge obj objOpenEdges
  			
  			for j in sharedFaces do (
  				
  				objFaceVerts = getFace obj j
  				
  				edge1 = j*3 - 2
  				edge2 = j*3 - 1
  				edge3 = j*3
  				
  				v1Idx = objFaceVerts.x
  				v2Idx = objFaceVerts.y
  				v3Idx = objFaceVerts.z
  				
  				v1 = getVert obj v1Idx
  				v2 = getVert obj v2Idx
  				v3 = getVert obj v3Idx
  
  				n1 = (normalize (cross (v1 - v2) (v2 - v3))) * edgeOffset
  
  				if (finditem objOpenEdges edge1) > 0 do (
  					n2 = (normalize (cross n1 (v1-v2))) * edgeOffset
  					append vertexPos #(v1+n1+n2, v2+n1+n2, v1-n1-n2, v2-n1-n2, v1-n1+n2, v2-n1+n2, v1+n1-n2, v2+n1-n2)
  					append foundEdges [v2Idx, v1Idx]
  				)
  				if (finditem objOpenEdges edge2) > 0 do (
  					n2 = (normalize (cross n1 (v2-v3))) * edgeOffset
  					append vertexPos #(v2+n1+n2, v3+n1+n2, v2-n1-n2, v3-n1-n2, v2-n1+n2, v3-n1+n2, v2+n1-n2, v3+n1-n2)
  					append foundEdges [v3Idx, v2Idx]
  				)
  				if (finditem objOpenEdges edge3) > 0 do (
  					n2 = (normalize (cross n1 (v3-v1))) * edgeOffset
  					append vertexPos #(v3+n1+n2, v1+n1+n2, v3-n1-n2, v1-n1-n2, v3-n1+n2, v1-n1+n2, v3+n1-n2, v1+n1-n2)
  					append foundEdges [v1Idx, v3Idx]
  				)
  					
  			)
  
  			format "Found %	UV Open Edges in %s
" vertexPos.count ((timeStamp() - st) / 1000.0)
  
  			registerRedrawViewsCallback cbDrawOpenEdges
  
  			delete obj
  			
  			gc light:true
  			
  			setArrowCursor()
  			
  		)
  		
  		forceCompleteRedraw()
  		
  	)
  
  	
  	try(destroyDialog RO_DISPLAY_UV_OE) catch()
  	rollout RO_DISPLAY_UV_OE "UV Open Edges" width:160 height:198
  	(
  		checkbutton bt_enable "Display UV Open Edges" pos:[8,8] width:144 height:32
  		dropdownList ddl_channel "UV Channel:" pos:[8,72] width:120 height:40 enabled:false
  		colorPicker cp1 "" pos:[131,90] width:21 height:21 enabled:true color:edgesColor modal:false
  		checkbox chk_redraw "Force Redraw" pos:[8,46] width:100 height:16 enabled:false
  		spinner spn_width "Edge Width:" pos:[8,120] width:120 height:16 range:[1,100,4] type:#integer fieldwidth:50
  		button bt_update "Update" pos:[8,160] width:144 height:28 enabled:false
  
  		fn destroy = (
  			callbacks.removeScripts #selectionSetChanged
  			unRegisterRedrawViewsCallback cbDrawOpenEdges
  			forceCompleteRedraw()
  		)
  		
  		on RO_DISPLAY_UV_OE close do destroy()
  		
  		on bt_enable changed arg do
  		(
  			bt_update.enabled = arg
  			ddl_channel.enabled = arg
  			chk_redraw.enabled = arg
  			destroy()
  			
  			if (arg == true) do (
  				callbacks.addscript #selectionSetChanged "cbSelectionChanged()"
  				cbSelectionChanged()
  			)
  		)
  		
  		on bt_update pressed do cbSelectionChanged()
  		
  		on ddl_channel selected arg do (
  			uvChannel = (ddl_channel.selected as integer)
  			cbSelectionChanged mForce:true
  		)
  		
  		on cp1 changed arg do edgesColor = arg
  			
  		on spn_width changed arg do edgeOffset = (arg-1)/100.0
  		
  		on chk_redraw changed arg do forceRedraw = arg
  
  	)
  
  	createDialog RO_DISPLAY_UV_OE
  	
  )
  

#19

good results… i’m using as a test object the all parts Teapot with 32 segments (32898 verts, 4096 open edges).

your code gives me:
time:175 memory:5705960L

my code (which is almost identical to yours but better optimized) gives:
time:111 memory:5046656L


#20

I noticed display statistics (shortcut 7) plays a BIG role in performance in max 2014. I tested with a teapot with 64segs + TS (~1 million polys), as expected viewport speed goes to a crawl, but as soon as I turn display statistics on I get ~60fps!

The same in max 2012 doesn’t work, performance is very poor but better than max 2014 with no statistics.