Maxscript auto-select convex edges


Having searched around I can’t find a way to start writing maxscript to select convex edges.

To increase the realism of a building render, I add a slight chamfer to the edges that are convex, leaving concave edges alone. The selection of such edges would be simple if all buildings were cubes (all edges being convex).

To save progressively adding to the manual selection of convex edges (once in edge sub-object mode of the building mesh), I was wondering if anyone has any sites (searched maxscript dot com and here) or ideas on putting together a script to run that would select all convex edges so chamfering could be assigned to only those edges?

The workflow would be: enter edge sub-object mode for the building object, run convex edge selection maxscript, click Chamfer.

Thanks for any help.


to start, you should probably grab data on which faces are connected to which other faces, get the faces’ normal data, and use that normal data to see if the two normals are 1. convex and 2. ‘convex enough’ (a sphere is all convex faces, but you wouldn’t usually bevel that), then select the shared edge(s).


Hi Steve,
here it is. Evaluate it and you’ll get a rollout with a button to select convex edges in Editable Poly objects. It works on face selections too. If there aren’t selected faces it processes the whole object. It’s not super optimized, but should work well enough.

rollout rolTest "Convex Edges"
    button btRun "Select!" width:90 align:#center offset:[0, -2]

    function selectConvexEdges oPoly =
        polyOp.setEdgeSelection oPoly #{}

        local baFaceSel = polyOp.getFaceSelection oPoly

        if ( (baFaceSel.isEmpty == true) or (subObjectLevel == 0) or (subObjectLevel == undefined) ) do
            baFaceSel = #{1..(polyOp.getNumFaces oPoly)}

        local baVertSet = polyOp.getVertsUsedOnlyByFaces oPoly baFaceSel
        local baEdgeTest = (polyOp.getEdgesUsingVert oPoly baVertSet) - (polyOp.getOpenEdges oPoly)

        local mGetFaceNormal = polyOp.getFaceNormal
        local mGetVert = polyOp.getVert

        local iNumFaces = polyOp.getNumFaces oPoly
        local ap3FaceNormals = for i = 1 to iNumFaces collect (mGetFaceNormal oPoly i)

        local iNumVerts = polyOp.getNumVerts oPoly
        local ap3VertPos = for i = 1 to iNumVerts collect (mGetVert oPoly i)

        local baConvexEdges = #{}

        local aiVerts = #()
        local aiFaces = #()

        local p3EdgeNorm = [0,0,0]
        local p3Cross = [0,0,0]

        for iEdge in baEdgeTest do
            aiVerts = polyOp.getEdgeVerts oPoly iEdge
            aiFaces = polyOp.getEdgeFaces oPoly iEdge

            p3EdgeNorm = normalize(ap3VertPos[aiVerts[2]] - ap3VertPos[aiVerts[1]])
            p3Cross = cross ap3FaceNormals[aiFaces[1]] p3EdgeNorm

            if (dot p3Cross (ap3FaceNormals[aiFaces[2]]) < 0) do
                baConvexEdges[iEdge] = true

        setCommandPanelTaskMode #modify
        subObjectLevel = 2
        polyOp.setEdgeSelection oPoly baConvexEdges

    on btRun pressed do
        if ( (selection.count == 1) and ( (classOf selection[1]) == Editable_Poly) ) do
            selectConvexEdges selection[1]

) -- End Rollout

createDialog rolTest 96 27 style:#(#style_toolwindow, #style_border, #style_sysmenu)
  • Enrico


Hi Enrico,

So sorry it’s been so long since your post for me to be replying - I had memory problems on my PC and had to quit my involvement in the Evermotion Rendering Comp (the reason why I had posted).

Had forgotten to check back until today when I started searching again for the same thing.

Awesome script. Many thanks friend. :bowdown: :thumbsup:


Hi Steve,
I just edited previous code. It is a little improved version (avoids memory leaks, thanks to Denis Trofimov).

  • Enrico


here is my version:

fn getConvexEdges node:selection[1] show:on threshold:45 ignoreHardEdges:off ignoreOpenEdges:on = if iskindof node Editable_Poly do
 local edgeFaces = polyop.getedgefaces 
 local faceNormal = polyop.getfacenormal 
 local faceCenter = polyop.getfacecenter 
 local faceSmooth = polyop.getfacesmoothgroup
 edges = #{}
 for e in (node.edges as bitarray) do
  ff = edgeFaces node e
  if (not ignoreOpenEdges and ff.count != 2) or 
   (ff.count == 2) and 
	n1 = faceNormal node ff[1]
	n2 = faceNormal node ff[2]
	vc = faceCenter node ff[2] - faceCenter node ff[1]
	(dot n1 vc <= 0) and (acos (dot n1 n2) > threshold) and 
	 not ignoreHardEdges or (bit.and (faceSmooth node ff[1]) (faceSmooth node ff[2]) > 0)
  do append edges e
 if show do node.selectededges = edges


Thanks DenisT. Loving the whole maxscript thing. :thumbsup:


This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.