SOLVED Inheritance created by Python script not getting saved

Hi,
I have a python script that creates inheritances with keyframes. All these work and get represented in the viewport, however when I save the project, close Cinema 4D, and then reopen the project, the inheritances are there with the keyframes, but their effects are not represented.

And this only happens to the inheritances created by the script, manually created inheritances do work.

What could be the issue?

Thank you for your time.
Joel.

Hello @joel,

Thank you for reaching out to us. Without your code and an example scene, it will be impossible to help you here. I am also not quite sure what you mean by 'creates inheritances with keyframes'. Are you talking about the 'Instance Object' and creating keyframes for it?

Please share your code and an example scene file.

Cheers,
Ferdinand

@ferdinand
This is the part of the code corresponding to the set issue.Inheritance_error.c4d

inheritance1 = c4d.BaseObject(1018775)
inheritance1_Name = 'Inheritance-'+str(number)+ '_1'
inheritance1.SetName(inheritance1_Name) # Set the first inheritance name.
inheritance1.InsertUnder(null) # Insert the first inheritance under the null.
inheritance1[c4d.MGINHERITANCEEFFECTOR_OBJECT] = cloner # Set the first inheritance Effector Object to the transition cloner.
inheritance1[c4d.MGINHERITANCEEFFECTOR_MORPHMG] = True # Set the first inheritance Effector Morph Motion Object to True.
inexcludedata1 = clonerbase[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] # Get the cloner base Effectors List
inexcludedata1.InsertObject(inheritance1, 1) # Insert the first inheritance in the Effectors List
clonerbase[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] = inexcludedata1 # Set the cloner base Effectors List.

InheritanceID1 = find_DescID(clonerbase, inheritance1_Name)
Inheritance1Track = c4d.CTrack(clonerbase, InheritanceID1) # Get the CTrack of the base cloner for the first inheritance.
clonerbase.InsertTrackSorted(Inheritance1Track)
Inheritance1Curve = Inheritance1Track.GetCurve() # Get the curve of the base cloner for the first inheritance.

time = c4d.BaseTime(frames[0] - 1, doc.GetFps())
clonerbase[InheritanceID1] = 0.0 # Set the strength of the first inheritance to 0.
dictKey = Inheritance1Curve.AddKey(time)
key = dictKey['key']
Inheritance1Track.FillKey(doc, clonerbase, key) # Set the keyframe of the base cloner first inheritance strength.

time = c4d.BaseTime(frames[0], doc.GetFps())
clonerbase[InheritanceID1] = 1.0 # Set the strength of the first inheritance to 1.
dictKey = Inheritance1Curve.AddKey(time)
key = dictKey['key']
Inheritance1Track.FillKey(doc, clonerbase, key)# Set the keyframe of the base cloner second inheritance strength.

I have not shared all the code as this code is not meant to be public.

I also have attached a Cinema 4D file where after the script run the inheritance works, but after a project save, close Cinema 4D reopen Cinema 4D, and the project, the inheritance does not work.

Inheritance_error.c4d

@ferdinand Is the information that I have provided sufficient?

Hello @joel,

it is. I simply did not get to your posting today, my apologies. Will be my first thing on my desk tomorrow.

Cheers,
Ferdinand

@ferdinand Thank you so much, looking forward to your reply.

Hello @joel,

so, I had a look at your file and your code. Your file seems broken beyond repair, I was not able to replicate your results there, the tracks themselves seem to be malformed.

The problem is here, that you do not show how you construct these track IDs returned by your find_DescID function, which then also leaves me a bit in the dark which parameter you want to keyframe. From the context I assumed that you want to animate the dynamically managed effector list strength values of a cloner.

Screenshot 2023-01-24 at 12.45.56.png

Without that code, I cannot tell you if you do construct your IDs correctly. What also stands out, is that you do not send c4d. MSG_MENUPREPARE to your Mograph nodes, causing your effectors not to be respected in loaded files. This is the likely cause of your problems. Find below an example script which does work fine for me, both on execution and a saved file.

Cheers,
Ferdinand

File: mg_effector_rig.c4d
Result: Screenshot 2023-01-24 at 13.03.29.png
Code:

"""Creates a Mograph rig with a cloner influenced by effectors, then creates a key frame animation
for the effector influences on the cloner.

Must be run from the Script Manager. Your key problem was probably not sending MSG_MENUPREPARE
to the effectors, possibly in combination with constructing the track IDs incorrectly.
"""
import c4d
import typing

doc: c4d.documents.BaseDocument # The active document.

