Home Technical Talk

Getting the "next" ring/loop edge in 3ds Max Maxscript

polycounter lvl 4
Offline / Send Message
LandeTLS polycounter lvl 4
Hey, i am back with another maxscript issue im facing.
in Maxscript for sw 3D Studio Max 2016

I am currently trying to build a series of functions to among other things get loop/ring edge "spans" between edge selections or specific edges.
The first step in such a function is reliably getting the next edge in the loop/ring from an edge.
I have explored a few methods underlined below:

Method 1:
Using the functions setRingshift/setLoopShift from the editablepoly interface,
and getting edge selection wont work for me for two reasons.
1: They do not have an equivalent function in edit_poly
( Editpoly has the RingSelect/LoopSelect functions.
But they both rely on +- edgesteps from each time you enter an editpoly,
which is a pain to get around*Test it out* ).
I could use PolytoolsSelect.GrowRing/Loop() ( Which grows in both directions ).
But then ill also have to also sort through the new edge selection for the edge i want to get
2: It seems horribly inefficient to set + use function to set + get selections to get an edge index, each time you want a new edge.
I ideally want to use a function that can do this without selecting.

Method 2:
Using conversion to get the 1 or 2 closest ring/loop edges ( In both directions )
Step 1:
GetFacesUsingEdge to get the ( 1 or 2 ) Connecting faces from the first edge.
Then GetEdgesUsingFace ( I had to do a custom function for editpoly for this )
To get the edges from those faces ( faceEdges )
Step 2:
GetVertsUsingEdge to get the verts from the first edge.
Then GetEdgesUsingVert to get the connecting edges from those verts(vertEdges)
Step 3:
To get the next 1 or 2 ring edges remove the vertEdges from the faceEdges.
To get the next 1 or 2 loop edges remove the faceEdges from the vertEdges.
The issue with this method is that it has a few problems with different topologies(Poles etc.). As well as i cant figure out the rule of how to get or figure out a certain direction(loop up/down ring left/right).
Is it the edge index order that determines the direction perhaps?

Method 3:
I have not really tested this but im putting it in here just for reference.
Setting the first edge as selection and doing a whole loop/ring selection.
Getting the new selection and getting the 1 closest -+ edge index to the first edge in the resulting BitArray.
This has the same second issue as method 1 as well probably not working in many(all) cases.
I am not sure how to sort out direction here either.

My question is how do i do this in a good way? Am i far off?
I know i probably need to do much more situation handling in the second method.
But how do i catch these situations?
I know border edges can be determined my if the edge has only 1 face connected.
But the others like poles etc. I have no idea.
I am probably also not catching other topology things that i havent thought of yet.

The Miauu scriptpack nr2 ( I think ) has a loop/ring step between that works for > 2 edges.
And IllusionCatalyst Ic.shape ( Selectspan ) Has a loop/ring step/spanning method that works for 2 edges.
But i cant figure out the inner workings of these functions from just looking at the code.
And for miauus pack i cant because it is encrypted.

I am also aware of PolytoolsSelect.StepLoop that selects the loop edges between two edges. that i could impliment as a set+set+get function for getting loop spans.
But this has the same issue as issue 2 in method 1. As well as not allowing for more control over the selection.
And also for some reason it does not have a similar StepRing function. And this is the most important functionality im after.

I really hope someone more experienced than me can answer.
I would be eternally grateful as iwe already spent almost a week on just this problem.

Edit: Fixed up the post fomatting and punctuation ( Hopefully ) . English is not my primary language

