How to store "cached" data in the document from the scenehook?
-
Hello @Havremunken,
no, there is no code example for what you are trying to do. There is the ActiveObject C++ Example, which is supposed to demonstrate branching but does IMHO a terrible job at it. When you want to look at some resources, I would recommend these (although none of them will cover your subject exactly):
- unpacking-animation-data-wrapped-by-the-motion-system-nla: Here I explained how our own NLA system stores data in branches of a scene.
- GeListHead Manual: Covers some technical details.
- mxutils.RecurseGraph: Python only function, but its documentation and playing around with it could you help to understand what branching is in Cinema 4D.
What is Branching
Branching is how Cinema 4D realizes arbitrary entity relationships, i.e., a scene graph. By default, it seems a bit like Cinema 4D only implements hierarchical relationships via
GeListNode
, but that is not true, as there is also the functionGeListNode::GetBranchInfo
. So, while a node can have a parent, a list of children, and up to two siblings, it can also have a list of other relations which are explicitly not hierarchical.A
BaseDocument
is for example also a node, its previous and next siblings realize the list of loaded documents. The branches of a document realize all its content, theObase
branch holds for example theGeListHead
(a special type ofGeListNode
for the root of a branch) to which all top level objects of the scene are parented (which then hold their local hierarchies). The 'trick' is then that every node in a scene has such branches, an object has branches for its tags, tracks, and more; a material has branches for its shaders, tracks, and more, and so on.Please see the links I have posted above for details.
How Do I Implement Branching?
Generally, at least without getting hacky, you cannot implement branches for nodes that you do not own. So, you can not add a foo branch to documents or objects in general, you can only add a foo branch to the specific
MyFooObjectData
plugin you implement (or any other node type you implement). Since, it worked apparently quite well last time, I will just write some mock code of what you are trying to do (very high level and obviously untested and uncompiled).--- removed ---
I hope this gets you going. I cannot stress enough that this is a sktech.This will quite likely not compile, but it demonstrates the core methods and principles. Feel free to come back with concrete code yours when you need help.
But I currently do not have the time to write a full example for this from scratch.
Cheeers,
Ferdinand -
Hi Ferdinand, and thanks for all the fish!
No worries about the psuedo code, this is precisely what I needed to understand what I need to implement in order to store my data with the document. I am going to try to write this into my project, and will of course return soon if something happens that I can't figure out.
Again, many thanks!
-
Hi Ferdinand,
I got sidetracked from implementing this, but back at it now. I think I have it figured out in my head, and my plan is basically in my asset Write method to write an int saying how many "cache items" I have, and then a pair of strings with the URL and the content for each of them.
But one question I just wanted to clarify - in your code above, in the Scene Hook part of it, you have the AddAsset method, and in it you allocate a new asset object... I presume? You write:
BaseList2D* asset = BaseList2D::Alloc(Tassetnode);
Am I correct in assuming that this should have been something like
BaseList2D* asset = BaseList2D::Alloc(MyAssetData);
Since I am supposed to allocate a new asset object, and not a new... int?
Asking just because I couldn't see the connection where MyAssetData was actually connected to the Tassetnode. -
Hey @Havremunken,
I am going to answer in a code block. As writing text without code is here probably pretty pointless and confusing.
using namespace cinema; // This line instantiates a node of type #Tassetnode. BaseList2D* asset = BaseList2D::Alloc(Tassetnode); // It is conceptually very similar to this line where we instantiate a cube object. BaseObject* const op = BaseObject::Alloc(Ocube); // #Ocube and #Tassetnode are type symbols or in other words the plugin IDs with which these nodes // have been registered. // Plugin ID for the generic asset node and at the same time its branch ID. T is not only the prefix // for tag types but also generic node types, e.g., Tbasedocument, Tpluginlayer, Tgelistnode, etc. static const Int32 Tassetnode = 1030001; // Register the plugin hook, here a genric node which we instantiate above. if (!RegisterNodePlugin(Tassetnode, "My Generic Node Type"_s, PLUGINFLAG_SMALLNODE, MyAssetData::Alloc, icon, 0, nullptr)) // Do something on failure ... // A hook is the driving interface, you could also say 'controller' in modern terms, of a node in a // scene. So, one could do this comparison: // // Data Layer (user facing) Logic Layer (system facing) // // BaseObject (Ocube) ---- GeListNode::GetNodeData()ยน ----> CubeObjectData // BaseList2D (Tassetnode) <-------- NodeData::Get()ยฒ ---------- MyAssetData // // I.e., you have a frontend/data layer that is the scene graph of Cinema 4D and it contains the // tangible content of a scene, but it effectively only holds the data of the scene (not 100% true // but close enough). The logic is separated into its own layer, the plugin hooks third party and // Maxon developers write. You can switch between these two layers with the methods GetNodeData(1) // and Get(2) of the respective interfaces. // BaseObject* op; // An instance of Ocube // Get the plugin hook for an Ocube instance. We can do the access in different levels of verboseness/ // concreteness. We generally do not advise accessing hooks for types you do not own. The last line // is the most concrete form and impossible in the public API as the cube object implementation is // not public (and is also not named like that as primitives are abstracted). NodeData* const plainHook = op->GetNodeData<NodeData>(); ObjectData* const objectHook = op->GetNodeData<ObjectData>(); CubeObjectData* const concreteHook = op->GetNodeData<CubeObjectData>(); // The way back from a hook is then this (usually called inside of methods of the hook or passed in // as an argument to hook methods). // Get the node that represents the hook. It requires implementation knowledge to know which hook can // be cast to what, technically that can be abstracted with information stored in the hook, but there // is no builtin automated way. GeListNode* const node = hook->Get(); BaseObject* const cube = static_cast<BaseObject*>(node);
I hope this sheds some light on how scene elements and their plugin hooks correspond.
Cheers,
Ferdinand -
Hi Ferdinand,
Thank you again! I see I misunderstood the point of the alloc parameter, as I was also missing the connection between the scene hook (owner of the branch I guess) and the asset nodes making up the branch. I love the explanation of the different layers as an added bonus.
-
This is perhaps not a big deal, but while implementing this, I met some resistance from Visual Studio while overriding GetBranchInfo in my scene hook. It did not match the signature from your example. Ok, so detective time - my scene hook inherits from SceneHookData, which has no GetBranchInfo of its own, this comes from it inheriting from NodeData, and this class does have a GetBranchInfo. However, the signature does not match entirely. I am on 2024 still, just in case this changed for 2025. This is the signature:
virtual maxon::Result<Bool> GetBranchInfo(const GeListNode* node, const maxon::ValueReceiver<const BranchInfo&>& info, GETBRANCHINFO flags) const;
Ok, sligtly different, but... It is supposed to return a bool? Ok, that is a bit confusing, in your example you returned the number of elements we filled in. Ok, let me look at the docs in the source file (c4d_nodedata.h):
/// @return The number of BranchInfo elements filled in the @formatParam{info} array.
Ok, that is clear as mud.
So I am not supposed to return a number, then, but just true if I actually put something in there? How does it learn the number of items, or doesn't it need to?
A minor point is that in your code example (in your first post) you write it as FlowTag::GetBranchInfo - I guess I can safely assume this was copied from another source example where a FlowTag class was implemented, so the name "FlowTag" has no meaning in this case?
From your example I get the sense that the MyAssetData is created in a way where one instance of the class represents one piece of data. In my case, my variant of this asset node would contain one URL and one content string. Then I use the list starting with the AssetHead to store each url+content combo I have in my cache, is this correct? So my version of AddAsset would take these two as params?
I am also trying to understand the choice to "feed" the asset object by sending it a message, instead of for instance calling Load directly. Is this because when you
BaseList2D::Alloc(Tassetnode);
you don't actually get direct access to the instance of the MyAssetData class itself, so we're in the Data Layer instead of in the Logic Layer, and have to use this mechanism?Hopefully final question in this round (at least I'm not one of the many spammers that keep trying their luck on the forum, huh?): When I create an object that causes some data to be cached using this system, and save the C4D file, then later reopen it - I understand that C4D reads this data from the file, and I imagine that the searching lambda in your GetAssetHead is what locates this again the first time it is called (when _assetHead is not already set). Fairly soon after opening the file, C4D will call GetVirtualObjects on my main plugin, and it will ask the scene hook for the data - am I correct in assuming that this would be when the scene hook calls GetAssetHead(), reads all the MyAssetData (or whatever I end up calling them) nodes, and finally has his cache so he can return data to the generator plugin?
I hope these questions are not too annoying, I am just trying to understand the code rather than just copy & paste it (or feed it to an AI).
Thanks!
-
Hey @Havremunken,
first of all, questions are never annoying to us. You are right, I incorrectly used there an outdated signature of
NodeData::GetBranchInfo
. I removed my pseudo code example from above, as I decided today, that I will just write a code example for branching for the SDK (the Asset Container thing from below which has the custom branchnet.maxonexample.branch.assetcontainer
with a cube and a sphere object stored in it).Just posting here so that you do not think we overlooked you, I will post the example on Monday, as I still have to make sure the example works properly in all aspects. For now I will sail of into the weekend
Cheers,
Ferdinand -
Thank you so much, Ferdinand, I look forward to that! And enjoy the well-deserved weekend in the mean time!
-
Hey @Havremunken,
I have posted the code example which will be shipped with an upcoming C++ SDK here. The example does not do 100% what you are trying to do, the node type implementing the custom branch is for example an object and not a scene hook, as I had to keep common applicability a bit in mind.
But it realizes a node which inernalizes other scene elements in a custom branch of itself, and should get you at least going. When you still need help with your specific case, please ask questions in this thread. When you have generic branching questions tied to the example, please use the other thread.
Cheers,
Ferdinand -
"Thank you" seems poor and inadequate for that fantastic example, but I don't have the English vocabulary to go beyond that!
I have read through the example once, and I think I understand it. Since this has been a real Monday (tm), I'm going to let this simmer in the brain for a day or two, and then use the techniques you demonstrate to add GetBranchInfo etc. to my scene hook. Both the example and the video were great at explaining the steps needed to get "extra" information into the document, and are much appreciated!
So while it does not express the full extent of my gratitude, once again - thank you!