Group Details Private

Global Moderators

Forum wide moderators

  • RE: 2025 Python - No Plugin UI, can't find the reason

    Hi @derudo,

    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: Asking Questions.

    About your First Question

    There's no single known "thing" we're aware of that could lead to such behavior. In other words, it can be lots of different "things" that caused the symptoms you're describing. Hence, without any further details everything I'm pointing out here is highly speculative.

    I also don't think answering the question "What could have happened?" is by any means productive, so let's switch the point of view to "How one could diagnose it?".

    Since you haven't posted any console output in your question, I assume you haven't checked that. This would actually be the first thing to check. Please refer to our Python Console Manual and searching on the forum.

    Next, try to figure out if it's only UI that stopped appearing or your plugin isn't registered at all anymore. You can do this by checking (e.g. with some temporary print statements) if you plugin is actually functional.

    On the screenshot you've posted the structure is kind of strange, but we're not sure if it's just a visualization matter of your software. Namely, the plugin.pyp file is expected to reside in the root of the plugin folder. Essentially, the following hierarchy level is not supposed to be there:
    232c47cd-171e-4642-8c12-7661b51e6ce5-image.png
    Please refer to the article Plugin Structure Manual: Directory Structure and double check your plugin in this regard.

    If the points above don't lead to any result, try debugging it step-by-step. Namely, strip out everything except the essentials (like plugin registration) and continue adding things piece-by-piece until it starts failing. By the way, one can easily achieve this by using any version control system (e.g. git), as they typically provide a lossless way to manage your code (i.e. remove and add parts of code, without being worried to lose any of them). This approach could also have prevented your scenario in first place, when something stopped working and there're no clues about the change that has lead to it.

    If this still doesn't help, you can share your plugin here (or send us via contact form, when confidential information is involved). However, I must warn you that our Support Procedures still fully apply, namely:

    We cannot debug your code for you and instead provide answers to specific problems.

    Cheers,
    Ilia

    posted in Cinema 4D SDK
  • RE: color space of MODATA_COLOR

    Hey @BruceC,

    Thank you for the added information, but I will still need that step by step bug report. Without it, I will not touch a debugger or a scene file. There are a plethora of reasons why this could go wrong. I am still very much in the dark at what you are looking exactly (e.g., are you using our Picture Viewer to view render results, or do you use your render engines one. What are your rendering and how, etc., pp.).

    As I told you, applying a manual transform is not intended. It might be what you have to do as a work-around for older versions which we won't (hot)fix anymore, but there is no default answer here for you. But for all of that I first need a reliable bug/issue report which I can reproduce, and then other than you look at with a debug version of Cinema 4D. As I said, feel free to use your render engine, and please be precise in the reproduction steps. If you want to, you can also use our beta program bug tracker instead (since you are an MRD).

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Retrieve the current Unit and listen to changes

    Hey @Cankar001,

    Good to hear that you found your solution! One minor thing - you should avoid ApplicationOutput in production code, as it leads to console spam which we want to avoid in Cinema 4D. Using it in test code is fine. See Debug and Output Functions for alternatives.

    An even better way to do what you did in your code would be to use error handling. E.g., your code could look like this:

    
    // Your original function, I turned this into a function using our error system, indicated by the 
    // Result<T> return type.
    static maxon::Result<maxon::Float> GetCurrentUnitScale(const BaseDocument* const document)
    {
        // The error scope handler for this function, i.e., all error returns exit through this handler.
        iferr_scope;
    
        // When there is no document, we return an error. When printed, this will then print the error 
        // message and the source code location, e.g., myfile.cpp:123.
        if (!document)
            return maxon::NullptrError(MAXON_SOURCE_LOCATION, "Invalid document pointer."_s);
    
        // Your code goes here.
        // ...
        return 1.0f;
    }
    
    // A function calling this function which does use error handling itself.
    static maxon::Result<void> Foo(const BaseDocument* const document)
    {
        iferr_scope;
    
        // Call the function and return, i.e., propagate the error upwards when there is one.
        const maxon::Float unitScale = GetCurrentUnitScale(document) iferr_return;
    
        // voids in the Result<T> return type are returned with maxon::OK.
        return maxon::OK;
    }
    
    // A function calling this function which does not use error handling itself, i.e., the error
    // must terminate here.
    static bool Bar(const BaseDocument* const document)
    {
        // Here we use a manual scope handler to terminate the error chain. You have often to do this in
        // Cinema API (e.g., ObjectData::GetVirtualObjects), as methods there are not error handled 
        // opposed to the Maxon API.
        iferr_scope_handler
        {
            // Print a debug message with the passed in error #err and the name of this function. And
            // force a debugger to halt when some condition is met.
            WarningOutput("@ failed with error: @"_s, MAXON_FUNCTIONNAME, err);
            if (someErrorCondition)
                DebugStop();
    
            return false;
        };
    
        // Call the function (we still have to handle the error with an iferr_return), and then let it
        // terminate in our manual scope handler.
        const maxon::Float unitScale = GetCurrentUnitScale(document) iferr_return;
    
        return true;
    }
    

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Python script for keyframing node material geometry opacity

    Hi @mia-elisenberg,
    Thanks for reaching out to us!

    I must note that as per our Support Procedures we cannot debug your code. Hence, in your future postings I kindly ask you to try simplifying your code to a minimal viable example, which highlights your question.

    Regarding your question, creating keyframes for nodes can be a little trickier comparing to the ordinary objects, but the general data accessing scheme stays the same. @Dunhou has thankfully posted the simplified example for your question, which already shows crucial pieces of how one would access the CTrack, CCurve and CKey for the node port. (@Dunhou I won't get tired showing our appreciation in playing an active role in our community! 😉 ). Namely, you're expected to use GetBaseListForNode to get the BaseList2D element that corresponds to the node you have. Additionally, NimbusBaseInterface.GetDescID can be used to get the DescID of the port. After you have this information, the process of interacting with animation data isn't any different. Your can check the animation examples in our repository: Cinema-4D-Python-API-Examples/scripts/04_3d_concepts/scene_elements
    /animation
    .

    The only thing I'd like to point out here is handling color data. Namely, CKey is designed to operate with float values, but Opacity channel works with color data. Hence, you need to create a separate CTrack for each color channel. You basically do this by pushing your DescID one level further to access the elements of your color data. Please find small example below (based on the code shared by @Dunhou):

            descLevelsRGB: list[c4d.DescLevel] = [
                c4d.DescLevel(c4d.COLOR_R, c4d.DTYPE_REAL, 0),
                c4d.DescLevel(c4d.COLOR_G, c4d.DTYPE_REAL, 0),
                c4d.DescLevel(c4d.COLOR_B, c4d.DTYPE_REAL, 0)
            ]
            opacityValue: list[float] = [0.55, 0.66, 0.77]  # example data to store in the keyframe
    
            ctime: c4d.BaseTime = c4d.BaseTime(doc.GetTime().GetFrame(doc.GetFps()), doc.GetFps())
            for opacityChannelDescLevel, opacityChannelValue in zip(descLevelsRGB, opacityValue):
                # Get opacity channel DescID and push it to access color channel
                channelDescID: c4d.DescID = nimbusRef.GetDescID(opacityPort.GetPath())
                channelDescID.PushId(opacityChannelDescLevel)
                
                track: c4d.CTrack = c4d.CTrack(opacityPortBL2D, channelDescID)
                opacityPortBL2D.InsertTrackSorted(track)
    
                curve: c4d.CCurve = track.GetCurve()
                key = c4d.CKey()
                track.FillKey(doc, opacityPortBL2D, key)  # this is optional
                key.SetValue(curve, opacityChannelValue)
                key.SetTime(curve, ctime)
                curve.InsertKey(key)
    

    Cheers,
    Ilia

    posted in Cinema 4D SDK
  • RE: Displacer Deformer with Mograph Camera Shader not working

    Hello @bioquet,

    Thank you for reaching out to us. While we understand that for users the distinction is not as clear as for us, I must remind you that developers.maxon.net is exclusively meant for API issues. For end user questions, as for example how to use MoGraph, please use our Support Center.

    Regarding your question of if it would be possible to do this in Python: Generally I would say "yes", as there are only a few things which are not possible with our Python API when you are an experienced developers.

    The general idea would to render the relevant scene with c4d.documents.RenderDocument or the c4d.documents.BatchRender (i.e., what is called the "Render Queue" in the UI). What to pick depends a bit on how you fashion the rest of the functionality. You would then either load that bitmap into a Bitmap shader, or create such shader, and then assign that shader to the displace deformer. Done would this be either from a Script Manager script (or CommandData plugin) or via a Python tag (or a TagData plugin).

    The exact path to pick depends on how "dynamic" you want this to be. Your question mentions that 'the source view is animated and many render are needed in production', which is a little bit ambiguous. But the gist would be that when you have to update the deformer image each frame, you could go two routes:

    1. The command route (i.e., Script Manager script) and render out a movie for the entire length, and load that mp4, mov etc. as the source in your displacement shader. Video compression could make this tricky, but otherwise this is the 'easy' solution.
    2. The tag route. Here you would render each frame on demand. The problem is here that you have to deal with:
      1. Threading issues TagData::Execute and by extension the main function of a Python tag do run off-main-thread, which entails certain Threading limitations. You might have to clone the document for rendering (it is a bit hazy to me which document is being rendered).
      2. Priority issues. Expressions, i.e., tags, come after generators in the scene execution. So, when a pass (think of it as a 'frame' - not really correct but close enough) is executed, Cinema 4D would first execute all generators, including the displace defomer, and then execute the expressions (i.e., your tag). When this pass was for a frame change, and you then update the bitmap of the displace deformer, it is 'too late', as the deformer already did its deformation. This can be solved by setting the execution priority of the tag to generators and a negative value, so that it is executed before all generators. But this can still lead to issues.

    What I would probably do when I would have to write this for a production environment and would want this to be done quickly, while side stepping potential issues from using a movie as a displacement source, would be going for a mix of both options. First a relatively simple Script Manager script which pre-renders the displacement source into a series of images relative to the document, e.g., tex/displace/{object_hash}/... and then a simple Python tag on each displacer, which grabs the correct texture file for each frame for the given displacer and sets it on the shader loaded into the displacer.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: color space of MODATA_COLOR

    Hey @BruceC,

    thank you for the scene. Just as an FYI, you can always share data confidentially with us via sdk_support(at)maxon(dot)net. In general, we encourage all users, including Maxon Registered Developers, to discuss problems in public, so that the whole community can benefit. But for sensitive topics there is of course always the option to switch to mail.

    Regarding your scene file:

    1. Could you please share the exact version you are using for testing. As I said, I am aware that you want to target multiple versions, but for bug reporting we should have a precise version, so that I can look at that exact version.
    2. Please also provide a bullet-point step-by-step bug report as lined out before. See Support Procedures: Reporting Bugs and Crashes for details. Our insistence on these bug reports might seem a 'unnecessary' from the user side, but they really make a world of difference, when you try to debug something as complex as Cinema 4D.

    Cheers,
    Ferdinand

    PS: I am a bit busy with the new C++ SDK at the moment, I will probably only get to having a closer look at this by the 27th.
    PPS: What I forgot to mention, is that it could also be the Picture Viewer itself which is here interfering (i.e., the actual bitmap saved to disk is okay, the PV is just displaying it incorrectly. We had a few issues with the PV and OCIO over time).

    posted in Cinema 4D SDK
  • RE: How to store "cached" data in the document from the scenehook?

    Hello @Havremunken,

    no, there is no code example for what you are trying to do. There is the ActiveObject C++ Example, which is supposed to demonstrate branching but does IMHO a terrible job at it. When you want to look at some resources, I would recommend these (although none of them will cover your subject exactly):

    What is Branching

    Branching is how Cinema 4D realizes arbitrary entity relationships, i.e., a scene graph. By default, it seems a bit like Cinema 4D only implements hierarchical relationships via GeListNode, but that is not true, as there is also the function GeListNode::GetBranchInfo. So, while a node can have a parent, a list of children, and up to two siblings, it can also have a list of other relations which are explicitly not hierarchical.

    A BaseDocument is for example also a node, its previous and next siblings realize the list of loaded documents. The branches of a document realize all its content, the Obase branch holds for example the GeListHead (a special type of GeListNode for the root of a branch) to which all top level objects of the scene are parented (which then hold their local hierarchies). The 'trick' is then that every node in a scene has such branches, an object has branches for its tags, tracks, and more; a material has branches for its shaders, tracks, and more, and so on.

    Please see the links I have posted above for details.

    How Do I Implement Branching?

    Generally, at least without getting hacky, you cannot implement branches for nodes that you do not own. So, you can not add a foo branch to documents or objects in general, you can only add a foo branch to the specific MyFooObjectData plugin you implement (or any other node type you implement). Since, it worked apparently quite well last time, I will just write some mock code of what you are trying to do (very high level and obviously untested and uncompiled).

    
    // Plugin ID for the generic asset node and at the same time its branch ID. T is not only the prefix
    // for tag types but also generic node types, e.g., Tbasedocument, Tpluginlayer, Tgelistnode, etc.
    static const Int32 Tassetnode = 1030001;
    
    // Realizes your asset manager just as before but this time it internalizes its asset data.
    //
    // Since a scene hook is a node and part of the scene graph, it can have children and branches. 
    // We therefore are going to establish an Tassetnode branch in the scene hook.
    class AssetAccessManager : public SceneHookData
    {
      INSTANCEOF(AssetAccessManager, SceneHookData)
    
      // The head of our custom asset branch.
      private:
        GeListHead* _assetHead;
    
      public:
        // Returns either an existing asset branch head for the AssetAccessManager node or creates a new one.
        GeListHead* GetAssetHead()
        {
          iferr_scope_handler
          {
            return nullptr;
          };
    
          // We already established an asset head before.
          if (_assetHead)
            return _assetHead;
    
          // Get the scene hook node for #this SceneHookData and try to find its asset branch.
          const GeListNode* node = Get();
          if (!node)
            return nullptr;
    
          Bool result = node->GetBranchInfo(
            [_assetHead](const BranchInfo& info) -> maxon::Result<maxon::Bool>
            {
              // Check if the branch is an asset branch.
              if (info.id != Tassetnode)
                return false; // Continue searching/iterating.
    
              // We found the asset branch, we can stop iterating.
              _assetHead = info.head;
              return true; 
            },
            GETBRANCHINFO::NONE) iferr_return;
    
          if (_assetHead)
            return _assetHead;
    
          // Create a new branch head for this asset manager's asset branch.
          _assetHead = GeListHead::Alloc();
          if (!_assetHead)
            return nullptr;
          
          // Attach the branch to the scene hook.
          _assetHead->SetParent(node);
    
          return _assetHead;
        }
    
        // Implements the branch info for your node, so that Cinema 4D knows about the asset branch.
        // It is important to implement this, without it Cinema 4D will crash.
        Int32 FlowTag::GetBranchInfo(GeListNode* node, BranchInfo* info, Int32 max, GETBRANCHINFO flags)
        {
          GeListHead* assetHead = GetAssetHead();
          if (!assetHead)
            return 0;
    
          info[0].head = assetHead;              // The head for the new branch.
          info[0].id = Tassetnode;               // The ID of the branch. This is usually the ID of the
                                                 // types of nodes to be found in the branch, e.g., Obase.
          info[0].name = "myAssetData"_s;        // A label for humans for the branch, make sure it is
                                                 // reasonably unique, there might be bad code out there
                                                 // which tries to find branches by name.
          info[0].flags = BRANCHINFOFLAGS::NONE; // The flags for this branch, see docs for details.
        
          return 1 // The number of branches we have defined.
        }
    
        // Realizes a custom function to add assets to the asset manager and with that to the scene.
        BaseList2D* AddAsset(const Url& url)
        {
          // Instantiate an asset node, it fully encapsulates the loading and storage of asset data.
          BaseList2D* asset = BaseList2D::Alloc(Tassetnode);
          if (!asset)
            return nullptr;
    
          // Load the asset data from the URL, this is done via a custom message, just as discussed in
          // example from the old thread.
          asset->Message(MSG_LOAD_ASSET, (void*)&url);
    
          // Add the asset to the asset branch. We realize here just a plain list, but we could also have
          // complex hierarchies, or assets which have branches themselves.
          GeListHead* assetHead = GetAssetHead();
          if (!assetHead)
            return nullptr;
    
          asset->InsertUnderLast(assetHead);
    
          return asset;
        }
    }; 
    
    // Realizes the asset node.
    //
    // We realize here a plain NodeData, the base type of ObjectData, TagData, SceneHookData, etc. We
    // could also realize a specialized node, but custom branches are one of the few cases where it can 
    // make sense to realize a plain node. But you could also realize a specialized node, e.g., 
    // MyAssetObjectData. You can also store existing node types in your branches, e.g., it is totally 
    // valid to implement your own Obase (i.e., object) branch. Might be better in your asset case, I do
    // not know. In that case you could only serialize the data an object does serialize, points, 
    // polygons, tags, tracks, etc., but would not have to implement your own node. Pulling up an asset 
    // would then also not work via a message but via a custom function, e.g., static BaseObject* 
    // LoadAsset(const Url& url).
    //
    // See also: 
    //  SDK/plugins/example.main/source/object/objectdata_hyperfile.cpp
    class MyAssetData : public NodeData
    {
      INSTANCEOF(AssetAccessManager, SceneHookData)
    
      private:
        // The asset data, this is just a mock, you would have to implement this. This example is extra
        // stupid, because something like an Int32 is easily stored in the data container of a node. An
        // option which always exists, and while slightly less performant than customizing reading and
        // writing the node, it is also much easier to implement.
        Int32 _assetData;
    
        // Loads the asset data from a URL.
        Bool LoadAsset(const Url& url)
        {
          _assetData = 42;
          return true;
        }
      
      public:
        // Handles messages for the asset node.
        Bool Message(GeListNode* node, Int32 type, void* data)
        {
          switch (type)
          {
            case MSG_LOAD_ASSET:
            {
              // Load the asset data from the URL.
              Url* url = reinterpret_cast<Url*>(data);
              if (!url)
                return false;
    
              return LoadAsset(*url);
            }
            break;
          }
    
          return true;
        }
    
        // Serializes the asset node. What I am showing here should only be done when we have to serialize
        // truly complex data, as for example a texture with meta data. For that specific case one could
        // ask how sensible it is to blow up scene sizes by internalizing textures in the scene graph. 
        // Then one could end up with a compromise, where one stores only information in the scene about 
        // where a localized texture has been stored, i.e., we just reinvented texture files :D.
    
        // When you implement serialization, you must always implement Read, Write, and CopyTo. See the
        // NodeData manual and SDK for details, I am too lazy to even mock-up all of them here, I am only
        // going to demo Read/Write.
    
        // Writes all data of this node to the scene file that is not already stored in the node's data
        // container.
        Bool Write(GeListNode* node, HyperFile* hf)
        {
          if (!hf)
            return false;
    
          // Write the asset data to the scene file.
          if (hf->WriteInt32(_assetData) == 0)
            return false;
    
          return true;
        }
    
        // Reads all data of this node from the scene file that is not already stored in the node's data
        // container.
        Bool Read(GeListNode* node, HyperFile* hf, Int32 level)
        {
          if (!hf)
            return false;
    
          // Read the asset data from the scene file.
          if (hf->ReadInt32(&_assetData) == 0)
            return false;
    
          return true;
        }
    }
    

    I hope this gets you going. I cannot stress enough that this is a sktech.This will quite likely not compile, but it demonstrates the core methods and principles. Feel free to come back with concrete code yours when you need help.

    But I currently do not have the time to write a full example for this from scratch.

    Cheeers,
    Ferdinand

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: color space of MODATA_COLOR

    Hey @BruceC,

    So I think in this case, it could be safely assume the data is linear values?

    Normally, there should be no assumptions, the data should just be what the color management says. But I now replicated your field driving colors setup, and I too stumbled upon something which I cannot explain, at least at the moment.

    I would really recommend sharing a scene file with us, and a more formal issue report (see Support Procedures: Reporting Bugs and Crashes). Feel free to also use your render engine in the file(s), we have licenses, but you always have the best chances of help, when you can isolate an issue down to the core API.

    And while I understand that you want to support many versions, you should also establish a concrete version of Cinema 4D you are using for testing.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: How to get the attributes of rsramp node in c++?

    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: Asking Questions.

    About your First Question

    You get these parameters just as any others. Please show us the code you have, as I am not going to write here a complex node example from scratch. The ramp itself is just a variadic port, and each knot in the ramp is then a port bundle of type com.redshift3d.redshift4c4d.portbundle.gradient.

    6c249a37-cd2c-4bc5-81ab-37d651b2620b-image.png
    d74bed8c-10b5-4f5d-9551-4876fb75b374-image.png

    So, the node path for the input port of the knot position of the 0ths node would be:

    // This node path goes from the root of the true node, i.e., the RS Ramp node itself.
    const String path = "<com.redshift3d.redshift4c4d.nodes.core.rsramp.ramp/_0/position"_s;
    

    Please note that there is absolutely no guarantee that each variadic port does gives its 0th child the ID "_0" and its n-th child the ID "_n-1". I just happen to know that this will be the case for a default RS Ramp node. In practice you will always have to get all children of the varidiadic port, and then select the desired child by index in that list.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Help Needed: Filtering Selected Edges by Length

    Hello @myosis,

    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: Asking Questions.

    About your First Question

    All the methods you list simply do not exist (neither in C++ nor in Python), see c4d.utils.Neighbor for the type overview.

    ⚠ I therefore must assume that you are using an AI, like, for example, ChatGPT, which hallucinated these methods. Please note that we reserve the right to refuse support when confronted with undisclosed AI gibberish, especially for beginner content. Always state when you used an AI to generate code.

    Something such as an edge does not exist concretely in our API and many other APIs, i.e., other than for points and polygons, there is no explicit data type for edges which would be stored. Edges are defined implicitly by CPolygon. To filter a selection for edges of a certain length, you would have to convert edge indices to polygon and point indices and then measure the distance between the relevant points.

    Cheers,
    Ferdinand

    Result

    074e2ada-a9ce-45b4-800a-acf7e060941a-image.png

    Code

    """Deselects all edges in the edge selection of the active object whose edge length exceeds
    MAX_EDGE_LENGTH.
    
    Must be run as a Script Manager script with an editable polygon object selected.
    """
    import c4d
    
    doc: c4d.documents.BaseDocument  # The currently active document.
    op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
    
    MAX_EDGE_LENGTH: float = 25.0  # The maximum length of an edge before to be considered too long.
    MAX_EDGE_LENGTH_SQUARED: float = MAX_EDGE_LENGTH ** 2  # The square of `MAX_EDGE_LENGTH`.
    
    def main() -> None:
        """Called by Cinema 4D when the script is being executed.
        """
        if not op or not op.IsInstanceOf(c4d.Opolygon):
            raise ValueError("The selected object is not a polygon object.")
        
        # Get the edge selection of the object and turn it into a list of selected edges indices. Also, 
        # get the points and polygons of the object.
        selection: c4d.BaseSelect = op.GetEdgeS()
        selectedEdges: list[int] = [i for i in range(op.GetEdgeCount()) if selection.IsSelected(i)]
        points: list[c4d.Vector] = op.GetAllPoints()
        polygons: list[c4d.CPolygon] = op.GetAllPolygons()
    
        def getPointByIndex(poly: c4d.CPolygon, index: int) -> c4d.Vector:
            """Returns the point of the polygon at the given index.
    
            CPolygon has no index access, so we fix that here.
            """
            if index == 0:
                return points[poly.a]
            elif index == 1:
                return points[poly.b]
            elif index == 2:
                return points[poly.c]
            elif index == 3:
                return points[poly.d]
            
        # Iterate over the edges and find the one's that are longer than MAX_EDGE_LENGTH. An edge index
        # is defined as: 
        #
        #   "The edges are indexed by 4 * polygon + edge where polygon is the polygon index and edge is 
        #    the edge index between 0 and 3."
        #
        # So, we must revert that here, then measure the edge length, and collect all too long edges.
        tooLongEdges: list[int] = []
        for edgeIndex in selectedEdges:
            polygonIndex: int = edgeIndex // 4
            edgeInPolygonIndex: int = edgeIndex % 4
            poly: c4d.CPolygon = polygons[polygonIndex]
            pointA: c4d.Vector = getPointByIndex(poly, edgeInPolygonIndex)
            pointB: c4d.Vector = getPointByIndex(poly, (edgeInPolygonIndex + 1) % 4)
            # Getting the length of a vector is quite expensive, so we compare the squared lengths.
            edgeLengthSq: float = (pointA - pointB).GetLengthSquared()
            
            if edgeLengthSq > MAX_EDGE_LENGTH_SQUARED:
                tooLongEdges.append(edgeIndex)
    
        # Print the indices of the edges that are too long.
        print("The following edges are too long:", tooLongEdges)
        
        # Deselect all edges in the object's edge selection that are too long.
        for edgeIndex in tooLongEdges:
            selection.Deselect(edgeIndex)
    
        # Push an update event to Cinema 4D to redraw the object.
        c4d.EventAdd()
    
    if __name__ == '__main__':
        main()
    
    posted in Cinema 4D SDK