• 0 Votes
    2 Posts
    30 Views
    ferdinandF
    Hey @aturtur, Thank you for reaching out to us. EventAdd will never really work in script manager scripts in the sense you mean it, unless you use hacks like dangling async dialogs (which as I always point out are a really bad idea). The reason is that Script Manager scripts are blocking, i.e., all scene and GUI execution is being halted until the script finishes. You can hack yourself around this with a dangling async dialog, i.e., a dialog that lives beyond the life time of its script. But that is not a good idea, you should implement some form of plugin to host your asnyc dialog, as you otherwise risk crashes. A modal dialog is just an extension of this. It is right in the name, it is modal, i.e., synchronous. All scene and GUI execution is being halted while this dialog is open and only resumes once it closes. When you want updates while your dialog is open, you need an async dialog (and a plugin which hosts it). Cheers, Ferdinand Since you also might misunderstand the nature of EventAdd() I am also putting here the C++ docs I updated a few weeks ago, to better reflect the nature of it (not yet live): /// @brief Enqueues an update event for the active document. /// @details Only must be called when modifying the active document and is without meaning for other documents. The typical example of using `EventAdd` is after adding or removing elements from the active document; and wanting these changes to be reflected in the UI. The function itself is technically thread-safe, but the vast majority of operations that require calling `EventAdd` are not thread-safe and must be called from the main thread (and therefore calling this function is usually main thread bound). The function also does not enqueue a dedicated event item, but rather sets a flag that is checked when the next update event is processed. Therefore, calling `EventAdd` multiple times in one function scope is unnecessary overhead which must be avoided. Because such multiple event flags cannot be consumed while a function on the main thread is still running, and instead the event will only be consumed after that function returns. /// @code /// Result<void> AddCubes() /// { /// CheckState(maxon::ThreadInterface::IsMainThread(), "AddCubes must be called from the main thread."_s); /// /// // EventAdd(); // We could also technically call it here with the same effect. The event /// // will only happen after this function returns. /// /// BaseDocument* doc = GetActiveDocument(); /// for (int i = 0; i < 10; ++i) /// { /// BaseObject* cube = BaseObject::Alloc(Ocube); /// if (!cube) /// return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "Failed to allocate cube object."_s); /// /// doc->InsertObject(cube); /// /// // Calling EventAdd here would have no extra effect, since this event cannot be consumed while /// // our main thread function is still running. And such extra calls on a large scale can cause /// // considerable overhead. /// } /// /// // Notify C4D that the active document has changed. The very end of a function or scope is the /// // canonical place to call EventAdd(). /// EventAdd(); /// } /// @endcode /// @see The article @link page_manual_coremessages Core Messages@endlink for more information. /// @param[in] eventflag The event to add: @enumerateEnum{EVENT}
  • Xref Material reference

    Moved General Talk windows 2026 2025 c++
    2
    0 Votes
    2 Posts
    32 Views
    ferdinandF
    Hello @Jespersather, thank you for reaching out to us. This is a developer forum, not an end user support forum. We cannot help you here with your end user issues. Please use our Support Center to get end user support for Cinema 4D. I have moved your topic into General Talk. Cheers, Ferdinand
  • 0 Votes
    3 Posts
    50 Views
    pislicesP
    Hi @ferdinand, I appreciate the reply! I didn't have a chance to update this post until now, but over the weekend I also found the Graph Descriptions Manual you've linked. The Scalar Ramp example in there was enough to help me figure out how to implement it with the Ramp node. Thank you for your response though, I'm sure it will make things easier if anyone else comes across this subject!
  • 0 Votes
    1 Posts
    36 Views
    No one has replied
  • Access Node Material Path Redshift 2026

    Cinema 4D SDK 2026 python windows
    2
    0 Votes
    2 Posts
    81 Views
    R
    hi there, I actually did sort this out in a very round about way with some "vibe coding". this was for a mapp creation project I am working where I am displaying various eras of map onto 20km grids (so as not to kill the viewport functionality). have a look at the below and let me know if this is a solid approach or if there was a better way: import c4d import maxon def main(): era = "INSERT_YOUR_ERA_HERE" basePath = "INSERT_YOUR_PATH_HERE" prefix = f"map_{era}_tile_20k_" extension = ".tif" doc = c4d.documents.GetActiveDocument() if doc is None: return nodeSpaceId = maxon.Id("com.redshift3d.redshift4c4d.class.nodespace") textureNodeId = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.texturesampler") for mat_index in range(1, 9): mat_name = f"column_{mat_index:02d}" mat = doc.SearchMaterial(mat_name) if not mat: print(f"Material {mat_name} not found.") continue nodeMat = mat.GetNodeMaterialReference() if nodeMat is None: print(f"{mat_name} is not a node material.") continue graph = nodeMat.GetGraph(nodeSpaceId) if graph.IsNullValue(): print(f"No Redshift graph for {mat_name}.") continue textureNodes = [] maxon.GraphModelHelper.FindNodesByAssetId(graph, textureNodeId, False, textureNodes) with graph.BeginTransaction() as transaction: for node in textureNodes: node_name = node.GetValue(maxon.NODE.BASE.NAME) if not node_name: print(f"Unnamed node in {mat_name}, skipping.") continue node_name = str(node_name) try: local_index = int(node_name) except: print(f"Non-numeric node name '{node_name}' in {mat_name}, skipping.") continue global_index = (mat_index - 1) * 11 + local_index filename = f"{prefix}{global_index:03d}{extension}" full_path = basePath + filename tex0 = node.GetInputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.texturesampler.tex0" ) if tex0.IsNullValue(): print(f"No tex0 on node '{node_name}' in {mat_name}") continue pathPort = tex0.FindChild("path") if pathPort.IsNullValue(): print(f"No path port on node '{node_name}' in {mat_name}") continue pathPort.SetDefaultValue(maxon.Url(full_path)) print(f"{mat_name} → Node '{node_name}' set to {full_path}") transaction.Commit() c4d.EventAdd() if __name__ == "__main__": main()
  • 0 Votes
    2 Posts
    98 Views
    ferdinandF
    Hey @Dunhou, Thank you for reaching out to us. We agree that this would be desirable. These methods are actually already wrapped but we hide them for now in the Python SDK. Find the reasons below. ExecuteJavascript: I just did remove the bindings of the Python API for that method in the current beta and also switched the C++ method to internal. The reason for that decision was is that we have security concerns about attackers being able to execute arbitrary JS in a web browser opened in Cinema 4D. SetWebMessageCallback: This is intended solution, i.e., the JS you want to execute must be already embedded into the HTML which is running in the HtmlView. On Windows/WebView2 it uses web messages, on MacOS/WebKit a custom solution emulating them. And SetURLCallback is then the way to get data back from the JS VM. For 2026.1 I already wrote examples for these methods, but on the last meters we discovered that something not only broke the Python bindings but the whole "execute JS" in the WebView2/WebKit bindings. My last info is that something broke there due to a project update, and that the two devs involved in it will have a look. I'll give them another bump and report here if there are any updates. Cheers, Ferdinand
  • Creating custom asset nodes via Python API

    Cinema 4D SDK 2026 python windows
    4
    0 Votes
    4 Posts
    153 Views
    ferdinandF
    Good to hear that things worked out for you!
  • 0 Votes
    4 Posts
    169 Views
    ferdinandF
    Hey @vaishhg, As I tried to explain there are no nested dependencies. *.rs is a full blown scene file format which can express geometry, curves, simulation data, materials and more. When you save a Cinema 4D scene as *.rs al data in it is exported to that format, including the "nested" case where a *.c4d scene is referencing a *.c4d scene. So when you start out with this *.c4d scene: Scene.c4d +-- Objects +-- Cloner ( creates 5 instances) +-- Cube Generator +-- Cache +-- Polygon Object +-- Tags +-- Material Tag (references 'Red Material') +-- Materials +-- Red Material And then export it to Scene.rs, you get this (this is not an actual depiction of the file format, just a visualization of what happens, rs is not an open format). Scene.rs +-- Objects +-- Cube.0 [Red Material] +-- Cube.1 [Red Material] +-- Cube.2 [Red Material] +-- Cube.3 [Red Material] +-- Cube.4 [Red Material] +-- Materials +-- Red Material (contains Red Material definition) If you load that file back into Cinema 4D you get this. All data - that these are 5 separate cubes with a red material each - resides in the Redshift core only, we only see a proxy in Cinema 4D, hence the name "RS Proxy Object". It is the Redshift Core which will resolve the data in the RS file at render time. ReferencingScene.c4d +-- Objects +-- RS Proxy Object.0 (loads Scene.rs) +-- Cache (will be empty by default, there is literally no data in the c4d core, only when we set 'Preview' to 'Mesh' there will be a cache so that the viewport can display something) +-- Polygon Object (one blob representing all 5 cubes and no material information) +-- RS Proxy Object.1 (loads Scene.rs) +-- Cache +-- Polygon Object When we now export ReferencingScene.c4d to ReferencingScene.rs we get this. Because when the exporter runs, it will encounter the two RS Proxy Objects when flattening the c4d scene and do what you cannot do, grab the rs scene data from the referenced Scene.rs files and inline that into the new ReferencingScene.rs file. So we end up with 10 cubes in total, each with the red material assigned. ReferencingScene.rs +-- Objects +-- Cube.0 [Red Material] (from RS Proxy Object.0) +-- Cube.1 [Red Material] ... +-- Cube.2 [Red Material] ... +-- Cube.3 [Red Material] ... +-- Cube.4 [Red Material] ... +-- Cube.0 [Red Material] (from RS Proxy Object.1) +-- Cube.1 [Red Material] ... +-- Cube.2 [Red Material] ... +-- Cube.3 [Red Material] ... +-- Cube.4 [Red Material] ... +-- Materials +-- Red Material (contains Red Material definition) And when we load that back into Cinema 4D we get this: SecondGeneration.c4d +-- Objects +-- RS Proxy Object.0 (loads ReferencingScene.rs) +-- Cache +-- Polygon Object (one blob representing all 10 cubes and no material information) The TLDR is that the Redshift Core can read *.rs files and the Cinema API cannot, it can only write them or load them via an RS Proxy Object. And there is no 'resolving [...] the full proxy chain' as you put it. An *.rs scene file is just a discrete scene representation that contains does not know concepts such as generators or assets known to the Cinema API/Core. When export a *.c4d scene that references *.rs files all data is just flattened into a single *.rs file (again, what I showed under the *.rs formats above was just a visualization, not the actual file format). There is currently no way to do what you want to do, even if you would request access to the Redshift Core C++ SDK. Because the RS file format is a GPU scene file format and very deeply integrated into the core. Even the RS Core SDK does not expose functionalities to read RS files to CPU memory structures. Cheers, Ferdinand
  • 0 Votes
    3 Posts
    227 Views
    lasselauchL
    Thank you, Ferdinand. (Again!) That was exactly what I needed. It's working great now! Thanks for taking the time to answer this so thoroughly and quickly! Cheers, Lasse
  • How to change the Node spaces

    Cinema 4D SDK python 2025 windows
    2
    0 Votes
    2 Posts
    138 Views
    ferdinandF
    Hello @gelobui, Welcome to the Maxon developers forum and its community, it is great to have you with us! Getting Started Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules. Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment. Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support. Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads. It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: How to Ask Questions. About your First Question It depends a bit on how you mean your question. There is GetActiveNodeSpaceId which allows you to get the ID of the current node space. But there is no setting equivalent of that function. So, you cannot set a node space by its ID. What you can do, is call the command which switches node spaces. These are however dynamically assigned and can have a different meaning, depending on how many render engines are installed. You can just check the script log after changing the space. On this installation I have for example no extra render engines or node spaces installed, therefore Redshift is there 72000, 4. [image: 1765458881919-a0da1ed7-7add-456e-a8cc-63d8bd1ced2a-image.png] But on this machine I have the C++ SDK installed and therefore the Example nodes space, so Redshift is now 72000, 5: [image: 1765459007165-49b0838b-49d5-4f14-b638-811d8d26ada4-image.png] When you really want to do this in a fail safe manner, you would have to parse the menu of Cinema 4D to know with which sub-id to call CallCommand. Cheers, Ferdinand
  • 0 Votes
    3 Posts
    175 Views
    ymoonY
    @ferdinand Thank you. It works well. --> tag.Message(c4d.MSG_EDIT)
  • 0 Votes
    3 Posts
    178 Views
    ferdinandF
    Hey @lionlion44, yes, that is the correct answer. The subject comes up from time to time, here is an answer of mine which is about the very case of yours - discovering substance channels. Cheers, Ferdinand
  • Educational Licenses

    Cinema 4D SDK python windows 2025
    9
    1
    0 Votes
    9 Posts
    593 Views
    DunhouD
    @ferdinand Thanks for your great examples! Very helpful!
  • set GvNode value via python

    Cinema 4D SDK windows python 2026
    4
    1
    0 Votes
    4 Posts
    248 Views
    DunhouD
    Thanks for @ferdinand awesome answer! I found DescLevel and DescId always confused to me, but them do can access more than simple set item, time to dive deeper to the DescLevel part Cheers~ DunHou
  • 0 Votes
    6 Posts
    534 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; }
  • 0 Votes
    14 Posts
    877 Views
    ferdinandF
    FYI, there has been a verified fix for the Booleans crash, it will be included in an upcoming release of Cinema 4D.
  • 1 Votes
    4 Posts
    525 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
    2 Posts
    267 Views
    ferdinandF
    Hey @Pheolix, Welcome to the Maxon developers forum and its community, it is great to have you with us! Getting Started Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules. Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment. Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support. Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads. It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: How to Ask Questions. About your First Question Your code looks generally good, especially for someone who is starting out with the API you did really well. With that being said, I do not really understand what you want to do: ... plugin that maps and arranges textures onto a pixel grid. The goal is to make it easier to create voxel-style or Minecraft-like models by linking real-world units (e.g., centimeters) to pixels. (for example, 1 pixel = 6.25 cm) A few pointers: A CommandData plugin is the perfect choice when you want to manipulate the scene without any restrictions and are fine with always having to press a button run your logic. Scene element plugins, e.g., objects, tags, etc. on the other hand will carry out their logic on their own when a scene update is invoked. But they come with the restriction that their major payload functions (ObjectData::Execucte, ObjectData::GetVirtualObjects, TagData::Execute, etc.) run in their own threads (so that scene execution is parallelized) and therefore are subject to threading restrictions (I am aware that you are on C++, but the Python docs are better on this subject). So, for example, in a TagData::Execute you would not be allowed to allocate a new UVW tag on the object that is also hosting your plugin tag. But you could implement a button in the description of the tag, which when clicked cerates your setup (because TagData::Message runs on the main thread and you therefore are there allowed to add and remove scene data). With TagData:Execute you could then continuously update the UVW tag you are targeting on each scene update (changing parameter values of other scene elements is fine when tags are executed). This workflow is not necessarily better than a command, I am just showing you an option. Commands are also easier to implement for beginners than a scene element. When you talk about units, you should be aware that both the object and texture coordinate system are unitless. What you see in edit fields, is just smoke and mirrors. We recently talked here about this subject. You did get the major gist of our error handling but what you do with maxon::Failed is not quite correct. It is meant to test the return value of a Result<T> for having returned an error instance instead of T. When you want to indicate an error, you must return an error, e.g.,: // Not correct. if (!doc || !selectedObject || !bitmap || !foundTag) return maxon::FAILED;T // This is how one indicates that a function failed because something was a nullptr. if (!doc || !selectedObject || !bitmap || !foundTag) return maxon::NullptrError(MAXON_SOURCE_LOCATION, "Could not get hold of scene data."_s); // For a function which is of type Result<void>, its also totally fine to do this on an error. void functions // can fail successfully, it is up to you to decide if an error is critical enough to halt execution of if you just // want it to silently terminate. if (!doc || !selectedObject || !bitmap || !foundTag) return maxon::OK; // we are okay with failing here. For details see Error handling and Error Types Cheers, Ferdinand
  • Set VERTEXCOLOR in PaintTool

    Cinema 4D SDK python r25 windows
    3
    0 Votes
    3 Posts
    254 Views
    R
    @m_adam Thank you so much for the help, that works perfectly and thank you for showing me both methods, that's very helpful and appreciated!
  • 0 Votes
    4 Posts
    357 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.