SOLVED 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