Solved PolygonObject.SetSelectEdges() and the Neighbor() class, a giant dilemma

PolygonObject's

SetSelectedEdges(self, e, pSel, ltype)

..., method takes a pSel argument of type BaseSelect.

The help for this method further tells us that:

The edges are indexed uniquely by a Neighbor object, so each 
edge has a single index.

OK, makes sense so far. We use the edge index from the Neighbor object to identify an edge once, rather than N times where N is the number of polygons that share said edge.

But, in order to use a "logical" edge index from the Neighbor object, we need to be able to tell which edge and/or edges this logical edge maps to. Unfortunately, the Neighbor class offers no way to get the properties of the logical edge to physical edge mapping, that it stores internally, given a logical edge index.

There is only one method in the Neighbor class that has anything to do with the logical indexes that it stores, and that is Neighbor.GetEdgeCount(). This puts a cap on the number of logical edges, which are clearly indexed from 0 to Neighbor.GetEdgeCount() - 1.

But, how in the world does one get info about these logical edges (by logical edge index) in order to decide whether or not to use them with the PolygonObject.SetSelectedEdges() method, if the representation and properties of the logical edges are completely opaque to users of the c4d.utils.Neighbor class? There is not a single method in this class that gives the developer any information about which physical points, edges, or polygons a logical edge, as referred to by its edge index in the Neighbor class, represents.

Without any information and mapping to something physical and concrete, how can one refer to a "distinct logical edge" by its index within Neighbor and use that as the basis for forming the selection criteria inside a BaseSelect object as required by the pSel argument of the SetSelectedEdges() method of PolygonObject?

TL;DR: If I can't tell which actual edges the edge indices of a Neighbor object refer to, I can't make use of Neighbor edge indices for any operation that requires them.

Am I overlooking something, or is this API completely broken?

Hi @mikegold10, to adds some clarification.
A PolygonObject doesn't have a strong edge representation. An edge is simply defined by 2 points.
With that in mind, there is actually no direct way (like GetPolygonCount or GetPointsCount) to retrieves the count of an edge in a PolygonObject, but a BaseSelect to select all elements needs this count to be able to select all elements. But here the tricky part because there is also ngnon edge(aka hidden edges) that can't be selected directly.
So the faster way to know the count of all selectable edges is to create a neighbor object and use GetEdgeCount.

So now let's say I want via python to select edge from pt ID 80 to pt ID 81.
Here is how to do it with both methods:

import c4d

def GetEdgeFromCpolygon(polygonObject, ptIdA, ptIdB):
    for polyId, cPoly in enumerate(polygonObject.GetAllPolygons()):
        edgeIdAB = cPoly.FindEdge(ptIdA, ptIdB)
        if edgeIdAB != c4d.NOTOK:
            return polyId, edgeIdAB

    raise ValueError("Unable to found edge for ptId {0} to ptId {1}".format(ptIdA, ptIdB))

def GetFromNeighboor(polygonObject, nbr, ptIdA, ptIdB):
    polyIdListA = nbr.GetPointPolys(ptIdA)
    polyIdListB = nbr.GetPointPolys(ptIdB)

    commonPolygonIdList = list(set(polyIdListA) & set(polyIdListB))
    for commonPolygonId in commonPolygonIdList:
        nbrPolyInfo = nbr.GetPolyInfo(commonPolygonId)

        neigboorPolyId = nbr.GetNeighbor(ptIdA, ptIdB, commonPolygonId)
        if not neigboorPolyId in nbrPolyInfo["face"]:
            continue
        
        nbrPolyId = nbrPolyInfo["face"].index(neigboorPolyId)
        return nbrPolyInfo["edge"][nbrPolyId]
    
    raise ValueError("Unable to found edge for ptId {0} to ptId {1}".format(ptIdA, ptIdB))


# Main function
def main():
    doc.StartUndo()

    # Select from the Cpolygon by editing directly the Edge BaseSelect
    polygonId, edgeId = GetEdgeFromCpolygon(op, 80, 81)

    edges = op.GetEdgeS()
    doc.AddUndo(c4d.UNDO_CHANGE, op)
    #edges.Select(polygonId * 4 + edgeId)

    # Select from SetSelectedEdges
    nbr = c4d.utils.Neighbor()
    nbr.Init(op)
    
    edges = c4d.BaseSelect()
    edgeId = GetFromNeighboor(op, nbr, 80, 81)
    edges.Select(edgeId)

    doc.AddUndo(c4d.UNDO_CHANGE, op)
    op.SetSelectedEdges(nbr, edges, c4d.EDGESELECTIONTYPE_SELECTION)

    doc.EndUndo()
    c4d.EventAdd()

