Generator sticks in a dirty loop



  • Dear Python experts,

    I am working on a Python Generator Plugin which creates Splines from Polygon Edges including some cleanup procedures. The problem I am facing is that the Generator works in a simple setup (e.g. it has a Voronoi Fracture or Clone under it) but when it comes in complex setups with more children it spins in an endless dirty loop. It really gets evil when Fields are involved in that setup. I tried everything including SetOptimizeCache(True) or checking dirty states of the children with a loop but nothing really helped here. Maybe it is because of all these modelling commands which probably make a scene dirty.

    Could you provide me with some help? I know it is a generic question, I would like to be more specific but I don't know how. I would really appreciate your help here.

    Here is the code of the entire plugin:

    import os
    import c4d
    from c4d import plugins, utils, bitmaps
    from c4d.utils import SplineHelp
    
    class SplFromEdge(plugins.ObjectData):
    
        def Init(self, node):
            # Retrieves the BaseContainer Instance to set the default values
            #self.SetOptimizeCache(True)
            data = node.GetDataInstance()
            if data is None:
                return False
            
            data.SetBool(c4d.WELDSIMILAR, False)
            data.SetFloat(c4d.THRESHOLD, 0.01)
            data.SetInt32(c4d.O_SPLINEFROMEDGEMODE, c4d.O_SPLINEFROMEDGEMODE_TAG)
            data.SetBool(c4d.O_SPLINEFROMEDGEMODE_AUTOUPDATE , True)
            data.SetBool(c4d.CLOSE,True)
            data.SetBool(c4d.O_SPLINEFROMEDGEMODE_OPTIMIZE, True)
            return True
    
        #grey out the threshold in the UI
        def GetDEnabling(self, node, id, t_data, flags, itemdesc):
            data = node.GetDataInstance()
            weld = data.GetBool(c4d.WELDSIMILAR)
            if id[0].id == c4d.THRESHOLD:
                return weld
    
            # Retrieves the current interpolation
            inter = node[c4d.O_SPLINEFROMEDGEMODE]
    
            # Defines enable state for the selection
            if id[0].id == c4d.EDGESELECTION:
                return inter == c4d.O_SPLINEFROMEDGEMODE_TAG
            return True
    
    
        #iterator to go through the whole hierarchy
        def walk(self,op) :  
            if not op: return  
            elif op.GetDown() :  
                return op.GetDown()  
            while op.GetUp() and not op.GetNext():  
                op = op.GetUp()  
            return op.GetNext()  
        
        #has all modeling operators
        def ExecModelingCommand(self, doc,opCtrl, op, parent):
            if op is None:
                return
    
            data = opCtrl.GetDataInstance()
            threshold = data.GetReal(c4d.THRESHOLD)
            weld = data.GetBool(c4d.WELDSIMILAR)
            mode = data.GetInt32(c4d.O_SPLINEFROMEDGEMODE)
            selectionSet = data.GetString(c4d.EDGESELECTION)
            optimize = data.GetBool(c4d.O_SPLINEFROMEDGEMODE_OPTIMIZE)
            close = data.GetBool(c4d.CLOSE)
            
            splineHelper = SplineHelp()
            container = c4d.BaseContainer()
            
            childObject = op.GetClone(c4d.COPYFLAGS_NONE)
            # Creates a temporary document.
            tempDoc = c4d.documents.BaseDocument()
            # Insert the cloned object to the temporary document.
            tempDoc.InsertObject(childObject)
            
            tempParent = c4d.BaseObject(c4d.Onull)
    
            if childObject is None or (mode==c4d.O_SPLINEFROMEDGEMODE_TAG and selectionSet==""):
                return
     
            parent.SetMg(childObject.GetMg())
    
            c4d.StatusSetText("Creating Splines from Edges")
            c4d.StatusSetSpin()
    
            res = utils.SendModelingCommand(command=c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                        list=[childObject],
                                        mode=c4d.MODELINGCOMMANDMODE_ALL,
                                        bc=container,
                                        doc=tempDoc)
    
            if res is None:
                return
            
            editableObject = res[0]
            #select edges based on a selection tag
            def selectEdges(obj,tag):
                selection = tag.GetBaseSelect()
                polyselection = obj.GetEdgeS()
                selection.CopyTo(polyselection)
    
            #iterate through the new created polygon objects and look for the ones with an edge selection tag
            objectList = []   
    
            while editableObject:
                if editableObject.CheckType(c4d.Opolygon):
                    if mode==c4d.O_SPLINEFROMEDGEMODE_TAG:
                        for i in editableObject.GetTags():
                            if i.GetName()==selectionSet and i.CheckType(c4d.Tedgeselection):
                                #select
                                selectEdges(editableObject,i)
                                #add to list                        
                                objectList.append(editableObject)
                    elif mode==c4d.O_SPLINEFROMEDGEMODE_ALL:
                        utils.SendModelingCommand(c4d.MCOMMAND_SELECTALL, 
                                                list = [editableObject], 
                                                mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION, 
                                                bc = c4d.BaseContainer())
                        objectList.append(editableObject)
                editableObject = self.walk(editableObject)
           
            if not objectList:
                c4d.StatusClear()
                return
    
            for i in objectList:
                i.InsertUnderLast(tempParent)
    
            spl = utils.SendModelingCommand(command=c4d.MCOMMAND_EDGE_TO_SPLINE,
                                                list=objectList,
                                                mode=c4d.MODELINGCOMMANDMODE_ALL,
                                                doc=objectList[0].GetDocument()) 
    
            c4d.StatusSetText("Optimizing Spline Objects")
            
            #Exploding Splines with multiple Segments which came from Edge to Spline
            splitList = []
            for i in objectList:
                child = i.GetDown()
                if child is not None:
                    if child.CheckType(c4d.Ospline):
    
                        splineHelper.InitSpline(child)
                        if splineHelper.GetSegmentCount() !=1:
                            splitList.append(child)
     
            #Explode Splines using the Explode Command
            if splitList:
                expSplines = utils.SendModelingCommand(command=c4d.MCOMMAND_EXPLODESEGMENTS,
                                                                        list=splitList,
                                                                        mode=c4d.MODELINGCOMMANDMODE_ALL,
                                                                        flags=c4d.MODELINGCOMMANDFLAGS_NONE,
                                                                        doc=splitList[0].GetDocument()) 
            
            #Creating a Spline List which will be later added under a parent object
            splineList = []
            obj=objectList[0]
            while obj:
                if obj.CheckType(c4d.Ospline):
                    splineHelper.InitSpline(obj)
                    if splineHelper.GetSegmentCount() !=0:
                        #Add all spline objects to a dedicated spline list. Every object which is not in that list will be deleted
                        splineList.append(obj)
                obj = self.walk(obj)
    
            #optimize the spline for duplicate points
            optimizeBC = c4d.BaseContainer()
            optimizeBC[c4d.MDATA_OPTIMIZE_POINTS]= optimize
            optimizeBC[c4d.MDATA_OPTIMIZE_UNUSEDPOINTS]= True
            optimizeBC[c4d.MDATA_OPTIMIZE_TOLERANCE]= 0.01
            optimizedSplines = utils.SendModelingCommand(command=c4d.MCOMMAND_OPTIMIZE,
                                                                        list=splineList,
                                                                        bc= optimizeBC,
                                                                        mode=c4d.MODELINGCOMMANDMODE_ALL,
                                                                        flags=c4d.MODELINGCOMMANDFLAGS_NONE)
            
            #filter overlapping objects
            c4d.StatusSetText("Cleaning Up and Welding")
    
            positionList = []
            keyList = []
            noDuplicateSplineList = []
            for i in splineList:
                points = i.GetAllPoints()
                bary = c4d.Vector(0)
                #find the center of all points            
                for j in points:
                    bary+=j
                bary = bary / len(points)
                # move the points by the amount represented by the new axis center              
                for index, point in enumerate(points) :  
                    point = point - bary 
                    i.SetPoint(index, point)  
                # notify about the points repositioning  
                i.Message(c4d.MSG_UPDATE)  
                # adjust the matrix offset component accordingly 
                currentMg = i.GetMg() 
                currentMg.off += bary
                # reposition the object in the space 
                i.SetMg(currentMg)
                #adjust object position
                polyParent = i.GetUp()
                offset = polyParent.GetMg().off
                #insert and adjust object position
                i.InsertUnderLast(tempParent)
                i[c4d.SPLINEOBJECT_CLOSED]=close
                currentMg = i.GetMg()
                currentMg.off += offset
                i.SetMg(currentMg)
                #remove overlapping splines if welding is active
                if weld:
                    #check spline object position if it mateches a certain threshold
                    overlapping = False
                    if positionList:
                        for j in positionList:
                            p1 = i.GetMg().off
                            p2 = j.GetMg().off
                            distance = (p2-p1).GetLength()
                            
                            if distance <= threshold: 
                                overlapping = True  
                    #check if it's overlapping and clear it
                    key = int((i.GetRad().x+i.GetRad().y+i.GetRad().z)/threshold)
                    if key in keyList and overlapping:
                        i.Remove()                    
                    else:
                        keyList.append(key)
                        positionList.append(i)
                        noDuplicateSplineList.append(i)
                else:
                    noDuplicateSplineList.append(i)
            c4d.StatusClear()
            
            #put everything under the parent null and return it
            for i in noDuplicateSplineList:
                i.InsertUnderLast(parent)
    
            return parent
        
        def GetVirtualObjects(self, op, hh):
    
            doc = op.GetDocument()
            data = op.GetDataInstance()       
    
            objInput = op.GetDown()
            
            if objInput is None:
                return None
            
            objRet = c4d.BaseObject(c4d.Onull)
            if doc is None or objRet is None:
                return None
    
            
            hierarchyClone = op.GetAndCheckHierarchyClone(hh, objInput, c4d.HIERARCHYCLONEFLAGS_ASPOLY, True)
            
            if hierarchyClone["dirty"] is False:
                return hierarchyClone["clone"]
                
            clone = hierarchyClone["clone"]
                    
            if clone is None:
                return op.GetCache()
        
            self.ExecModelingCommand(doc, op, clone, objRet)
            return objRet
    
    ###############################################################################################################
    # Plugin Registration
    ###############################################################################################################
    
    PLUGIN_ID_GENERATOR = 954679
    
    def RegisterObjectData(id, name, bmpPath, objData, desc, flags):
        bmp = bitmaps.BaseBitmap()
        bmp.InitWith(os.path.join(bmpPath, "res", "icon.tif"))
        plugins.RegisterObjectPlugin(id=id, str=name,
                                    g=objData,
                                    description=desc, icon=bmp,
                                    info=flags)
    
    if __name__ == "__main__":
        path, fn = os.path.split(__file__)
    
        RegisterObjectData(PLUGIN_ID_GENERATOR, "Spline from Edge", path, SplFromEdge, "osplinefromedge", c4d.OBJECT_GENERATOR | c4d.OBJECT_INPUT)
    


  • Hi,

    I think your biggest problem is your coding style. And I do not mean that in an offensive way, but simply am pointing out that you seem to have cornered yourself into a state of code that contains bugs where you (and also I) have no clue where they exactly stem from. You might want to read PEP 8 and the excellent Google Python Style Guide.

    With that out of the way, the obvious thing first: When you use the dirty state of the children of your generator to determine if it has to be rebuild or not, either touching one of the children or having a child which determines its dirty state by its parent state (i.e. the generator), will result in a loop.

    Some points:

    1. Returning None in GetVirtualObjects indicates a memory error to Cinema 4D. You rarely want to do this. Return a c4d.Onull-object instead. Also neither doc nor objRet can be None in your GVO, so checking for that is somewhat moot.
    2. You clone the children of your generator with GetAndCheckHierarchyClone and one of the first things you do in ExecModelingCommand is to clone that clone again without ever using or comparing against that first clone again. This obviously won't break anything, but it is a good indicator for being lost in ones own code.

    I do not have any more specific advice, since I stopped reading at some point as it got more and more convoluted (sorry, no offense intended). Instead I have some general advice:

    1. You probably need some abstraction or specifically split up ExecModelingCommand into its steps, so that you can describe and test each part.
    2. Follow a style and write some documentation (for yourself as one tends to overlook or forget things when they are not written down).
    3. Since your intention seems to be to return a spline, you should overwrite GetContour and not GVO.
    4. While your approach is valid, the heavy use of SendModellingCommand is probably not the best for the performance of your plugin and also a source of uncertainty for debugging. Creating a consecutive line segment array (i.e a linear spline) from an edge selection is not that hard. By doing so, you would also remove the need of any clean up clutter.

    I hope this helps.

    Cheers,
    zipit



  • Hi @matniedoba, thanks for reaching out us.

    With regard to your issue, given that it's not our primary task to debug plugins, to understand what's getting wrong I need you to provide:

    • test scenes of different complexity;
    • the smallest code able to reproduce the issue - rather than the whole plugin - considering that it's a matter of object dirty check.

    I also second @zipit recommendations because improving code design via better function abstraction and documentation will lead to an increased clarity, reliability and, in the end, comprehension.
    I'll try to find time in the course of the week to have a look at your issue given the items requested above.

    Best, R



  • Thank you @zipit for your comprehensive response, especially with the link to the coding guide. I guess it might be better to create the spline manually from the points which are connected by the edges without the usage of SendModellingCommand. It was good for prototyping functionality but as you mentioned, it is hard to debug. I appreciate you took the time (and I know it's a lot of convoluted code) to read through it. @r_gigante -> the work is currently on my side so you don't need to bother. I think I have enough information to do a second iteration and rewriting this from scratch.


Log in to reply