• set GvNode value via python

    Cinema 4D SDK windows python 2026
    4
    1
    0 Votes
    4 Posts
    50 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
    248 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
    437 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
    228 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
    119 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
    116 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
    208 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.
  • exporting usd with python

    Cinema 4D SDK 2025 python windows
    2
    0 Votes
    2 Posts
    198 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
    186 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
    462 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.
  • Cineware as dll

    Cineware SDK c++ windows
    4
    0 Votes
    4 Posts
    263 Views
    ferdinandF
    Hey, well, the reason for the "compiler" restrictions mentioned in the docs, is that the SDK is shipped with code example projects and precompiled static libraries for these targets. But the SDK also comes with its full source (the 'includes'), so nothing is really preventing you from using another IDE/compiler. The VS 2019 projects should also be relatively safe to upgrade to 2022. With XCode things are more complicated, and I would there recommend creating your own project from scratch instead of updating the existing one. In the bigger picture, we probably should update the Cineware SDK to also using CMake as the Cinema 4D SDK does these days, and with that offer more flexibility. I have this on my bucket list, but it is not very high in priority, because the Cineware SDK is our least requested SDK. When you have concrete problems with creating a build system for the Cineware SDK for your desired compiler (and IDE), just post the questions here, and I will try to help you. Cheers, Ferdinand
  • Can I get keyboard input mode?

    Cinema 4D SDK windows python c++ 2025
    4
    1
    0 Votes
    4 Posts
    267 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
    177 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
  • How to get explicit import node space?

    Cinema 4D SDK windows python 2025
    3
    1
    0 Votes
    3 Posts
    196 Views
    DunhouD
    Thanks @ferdinand for your great solution! Cheers~ DunHou
  • 0 Votes
    5 Posts
    309 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
    254 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
    402 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
  • 0 Votes
    2 Posts
    297 Views
    ferdinandF
    Hey @Amazing_iKe, Thank you for reaching out to us. That RenderDocument does not support animations is only partially true. Effectively it does not for users, but that is more a regression than the lack of an actual feature. When you look into its documentation, you will see that many symbols and arguments are animation related. And when you send it a document which holds an animation, you are paying the price for rendering the full animation. The regressed state is however that you effectively only have access to the bitmap of the last frame of that animation. There is also the issue that the OCIO workflow with RenderDocument is a bit clunky at the moment. We have these issues in our backlog by I unfortunately cannot make any guarantees as to when we will fix them. You have two options: Render the document frame by frame yourself with RenderDocument and then assemble the animation yourself. Use the BatchRender, i.e., what is shown to the user as the render queue. You can use this in a fully programmatic manner, but you are restricted to rendering documents that have been saved to disk, and the output will also be put to disk. I.e., you do not have the 'memory only'-aspect of RenderDocument. There are many topics about this subject on this forum, for example here where I showed how you abstract some of the disc-only limitations aways. Maxime also recently added some features to the batch render like switching cameras and takes, which makes it less likely that you have to modify the file. I cannot write examples for all possible routes here. You have to decide yourself which route you want to go, and when you are then stuck there, I will help you. What to pick depends on your goals. When you expect the renderings to be quick, doing it manually with RenderDocument could be a solution. For more complex animations, the batch renderer is probably the better route. Cheers, Ferdinand
  • 0 Votes
    3 Posts
    340 Views
    ferdinandF
    Hey @Amazing_iKe, Thank you for reaching out to us. You do the same thing you did incorrectly in your other thread. You create an async dialog and you do not keep it alive. We would prefer it to keep things G-rated here. I.e., no nudity, no violence, and no weapons. I removed the image in your posting. With that being said, and when I try out your script (with the dlg thing fixed). I end up with this: [image: 1752854682502-fc83d223-abc0-4568-98fc-deb82596b059-image.png] I.e., it just works. But you docked your dialog (and so did I imitating what you did). I now cannot reproduce this anymore, but when I did something in my layout, I think I dragged a palette, I got a similar result as yours. That is not too surprising though as you implement here an async dialog in a hacky manner. You have no CommandData.RestoreLayout which would handle layout events for your dialog. Something getting out of whack with scroll areas is not out of question for such irregular cases. Please follow the patterns shown in our GUI examples I would recommend to follow py-cmd_gui_simple_2024, it also contains code for handling layout events. I.e., this: def RestoreLayout(self, secret: any) -> bool: """Restores the dialog on layout changes. Implementing this is absolutely necessary, as otherwise the dialog will not be restored when the user changes the layout of Cinema 4D. """ return self.Dialog.Restore(self.ID_PLUGIN, secret) And to be clear, it is absolutely fine to share plugins with us here. It does not have to be a script manager script. Things should just not get too long. Feel free to anonymize your plugin IDs when you feel skittish about sharing them in public. Cheers, Ferdinand edit: Okay, there it is again. Not exactly the same as yours but close. But I would still have to ask you to provide an example where this happens inside a valid async dialog implementation (with a command or a similar owner). It is not out of question that there is a bug but we still need a non-hacky example. [image: 1752856082257-df89038f-124d-434f-8cde-3442bd9aebba-image.png]
  • 0 Votes
    3 Posts
    291 Views
    ferdinandF
    And just to be clear, using a modal dialog, e.g., DLG_TYPE_MODAL, is absolutely fine in a script manager script. Because then the GeDialog.Open call will only return when the dialog is closed (and you therefore do not have to keep the dialog alive). The hack I showed above is only needed when you need one of the async dialog types in a script manager script for testing purposes.