Capsules Drag & Drop
-
Hi.
When working on a tag plugin I've recently been encountering a problem when I try to interact with Capsules, my plugin is meant to have parameters dropped onto it from the object it is attached to.
In my code I can properly receive the drag and drop operation and get the information from the parameter that was dropped onto it, the issue comes in when I try to determine which object the parameter came from.
When it comes to the Capsules in the object manager my code does not return the results that I am expecting when I try to verify that the parameters were dragged from the object that the tag is on.
The code below is a stripped down version of my drag and drop code.
Bool TagExample::Message(GeListNode* node, Int32 type, void* t_data) { BaseDocument* doc = node->GetDocument(); if (!doc) return TRUE; BaseTag* tag = static_cast<BaseTag*> (node); if (!tag) return TRUE; if (type == MSG_DRAGANDDROP) { DragAndDrop* dnd = static_cast<DragAndDrop*>(t_data); if (!dnd) return TRUE; if (dnd->type == DRAGTYPE_DESCID) { DescPropertyDragData* dndid = static_cast<DescPropertyDragData*>(dnd->data); AutoAlloc<AtomArray> dndidarr; dndid->arr->CopyTo(dndidarr); if (dndidarr->GetCount() <= 0) return TRUE; BaseObject* tagObject = tag->GetObject(); if (!tagObject) return TRUE; BaseObject *draggedObject = (BaseObject*)dndidarr->GetIndex(0); if (!draggedObject) return TRUE; if (draggedObject != tagObject) ApplicationOutput("Do not equal"); else ApplicationOutput("The objects equal") return TRUE; } } return true; }
For some reason when I drag a parameter from a Neutron Cube, that my tag is on, onto my tag I don't get the expected result of the dragged object and the tag object equalling each other. If I do the same procedure on a regular Cube (Ocube) I am able to properly catch that the objects are one and the same. The same behavior is encountered with other Capsules.
I'm not very familiar with Capsules, is there a specific way that Capsules need to be interacted with in order to achieve the desired results?
Any help would be greatly appreciated.
John Terenece
-
Hey @JohnTerenece ,
Thank you for reaching out to us. I am not 100% sure that I am understanding you correctly, but from what I get, you want to compare the
BaseList2D
representations of twoGraphNode
instances at the specific case of aNodes Mesh
.Why is this Happening?
We have something like this, where the Python Programming tag is your tag plugin:
Fig I.: ANodes Mesh
capsule with a Python tag on it.You now want to drag the
Nodes Mesh
node onto your tag node and compare the values. In general, it is not too surprising that these nodes do not always match, as allBaseList2D
representing aGraphNode
, e.g., theCube [Cube] BaseList2D
shown in the Attribute Manager for the selectedCube GraphNode
, are just dynamically allocated dummy nodes.These dummy nodes live parented to the
NimbusRef
owning them, in the case of 'normal' scene nodes that is the scene hook named 'scene nodes', in case of capsules it is the Object Manager object representing them:
Fig II.: The branches of theNodes Mesh
object from above. It holds twoBaseList2D
dummies, one for the graph container which is a node itself (the root) and one for the cube node. These are however much more volatile (BaseList2D
) nodes than a classic API scene element as they just wrap underlyingGraphNode
data and might be regenerated whenever Cinema 4D feels like it.What can I do?
The easiest way to deal with this is going to source of the data, as these
BaseList2D
are just smoke and mirrors. With BaseList2D::GetNimbusRef you can get the terribly named NimbusBaseInterface reference for aBaseList2D
. The term 'Nimbus' is just an echo of an internal code name and has otherwise no higher meaning. You can think of aNimbusBaseInterface
as the managing entity that ties an element in a classic API scene graph, aBaseList2D
, to a maxon API scene graph. So, aNimbusRef
ties a material nodes graph to aBaseMaterial
(that happens to be a node material), or as in this case, a scene nodes graph to an object in the Object Manager.NimbusBaseInterface::GetGraph
: Returns the Nodes API graph associated with this nimbus handler, i.e., the actual data.NimbusBaseInterface::BaseList2DToUuid and ::BaseList2DToUuid
: Allow you to identifyBaseList2D
wrapping nodes over reallocation boundaries, if they describe the "same" node.
As untested code:
BaseObject* const op; // Try to get the scene nodes nimbus handler for the object #op. const maxon::NimbusBaseRef handler = op->GetNimbusRef(maxon::neutron::NODESPACE()); if (!handler ) return false; // Get the graph that is wrapped by #op and #handler. const maxon::nodes::NodesGraphModelRef& graph = handler.GetGraph(); // Get an identifier which identifies #graph, I would suggest comparing capsules with this hash. const maxon::UniqueHash gid = graph.GetUniqueHashCode(); // I would avoid comparing graphs directly. You should also use ::Compare when you must compare the // graphs directly. const maxon::nodes::NodesGraphModelRef& other; if (graph.Compare(other) == maxon::COMPARERESULT::EQUAL) // Do something when #graph and #other are equal.
Please share a project zip when you require further assistance.
Cheers,
Ferdinand -
Thanks for the response.
I tried implementing the small example bit of code you gave into my own and I was unable to get it properly working. Heres the project file I'm working from that's just a trimmed down version of the sdk example (cinema4dsdk.zip).
Inside of the example.assets under modules I was able to successfully get it to compile but when I tried to do it inside of the cinema4dsdk module it would continuously give errors that I was missing various header files. When I followed the usual procedure to include the various header files requried I started getting errors inside of the header files themselves saying they were missing other files that they had included that I can't find on my computer.
This is the first time I'm encountering these kinds of errors so I'm not entirely sure if I'm missing something extremely simple.
And just to be clear with what I'm attempting to do is I want to be able to catch if a parameter dragged onto my tag is coming from the object that the tag is attached to. With an Ocube my code properly works, with a Neutron Cube my code will not tell me that the dragged parameter's object matches the tag's object.
John Terenece
-
Hello @JohnTerenece,
Thank you for the added information.
Find below my answer. I could not make the circumstance work that the dragged and the host node do not match but I did not spend too much time in dragging things left and right in the editor. Since these classic API nodes which wrap maxon API nodes are volatile, allocated on demand, a mismatch is not too suprising.
Cheers,
FerdinandNote that I modified the resources as well as
projectdefinition.txt
in your project. So copying over source files alone will not be enough. You will have to place this project in your solution and run the project tool on it (your project was missing the necessary framework dependencies to work with nodes in the first place).Result:
Project: example.tag.zip
Code:
For includes and the rest of the tag implementation see the project zip.Bool ExampleTag::Message(GeListNode* node, Int32 type, void* data) { // Returning true or false in a message stream (be it plugin, atom, GUI, or core messages) means: // // true: "I have received this event, it was intended for me, and I handled it." // false: "I have received this event, it was intended for me, but I could not handle it." // // When neither of these two apply because you for example have no clue what and for whom that // message is for, you must let the base impl. take over. Consuming messages that are not intended // for you without doing anything to handle them can cause significant problems. // When #node is null, not a tag, or not attached to a document/object, let the base impl. take // over. We could also return #false instead (which is what SUPER::Message will do), but we // certainly do not want to indicate that we consumed the message by returning true. BaseTag* const tag = static_cast<BaseTag*>(node); if (!tag || !tag->GetDocument() || !tag->GetObject()) return SUPER::Message(node, type, data); switch (type) { case MSG_DRAGANDDROP: { // First make sure that our host object holds a scene nodes graph. BaseObject* const host = tag->GetObject(); maxon::NimbusBaseRef hostHandler = host->GetNimbusRef(maxon::neutron::NODESPACE); if (!hostHandler || !hostHandler.GetGraph()) { ApplicationOutput( "Drag event for '@', host object '@' does not hold valid scene nodes graph.", tag->GetName(), host->GetName()); return false; } // Try to interpret #data as drag data and make sure a node parameter is being dragged. DragAndDrop* const dragData = static_cast<DragAndDrop*>(data); if (!dragData || dragData->type != DRAGTYPE_DESCID) return false; // Attempt to get the node(s) for which a parameter is being dragged for. DescPropertyDragData* const dragItems = static_cast<DescPropertyDragData*>(dragData->data); if (!dragItems || dragItems->arr->GetCount() < 1) return false; // This line: // // BaseObject *draggedObject = (BaseObject*)dndidarr->GetIndex(0); // // was quite dangerous, because unless I have overlooked something, you nowhere made sure that // index 0 is a BaseObject, used a C-style cast, and finally did not check if index 0 was // null or not. The combination of assuming the dragged node type and the re-interpretive // nature of C-style casts could have wrecked some havoc here. BaseList2D* const item = static_cast<BaseList2D*>(dragItems->arr->GetIndex(0)); // Jump ship when we could not make sense of the dragged node or it is not an object. The // latter is optional. if (!item || item->GetClassification() != Obase) return false; // Now get the scene nodes data for the dragged object. maxon::NimbusBaseRef itemHandler = item->GetNimbusRef(maxon::neutron::NODESPACE); if (!itemHandler || !itemHandler.GetGraph()) { ApplicationOutput( "Drag event for '@', drag data '@' does not hold valid scene nodes graph.", tag->GetName(), item->GetName()); return false; } // Now we finally do the comparison. We venture deeper into the maxon API and therefore need // an error handler, it is basically just an exit point for things that return a // #maxon::Result<T> and therefore are called with a #iferr_return. When these things throw // an error, we will exit though our #iferr_scope_handler since this ::Message function has // no error handling (it is of type #Bool and not #Result<Bool>). iferr_scope_handler { ApplicationOutput("maxon API error in drag handling: @", err); return false; }; // Compare UUIDs of wrapping nodes. maxon::Uuid u0 = hostHandler.BaseList2DToUuid(static_cast<BaseList2D*>(host)) iferr_return; maxon::Uuid u1 = itemHandler.BaseList2DToUuid(item) iferr_return; ApplicationOutput("UUIDs: a == b = @", u0.Compare(u1) == maxon::COMPARERESULT::EQUAL); const maxon::nodes::NodesGraphModelRef& g0 = hostHandler.GetGraph(); const maxon::nodes::NodesGraphModelRef& g1 = itemHandler.GetGraph(); // Compare graphs directly, as hinted at in my forum posting, this is not the most reliable // thing to do. ApplicationOutput("Graphs: a == b = @", g0.Compare(g1) == maxon::COMPARERESULT::EQUAL); // Compare graph hashes (I would pick this one). ApplicationOutput("Graph Hashes: a == b = @", g0.GetUniqueHashCode() == g1.GetUniqueHashCode()); // And finally the wrapping nodes, can be true but does not have to be. I could not get it to // fail in my short tests, but at some point Cinema 4D will reallocate these nodes. ApplicationOutput("Atoms: a == b = @", host == item); break; } default: break; } return SUPER::Message(node, type, data); }
-
Thanks for the detailed response.
I was able to successfully compile and run the code you provided but I'm running into some weirdness when trying to drag different parameters onto the tag.
From your code the only change I made was removing the one part you commented as optional.
BaseList2D* const item = static_cast<BaseList2D*>(dragItems->arr->GetIndex(0)); // Jump ship when we could not make sense of the dragged node or it is not an object. The // latter is optional. if (!item) return false; // Now get the scene nodes data for the dragged object. maxon::NimbusBaseRef itemHandler = item->GetNimbusRef(maxon::neutron::NODESPACE); if (!itemHandler || !itemHandler.GetGraph()) { ApplicationOutput( "Drag event for '@', drag data '@' does not hold valid scene nodes graph.", tag->GetName(), item->GetName()); return false; }
I'm not very familiar with interacting with Nodes, is there a reason that parameters from the two different tabs would trigger the if statement?
John Terenece
-
Hey @JohnTerenece,
Your question is quite ambiguous, let me unfold some things first:
- It looks like you have a mesh primitive capsule for a cube in your scene. On it, you have an
ExampleTag
. The capsule inlines theBaseList2D
representing theCube [Geometry]
node, just as it is inlining its two tags Phong and Example Tag. - You now drag a parameter from the capsule
BaseObject
, e.g., itsP.X
parameter, and are wondering why it behaves differently than theSegments.X
parameter from the inlinedBaseList2D
representation of theCube [Geometry]
node.
The answer to this is:
- A
GraphNode
and a(Nodes)GraphModelRef
are not the same thing, the latter is the graph while the former is a node in it. TheCube [Geometry]
is a node within a graph, not the graph itself. In the case of a geometry operator capsule this is hidden away from the user as you are not supposed to edit these capsules, but it still applies. - Not every
BaseList2D
representation of an entity inside a graph has aNimbusRef
. It is only theBaseList2D
which represents the graph itself, e.g., theBaseObject
in case of a capsule graph, which holds it. Which is also why I posted the screenshot from the Active Object plugin in my prior answer:
The representations of all entities in a graph are in a "Nodes" branching relation to the entity which holds theNimbusRef
for their graph. - You currently can associate a
BaseList2D
wrapper for a node in a graph with itsNodeGraphModelRef
(i.e., "graph") but you cannot associate it with itsGraphNode
, i.e., the actual node (in the public API).
Finde below my code example.
Cheers,
FerdinandResult:
I compare here a
Mesh Primitive Group
capsule andMesh Primitive
capsule which is what you were using to clarify the relation of things. In both cases the "Cube" is just a node which lives inside the capsule. It is only that theMesh Primitive
capsule inlines its geometry node because other than theMesh Primitive Group
it is not editable.Code:
Bool ExampleTag::Message(GeListNode* node, Int32 type, void* data) { iferr_scope_handler { return false; }; // Returning true or false in a message stream (be it plugin, atom, GUI, or core messages) means: // // true: "I have received this event, it was intended for me, and I handled it." // false: "I have received this event, it was intended for me, but I could not handle it." // // When neither of these two apply because you for example have no clue what and for whom that // message is for, you must let the base impl. take over. Consuming messages that are not intended // for you without doing anything to handle them can cause significant problems. // When #node is null, not a tag, or not attached to a document/object, let the base impl. take // over. We could also return #false instead (which is what SUPER::Message will do), but we // certainly do not want to indicate that we consumed the message by returning true. BaseTag* const tag = static_cast<BaseTag*>(node); if (!tag || !tag->GetDocument() || !tag->GetObject()) return SUPER::Message(node, type, data); switch (type) { case MSG_DRAGANDDROP: { // First make sure that our host object holds a scene nodes graph. BaseObject* const host = tag->GetObject(); maxon::NimbusBaseRef hostHandler = host->GetNimbusRef(maxon::neutron::NODESPACE); if (!hostHandler || !hostHandler.GetGraph()) { ApplicationOutput( "Drag event for '@', host object '@' does not hold valid scene nodes graph.", tag->GetName(), host->GetName()); return false; } // Try to interpret #data as drag data and make sure a node parameter is being dragged. DragAndDrop* const dragData = static_cast<DragAndDrop*>(data); if (!dragData || dragData->type != DRAGTYPE_DESCID) return false; // Attempt to get the node(s) for which a parameter is being dragged for. DescPropertyDragData* const dragItems = static_cast<DescPropertyDragData*>(dragData->data); if (!dragItems || dragItems->arr->GetCount() < 1) return false; // This line: // // BaseObject *draggedObject = (BaseObject*)dndidarr->GetIndex(0); // // was quite dangerous, because unless I have overlooked something, you nowhere made sure that // index 0 is a BaseObject, used a C-style cast, and finally did not check if index 0 was // null or not. The combination of assuming the dragged node type and the re-interpretive // nature of C-style casts could have wrecked some havoc here. // --- snip ---------------------------------------------------------------------------------- BaseList2D* const item = static_cast<BaseList2D*>(dragItems->arr->GetIndex(0)); if (!item) return false; // Try to get the scene nodes data for the dragged object. maxon::nodes::NodesGraphModelRef graph; // The dragged item is an entity which holds a graph relation directly, i.e., it represents // a graph itself, not a node in it. maxon::NimbusBaseRef itemHandler = item->GetNimbusRef(maxon::neutron::NODESPACE); if (itemHandler && itemHandler.GetGraph()) graph = itemHandler.GetGraph(); // Try to unpack a graph relation for a BaseList2D wrapping a node inside a graph. if (!graph && item->GetMain() && item->GetMain()->GetClassification() == Obase) { // Get the entity hosting the dragged item and check if it holds a scene nodes graph. const BaseList2D* const itemHost = item->GetMain(); itemHandler = itemHost->GetNimbusRef(maxon::neutron::NODESPACE); if (itemHandler && itemHandler.GetGraph()) graph = itemHandler.GetGraph(); } if (!graph) { ApplicationOutput("Could not find scene nodes relation for: @", item); return false; } // To explain a bit what we just did above: When we have the classic API document scene // graph shown below (I would recommend exploring scene graphs with the Active Object // plugin contained in the C++ SDK on your own): // // Fig. I: // Document_0 [BaseDocument] // ... // Object Branch // My_Nimbus_Host [BaseObject] <---> My_Graph // Nodes Branch // Node_0_Wrapper [BaseList2D] // Node_1_Wrapper [BaseList2D] // ... // Tag Branch // Example_Tag [BaseTag] // ... // .. // // And this nodes graph managed by My_Nimbus_Host: // // Fig. II: // My_Graph [NodeGraphModelInterface] // Root [GraphNode] // Node_0 [GraphNode] // Node_1 [GraphNode] // // Then the dragged #item could be here #Node_0_Wrapper, i.e., a user is dragging the // representation of #Node_0 or a parameter in it. A NimbusRef, the glue which ties // an element in a classic API scene graph (i.e., Fig. I) to a nodes graph (i.e., Fig. II), // only exists between the thing which represents the graph itself (a BaseObject in case of // a capsule, a BaseMaterial in case of node materials, and a SceneHook in case of the main // scene nodes graph tied to a document) and the graph. // // The BaseList2D wrappers which represent nodes within a graph do not hold that NimbusRef // since they are just smoke and mirrors. // // We can establish the graph they are associated with by traversing the classic API "Nodes" // branching relation, e.g., go from #Node_0_Wrapper to #My_Nimbus_Host, and there get the // NimbusRef to get the graph. What we cannot do (in the public API), is associate the // #Node_0_Wrapper with #Node_0. You could of course do pattern matching over node attribute // and BaseList2D parameter names, but there is no direct way. // --- snip ---------------------------------------------------------------------------------- iferr_scope_handler { ApplicationOutput("maxon API error in drag handling: @", err); return false; }; // Compare UUIDs of wrapping nodes. maxon::Uuid u0 = hostHandler.BaseList2DToUuid(static_cast<BaseList2D*>(host)) iferr_return; maxon::Uuid u1 = itemHandler.BaseList2DToUuid(item) iferr_return; ApplicationOutput("UUIDs: a == b = @", u0.Compare(u1) == maxon::COMPARERESULT::EQUAL); const maxon::nodes::NodesGraphModelRef& g0 = hostHandler.GetGraph(); const maxon::nodes::NodesGraphModelRef& g1 = itemHandler.GetGraph(); // Compare graphs directly, as hinted at in my forum posting, this is not the most reliable // thing to do. ApplicationOutput("Graphs: a == b = @", g0.Compare(g1) == maxon::COMPARERESULT::EQUAL); // Compare graphs (I would pick this one). ApplicationOutput("Graph Hashes: a == b = @", g0.GetUniqueHashCode() == g1.GetUniqueHashCode()); // And finally the wrapping nodes, can be true but does not have to be. I could not get it to // fail in my short tests, but at some point Cinema 4D will reallocate these nodes. ApplicationOutput("Atoms: a == b = @", host == item); return true; } default: break; } return SUPER::Message(node, type, data); }
- It looks like you have a mesh primitive capsule for a cube in your scene. On it, you have an
-
Thanks for the response, it's all working properly now.
John Terenece