SOLVED How to Manually Build the Cache for a Vector Import Instance?

Hello,

now with the updated Vector Import object in S26 I thought there might be a more elegant way to import an SVG directly to a spline.
But after playing around with it for a bit I am struggling to extract the spline from the setup using python only.

Here is my code:

        vector_import = c4d.BaseObject(1057899)
        self.document.InsertObject(vector_import)
        # set file path
        file_path = os.path.join(SVG_PATH, self.file_name + ".svg")
        vector_import[c4d.ART_FILE] = file_path
        # extract spline from hierarchy
        vector_import[c4d.ART_SHOW_HIERARCHY] = True
        c4d.CallButton(vector_import, c4d.ART_RELOAD)
        c4d.EventAdd()
        spline = vector_import.GetDown().GetDown().GetDownLast()

The problem seems to be that even after calling the reload button and c4d.EventAdd() which makes the hierarchy containing the spline visible in the GUI, C4D does not recognise that the vector_import object now has children.

Help would be appreciated!

Hello @interfaceguy,

Thank you for reaching out to us. Please open a new topic for new questions as lined out in our Forum Guidelines: Rules of Conduct. This is not a follow up question to your initial question. I have forked your question, for future cases we might refuse to answer your questions when this rule is not followed.

About your question, the following statement is not true:

calling the reload button and c4d.EventAdd() which makes the hierarchy containing the spline visible in the GUI, C4D does not recognize that the vector_import object now has children.

Invoking the button and EventAdd will not build the caches for your object. It is only after your script has ended that Cinema 4D will do that automatically for you. Which is probably why you came to that conclusion, but when you call GetDown() on your vector_import, it still has no children. You must execute the passes on an object/document when you want to access/build the caches of it immediately after a parameter change or when the object has been newly instantiated.

Find an example at the end of this posting.

Cheers,
Ferdinand

The result:

ecd4127d-5788-4245-9b5b-22298018b8aa-image.png

The code:

"""Simple example for evaluating the cache of a generator.
"""

import c4d
import typing

doc: c4d.documents.BaseDocument  # The active document
op: typing.Optional[c4d.BaseObject]  # The active object, None if unselected

def AssertType(item: any, t: typing.Union[typing.Type, tuple[typing.Type]], lbl: str) -> any:
    """Asserts #item to be of type #t.

    When the assertion succeeds, #item is passed through, otherwise a type error with #lbl referring
    to #item is being raised.
    """
    if not isinstance(item, t):
        raise TypeError(f"Expected item of type '{t}' for '{lbl}'. Received: {type(item)}")
    
    return item

def main() -> None:
    """
    """
    # Instantiate the vector import object.
    node: c4d.BaseObject = AssertType(c4d.BaseObject(1057899), c4d.BaseObject, "node")

    # Due to a bug in the vector import object, Cinema 4D will crash when attempting to write the
    # ART_FILE parameter without the node being attached to a document. Normally we would set here
    # this parameter, as this would make more sense, now it has been moved to line 44 for this
    # reason.

    # The node #node has now no caches, i.e., the generator has not run yet and no amount of button
    # clicking and EventAdd() will change that. We must build the caches by executing the passes
    # on #node. For that #node must be part of a document. We could insert #node into the active 
    # document and execute the passes there, but this has two drawbacks:
    #
    #   1. This cannot be done in any non-main thread context, as it might then crash Cinema 4D.
    #   2. This can also be slow, as it will also revaluate everything else which is in the active
    #      document.
    #
    # So, we are going to do this in a temporary document.

    BaseDocument = c4d.documents.BaseDocument
    temp: BaseDocument = AssertType(BaseDocument(), BaseDocument, "temp")
    temp.InsertObject(node)

    # Set the parameter for the file.
    node[c4d.ART_FILE] = "E:\myFile.svg"

    # Build all caches in the document.
    temp.ExecutePasses(
        bt=None, animation=False, expressions=False, caches=True, flags=c4d.BUILDFLAGS_NONE)

    # Node now has a cache, and also its hierarchy would be populated if we had set 
    # ART_SHOW_HIERARCHY to True. But we can also just reach into the cache now when we want
    # to access the output of the generator. We can also remove #node now from the temporary
    # document, as the cache is bound to the node itself. We also must do this if we want to
    # insert #node into another document (or return it in a GVO method), as a node can only
    # be part of one document.
    node.Remove()

    # Get the cache.
    cache: c4d.BaseObject = AssertType(node.GetCache(), c4d.BaseObject, "cache")
    print (f"{cache = }")

    # Some like this should never be done, as every step in this chained function call can return
    # a null-pointer, i.e., None in Python.
    # spline = vector_import.GetDown().GetDown().GetDownLast()

    # Get the sort of thing you wanted to get. It is in general not safe to navigate hierarchies in
    # this predetermined manner but this will at least not stop your code when the assumption
    # fails inevitably.
    if cache.GetDown():
        cache = cache.GetDown()
        if cache.GetDown():
            cache = cache.GetDown()
            if cache.GetDownLast():
                cache = cache.GetDownLast()

    # Do stuff with this part of the cache.
    if isinstance(cache, c4d.PointObject):
        for p in cache.GetAllPoints():
            print (p)

    # When we wanted to insert this part of the cache into the active (or any other) document, we
    # must clone it first.
    clone: c4d.BaseObject = AssertType(cache.GetClone(c4d.COPYFLAGS_0), c4d.BaseObject, "clone")

    # Insert both the generator and a copy of a part of its cache.
    doc.InsertObject(node)
    doc.InsertObject(clone)
    
    c4d.EventAdd()

