Detecting a Change in Position/Rotation Keyframes from Tag

How can I create a function in a tag that only executes when there has been a change to its object's position or rotation? Is there an event I can listen for?

This is what I'm trying now and it doesn't work.

    def Message(self, tag, type, data):
        obj = tag.GetObject()
        if obj:
            if obj.IsDirty(c4d.DIRTYFLAGS_MATRIX):
               # code goes here
        return True

Thank you.

Hello @blastframe,

thank you for reaching out to us. No, there is no specific message for an object being moved. The closest message is EVMSG_CHANGE, but that is a core message which is not being broadcasted to the NodeData message stream, .e.g., the message() function of a scripting object. One could also (ab)use some node messages in this scenario of yours, but it would not be my first choice.

I would simply store the data in the script module or use Cinema's dirty flags. For details see example below. I didn't bother with hitting your extremely specific requirements of reacting to translation and rotation transforms, but not scaling transforms, and instead just compare against the plain matrix. If you wanted to do this, you would have to compare against the normalized frame components of the matrix (v1, v2, v3) and its offset.

Edit: Eh, mistook this for a scripting object question. The answer remains the same, it is only that you then probably want to attach MATRIX_CACHE to your NodeData implementation and not some module. In a NodeData environment you would also have to make sure that MATRIX_CACHE is flushed with frame zero if you want it to be flushed and as it can be done by a Python Programming Tag.


The file: move_cube.c4d
The result: cubeMove.gif
The code:

"""Example for storing data in the module of a scripting object. 

This approach can be sometimes not be sufficient, then one can do more or less
the same by storing data in the BaseContainer of an object. For that one will
need a Plugin ID to store that data collision free.

One could also take a more message oriented approach, but it seems a bit over
the top here. The strategy would be there to check for 

As discussed in:

import c4d

def main():
    # Make the module attribute storing the last seen matrix locally visible,
    # get the object attached to the tag and the global matrix of the that
    # object.
    global MATRIX_CACHE
    obj = op.GetObject()
    mg = obj.GetMg()
    # Compare them and if they are not equal, then do stuff.
    if mg != MATRIX_CACHE:
        MATRIX_CACHE = mg
        print (f"Global matrix of {obj.GetName()} has changed to: {mg}")

    # We can also more or less do the same with DIRTYFLAGS_MATRIX. The problem
    # with this approach is, that dirty flags can be consumed before one had
    # a chance to react to them. It is more safe to use dirty counts, but 
    # there one will need some form of cache/look-up value again, since the
    # dirty count only stores the total number of changes, not their purpose.
    if obj.IsDirty(c4d.DIRTYFLAGS_MATRIX):
        print (f"{obj.GetName()} is DIRTYFLAGS_MATRIX dirty.")

MAXON SDK Specialist

@ferdinand Thank you for the reply. I mentioned in the title, but (my apologies), I didn't specify in the body of my topic or code example: Is it possible to detect when there a new keyframe has been added for transformation from a tag plugin?

Hello @blastframe,

that is only possible through caching it yourself, like I have shown it more generally above. There is c4d.C4DAtom.GetHDirty(c4d.HDIRTYFLAGS_ANIMATION), but it won't tell you what increased that dirty count. But if I would implement this, I still might use this to throttle a bit the overhead of always building and checking my cache.

Noteworthy seems also, that you cannot just reference the track or curve of the position or scale animation of your object in question, because both CTrack and CCurve (and also CKey) are mutable. You must make sure to copy the data, by for example using C4DAtom.GetClone(). Comparing then between your cache and some other CCurve or CTrack, will then of course be a bit more manual since both classes can only compare by identity and not by equality. Please also make sure that you keep in mind that c4dpy has sometimes the weird behavior that it wraps (undocumented) equality comparisons for some types but then factually carries out identity comparisons. So, while myTrackCache == someOtherTrack might run without an error (haven't tried myself), it will likely not do what one would expect it do, i.e., compare for equality, but instead compare of identity, what one would usually express as myTrackCache is someOtherTrack in Python.


MAXON SDK Specialist

@ferdinand Thank you. I'll try to cache myself.