Alternative to createFace for editable Poly and Edit Poly modifier?


Does anyone know of a fast way to create multiple faces at once from existing verts in an Editable Poly and/or Edit Poly modifier? The Maxscript createFace function is absurdly slow on dense models, if you want to create more then 1 or 2 faces at a time.


You could create a trimesh from these verts and then attach it to the main mesh. Of course it will require to weld them to the main mesh afterwards

Did you also try polyop.createPolygon <Poly poly> <vertex array> ?


Late response, but wanted to give an update.
I tried polyop.createPolygon and found it wasn’t any faster. So instead wrote a function for generating faces using the mesh “setface” command, and for the most part it does what I want. The problem is the faces created by setface often have flipped normals compared to their adjacent faces, and so far I haven’t been able to figure out why. I can detect and fix the flipped faces afterwards, but getting the needed position and normal data kills performance.

Anyone know how to prevent setface from creating flipped face normals?


You need to set the correct vertex order.

	delete objects
	mdf = edit_normals displaylength:100
	tmesh = mesh numverts:3 numfaces:1
	setvert tmesh 1 [  0,  0,0]
	setvert tmesh 2 [100,  0,0]
	setvert tmesh 3 [  0,100,0]
	setface tmesh 1 [1,2,3]
	for j = 1 to 3 do setedgevis tmesh 1 j true
	addmodifier tmesh mdf
	tmesh = mesh numverts:3 numfaces:1
	setvert tmesh 1 [120,  0,0]
	setvert tmesh 2 [220,  0,0]
	setvert tmesh 3 [120,100,0]
	setface tmesh 1 [1,3,2]
	for j = 1 to 3 do setedgevis tmesh 1 j true
	addmodifier tmesh mdf
	max select all
	max modify mode


I see. I was hoping their was a way to have it directly inherit the flipped direction based on a connected face, but of course nothings ever that easy. Well, here are the functions I’m using right now, maybe someone can recommend some improvements since I’m running out of ideas how to improve performance further:

