Ctr+drag copy vs internal copy in CopyTo function in C++
-
hello,
i want to distinguish between c4d's internal use of "CopyTo" and when a user drags the object (in this case it's a tag), also is there a message specifically called when user drag copy an object? -
Hey @aghiad322,
Thank you for reaching out to us. Your question is very hard to answer like this. Please familiarize yourself with out support procedures and what makes a good technical question. I have effectively no idea what you are doing.
When you are talking about a NodeData::CopyTo call, its context is expressed in the flags, including if this was a clipboard event. I would assume that a drag and drop operation, especially for tags, does not entail the tag being copied, but actually rather be moved (i.e., being removed and then re-inserted). If it is being copied, or at least being copied in your case, I do not see how this should then be distinguished from an 'internal' call, since they are all 'internal'.
I would just look at the flags and see if they change in a manner that is helpful for your task. Although what you are trying to do does not sound like a good idea, but I more or less know nothing about what you are doing and am guessing mostly.
Cheers,
Ferdinand -
Hi @ferdinand , thanks for the reply, my main problem is that "init" is not being called on copying, what im trying to do is that i have a scenehook and it has an array member variable pointing to all tags in the scene with this tag type, and with same parameter (grouping tags based on id parameter), and i need to keep this array in sync with the scene tags.
Thanks and regards
-
Hey @aghiad322,
Thank you for your reply.
NodeData::Init
is always being called when a node is initialized, explicitly also when a node is being copied (that is what the argumentisCloneInit
is for). But scene hooks only very rarely get copied since they are effectively singletons.All I can tell you from your description, is that what you are doing in general - "have a scenehook and it has an array member variable pointing to all tags in the scene with this tag type", sounds like a bad idea. It sounds like you have a
BaseArray<BaseTag*>
- or even worse, astd::vector<BaseTag*>
- on your scene hook as a field; and this is just asking for crashes. You must use either smart pointers (BaseArray<WeakRawPointer<const BaseTag>>
) or BaseLink's. But even then this is not such a great idea, your scene hook should gather its inputs each time it needs them. And when you end up constantly scanning the scene, you are doing something wrong design-wise. There are niche scenarios where this cannot be avoided, but I cannot really evaluate that if I have absolutely no idea what you are doing.As already lined out, we cannot help you if you do not show us what you are doing. You are likely talking about your tags being copied, but you do not show us if you implement the tag yourself, what your code is, and what you are trying to achieve in general. How are we supposed to help you, when you do not tell us/show us what you are doing?
Cheers,
Ferdinand -
Hi @ferdinand,
Thanks for your reply!
Currently, I can’t use isCloneInit because I’m still developing on the Cinema 4D 2026 SDK. I do plan to migrate to the 2026 version later.
For context — my plugin is essentially a grouping system based on an ID parameter, similar in concept to how:
The Simulation system tracks objects containing simulation tags, or
The Arnold tag groups objects into “trace sets” based on a string ID.
I’m not scanning the scene constantly for updates. Instead, each tag updates itself through a SceneHook when needed, using the following function:
void NexusRegistry::UpdateGroupFromTag(BaseDocument* doc, BaseTag* tag) { if (!tag) return; BaseObject* obj = tag->GetObject(); if (!obj) return; BaseContainer* tagContainer = tag->GetDataInstance(); // Read new values from tag String id = tagContainer->GetString(ID); Int32 hashedID = HashID(id); Bool includeChildren = tagContainer->GetBool(CHILDREN); // Handle old ID if changed Int32 oldIdHash = tagContainer->GetInt32(NEXUS_TAG_PREV_ID, NOTOK); if (oldIdHash != NOTOK && oldIdHash != hashedID) { auto it = groups.find(oldIdHash); if (it != groups.end()) { auto& members = it->second.members; members.erase(std::remove_if(members.begin(), members.end(), [obj](const NexusMember& m) { return m.object == obj; }), members.end()); it->second.dirtyLevel++; if ((Int32)members.size() == 0) groups.erase(it); } } // Update new group NexusGroup& group = groups[hashedID]; // Remove duplicates of this object first group.members.erase(std::remove_if(group.members.begin(), group.members.end(), [obj](const NexusMember& m) { return m.object == obj; }), group.members.end()); group.members.push_back({ obj, includeChildren }); ((Nexus*)tag)->UpdateInfo(doc, tag); // Store current ID for next update tagContainer->SetInt32(NEXUS_TAG_PREV_ID, hashedID); }
To circumvent the tag copy issue, I’m currently deferring registration:
During CopyTo, tags are added to a pending registration list.
Then, in the SceneHook Execute() method, I check for tags that have been attached to a document and process them there.
Here’s that part:
void NexusRegistry::ProcessPendingTags(BaseDocument* doc) { if (pendingRegistration.IsEmpty()) return; Int32 i = 0; while (i < pendingRegistration.GetCount()) { BaseTag* tag = pendingRegistration[i]; BaseObject* op = tag->GetObject(); if (op) { UpdateGroupFromTag(doc, tag); maxon::ResultRef eraseresult = pendingRegistration.Erase(i, 1); } else { i++; } } } EXECUTIONRESULT NexusRegistry::Execute(BaseSceneHook* node, BaseDocument* doc, BaseThread* bt, Int32 priority, EXECUTIONFLAGS flags) { ProcessPendingTags(doc); return EXECUTIONRESULT::OK; }
Here’s the SceneHook class structure for reference:
struct NexusMember { BaseObject* object = nullptr; // weak ref, don’t own it Bool isChild = false; // "include children" flag }; struct NexusGroup { Vector color; std::vector<String> links; std::vector<NexusMember> members; Int32 dirtyLevel = 0; }; class NexusRegistry : public SceneHookData { public: static NexusRegistry* Get(BaseDocument* doc); static NodeData* Alloc(); virtual Bool Message(GeListNode* node, Int32 type, void* data); virtual EXECUTIONRESULT Execute(BaseSceneHook* node, BaseDocument* doc, BaseThread* bt, Int32 priority, EXECUTIONFLAGS flags); std::unordered_map<Int32, NexusGroup> groups; maxon::BaseArray<BaseTag*> pendingRegistration; void ProcessPendingTags(BaseDocument* doc); void RebuildRegistry(BaseDocument* doc); void UpdateGroupFromTag(BaseDocument* doc, BaseTag* tag); void RemoveObjectFromGroups(BaseObject* obj); void RemoveTagFromGroup(BaseTag* tag); const NexusGroup* GetGroup(Int32 hash) const; };
The main purpose of this system is to allow other plugins (e.g., object plugins) to:
Reference tags that share the same ID,
Access their data via the registry, and
Rebuild themselves when the related group’s dirtyLevel changes.
Thanks for your time, and sorry if this looks a bit rough, I just wanted to show the current design.
If you have any design suggestions or best practices for handling deferred tag registration or object cloning across documents, I’d really appreciate your insights!