• 0 Votes
    6 Posts
    150 Views
    ferdinandF
    I have moved this into bugs.
  • 0 Votes
    6 Posts
    181 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; }
  • Several Asset Browser Issues

    Cinema 4D SDK 2026 2025 python
    7
    0 Votes
    7 Posts
    242 Views
    B
    Hi @ferdinand Thank you for the reply and I totally understand your point. I think I will try to find another solution and will adjust my rigs to maybe get rid of the python tag thats causing the issue. I wasn't aware that this is considered "dark magic" python code^^ haha Thank you once again for your efforts! Cheers, Ben
  • Setting Preferences via script

    Cinema 4D SDK python 2025 2026
    5
    0 Votes
    5 Posts
    124 Views
    ferdinandF
    Hello @CJtheTiger, I am not quite sure how your question is meant, and generally new questions should constitute new topics. When you are asking, if when there is an ID_FOO: int = 12345 in V1, if we then just silently switch out the numeric value to ID_FOO: int = 54321 in V2, then the answer is sort of yesn't. We try to keep identifiers persistent. And for things like plugin IDs this is true without ifs and buts. I.e., once Ocube: int = 5159 has been defined, it will stay like this. But parameter values, e.g., PRIM_CUBE_LEN: int = 1100, can technically change. The goal is also to keep them persistent but in ABI breaking releases we sometimes have to modify descriptions. That is why we recommend that you always use symbols and not numbers. Cheers, Ferdinand
  • 1 Votes
    4 Posts
    198 Views
    ferdinandF
    Hey Jacob, Thank you for the added data. First of all, I have invited you to the forum to end this middle man communication, which is a bit odd. The pyz file is part of python ... I am aware of what pyz is, I just pointed this out because I of course looked inside your package and found all the py_armor obfuscated code and the injected binaries in there. So, I pointed out that this is bit more than just "packaged in a pyz file for ease of distribution [...]" as Lasse/you put it, the goal is here clearly obfuscation. Which is also relevant for support, as it limits what you and I can see (without getting hacky). My finding with the 10mb file freeze comes from my trial and error ... mean[t] when you run a script from Extensions -> User Scripts. Your code also freezes when you load it as a Script Manager script. That is what I did with the last package from Lasse, and now also yours. The code in your script is again wrong, which is why it won't freeze until you fix it. This is the code I found: [image: 1760086817339-2bd4290e-78b2-43d4-936d-1e2a7eaf366b-image.png] And I fixed it then to this. When I ran it then, Cinema 4D froze for two minutes or so, after that it opened a myriad of dialogs to then terminate into two crash dialogs (it was pure luck that I let it run for so long, Lasses previous version might have acted similar, but there I killed the C4D process, as soon as I saw the 'beach ball of death' cursor on MacOS). [image: 1760086755748-69fcb5da-ac49-477e-8f70-9daeb1daa1aa-image.png] Please read my answer below carefully, as I already pointed out most of this in my previous posting. I would STRONGLY suggest debugging this without obfuscation. Maxon also cannot debug larger sections of code or test further packages for you. I understand that obfuscation might not be your choice, but it will make your life harder in debugging this, as you always fly blind. We of course still will provide support, but you have to provide more than "it does not work/crashes/freezes, please help us", especially when this is not code tied to our APIs. Attach a debugger from the Script Manager and see why your code crashes/freezes (see link in last posting when unsure how to do this). But you need an un-obfuscated code base for this to make any sense. Defer your loading to a later point, e.g., C4DPL_PROGRAM_STARTED, when you have issues in the direct plugin registration context. In that case you would always register your plugin, but then only execute it when the your own license check succeeded. But you absolutely cannot ship a plugin which freezes Cinema 4D for multiple minutes on startup or when invoking your plugin because your licensing takes so long. When we see this in the wild, we will have to blacklist your plugin IDs, as this damages the brand Cinema 4D. Please use threading then to not block the main thread with your long running code. What I did not notice before is that you apparently try to open multiple dialogs (for me it opened multiple dialogs when I ran the script). The GUI and many other systems are not yet available when Cinema 4D is still booting, e.g., in the splash screen. You can expect all systems to be up and running when C4DPL_STARTACTIVITY is emitted, but it is better to wait for C4DPL_PROGRAM_STARTED for long running tasks (i.e., the two events I tested in my previous posting). Please also keep in mind that Cinema 4D has its own anti-piracy measures. Python plugins are sort of special in that they work slightly different than native C++ plugin modules (the Python C++ module shipped by Maxon sort of acts as a surrogate for Python plugins in the module registration phase). But Cinema 4D won't allow plugin threads to start their own processes at this point (which you might be trying to do with your injected binaries), and threading should also be avoided at this point, as the job system of Cinema 4D might be still booting. What you are meant to do in PluginStart (or the __main__ context of a pyp file), is register your plugins. You can run some quick logic there, but you are certainly not meant to start communicating with servers and opening GUIs there. You can read here a bit more about this from a C++ system perspective. I would recommend to do your license check in the background in its own thread once C4DPL_PROGRAM_STARTED has been emitted (so that you can also open dialogs to signal errors). An alternative would be to do it when the user clicks the button of your command. But you should also here put it into its own thread, so that it does not block everything else. Cheers, Ferdinand
  • 0 Votes
    3 Posts
    108 Views
    gheyretG
    Thank you for the C++ code—it's been very helpful! Cheers!
  • 0 Votes
    4 Posts
    192 Views
    M
    I'm not sure I understand you correctly, you do not have to use QuickTabRadio bar with resource, you just retrieve the int value of the parameter, in the case of the previous example I share with a GetInt32(c4d.MGCLONER_VOLUMEINSTANCES_MODE). If you can provide a code example of what is blocking you that would be nice. You can find a non-exhaustive list of type of control available in *res file within the C++ documentation in Description Resource. Cheers, Maxime.
  • 0 Votes
    2 Posts
    128 Views
    M
    Hey you need to react to MSG_GETCUSTOM_NAME_ADDITION and return a dict with the expected string with the "res" key. You need to register your object with c4d.OBJECT_CUSTOM_NAME_ADDITION to have the additional name displayed in the Object Manager. Cheers, Maxime.
  • exporting usd with python

    Cinema 4D SDK 2025 python windows
    2
    0 Votes
    2 Posts
    169 Views
    ferdinandF
    Hey @lionlion44, Thank you for reaching out to us. I doubt that this will not work in Python, it is just that we have not documented the symbols there yet. But you can find them in the C++ docs: [image: 1757607809636-6e23eada-2198-4c34-84bf-a576bbdd0ef9-image.png] I.e., it will be c4d.FORMAT_USDIMPORT and c4d.FORMAT_USDEXPORT. Cheers, Ferdinand
  • GetRad() / GetMp() update issue

    Cinema 4D SDK windows python 2025
    2
    0 Votes
    2 Posts
    161 Views
    ferdinandF
    Hey @datamilch, Thank you for reaching out to us. GetRad and GetMp express the locally aligned bounding box of an object. I.e., the bounding box in the coordinate system of the object. For a world space aligned bounding box we once had this thread, the Cinema API does not offer this out of the box. So, transforming an object will never change its bounding box. What will change the bounding box of an object, is changing its size or changing the parameters of a generator object. When you change the parameters of a generator or when your instantiate a new object, you will have to execute the passes (BaseDocument.ExecutePasses) on a document that contains the object to see the correct bounding box - or wait for the next scene update. PS: Your script is also running illegal code. You cannot modify the active document off-main-thread. I assume the is a script for a Python generator object. Its main function runs off-main-thread and you call there your csto which in turn calls SendModelingCommand on the active document. This will all sooner or later crash. https://developers.maxon.net/docs/py/2025_3_1/manuals/manual_threading.html#threading-information Cheers, Ferdinand
  • 0 Votes
    11 Posts
    442 Views
    A
    @ferdinand Thank you for your in-depth analysis. It would have taken me way too long to figure this out lol, especially finding "CUSTOMDATA_BLEND_LIST". The first example seems to work for me, but like you said, it may not be reliable. The Python node example also works great. Again, thank you for your time.
  • Can I get keyboard input mode?

    Cinema 4D SDK windows python c++ 2025
    4
    1
    0 Votes
    4 Posts
    238 Views
    ferdinandF
    Hey @Dunhou, I have edited your posting. It is fine to mention the beta forum, but we would rather not see its name written here. When you are talking about 'C++', I assume you are talking here about the Windows SDK/API, as for example GetGUIThreadInfo. I would doubt that you get very far with that here. Cinema 4D is very OS agnostic and we have our whole GUI decoupled from the OS. The little rename window in our tree view control is not an OS rename dialog. I am not even sure if this is something with an HWND handle which you could address so that you could intercept keyboard events to it, or if this is just a virtual window within our API. For this IMM thing to work, we would have to use the IME API in Cinema 4D, so that our text edit gadgets support it. I also do not quite understand what you are trying to do. The missing piece is here probably that our text edit gadget does not send its current content to the IME API (at a glance, via ImmSetCompositionStringW as shown here). And as a third party, you cannot really fix that. Because you (a) do not own the implementation and you (b) are not informed about an edit event, so you cannot just write a plugin which sends the object name to the IME API. Cheers, Ferdinand
  • 0 Votes
    4 Posts
    172 Views
    ferdinandF
    Hey, thanks for the extended code, but I still cannot run this, as it is only a fragment So, I only have a very rough understanding of what is going wrong. A likely issue apart from you just having a bug in your hit logic, is that you use the wrong coordinate system. if msg[c4d.BFM_INPUT_DEVICE] == c4d.BFM_INPUT_MOUSE \ and msg[c4d.BFM_INPUT_CHANNEL] == c4d.BFM_INPUT_MOUSELEFT \ and msg[c4d.BFM_INPUT_VALUE]: local_x = msg[c4d.BFM_INPUT_X] local_y = msg[c4d.BFM_INPUT_Y] scroll_y = self.dialog.get_scroll_offset() I would have to check myself, but there is some inconsistency with which dialogs send mouse input messages regarding the used coordinate system. Sometimes they send messages in the coordinate system of the dialog and sometimes messages in the coordinate system of the gadget. Just print out your (local_x and local_y) and check if the values make sense as local coordinates as you seem to treat them. On GeUserArea are multiple coordinate conversion methods. I think BFM_INPUT in this context is in local user area coordinates but I am not sure, it could also be that you have to convert them. The other thing is of course that you add this self.dialog.get_scroll_offset() on top of things. I assume this is sourced by something like GeDialog.GetVisibleArea? There you could also unintentionally mix coordinate systems. Cheers, Ferdinand
  • 0 Votes
    3 Posts
    269 Views
    d_keithD
    Thanks for the response! I appreciate the ASSETDATA_FLAG_MULTIPLEUSE tip. My hope was that there was some sort of existing RenameAsset SDK method that would also update all references to that asset without implementing custom handling for various element types (Redshift Nodal Material, Cinema 4D Classic Material, Arnold Dome Light, XRef, etc). Evidently such a public python SDK method doesn't exist. My example code was texture specific as that's the part of the problem that's most pressing to me and I was able to implement with custom per-type handling - if I manage to get other types working, I'll attempt to update over time.
  • Motion Blur, ExecutePasses in VideoPostData

    Cinema 4D SDK c++ 2025
    2
    0 Votes
    2 Posts
    170 Views
    ferdinandF
    Hello @Aaron, Thank you for reaching out to us. Please follow our support procedures. You should consolidate your postings until we answer, i.e., update your last posting instead of creating new ones. I have consolidated your postings for you. You should also spend more time on making your problems reproducible for us. I know it can be easy to overlook in the heat of the battle but your question(s) are not as obvious to me as you probably think they are. As lined out in our support procedures, example code and/or scene data is usually required. I cannot answer every possible angle and interpretation this topic of yours could take, but I can state three things which come to mind when reading your two postings. What's the way to get several steps of motion blur transformation matrices for BaseObjects of the BaseDocument. That is simply not possible. A document has no notion of motion blur as it is a render effects implemented by a render engine, not something documents implement. Cinema's time system BaseTime allows for sub-frame accuracy, e.g., you could set a 10fps document to BaseTime(i, 30) and with that step with three sub-frames through the document. Simple things like position animations or other interpolated values will also work doing this. But simulations might yield different results than stepping with the natural frame rate through the document. And the most important thing is that there is absolutely no guarantee that this approach will result in 'matrices', i.e., transforms, that will match with what a render engine produces when you add some kind of sub-frame-based motion blur. Using this BaseTime approach is one way how the render engine could do it, but since render engines always have their own scene format, they could also do the calculation there, yielding different results than what Cinema's animation system would yield. When we want to add Motion Blur and call document->ExecutePasses(...) for some time then this POC is flushed and has no polygons or vertices, so it's not collected and can't be rendered It is too opaque for me what you are doing there to give a real answer, but: There is no guarantee that the caches of an object are built at any point except for when a render engine gets a scene state to render or inside GetVirtualsObjects when you manually build your dependent objcts. Usually, caches are built but depending on where you are, the cache of another object might not have been built or updated yet, resulting in either an empty cache or you looking at the cache output from the previous scene update. ExecutePasses has become a bit too popular tool recently. I guess that is partially my fault, because I have shown its usage in some examples. But executing the passes on a document is a very expensive operation and should only be carried out when absolutely necessary. I would absolutely not be surprised if something like liquids do not support stepping through a document with a frame rate lower than the documents frame rate. But without concrete data and example code I am just guessing. Cheers, Ferdinand
  • 0 Votes
    3 Posts
    252 Views
    A
    okay the docs you gave were great for insight working code to change parameter of roughness for x given mat selections import c4d import maxon from maxon import GraphDescription doc = c4d.documents.GetActiveDocument() # Get selected materials directly selected_materials = doc.GetActiveMaterials() # Modify roughness for each selected material for material in selected_materials: graph = GraphDescription.GetGraph(material, nodeSpaceId=maxon.NodeSpaceIdentifiers.RedshiftMaterial) GraphDescription.ApplyDescription(graph, { "$query": { "$type": "Standard Material" }, "Reflection/Roughness": 1 }) c4d.EventAdd()
  • How to get explicit import node space?

    Cinema 4D SDK windows python 2025
    3
    1
    0 Votes
    3 Posts
    192 Views
    DunhouD
    Thanks @ferdinand for your great solution! Cheers~ DunHou
  • 0 Votes
    5 Posts
    270 Views
    DunhouD
    Hey @ferdinand , thanks for details. Cheers~ DunHou About forum, I can get notification about my own topic, And I clear all the cache, but nothing changed. After I tried to reset my account settings manually, then it worked as expected.
  • Knowing if a font exists

    Cinema 4D SDK windows python 2025
    4
    0 Votes
    4 Posts
    224 Views
    D
    for my use case i will probably have a fallback solution with web-save or system fonts like this: import platform system_name = platform.system() if system_name == "Windows": font_name = "Consolas" elif system_name == "Darwin": font_name = "Menlo" elif system_name == "Linux": font_name = "DejaVu Sans Mono"
  • 0 Votes
    8 Posts
    383 Views
    A
    @ferdinand said in BaseBitmap.Init("somefile.exr") crashes with multiple std::thread: omp #parallel Sorry for late reply, I am not advocating for std::threads, I understand your design explanation. The omp #parallel thing is OpenMP directive to launch parallel threads. It's an old but simple method. Actually I have made a solution by exploiting Cinema way of launching threads and it works fine. However, I hope it would be good to make BaseBitmap->Init a thread safe and independent call from other C4D threads and resources. I will collect an example for you with exr images soon. Cheers, Aaron