fn dotFixed V1 V2 = (
    --Fixes a rounding error in maxs dot product (if ignored can result in acos producting a "-1.#IND" value).
    d = dot V1 V2
    case of ( 
        (d < -1.0):( d = -1.0)
        (d > 1.0):( d = 1.0)
    d --return
fn compareFloats f1 f2 = (
    f1 == f2 OR abs (f1 - f2) <= (1e-4)

fn triangleIsFlipped midPnts outerPnts faceNormals = (
    Function takes info from two adjacent triangles and determines if one of them faces is flipped compared to the other.
    To do this, first it removes any skewing (makes sure outerPnts are each perpendicular to midPnts line), then checks if they are pointing in the same or opposite direction...
    ...if they're pointing the same direction the face normals angle should be 90-180 degrees, else if pointing away then 0-90. If that's not the case then the face is flipped.
        midPnts is an array of the two vert positions shared between both triangles. order doesn't matter.
        outerPnts is an array of the two vert positions that are NOT shared by the triangles. values must be in same order as faceNormals.
        faceNormals is an array of the two faces normals (one per face). values must be in same order as outerPnts.
    ----Get normalized un-skewed triangle directions----
    --mid pole
    mpC = (midPnts[1] + midPnts[2]) / 2
    mpDir = normalize(midPnts[2] - midPnts[1])
    --triangle 1
    t1Dir = normalize(outerPnts[1] - mpC)
    t1DirFixed = normalize(cross mpDir (cross mpDir t1Dir))
    if (distance t1DirFixed t1Dir) > (distance (t1DirFixed * -1) t1Dir) do t1DirFixed *= -1
    --triangle 2
    t2Dir = normalize(outerPnts[2] - mpC)
    t2DirFixed = normalize(cross mpDir (cross mpDir t2Dir))
    if (distance t2DirFixed t2Dir) > (distance (t2DirFixed * -1) t2Dir) do t2DirFixed *= -1
    --Get angles
    nrmAng = acos(dotFixed faceNormals[1] faceNormals[2])
    dirDot = dot t2DirFixed t1DirFixed
    --Check if face normals pointing correct direction
    faceIsFlipped = false
    if compareFloats dirDot 0.0 then (
        if (dot faceNormals[1] t2DirFixed) < 0 do faceIsFlipped = true
    else if dirDot > 0 then ( --close
        if nrmAng < 90.0 do faceIsFlipped = true
    else ( --far
        if nrmAng > 90.0 do faceIsFlipped = true
    faceIsFlipped --return

fn createFacesFromVertGroups  theClass theObj theMod vertGroups = (
    vertGroups contains one sub-array of vert indexs per triangle or quad to be created (in clockwise/counterclockwise order).
    New triangle normals are flipped based on the adjacent triangle which shares its first and last vert (for performance reasons other sides not checked).
    Only supports triangles and quads atm.
    May have issues with instanced edit_poly modifiers (if they have different showendresult states).
    --Turn off end result so we can get trimesh without modifiers changes.
    serStart = showendresult
    showendresult = false --note this does nothing if multiple objects with different showendresult states selected
    --Convert vert index arrays into point3 values, and if quads get invisible edge indexs.
    triangleVrtGrps = #()
    invisEdjIdxs = #()
    for curGrp in vertGroups do (
        if curGrp.count == 3 then (
            append triangleVrtGrps [curGrp[1], curGrp[2], curGrp[3]]
            append invisEdjIdxs #(true , true , true)
        else if curGrp.count == 4 do (
            append triangleVrtGrps [curGrp[1], curGrp[2], curGrp[4]]
            append invisEdjIdxs #(true , false , true)
            append triangleVrtGrps [curGrp[4], curGrp[3], curGrp[2]]
            append invisEdjIdxs #(true , true , false)
    extraFaceCount = triangleVrtGrps.count
    --Get mesh data
    theMesh = snapshotasmesh theObj
    oldFaceCount = theMesh.numfaces
    newFaceCount = oldFaceCount + extraFaceCount
    allFacsBIT = #{1..oldFaceCount}
    --Create expanded editable_mesh, and append existing mesh data to it.
    tmpEMesh = Editable_mesh()
    tmpEMesh.numfaces = newFaceCount
    attach tmpEMesh theMesh
    --Create new faces on temporary editable_mesh
    for i=1 to extraFaceCount do ( setface tmpEMesh (oldFaceCount + i) triangleVrtGrps[i] )
    update tmpEMesh
    --Update edge visibility (must be done here, before flipping faces, else edge order will be different!)
    for i=1 to extraFaceCount do (
        facIdx = oldFaceCount + i
        edjVis = invisEdjIdxs[i]
        setEdgeVis tmpEMesh facIdx 1 edjVis[1]
        setEdgeVis tmpEMesh facIdx 2 edjVis[2]
        setEdgeVis tmpEMesh facIdx 3 edjVis[3]
    --For each new face that has an adjacent triangle (on edge between first/last verts), check for and fix if flipped.
    --This is slooooow, but haven't found a better way to do it yet unfortunately.
    flippedFacs = #{}
    gfuv = meshop.getFacesUsingVert
    gvuf = meshop.getVertsUsingFace
    gvp = meshop.getVert
    for i=1 to extraFaceCount do (
        facIdx = oldFaceCount + i
        vrtAFacesBIT = gfuv tmpEMesh #{triangleVrtGrps[i][1]}
        vrtBFacesBIT = gfuv tmpEMesh #{triangleVrtGrps[i][3]}
        sharedFac = 0 ; for fac in ((vrtAFacesBIT * vrtBFacesBIT) - #{facIdx}) while (sharedFac=fac;false) do ()
        if sharedFac != 0 do (
            if flippedFacs[sharedFac] then ()--(append flippedFacs facIdx)
            else (
                midVrts = #(triangleVrtGrps[i][1],triangleVrtGrps[i][3])
                midPnts = for vrt in midVrts collect (gvp tmpEMesh vrt)
                f1Vrt = 0 ; for vrt in ((gvuf tmpEMesh #{facIdx}) - #{triangleVrtGrps[i][1],triangleVrtGrps[i][3]}) while (f1Vrt=vrt;false) do ()
                f2Vrt = 0 ; for vrt in ((gvuf tmpEMesh #{sharedFac}) - #{triangleVrtGrps[i][1],triangleVrtGrps[i][3]}) while (f2Vrt=vrt;false) do ()
                outerVrts = #(f1Vrt,f2Vrt)
                outerPnts = for vrt in outerVrts collect (gvp tmpEMesh vrt)
                faceNormals = #((getFaceNormal tmpEMesh facIdx), (getFaceNormal tmpEMesh sharedFac))
                isFlipped = triangleIsFlipped midPnts outerPnts faceNormals
                if isFlipped do (append flippedFacs facIdx)
    if flippedFacs.numberset > 0 do (meshop.flipNormals tmpEMesh flippedFacs)
    --Attach temp mesh to original obj
    if theClass == Edit_Poly then (
        totalFaces = theMod.GetNumFaces node:theObj
        allFacsBIT = #{1..totalFaces}
        allFacsBIT.count = totalFaces --setSlection requires the bitarray to be modified like this beforehand to work!
        theMod.SetSelection #Face #{} node:theObj
        EditPolyMod.setSelection theMod #Face allFacsBIT node:theObj
        theMod.buttonop #deleteface
        theMod.attach tmpEMesh
    else if theClass == Editable_Poly then (
        polyop.deleteFaces theObj #{1..(polyop.getNumFaces theObj)}
        polyop.deleteVerts theObj #{1..(polyop.getNumVerts theObj)}
        polyop.attach theObj tmpEMesh
    else if theClass == Editable_Mesh do (
        meshop.deleteFaces theObj #{1..oldFaceCount}
        attach theObj tmpEMesh
        update theObj
    --restore end result setting
    showendresult = serStart
    --no return...


It is hard to guess how you implement the code in a real situation.
Could you provide a fully working test, for example using a teapot, in order to understand what you are trying to achieve?

teapot segments:64