Notifications
Clear all

[Closed] Collect continuous selected edges/vertices in order?

Trying to write a function to break the selected vertices or edges of an object into contiguous groups, such as multiple loops, while keeping them in order (i.e. according to how they connect on the object.)

I’ve found or been able to come up with scripts that do one or the other; in other words either
a) Get a single group of contiguous verts or edges in order, or
b) Collect multiple groups of contiguous verts or edges, but in numerical order rather than the order in which they connect on the model

But I can’t seem to find or figure out a function that satisfies both.

15 Replies

i don’t want to disappoint you but there is no safe way in mxs (and sdk) to get vertex selection (or poly loop/ring) order.
depending on what you want to do it can be a workaround. let’s say if you want to copy some vertex data from one set of verts to another (weights, uvs, etc.) they can be sorted in a specific order…
the idea is to find the right sorting method for the desirable mapping

When I say “in order” I meant in terms of how they are arranged contiguously on the model, as shown in this post:

http://forums.cgsociety.org/showthread.php?f=98&t=639599

(or in this one: http://forums.cgsociety.org/showthread.php?f=98&t=481192 )

I just need something that will do this for multiple groups of selected edges/vertices, creating a separate array for each group…

Maybe this could help:


(
	fn getContinuousEdgeGroups obj =
	(
		local edgesMask = polyop.getEdgeSelection obj
		local selectedEdges = edgesMask as array
		local continuousEdgeGroups = #()
		
		for i in selectedEdges where edgesMask[i] do 
		(
			local newGroup = #{}
			local frontier = #(i)
			
			while frontier.count > 0 do 
			(
				local current = frontier[1]
				deleteItem frontier 1
				edgesMask[current] = off
				newGroup[current] = on
				
				local verts = polyop.getVertsUsingEdge obj current
				local neighbors = polyop.getEdgesUsingVert obj verts
				neighbors *= edgesMask
				neighbors = neighbors as array
				for j in neighbors do
					appendIfUnique frontier j
			)
			
			append continuousEdgeGroups newGroup
		)
		
		continuousEdgeGroups
	)
	
	fn getOrderedEdgeGroups obj continuousEdgeGroups =
	(
		local orderedEdgeGroups = #()
		for edgeGroupMas in continuousEdgeGroups do
		(
			local ends = #()
			local edgeGroup = edgeGroupMas as array
			for i in edgeGroup as array do (
				local verts = polyop.getVertsUsingEdge obj i
				local neighbors = polyop.getEdgesUsingVert obj verts
				neighbors[i] = off
				neighbors *= edgeGroupMas
				if neighbors.numberSet < 2 then
					append ends i
			)
			
			local root
			if ends.count > 0 then (
				-- there is a starting point
				sort ends
				root = ends[1]
			) else (
				-- there is no starting point => this is a loop
				sort edgeGroup
				root = edgeGroup[1]
			)
			
			local newGroup = #()
			local frontier = #(root)
			local usedEdges = #{}
			
			while frontier.count > 0 do (
				local current = frontier[1]
				deleteItem frontier 1
				append newGroup current
				usedEdges[current] = on
				
				local verts = polyop.getVertsUsingEdge obj current
				local neighbors = polyop.getEdgesUsingVert obj verts
				neighbors *= edgeGroupMas
				neighbors -= usedEdges
				neighbors = neighbors as array
				sort neighbors
				join frontier neighbors
			)
			
			append orderedEdgeGroups newGroup
		)
		
		orderedEdgeGroups
	)
	
	if selection.count == 1 and classof $ == Editable_Poly then (
		local obj = $
		local continuousEdgeGroups = getContinuousEdgeGroups obj
		getOrderedEdgeGroups obj continuousEdgeGroups
	)
)

It works about as well as anything I’ve come up with and is also a lot shorter. However, when dealing with closed loops it no longer keeps the edges in order (and also collects the last edge in sequence twice). E.g., what should be #(1,2,3,4) comes up as #(1, 2, 4, 3, 3).

Malkalypse you are right, I didn’t test my code on loops but I did have a special case for it that I should have tested. Here is a fixed version with an extra visualisation / debugging function:

(
	fn getEdgeNeighbors obj i edgesMask:#{} =
	(
		local verts = polyop.getVertsUsingEdge obj i
		local neighbors = polyop.getEdgesUsingVert obj verts
		neighbors[i] = off
		neighbors * edgesMask
	)
	
	fn getContinuousEdgeGroups obj =
	(
		local edgesMask = polyop.getEdgeSelection obj
		local selectedEdges = edgesMask as array
		local continuousEdgeGroups = #()
		
		for i in selectedEdges where edgesMask[i] do 
		(
			local newGroup = #{}
			local frontier = #(i)
			
			while frontier.count > 0 do 
			(
				local current = frontier[1]
				deleteItem frontier 1
				edgesMask[current] = off
				newGroup[current] = on
				
				local neighbors = (getEdgeNeighbors obj current edgesMask:edgesMask) as array
				for j in neighbors do
					appendIfUnique frontier j
			)
			
			append continuousEdgeGroups newGroup
		)
		
		continuousEdgeGroups
	)
	
	fn getOrderedEdgeGroups obj continuousEdgeGroups =
	(
		local orderedEdgeGroups = #()
		for edgeGroupMas in continuousEdgeGroups do
		(
			local ends = #()
			local edgeGroup = edgeGroupMas as array
			for i in edgeGroup as array do (
				local neighbors = getEdgeNeighbors obj i edgesMask:edgeGroupMas
				if neighbors.numberSet < 2 then
					append ends i
			)
			
			local root
			local extraEdge
			if ends.count > 0 then (
				-- there is a starting point
				sort ends
				root = ends[1]
			) else (
				-- there is no starting point => this is a loop
				sort edgeGroup
				root = edgeGroup[1]
				local neighbors = (getEdgeNeighbors obj root edgesMask:edgeGroupMas) as array
				extraEdge = neighbors[1]
				edgeGroupMas[extraEdge] = off
			)
			
			local newGroup = #()
			local frontier = #(root)
			local usedEdges = #{}
			
			while frontier.count > 0 do (
				local current = frontier[1]
				deleteItem frontier 1
				append newGroup current
				usedEdges[current] = on
				
				local neighbors = getEdgeNeighbors obj current edgesMask:edgeGroupMas
				neighbors -= usedEdges
				neighbors = neighbors as array
				sort neighbors
				join frontier neighbors
			)
			
			if extraEdge != undefined then
				append newGroup extraEdge
			
			append orderedEdgeGroups newGroup
		)
		
		orderedEdgeGroups
	)
	
	fn visualizeGroup obj edgeGroup radius:0.1 =
	(
		for i = 1 to edgeGroup.count do (
			local verts = (polyop.getVertsUsingEdge obj edgeGroup[i]) as array
			local faces = (polyop.getFacesUsingEdge obj edgeGroup[i]) as array
			local v1 = polyop.getVert obj verts[1]
			local v2 = polyop.getVert obj verts[2]
			local x = normalize (v2 - v1)
			local y = [0,0,0]
			for j in faces do
				y += polyop.getFaceNormal obj j
			local z = normalize (cross x y)
			y = normalize (cross z x)
			local m = matrix3 x y z ((v1 + v2) / 2)
			local c = cylinder radius:radius wirecolor:red height:(distance v1 v2) pos:v1 dir:x
			Text text:(i as string) wirecolor:red size:(100. * radius) transform:m
		)
	)
	
	local debugMode = true
	if selection.count == 1 and classof $ == Editable_Poly then (
		local obj = $
		local continuousEdgeGroups = getContinuousEdgeGroups obj
		local orderedEdgeGroups = getOrderedEdgeGroups obj continuousEdgeGroups
		if debugMode == true then
			for g in orderedEdgeGroups do
				visualizeGroup obj g
	)
)

There are still unhanded cases, like if you have multiple loops in a single group, for these complex cases I think it’s better to build a directed graph and brake it to form a tree data structure and sort the edges this way, if I’ll have some more spare time tomorrow I can give it a go if you’re interested…

Sure, if you don’t mind! One can never have too many tools, after all

EDIT:
Forgot to mention – that debugging option is really trippy looking!

if we are talking about poly edge loops, it’s much easier to use <EditablePoly>.setLoopShift

edtable poly has already sorted loop structures. so all that you need is

pick any edge from a loop

shift any direction until meet any not from loop or meet the start pick (closed loop)

if you didn’t meet a start pick do shift another direction

merge two sides and get sorted loop edges

Here is the (probably no completely) correct method:

(
	fn getEdgeNeighbors obj i edgesMask:#{} vertMask:#{} =
	(
		local verts = polyop.getVertsUsingEdge obj i
		verts -= vertMask
		local neighbors = polyop.getEdgesUsingVert obj verts
		neighbors[i] = off
		neighbors * edgesMask
	)
	
	struct s_TreeNode
	(
		index,
		Children = #(),
		
		fn getChildren obj &edgesMask:#{} &vertsMask:#{} =
		(
			edgesMask[index] = off
			local neighbors = getEdgeNeighbors obj index edgesMask:edgesMask vertMask:vertsMask
			local NeighborTreeNodes = for i in neighbors collect s_TreeNode index:i
			
			for i in polyop.getVertsUsingEdge obj index do
				vertsMask[i] = on
			
			for i in neighbors where edgesMask[i] do
			(
				local Child = s_TreeNode index:i
				Child.getChildren obj edgesMask:&edgesMask vertsMask:&vertsMask
				append Children Child
			)
		),
		
		fn getGroup =
		(
			local edgeGroup = #(index)
			for Child in Children do
				join edgeGroup (Child.getGroup())
			edgeGroup
		)
	)
	
	fn getContinuousEdgeGroups obj =
	(
		local edgesMask = polyop.getEdgeSelection obj
		local selectedEdges = edgesMask as array
		local continuousEdgeGroups = #()
		
		for i in selectedEdges where edgesMask[i] do 
		(
			local newGroup = #{}
			local frontier = #(i)
			
			while frontier.count > 0 do 
			(
				local current = frontier[1]
				deleteItem frontier 1
				edgesMask[current] = off
				newGroup[current] = on
				
				local neighbors = (getEdgeNeighbors obj current edgesMask:edgesMask) as array
				for j in neighbors do
					appendIfUnique frontier j
			)
			
			append continuousEdgeGroups newGroup
		)
		
		continuousEdgeGroups
	)
	
	fn getOrderedEdgeGroups obj continuousEdgeGroups =
	(
		local orderedEdgeGroups = #()
		for edgeGroupMas in continuousEdgeGroups do
		(
			local ends = #()
			local edgeGroup = edgeGroupMas as array
			for i in edgeGroup do 
			(
				local isEndEdge = false
				local verts = polyop.getVertsUsingEdge obj i
				for j in verts do
				(
					local neighbors = polyop.getEdgesUsingVert obj j
					neighbors *= edgeGroupMas
					if neighbors.numberSet == 1 then
						exit with isEndEdge = true
				)
				
				if isEndEdge then
					append ends i
			)
			
			local Root = s_TreeNode index:(if ends.count > 0 then ends[1] else edgeGroup[1])
			Root.getChildren obj edgesMask:edgeGroupMas
			append orderedEdgeGroups (Root.getGroup())
		)
		
		orderedEdgeGroups
	)
	
	fn visualizeGroup obj edgeGroup radius:0.1 =
	(
		for i = 1 to edgeGroup.count do 
		(
			local verts = (polyop.getVertsUsingEdge obj edgeGroup[i]) as array
			local faces = (polyop.getFacesUsingEdge obj edgeGroup[i]) as array
			local v1 = polyop.getVert obj verts[1]
			local v2 = polyop.getVert obj verts[2]
			local x = normalize (v2 - v1)
			local y = [0,0,0]
			for j in faces do
				y += polyop.getFaceNormal obj j
			local z = normalize (cross x y)
			y = normalize (cross z x)
			local m = matrix3 x y z ((v1 + v2) / 2)
			local c = cylinder radius:radius wirecolor:red height:(distance v1 v2) pos:v1 dir:x
			Text text:(i as string) wirecolor:red size:((distance v1 v2) / 2) transform:m
		)
	)
	
	local debugMode = true
	if selection.count == 1 and classof $ == Editable_Poly then 
	(
		local obj = $
		local continuousEdgeGroups = getContinuousEdgeGroups obj
		local orderedEdgeGroups = getOrderedEdgeGroups obj continuousEdgeGroups
		if debugMode == true then
			for g in orderedEdgeGroups do
				visualizeGroup obj g
	)
)

If the debugging option doesn’t work very well it’s probably because it’s scale dependent, I didn’t bother making it scale independent :shrug:

EDIT: Already fixed a few issues

After some poking and prodding, I think I get how it works now. One question though, what would need changing in order to collect the vertices in order instead of the edges?

1 Reply
(@matanh)
Joined: 10 months ago

Posts: 0

this is pretty easy to do, we can simply convert every edge in the ordered edge group to verts and add them to the vert group if they are not in it yet like this function does:

fn convertEdgesToVerts obj edgeGroup =
(
	local vertsGroup = #()
	for i in edgeGroup do (
		local verts = (polyop.getVertsUsingEdge obj i) as array
		for j in verts do
			appendIfUnique vertsGroup j
	)
	vertsGroup
)

and the complete example with debugging for both the edges and verts is here:

(
	fn getEdgeNeighbors obj i edgesMask:#{} vertMask:#{} =
	(
		local verts = polyop.getVertsUsingEdge obj i
		verts -= vertMask
		local neighbors = polyop.getEdgesUsingVert obj verts
		neighbors[i] = off
		neighbors * edgesMask
	)
	
	struct s_TreeNode
	(
		index,
		Children = #(),
		
		fn getChildren obj &edgesMask:#{} &vertsMask:#{} =
		(
			edgesMask[index] = off
			local neighbors = getEdgeNeighbors obj index edgesMask:edgesMask vertMask:vertsMask
			local NeighborTreeNodes = for i in neighbors collect s_TreeNode index:i
			
			for i in polyop.getVertsUsingEdge obj index do
				vertsMask[i] = on
			
			for i in neighbors where edgesMask[i] do
			(
				local Child = s_TreeNode index:i
				Child.getChildren obj edgesMask:&edgesMask vertsMask:&vertsMask
				append Children Child
			)
		),
		
		fn getGroup =
		(
			local edgeGroup = #(index)
			for Child in Children do
				join edgeGroup (Child.getGroup())
			edgeGroup
		)
	)
	
	fn getContinuousEdgeGroups obj =
	(
		local edgesMask = polyop.getEdgeSelection obj
		local selectedEdges = edgesMask as array
		local continuousEdgeGroups = #()
		
		for i in selectedEdges where edgesMask[i] do 
		(
			local newGroup = #{}
			local frontier = #(i)
			
			while frontier.count > 0 do 
			(
				local current = frontier[1]
				deleteItem frontier 1
				edgesMask[current] = off
				newGroup[current] = on
				
				local neighbors = (getEdgeNeighbors obj current edgesMask:edgesMask) as array
				for j in neighbors do
					appendIfUnique frontier j
			)
			
			append continuousEdgeGroups newGroup
		)
		
		continuousEdgeGroups
	)
	
	fn getOrderedEdgeGroups obj continuousEdgeGroups =
	(
		local orderedEdgeGroups = #()
		for edgeGroupMas in continuousEdgeGroups do
		(
			local ends = #()
			local edgeGroup = edgeGroupMas as array
			for i in edgeGroup do 
			(
				local isEndEdge = false
				local verts = polyop.getVertsUsingEdge obj i
				for j in verts do
				(
					local neighbors = polyop.getEdgesUsingVert obj j
					neighbors *= edgeGroupMas
					if neighbors.numberSet == 1 then
						exit with isEndEdge = true
				)
				
				if isEndEdge then
					append ends i
			)
			
			local Root = s_TreeNode index:(if ends.count > 0 then ends[1] else edgeGroup[1])
			Root.getChildren obj edgesMask:edgeGroupMas
			append orderedEdgeGroups (Root.getGroup())
		)
		
		orderedEdgeGroups
	)
	
	fn convertEdgesToVerts obj edgeGroup =
	(
		local vertsGroup = #()
		for i in edgeGroup do (
			local verts = (polyop.getVertsUsingEdge obj i) as array
			for j in verts do
				appendIfUnique vertsGroup j
		)
		vertsGroup
	)
	
	fn visualizeEdgeGroup obj edgeGroup radius:0.1 =
	(
		for i = 1 to edgeGroup.count do 
		(
			local verts = (polyop.getVertsUsingEdge obj edgeGroup[i]) as array
			local faces = (polyop.getFacesUsingEdge obj edgeGroup[i]) as array
			local v1 = polyop.getVert obj verts[1]
			local v2 = polyop.getVert obj verts[2]
			local x = normalize (v2 - v1)
			local y = [0,0,0]
			for j in faces do
				y += polyop.getFaceNormal obj j
			local z = normalize (cross x y)
			y = normalize (cross z x)
			local m = matrix3 x y z ((v1 + v2) / 2)
			local c = cylinder radius:radius wirecolor:red height:(distance v1 v2) pos:v1 dir:x
			Text text:(i as string) wirecolor:red size:((distance v1 v2) / 2) transform:m
		)
	)
	
	fn visualizeVertGroup obj vertGroup radius:0.2 =
	(
		for i = 1 to vertGroup.count do 
		(
			local faces = (polyop.getFacesUsingVert obj vertGroup[i]) as array
			local v = polyop.getVert obj vertGroup[i]
			local n = [0,0,0]
			for j in faces do
				n += polyop.getFaceNormal obj j
			n = normalize n
			local s = sphere radius:radius wirecolor:blue pos:v dir:n
			local t = Text text:(i as string) wirecolor:blue size:(radius * 50.) pos:v dir:n
			t.transform = PreRotateX t.transform 90
		)
	)
	
	local debugMode = true
	if selection.count == 1 and classof $ == Editable_Poly then 
	(
		local obj = $
		local continuousEdgeGroups = getContinuousEdgeGroups obj
		local orderedEdgeGroups = getOrderedEdgeGroups obj continuousEdgeGroups
		if debugMode == true then
			for g in orderedEdgeGroups do
				visualizeEdgeGroup obj g
		local orderedVertGroups = for g in orderedEdgeGroups collect convertEdgesToVerts obj g
		if debugMode == true then
			for g in orderedVertGroups do
				visualizeVertGroup obj g
	)
)
Page 1 / 2