Fast way to get selected face outer 'border' edges on Trimesh / Editable Mesh


#1

With a bunch of random faces selected on an editable mesh / trimesh I’d like to be able to quickly get the outer edges that directly surround them. Essentially I’m trying to mimic the behavior found in an editable poly or edit poly modifier where you can select faces then shift + left mouse button click on the edge icon to convert to them. I already have a working function, but for some of things I want to use it for it’s just too slow:

fn getFaceBorderEdges_fn curObj faceSelection borderVerts allHiddenEdges = ( --returns array containing bitarrays of edge indexs
    allBorderEdges = #{}
    gve = meshop.getEdgesUsingVert
    gev = meshop.getVertsUsingEdge
    vertEdges = gve curObj borderVerts
    faceVerts = meshop.getVertsUsingFace curObj faceSelection
    faceEdges = meshop.getEdgesUsingFace curObj faceSelection
    innerEdges = gve curObj (faceVerts - borderVerts) --Does not account for edges that bridge through selected faces from one border vert to another
    possibleEdges = ((vertEdges * faceEdges) - innerEdges) - allHiddenEdges
    for edj in possibleEdges do (
        edgeVerts = gev curObj edj
        sideVertEdges = for v in edgeVerts collect (gve curObj v)
        revEdge = (sideVertEdges[1] * sideVertEdges[2]) - #{edj}
        if (revEdge.numberset == 1) and (revEdge * faceEdges == 0) do append allBorderEdges edj
    )
    allBorderEdges --return
)

It works, but the loop portion which checks for edges which have two verts on the faces ‘borders’ , but which are actually crossing through the face selection instead of around it, is slowing the whole thing way down. I can’t think of a better method to do that check though.


#2

?

meshop.getOpenEdges <Mesh mesh>

#3

Not the same thing. That just gets edges which have no reverse edges.


#4

oh, so you need to fence faces
gGtti0gFEc

tri = $
fsel = getFaceSelection tri
esel = meshop.getEdgesUsingFace tri fsel
meshopGetEdgesReverseEdge = meshop.getEdgesReverseEdge
for i in esel do esel -= meshopGetEdgesReverseEdge tri i
setEdgeSelection tri esel

here on forum was a great thread somewhere about efficient getEdgesReverseEdge


#5

Thanks for the help! I tried out the solution you posted as well as looked into the threads for getting reverse edges, and found a great function written by PolyTools3D that when replacing my bitarray method (or the built in function) improved performance dramatically: Mini-challenge #7 (mxs only)


#6

perhaps this why getedgesreverseEdge gets a bad rap :slight_smile:

tri = $
fsel = getFaceSelection tri
esel = meshop.getEdgesUsingFace tri fsel
esel -= meshop.getEdgesReverseEdge tri esel
setEdgeSelection tri esel

#7

it doesn’t produce the same exact result but it is indeed much more performant
3dsmax_MFcJZUAd46


#8

can you post a selection where it doesn’t work please ?

as

tri = $

fsel = getFaceSelection tri
esel1 = meshop.getEdgesUsingFace tri fsel
esel1 -= meshop.getEdgesReverseEdge tri esel1

esel2 = meshop.getEdgesUsingFace tri fsel
meshopGetEdgesReverseEdge = meshop.getEdgesReverseEdge
for i in esel2 do esel2 -= meshopGetEdgesReverseEdge tri i
	
diff = (esel2 - esel1)
setEdgeSelection tri diff

always produces an empty bitarray for me


#9

sure


(
delete objects

tri = Teapot segments:4
convertToMesh tri
setFaceSelection tri #{25..40, 97..112, 153..168, 225..240, 281..296, 353..368, 391..392, 399..400, 405..422, 481..486, 489..495, 501..504, 511..512, 769..810, 817..818, 825..826, 833..834, 841..842, 849..850, 857..858, 865..898, 905..908, 913..916, 921..936, 993..1012, 1017..1020}
fsel = getFaceSelection tri
	
-- 1	
esel = meshop.getEdgesUsingFace tri fsel
esel -= meshop.getEdgesReverseEdge tri esel


-- 2
-- esel = meshop.getEdgesUsingFace tri fsel
-- meshopGetEdgesReverseEdge = meshop.getEdgesReverseEdge
-- for i in esel do esel -= meshopGetEdgesReverseEdge tri i
	
setEdgeSelection tri esel

select tri 
subObjectLevel = 2
)

