Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
Aha I see, this makes sense for the NormalTag data. And yeah of course cross product is what I need...
Regarding the primitives not turning into polygon object, I thought that was happening with a cube, but now after testing it again it definitely is. So I must have been mistaken before.
Thanks for the help, going to mark this as solved.
I'm developing a plugin on Windows 10 with C++, R23, which represents a fluid volume.
So I've got a functionality which needs to preview the intersection of my object with a Cinema 4D object. You can imagine the below screenshot to have the magenta mesh only in the hollow hemisphere.
Anyway for that I need the vertices, triangle faces and normals data from the hemisphere. For that I do the following:
BaseObject *intersectionGeometry = ...; // ACQUIRED FROM LINK if (intersectionObject != nullptr) { ModelingCommandData mcd; mcd.doc = document; mcd.op = intersectionObject; if (!SendModelingCommand(MCOMMAND_CURRENTSTATETOOBJECT, mcd)) return; C4DAtom* atom = mcd.result->GetIndex(0); BaseObject* const res = static_cast<BaseObject*>(atom); // WITH SOME STANDARD PRIMITIVES THIS IS NULLPTR if (res != nullptr && res->GetType() == Opolygon) { PolygonObject* polyObjectRes= static_cast<PolygonObject*>(res); Vector* points = polyObjectRes->GetPointW(); CPolygon* polygons = polyObjectRes->GetPolygonW(); unsigned polygonsCount = polyObjectRes->GetPolygonCount(); unsigned pointsCount = polyObjectRes->GetPointCount(); NormalTag* normalTag = static_cast<NormalTag*>(polyObjectRes>GetTag(Tnormal)); // THIS IS ALWAYS NULLPTR! } }
So as you can see I'm taking a BaseObject* and convert to a PolygonObject* with ModelingCommandData* . Not sure if this is the best way but it works for most standard primitives (like a sphere or torus). After I have the PolygonObject* I take its vertices and polygons arrays which is fine. But then I'm having difficulty with the NormalTag* which is never anything else but nullptr.
BaseObject*
PolygonObject*
ModelingCommandData*
NormalTag*
My main question is about the NormalTag and how can I access that data? And my secondary question is how come not all primitives can be converted to a PolygonObject*? Any help would be appreciated.
NormalTag
Regards, Georgi.
Hey guys, thanks for the explanations.
My only concern is that I can't really do anything besides closing Cinema4D to cancel the operation. Just for reference my plugin is a fluid volume that needs to be visualized in the viewport. This is not an expected workload, stumbled on it by chance, but so can one of the users.
Anyway we can close this thread if it's not a bug.
@Cairyn hey man thanks for the suggestion!
Actually my object data plugin is no longer crashing, but enters an infinite loop trying to allocate the memory it needs until it sends "Not enough memory" message and tries again. I only have 16GB ram. This behavior is reproducible with a simple cube with segmentation to 100x100x100, inserted in a subdivision surface object with subdivision editor field set to 6. I'm not sure if it's a bug guys, but it seems to require a reboot to fix.
Hey guys,
I'm developing an ObjectData plugin with C++ on Windows10, Cinema 4D R23.
I'm getting a crash when I insert my plugin in a subdivision surface and crank up the subdivision editor field to 6. The crash is in Cinema 4D but the culprit is probably my implementation. Just in case I wanted to test this with some native Cinema4D object to see if I can reproduce it, but I'm having a hard time to simply create a PolygonObject with say 100k polygons.
What would be the easiest way to generate such an object? I think I can also reproduce it with less polygons so it doesn't have to be 100k, could be less.
Warm regards, Georgi.
@ferdinand
Hey I recently noticed that when I get over 2 million points and 1 million segments the viewport freezes. The UI is still responsive so I can reduce the lines in my object and it starts drawing in the viewport again.
So I'm wondering if this is expected for this amount of points and segments? I haven't found anything that is obviously wrong in my implementation yet.
@ferdinand thank you for the suggestions, totally forgot to write back.
Grouping the LineObject objects by a few shades worked out pretty well.
Hey @PluginStudent, yeah that's an alternative but I want to avoid overriding Draw() unless there's no other way.
Hi guys, Using C++, R23. I've got an ObjectData plugin that represents a volume in the viewport. One of the representations is a velocity field, which is just a bunch of lines with colors. I'm inserting a LineObject in GetVirtualObjects() to do the job, and it is being generated and working as expected, but I'm not sure how to pass my custom colors. Is there something like the VertexColorTag for a PolygonObject or an alternative routine?
ObjectData
LineObject
GetVirtualObjects()
VertexColorTag
PolygonObject
All right, thank you for all the help.
I'll close this now.
@zipit yes ok seems I got this now, after completely disregarding the fact that object is part of the Cloner's cache.
So for my original problem of creating just X amount of materials for X amount of clones, that won't be doable. I'll just have to create as many as new objects are created and then just clean them all up at some point.
For the clean up mechanism, I have this dummy object which links to my original object and its material. The original object also links to the dummy object. The dummy objects are inserted in the a scene hook and at some point I iterate them and check if they link to nullptr object to determine whether the original object is still part of the document and whether to release the associated materials.
Do you think there's easier way to achieve this?
@zipit said in Difficulty with cloners and and lifetime of objects:
# This is the cache of the node, it will also contain an MAXON_CREATOR_ID, # but it will NOT always be the same, since caches can change in "shape # and form", so there is no way to maintain this continuity. # I assume this is what you are trying to do, or at least a good part of # it. This cannot be done, because you are basically asking here the # caches to be static. if cache is not None: print ("cache uuid:", uuid_1.hex())
you are right, assuming you mean with cloned objects generators with caches
Ooooh maybe I misunderstood your point slightly. My 9 objects in the cloner would always be different because they are part of the cache of the Cloner object. If they stand on their own it is a different story, and the UUIds are persistent.
Hope i'm getting it right now ?
Hey @zipit happy holidays and thank you for the reply, I understand how this id works now.
However this ID seems to not be persistent for cloned objects. If I put my object in a cloner in instance mode grid array 3,1, 3 dimensions GetVirtualObjects(BaseObject* object...) would get called 9 times with 9 different objects. There are 9 different UUIDs and this is ok.
GetVirtualObjects(BaseObject* object...)
But then every subsequent call to GetVirtualObjects(BaseObject* object...), after I change some setting or simply move the object, there will be a different set of 9 UUIDs, so I can't really rely on them anymore. So with the cloner this reallocation boundary changes the UUIDs.
Is there a way around it?
EDIT: If I go in edit mode and change the settings of each instance in the cloner separately then the UUIDs seem to be persistent, but that's just in edit mode.
@zipit yes thank you for the script. What I need seems to be exactly the Node uuid.
But I'm struggling with the C++ equivalent. I have this signature: Bool FindUniqueID(Int32 appid, const Char*& mem, Int& bytes ) const and I use it like so :
Bool FindUniqueID(Int32 appid, const Char*& mem, Int& bytes ) const
const Char* value = nullptr; Int size = sizeof(Int32); if (object->FindUniqueID(MAXON_CREATOR_ID, value, size)) { /// do stuff with value }
I was expecting *(Int32*)value to be my unique ID but it is always the same. for all objects in the scene. This is not the case your Python script, each node has its own ID, so I guess I'm using this wrong ?
*(Int32*)value
Hey thanks for the reply.
The crux of my question is exactly as you guessed. How to get unique ID's for each object so I can build a hashmap. These ID's should be consistent between reallocations of the node during the lifetime of its GUI representation. And they should be unique even for objects that are cloned. The context is that I have volume caches which I load into ram using a separate DLL and I want to synchronize those with the objects in the scene. To load and free them just once, when it is most appropriate.
This Add/FindUniqueID is something I had missed so far and I'm looking at the previous threads as you advised. In some of the threads you refer to FindUniqueID returning a hash, but in C++ it seems I have to create the ID myself and add it to the object with AddUniqueID(Int32 appid, const Char* const mem, Int bytes) which include some extra parameters. How would I go about letting Cinema create the hash?
Add/FindUniqueID
FindUniqueID
AddUniqueID(Int32 appid, const Char* const mem, Int bytes)
Hey guys, using R23, Windows 10, C++.
I've got another question about my ObjectData plugin and GetVirtualObjects(). The workflow is that a material needs to be created and inserted into the document. Each object in the scene needs to have a its own material and its own file loaded into memory.
In general the problem is that I can't exactly track the lifetime of an object in C4D scene. Functions like ObjectData::Init() and ObjectData::Free() get called all over the place s o I don't rely on them.
ObjectData::Init()
ObjectData::Free()
My solution is to insert the material in GetVirtualObjects(). It consists of a different ObjectData "dummy" object and SceneHook object. The 2 ObjectData objects have a Link in their description pointing to each other. The second "dummy" ObjectData is inserted in the SceneHook:
GetVirtualObjects(BaseObject* object) { BaseObject* dummy = getAtomBaseLink<BaseObject>(*object, DUMMY_OBJECT, NONE, false).GetValue(); BaseObject* dummyParent = nullptr; if (dummy ) { DebugAssert(link->GetType() == ID_DUMMY_OBJECT); dummyParent = getAtomBaseLink<BaseObject>(*dummy, PARENT_OBJECT, document, NONE, false).GetValue(); } if (!dummy || dummyParent != object) { BaseSceneHook* hook = document->FindSceneHook(HOOK_ID); if (hook) { BranchInfo branchInfo[1]; const Int32 count = hook->GetBranchInfo(branchInfo, COUNT_OF(branchInfo), NONE); if (branchInfo[0].head) { GeListHead *dummiesHead = branchInfo[0].head; dummy = BaseObject::Alloc(ID_DUMMY_OBJECT); if (dummy != nullptr) { dummy->InsertUnderLast(dummiesHead); // set the links of the 2 objects to point to each other // do other stuff for the material } } } } }
At some points I iterate the dummies in the hook and if their Links are nullptr I know the original ObjectData object has been removed from the scene and I can free the material. That's the "garbage collection" process.
Overall this routine is working ok. The material is created just once for each object in the scene. But when I put my object in a Cloner, GetVirtualObjects(BaseObject* object) would get called 9 different times with 9 different objects. I'd get 9 different dummies and 9 different materials which is what we want. But every time a setting is changed GetVirtualObjects(BaseObject* object) would get called with yet another set of objects and add more materials, and this would continue indefinitely. In the end my "garbage collection" process would clean them all up but still I really want to limit the creation to just the amount I need, but I'm not seeing how.
GetVirtualObjects(BaseObject* object)
Furthermore I'm also keeping a hashmap in a different dll with some data, which corresponds to each object in the C4D scene. So i need some unique identifier, and I was using GetGUID but again it is not suitable for the situation with the cloners.
GetGUID
Thanks for the attention and any help will be appreciated!
So in the end for the UI workflow this seems to work but it skips some of the steps, like linking to the BaseContainer*, you proposed in the last post. Capturing the links by reference was also causing the issues with the GetLink() calls I had mentioned and if lots of copies were made we'd crash in the BaseLink::Free() call.
BaseContainer*
GetLink()
BaseLink::Free()
BaseLink *objectLink = BaseLink::Alloc(); if (objectLink != nullptr) { objectLink->SetLink(object); auto copySettingsLambda = [objectLink, document]() -> void { if (objectLink != nullptr) { BaseObject* myObject = static_cast<BaseObject*>(objectLink->GetLink(document)); if (myObject != nullptr) { // SetAtomParamter<> calls with myObject } BaseLink* toDelete = objectLink; BaseLink::Free(toDelete); } }; maxon::ExecuteOnMainThread(copySettingsLambda, false); }
And for the Material workflow the lambda captures a different BaseObject* myObject, which has a link in its description to the Material I want to insert in the document. I wasn't able to just link the material in the BaseLink.
BaseObject* myObject
BaseLink *materialLink= BaseLink::Alloc(); if (materialLink!= nullptr) { materialLink->SetLink(myObject); auto insertMaterialLambda= [materialLink, document]() -> void { if (materialLink!= nullptr) { BaseObject* myObject = static_cast<BaseObject*>(materialLink->GetLink(document)); if (myObject != nullptr) { BaseMaterial* material= getAtomBaseLink<BaseMaterial>(*myObject, MATERIAL_ID, document).GetValue(); if(material != nullptr) { document->insertMaterial(material); } BaseLink* toDelete = material; BaseLink::Free(toDelete); } } }; maxon::ExecuteOnMainThread(copySettingsLambda, false); }
Do you think this is safe now compared to the original capturing of the BaseObject* object ? Stressing it a lot and it doesn't seem to crash (although it also wasn't crashing with the original solution)
Many thanks for the attention, Georgi.
Hey thanks @m_adam . This seems to doing it for this workflow.
bc->SetLink(PLUGIN_ID, link); // I use PLUGIN_ID since you want to be sure no one else will overwrite this value.
So what if I have another thing I want to link. (PLUGIN_ID + 1) seems to be doing the trick. Would that be fine ?
This other thing is a BaseMaterial* instead of a BaseObject*, like so:
BaseMaterial*
material = BaseMaterial::Alloc(Mmaterial); if(material==nullptr) return; BaseContainer* bc = object->GetDataInstance(); if (bc != nullptr) { BaseLink* materialLink = bc->GetBaseLink(PLUGIN_ID + 1); if (materialLink == nullptr || materialLink->ForceGetLink() == nullptr){ BaseLink* materialLink = BaseLink::Alloc(); if (materialLink != nullptr) { materialLink->SetLink(material); bc->SetLink(PLUGIN_ID + 1, material); auto insertMaterialLambda = [materialLink, document]() -> void { // GetLink() for the material but it is always nullptr BaseMaterial* material= static_cast<BaseMaterial*>(materialLink->GetLink(document)); } maxon::ExecuteOnMainThread(insertMaterialLambda, false); } } }
It is as if BaseMaterial* can't be set to the BaseLink. The material that I get from the link in the lambda is always nullptr.
BaseLink
Hey @m_adam this is what I've tried:
GetVirtualObjects(BaseObject* object...) { . . . BaseLink* link = BaseLink::Alloc(); if (link != nullptr) { link->SetLink(object); auto myLambda = [&link, document]() -> void { if(link != nullptr) { BaseObject* object = static_cast<BaseObject*>(link->GetLink(document)); if(object != nullptr) { // do something with object } } } ExecuteOnMainThread(myLambda,false); } . . . }
But this seems to break easily in the link->GetLink(document) call when I copy my object a few times. Maybe that is why you mentioned overriding the ObjectData::CopyTo earlier? Or maybe i'm using the BaseLink wrong?
link->GetLink(document)
ObjectData::CopyTo
@m_adam said in Calling ExecuteOnMainThread() from ObjectData::GetVirtualObjects():
If you just need to set a value of a given gadget in your UI then call a SetParameter on the object instance and this should work.
That's all.
What you can do is passing a BaseLink* linking to your own Generator that your Generator is owning. This way you can set it to nullptr safely (the reference in your lambda will also be nullptr this way) and BaseLink offers a way to retrieve the original AtomObject. For more information see BaseLink Manuel.
Hey i've been trying to understand this but the manual hasn't been speaking to me... What do you mean by passing a BaseLink* to my Generator ?
As for a real example what more do you need than this minimal snippet ? There is an integer setting "MY_OBJECT_INTEGER_PROPERTY" that needs to be set, so some number is generated in GetVirtualObjects() and it is set. Btw this hasn't crashed for me yet, even though it was tested with lots of scenarios.
MyObjectDataPlugin::GetVirtualObjects(BaseObject* object) { int toSet = GetRandomNumber(); auto myLambda = [object]() -> void { if (object != nullptr) { setAtomParameter<maxon::Int32>(*object, MY_OBJECT_INTEGER_PROPERTY, toSet) iferr_cannot_fail("Setting should not fail"); } } ExecuteOnMainThread(myLambda,false); };
Did you mean to allocate a BaseLink* with BaseLink::Alloc() before the lambda or have the BaseLink in the Description of MyObjectData plugin? Sorry for being a bit slow...