FieldList, and how to properly use it
-
Hello,
in my plugin, I support Fields. I have a
FIELDLIST
element in my.res
file, and I can get theFieldList
custom datatype, et cetera.But there are some things I really don't know how to handle:
1. Dirtyness
I need to track dirtyness of all Field objects in the list, so I can determine when to rebuild my plugin object's cache.FieldList::GetDirty()
seems to catch only changes that were made to the list, not to any of the stuff in the list. I found that theIsDirty()
functions often report dirtyness when actually nothing has changed, so I am using theGetDirty()
function of each Field object, then store the dirty checksums and later compare if the checksums I get from the Field objects to see if they have changed.This seems to work ok. However, it's quit a lot of code required, just for tracking the dirtyness:
// Check for dirty fields GeData geFieldData; if (op->GetParameter(MYOBJECT_FIELDS_LIST, geFieldData, DESCFLAGS_GET::NONE)) { // Get FieldList CustomDataType* const fieldData = geFieldData.GetCustomDataType(CUSTOMDATATYPE_FIELDLIST); FieldList* const fieldList = static_cast<FieldList*>(fieldData); if (fieldList && fieldList->HasContent()) { // Dirty fieldList _currentDirtyChecksum_fieldList = fieldList->GetDirty(doc); needsEvaluation |= _currentDirtyChecksum_fieldList != _compareDirtyChecksum_fieldList; // Dirty field objects GeListHead *listHead = fieldList->GetLayersRoot(); if (listHead) { _currentDirtyChecksum_fieldLayers = 0; _currentDirtyChecksum_fieldObjects = 0; for (FieldLayer *layer = static_cast<FieldLayer*>(listHead->GetFirst()); layer; layer = IterateNextFieldLayer(layer)) { // Layer node (in list) _currentDirtyChecksum_fieldLayers += layer->GetDirty(DIRTYFLAGS::DATA); GePrint(layer->GetName()); // Actual field object const FieldLayerLink layerLink = layer->GetLinkedObject(doc); BaseObject *fieldObject = static_cast<BaseObject*>(layerLink._object); if (fieldObject) { _currentDirtyChecksum_fieldObjects += fieldObject->GetDirty(DIRTYFLAGS::CACHE|DIRTYFLAGS::DATA|DIRTYFLAGS::MATRIX); } } needsEvaluation |= _currentDirtyChecksum_fieldLayers != _compareDirtyChecksum_fieldLayers; needsEvaluation |= _currentDirtyChecksum_fieldObjects != _compareDirtyChecksum_fieldObjects; } } }
_currentDirtyChecksum_fieldObjects
,_currentDirtyChecksum_fieldLayers
,_compareDirtyChecksum_fieldLayers
, and_compareDirtyChecksum_fieldObjects
are members of myNodeData
. The_compare*
checksums are later updated with the values from the_current*
checksums.2. FieldList contents
There are the functionsFieldList::HasContent()
andFieldList::GetCount()
. Why is this:- Removing Field objects from the
FieldList
correctly decreases the return value ofGetCount()
, and, after removing the last Field object from the list, makesHasContent()
returnfalse
. - Deleting Field objects that are linked in the
FieldList
from the document does not decrease the the return value ofGetCount()
andHasContent()
always returnstrue
.
That means, if the user deletes linked Field objects in the Object Manager, my plugin object is permanently broken. It behaves as if there still were Field objects to take into account. And the user has no chance of repairing it, because the
FieldList
appears empty to the user.I know this behaviour is consistent with the
InExcludeList
, which also maintains zombie BaseLinks. But I never understood the use of this. I also tried iterating the list and removing all zombie elements by deleting the list item ifGetLinkedObject()
does not return a valid pointer, but unfortunately that would also remove folders in theFieldList
.MoGraph seems to handle it well. If I delete a Field object that is linked in an effector object, the effector will show full effect, even though there should be a zombie link in the
FieldList
. What does MoGraph do to handle this so robustly?It really is a pity that there's not a single example in the SDK (not in the GitHub repo either) that uses a
FieldList
.Thanks for any help!
Cheers,
Frank - Removing Field objects from the
-
Is anyone from the Tech Support looking into this?
Or is any of this unclear, should I elaborate more? -
Hi @fwilleke80 sorry I was busy yesterday, so for the first point there is nothing to help you more, as you may know, some mograph stuff (especially if you rely on matrix or shader) are constantly dirty... And if one of those is on the fieldlist, then all the fieldlist will be dirty. So your approach to monitoring each one is the most optimized.
Regarding your second point, I've reached the development team. For me, the reason is for undo operation, but that's true its a bit unexpected, so let's see what the development team will say.
But when a field object is deleted, the FieldLayer has his flags set to SKIP | HIDE | TEMPORARY. You can retrieve them via FieldLayer::GetLayerFlags.
To Exclude folder you can simply check FieldLayer for its typeFLfolder
.Regarding example we do have mograph_fields.cpp which demonstrate:
- A CommandData that samples a FieldObject
- A CommandData that samples the field list of a plain effector.
- A Custom FieldData implementation
- A FieldLayerData implementation
So you are right we don't have an example of how to handle field within an ObjectData. This is something we will add on our ToDo list.
Cheers,
Maxime. -
Just et a reply from the developer and as suspected this is to properly support undo.
Hi,
This is the same system used in inexclude lists throughout Cinema. The object in the list isnβt the object itself, just a layer with a link to the object, if the real world object is deleted then the entry simply points to nothing. When you delete in the list though you are deleting the layers and adding undos to the object the list is on via the GUI system, the list additionally checks for any other uses of the object in scene and deletes it as well if none are found.
Because of differences in how the object system and GUI system works, and that fields may be shared in multiple lists this approach allows undos, GUI display and evaluation to work correctly. These layers are then marked for omission in file operations.
Hope it makes sense,
Cheers,
Maxime. -
Thank you! I'll see if the flags solve my problem. Sounds like I can work with this.
I'll mark this SOLVED for now!An example for a
FieldList
in anObjectData
would be most appreciated!Cheers,
Frank -
Back from a short autumn vacation. Sorry, I have to mark this as UNSOLVED again.
But when a field object is deleted, the FieldLayer has his flags set to SKIP | HIDE | TEMPORARY. You can retrieve them via FieldLayer::GetLayerFlags.
After deleting a linked
FieldObject
, the respectiveFieldLayer
in theFieldList
is not set toSKIP
orHIDE
orTEMPORARY
. It is stillNONE
.EDIT: Interesting. I wrote a little Python tag to print out the flags of all
FieldLayers
in theFieldList
of a MoGraph Effector object, and in deed, the flags are set in the Effector'sFieldList
'sFieldLayer
. They are not set in my object.I guess the
EffectorData
usually takes care for flagging the deadFieldLayer
s. My object is not an effector. Is there any message I need to catch and handle?For now, I'll try and recognise dead and otherwise unusable
FieldLayers
like this. but it doesn't feel safe:inline Bool IsActiveFieldLayer(FieldLayer *fieldLayer, BaseDocument *doc) { // Inactive or not sampling the value (we only need the ones that sample value) const FIELDLAYER_CHANNELFLAG channelFlags = fieldLayer->GetChannelFlags(); if (!(channelFlags&FIELDLAYER_CHANNELFLAG::ENABLE) || !(channelFlags&FIELDLAYER_CHANNELFLAG::VALUE)) return false; // Flagged as no-to-be-used (doesn't seem to work, flags are never set) const FIELDLAYER_FLAG layerFlags = fieldLayer->GetLayerFlags(); if (layerFlags&FIELDLAYER_FLAG::SKIP || layerFlags&FIELDLAYER_FLAG::ERRORSKIP || layerFlags&FIELDLAYER_FLAG::HIDE || layerFlags&FIELDLAYER_FLAG::TEMPORARY) return false; // Dead link but not a folder (once the flags actually work, the first line can be removed) if (!fieldLayer->GetLinkedObject(doc)._object && fieldLayer->GetType() != FLfolder) return false; // We can consider the FieldLayer active and usable return true; }
Cheers,
Frank -
Hi @fwilleke80 looking at the code this is something done in the CheckDirty and FieldData::SetLinkedObjectin all our FieldData.
The logic is similar by checking if the link is still valid.So there is no message for that, and this should be handled automatically if in your
ObjectData::CheckDirty
your properly forward the call to the falloff. Find an example in Deformer update and Fields.Note that this method is not magic and rely on the CheckDirty to be called so I would say even our solution is not the most robust.
Cheers,
Maxime. -
Thank you!