• How to get edge 'island' selection

    python 2026
    1
    1
    0 Votes
    1 Posts
    2 Views
    No one has replied
  • BaseLink across documents

    c++
    4
    0 Votes
    4 Posts
    66 Views
    ferdinandF
    Hey @WickedP, As I hinted at above, markers are eternally persistent. I.e., you can unload, load, and modify a scene S or the non-cache* node N in it, the node N will always have the same marker. The flag you found has a very special purpose and also very misleading documentation (I just fixed that). Each node in a scene must have a unique marker, as otherwise not only BaseLink but also things like undo and more won't work properly when not each node has exactly one marker and each marker exactly one node (i.e., a bijective or 'one-to-one' relation). But there can be bad actors such as bugs in our codebase or third party plugins which violate that rule. To combat that, Cinema 4D checks the uniqueness of markers of nodes when a scene is being loaded. When it finds duplicate markers, it will open a question dialog, asking the user if he/she wants to repair that scene. When the user says 'yes', only the nodes which had duplicate markers will get a new marker each (so that they are in a bijective marker relation again). This flag you found will suppress this whole behavior, i.e., it will let you load corrupted scenes as is. I would not recommend using it. I am currently not 100% sure what happens when your LoadDocument call is not DIALOGSALLOWED, it looks a bit like that this check then always never runs (which sounds a bit dangerous). Cheers, Ferdinand edit: okay now I see it, the question dialog part has been commented out, so this runs always without asking the user (and with that also without DIALOGSALLOWED), unless you pass the flag NONEWMARKERS. [*] Object and other nodes in caches, i.e., the partial scene graph returned by BaseObject::GetCache and generated by ObjectData::GetVirtualObjects, are allocated each time the cache is being built and therefore also have a new marker each time. But you cannot (or better should not) try to build base links, undos, xrefs, etc. for object and other nodes in caches. TLDR: Markers do not work for caches.
  • Dynamically Add Massign to Material in Material Editor

    c++
    2
    0 Votes
    2 Posts
    14 Views
    ferdinandF
    Hey @ECHekman, I currently use INCLUDE Massign in my .res file. However i would like to do this dynamically so that I can change the order at which it is placed in the Material Editor and Attribute Editor. Is there a way to do this? There is no INCLUDE which you could call programmatically in GetDDescription, but there is Description::LoadDescription. When Cinema 4D is asking you to update a certain part of your description, you could try to call it on your description instance but not with its id, but Massign. This is however very likely to fail or cause crashes, because your description is already being processed, I never tried this myself though. Another approach could be to allocate a dummy description, then load your resource (e.g., Massign) into that dummy description, to then copy parameters bit by bit into your active/actual description passed into GetDDescription. But the whole approach of a 'dynamic include' is a bit backwards without context why it has to be this way. Because when you dynamically include something, this also means you have to remove existing data where it shall not be included anymore. Which is possible but unnecessary extra work. Also copying a whole partial description is not a great idea performance wise, GetDDescription is being called a lot. The better approach would be to just include your partial description in your res file in the a place where it might appear and then dynamically change the parent of that container. As this workflow would be the common dynamic description workflow. Additionally Also is there a way to hide settings from the Obaselist group or hide settings in the Material Editor but keep them in the Attribute editor? No, both managers are more or less just a DescriptionCustomGui, they will always show the same data, unless you have access to the implementation (which you do not). When you would implement your own material dialog, with your own DescriptionCustomGui in it, you could mangle what is shown in this particular description view to your liking, there are multiple methods to highlight, filter, and modify a description on that class. To operate this dialog you would then set this description GUI to the material, shader or whatever BaseList2D you want to display, and then either filter or outright modify the description which is being displayed. Cheers, Ferdinand
  • Adjust spline layers settings in Field Force Objects with Python?

    r25 python
    2
    0 Votes
    2 Posts
    42 Views
    ferdinandF
    Hey @Simon-Lucas, 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 Do you really mean you are on R25? Or do you mean you are on 2025? Anyway, please share an example scene and the code you have so far. Otherwise we won't be able to help you. Cheers, Ferdinand
  • Educational Licenses

    python windows 2025
    7
    1
    0 Votes
    7 Posts
    151 Views
    ferdinandF
    FYI, I have not overlooked this question here. Will post an answer soon(ish).
  • VertexMap Display (Behavior Equivalent to Double-Click)

    2026 python windows
    3
    0 Votes
    3 Posts
    51 Views
    ymoonY
    @ferdinand Thank you. It works well. --> tag.Message(c4d.MSG_EDIT)
  • 0 Votes
    5 Posts
    68 Views
    chuanzhenC
    @ferdinand Thanks for reply Other channels(BOX,X-Ray....) also seem to be unfeasible, and currently it seems only can accept this limitation (affected by depth of field) Due to the current not to transfer to C++, some plugin ideas are indeed somewhat unusual only using some simple C4D drawings to generate Bitmaps, so I did not pay attention to the handling of Ocio. I will carefully read the manual on this aspect in the document.
  • How do I return a list of names and ids for a drop down menu?

    windows python 2025
    3
    0 Votes
    3 Posts
    58 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
  • How to get the fully resolved file path of an upcoming rendering?

    2
    0 Votes
    2 Posts
    37 Views
    ferdinandF
    Hey @BigRoy, Thank you for reaching out to us and your question. We do not allow for topics that are a collection of questions from one user. New questions mandate new topics, unless they are clear follow up questions on an existing topic. The reason for this is so that this forum remains a searchable database. Please create new topics on your own in the future. I have forked your topic. Your topic is also lacking an actual question. Here I can infer your question, but for more complex subjects I might not be able to. It is important to have a literal question close to the start of your topic. See Support Procedures: How to Ask Questions and the examples below for what makes a good technical question. From the context I am assuming here the question is: Is there an API function that returns the full physical file path of a rendered file for a given render setting and in particular the Save > Name format? [image: 1762787643631-34d82dba-bf2f-411a-8489-a8dccc5d9f77-image.png] The answer is unfortunately that no such function exists. When you need this feature, you have to hard code it yourself. Cheers, Ferdinand
  • Best place to do version conversion for scenes and assets

    c++
    2
    0 Votes
    2 Posts
    43 Views
    ferdinandF
    Hey @ECHekman, Thank you for your question. This is a tricky one. When NodeData::Read is emitted, Cinema 4D is currently reading a file and in the process of deserializing your node; so it indeed is not yet 'fully instantiated', as that is the whole point of the method. You are here not meant to manipulate the scene graph (the node is not yet attached), but read and store data on your plugin hook. I.e., you are supposed to do something with your MyMaterialData and not the GeListNode/BaseMaterial which is being passed in as the first argument of Read. The common workflow is to read data from the HyperFile which is passed in and store it on your NodeData instance. Passed in is also the important version number (which is called for some odd reason disklevel) of your HyperFile container. As always, you are bound to the main thread for scene graph manipulations. So, NodeData::Init is not a good candidate when wanting to poke around in the scene graph, as it can be called off main thread and on dettached dummy nodes. A good approach is to hook into NodeData::Message and listen for MSG_DOCUMENTINFO to catch a document just having been loaded. This is for your specific case of wanting to manipulate nodes which have been deserialized. If you want to manipulate node instantiation, MSG_MENUPREPARE is a god choice. But it is only called for nodes which have been instantiated from direct user interactions such as a menu click. So, MSG_MENUPREPARE is not being emitted for nodes loaded from a file. The drill to do what you want to do would be: [optional] Overwrite NodeData::Read/Write/CopyTo to read, write, and copy custom data, and possibly use/detect different disk levels to catch different versions of a node type being loaded. You would have to match this with incrementing the disk level in your RegisterMaterialPlugin call, so that there are old and new versions of your material type out there. Hook into the document being loaded, check the scene state, and possible data left behind for you by NodeData::Read and ::Init. Manipulate the scene graph to your liking. Cheers, Ferdinand
  • How to compute Redshift MoGraph index ratios (RSMGIDRatioColor)?

    2025
    8
    2
    0 Votes
    8 Posts
    175 Views
    B
    @ferdinand Thanks!
  • set GvNode value via python

    windows python 2026
    4
    1
    0 Votes
    4 Posts
    115 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
  • Is it possible to hide the Basic Tab in the attribute viewer?

    c++
    3
    1
    0 Votes
    3 Posts
    98 Views
    CJtheTigerC
    Hi @ECHekman, Please also think about UX when deciding something like this. Your everyday C4D user expects just about any object to be customizable in this regard, even if they mostly don't use it. And while I think that objects like fields or deformers don't need things like X-Ray or the visibility flags and I think that it would be better to hide those for objects that don't use them, it's still established and expected as a common base. Unless this is something that's adjusted in all of base C4D I don't think it's a good idea to break this sort of user experience for your own plugins. Coherence is a big thing in UX if you want to make your plugin feel like a seemless integration. Also unless you're making this plugin solely for yourself you better believe that some peaceful day some user will show up yelling "why can't I change the icon color?" which even might be your future self.
  • Python tag or Python node in Xpresso crash Cinema 4D

    python 2026 windows
    14
    0 Votes
    14 Posts
    515 Views
    ferdinandF
    FYI, there has been a verified fix for the Booleans crash, it will be included in an upcoming release of Cinema 4D.
  • Several Asset Browser Issues

    2026 2025 python
    7
    0 Votes
    7 Posts
    359 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
  • Usage of SplineHelp.InitSplineWith() regarding flags -> returned data

    python
    5
    0 Votes
    5 Posts
    146 Views
    M
    Hey ferdinand, I mixed different questions - I'll keep to the theoretical part to keep the Post relevant to the original question. I appreciate the informative post as always. Parametric concept of a spline is clear to me -> that's why i asked about "C4D can not get more precise than a 0° degree line object ?" which was formulated to lose as 0° is mathematical problematic? and I know 0.1 would be better. sorry about that. The tip about spline 'Adaptive' setting was a good hint, I never use it in work so I forgot about it. So to summarize regarding my question. The Line object always samples what the user has set in the spline settings and not by a unknown internal setting (for example to give good viewport performance, or nice equal sections) And its a better fit for my application, hence there is no "linear transport" shenanigans going on. And if I want to display a high resolution of my calculation, I make an internal copy of that spline and edit the spline settings, instead of sampling a low division spline with spline helper. Normalization / Jagged lines: I use Menger-Krümmung. -> will open a new topic. Your code might come in handy when I'll extend the plugin to polygon meshes. ... ... ... Remark as humans cannot see that level of detail. I have to disagree on this topic. As a tell tale we import CAD data here into C4D to evaluate the work of the engineers. Not long ago I said to our surface engineer. "take a look at the end of that surface something feels off, it looks like the surface is lifting" -> The engineer reportet back "you just spoted a 0.001mm surface deviation of an unconstrained spline ending I did not set." We could argue that the seen result was in fact not 0.001mm but perhaps to surface setup more than 1mm, (or by some rounding error C4D normal tag shading where continuous and then stagnant for 1 pixel) but my point is settings in precision are important and carry through till the final product. Even though a setting of 0.1° for a spline might might be unsuitable in most cases (rendering, performance, ...) it is not for evaluation. So even C4D and all that rounding / conversion / floating point precision might look like unrealiable, unecessary or even esotheric it caries through to a certain degree and its more reliable than you think. (Yes we evaluate in the original CAD Program, but the tale is that I saw something even after importing it into C4D) As reference to why I need this: Blender Plugin: Surface Diagnostics - Josef Ludvík Böhm Thank You
  • Ctr+drag copy vs internal copy in CopyTo function in C++

    Moved 2025 c++ windows macos
    6
    0 Votes
    6 Posts
    345 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; }
  • Setting Preferences via script

    python 2025 2026
    5
    0 Votes
    5 Posts
    176 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
  • Editable Object Plugin returns Null after scaling

    python
    4
    0 Votes
    4 Posts
    193 Views
    ferdinandF
    Hey @JH23, there is no need to apologize for a lack of context. Now that I read your first posting again, it is actually perfectly clear what you are asking for. I just did not read it properly, my bad, sometimes this happens when I am in a hurry. The answer to your question is not trivial, but the TLDR is that you have only little control over it. There exist two commands, 'Current State to Object' (CSTO) and 'Make Editable' (ME). CSTO is basically the less aggressive version of ME, where it runs through all the elements of a cache of something and tries to collapse them gracefully (which could result in output that is still collapsible itself). ME more aggressively flattens the cache hierarchy. For both commands, at the very basic level, there is the distinction between generators and and non-generators. When you CSTO some generator object which just holds another generator in its cache (imagine your GetVirtualObjects or main just returning c4d.BaseObject(c4d.Ocube)), it will just return a copy of that generator (which is still a generator, i.e., something that has a cache). But when your generator returns a non-generator, i.e., a discrete PolygonObject, it will wrap the copy of this cache in a null when returning it. But that is not all of it. Because, there are also functions such as TransferDynamicProperties and OptimizeHierarchy which ensure that relevant scene data is kept and the output is as compact as possible. They run after ME/CSTO by further manipulating the ME/CSTO output. The original MEed/CSTOed object might have had a transform which must be copied onto the flattened output, so that it has the same transform in world space. Objects often also hold hidden data in form of tags in Cinema 4D which might have to be copied from the original object onto the flattened result. This all might lead to the functions either removing unnecessary null objects which before have been created by ME/CSTO or adding new ones. That your scale manipulations result in an extra null is a bit surprising, but that must have to do with the transform normalization code which runs after ME/CSTO. At first I thought, it might transfer the scale to the null object, so that it can normalize the scale of the cache. But that is not the case, it is still the cache object which has the scale 2 when you CSTO/ME your generator. I would really have to debug this in detail, to find out why exactly this happens. But in general, I would advise against scaling the matrix/transform of an object, and instead, apply the scale to the points of a discrete PolygonObject or the parameters of a BaseObject itself. Transforms which have axis components of non-unit length (i.e., a scale != 1.0), often lead to problems. The important message is that even when you find a solution which gives you the desired flat cache for this problem (by for example scaling the points), there could be countless other scenarios such as a user having a MoGraph tag on your generator or something like that, where then MEing or CSTOing would result in a null + cache output. You should not build your code on the assumption that the MEed or CSTOed output of your generator will always be flat (or the opposite), because you simply cannot guarantee that. Not the most satisfying answer, I know, but I hope it helps to clarify the situation. Cheers, Ferdinand Here is how you could post process points with a transform: def main(): obj: c4d.PointObject = Cube() # We could of course also just pass this #transform into your #Cube function, to do it right # there. But we are making a point of doing this as a post processing step (so that we can do # this at any point in time). We scale all points by a factor of 2 and rotate them 45° on the # Y axis in the coordinate system of the object. When we now CTSO your object, we have a flat # output. transform: c4d.Matrix = (c4d.utils.MatrixScale(c4d.Vector(2, 2, 2)) * c4d.utils.MatrixRotY(c4d.utils.DegToRad(45))) obj.SetAllPoints([n * transform for n in obj.GetAllPoints()]) obj.Message(c4d.MSG_UPDATE) return obj
  • Plugin module import freezes on startup

    python macos windows 2026 2025
    4
    1 Votes
    4 Posts
    267 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