Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Generator sticks in a dirty loop

    Cinema 4D SDK
    3
    4
    921
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • matniedobaM
      matniedoba
      last edited by

      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)
      
      1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand
        last edited by ferdinand

        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

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 1
        • r_giganteR
          r_gigante
          last edited by

          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

          1 Reply Last reply Reply Quote 0
          • matniedobaM
            matniedoba
            last edited by

            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.

            1 Reply Last reply Reply Quote 0
            • First post
              Last post