Notifications
Clear all

[Closed] select polys by angle function?

is there a maxscript function to select polys in and Editable_Poly by angle like using the interface. Ideally it would work something like:
getFacesByAngle <integer>faceIndex <float>angleThreshold

does it exist and I just can’t find it in the reference?

7 Replies

Hi Joel,
the following function selects every face in an Editable Poly whose normal is within the specified angle threshold expressed in degrees. It returns a BitArray with bit set for matching faces. It doesn’t consider continuity between faces, so there could be separated groups. To do that would require a littler more effort and a bit slower algorithm. If you plan to use this multiple times on a fixed selection, like varying the threshold with a spinner to see effects in realtime, it would be better to fill an array with face normals and then perform test on stored values rather than querying the Editable Poly each time.

Uhm… I just realized I didn’t look in the MaxScript reference.

function getFacesByAngle oPoly iFace fDegThresh =
(
    if ((classOf oPoly) != Editable_Poly) then
        throw "Wrong input in function getFacesByAngle() - Editable Poly"
        
    if ((classOf iFace) != Integer) then
        throw "Wrong input in function getFacesByAngle() - Face Index"

    if ( (iFace <= 0) or (iFace > (polyOp.getNumFaces oPoly)) ) then
        throw "Wrong input in function getFacesByAngle() - Face Index out of boundaries"
        
    if ( (classOf fDegThresh != Float) and (classOf fDegThresh != Integer) ) then
        throw "Wrong input in function getFacesByAngle() - Angle Threshold"

    -- I've become a little precautionary about errors,
    -- remove all the previous if not necessary

    local p3FaceNormal = polyOp.getFaceNormal oPoly iFace
    local fTestDot = cos fDegThresh
    
    local iNumFaces = polyOp.getNumFaces oPoly 
    local baSelFaces = #{1..iNumFaces}
    
    for i = 1 to iNumFaces do
    (
        p3ItemNormal = polyOp.getFaceNormal oPoly i
            
        if ((dot p3FaceNormal p3ItemNormal) < fTestDot) then
            baSelFaces[i] = false
    )

    ( -- just for visual feedback, remove if unneeded
        polyOp.setFaceSelection oPoly baSelFaces
        forceCompleteRedraw()
    )

    return baSelFaces
)
  • Enrico

aw :\

fn getFacesByAngle obj faceIndex angleThreshold:10.0 ignoreBackfacing:true = (
	local numFaces = obj.numFaces
	local faceNormals = for i = 1 to numFaces collect (
		polyOp.getFaceNormal obj i
	)
	local normalAround = faceNormals[faceIndex]
	local angleDot = cos(angleThreshold)
	
	local matchFaces
	if (ignoreBackFacing) then (
		matchFaces = for i = 1 to numFaces collect (
			if ((dot normalAround faceNormals[i]) >= angleDot) then ( i )
			else ( dontcollect )
		)
	)
	else (
		matchFaces = for i = 1 to numFaces collect (
			if ((abs (dot normalAround faceNormals[i])) >= angleDot) then ( i )
			else ( dontcollect )
		)
	)
)

Richard
just a bunch of ticks before you, but mine doesn’t take into account backfacing, so it’s a tie

hooray for backfacing (or lack thereof)!

thanks guys, that dot product thing is a better idea than what I was using (acos (dot normalize vec1 normalize vec2))

I was after something that does consider continuity between faces which also means backfacing is not required. I wrote this based off you’re responses:

fn getFacesByAngle obj faceIndex angleThreshold:20 restoreSelection:true =
 (
 	local numFaces = obj.numfaces
 	local faceNormals = for i = 1 to numFaces collect polyOp.getFaceNormal obj i
 	local normalAround = faceNormals[faceIndex]
 	local angleDot = cos angleThreshold
 	
 	local oldFaceSel = if restoreSelection then polyOp.getFaceSelection obj else undefined
 	local grow = obj.EditablePoly.GrowSelection
 	local matchFaces = #(faceIndex)
 	local oldCount = matchFaces.count
 	local notFinished = true
 	
 	polyOp.setFaceSelection obj faceIndex
 	while notFinished do
 	(
 		if keyboard.escPressed do throw " ESCAPE KEY PRESSED " -- just in case (thanks Richard for this from another thread)
 		grow selLevel:#Face -- grow the face selection
 		matchFaces = (polyop.getFaceSelection obj) as array
 		matchFaces = for f in matchFaces where (dot normalAround faceNormals[f]) >= angleDot collect f
 		
 		if matchFaces.count != oldCount then oldCount = matchFaces.count else notFinished = false
 	)
 	
 	if restoreSelection do polyOp.setFaceSelection obj oldFaceSel
 	
 	matchFaces
 )

