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

    Script: Connect + Delete all subdivision surface objects

    Cinema 4D SDK
    s26 python 2023
    3
    4
    996
    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.
    • D
      derekheisler
      last edited by

      Hi,

      I'm trying to write a script that will scan the full c4d file and auto connect + delete all subd objects.

      This is what I've written so far but it doesn't seem to find any subd objects, please help:

      import c4d
      
      def main():
          c4d.gui.MessageDialog("Running the main() function...")
      
          # Get the active document
          doc = c4d.documents.GetActiveDocument()
      
          # Initialize counters for number of Subdivision Surface objects and number of Subdivision Surface objects that were connected and deleted
          ss_count = 0
          ss_cd_count = 0
      
          # Get a list of all objects in the document
          objs = doc.GetObjects()
      
          # Loop through all objects in the document
          for obj in objs:
              # Check if the object is a Subdivision Surface object
              if obj.GetType() == c4d.Osubdiv:
                  # Increment the Subdivision Surface count
                  ss_count += 1
      
                  # Select the Subdivision Surface object
                  doc.SetActiveObject(obj, c4d.SELECTION_NEW)
      
                  # Connect and delete the object
                  new_obj = c4d.utils.SendModelingCommand(c4d.MCOMMAND_CURRENTSTATETOOBJECT, [obj], c4d.MODELINGCOMMANDMODE_ALL, doc, c4d.BaseThread())
                  if new_obj is not None:
                      # Increment the Subdivision Surface Connect and Delete count
                      ss_cd_count += 1
      
                      doc.SetActiveObject(new_obj[0], c4d.SELECTION_NEW)
                      c4d.CallCommand(12109) # Delete command ID
                      doc.SetActiveObject(None, c4d.SELECTION_NEW)
      
          # Display the counts in message dialog boxes
          msg = "Found {} Subdivision Surface objects.".format(ss_count)
          c4d.gui.MessageDialog(msg)
      
          msg = "Connected and deleted {} Subdivision Surface objects.".format(ss_cd_count)
          c4d.gui.MessageDialog(msg)
      
          # Update the document
          c4d.EventAdd()
      
      if __name__=='__main__':
          main()
      

      I have 3 subd objects in my scene and it does not detect any of them. So I'm not sure where I'm going wrong. Thanks in advance!

      1 Reply Last reply Reply Quote 0
      • H
        HerrMay
        last edited by

        Hi @derekheisler,

        there are multiple reasons why your script isn't catching all of you SDS objects. Number one is that you only take the top level of objects into account. doc.GetObjects() does not account for nested objects that live as children under some other objects. So one solution here is to write a custom function that traverses the complete document i.e. not only the top level objects but every child, sibling, grandchild and so on.

        Number two is in Cinema there is no symbol c4d.Osubdiv. The correct symbol is c4d.Osds. ChatGPT knows and can do a lot, but not everything. 😉

        One additional suggestion. While it can be comfortable to simply use c4d.CallCommand, for proper undo handling you should avoid it and instead use e.g. node.Remove() if you want to delete some object.

        Having said all of that, find below a script that finds all SDS objects gets a copy and of them, bakes them down and inserts the new polygon objects into your document.

        I left out the part to set back the objects position back for you to find out.

        Cheers,
        Sebastian

        import c4d
        
        def get_next(node):
            """Return the next node from a tree-like hierarchy."""
            
            if node.GetDown():
                return node.GetDown()
            
            while not node.GetNext() and node.GetUp():
                node = node.GetUp()
                
            return node.GetNext()
        
        def iter_tree(node):
            """Iterate a tree-like hierarchy yielding every object starting at *node*."""
            
            while node:
                yield node
                node = get_next(node)
                
        def iter_sds_objects(doc):
            """Iterate a tree-like hierarchy yielding every +
            SDS object starting at the first object in the document.
            """
            
            is_sds = lambda node: node.CheckType(c4d.Osds)
            node = doc.GetFirstObject()
            
            for obj in filter(is_sds, iter_tree(node)):
                yield obj
        
        
        def join_objects(op, doc):
            """Join a hierarchy of objects and return them as a single polygon."""
            
            settings = c4d.BaseContainer()
        
            res = c4d.utils.SendModelingCommand(
                command=c4d.MCOMMAND_JOIN,
                list=[op],
                mode=c4d.MODELINGCOMMANDMODE_ALL,
                bc=settings,
                doc=doc
            )
            
            if not isinstance(res, list) or not res:
                return
            
            res = res[0]
            return res.GetClone()
        
        
        # Main function
        def main():
            
            doc.StartUndo()
            
            null = c4d.BaseObject(c4d.Onull)
            
            tempdoc = c4d.documents.BaseDocument()
            tempdoc.InsertObject(null)
            
            for sds in iter_sds_objects(doc):
                clone = sds.GetClone()
                clone.InsertUnderLast(null)
                
            joined = join_objects(null, tempdoc)
            
            doc.InsertObject(joined)
            doc.AddUndo(c4d.UNDOTYPE_NEW, joined)
            
            doc.EndUndo()
            c4d.EventAdd()
        
        # Execute main()
        if __name__=='__main__':
            main()
        
        1 Reply Last reply Reply Quote 1
        • D
          derekheisler
          last edited by derekheisler

          Hey @HerrMay,
          Thanks for reaching out. Nice catches. I was hoping chatGPT might be able to help out but it couldn't get me all the way there. I tried your script but found it only deposited a Null object at the top of the project manager but did not connect+delete the SDS objects. They do exist under a grouped null but as you mentioned searching through parents and children should resolve that. Any thoughts?

          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @derekheisler
            last edited by ferdinand

            Hello @derekheisler,

            Welcome to the Plugin Café forum and the Cinema 4D development community, it is great to have you with us!

            Getting Started

            Before creating your next postings, we would recommend making yourself accustomed with our Forum and Support Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:

            • Support Procedures: Scope of Support: Lines out the things we will do and what we will not do.
            • Support Procedures: Confidential Data: Most questions should be accompanied by code but code cannot always be shared publicly. This section explains how to share code confidentially with Maxon.
            • Forum Structure and Features: Lines out how the forum works.
            • Structure of a Question: Lines out how to ask a good technical question. It is not mandatory to follow this exactly, but you follow the idea of keeping things short and mentioning your primary question in a clear manner.

            About your First Question

            Please excuse the slight delay and thank you for the community answer provided by @HerrMay.

            The core misconception in your code lies in the line objs = doc.GetObjects(), as it does not get a list of all objects in doc, but only the root level objects. Users are expected to do scene graph traversal on their own at the moment, there is no method which would do it for you. There are also other problems with your code, as for example trying to instantiate a BaseThread or using CallCommand (not a real problem but not recommended in most cases).

            There have been also multiple similar threads in the past:

            • How do you collapse complex dependencies in order?: This is about implementing a connect and delete.
            • How to traverse a GeListNode Tree?: This is about hierarchical traversal, i.e., getting all descendants and siblings for a node. There is also something that I would call branch traversal, it is much more complex and we have talked about it for example here.
            • CAD Normal Tag flipped: Entirely different topic, but could give you an idea on how to realize a simple GUI for this.

            Finde below a simple example.

            Cheers,
            Ferdinand

            Result:
            collapse.gif

            Code:

            """Demonstrates how to collapse all SDS objects in a scene.
            
            One could also make this more fancy with a GUI and undo steps, I did not do this here.
            """
            
            import c4d
            
            doc: c4d.documents.BaseDocument # The active document.
            
            def IterNodes(node: c4d.GeListNode) -> c4d.GeListNode:
                """Yields all descendants and next-siblings of `node` in a semi-iterative fashion.
                """
                if node is None:
                    return
            
                while node:
                    yield node
            
                    # The recursion moving down.
                    for descendant in IterNodes(node.GetDown()):
                        yield descendant
            
                    # The iteration in one level.
                    node = node.GetNext()
            
            def Collapse(objects: list[c4d.BaseObject]) -> None:
                """Collapses all items in #objects as individual root nodes into singular objects.
            
                This function mimics the behaviour of the builtin (but unexposed) "Connect & Delete" command 
                by first running the "CSTO" and then "JOIN" command. With setups complex enough, this can still
                fail due to the non-existent dependency graph of the classic API (when one does CSTO things in
                the wrong order). In 99.9% of the cases this will not be the case, but one should get the
                inputs with #GETACTIVEOBJECTFLAGS_SELECTIONORDER as I did below to give the user more control.
                (or alternatively do not batch operate).
                """
                if len(objects) < 1:
                    raise RuntimeError()
                doc: c4d.documents.BaseDocument = objects[0].GetDocument()
                doc.StartUndo()
            
                # CSTO all local hierarchies in #objects and replace these root nodes with their collapsed
                # counter parts.
                result = c4d.utils.SendModelingCommand(c4d.MCOMMAND_CURRENTSTATETOOBJECT, objects, 
                    c4d.MODELINGCOMMANDMODE_ALL, c4d.BaseContainer(), doc, c4d.MODELINGCOMMANDFLAGS_NONE)
            
                if not result or len(result) != len(objects):
                    raise RuntimeError()
            
                for old, new in zip(objects, result):
                    parent, pred = old.GetUp(), old.GetPred()
                    doc.AddUndo(c4d.UNDOTYPE_DELETEOBJ, old)
                    old.Remove()
                    doc.InsertObject(new, parent, pred)
                    doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, new)
            
                # Join the CSTO results root by root object, and then replace the CSTO results with the final
                # collapsed result. JOIN is a bit weird when it comes to transforms, so we must store the 
                # transform of the to be joined object, then zero it out, and finally apply it to the joined 
                # result again.
                for obj in result:
                    mg: c4d.Matrix = obj.GetMg()
                    obj.SetMg(c4d.Matrix())
            
                    joined = c4d.utils.SendModelingCommand(c4d.MCOMMAND_JOIN, [obj], 
                        c4d.MODELINGCOMMANDMODE_ALL, c4d.BaseContainer(), doc, c4d.MODELINGCOMMANDFLAGS_NONE)
                    
                    if not joined:
                        raise RuntimeError()
                    
                    parent, pred = obj.GetUp(), obj.GetPred()
                    doc.AddUndo(c4d.UNDOTYPE_DELETEOBJ, obj)
                    obj.Remove()
            
                    new: c4d.BaseObject = joined[0]
                    new.SetMg(mg)
                    doc.InsertObject(new, parent, pred)
                    doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, new)
            
                doc.EndUndo()
                c4d.EventAdd()
            
            def main() -> None:
                """Runs the example.
                """
                # Find all SDS objects in the scene.
                objectList: list[c4d.BaseObject] = [
                    n for n in IterNodes(doc.GetFirstObject()) if n.CheckType(c4d.Osds)]
            
                # Collapse all SDS objects we have found.
                for obj in objectList:
                    # We have to check if #obj is still a valid reference because we could have already 
                    # collapsed the object away in a prior iteration; sds-objects can live inside sds-objects.
                    if not obj.IsAlive():
                        continue
                    # Collapse the object.
                    Collapse([obj])
            
                c4d.EventAdd()
            
            if __name__ == "__main__":
                main()
            

            MAXON SDK Specialist
            developers.maxon.net

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