#10
(
delete objects

tri = Teapot segments:4
convertToMesh tri
setFaceSelection tri #{25..40, 97..112, 153..168, 225..240, 281..296, 353..368, 391..392, 399..400, 405..422, 481..486, 489..495, 501..504, 511..512, 769..810, 817..818, 825..826, 833..834, 841..842, 849..850, 857..858, 865..898, 905..908, 913..916, 921..936, 993..1012, 1017..1020}
fsel = getFaceSelection tri
	
-- 1	
esel1 = meshop.getEdgesUsingFace tri fsel
esel1 -= meshop.getEdgesReverseEdge tri esel1


-- 2
 esel2 = meshop.getEdgesUsingFace tri fsel
 meshopGetEdgesReverseEdge = meshop.getEdgesReverseEdge
 for i in esel2 do esel2 -= meshopGetEdgesReverseEdge tri i
	
setEdgeSelection tri (esel2 - esel1)

select tri 
subObjectLevel = 2
 
 (esel2 - esel1)
)

produces the same result and returns an empty bitarray


#11
(esel2 - esel1).isEmpty and (esel1 - esel2).isEmpty

returns false
IgnGsa551J
I clearly see the difference between the two


#12

returns true for me

this is the edge selection from --1


#13

Today I found that when I select a single object(from a particular scene) and execute this:
isOpenGroupMember selection[1]
in 3dsMax 2014 the result is TRUE
in 3dsMax 2020 the result is FALSE

The correct “answer” is FALSE and in 3ds Max 2014 a particular script not works as expected.

So, maybe the differences that you have are because of the 3ds Max you use. :slight_smile:


#14

yeah I tested it in a more recent version and it failed, looking at the code in the mxsagni project my guess would be a change to AdjEdgeList::FindEdge as the core function remains unchanged between versions so it looks like the likely culprit


#15

Oh! I’ve re-read the thread and gotten a lot of fun… It was the great time. I miss it so much.
As a memory about this time I keep a fast version of get-mesh-reverse-edge in my MXS extension


#16

an mxs extension function is pretty simple to implement…

def_struct_primitive(meshop_getFacesBorderEdges, meshop, "getFacesBorderEdges");

Value* meshop_getFacesBorderEdges_cf(Value** arg_list, int count)
{
	check_arg_count(getFacesBorderEdges, 2, count);
	Mesh* mesh = get_meshForValue(arg_list[0], MESH_READ_ACCESS, NULL, getFacesBorderEdges);
	
	int numFaces = mesh->getNumFaces();
	BitArray faces(numFaces);
	ValueToBitArrayM(arg_list[1], faces, numFaces, GetString(IDS_FACE_INDEX_OUT_OF_RANGE), MESH_FACESEL_ALLOWED, mesh);

	one_typed_value_local(BitArrayValue* edges);
	vl.edges = new BitArrayValue(numFaces * 3);

	AdjEdgeList adjedges(*mesh);
	int numEdges = adjedges.edges.Count();
	for (int e = 0; e < numEdges; ++e)
	{	
		MEdge& edge = adjedges.edges[e];

// if both sides are selected or both sides are unselected we're not on the border

		if((faces[edge.f[0]] && faces[edge.f[1]]) || (!faces[edge.f[0]] && !faces[edge.f[1]])) continue;
		
		if(faces[edge.f[0]])
			vl.edges->bits.Set(edge.f[0]*3 + edge.EdgeIndex(mesh->faces, 0));
		if(faces[edge.f[1]])
			vl.edges->bits.Set(edge.f[1]*3 + edge.EdgeIndex(mesh->faces, 1));
	}
	return_value(vl.edges);
}

though probably not optimal