if __name__=='__main__':
    main()

Note that the SetSelectedEdges fail if one of the points targeted to be select have more than 1 open edge, since in GetFromNeighboor, neigboorPolyId will have an id of -1 and in the face list returned by GetPolyInfo 2 face will have an Id of -1, so there is no way to know which edge is the correct one.

However, looking at the implementation of SetSelectedEdges I would really recommend not using it except for full selection since it iterates each polygon and do.

for polyId in range(op.GetPolygonCount():
    polyInfo = nbr.GetPolyInfo(polyId)
        
    # {0, 1, 2, 3} mean there is one condition for each so understand it as ["edge"][0] then ["edge"][1], etc..
    if passedEdgeSelectToSetSelectedEdges.IsSelected(polyInfo["edge"][{0, 1, 2, 3}]:
        baseSelect.Select(polyId  * 4 + {0, 1, 2, 3})

So using SetSelectedEdges is way more inefficient, can give false result than using the BaseSelect directly so I will really recommend using GetEdgeS, GetEdgeH or GetPhongBreak.

Hope it answers your questions,
Cheers,
Maxime

Hi,

edges are globally indexed by polygon_index * 4 + local edge_index (see PolygonObject.GetEdgeS), where the local edge index lies in the interval [0, 3]. So the edge between the points c and d of the polygon with the id 10 would have the index 43. You will have to pass edges in this form to SetSelectEdges, the function will then turn your partial edge selection into a full selection by selecting the corresponding global edge indices of polygons that share the selected edges.

You can also do the same by using the Neighbor class and do it manually by retrieving the polygons attached to an edge defined by two points.

Cheers,
zipit

MAXON SDK Specialist
developers.maxon.net

As discussed here going from unique edge index to global edge index (polygon * 4 + polygon side) is not possible. You are better of working with global edge indeces to start with ... I have learned that the hard way.

@zipit said in PolygonObject.SetSelectEdges() and the Neighbor() class, a giant dilemma:

Hi,

edges are globally indexed by polygon_index * 4 + local edge_index (see PolygonObject.GetEdgeS), where the local edge index lies in the interval [0, 3]. So the edge between the points c and d of the polygon with the id 10 would have the index 43. You will have to pass edges in this form to SetSelectEdges, the function will then turn your partial edge selection into a full selection by selecting the corresponding global edge indices of polygons that share the selected edges.

You can also do the same by using the Neighbor class and do it manually by retrieving the polygons attached to an edge defined by two points.

Cheers,
zipit

The whole point of SetSelectedEdges is not to do the 4*PolygonIdx+PolygonEdgeIdx math and have to supply multiple physical edges where a single logical edge from Neighbor will suffice).

Please reread the below quoted portion of my post and especially the part I marked in bold:

SetSelectedEdges(self, e, pSel, ltype)

..., method takes a pSel argument of type BaseSelect.

The help for this method further tells us that:

The edges are indexed uniquely by a Neighbor object, so each
edge has a single index.

This tells us that we should not use the 4*PolygonIdx+PolygonEdgeIdx formula to specify the edge indices for the BaseSelect, but instead refer to the logical edge indices as determined and governed by the Neighbor object after it does its calculations. The problem and crux of the question is that we don't know what those edges are, since they don't seem to be accessible to code that is outside of the Neighbor object. This information seems to be encapsulated within the Neighbor's guts and hidden from the users of the object, making it inadequate for its intended role in this case: The selection of a subset of the logical edge indices from the Neighbor as required by the pSel argument of SetSelectedEdges().

My point with regard to which edge indexes to use is further driven home by the following sample code that appears in the help for BaseSelect.SelectAll() which shows the correct usage of the BaseSelect with the SetSelectedEdges() method and coincides completely with what I am asserting:

def main():
    nbr = c4d.utils.Neighbor()
    nbr.Init(op) # Initialize neighbor with a polygon object

    edges = c4d.BaseSelect()
    
    # Select all edges in the range [0, nbr.GetEdgeCount()-1]   
    edges.SelectAll(nbr.GetEdgeCount()-1) 

    # ### Dev Comments
    # ### ============
    # ### The preceding line clearly demonstrates that Neighbor based
    # ### logical indices should be used with the BaseSelect since
    # ### nbr.GetEdgeCount() returns the number of Neighbor deduced
    # ### logical edges and not the total number of (potential)
    # ### physical edges (i.e., which would be 4*num_polys), at least the
    # ### the way I understand it.

    # Select all edges from our edges selection
    op.SetSelectedEdges(nbr, edges, c4d.EDGESELECTIONTYPE_SELECTION) 
    c4d.EventAdd() # Update Cinema 4D

if __name__ == '__main__':
    main()

Note: The Dev Comments portion in the above code was added by me to point out what can be deduced from the line of significance in the sample code from the help documentation.

Hi @mikegold10, to adds some clarification.
A PolygonObject doesn't have a strong edge representation. An edge is simply defined by 2 points.
With that in mind, there is actually no direct way (like GetPolygonCount or GetPointsCount) to retrieves the count of an edge in a PolygonObject, but a BaseSelect to select all elements needs this count to be able to select all elements. But here the tricky part because there is also ngnon edge(aka hidden edges) that can't be selected directly.
So the faster way to know the count of all selectable edges is to create a neighbor object and use GetEdgeCount.

So now let's say I want via python to select edge from pt ID 80 to pt ID 81.
Here is how to do it with both methods:

import c4d

def GetEdgeFromCpolygon(polygonObject, ptIdA, ptIdB):
    for polyId, cPoly in enumerate(polygonObject.GetAllPolygons()):
        edgeIdAB = cPoly.FindEdge(ptIdA, ptIdB)
        if edgeIdAB != c4d.NOTOK:
            return polyId, edgeIdAB

    raise ValueError("Unable to found edge for ptId {0} to ptId {1}".format(ptIdA, ptIdB))

def GetFromNeighboor(polygonObject, nbr, ptIdA, ptIdB):
    polyIdListA = nbr.GetPointPolys(ptIdA)
    polyIdListB = nbr.GetPointPolys(ptIdB)

    commonPolygonIdList = list(set(polyIdListA) & set(polyIdListB))
    for commonPolygonId in commonPolygonIdList:
        nbrPolyInfo = nbr.GetPolyInfo(commonPolygonId)

        neigboorPolyId = nbr.GetNeighbor(ptIdA, ptIdB, commonPolygonId)
        if not neigboorPolyId in nbrPolyInfo["face"]:
            continue
        
        nbrPolyId = nbrPolyInfo["face"].index(neigboorPolyId)
        return nbrPolyInfo["edge"][nbrPolyId]
    
    raise ValueError("Unable to found edge for ptId {0} to ptId {1}".format(ptIdA, ptIdB))


# Main function
def main():
    doc.StartUndo()

    # Select from the Cpolygon by editing directly the Edge BaseSelect
    polygonId, edgeId = GetEdgeFromCpolygon(op, 80, 81)

    edges = op.GetEdgeS()
    doc.AddUndo(c4d.UNDO_CHANGE, op)
    #edges.Select(polygonId * 4 + edgeId)

    # Select from SetSelectedEdges
    nbr = c4d.utils.Neighbor()
    nbr.Init(op)
    
    edges = c4d.BaseSelect()
    edgeId = GetFromNeighboor(op, nbr, 80, 81)
    edges.Select(edgeId)

    doc.AddUndo(c4d.UNDO_CHANGE, op)
    op.SetSelectedEdges(nbr, edges, c4d.EDGESELECTIONTYPE_SELECTION)

    doc.EndUndo()
    c4d.EventAdd()

if __name__=='__main__':
    main()

Note that the SetSelectedEdges fail if one of the points targeted to be select have more than 1 open edge, since in GetFromNeighboor, neigboorPolyId will have an id of -1 and in the face list returned by GetPolyInfo 2 face will have an Id of -1, so there is no way to know which edge is the correct one.

However, looking at the implementation of SetSelectedEdges I would really recommend not using it except for full selection since it iterates each polygon and do.

for polyId in range(op.GetPolygonCount():
    polyInfo = nbr.GetPolyInfo(polyId)
        
    # {0, 1, 2, 3} mean there is one condition for each so understand it as ["edge"][0] then ["edge"][1], etc..
    if passedEdgeSelectToSetSelectedEdges.IsSelected(polyInfo["edge"][{0, 1, 2, 3}]:
        baseSelect.Select(polyId  * 4 + {0, 1, 2, 3})

So using SetSelectedEdges is way more inefficient, can give false result than using the BaseSelect directly so I will really recommend using GetEdgeS, GetEdgeH or GetPhongBreak.

Hope it answers your questions,
Cheers,
Maxime

@m_adam said in PolygonObject.SetSelectEdges() and the Neighbor() class, a giant dilemma:
...

Hope it answers your questions,
Cheers,
Maxime

Thank you Maxime, this is a lot of useful info. Let me think through all of this and reply here if I have any further issues.