Connect + Delete groups iteratively



  • C4D R20

    I started the "SendModelCommand" path but trying to just get this stood up quickly with CallCommands first. The issue im having is obviously Connect + Delete happens on "All" selected objects, collapsing into one, when I want each to collapse into their own respective objects . My thoughts are to find all "MoText" under op root, store in an array and run the[ select > make editable >] select children of new hierarchy > connect+delete (deselect all?) stepping through the whole sequence per item. before moving onto the next item since connect+delete doesn't care about hierarchy, at least in CallCommand form anyway.

    I've made a couple of attempts at executing that logic, but am fairly new to writing python for C4D so haven't had success yet. Hoping i can be shown how to achieve my logic or be told/shown there's a much better way to go about it altogether.

    Thanks in advance!

    import c4d
    from c4d import gui
    from c4d import utils
    
    def selchildren(obj,next): # Scan obj hierarchy and select children
    
        while obj and obj != next:
    
            doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL,obj)
    
            if obj.CheckType(1019268): #find MoText
                obj.SetBit(c4d.BIT_ACTIVE)
    
            selchildren(obj.GetDown(),next)
    
            obj = obj.GetNext()
    
        c4d.EventAdd()
    
        return True
    
    
    
    def main():
    
    
        for obj in doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN):
            if selchildren(obj, obj.GetNext()):
                obj.DelBit(c4d.BIT_ACTIVE)
                c4d.CallCommand(12236) # Make Editable
                c4d.CallCommand(16768, 16768) # Connect + Delte
                
    
    # Execute main()
    if __name__=='__main__':
        main()
    


  • hello,

    if i understood correctly, you want to make editable the motext, collapse the result (connect) and delete the original one.

    I did create an example for that.

    I've build the logic slowly. It was pretty straight forward.

    The first thing is to have a function that can return the next object in the hierarchy but that only act on the child and not the whole document.

    • For each selected object, i check in all children if i found a MoText.
    • Than, i send a modeling command to retrieve the current state, and a join command. (using SendModelingCommand)
    • I insert the newObject, using InsertObject, in the document (after the motext object) and i add the motext to a remove list.
    • once everything is done, i check the keyboard, if shift is pressed i don't remove the original motext.
    import c4d
    from c4d import gui
    
    def GetNextObject(ori, op):
        """ Retrieves the next object in the hierarchy. 
    
        :param      ori: the original object to sompare with the next so we never get upper this level.
        :type:     BaseObject
        :param      op: the actual object in the hierarchy
        :type:     BaseObject
        :Return:    the next object in the hierarchy or None
        :rtype:    BaseObject
        """
        if op==None:
            return None
        if op.GetDown():
            return op.GetDown()
    
        while not op.GetNext() and op.GetUp():
            # CHecks if the next object up in the hierarchy is not the original one.
            if op.GetUp() != ori:
                op = op.GetUp()
            else:
                return None
        return op.GetNext()
    
    
    def CSTO(op ):
        """ Current State To Object
        :param      op: the object to apply current state to object to.
        :type: BaseObject
        :return: the result of the command or raise an error if command failed
        :rtype: BaseObject
        """
        # Retrieves the document that the object belong to
        doc = op.GetDocument()
        # send the modeling command
        res = c4d.utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                    list = [op],
                                    mode = c4d.MODELINGCOMMANDMODE_ALL,
                                    bc = c4d.BaseContainer(),
                                    doc = doc)
        
        # Cheks if the command didn't failed
        if res is False:
            raise TypeError("return value of CSTO is not valie")
        
        # Returns the first object of the list.
        return res[0]
    
    def JoinCommand(op):
        """ Apply a join command to the object passe as argument
         :param      op: the object to apply current state to object to.
        :type: BaseObject
        :return: the result of the command or raise an error if command failed
        :rtype: BaseObject
        """
        # Retrieves the document that the object belong to
        doc = op.GetDocument()
    
        # send the modeling command
        res = c4d.utils.SendModelingCommand(command = c4d.MCOMMAND_JOIN,
                                    list = [op],
                                    doc= doc)
    
         # Cheks if the command didn't failed
        if res is False:
            raise TypeError("return value of Join command is not valie")
    
        # Returns the first object of the list.
        return res[0]
    
    
    # Main function
    def main():
        doc.StartUndo()
       
        # Retrieves the active objects
        objs = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE)
        if len(objs) <1:
            raise ValueError("select one objectr please")
        
        # prepare a list of object that need to be removed at the end.
        removeObjectList = []
    
        # Iterates trough the selected objects.
        for obj in objs:
            # Use another variable so we can keep one with the original object to compare in the 'GetNextObject' function
            currentObject = obj
            while currentObject:
                # Checks if the current object is a MoText Object.
                if currentObject.IsInstanceOf(1019268): 
                    myNewObject = CSTO(currentObject)
                    myNewObject = JoinCommand(myNewObject)
                    # Inserts the new object after the motext                
                    doc.InsertObject(myNewObject, None, currentObject)
                    doc.AddUndo(c4d.UNDOTYPE_NEW, myNewObject)
                    # Adds the motext object to the remove list
                    removeObjectList.append(currentObject)
    
                currentObject = GetNextObject(obj, currentObject)
        
        # Retrieves the keyboard states for the shift key
        bc = c4d.BaseContainer()
        shift = None
        if c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD,c4d.BFM_INPUT_CHANNEL,bc):
            shift = bc[c4d.BFM_INPUT_QUALIFIER] & c4d.QSHIFT == c4d.QSHIFT
        
        # if shift key is not pressed, remove the objects.
        if not shift:
            # Removes all object in the remove list.
            for obj in removeObjectList:
                doc.AddUndo(c4d.UNDOTYPE_DELETE, obj)
                obj.Remove()
            
    
    
        doc.EndUndo()
        
        c4d.EventAdd()
    # Execute main()
    if __name__=='__main__':
        main()
    

    If you have any question just ask

    Cheers,
    Manuel



  • @m_magalhaes This is fantastic, it works and gives me a lot to dissect. I'll be further expanding on it to include Instance objects and cloners [have already had that working as theyre a simple "make editable"]. In my day to day i have to often share assets via FBX, and if my assets are built with instances/clonser/MoText [<-particularly messy], FBXs auto conversion of those assets create quite a messy hierarchy, roots on roots for days lol. This will help clean things up. Thanks!

    I think one issue i need to solve with this method is reapplying/maintaining the original objects applied Material.



  • hello,

    The code is using the same command that the user should use using the UI.
    Could you be a bit more specific about what's not working with material ?

    Cheers,
    Manuel.



  • hello,

    I'will consider this thread as solved tomorrow if you have nothing to add :)

    Cheers,
    Manuel


Log in to reply