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):
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 function GeListNode::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, the Obase
branch holds for example the GeListHead
(a special type of GeListNode
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).
// 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;
// Realizes your asset manager just as before but this time it internalizes its asset data.
//
// Since a scene hook is a node and part of the scene graph, it can have children and branches.
// We therefore are going to establish an Tassetnode branch in the scene hook.
class AssetAccessManager : public SceneHookData
{
INSTANCEOF(AssetAccessManager, SceneHookData)
// The head of our custom asset branch.
private:
GeListHead* _assetHead;
public:
// Returns either an existing asset branch head for the AssetAccessManager node or creates a new one.
GeListHead* GetAssetHead()
{
iferr_scope_handler
{
return nullptr;
};
// We already established an asset head before.
if (_assetHead)
return _assetHead;
// Get the scene hook node for #this SceneHookData and try to find its asset branch.
const GeListNode* node = Get();
if (!node)
return nullptr;
Bool result = node->GetBranchInfo(
[_assetHead](const BranchInfo& info) -> maxon::Result<maxon::Bool>
{
// Check if the branch is an asset branch.
if (info.id != Tassetnode)
return false; // Continue searching/iterating.
// We found the asset branch, we can stop iterating.
_assetHead = info.head;
return true;
},
GETBRANCHINFO::NONE) iferr_return;
if (_assetHead)
return _assetHead;
// Create a new branch head for this asset manager's asset branch.
_assetHead = GeListHead::Alloc();
if (!_assetHead)
return nullptr;
// Attach the branch to the scene hook.
_assetHead->SetParent(node);
return _assetHead;
}
// Implements the branch info for your node, so that Cinema 4D knows about the asset branch.
// It is important to implement this, without it Cinema 4D will crash.
Int32 FlowTag::GetBranchInfo(GeListNode* node, BranchInfo* info, Int32 max, GETBRANCHINFO flags)
{
GeListHead* assetHead = GetAssetHead();
if (!assetHead)
return 0;
info[0].head = assetHead; // The head for the new branch.
info[0].id = Tassetnode; // The ID of the branch. This is usually the ID of the
// types of nodes to be found in the branch, e.g., Obase.
info[0].name = "myAssetData"_s; // A label for humans for the branch, make sure it is
// reasonably unique, there might be bad code out there
// which tries to find branches by name.
info[0].flags = BRANCHINFOFLAGS::NONE; // The flags for this branch, see docs for details.
return 1 // The number of branches we have defined.
}
// Realizes a custom function to add assets to the asset manager and with that to the scene.
BaseList2D* AddAsset(const Url& url)
{
// Instantiate an asset node, it fully encapsulates the loading and storage of asset data.
BaseList2D* asset = BaseList2D::Alloc(Tassetnode);
if (!asset)
return nullptr;
// Load the asset data from the URL, this is done via a custom message, just as discussed in
// example from the old thread.
asset->Message(MSG_LOAD_ASSET, (void*)&url);
// Add the asset to the asset branch. We realize here just a plain list, but we could also have
// complex hierarchies, or assets which have branches themselves.
GeListHead* assetHead = GetAssetHead();
if (!assetHead)
return nullptr;
asset->InsertUnderLast(assetHead);
return asset;
}
};
// Realizes the asset node.
//
// We realize here a plain NodeData, the base type of ObjectData, TagData, SceneHookData, etc. We
// could also realize a specialized node, but custom branches are one of the few cases where it can
// make sense to realize a plain node. But you could also realize a specialized node, e.g.,
// MyAssetObjectData. You can also store existing node types in your branches, e.g., it is totally
// valid to implement your own Obase (i.e., object) branch. Might be better in your asset case, I do
// not know. In that case you could only serialize the data an object does serialize, points,
// polygons, tags, tracks, etc., but would not have to implement your own node. Pulling up an asset
// would then also not work via a message but via a custom function, e.g., static BaseObject*
// LoadAsset(const Url& url).
//
// See also:
// SDK/plugins/example.main/source/object/objectdata_hyperfile.cpp
class MyAssetData : public NodeData
{
INSTANCEOF(AssetAccessManager, SceneHookData)
private:
// The asset data, this is just a mock, you would have to implement this. This example is extra
// stupid, because something like an Int32 is easily stored in the data container of a node. An
// option which always exists, and while slightly less performant than customizing reading and
// writing the node, it is also much easier to implement.
Int32 _assetData;
// Loads the asset data from a URL.
Bool LoadAsset(const Url& url)
{
_assetData = 42;
return true;
}
public:
// Handles messages for the asset node.
Bool Message(GeListNode* node, Int32 type, void* data)
{
switch (type)
{
case MSG_LOAD_ASSET:
{
// Load the asset data from the URL.
Url* url = reinterpret_cast<Url*>(data);
if (!url)
return false;
return LoadAsset(*url);
}
break;
}
return true;
}
// Serializes the asset node. What I am showing here should only be done when we have to serialize
// truly complex data, as for example a texture with meta data. For that specific case one could
// ask how sensible it is to blow up scene sizes by internalizing textures in the scene graph.
// Then one could end up with a compromise, where one stores only information in the scene about
// where a localized texture has been stored, i.e., we just reinvented texture files :D.
// When you implement serialization, you must always implement Read, Write, and CopyTo. See the
// NodeData manual and SDK for details, I am too lazy to even mock-up all of them here, I am only
// going to demo Read/Write.
// Writes all data of this node to the scene file that is not already stored in the node's data
// container.
Bool Write(GeListNode* node, HyperFile* hf)
{
if (!hf)
return false;
// Write the asset data to the scene file.
if (hf->WriteInt32(_assetData) == 0)
return false;
return true;
}
// Reads all data of this node from the scene file that is not already stored in the node's data
// container.
Bool Read(GeListNode* node, HyperFile* hf, Int32 level)
{
if (!hf)
return false;
// Read the asset data from the scene file.
if (hf->ReadInt32(&_assetData) == 0)
return false;
return true;
}
}
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
Cheers,
Ferdinand