Replies

  • Eric Chadwick
    Options
    Offline / Send Message
    1. Software and version?
    2. Lack of punctuation and capitalization makes this difficult to read.
    3. Subject should mention this is Maxscript, to pull in the right people.

    Otherwise the post seems well written. If I could help I would. Fixing the above will get you answers faster.
  • LandeTLS
    Options
    Offline / Send Message
    LandeTLS polycounter lvl 4
    1. Software and version?
    2. Lack of punctuation and capitalization makes this difficult to read.
    3. Subject should mention this is Maxscript, to pull in the right people.

    Otherwise the post seems well written. If I could help I would. Fixing the above will get you answers faster.
    Thanks for pointing it out, i was adding maxscript to the description while i saw the reply. But i saw the formatting could use some work. Hopefully it is more readable now :)
  • MoP
    Options
    Offline / Send Message
    MoP polycounter lvl 18
    This might be horribly inefficient, just thinking off the top of my head here, but you could do something like:
    1. Get polys from a single current edge (e.g. in ring)
    2. For each poly, get edges - if it has 4 edges, then it's a quad
    3. For each quad, get edges, and get verts from each edge
    4. Check the edges from the quad and only keep the edge where both the verts were not found in your original edge - this will be the edge on the "other side" of the quad (i.e. next ring up/down)
    No idea how slow/fast this would be in script but at least it wouldn't involve any selection/converting through built-in commands as that will definitely be slower than doing it all internally.
    To be honest I haven't done much geometry work in maxscript but it seems like something you could do with all the internal "getBlahByBlah" functions and some appropriate sorting/checking.

    Edit: SyncViewS (the guy who wrote the IllusionCatalyst tools) is actually a fairly regular Polycounter so it's possible he might be able to help you out here too, he's a smart and helpful guy :)
    Also, considering English is not your first language, all your writing is excellent!
  • kio
    Options
    Offline / Send Message
    kio polycounter lvl 15
    heres a snipped i stumpled uppon..

    grabbed from edge straighten from Christoph Kubisch:
    fn bitarray2array bitarray_ =
    (
        local outgoing = for BA in bitarray_ collect BA
    )
    -- Written by Laszlo Sebo (MeshTools 2.5)
    fn findnewedges_poly obj this_edge =        -- finds the edges that are going from this edge onto edgeloops
    (
            local nextedge = #()
            local exclude_edges = #()
            local localverts = polyop.getedgeverts obj this_edge

            local edge_faces = polyop.getedgefaces obj this_edge

            for nf in edge_faces do join exclude_edges (polyop.getfaceedges obj nf)

            local nextedges = polyop.getedgesusingvert obj localverts[1]            --check one end of the edge
            if ((bitarray2array nextedges).count <= 4) do
                nextedge = bitarray2array (nextedges - (exclude_edges as bitarray) * nextedges)

            local nextedges = polyop.getedgesusingvert obj localverts[2]            --check one other of the edge
            if ((bitarray2array nextedges).count <= 4) do
                join nextedge (bitarray2array (nextedges - (exclude_edges as bitarray) * nextedges))
          
            nextedge
    )
     



  • LandeTLS
    Options
    Offline / Send Message
    LandeTLS polycounter lvl 4
    Thanks for those snippets! I will have to try them out
    For reference this the current code i am testing inside max by using the script editor
    (tried to put it in some kind of code block) (edit: made it blue as it was hard to see with the grey text on gray bg, also fixed a bitarray assignement)
    Function GetAUsingB modOrObj bInVar aType:#currentLevel bType:#currentLevel =
    (
    	print ("GetAUsingB" + " bInVar: " + bInVar as string + " aType: " + aType as string + " bType: " + bType as string)
    	local bBitArray = #{}
    	local returnBitArray = #{}
    
    	--class conversion
    	case classOf bInVar of
    	(
    		Integer: bBitArray = #{bInVar}
    		BitArray: bBitArray = bInVar
    		Array: bBitArray = bInVar as BitArray
    		default: (print "invalid inVar class"; return())
    	)
    	--currentLvl conversion
    	if aType == #currentLevel do (aType = modOrObj.GetMeshSelLevel())
    	if bType == #currentLevel do (bType = modOrObj.GetMeshSelLevel())
    
    	--if both a and b are of the same type return b
    	if aType == bType do (return bBitArray)
    
    	print ("Get "+ aType as string + " From " + bType as string)
    	print (bType as string + " bitarray: " + bBitArray as string)
    	case aType of
    	(
    		#vertex: case bType of
    		(
    			#Edge: case classof modOrObj of
    					(
    						Editable_Poly:(returnBitArray = polyOp.getVertsUsingEdge modOrObj bBitArray)
    						Edit_Poly:(modOrObj.getVertsUsingEdge &returnBitArray bBitArray)
    					)
    			#Face: case classof modOrObj of
    					(
    						Editable_Poly:(returnBitArray = polyOp.getVertsUsingFace modOrObj bBitArray)
    						Edit_Poly:(modOrObj.getVertsUsingFace &returnBitArray bBitArray)
    					)
    			default:(print "invalid bType")
    		)
    		#Edge: case bType of
    		(
    			#Vertex: case classof modOrObj of
    					(
    						Editable_Poly:(returnBitArray = polyOp.getEdgesUsingVert modOrObj bBitArray)
    						Edit_Poly:(modOrObj.getEdgesUsingVert &returnBitArray bBitArray)
    					)
    			#Face: case classof modOrObj of
    					(
    						Editable_Poly:(returnBitArray = polyOp.getEdgesUsingFace modOrObj bBitArray)
    						Edit_Poly:
    						(--thanks to: IllusionCatalyst
    							local iFaceDeg = 0
    							for iFace in (bBitArray as array) do
    							(
    								iFaceDeg = modOrObj.getFaceDegree iFace
    								--print ("faceIndex: " + iface as string + " degree: " + iFaceDeg as string)
    								for i = 1 to iFaceDeg do 
    								(
    									--print("degree: " + i as string)
    									returnBitArray[modOrObj.getFaceEdge iFace i] = true
    								)
    							)
    						)
    					)
    			default:(print "invalid bType")
    		)
    		#Face: case bType of
    		(
    			#Vertex: case classof modOrObj of
    					(
    						Editable_Poly:(returnBitArray = polyOp.getFacesUsingVert modOrObj bBitArray)
    						Edit_Poly:(modOrObj.getFacesUsingVert &returnBitArray bBitArray)
    					)
    			#Edge: case classof modOrObj of
    					(
    						Editable_Poly:(returnBitArray = polyOp.getFacesUsingEdge modOrObj bBitArray)
    						Edit_Poly:(modOrObj.getFacesUsingEdge &returnBitArray bBitArray)
    					)
    			default:(print "invalid bType")
    		)
    		default:(print "invalid aType")
    	)
    	print ("returnArray:"+ returnBitArray as string)
    
    	--Final return
    	return returnBitArray
    )
    	
    Function GetNextRingLoop oEdge modOrObj:(Filters.GetModOrObj()) =
    (
    	--make sure we have the correct variable type for edge
    	case classof oEdge of
    	(
    		BitArray: if oEdge.numberset > 1 do oEdge = #{(oEdge as array)[1]}
    		Integer: oEdge = #{oEdge}
    		Array: oEdge = #{oEdge[1]}
    		default:(print "unhandled oEdge variable"; return #{})
    	)
    	
    	forwardLoopEdges = #{}
    	reverseLoopEdges = #{}
    	forwardRingEdges = #{} 
    	reverseRingEdges = #{}
    	ringFilterEdges = #{}
    	loopFilterEdges = #{}
    	
    	edgeVerts = GetAUsingB modOrObj oEdge aType:#Vertex bType:#Edge --edgeVerts should always be 2
    	if edgeVerts.numberSet == 2 then
    	(	
    		reverseLoopVert = (edgeVerts as array)[1]
    		forwardLoopVert = (edgeVerts as array)[2]
    		print ("edgeVerts: " + edgeVerts as string)
    		reverseLoopEdges = GetAUsingB modOrObj reverseLoopVert aType:#Edge bType:#Vertex
    		forwardLoopEdges = GetAUsingB modOrObj forwardLoopVert aType:#Edge bType:#Vertex
    		ringFilterEdges = (forwardLoopEdges + reverseLoopEdges) --union and store these for later
    	) else return #{} --should be an impossible abortcondition unless passed something other than an Edge
    	
    	edgeFaces = GetAUsingB modOrObj oEdge aType:#Face bType:#Edge --edgeFaces can be either 2 or 1(for border oEdge)
    	if (numFaces = edgeFaces.numBerset) > 0 then
    	(
    		reverseRingEdges = GetAUsingB modOrObj (edgeFaces as array)[1] aType:#Edge bType:#Face
    		if numFaces == 2 then
    		(
    			forwardRingEdges = GetAUsingB modOrObj (edgeFaces as array)[2] aType:#Edge bType:#Face
    		) else (print "is border Edge")
    		
    		loopFilterEdges = (forwardRingEdges + reverseRingEdges)  --union and store these for later
    	) else return #{}--should also be an impossible abortcondition unless passed something other than an oEdge
    	
    	--do subtraction ops to get rid of most of the junk
    	forwardLoopEdges -= loopFilterEdges
    	reverseLoopEdges -= loopFilterEdges
    	forwardRingEdges -= ringFilterEdges
    	reverseRingEdges -= ringFilterEdges
    	
    	print ("candidate forward loop edges: " + forwardLoopEdges as string)
    	print ("candidate reverse loop edges: " + reverseLoopEdges as string)
    	print ("candidate forward ring edges: " + forwardRingEdges as string)
    	print ("candidate reverse ring edges: " + reverseRingEdges as string)
    	
    	print "selecting the new edges just for testing"
    	modOrObj.SetSelection #Edge (forwardLoopEdges + reverseLoopEdges + forwardRingEdges + reverseRingEdges)
    	--***From here on the code is incomplete***
    )
    
    Function testGNRL =
    (
    	if (getCommandPanelTaskMode() != #modify) do setCommandPanelTaskMode #modify
    	modOrObj = Filters.GetModOrObj()
    	
    	subobjectLevel = 2
    	edgeBitArray = modOrObj.GetSelection #Edge
    	if edgeBitArray.numberset > 0 then
    	(
    		print ("selected edges: " + edgeBitArray as string )
    		GetNextRingLoop edgeBitArray modOrObj:modOrObj
    	)
    	else
    	(
    		print "nothing selected"
    	)
    )
    

    To test it:

    Copy paste it into a new file in the script editor.

    Do an evaluate all

    Then create a primitive cylinder etc. in the scene

    Put an Edit_Poly on top or collapse to editable_poly

    Go into the edge subobject(2) and select an edge

    Open the listener(F11) .

    Type in a new line "testGNRL()" (without the quotes). Press enter

    The listener window will output some info about the process. As well as the candidate edges will be selected in the viewport

    For now this appears to work well for normal quad grid topology and borders atleast. But for edges in or around corners, poles, ngons and tris it gets a bit "confused"


  • cptSwing
    Options
    Offline / Send Message
    cptSwing polycounter lvl 11
    Very hasty reply, but SpanSelect by SyncViews might be helpful?
Sign In or Register to comment.