SOLVED Placing an effector at specific Index in effector list

Hi, this is probably an easy question but I'm placing an effector into a cloner and I want to put it in the first position so the cloner reads it first. I see the InExcludeData has the InsertObject() command which always puts the effector in the last position and no option to place at a specific index. Is there a way to specify which index to place the effector?

Hello @Motion4D,

thank you for reaching out to us. Unfortunately, there is no method attached to InExcludeData in either the C++ or Python SDK which would allow this. The only way to achieve the desired result is to instantiate a new InExcludeData , for details see example below. It also seems noteworthy to point out that an InExcludeData does not come with a guarantee of execution, i.e., you cannot be sure that everything will consider the first index entry as the first thing in an InExcludeData. While a Cloner might currently behave this way, there is no guarantee that this won't change with a future revision.

Cheers,
Ferdinand

The code example:

"""Example for inserting a node at an arbitrary index in an InExcludeData.

As discussed in:
    plugincafe.maxon.net/topic/13388
"""

import c4d

def InsertNodeIntoInExclude(data, node, index, isActive=True):
    """Inserts a node at a certain index into an InExcludeData.

    Does return a copy of the passed data with the node inserted at the 
    specified index. The function assumes the argument to be in the same
    document as the BaseList2D referenced by data. 

    Args:
        data (c4d.InExcludeData): The data to insert into.
        node (c4d.BaseList2D): The node to insert.
        index (int): The index to insert at.
        isActive (bool, optional): If the node is active or not. Defaults to 
         True.
    
    Returns:
        c4d.InExcludeData: A copy of data with node inserted at index.
    
    Raises:
        IndexError: When index is out of bounds.
    """
    dataCount = data.GetObjectCount()
    doc = node.GetDocument()

    if index > dataCount:
        raise IndexError(f"The index {index} is out of bounds.")

    result, i = c4d.InExcludeData(), 0
    while data.ObjectFromIndex(doc, i):
        if i == index:
            result.InsertObject(node, int(isActive))
        
        item = data.ObjectFromIndex(doc, i)
        flags = data.GetFlags(i)
        result.InsertObject(item, flags)
        i += 1
    return result


def main():
    """Insert an effector at an arbitrary index.
    """
    # Check if the first object is a cloner object, there is no type symbol
    # for the cloner object, so, we have to use the raw identifier.  
    cloner = doc.GetFirstObject()
    if not (isinstance(cloner, c4d.C4DAtom) and
            cloner.CheckType(1018544)):
        raise TypeError("Expected a cloner as the first object in the scene.")

    # Check if the second object is an effector.
    effector = cloner.GetNext()
    if not (isinstance(effector, c4d.C4DAtom) and
            effector.CheckType(c4d.Obaseeffector)):
        raise TypeError(
            "Expected an effector as the second object in the scene.")

    # Get the InExcludeData of the cloner.
    inExData = cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]

    # Insert an effector wrapped in an undo.
    doc.StartUndo()
    modifiedInExData = InsertNodeIntoInExclude(inExData, effector, 1)
    cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] = modifiedInExData
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, cloner)
    doc.EndUndo()

    # Update Cinema 4D.
    c4d.EventAdd()

if __name__ == '__main__':
    main()

Hello @Motion4D,

thank you for reaching out to us. Unfortunately, there is no method attached to InExcludeData in either the C++ or Python SDK which would allow this. The only way to achieve the desired result is to instantiate a new InExcludeData , for details see example below. It also seems noteworthy to point out that an InExcludeData does not come with a guarantee of execution, i.e., you cannot be sure that everything will consider the first index entry as the first thing in an InExcludeData. While a Cloner might currently behave this way, there is no guarantee that this won't change with a future revision.

Cheers,
Ferdinand

The code example:

"""Example for inserting a node at an arbitrary index in an InExcludeData.

As discussed in:
    plugincafe.maxon.net/topic/13388
"""

import c4d

def InsertNodeIntoInExclude(data, node, index, isActive=True):
    """Inserts a node at a certain index into an InExcludeData.

    Does return a copy of the passed data with the node inserted at the 
    specified index. The function assumes the argument to be in the same
    document as the BaseList2D referenced by data. 

    Args:
        data (c4d.InExcludeData): The data to insert into.
        node (c4d.BaseList2D): The node to insert.
        index (int): The index to insert at.
        isActive (bool, optional): If the node is active or not. Defaults to 
         True.
    
    Returns:
        c4d.InExcludeData: A copy of data with node inserted at index.
    
    Raises:
        IndexError: When index is out of bounds.
    """
    dataCount = data.GetObjectCount()
    doc = node.GetDocument()

    if index > dataCount:
        raise IndexError(f"The index {index} is out of bounds.")

    result, i = c4d.InExcludeData(), 0
    while data.ObjectFromIndex(doc, i):
        if i == index:
            result.InsertObject(node, int(isActive))
        
        item = data.ObjectFromIndex(doc, i)
        flags = data.GetFlags(i)
        result.InsertObject(item, flags)
        i += 1
    return result


def main():
    """Insert an effector at an arbitrary index.
    """
    # Check if the first object is a cloner object, there is no type symbol
    # for the cloner object, so, we have to use the raw identifier.  
    cloner = doc.GetFirstObject()
    if not (isinstance(cloner, c4d.C4DAtom) and
            cloner.CheckType(1018544)):
        raise TypeError("Expected a cloner as the first object in the scene.")

    # Check if the second object is an effector.
    effector = cloner.GetNext()
    if not (isinstance(effector, c4d.C4DAtom) and
            effector.CheckType(c4d.Obaseeffector)):
        raise TypeError(
            "Expected an effector as the second object in the scene.")

    # Get the InExcludeData of the cloner.
    inExData = cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]

    # Insert an effector wrapped in an undo.
    doc.StartUndo()
    modifiedInExData = InsertNodeIntoInExclude(inExData, effector, 1)
    cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] = modifiedInExData
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, cloner)
    doc.EndUndo()

    # Update Cinema 4D.
    c4d.EventAdd()

if __name__ == '__main__':
    main()

@ferdinand Perfect! that seemed to do the trick. I'm not entirely sure what's going on in that function but it works. I tried different index positions and it threw an error but it works for the zero index and that's all I really needed. Thanks for the help!

AJ

Hello @Motion4D,

I am sorry, there snuck in an else where should not have been one. I fixed the code, which now works as I would expect it to work (did not test very thoroughly though). I also added an argument for the initial state of the added node.

Please note that we cannot deliver full solutions, but if you have specific questions, I am more than happy to help.

Cheers,
Ferdinand

Hello @Motion4D,

without any further questions or replies, we will consider this topic as solved by Wednesday and flag it accordingly.

Thank you for your understanding,
Ferdinand