Solved add/remove/modify FieldList with python?

Hello, I am having trouble making changes via python to a FieldList existing on a deformer. Ultimately, I will need to flush and procedurally rebuild a FieldList if possible.

As a first test of the API, I tried c4d.FieldList.Flush() and this does not seem to stick- existing FieldLayers remain. Also, I don't see any options to access specific layer?

I feel like I am missing something obvious- any help is appreciated :)

Hello @jenandesign,

thank you for your follow-up questions. I must unfortunately point out that we do not condone diary-style topics, where developers document various stages of their problem solving. The forum guidelines state:

The initial question of a topic author must be consolidated into a singular posting. Diary style topics with multiple postings are not allowed. See Structure of a Question for more information on how to make a good initial posting. When you want to add information at a later point, please use the edit function.

For an ongoing topic, i.e., information exchange after the initial question, we usually cut everyone some slack, but there are also limits to that. I have consolidated your postings here for you. Please make sure to avoid posting multiple messages in favor of editing your last message. Irrelevant information should also be removed, e.g., code that is not relevant anymore.

About your Question

Please share details of what you are doing. Because we are very accustomed to our APIs, we can of course often guess things, but that tends to be a lot of extra work for us, as we then for example have to write lengthy paragraphs such as this to contextualize our answers.

  • What do you want to do?
  • Where are you stuck, what is your question?
  • Provide a scene file and/or executable code.

When I read here between the lines, I understand that you want to:

  • Add spline field layers for all direct spline children of some root object.
  • You attempt to do that from inside a Python Programming tag.

Inside the main function of a Python Programming tag, you are bound to the threading restrictions of Cinema 4D. Adding or removing scene elements and invoking events it not allowed among other things. Your code violates both restrictions.

There are ways to circumvent that problem, you for example can offload work to NodeData.Message which most of the time runs on the main thread (i.e., the message function inside a Python Programming tag). But that does not alleviate the fundamental logic problem your code has. TagData.Execute, the main function, can be called multiple times per scene execution, and a tag is executed at least once per scene execution. Every time the user does something in a document which changes its "state", the scene will be executed and so your tag. So, even when ignoring all the other problems, your code would constantly overwrite the content of the deformer field list, invalidating all manual changes made by a user.

Adding things to a scene most of the time either constitutes a command (CommandData or a Script Manager script) or a tool (ToolData, ToolDescriptionData). There are more fringe plugin types which can be used as for example MessageData, but they are mostly irrelevant.

Anything that itself is a scene element, e.g., an object, tag, material, shader, should not modify the state of the scene it is part of. There are ways to circumvent these problems, but this is a case of "if you have to ask, you should not do it". In your case this probably means that you should rewrite your script to work as command/Script Manager script.

Cheers,
Ferdinand

Result:
075ff0f5-9763-4e76-9a0f-5998401aa7ac-image.png
Code:

"""Demonstrates how to add spline field layers to a field list.

THIS SCRIPT VIOLATES THE THREADING RESTRICTIONS OF CINEMA 4D. IT WILL CAUSE CRASHES AND LOSS OF 
DATA.
"""

import c4d

doc: c4d.documents.BaseDocument
op: c4d.BaseTag

def main() -> None:
    """Called by Cinema 4D to execute the tag.
    """
    # Attempt to access our input data.
    try:
        deformer: c4d.BaseList2D = op[c4d.ID_USERDATA, 1]
        null: c4d.BaseList2D = op[c4d.ID_USERDATA, 2]
        fields: c4d.FieldList = deformer[c4d.FIELDS]
        if None in (deformer, null):
            return
    except:
        return

    # At least throttle the output by not constantly overwriting an already populated list.
    if fields.GetCount() > 0:
        return
    
    # Add the layers and write the data back.
    root: c4d.GeListHead = fields.GetLayersRoot()
    for spline in null.GetChildren():
        layer: c4d.modules.mograph.FieldLayer = c4d.modules.mograph.FieldLayer(c4d.FLspline)
        layer.SetLinkedObject(spline)
        # This is the illegal operation, we insert an element into the scene graph while being
        # outside of the main thread. Sooner or later this will lead to crashes. 
        layer.InsertUnderLast(root)

    deformer[c4d.FIELDS] = fields