def CreateClonerRig(doc: c4d.documents.BaseDocument) -> c4d.BaseObject:
    """Creates a MoGraph cloner rig with an inheritance and a random effector.
    """
    if not isinstance(doc, c4d.documents.BaseDocument):
        raise TypeError(f"{doc = }")

    # Instantiate the cloner, the cloned object, the two effectors and a motion source for the
    # inheritance effector. I am using here the Mograph symbols exposed with 2023.0, e.g.,
    # Omgcloner. For prior versions one must define them manually.
    clonerObj: c4d.BaseObject = c4d.BaseObject(c4d.Omgcloner)
    cloneObj: c4d.BaseObject = c4d.BaseObject(c4d.Ocube)
    motionSourceObj: c4d.BaseObject = c4d.BaseObject(c4d.Osphere)
    inheritanceEffector: c4d.BaseObject = c4d.BaseObject(c4d.Omginheritance)
    randomEffector: c4d.BaseObject = c4d.BaseObject(c4d.Omgrandom)

    if None in (clonerObj, cloneObj, motionSourceObj, inheritanceEffector, randomEffector):
        raise MemoryError("Could not allocate cloner setup.")

    # Create the Phong and Vibrate tag on the motion source object.
    phongTag: c4d.BaseTag = motionSourceObj.MakeTag(c4d.Tphong)
    vibrateTag: c4d.BaseTag = motionSourceObj.MakeTag(c4d.Tvibrate)

    if None in (phongTag, vibrateTag):
        raise MemoryError("Could not allocate cloner setup.")

    # Insert the objects into the document.
    doc.InsertObject(clonerObj)
    doc.InsertObject(inheritanceEffector)
    doc.InsertObject(randomEffector)
    doc.InsertObject(motionSourceObj)
    cloneObj.InsertUnder(clonerObj)

    # Set the size of the cloned cube object, set a position vibration on the Vibrate tag, and
    # set the source object for the inheritance effector.
    cloneObj[c4d.PRIM_CUBE_LEN] = c4d.Vector(50)
    vibrateTag[c4d.VIBRATEEXPRESSION_POS_ENABLE] = True
    vibrateTag[c4d.VIBRATEEXPRESSION_POS_AMPLITUDE] = c4d.Vector(100)
    inheritanceEffector[c4d.MGINHERITANCEEFFECTOR_OBJECT] = motionSourceObj

    # Add both effectors to the cloner.
    effectorList: c4d.InExcludeData = clonerObj[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]
    effectorList.InsertObject(inheritanceEffector, 1)
    effectorList.InsertObject(randomEffector, 1)
    clonerObj[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] = effectorList

    # It is important to send this message to all Mograph objects on instantiation.
    for node in (clonerObj, inheritanceEffector, randomEffector):
        node.Message(c4d.MSG_MENUPREPARE, doc)

    c4d.EventAdd()
    return clonerObj

def AnimateEffectorList(clonerObj: c4d.BaseObject) -> None:
    """Creates a keyframe animation for each effector strength in the effector list of the given
    cloner.
    """
    # Make sure the passed cloner is indeed a cloner and attached to a document.
    if not isinstance(clonerObj, c4d.BaseObject) or clonerObj.GetType() != c4d.Omgcloner:
        raise TypeError(f"{clonerObj = }")

    doc: c4d.documents.BaseDocument = clonerObj.GetDocument()
    if not isinstance(doc, c4d.documents.BaseDocument):
        raise RuntimeError(f"{clonerObj} is not attached to a document.")

    # Define the three points in document time to insert keyframes for.
    fps: int = doc.GetFps()
    minTime: c4d.BaseTime = doc.GetMinTime() # 1st frame
    maxTime: c4d.BaseTime = doc.GetMaxTime() # last frame
    midTime: c4d.BaseTime = c4d.BaseTime(
        int((maxTime.GetFrame(fps) - minTime.GetFrame(fps)) / 2), fps) # 'middle' frame

    # Get the effectors which are linked in the cloner. Note that #linkedObjects can contain
    # None as ObjectFromIndex() can return None. This happens when a link cannot be resolved
    # anymore, i.e., an InExcludeData list is referencing something which does not exist anymore.
    effectorList: c4d.InExcludeData = clonerObj[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]
    linkedObjects: list[typing.Optional[c4d.BaseObject]] = [
        effectorList.ObjectFromIndex(doc, i) for i in range(effectorList.GetObjectCount())]

    # Iterate over all objects in the InEx:
    for i, effector in enumerate(linkedObjects):
        # Step over dangling references.
        if effector is None:
            continue

        # Define the ID for the parameter. The parameters are dynamically generated and start of at
        # ID 1500 and then continue in the order and length of the InExcludeData.
        did: c4d.DescID = c4d.DescID(c4d.DescLevel(1500 + i, c4d.DTYPE_REAL, c4d.Omgcloner))
        print (f"{did = }, {effector.GetName() = }")

        # Get or create a track for the parameter ID.
        track: typing.Optional[c4d.CTrack] = clonerObj.FindCTrack(did)
        if track is None:
            track = c4d.CTrack(clonerObj, did)
            clonerObj.InsertTrackSorted(track)

        # Get the curve of the track and start adding keyframes.
        curve: c4d.CCurve = track.GetCurve()
        for time, value in ((minTime, 0.), (midTime, .25), (maxTime, 1.)):
            key: c4d.CKey = curve.AddKey(time)["key"]
            key.SetValue(curve, value)

            print (f"Created key for {did} at '{time.Get()}' sec with the value '{value}'.")


def main() -> None:
    """Runs the example.
    """
    clonerObj: c4d.BaseObject = CreateClonerRig(doc)
    AnimateEffectorList(clonerObj)

    c4d.EventAdd()

if __name__ == "__main__":
    main()

@ferdinand Thank you so much. Adding:

inheritance1.Message(c4d.MSG_MEUPREPARE, doc)

Solved the issue.