Python Tag: Matrix is always dirty? (or vice versa)
-
In continuation with my exploration of how to use the dirty state in python, I'm trying to check a Matrix object's dirty state with a python tag.
If I check the MoData for dirty state, then it's ALWAYS dirty, no matter which flag I use.
If I check the BaseObject's cache for dirty state, then it's ALWAYS not dirty.Am I doing something wrong here?
My end goal is to get the Matrix' dirty state when its target spline is changing.
Code and file attached:- Checking Matrix' MoData. I'm using the Count flag as the count is definitely not changing under any circumstances, but it still comes out positive.
Result: Always Dirty
import c4d from c4d.modules import mograph as mo def main(): moData = mo.GeGetMoData(op.GetObject()) if moData.GetDirty(c4d.MDDIRTY_COUNT): print "object is dirty" else: print "not dirty"
- Checking Matrix as BaseObject:
Result: Always not Dirty
import c4d def main(): obj = op.GetObject() if obj.IsDirty(c4d.DIRTYFLAGS_CACHE): print "object is dirty" else: print "not dirty"
- Checking Matrix' MoData. I'm using the Count flag as the count is definitely not changing under any circumstances, but it still comes out positive.
-
Hi
c4d.C4DAtom.GetDirty()
returns an integer, the dirty checksum. Since integers greater than zero all evaluate asTrue
you get your always dirty behaviour.IsDirty()
is a specific method ofBaseObject
and can be consumed (as I showed in my script). A more appropriate flag four your case would bec4d.DIRTYFLAGS_DATA
.Cheers
zipit -
Thanks a lot. I didn't notice that GetDirty gives a dirty count instead of a boolean.
I adapted your code (very helpful!) and I used a user-data instead of a plugin id, it seems to work for normal objects, but with the Matrix I still have trouble...Changing the Matrix parameters does change the GetDirty, but when using an Effector that changes the matrices, even though I can read these matrices from the MoData, the GetDirty doesn't increase the count, at least with the "MDDIRTY_ALL" flag.
In the documentation it says for the "MDDIRTY_DATA":
"Data in the arrays changed, must be manually set."
But I'm not sure what it means by "manually set. I assume it refers to when manually setting the array data? What happens when using an effector or changing a target object?This is the current code:
def main(): dirty_storage = op[c4d.ID_USERDATA,1] moData = mo.GeGetMoData(op.GetObject()) # Get the last cached dirty count and the current dirty count. lst_dcount = dirty_storage cur_dcount = moData.GetDirty(c4d.MDDIRTY_ALL) print "dirty count: " + str(cur_dcount) # count or there is no cached dirty count. if lst_dcount < cur_dcount: op[c4d.ID_USERDATA,1] = cur_dcount print "dirty" else: print "not dirty"
And the file:
matrix_dirty_0001.c4d -
Hi,
lst_dcount < cur_dcount
should belst_dcount != cur_dcount
, because when you have initialized that field with a value larger than the current dirty count (like in your document) , the whole thing will always evaluate asFalse
. Same goes for the Python generator thing.Cheers
zipit -
Hi,
yes I noticed that this happens and your suggestion is more fool-proof.
But the issue is that moData.GetDirty(c4d.MDDIRTY_ALL) doesn't increase when affecting the matrix using an Effector, or when adjusting its target object. -
Hi,
I am not quite sure, why the
MoData
does not keep track of the changes, but I also do not know much about MoGraph. Has probably something to do with that effector-field construction of yours.But you can always keep track of your data manually. Something like this:
import c4d ID_DIRTY_CONTAINER = 1000000 ID_DIRTY_LAST_COUNT = 0 ID_DIRTY_MATRIX_CONTAINER = 1 def main(): """ Manually track the state of a MoData object. """ def is_dirty(dirty_count, clone_matrices): """Determines if the dirty count of the MoData object or the specific clone matrix array data is dirty. Also updates the dirty cache. Note: For every other array (weigth, color, size, etc.) you want to keep track off, you would have to do the same as for the matrices. Args: dirty_count (int): The dirty count of the MoGraph object. matrices (list[c4d.Matrix]): The matrix array of the clones. Returns: bool: If the data is dirty """ is_dirty = False bc_data = op[ID_DIRTY_CONTAINER] # Read and compare the cached dirty data with the current data if bc_data is not None: bc_matrices = bc_data[ID_DIRTY_MATRIX_CONTAINER] # Compare the clone matrices with our cached version if len(bc_matrices) == len(clone_matrices): for i, m in enumerate(clone_matrices): is_dirty |= m != bc_matrices[i] else: is_dirty = True # Compare the dirty count with our cached value is_dirty |= dirty_count != bc_data[ID_DIRTY_LAST_COUNT] # No cache of the dirty data has been generated yet else: bc_data = c4d.BaseContainer() is_dirty = True # Write the dirty data back bc_data[ID_DIRTY_LAST_COUNT] = dirty_count bc_matrices = c4d.BaseContainer() for i, m in enumerate(clone_matrices): bc_matrices[i] = m bc_data[ID_DIRTY_MATRIX_CONTAINER] = bc_matrices op[ID_DIRTY_CONTAINER] = bc_data return is_dirty md = c4d.modules.mograph.GeGetMoData(op.GetObject()) if is_dirty(dirty_count=md.GetDirty(c4d.MDDIRTY_ALL), clone_matrices=md.GetArray(c4d.MODATA_MATRIX)): print "Was dirty" else: print "Was not dirty"
-
Thanks a lot, I'll try this out.
Unfortunately iterating through the matrices one-by-one makes it much more costly to just check for the dirty state, so I'm not sure if is practical to use this solution.
It would be more viable if the built-in Get Dirty worked, maybe that's a bug or a mistake from my side. -
Hi,
I do not think that there is a mistake of ours. The docs are pretty clear on that the other operand (e.g. the effector) is responsible for maintaining the dirty checksum. This apparently does not happen in your setup. The why on that would be pure speculation on my side and would require further investigation. As already stated, I would suspect the field and effector combo as a possible cause.
My example just shows you, how you could deal with the scenario of yours. I also do not think complexity is an issue here, since this is all linear and Python is not that slow, that it cannot even deal with linear. Or in other words: If runtime is getting an issue here, because the number of matrices is getting ridiculously large, you probably should neither use Python nor MoGraph at all.
Cheers
zipit -
Hi @orestiskon I'm afraid there is nothing much more to say except what @zipit said.
Just a few notes:
- IsDirty is build to be used within a generator to check the current object only.
- GetDirty retrieves an integer value ta represents the dirty state of an object. It can be used to retrieve DirtyCount from outside.
Now take into consideration that the matrix object is kind of special since in reality, it creates nothing. But only feed some MoData and display them (but create no geometry). So how does an object that creates nothing can have its cache dirty? That's why it's up to the object that modifies the MoData (stored in its hidden tag ID_MOTAGDATA) to tell the matrix its content is dirty so other people that rely on this matrix know about it.
Additionally to what @zipit said (which will work in any case and it's preferred)
You can also consider checking for the dirtiness of the linked effector (but this will not consider Field).
Here an example in a Python Generatorimport c4d def CheckDirtyObj(obj, uuid, flag): """ Checks if an object by comparing the current Dirt Value with the one stored in the current op.BaseContainer :param obj: The BaseList2D to retrieve the dirty state from. :param uuid: The uuid used to store in the BaseContainer. :param flag: The dirtiness flag to check for. :return: True if the object is dirty, False otherwise. """ def GetBc(): """ Retrieves a BC stored in the object BC, or create it if it does not exist yet :return: A BaseContainer where value can be stored. """ bcId = 100001 # Make sure to obtain an UNIQUE ID in plugincafe.com bc = op.GetDataInstance().GetContainerInstance(bcId) if bc is None: op.GetDataInstance().SetContainer(bcId, c4d.BaseContainer()) bc = op.GetDataInstance().GetContainerInstance(bcId) if bc is None: raise RuntimeError("Unable to create BaseContainer") return bc # Retrieves the stored value and the true DirtyCount storedDirtyCount = GetBc().GetInt32(uuid, -1) dirtyCount = obj.GetDirty(flag) # Compares them, update stored value and return if storedDirtyCount != dirtyCount: GetBc().SetInt32(uuid, dirtyCount) return True return False def main(): # Retrieve attached object and check if it's a matrix object matrixObj = op[c4d.ID_USERDATA, 1] if matrixObj is None or not matrixObj.CheckType(1018545): return c4d.BaseObject(c4d.Onull) # Retrieve the current cache opCache = op.GetCache() # We need a new object if one of the next reason are False # The Cache is not valid # The Parameter or Matrix of the current generator changed # The Parameter or Matrix of the linked Matrix changed needNewObj = opCache is None needNewObj |= not opCache.IsAlive() needNewObj |= op.IsDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX) needNewObj |= CheckDirtyObj(matrixObj, 0, c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX) # The Parameter or Matrix of effectors of the linked Matrix changed objList = matrixObj[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] for objIndex in xrange(objList.GetObjectCount()): # If the effector is disabled in the effector list, skip it if not objList.GetFlags(objIndex): continue # If the effector is not valid or not enabled, skip it obj = objList.ObjectFromIndex(op.GetDocument(), objIndex) if obj is None or not obj.IsAlive() or not obj.GetDeformMode(): continue # Check for the dirty value stored (+1 because we already used ID 0 for the matrix object) needNewObj |= CheckDirtyObj(obj, objIndex + 1, c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX) if not needNewObj: print "Old Obj" return opCache print "Generated New Object" return c4d.BaseObject(c4d.Ocube)
Cheers,
Maxime.