MAXON SDK Specialist
developers.maxon.net

Hello @jenandesign,

Thank you for reaching out to us. Without tangible code of yours, it will be hard to help you. Could it be that you forgot to write your flushed value back? Regarding FieldList traversal, which you seem to hint at, you might want to look at this thread here. In short: Field lists are not the monolithic structures they appear to be.

Cheers,
Ferdinand

Before:
b90d430d-9b8d-46a8-9081-b19a30472631-image.png

After:
dc78d314-2a54-41c4-9d97-63271f3d0c6c-image.png

Code:

"""Nukes the field list parameter at #FIELDS of the selected object.
"""

import c4d

doc: c4d.documents.BaseDocument  # The active document
op: c4d.BaseObject | None  # The active object, None if unselected

def main() -> None:
    """Runs the example.
    """
    # Bail when there is no selected object or it has no #FIELDS parameter of type #FieldList.
    if not op:
        return

    data: c4d.FieldList | any =  op.GetParameter(c4d.DescID(c4d.FIELDS), c4d.DESCFLAGS_GET_NONE)
    if not isinstance(data, c4d.FieldList):
        return

    # Flush the data and write it back wrapped in an undo.
    data.Flush()

    doc.StartUndo()
    doc.AddUndo(c4d.UNDO_CHANGE, op)
    op[c4d.FIELDS] = data
    doc.EndUndo()

    c4d.EventAdd()

if __name__ == '__main__':
    main()

MAXON SDK Specialist
developers.maxon.net

Thanks so much, this points me in the right direction!

Oof, I wish this was more easily accessible/abstracted like c4d.InExcludeData with insert/delete/get/count. Either way I appreciate the tip!


edit 1: Ok, I see that I can just create a new field list and replace the object's data with it, this works.

import c4d

doc: c4d.documents.BaseDocument
op: c4d.BaseTag

def main() -> None:
    deformer = op[c4d.ID_USERDATA,1]
    splineGroup = op[c4d.ID_USERDATA,2]

    fieldList = c4d.FieldList()
    deformer[c4d.FIELDS] = fieldList
    c4d.EventAdd()
    pass

I have a null object splineGroup containing several c4d.SplineObject that I would like to add as spline field layers to the FieldList stack


edit 2: Digging through, but I don't see how it is possible to add a Spline Field Layer with python.


edit 3: I figured it out :)

import c4d

doc: c4d.documents.BaseDocument
op: c4d.BaseTag

def main() -> None:
    deformer = op[c4d.ID_USERDATA,1]
    splineGroup = op[c4d.ID_USERDATA,2]
    splines = splineGroup.GetChildren()

    fieldList = c4d.FieldList()

    for spline in splines:
        fieldLayer = c4d.modules.mograph.FieldLayer(c4d.FLspline)
        fieldLayer.SetLinkedObject(spline)
        fieldList.InsertLayer(fieldLayer)

    deformer[c4d.FIELDS] = fieldList
    c4d.EventAdd()
    pass

edit 4: Ok, it appears that the script above does not work for multiple splines/layers


edit 5: I know this is probably a simple solve, but it's eluding me.

If I break out the for loop with unique variable names, I can have multiple FieldLayers populating the FieldList.

However, running in a for loop as shown above, I only get one... The last spline in the for loop which is definitely a clue.

Hello @jenandesign,

thank you for your follow-up questions. I must unfortunately point out that we do not condone diary-style topics, where developers document various stages of their problem solving. The forum guidelines state:

The initial question of a topic author must be consolidated into a singular posting. Diary style topics with multiple postings are not allowed. See Structure of a Question for more information on how to make a good initial posting. When you want to add information at a later point, please use the edit function.

For an ongoing topic, i.e., information exchange after the initial question, we usually cut everyone some slack, but there are also limits to that. I have consolidated your postings here for you. Please make sure to avoid posting multiple messages in favor of editing your last message. Irrelevant information should also be removed, e.g., code that is not relevant anymore.

About your Question

Please share details of what you are doing. Because we are very accustomed to our APIs, we can of course often guess things, but that tends to be a lot of extra work for us, as we then for example have to write lengthy paragraphs such as this to contextualize our answers.

  • What do you want to do?
  • Where are you stuck, what is your question?
  • Provide a scene file and/or executable code.

When I read here between the lines, I understand that you want to:

  • Add spline field layers for all direct spline children of some root object.
  • You attempt to do that from inside a Python Programming tag.

Inside the main function of a Python Programming tag, you are bound to the threading restrictions of Cinema 4D. Adding or removing scene elements and invoking events it not allowed among other things. Your code violates both restrictions.

There are ways to circumvent that problem, you for example can offload work to NodeData.Message which most of the time runs on the main thread (i.e., the message function inside a Python Programming tag). But that does not alleviate the fundamental logic problem your code has. TagData.Execute, the main function, can be called multiple times per scene execution, and a tag is executed at least once per scene execution. Every time the user does something in a document which changes its "state", the scene will be executed and so your tag. So, even when ignoring all the other problems, your code would constantly overwrite the content of the deformer field list, invalidating all manual changes made by a user.

Adding things to a scene most of the time either constitutes a command (CommandData or a Script Manager script) or a tool (ToolData, ToolDescriptionData). There are more fringe plugin types which can be used as for example MessageData, but they are mostly irrelevant.

Anything that itself is a scene element, e.g., an object, tag, material, shader, should not modify the state of the scene it is part of. There are ways to circumvent these problems, but this is a case of "if you have to ask, you should not do it". In your case this probably means that you should rewrite your script to work as command/Script Manager script.

Cheers,
Ferdinand

Result:
075ff0f5-9763-4e76-9a0f-5998401aa7ac-image.png
Code:

"""Demonstrates how to add spline field layers to a field list.

THIS SCRIPT VIOLATES THE THREADING RESTRICTIONS OF CINEMA 4D. IT WILL CAUSE CRASHES AND LOSS OF 
DATA.
"""

import c4d

doc: c4d.documents.BaseDocument
op: c4d.BaseTag

def main() -> None:
    """Called by Cinema 4D to execute the tag.
    """
    # Attempt to access our input data.
    try:
        deformer: c4d.BaseList2D = op[c4d.ID_USERDATA, 1]
        null: c4d.BaseList2D = op[c4d.ID_USERDATA, 2]
        fields: c4d.FieldList = deformer[c4d.FIELDS]
        if None in (deformer, null):
            return
    except:
        return

    # At least throttle the output by not constantly overwriting an already populated list.
    if fields.GetCount() > 0:
        return
    
    # Add the layers and write the data back.
    root: c4d.GeListHead = fields.GetLayersRoot()
    for spline in null.GetChildren():
        layer: c4d.modules.mograph.FieldLayer = c4d.modules.mograph.FieldLayer(c4d.FLspline)
        layer.SetLinkedObject(spline)
        # This is the illegal operation, we insert an element into the scene graph while being
        # outside of the main thread. Sooner or later this will lead to crashes. 
        layer.InsertUnderLast(root)

    deformer[c4d.FIELDS] = fields

MAXON SDK Specialist
developers.maxon.net

Hello @jenandesign ,

without further questions or postings, we will consider this topic as solved by Friday, the 11th of august 2023 and flag it accordingly.

Thank you for your understanding,
Maxon SDK Group