Object generator, GVO, cache and child objects
-
On 05/12/2013 at 17:58, xxxxxxxx wrote:
User Information:
Cinema 4D Version: R15
Platform: Windows ;
Language(s) : C++ ;---------
Hi,I have a problem with an object generator plugin and the cache handling (the "dirty" stuff).
The plugin should be able to use child objects but without a child it should use a default object.
Now the problem is, with the internal default object everything works as expected but adding an object as a child or removing it from the generator object (in the object manager inside C4D!) won't be recognized as a change and doesn't set "dirty" to TRUE (and I checked all available DIRTYFLAGS even those who have nothing to do with it at all).
First there have to be another change that the child object replaces the default object.
Like to alter an attribute of the generator object or deactivating/activating it.And it's the same the other way around when the child object will be removed or deleted.
Without these lines, the child object changes are recognized immediately of course:
Bool dirty = op->CheckCache(hh) || op->IsDirty(DIRTYFLAGS_DATA); if (!dirty) return op->GetCache(hh);
But at the cost of a lot of unnecessary rebuilts of the generator of course too.
Or do I somehow have to utilize that DependenceList stuff to make it work?
(I thought and hope that wouldn't be necessary as long as there are just base objects for my default object involved and I didn't create one from scratch.)Here is a very basic stripped down plugin example to test and demonstrate the behaviour I'm talking about:
////////////////////////////////////////////////////////////// // CINEMA 4D ObjectGeneratorTest // ////////////////////////////////////////////////////////////// // objectgeneratortest.cpp // ////////////////////////////////////////////////////////////// #include "c4d.h" ///////////////////////////////////////////////////////// // Class functions ///////////////////////////////////////////////////////// class ObjectGeneratorTest : public ObjectData { public: //ObjectData (Generator) virtual BaseObject* GetVirtualObjects(BaseObject *op, HierarchyHelp *hh); static NodeData *Alloc(void) { return gNew ObjectGeneratorTest; } }; // main routine: build virtual testobject BaseObject *ObjectGeneratorTest::GetVirtualObjects(BaseObject *op, HierarchyHelp *hh) { CallCommand(13957); // Clear Console GePrint("Cache: " + RealToString(op->CheckCache(hh))); // console output for debugging! GePrint("DIRTYFLAGS_0: " + RealToString(op->IsDirty(DIRTYFLAGS_0))); // console output for debugging! GePrint("DIRTYFLAGS_MATRIX: " + RealToString(op->IsDirty(DIRTYFLAGS_MATRIX))); // console output for debugging! GePrint("DIRTYFLAGS_DATA: " + RealToString(op->IsDirty(DIRTYFLAGS_DATA))); // console output for debugging! GePrint("DIRTYFLAGS_SELECT: " + RealToString(op->IsDirty(DIRTYFLAGS_SELECT))); // console output for debugging! GePrint("DIRTYFLAGS_CACHE: " + RealToString(op->IsDirty(DIRTYFLAGS_CACHE))); // console output for debugging! GePrint("DIRTYFLAGS_CHILDREN: " + RealToString(op->IsDirty(DIRTYFLAGS_CHILDREN))); // console output for debugging! GePrint("DIRTYFLAGS_DESCRIPTION: " + RealToString(op->IsDirty(DIRTYFLAGS_DESCRIPTION))); // console output for debugging! GePrint("DIRTYFLAGS_SELECTION_OBJECTS: " + RealToString(op->IsDirty(DIRTYFLAGS_SELECTION_OBJECTS))); // console output for debugging! GePrint("DIRTYFLAGS_SELECTION_TAGS: " + RealToString(op->IsDirty(DIRTYFLAGS_SELECTION_TAGS))); // console output for debugging! GePrint("DIRTYFLAGS_SELECTION_MATERIALS: " + RealToString(op->IsDirty(DIRTYFLAGS_SELECTION_MATERIALS))); // console output for debugging! // check if something has been changed Bool dirty = op->CheckCache(hh) || op->IsDirty(DIRTYFLAGS_DATA); // if !dirty object is already cached and doesn't need to be rebuilt GePrint("dirty: " + RealToString(dirty)); // console output for debugging! if (!dirty) return op->GetCache(hh); // if nothing has changed return cache // create a null object BaseObject *parent = BaseObject::Alloc(Onull); if (!parent) return NULL; BaseObject *testobject; if (!op->GetDown()) { // create a figure as testobject testobject = BaseObject::Alloc(Ofigure); } else { // clone child object(s) as testobject // testobject = (BaseObject* ) op->GetDown()->GetClone(COPYFLAGS_0, NULL); // use the first child object only testobject = op->GetAndCheckHierarchyClone(hh, op->GetDown(), HIERARCHYCLONEFLAGS_ASIS, &dirty, NULL, TRUE); // use all child objects } if (testobject) { testobject->InsertUnderLast(parent); } return parent; } ///////////////////////////////////////////////////////// // Register function ///////////////////////////////////////////////////////// // be sure to use a unique ID obtained from www.plugincafe.com #define ID_OBJECTGENERATORTEST 1000001 // Plugin IDs 1000001-1000010 are reserved for development only! Bool RegisterObjectGeneratorTest(void) { return RegisterObjectPlugin(ID_OBJECTGENERATORTEST, "Object Generator Test", OBJECT_GENERATOR|OBJECT_INPUT, ObjectGeneratorTest::Alloc, "", NULL, 0); }
Edit:
By the way, MSG_DRAGANDDROP is only triggered when something is dropped on the plugin object, not away from it!
Just in case someone would come to the idea to use that in any way.Kind regards,
Tom -
On 07/12/2013 at 21:19, xxxxxxxx wrote:
101 views and not a single answer?
-
On 08/12/2013 at 14:06, xxxxxxxx wrote:
I really suck at Generator plugins.
But since nobody is replying. I'll post some code that I've used.I have an ObjectData plugin that generates a circle spline.
But when I drop an object as the child of the generator. It generates a clone of that child instead of the circle spline. Which sounds similar to what you want.These are the parts of the code that deal with handling the child object scenario.
I also have an if() statement in my GetContour() method that sets it to return NULL depending on the value of the child variable. But that's just for generating splines. So you probably don't need to see that.class MyObject: public ObjectData { private: LONG child; public: virtual Bool Init (GeListNode *node); virtual Bool Message(GeListNode *node, LONG type, void *data); //...etc }; Bool MyObject::Message(GeListNode *node, LONG type, void *data) { //Poll the generator to see if it has a child if( ((BaseObject* )node)->GetDown() ) child = TRUE; else child = FALSE; return TRUE; } BaseObject *MyObject::GetVirtualObjects(BaseObject* op, HierarchyHelp *hh) { BaseContainer *data = op->GetDataInstance(); //"data" will be used get any of the gizmos we've added to the generator BaseDocument *doc = hh->GetDocument(); //We MUST generate an object. So this will be the parent object we generate (an "empty" object...NOT a "NULL" BaseObject!!) BaseObject *parent = NULL; //If the the child variable is true //This code block replaces the empty NULL generated object with a clone of the first child of the generator if(child) { //First we replace the empty NULL generator with an actual Null object //We do this so we can use GetDown() on the generator object to get the child object parent = BaseObject::Alloc(Onull); //Now get the child object that's under the generator if(op->GetDown()) { BaseObject *child = op->GetDown(); BaseObject *clone = static_cast<BaseObject*>(child->GetClone(COPYFLAGS_0,NULL)); clone->InsertUnder(parent); //Insert the clones under the virtual Null object //This code makes the original child object invisible so we only see the generated clone op->NewDependenceList(); op->AddDependence(hh, op->GetDown()); op->TouchDependenceList(); } } return parent; }
-ScottA
-
On 09/12/2013 at 02:11, xxxxxxxx wrote:
Hi Scott,
thank you for trying to help me.
But as far as it appears to me, you're not using the cache at all, or do you?
So your code will always be recalculated in any case whether if needed or not.
And if that's the fact, then of course you won't even face that problem I was talking about.But please correct me if I'm wrong.
Kind regards,
TomEdit:
By the way, I noticed you declared your "child" variable as "LONG" though "Bool" would be enough in my opinion.
Any special reason for that or just by accident? -
On 09/12/2013 at 08:15, xxxxxxxx wrote:
Sorry about that. I used LONG by mistake there. It should be a Bool.
Yeah. I'm not using the cache for targeting the child object status.What I've seen is that it's it's very hard to get any information for things that are happening in other places from inside the GetVirtualObjects() function.
That's why I resorted to using the Message() function for polling the scene. And then sort of snuck that info into the GetVirtualObjects() function using the global class variable "child" to swap the generated object.There's possibly another (better?) way to do it.
But this is the only way I got child checking, and generator swapping, to work.-ScottA
-
On 10/12/2013 at 02:12, xxxxxxxx wrote:
As far as I can tell from the code you posted, you don't check if there is a child or not. As you pointed out
by yourself already, hierarchy changes do not affect any of the dirtyflags. Have you tried using the
NewDependenceList() method in the BaseObject class?You can do what the Dependence List does internally yourself, too:
class MyObjectData : public ObjectData { Int32 m_dcount; public: /\* ObjectData Overrides \*/ virtual BaseObject\* GetVirtualObject(BaseObject\* op, HierarchyHelp\* hh) { if (!op || !hh) return nullptr; /\* Sum up the dirty counts. \*/ Int32 dcount = op->GetDirty(DIRTYFLAGS_DATA); BaseObject\* child = op->GetDown(); if (child) dcount += child->GetDirty(DIRTYFLAGS_DATA); /\* Return the cache if the dirty counts didn't change. \*/ if (dcount == m_dcount) { BaseObject\* cache = op->GetCache(hh); if (cache) return cache; } m_dcount = dcount; /\* Generate the new virtual objects. \*/ /\* ... \*/ } };
Best,
-NiklasEdit: Updated code, the member dirtycount was not updated.
-
On 10/12/2013 at 10:03, xxxxxxxx wrote:
^ The problem I ran into wasn't getting the dirty cache. It was testing if the generator had a non-generated child object or not.
Every attempt to check for generator->GetDown(); or Generator->Parent->GetDown(); resulted in crashing.
The only way I could do it was by checking for a non-generated object someplace other than from inside of the GetVirtualObject() function.
That's why I did it from inside Message().
It's the only way I could safely poll for a non-generated child object without crashing.-ScottA
-
On 11/12/2013 at 15:24, xxxxxxxx wrote:
Thank you for your replies Scott and Niklas!
I'll try to figure out how I can adapt your informations for my needs.I'm just afraid it'll have to wait a while for now, cause I've a few more important things to do first.
But I'll notify you as soon as I have a final solution (or maybe more questions ).
-
On 10/09/2014 at 15:47, xxxxxxxx wrote:
Recently I came back to this old problem by me and also found a solution that works for me meanwhile.
So I want to share it with you since many C4D plugin coder seem to stumble upon this problem from time to time. (Just do a forum search about related topics.)And maybe you could also tell me if my solution is clean and recommendable or if you would suggest some improvements.
E.g. if you know a better/cleaner alternative to my "childcheck trigger dirty state trick", please let me know.To remember you, the goal was a generator that use it's first child object for doing "something" and if there is no child it should use a predefined default object for it's work instead.
(Check out the first post of this thread.)And the main problem was to avoid all the unnecessary rebuilts but a correct handling of changes to the child object(s).
Okay, so here is my working example demo code now:
////////////////////////////////////////////////////////////// // CINEMA 4D ObjectGeneratorTest // ////////////////////////////////////////////////////////////// // objectgeneratortest.cpp // ////////////////////////////////////////////////////////////// #include "c4d.h" ///////////////////////////////////////////////////////// // Class functions ///////////////////////////////////////////////////////// class ObjectGeneratorTest : public ObjectData { public: //ObjectData (Generator) virtual BaseObject* GetVirtualObjects(BaseObject *op, HierarchyHelp *hh); static NodeData *Alloc(void) { return gNew ObjectGeneratorTest; } Bool childcheck = TRUE; }; // main routine: build virtual clone BaseObject *ObjectGeneratorTest::GetVirtualObjects(BaseObject *op, HierarchyHelp *hh) { // check master object for changes Bool dirty = op->IsDirty(DIRTYFLAGS_DATA); BaseObject *parent = NULL; BaseObject *clone = NULL; if (!op->GetDown()) { if (childcheck) { childcheck = FALSE; dirty = TRUE; } if (dirty) { // create a figure as default object clone = BaseObject::Alloc(Ofigure); if (!clone) return NULL; } } else { if (!childcheck) { childcheck = TRUE; dirty = TRUE; } // Get first input object BaseObject* child = op->GetDown(); if (!child) return NULL; // generate clone of input object clone = op->GetAndCheckHierarchyClone(hh, child, HIERARCHYCLONEFLAGS_ASIS, &dirty, NULL, FALSE); if (!clone) return NULL; clone->SetAbsPos(Vector(0)); // make the original (first) child object and it's hierarchy invisible and get rid of it when the generator will be converted dirty = dirty || !op->CompareDependenceList(); // compare if anything in the dependence list has changed op->TouchDependenceList(); // mark all the objects in the dependence list that they will be replaced by the generator } // if !dirty object is already cached and doesn't need to be rebuilt if (!dirty) return op->GetCache(hh); GePrint("clone: " + clone->GetName()); // console output for debugging! // group all further objects with this null object parent = BaseObject::Alloc(Onull); if (!parent) goto Error; clone->InsertUnderLast(parent); return parent; Error: GePrint("ERROR!"); Free(clone); Free(parent); return NULL; } ///////////////////////////////////////////////////////// // Register function ///////////////////////////////////////////////////////// // be sure to use a unique ID obtained from www.plugincafe.com #define ID_OBJECTGENERATORTEST 1000001 // Plugin IDs 1000001-1000010 are reserved for development only! Bool RegisterObjectGeneratorTest(void) { return RegisterObjectPlugin(ID_OBJECTGENERATORTEST, "Object Generator Test", OBJECT_GENERATOR|OBJECT_INPUT, ObjectGeneratorTest::Alloc, "", NULL, 0); }
One important thing I've learned during all this is not to use the "NewDependenceList ()" and "AddDependence ()" stuff (with going through all the objects replaced by the generator with own code) if the "GetAndCheckHierarchyClone()" function is used because all the necessary DependenceList handling is yet built in there and you'll come into trouble if you interfere with that with additional DependenceList creation and adding stuff!
That's it so far from me.
I'm looking forward to your comments!Kind regards,
Tom