Hey @thomasb,
Thank you for reaching out to us. While I understand the general idea of your question, some aspects of it are ambiguous for me. I will answer your main question abstractly, and then list out the things I do not understand below.
Abstract Answer
Generally speaking, BaseObject.OptimizeCache
is one of the very few cases where the Python API deviates substantially from the C++ API, as the C++ API does not have such method. The method ties the call frequency of ObjectData.GetVirtualObjects
to the parameter dirtyness of the BaseObject
instance representing the plugin hook. But nothing prevents you from doing this yourself and adding custom checks. There is an example in the PyRoundedTube code, although the relevant comment is a bit cloudy, I personally would have commented it like this instead:
# When there is no cache or the cache is invalid - CheckCache() returns True for invalid or missing
# caches and False for valid ones -, or one of the node parameters has changed, then consider the
# plugin node to be dirty. When the node is NOT dirty in this sense, then return the existing cache.
# This is an approximation of what `ObjectData.SetOptimizeCache(True)` does.
dirty: bool = op.CheckCache(hierarchyhelp) or op.IsDirty(c4d.DIRTYFLAGS_DATA)
if not dirty:
return op.GetCache(hierarchyhelp)
# Start building and returning a new cache here ...
And doing the dirtyness evaluation manually is usually only interesting when one deviates from the
behaviour of ObjectData.SetOptimizeCache(True)
, as for example by making the cache frame dependent:
# Stores the last frame for which this plugin instance has build the cache.
self._lastCacheFrame: int
# Get the node document and the current frame.
doc: c4d.documents.BaseDocument = op.GetDocument()
currentFrame: int = doc.GetTime().GetFrame(doc.GetFps())
# Consider the node dirty when it has no or an invalid cache, is parameter dirty, or the document
# frame has changed since the last time the cache has been built. One could also store _lastFrame
# inside the data container of the node itself, to have a more robust evaluation. I personally would
# consider this simpler form of storing it on the plugin hook instance to be okay too, as doing it
# inside the container will be non-trival because one has then to avoid dirtyness feedback loops.
dirty: bool = (op.CheckCache(hierarchyhelp) or
op.IsDirty(c4d.DIRTYFLAGS_DATA) or
self._lastCacheFrame != currentFrame)
if not dirty:
return op.GetCache(hierarchyhelp)
# Start building and returning a new cache here ...
self._lastCacheFrame = currentFrame
The evaluation usually takes place at the top of ObjectData.GetVirtualObjects
as shown in the rounded tube example, but one can also use ObjectData.AddToExecution
to manage how often GetVirtualObjects
is being called from an earlier point. But in most cases doing it from GetVirtualObjects
should be sufficient.
Ambiguities in the Question
blink function [...] a looping blink behavior depending on the [current] frame
I assume you want the cache to be frame dependent, i.e., a new frame means a new cache. But it is not clear to me what you mean by 'blink function or behaviour' specifically.
Internally it exchanges the reference object in the instances which were returned.
I do not understand what you are talking about here. Are you talking about returning the cache of a node when it is not dirty? In general, GVO
should never return references. In Python is everything a reference when one takes the term literally, but GVO
should not return a node tree which is being used in and owned by another context (i.e., what is commonly dubbed as 'reference'). So, you should not attach the cache output of GVO to for example MyPluginHook._lastCache
or have the same cache appear more than once in a scene graph.
I set OptimizeCache to False if the parameter is checked otherwise it is True.
You should not toggle the caching behaviour of a node in such manner. The method is meant to be used once in the __init__
method of the plugin hook. Ignoring this will not induce crashes or malformed data, but one can miss cache updates when switching the behaviour at runtime.
Cheers,
Ferdinand