if __name__ == '__main__':
    main()

Hello @interfaceguy,

Thank you for reaching out to us. Please open a new topic for new questions as lined out in our Forum Guidelines: Rules of Conduct. This is not a follow up question to your initial question. I have forked your question, for future cases we might refuse to answer your questions when this rule is not followed.

About your question, the following statement is not true:

calling the reload button and c4d.EventAdd() which makes the hierarchy containing the spline visible in the GUI, C4D does not recognize that the vector_import object now has children.

Invoking the button and EventAdd will not build the caches for your object. It is only after your script has ended that Cinema 4D will do that automatically for you. Which is probably why you came to that conclusion, but when you call GetDown() on your vector_import, it still has no children. You must execute the passes on an object/document when you want to access/build the caches of it immediately after a parameter change or when the object has been newly instantiated.

Find an example at the end of this posting.

Cheers,
Ferdinand

The result:

ecd4127d-5788-4245-9b5b-22298018b8aa-image.png

The code:

"""Simple example for evaluating the cache of a generator.
"""

import c4d
import typing

doc: c4d.documents.BaseDocument  # The active document
op: typing.Optional[c4d.BaseObject]  # The active object, None if unselected

def AssertType(item: any, t: typing.Union[typing.Type, tuple[typing.Type]], lbl: str) -> any:
    """Asserts #item to be of type #t.

    When the assertion succeeds, #item is passed through, otherwise a type error with #lbl referring
    to #item is being raised.
    """
    if not isinstance(item, t):
        raise TypeError(f"Expected item of type '{t}' for '{lbl}'. Received: {type(item)}")
    
    return item

def main() -> None:
    """
    """
    # Instantiate the vector import object.
    node: c4d.BaseObject = AssertType(c4d.BaseObject(1057899), c4d.BaseObject, "node")

    # Due to a bug in the vector import object, Cinema 4D will crash when attempting to write the
    # ART_FILE parameter without the node being attached to a document. Normally we would set here
    # this parameter, as this would make more sense, now it has been moved to line 44 for this
    # reason.

    # The node #node has now no caches, i.e., the generator has not run yet and no amount of button
    # clicking and EventAdd() will change that. We must build the caches by executing the passes
    # on #node. For that #node must be part of a document. We could insert #node into the active 
    # document and execute the passes there, but this has two drawbacks:
    #
    #   1. This cannot be done in any non-main thread context, as it might then crash Cinema 4D.
    #   2. This can also be slow, as it will also revaluate everything else which is in the active
    #      document.
    #
    # So, we are going to do this in a temporary document.

    BaseDocument = c4d.documents.BaseDocument
    temp: BaseDocument = AssertType(BaseDocument(), BaseDocument, "temp")
    temp.InsertObject(node)

    # Set the parameter for the file.
    node[c4d.ART_FILE] = "E:\myFile.svg"

    # Build all caches in the document.
    temp.ExecutePasses(
        bt=None, animation=False, expressions=False, caches=True, flags=c4d.BUILDFLAGS_NONE)

    # Node now has a cache, and also its hierarchy would be populated if we had set 
    # ART_SHOW_HIERARCHY to True. But we can also just reach into the cache now when we want
    # to access the output of the generator. We can also remove #node now from the temporary
    # document, as the cache is bound to the node itself. We also must do this if we want to
    # insert #node into another document (or return it in a GVO method), as a node can only
    # be part of one document.
    node.Remove()

    # Get the cache.
    cache: c4d.BaseObject = AssertType(node.GetCache(), c4d.BaseObject, "cache")
    print (f"{cache = }")

    # Some like this should never be done, as every step in this chained function call can return
    # a null-pointer, i.e., None in Python.
    # spline = vector_import.GetDown().GetDown().GetDownLast()

    # Get the sort of thing you wanted to get. It is in general not safe to navigate hierarchies in
    # this predetermined manner but this will at least not stop your code when the assumption
    # fails inevitably.
    if cache.GetDown():
        cache = cache.GetDown()
        if cache.GetDown():
            cache = cache.GetDown()
            if cache.GetDownLast():
                cache = cache.GetDownLast()

    # Do stuff with this part of the cache.
    if isinstance(cache, c4d.PointObject):
        for p in cache.GetAllPoints():
            print (p)

    # When we wanted to insert this part of the cache into the active (or any other) document, we
    # must clone it first.
    clone: c4d.BaseObject = AssertType(cache.GetClone(c4d.COPYFLAGS_0), c4d.BaseObject, "clone")

    # Insert both the generator and a copy of a part of its cache.
    doc.InsertObject(node)
    doc.InsertObject(clone)
    
    c4d.EventAdd()

if __name__ == '__main__':
    main()

works like a charm, thank you!