Hello @lingza,
Thank you for reaching out to us. There are in principle two ways how you can do this:
Do not use an InExcludeData and make the dependencies of your deformer direct children of the deformer. A deformer will automatically be invoked when either the direct parent or children are modified. This is IMHO a much simpler solution, but you are of course restricted in where you can put these dependencies in a scene, as they must be children of the deformer.
When you want to go with InExcludeData, you must overwrite ObjectData.CheckDirty to manually flag the deformer as dirty when necessary. You iterate over all objects in the InExcludeData and based on their dirty count decide if the deformer is dirty or not. Find a draft for the method at the end of my posting.
Cheers,
Ferdinand
The result:
modifierdirty.gif
The code:
def CheckDirty(self, op: c4d.BaseObject, doc: c4d.documents.BaseDocument) -> None:
"""Called by Cinema 4D to let a modifier flag itself as dirty.
Args:
op: The object representing the modifier hook.
doc: The document containing the modifier.
"""
# When we want to track dependencies we must keep a cache for what we consider to be the
# last no-dirty state of things. This could be done in many ways, I have chosen here a
# dict of (bytes, int) tuples.
#
# The general idea is to store the last known dirty state data for each item in the
# InExcludeData as such:
#
# { Object_A: 193, Object_B: 176, Object_C: 25, ...}
#
# But BaseList2d instances itself are not hashable, i.e., cannot be keys in a dict, and
# doing something like `myDict[id(someObject)] = someValue` would be a really bad idea,
# as objects are managed in the backend by Cinema 4D and can be reallocated all the time,
# so id(myBaseList2d) is not a safe way to identify objects. Instead we are using
# C4DAtom.FindUniqueID to get the unique MAXON_CREATOR_ID marker of each item and then
# use that marker to store the dirty flags.
# The dirty cache data structure attached to the plugin class and the InExcludeData parameter
# in question.
self._linklistDirtyCache: dict[bytes: int]
data: c4d.InExcludeData = op[c4d.ID_LINKLIST]
if not isinstance(data, c4d.InExcludeData):
return
# Now we iterate over all items in the InExcludeData to see if their dirty state has changed.
isDirty: bool = False
for i in range(data.GetObjectCount()):
node: c4d.BaseList2D = data.ObjectFromIndex(doc, i)
if not isinstance(node, c4d.BaseList2D):
continue
# Get the MAXON_CREATOR_ID unique ID of this node.
mem: memoryview = node.FindUniqueID(c4d.MAXON_CREATOR_ID)
if not isinstance(mem, memoryview):
continue
uuid: bytes = bytes(mem)
# Get the current and cached dirty state of that item.
currentDirtyCount: int = node.GetDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX)
cachedDirtyCount: typing.Optional[int] = self._linklistDirtyCache.get(uuid, None)
# When there is either no cache or the cache differs from the current value, we update
# our cache and set isDirty to True.
if (currentDirtyCount is None) or (cachedDirtyCount != currentDirtyCount):
isDirty = True
self._linklistDirtyCache[uuid] = currentDirtyCount
# When isDirty is True, we flag ourselves as dirty, which will cause ModifyObject to be called.
if isDirty:
op.SetDirty(c4d.DIRTYFLAGS_DATA)
print (f"Modifier has been set dependency dirty: {self._linklistDirtyCache.values()}")