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
    1. Maxon Developers Forum
    2. aghiad322
    3. Topics
    Offline
    • Profile
    • Following 1
    • Followers 0
    • Topics 8
    • Posts 18
    • Groups 0

    Topics

    • aghiad322A

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

      Watching Ignoring Scheduled Pinned Locked Moved Cinema 4D SDK 2025 c++ windows macos
      6
      0 Votes
      6 Posts
      150 Views
      ferdinandF
      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); // The same applies here, the Maxon API alternatives to unordered_map are: // // * HashSet (just a hash set of fixed type values without keys) // * HashMap(a hash map of fixed key and value type), // * DataDictionary (a hash map of arbitrary key and value types). 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 too much sense to return a Bool, so our // return 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 too much light on what you are doing for me :) The main questions // is if 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; }
    • aghiad322A

      how to store/serialize custom data type?

      Watching Ignoring Scheduled Pinned Locked Moved Cinema 4D SDK c++
      4
      0 Votes
      4 Posts
      712 Views
      ferdinandF
      Hey @aghiad322, as I said, classic API CustomDataType types (e.g., the "dots" example) realize their serialization via CustomDatatypeClass::Read and ::Write. The DotsDataClass example implements both methods to read and write the dots data. If you want things to be stored, e.g., your gradient, you will have to implement it there. E.g., it could look like this: Bool MyDataTypeClass::WriteData(const CustomDataType* t_d, HyperFile* hf) { // Cast the passed in data to your datatype. const MyDataType* const data = static_cast<const MyDataType*>(t_d); // Bail when #data is malformed, has no gradient. _gradient is a member of MyDataType and supposed // to be of type Gradient* just as in your case. if (!data || !data->_gradient) return false; // Wrap the dereferenced _gradient as a GeData instance. GeData geData; geData.SetCustomDataType(*data->_gradient); // Write your data into the hyper file and bail if it somehow fails. if (!hf->WriteGeData(geData)) return false; // Write other data members of your datatype. if (!hf->WriteInt32(data->_someInt32)) return false; if (!hf->WriteFloat(data->_someFloat)) return false; if (!hf->WriteBool(data->_someBool)) return false; return true; } Cheers, Ferdinand
    • aghiad322A

      Expose cpp-registered datatype to python

      Watching Ignoring Scheduled Pinned Locked Moved Cinema 4D SDK c++ python
      4
      0 Votes
      4 Posts
      791 Views
      ferdinandF
      Hey @aghiad322, please re-read my answer I have given above. You implement a custom data type in C++, your SplineVisDataType in splinevis.h. Then you reference that data type in a resource of a Python plugin (SPLINEVIS ANIMATION_CLONER_SPLVIS { ANIM OFF; };). You will not be able to interact with this parameter/data type in Python until you have ported it or use the other approach I have lined out above - decomposition/channels. As also lined out above, you will be never able to get-access a parameter with a custom data type you have implemented yourself in Python because C4DAtom.GetParameter hardcodes the casting/unpacking of data types to Python. Cheers, Ferdinand PS: And if you only want another way to display SplineData, you can also just write a custom GUI targeting that data type. There is no necessity to always write your own custom data type for a custom GUI, you can also target existing ones. You would then have to set the custom GUI to yours when you define a SPLINE element in a dialog or description resource
    • aghiad322A

      Effector plugin is only executing in deformation mode

      Watching Ignoring Scheduled Pinned Locked Moved Cinema 4D SDK c++
      7
      0 Votes
      7 Posts
      1k Views
      aghiad322A
      @ferdinand Thanks a lot it worked
    • aghiad322A

      how to make parameter stick to position with dynamic parameters hiding?

      Watching Ignoring Scheduled Pinned Locked Moved General Talk programming
      3
      1
      0 Votes
      3 Posts
      957 Views
      aghiad322A
      Thanks a lot @i_mazlov that's exactly what i needed. however on one of the options for the dropdown i have a single Text input that spans across the whole attribute manager, now after i added the "STATICTEXT { SCALE_V; }" it eats up half the space in the image below [image: 1703269667505-screenshot-2023-12-22-211432.png] i tried to give it a Description id and treat it as any other parameter and hiding it using "c4d.DESC_HIDE", below is the layout of the whole tab GROUP ADVANCED_TAB { SCALE_V; STRING CUSTOM_TXT_PARAMETER{ ANIM OFF; CUSTOMGUI MULTISTRING; SCALE_V; WORDWRAP; }; GROUP{ COLUMNS 2; LONG ANIMATION_CLONER_FROM { MIN 0; ANIM OFF; }; LONG ANIMATION_CLONER_TO { MIN 0; ANIM OFF; }; LONG ANIMATION_CLONER_SPAN { MIN 1; ANIM OFF; }; GROUP{ COLUMNS 1; SEPARATOR ANIMATION_CLONER_SEPARATOR{ LINE; }; REAL ANIMATION_CLONER_MULTIPLIER { MIN 0.1 ; STEP 0.1 ; ANIM OFF; }; } } GROUP{ COLUMNS 2; REAL SWEEP_PRO_RADIUS { UNIT METER ; STEP 0.1 ; MIN 0.1; ANIM OFF; }; REAL SWEEP_PRO_MULTIPLIER { MIN 0.1; ANIM OFF; }; } STATICTEXT UI_FILLER{SCALE_V;} GROUP{ COLUMNS 2; LONG PRESET { ANIM OFF; CYCLE { ANIMATION_CLONER; SWEEP_PRO; CUSTOM_INPUT; } } } } but when i try to hide it from the "GetDDescription()" the description "description.GetParameterI(paramId)" is empty ! ,in the "GetDDescription()" in my source code i have the hiding for the other parameters implemented as well but didn't copy them to keep the snippet at minimum lines def GetDDescription(self, node: c4d.GeListNode, description: c4d.Description, flags: int) -> typing.Union[bool, tuple[bool, int]]: if not description.LoadDescription(node.GetType()): return False, flags paramId: c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.UI_FILLER, c4d.DTYPE_STRING, 0)) evalId: c4d.DescID = description.GetSingleDescID() if (evalId and not paramId.IsPartOf(evalId)): return True, flags paramData: c4d.BaseContainer = description.GetParameterI(paramId) #paramData for the "c4d.UI_FILLER" is empty if paramData is None: return True, flags # parameter ID_HIDE_CONDITION. paramData[c4d.DESC_HIDE] = True if node[c4d.PRESET] == c4d.CUSTOM_INPUT else False return True, flags | c4d.DESCFLAGS_DESC_LOADED
    • aghiad322A

      Plugin Tag printing randomly to the console

      Watching Ignoring Scheduled Pinned Locked Moved Bugs python
      4
      1
      0 Votes
      4 Posts
      976 Views
      ferdinandF
      Hey @aghiad322, Great to hear that you solved your problem. And although I understand how you meant your last sentence, the questions of users are never inconvenient to us. All questions are welcome, but sometimes we must draw some boundaries for the scope of support. Cheers, Ferdinand
    • aghiad322A

      Message system isn't working in python effector

      Watching Ignoring Scheduled Pinned Locked Moved Cinema 4D SDK python
      4
      1
      0 Votes
      4 Posts
      780 Views
      ferdinandF
      Hello @aghiad322, I have closed this thread as it has been answered. The task has been added to our internal task pool. However, since this is not a bug but a feature limitation, I have not marked this thread as to_fix as there is nothing to fix here. This does not mean that we will not do it, I have added the task for now to the task pool of a close by release. But we treat feature requests differently than bugs, and the task might therefore be postponed/pushed when we run out of time in that release. Cheers, Ferdinand
    • aghiad322A

      Instance Object Link is not working

      Watching Ignoring Scheduled Pinned Locked Moved Cinema 4D SDK python
      3
      1
      0 Votes
      3 Posts
      736 Views
      M
      Hello @aghiad322, without further questions or postings, we will consider this topic as solved by Monday 05/06/2023 and flag it accordingly. Thank you for your understanding, Maxime.