Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Ctr+drag copy vs internal copy in CopyTo function in C++

    Cinema 4D SDK
    2025 c++ windows macos
    2
    6
    49
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • aghiad322A
      aghiad322
      last edited by ferdinand

      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?

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @aghiad322
        last edited by ferdinand

        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

        MAXON SDK Specialist
        developers.maxon.net

        aghiad322A 1 Reply Last reply Reply Quote 0
        • aghiad322A
          aghiad322 @ferdinand
          last edited by aghiad322

          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

          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @aghiad322
            last edited by ferdinand

            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 argument isCloneInit 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, a std::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

            MAXON SDK Specialist
            developers.maxon.net

            aghiad322A 1 Reply Last reply Reply Quote 0
            • aghiad322A
              aghiad322 @ferdinand
              last edited by 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!

              ferdinandF 1 Reply Last reply Reply Quote 0
              • ferdinandF ferdinand moved this topic from General Talk
              • ferdinandF
                ferdinand @aghiad322
                last edited by ferdinand

                Hey @aghiad322,

                Thank you for your code. It is still very unclear to me what you are doing on a higher more abstract level, and on a concrete level, where exactly you want to detect something. Find below your commented code and at the end a few shots into the dark from me regarding what you are trying to do.

                I also just saw now that you posted in the wrong forum. I moved your topic and marked it as C++ and 2025 since you said you are not using the 2026 SDK.

                I hope this help and cheers,
                Ferdinand

                // I wrote this blind without compiling it. So ,there might be some syntax issues in the code I wrote,
                // but it should still illustrate the concepts well enough.
                
                #include "maxon/weakrawptr.h"
                
                using namespace cinema;
                using namespace maxon;
                
                struct NexusMember
                {
                    // While we could consider a pointer somewhat conceptually a weak reference, it is factually 
                    // not one, as the pointed object being deleted does not invalidate the pointer. This can lead 
                    // to dangling pointers and access violation crashes.
                    BaseObject* object = nullptr; // weak ref, don’t own it
                    Bool isChild = false;         // "include children" flag
                
                    // This is better.
                    WeakRawPtr<BaseTag> _tagWeakPtr;
                
                    // Gets the tag pointed to by this member, or nullptr if it has been deleted.
                    BaseTag* GetTag() const
                    {
                        return _tagWeakPtr.Get();
                    }
                
                    // Sets the tag for this member.
                    void Set(const BaseTag* tag)
                    {
                        _tagWeakPtr = _tagWeakPtr.Set(tag);
                    }
                };
                
                struct NexusGroup
                {
                    Vector color;
                
                    // No, do not use the std library. Cinema 4D modules are by default compiled without exception 
                    // handling for performance reasons, and the standard library uses exceptions for error handling,
                    // making its behavior undefined in such builds. std::vector::push_back() can throw 
                    // std::bad_alloc, for example.
                
                    std::vector<String> links;
                    std::vector<NexusMember> members;
                    Int32 dirtyLevel = 0;
                
                    // MUST be:
                    BaseArray<String> links;
                    BaseArray<NexusMember> members;
                };
                
                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;
                };
                
                // -------------------------------------------------------------------------------------------------
                
                void NexusRegistry::UpdateGroupFromTag(BaseDocument* doc, BaseTag* tag)
                {
                    if (!tag) return;
                    BaseObject* obj = tag->GetObject();
                    if (!obj) return;
                    BaseContainer* tagContainer = tag->GetDataInstance();
                
                    String id = tagContainer->GetString(ID);
                
                    // I do not know of which nature this HashID is, but our API has multiple hash functions. 
                    Int32 hashedID = HashID(id);
                    
                    // There is for once StringTemplate::GetHashCode(). I.e., you could do this:
                    const HashInt = id.GetHashCode();
                
                    // But all scene elements already have a marker on them which uniquely identifies them (if that
                    // is what you are after).
                
                    // Uniquely identifies over its time of creation and the machine it was created on. I.e., this
                    // value is persistent across sessions and unique across machines.
                    const GeMarker uuid = tag->GetMarker();
                
                    Bool includeChildren = tagContainer->GetBool(CHILDREN);
                
                    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;
                            // std::vector::erase is not noexcept, this can crash Cinema 4D, unless you compile your
                            // module with exception handling enabled (which we do not recommend for performance
                            // reasons). I am not going to repeat this comment in similar places below.
                            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);
                }
                
                // -------------------------------------------------------------------------------------------------
                
                
                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);
                
                            // This is not how our error system works. Functions of type Result<T> are our exception
                            // handling equivalent. You need iferr statements to catch and/or propagate errors. See
                            // code below.
                            maxon::ResultRef eraseresult = pendingRegistration.Erase(i, 1);
                        }
                        else
                        {
                            i++;
                        }
                    }
                }
                
                // -------------------------------------------------------------------------------------------------
                
                // See https://developers.maxon.net/docs/cpp/2026_0_0/page_maxonapi_error_overview.html for a more in detail
                // explanation of our error handling system.
                
                // So we have some class with a field whose type has a function of return type Result<T>, e.g., your
                // NexusRegistry. We have now three ways to handle errors when calling such functions:
                
                // Ignoring errors (not recommended):
                void NexusRegistry::AddItem(const String name)
                {
                    links.Append(name) iferr_ignore("I do not care about errors here."); // Append is of type Result<void>
                }
                
                // Handling errors locally, i.e., within a function that itself is not of type Result<T>.
                Bool NexusRegistry::RemoveItem(const String name)
                {
                    // The scope handler for this function so that we can terminate errors when the are thrown.
                    iferr_scope_handler
                    {
                        // Optional, print some debug output to the console, #err is the error object.
                        DiagnosticOutput("Error in @: @", MAXON_FUNCTIONNAME, err);
                
                        // We just return false to indicate failure. If we would have to do cleanup/unwinding, we
                        // would do it here.
                        return false;
                    };
                
                    const Int32 i = links.FindIndex(name);
                
                    // We call our Result<T> function and propagate any error to the scope handler if an error 
                    // occurs. The iferr_return keyword basically unpacks a Result<T> into its T value, or jumps 
                    // to the error handler in the current or higher scope and propagates the error.
                    const String item = links.Erase(i) iferr_return;
                    
                    return true;
                }
                
                // And the same thing in green but we propagate errors further up the call chain, i.e., our function
                // is itself of type Result<T>. It now also does not make to much sense to return a Bool, so our type
                // is now Result<void>.
                Result<void> NexusRegistry::RemoveItem(const String name)
                {
                    // Here we just use the default handler which will just return the #err object to the caller.
                    iferr_scope;
                
                    const Int32 i = links.FindIndex(name);
                    const String item = links.Erase(i) iferr_return;
                
                    return OK; // Result<void> functions return OK on success.
                }
                
                // -------------------------------------------------------------------------------------------------
                
                
                EXECUTIONRESULT NexusRegistry::Execute(BaseSceneHook* node, BaseDocument* doc,
                                                       BaseThread* bt, Int32 priority, EXECUTIONFLAGS flags)
                {
                    ProcessPendingTags(doc);
                    return EXECUTIONRESULT::OK;
                }
                
                
                // -------------------------------------------------------------------------------------------------
                
                // So, all in all, this does not shed to much light on what you are doing. The main questions is you
                // implement your tags yourself, i.e., the items stored in your NexusGroup::members.
                
                // When you implement a node yourself, you can override its ::Read, ::Write, and ::CopyTo functions
                // to handle the node serialization and copying yourself. See https://tinyurl.com/2v4ajn58 for a
                // modern example for that. So for your, let's call it NexusTag, you would do something like this:
                class NexusTag : public TagData
                {
                    Bool CopyTo(NodeData* dest, const GeListNode* snode, GeListNode* dnode, COPYFLAGS flags, 
                                AliasTrans* trn) const
                    {
                        // This is a copy and paste event. There is no dedicated event for CTRL + DRAG you seem
                        // to after.
                        if (flags & PRIVATE_CLIPBOARD_COPY)
                        {
                            // Do something special with the destination node #dnode or call home to you registry.
                        }
                        else
                        {
                            // Do something different.
                        }
                
                        // We should always call the base class implementation, unless we want interrupt the copy.
                        return SUPER::CopyTo(dest, snode, dnode, flags, trn);
                    }
                };
                
                // ---
                
                // Another way could using a MessageData hook and monitoring the EVMSG_CHANGE events, i.e., when
                // something in a scene changed. This is usually how render engines synchronize scene graphs. I am
                // not going to exemplify this here, as this is a lot of work.But you can have a look at this thread
                // which is the most simple example we have for this (in Python, but is more or less the same in C++):
                // https://developers.maxon.net/forum/topic/14124/how-to-detect-a-new-light-and-pram-change
                
                // Here you do not have to own the tag implementation. But you could not detect how something has
                // been inserted, only that it has been inserted.
                
                // ---
                
                // Yet another thing which could help are event notifications. I.e., you hook yourself into the copy
                // event of any node (you do not have to own the node implementation for this) and get notified when
                // a copy occurs. But event notifications are private for a reason, as you can easily crash Cinema
                // with them. You will find some material on the forum, but they are intentionally not documented.
                
                // https://tinyurl.com/2jj8xa6s
                
                // ---
                
                // Finally, with NodeData::Init you can also react in your node to it being cloned.
                Bool NexusTag::Init(GeListNode* node, Bool isCloneInit)
                {
                    if (isCloneInit)
                    {
                        // Do something special when this is a clone operation.
                    }
                
                    return true;
                }
                

                MAXON SDK Specialist
                developers.maxon.net

                1 Reply Last reply Reply Quote 0
                • First post
                  Last post