Hi Joel,
I just upgraded the function to take into account face contiguity. You can now choose between:

#none -> no contiguity limits
#byVert -> faces must be contiguous for (share) at least a vertex
#byEdge -> faces must be contiguous for (share) at least an edge

function getFacesByAngle oPoly iFace fDegThresh contig:#none =
(
    if ((classOf oPoly) != Editable_Poly) then
        throw "Wrong input in function getFacesByAngle() - Editable Poly"
        
    if ((classOf iFace) != Integer) then
        throw "Wrong input in function getFacesByAngle() - Face Index"

    if ( (iFace <= 0) or (iFace > (polyOp.getNumFaces oPoly)) ) then
        throw "Wrong input in function getFacesByAngle() - Face Index out of boundaries"
        
    if ( (classOf fDegThresh != Float) and (classOf fDegThresh != Integer) ) then
        throw "Wrong input in function getFacesByAngle() - Angle Threshold"

    if ( (fDegThresh < 0) or (fDegThresh > 180) ) then
        throw "Wrong input in function getFacesByAngle() - Angle Threshold out of boundaries"

    -- remove all the previous test if not necessary

    local p3FaceNormal = polyOp.getFaceNormal oPoly iFace
    local fTestDot = cos fDegThresh
    
    local iNumFaces = polyOp.getNumFaces oPoly 
    
    if (contig == #none) then
    (
        local baSelFaces = #{1..iNumFaces}

        for i = 1 to iNumFaces do
        (
            p3ItemNormal = polyOp.getFaceNormal oPoly i
                
            if ((dot p3FaceNormal p3ItemNormal) < fTestDot) then
                baSelFaces[i] = false
        )
    )
    else if (contig == #byVert) then
    (
        local baLimitVerts = #{}
        local baLimitFaces = #{iFace}
        
        local baSelFaces = #{iFace}
        local baUnselFaces = #{}

        while (baLimitFaces.numberSet > 0) do
        (
            baLimitVerts = (polyOp.getVertsUsingFace oPoly baSelFaces) - (polyOp.getVertsUsedOnlyByFaces oPoly baSelFaces)
            baLimitFaces = (polyOp.getFacesUsingVert oPoly baLimitVerts) - baSelFaces - baUnselFaces

            for item in baLimitFaces do
            (
                p3ItemNormal = polyOp.getFaceNormal oPoly item
                    
                if ((dot p3FaceNormal p3ItemNormal) >= fTestDot) then
                (
                    baSelFaces[item] = true
                )
                else
                (
                    baUnselFaces[item] = true
                )
            )
        )
    )
    else if (contig == #byEdge) then
    (
        local iNumFaces = polyOp.getNumFaces oPoly
    
        local baLimitVerts = #{}
        local baLimitFaces = #{iFace}
        
        local baSelFaces = #{iFace}
        local baUnselFaces = #{}

        local baEdgeSet01 = #{}
        local baFaceSet01 = #{}
        local baEdgeSet02 = #{}

        while (baLimitFaces.numberSet > 0) do
        (
            baEdgeSet01 = polyOp.getEdgesUsingFace oPoly baSelFaces
            baFaceSet01 = #{1..iNumFaces} - baSelFaces
            baEdgeSet02 = polyOp.getEdgesUsingFace oPoly baFaceSet01

            baLimitEdges = baEdgeSet01 * baEdgeSet02
            baLimitFaces = (polyOp.getFacesUsingEdge oPoly baLimitEdges) - baSelFaces - baUnselFaces

            for item in baLimitFaces do
            (
                p3ItemNormal = polyOp.getFaceNormal oPoly item
                    
                if ((dot p3FaceNormal p3ItemNormal) >= fTestDot) then
                (
                    baSelFaces[item] = true
                )
                else
                (
                    baUnselFaces[item] = true
                )
            )
        )
    )

    ( -- just for visual feedback, remove if unneeded
        polyOp.setFaceSelection oPoly baSelFaces
        forceCompleteRedraw()
    )

    return baSelFaces
)
  • Enrico

Thanks Enrico

Your #byVertex method is nice and short and works correctly although I dont quite understand why… The method I posted sometimes returns some odd results so i’m using yours. Thanks again