Hey @ThomasB,
Thank you for reaching out to us. I have split your question into two sections:
Detecting Change Counts
One part of your question is how to determine how often a scene element has changed. Change counters, or how Cinema 4D calls them, dirty states, are built into the base class of all scene elements, c4d.C4DAtom
. So, when you have a node myNode
(a BaseObject
, BaseShader
, etc.), you would retrieve its current data container change count with cnt: int = myNode.GetDirty(c4d. DIRTYFLAGS_DATA)
. cnt
would denote how often the data container of myNode
has changed since it has been instantiated, i.e., or how often the parameter values of myNode
changed. There are also other categories of dirtiness, I would recommend having a look at the docs.
Detecting Change Events
The other part of your question is to detect when a change event occurred. And while core messages can be a solution (by manually making objects dirty which should update), we want to avoid using such an overly complex solution when we can avoid that [1].
First, you should turn off SetOptimizeCache
as it is just Python-exclusive convenance method which is targeted at beginners. Cinema 4D will now call your GetVirtualObjects
quite often, and your plugin "will work" but you are also calculating the cache of your plugin more often than necessary. The rounded_tube
example highlights the basic implementation of determining the dirtiness of your node yourself:
def GetVirtualObjects(self, op: c4d.BaseObject, hh: "HierarchyHelp") -> c4d.BaseObject:
"""
"""
# Determine if #op, the node that is representing this plugin hook instance, is dirty,
# i.e., has changed and therefore must be rebuilt.
dirty: bool = op.CheckCache(hh) or op.IsDirty(c4d.DIRTYFLAGS_DATA)
if not dirty:
return op.GetCache(hh) # The node is not dirty we simply return its cache.
# Compute and return a new cache.
return c4d.BaseObject(c4d.Ocube)
Usually, doing this will be enough, as Cinema 4D will call GetVirtualObjects
quite often, but you can get event starved. For example, Cinema 4D will not try to update the cache of your object when the user pans with the camera around it. Modifying a BasaeList2D
which has been linked in the description of your object will invoke a cache building attempt out of the box.
Your Concrete Case
You did not provide any code here, so I can only speculate, but in essence, it will look something like this:
import c4d
class MyPlugin (c4d.plugins.ObjectData):
def __init__(self) -> None:
"""
"""
# We initialize two fields to track the linked node via its UUID and the last known (data)
# dirty count of that linked node. We could also overwrite SetDParameter to track changes
# to our link parameter, then we would not have to track the UUID.
self._linkDirtyCountCache: int = c4d.NOTOK
self._linkUuidCache: bytes | None = None
super().__init__()
def GetVirtualObjects(self, op: c4d.BaseObject, hh: "HierarchyHelp") -> c4d.BaseObject:
"""
"""
# Try to get the linked object or bail when there is None.
link: c4d.BaseList2D | None = op.GetParameter(c4d.DescID(c4d.ID_MYPLUGIN_LINK),
c4d.DESCFLAGS_GET_NONE)
if link is None:
return c4d.BaseObject(c4d.Onull)
# Determine if the node itself is dirty. This will probably already suffice alone, because a
# link field should make its host data dirty once the linked node changed.
selfDirty: bool = op.CheckCache(hh) or op.IsDirty(c4d.DIRTYFLAGS_DATA)
# Or we compute the dirtiness of #link ourselves. There are two ways how we can do this, for
# once also with IsDirty (which can fail when there are also other interacting entities)
linkDirty: bool = link.IsDirty(c4d.DIRTYFLAGS_DATA)
# ... or by manually managing the dirty counter of the linked object.
dirtyCnt: int = link.GetDirty(c4d.DIRTYFLAGS_DATA)
uuid: bytes = bytes(link.FindUniqueID(c4d.MAXON_CREATOR_ID))
# First check if the linked object is still the same as the last time we built the cache.
if uuid != self._linkUuidCache:
linkDirty = True
self._linkUuidCache = uuid
self._linkDirtyCache = dirtyCnt
# Now compare the dirty count ...
elif dirtyCnt > self._linkDirtyCountCache:
linkDirty = True
self._linkDirtyCache = dirtyCnt
# Make use of the computed dirty states.
if not any((selfDirty, linkDirty)):
return op.GetCache(hh)
# Compute and return a new cache.
return c4d.BaseObject(c4d.Ocube)
Cheers,
Ferdinand
[1] The node message stream you were trying to use is useless in this case because node messages are sealed, i.e., you cannot peek into the node message stream of your measure object.