Navigation

    • Register
    • Login
    • Search
    1. Home
    2. ferdinand
    3. Posts
    • Profile
    • More
      • Following
      • Followers
      • Topics
      • Posts
      • Best
      • Groups

    Posts made by ferdinand

    RE: How to access the BaseContainer of an Command-Dialog

    Hey @ThomasB,

    my apologies, you were quite clear in your first posting that you meant the timeline, I simply overlooked that information. You are talking about this dialog here, right?

    d2a2c1fd-21ac-4a84-b654-0acf3eed24ee-image.png

    My answer from above applies here in general too. This is a command, and you can c4d.CallCommand it, but that is all the control you have. And other than for texture baking, the underlying functionality is unfortunately not exposed. If you wanted something like this, you would have to write it yourself (which would probably be too much work unless one REALLY, REALLY needs this).

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: New Child Render settings with python

    Hello @masterofthejack,

    One more problem I am having is camera that is a part of my setup [...] doesn't update its frustum or aspect ratio until I manually change [... the] settings slightly [...] I should mention that the camera has some Xpresso attached [...]

    I have speculate here a bit, since I do not fully understand what you are doing.

    1. When you change the render data of a document (or change anything else in it), this inherently will flag things as dirty and cause the scene passes to be executed, i.e., stuff to update.
    2. This however does not happen immediately when you run for example the line rdata[c4d.RDATA_XRES] = 42 but when the main loop of Cinema 4D evaluates everything in a scene which is dirty. You can also trigger this yourself manually so that you can benefit early from changes (see below).
    3. c4d.EventAdd() is a necessary component in Script Manager scripts, as things often seemingly 'won't update' . Things actually do update, but the UI does not immediately reflect that without an EventAdd until the user then interacts with the scene (which causes an EventAdd). I would however avoid using EVENT_FORCEREDRAW unless it is really necessary - which seems unlikely here.

    What to do exactly, depends a bit on what you want to do and what your setup is. But in general you seem to run into the problem that the passes are not executed often enough (or at all) on your scene, what we talked about under (2.). You can do this with BaseDocument.ExecutePasses. It usage is quite straight forward, you just run it and things are then updated.

    Cheers,
    Ferdinand

    """Demonstrates how to execute the passes on a document.
    """
    
    import c4d
    from mxutils import CheckType
    
    doc: c4d.documents.BaseDocument  # The active document
    
    def main() -> None:
        """
        """
        # Allocate a cube object and a look at camera tag on it.
        cube: c4d.BaseObject = CheckType(c4d.BaseObject(c4d.Ocube))
        tag: c4d.BaseTag = CheckType(cube.MakeTag(c4d.Tlookatcamera))
    
        doc.InsertObject(cube)
        c4d.EventAdd() # Meaningless here since the passes have not run #doc.
    
        # The cube has no cache at this point, and also the tag did not orient the cube yet.
        print(f"{cube.GetCache() = }")
        print(f"{cube.GetMg() = }")
    
        # Now we execute the passed on doc, specifically the cache and expression (i.e., tags) pass.
        doc.ExecutePasses(bt=None, animation=False, expressions=True, caches=True, flags=c4d.BUILDFLAGS_NONE)
    
        # Now things are "updated".
        print(f"{cube.GetCache() = }")
        print(f"{cube.GetMg() = }")
    
        # What we did here will automatically happen once a Script Manager script ended. There are two
        # scenarios where one has to execute the passes manually.
        #
        # 1. We want to make in code use of an updated scene before Cinema 4D on its own executes the
        #    passes on it to get hold of of information such as the cache or matrix here.
        # 2. Sometimes complex dependencies between scene elements can require passes to be executed 
        #    more than once when we build or update such setups. Three executions on a row should 
        #    suffice even for the most complex setups.
    
        # We have a field driven by tag data which is dependent on particle information. We changed some
        # deep down in the dependency relation and want things to update all at once. Note that it is
        # NOT dependencies == number of executions. Internally, Cinema 4D executes the passes three
        # times in a row at most. Executing the passes is expensive.
    
        # This should wiggle pretty much everything into place.
        for _ in range(3):
            doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_NONE)
    
    if __name__ == '__main__':
        main()
    
    
    posted in Cinema 4D SDK •
    RE: 2024 c4d python doc link error

    Hello @chuanzhen,

    Thank you for reporting this. I have fixed this both for the online and offline docs, and also included a small fix which puts the icon index on the landing page.

    743fc4e1-f9bc-46ea-b26e-f42399688a4a-image.png

    When one is in the habit of hard linking to a specific version of the docs with a bookmark, one must update one's bookmark. Because I had to push a minor revision of the docs to keep the page caches as they should be, i.e., the old revision still does exist but is hidden.

    The link https://developers.maxon.net/docs/py will always point to the most recent version of the Python docs.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: How to access the BaseContainer of an Command-Dialog

    Hello @ThomasB,

    Thank you for reaching out to us. Maxon never exposes UIs, e.g., dialogs, in its APIs and a dialog also does not have data container which you could get or set.

    You can get and set the data containers for tools, but the main characteristic of a tool is that its UI lives inside the Attribute Manager instead of having its own dialog. That does not apply to the Bake Objects dialog, it is not a tool but a command (a distinction which admittedly might seem very subtle for beginners). But as always, we expose the functionalities, the logic, behind things, in this case in the form of the functions c4d.utils.InitBakeTexture and c4d.utils.BakeTexture.

    The py-texture_baker_r18.pyp example demonstrates the usage of these functions.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: R23 Monterey SDK compile errors

    Hey @spedler,

    2023 still supports Xcode 12 and macOS 11 (alongside Xcode 13 and macOS 12) as shown here. I just cleaned up a bit in this list for 2024 to streamline it for future versions, as the list is propagated through all versions.

    For 2024, the list is looking back in retrospective in a certain sense, and I brought things to the "latest tech" for 2023. This will also simplify support of legacy versions for us. But when you still have a Xcode 12.5.1 (macOS 11.X) setup around, you can still compile on it for 2023. But Xcode 13 and Monterey will likely be the preferred build environment for 2023, 2024, and 2025. Internally, we are already gearing up for Xcode 14 & 15 and with that Ventura and Sonoma, and the removal of the Xcode legacy build system that comes with it.

    As a plugin developer you could have two build environments now to build macOS plugins for up to S24.

    • S24-S26: Xcode 12 + Big Sur
    • 2023-2024: Xcode 13 + Monterey (and quite likely 2025)

    I understand that plugin authors often want to support up to R23 or even R21, but that will increase the number of environments you will need. S24 is IMHO the best compromise of effort and output at the moment.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: New Child Render settings with python

    Hello @masterofthejack,

    Although this is your second posting, it is your first thread, so let me welcome you to the Plugin Café forum and the Cinema 4D development 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 Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:

    • Support Procedures: Scope of Support: Lines out the things we will do and what we will not do.
    • Support Procedures: Confidential Data: Most questions should be accompanied by code but code cannot always be shared publicly. This section explains how to share code confidentially with Maxon.
    • Forum Structure and Features: Lines out how the forum works.
    • Structure of a Question: Lines out how to ask a good technical question. It is not mandatory to follow this exactly, but you should follow the idea of keeping things short and mentioning your primary question in a clear manner.

    About your First Question

    In short, this functionality is not public and works in general not too well with Python API as it assumes things to happen in a certain order (which the Python API can violate). The cloning behavior is determined by this setting of the parent you clone from:

    c1869486-5157-45ef-b1b4-644e18a5a7bb-image.png

    When you clone from such parent, you will also clone this setting. It is expressed as a bitflag. The flag is not exposed in the public API (neither Python nor C++) because this is part of the backend behavior of a module. Internally it has the symbol RENDERSETTINGS_BIT_OVERRIDEBEHAVIOUR, and while one can set this flag and emulate other things, the Inherit Override Behavior option is tightly coupled to the Render Settings Menu and we cannot emulate everything it does.

    In practice this can mean that things go out of whack when you just toggle this setting often enough programmatically.

    Cheers,
    Ferdinand

    Result:
    You see there at the end that when I turn the behavior off for a node where we programmatically enabled it, the bitmask of that node goes out of whack, and things aren't anymore as they should be.

    Code:

    """Demonstrates how to toggle the private "Inherit Parent Behavior" flag for a RenderData element.
    
    Note:
        I would strongly advise against making use of this, as you can potentially corrupt your scenes
        with this. It is extremely unlikely, but not impossible.
    """
    
    import c4d
    
    doc: c4d.documents.BaseDocument # The active document.
    
    # This a non-public bit flag for render settings which controls the "Inherit Parent Behavior"
    # setting of a render data node.
    c4d.RENDERSETTINGS_BIT_OVERRIDEBEHAVIOUR: int = 1 << 6
    
    def main():
        """
        """
        # Clone the active render data insert the clone under the parent.
        parent: c4d.RenderData = doc.GetActiveRenderData()
        if not parent:
            return
        
        clone: c4d.RenderData = parent.GetClone(c4d.COPYFLAGS_NONE)
        clone.InsertUnderLast(parent)
    
        # Make sure that the "Inherit Parent Behavior" setting is enabled. When #parent had it enabled,
        # we will have cloned that setting anyways, this is just for the case that we want to inherit
        # from a parent which does not have it enabled (but we want the child to have it enabled).
        clone.DelBit(c4d.RENDERSETTINGS_BIT_OVERRIDEBEHAVIOUR)
        clone.SetName(f"{clone.GetName()} (CLONE)")
    
        # In case we wanted to turn off this behaviour for a clone of a parent which has it enabled, we
        # would have to do this.
        # clone.SetBit(c4d.RENDERSETTINGS_BIT_OVERRIDEBEHAVIOUR)
    
        # This a bit weird stuff ensures that the inheritance relation does not go out of whack so 
        # easily, but we can only emulate what the Render settings implementation is doing internally.
        parent.SetDirty(c4d.DIRTYFLAGS_DATA)
        doc.SetParameter(c4d.DOCUMENT_USERCHANGE, True, (1 << 3))
    
        c4d.EventAdd()
    
    if __name__ == "__main__":
        main()
    

    posted in Cinema 4D SDK •
    RE: R23 Monterey SDK compile errors

    Hey Steve,

    @Rox is talking about R23, you are looking at 2023. Unless @Rox also misspoke, there are multiple versions and years between these two releases ;) The information for 2023 is correct in the documentation.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Capsules Drag & Drop

    Hey @JohnTerenece,

    Your question is quite ambiguous, let me unfold some things first:

    1. It looks like you have a mesh primitive capsule for a cube in your scene. On it, you have an ExampleTag. The capsule inlines the BaseList2D representing the Cube [Geometry] node, just as it is inlining its two tags Phong and Example Tag.
    2. You now drag a parameter from the capsule BaseObject, e.g., its P.X parameter, and are wondering why it behaves differently than the Segments.X parameter from the inlined BaseList2D representation of the Cube [Geometry] node.

    The answer to this is:

    1. A GraphNode and a (Nodes)GraphModelRef are not the same thing, the latter is the graph while the former is a node in it. The Cube [Geometry] is a node within a graph, not the graph itself. In the case of a geometry operator capsule this is hidden away from the user as you are not supposed to edit these capsules, but it still applies.
    2. Not every BaseList2D representation of an entity inside a graph has a NimbusRef. It is only the BaseList2D which represents the graph itself, e.g., the BaseObject in case of a capsule graph, which holds it. Which is also why I posted the screenshot from the Active Object plugin in my prior answer:

      The representations of all entities in a graph are in a "Nodes" branching relation to the entity which holds the NimbusRef for their graph.
    3. You currently can associate a BaseList2D wrapper for a node in a graph with its NodeGraphModelRef (i.e., "graph") but you cannot associate it with its GraphNode, i.e., the actual node (in the public API).

    Finde below my code example.

    Cheers,
    Ferdinand

    Result:

    I compare here a Mesh Primitive Group capsule and Mesh Primitive capsule which is what you were using to clarify the relation of things. In both cases the "Cube" is just a node which lives inside the capsule. It is only that the Mesh Primitive capsule inlines its geometry node because other than the Mesh Primitive Group it is not editable.

    Code:

    Bool ExampleTag::Message(GeListNode* node, Int32 type, void* data)
    {
      iferr_scope_handler
      {
        return false;
      };
    
      // Returning true or false in a message stream (be it plugin, atom, GUI, or core messages) means: 
      // 
      //   true:  "I have received this event, it was intended for me, and I handled it." 
      //   false: "I have received this event, it was intended for me, but I could not handle it." 
      // 
      // When neither of these two apply because you for example have no clue what and for whom that 
      // message is for, you must let the base impl. take over. Consuming messages that are not intended
      // for you without doing anything to handle them can cause significant problems. 
    
      // When #node is null, not a tag, or not attached to a document/object, let the base impl. take 
      // over. We could also return #false instead (which is what SUPER::Message will do), but we 
      // certainly do not want to indicate that we consumed the message by returning true.
      BaseTag* const tag = static_cast<BaseTag*>(node);
      if (!tag || !tag->GetDocument() || !tag->GetObject())
        return SUPER::Message(node, type, data);
    
      switch (type)
      {
        case MSG_DRAGANDDROP:
        {
          // First make sure that our host object holds a scene nodes graph.
          BaseObject* const host = tag->GetObject();
          maxon::NimbusBaseRef hostHandler = host->GetNimbusRef(maxon::neutron::NODESPACE);
          if (!hostHandler || !hostHandler.GetGraph())
          {
            ApplicationOutput(
              "Drag event for '@', host object '@' does not hold valid scene nodes graph.", 
              tag->GetName(), host->GetName());
            return false;
          }
    
          // Try to interpret #data as drag data and make sure a node parameter is being dragged.
          DragAndDrop* const dragData = static_cast<DragAndDrop*>(data);
          if (!dragData || dragData->type != DRAGTYPE_DESCID)
            return false;
    
          // Attempt to get the node(s) for which a parameter is being dragged for.
          DescPropertyDragData* const dragItems = static_cast<DescPropertyDragData*>(dragData->data);
          if (!dragItems || dragItems->arr->GetCount() < 1)
            return false;
    
          // This line:
          // 
          //		BaseObject *draggedObject = (BaseObject*)dndidarr->GetIndex(0);
          // 
          // was quite dangerous, because unless I have overlooked something, you nowhere made sure that
          // index 0 is a BaseObject, used a C-style cast, and finally did not check if index 0 was
          // null or not. The combination of assuming the dragged node type and the re-interpretive 
          // nature of C-style casts could have wrecked some havoc here.
    
          // --- snip ----------------------------------------------------------------------------------
    
          BaseList2D* const item = static_cast<BaseList2D*>(dragItems->arr->GetIndex(0));
          if (!item)
            return false;
    
          // Try to get the scene nodes data for the dragged object.
          maxon::nodes::NodesGraphModelRef graph;
    
          // The dragged item is an entity which holds a graph relation directly, i.e., it represents
          // a graph itself, not a node in it.
          maxon::NimbusBaseRef itemHandler = item->GetNimbusRef(maxon::neutron::NODESPACE);
          if (itemHandler && itemHandler.GetGraph())
            graph = itemHandler.GetGraph();
    
          // Try to unpack a graph relation for a BaseList2D wrapping a node inside a graph.
          if (!graph && item->GetMain() && item->GetMain()->GetClassification() == Obase)
          {
            // Get the entity hosting the dragged item and check if it holds a scene nodes graph.
            const BaseList2D* const itemHost = item->GetMain();
            itemHandler = itemHost->GetNimbusRef(maxon::neutron::NODESPACE);
            if (itemHandler && itemHandler.GetGraph())
              graph = itemHandler.GetGraph();
          }
          if (!graph)
          {
            ApplicationOutput("Could not find scene nodes relation for: @", item);
            return false;
          }
    
          // To explain a bit what we just did above: When we have the classic API document scene 
          // graph shown below (I would recommend exploring scene graphs with the Active Object
          // plugin contained in the C++ SDK on your own):
          //
          // Fig. I:
          //      Document_0 [BaseDocument]
          //          ...
          //          Object Branch
          //              My_Nimbus_Host [BaseObject] <--->  My_Graph
          //                  Nodes Branch
          //                      Node_0_Wrapper [BaseList2D]
          //                      Node_1_Wrapper [BaseList2D]
          //                      ...
          //                  Tag Branch
          //                      Example_Tag [BaseTag]
          //                      ...
          //          ..
          //
          // And this nodes graph managed by My_Nimbus_Host:
          //
          // Fig. II:  
          //      My_Graph [NodeGraphModelInterface]
          //          Root [GraphNode]
          //              Node_0 [GraphNode]
          //              Node_1 [GraphNode]
          // 
          // Then the dragged #item could be here #Node_0_Wrapper, i.e., a user is dragging the 
          // representation of #Node_0 or a parameter in it. A NimbusRef, the glue which ties
          // an element in a classic API scene graph (i.e., Fig. I) to a nodes graph (i.e., Fig. II),
          // only exists between the thing which represents the graph itself (a BaseObject in case of
          // a capsule, a BaseMaterial in case of node materials, and a SceneHook in case of the main
          // scene nodes graph tied to a document) and the graph.
          // 
          // The BaseList2D wrappers which represent nodes within a graph do not hold that NimbusRef
          // since they are just smoke and mirrors.
          // 
          // We can establish the graph they are associated with by traversing the classic API "Nodes"
          // branching relation, e.g., go from #Node_0_Wrapper to #My_Nimbus_Host, and there get the
          // NimbusRef to get the graph. What we cannot do (in the public API), is associate the 
          // #Node_0_Wrapper with #Node_0. You could of course do pattern matching over node attribute
          // and BaseList2D parameter names, but there is no direct way.
    
          // --- snip ----------------------------------------------------------------------------------
    
          iferr_scope_handler
          {
            ApplicationOutput("maxon API error in drag handling: @", err);
            return false;
          };
    
          // Compare UUIDs of wrapping nodes.
          maxon::Uuid u0 = hostHandler.BaseList2DToUuid(static_cast<BaseList2D*>(host)) iferr_return;
          maxon::Uuid u1 = itemHandler.BaseList2DToUuid(item) iferr_return;
          ApplicationOutput("UUIDs: a == b = @",  u0.Compare(u1) == maxon::COMPARERESULT::EQUAL);
    
          const maxon::nodes::NodesGraphModelRef& g0 = hostHandler.GetGraph();
          const maxon::nodes::NodesGraphModelRef& g1 = itemHandler.GetGraph();
    
          // Compare graphs directly, as hinted at in my forum posting, this is not the most reliable
          // thing to do.
          ApplicationOutput("Graphs: a == b = @", g0.Compare(g1) == maxon::COMPARERESULT::EQUAL);
    
          // Compare graphs (I would pick this one).
          ApplicationOutput("Graph Hashes: a == b = @", 
                            g0.GetUniqueHashCode() == g1.GetUniqueHashCode());
    
          // And finally the wrapping nodes, can be true but does not have to be. I could not get it to
          // fail in my short tests, but at some point Cinema 4D will reallocate these nodes.
          ApplicationOutput("Atoms: a == b = @", host == item);
    
          return true;
        }
        default:
          break;
      }
      return SUPER::Message(node, type, data);
    }
    

    posted in Cinema 4D SDK •
    RE: R23 Monterey SDK compile errors

    Hello @Rox,

    Thank you for reaching out to us and please excuse the delay. As lined out in the R23 C++ SDK documentation, we neither support Xcode 13 nor macOS 12 in the 23 SDK. The supported OS are Catalina (10.15.6+) and Big Sur (11), and the supported Xcode versions are 11.3+ and 12.2+.

    You might get away with installing Xcode 11/12 on Monterey and forcefully running these earlier versions. Please note that this is a hack to make your life easier and we cannot provide support for problems that arise from it. When you run into problems, we must ask you to use the intended environment.

    Cheers,
    Ferdinand

    PS: A super handy website in this context is https://xcodereleases.com/.

    posted in Cinema 4D SDK •
    RE: c4d 2024 "MENURESOURCE_SEPERATOR"

    Hello @chuanzhen,

    Thank you for reaching out to us. And thank you @Dunhou for providing an answer. @Dunhou is right, a developer of ours fixed the spelling error in the symbol in our C++ API, which then automatically has been propagated to Python.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Can't debug C4D 2023 and 2024 on macOS

    Hello @fwilleke80,

    first, thank you very much for investing more time into this and with that saving time for me, truly appreciated. But let us discuss the details per mail.

    I will report back when there is a tangible outcome which relevant for the community.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Capsules Drag & Drop

    Hello @JohnTerenece,

    Thank you for the added information.

    Find below my answer. I could not make the circumstance work that the dragged and the host node do not match but I did not spend too much time in dragging things left and right in the editor. Since these classic API nodes which wrap maxon API nodes are volatile, allocated on demand, a mismatch is not too suprising.

    Cheers,
    Ferdinand

    Note that I modified the resources as well as projectdefinition.txt in your project. So copying over source files alone will not be enough. You will have to place this project in your solution and run the project tool on it (your project was missing the necessary framework dependencies to work with nodes in the first place).

    Result:

    Project: example.tag.zip

    Code:
    For includes and the rest of the tag implementation see the project zip.

    Bool ExampleTag::Message(GeListNode* node, Int32 type, void* data)
    {
      // Returning true or false in a message stream (be it plugin, atom, GUI, or core messages) means: 
      // 
      //   true:  "I have received this event, it was intended for me, and I handled it." 
      //   false: "I have received this event, it was intended for me, but I could not handle it." 
      // 
      // When neither of these two apply because you for example have no clue what and for whom that 
      // message is for, you must let the base impl. take over. Consuming messages that are not intended
      // for you without doing anything to handle them can cause significant problems. 
    
      // When #node is null, not a tag, or not attached to a document/object, let the base impl. take 
      // over. We could also return #false instead (which is what SUPER::Message will do), but we 
      // certainly do not want to indicate that we consumed the message by returning true.
      BaseTag* const tag = static_cast<BaseTag*>(node);
      if (!tag || !tag->GetDocument() || !tag->GetObject())
        return SUPER::Message(node, type, data);
    
      switch (type)
      {
        case MSG_DRAGANDDROP:
        {
          // First make sure that our host object holds a scene nodes graph.
          BaseObject* const host = tag->GetObject();
          maxon::NimbusBaseRef hostHandler = host->GetNimbusRef(maxon::neutron::NODESPACE);
          if (!hostHandler || !hostHandler.GetGraph())
          {
            ApplicationOutput(
              "Drag event for '@', host object '@' does not hold valid scene nodes graph.", 
              tag->GetName(), host->GetName());
            return false;
          }
    
          // Try to interpret #data as drag data and make sure a node parameter is being dragged.
          DragAndDrop* const dragData = static_cast<DragAndDrop*>(data);
          if (!dragData || dragData->type != DRAGTYPE_DESCID)
            return false;
    
          // Attempt to get the node(s) for which a parameter is being dragged for.
          DescPropertyDragData* const dragItems = static_cast<DescPropertyDragData*>(dragData->data);
          if (!dragItems || dragItems->arr->GetCount() < 1)
            return false;
    
          // This line:
          // 
          //		BaseObject *draggedObject = (BaseObject*)dndidarr->GetIndex(0);
          // 
          // was quite dangerous, because unless I have overlooked something, you nowhere made sure that
          // index 0 is a BaseObject, used a C-style cast, and finally did not check if index 0 was
          // null or not. The combination of assuming the dragged node type and the re-interpretive 
          // nature of C-style casts could have wrecked some havoc here.
    
          BaseList2D* const item = static_cast<BaseList2D*>(dragItems->arr->GetIndex(0));
    
          // Jump ship when we could not make sense of the dragged node or it is not an object. The 
          // latter is optional.
          if (!item || item->GetClassification() != Obase)
            return false;
    
          // Now get the scene nodes data for the dragged object.
          maxon::NimbusBaseRef itemHandler = item->GetNimbusRef(maxon::neutron::NODESPACE);
          if (!itemHandler || !itemHandler.GetGraph())
          {
            ApplicationOutput(
              "Drag event for '@', drag data '@' does not hold valid scene nodes graph.",
              tag->GetName(), item->GetName());
            return false;
          }
    
          // Now we finally do the comparison. We venture deeper into the maxon API and therefore need
          // an error handler, it is basically just an exit point for things that return a 
          // #maxon::Result<T> and therefore are called with a #iferr_return. When these things throw 
          // an error, we will exit though our #iferr_scope_handler since this ::Message function has 
          // no error handling (it is of type #Bool and not #Result<Bool>).
    
          iferr_scope_handler
          {
            ApplicationOutput("maxon API error in drag handling: @", err);
            return false;
          };
    
          // Compare UUIDs of wrapping nodes.
          maxon::Uuid u0 = hostHandler.BaseList2DToUuid(static_cast<BaseList2D*>(host)) iferr_return;
          maxon::Uuid u1 = itemHandler.BaseList2DToUuid(item) iferr_return;
          ApplicationOutput("UUIDs: a == b = @",  u0.Compare(u1) == maxon::COMPARERESULT::EQUAL);
    
          const maxon::nodes::NodesGraphModelRef& g0 = hostHandler.GetGraph();
          const maxon::nodes::NodesGraphModelRef& g1 = itemHandler.GetGraph();
    
          // Compare graphs directly, as hinted at in my forum posting, this is not the most reliable
          // thing to do.
          ApplicationOutput("Graphs: a == b = @", g0.Compare(g1) == maxon::COMPARERESULT::EQUAL);
    
          // Compare graph hashes (I would pick this one).
          ApplicationOutput("Graph Hashes: a == b = @", 
                            g0.GetUniqueHashCode() == g1.GetUniqueHashCode());
    
          // And finally the wrapping nodes, can be true but does not have to be. I could not get it to
          // fail in my short tests, but at some point Cinema 4D will reallocate these nodes.
          ApplicationOutput("Atoms: a == b = @", host == item);
    
          break;
        }
        default:
          break;
      }
      return SUPER::Message(node, type, data);
    }
    

    posted in Cinema 4D SDK •
    RE: Can't debug C4D 2023 and 2024 on macOS

    Okay, will have a look on Monday!

    posted in Cinema 4D SDK •
    RE: Can't debug C4D 2023 and 2024 on macOS

    Hey @fwilleke80,

    Thank you for reaching out to us. That all looks quite mysterious. It is two months ago that I did any substantial development on Xcode which included debugging. I think I also wrote parts of the 2024 migration examples on my MacBook Pro 2019, without any problems.

    I am currently in the office and do not have my MacBook with me. I will do a test build on the 2023.2 SDK on Monday. What seems to be failing in your screen, is the Image API, i.e., the image.framework, and a pixel packing format. Since the Image API is OS dependent, it could be that Apple has provided a late life service patch for Monterey, with then the Image API not working anymore. But still quite odd since all that stuff is quite fundamental. Could you give us the exact build number for your failing Monterey system?

    Now that I write all this, I remember that I upgraded my MacBook to Ventura + Xcode 14 which will complicate things. I will test on that MacBook anyways for now, if necessary, I will get an older machine then. When you can reproduce the problem under 2024, it would be helpful to know its exact conditions too, as it is for me easier to test here (but that does not mean we will ignore 2023).

    Cheers,
    Ferdinand

    PS: But you are running things with the so-called legacy build system, right? I know that you are an experienced developer, but if you have somehow managed to get the SDK with the new build system going, all sorts of weird errors are to be expected.

    posted in Cinema 4D SDK •
    RE: Capsules Drag & Drop

    Hey @JohnTerenece ,

    Thank you for reaching out to us. I am not 100% sure that I am understanding you correctly, but from what I get, you want to compare the BaseList2D representations of two GraphNode instances at the specific case of a Nodes Mesh.

    Why is this Happening?

    We have something like this, where the Python Programming tag is your tag plugin:

    7a422f2a-535e-442c-a5a6-bcac2668ce78-image.png
    Fig I.: A Nodes Mesh capsule with a Python tag on it.

    You now want to drag the Nodes Mesh node onto your tag node and compare the values. In general, it is not too surprising that these nodes do not always match, as all BaseList2D representing a GraphNode, e.g., the Cube [Cube] BaseList2D shown in the Attribute Manager for the selected Cube GraphNode, are just dynamically allocated dummy nodes.

    These dummy nodes live parented to the NimbusRef owning them, in the case of 'normal' scene nodes that is the scene hook named 'scene nodes', in case of capsules it is the Object Manager object representing them:

    a52e40e3-0ddc-47e6-9627-bbe4f10db1dc-image.png
    Fig II.: The branches of the Nodes Mesh object from above. It holds two BaseList2D dummies, one for the graph container which is a node itself (the root) and one for the cube node. These are however much more volatile (BaseList2D) nodes than a classic API scene element as they just wrap underlying GraphNode data and might be regenerated whenever Cinema 4D feels like it.

    What can I do?

    The easiest way to deal with this is going to source of the data, as these BaseList2D are just smoke and mirrors. With BaseList2D::GetNimbusRef you can get the terribly named NimbusBaseInterface reference for a BaseList2D. The term 'Nimbus' is just an echo of an internal code name and has otherwise no higher meaning. You can think of a NimbusBaseInterface as the managing entity that ties an element in a classic API scene graph, a BaseList2D, to a maxon API scene graph. So, a NimbusRef ties a material nodes graph to a BaseMaterial (that happens to be a node material), or as in this case, a scene nodes graph to an object in the Object Manager.

    • NimbusBaseInterface::GetGraph: Returns the Nodes API graph associated with this nimbus handler, i.e., the actual data.
    • NimbusBaseInterface::BaseList2DToUuid and ::BaseList2DToUuid: Allow you to identify BaseList2D wrapping nodes over reallocation boundaries, if they describe the "same" node.

    As untested code:

    BaseObject* const op;
    
    // Try to get the scene nodes nimbus handler for the object #op.
    const maxon::NimbusBaseRef handler = op->GetNimbusRef(maxon::neutron::NODESPACE());
    if (!handler )
      return false;
    
    // Get the graph that is wrapped by #op and #handler.
    const maxon::nodes::NodesGraphModelRef& graph = handler.GetGraph();
    // Get an identifier which identifies #graph, I would suggest comparing capsules with this hash.
    const maxon::UniqueHash gid = graph.GetUniqueHashCode();
    
    // I would avoid comparing graphs directly. You should also use ::Compare when you must compare the 
    // graphs directly.
    const maxon::nodes::NodesGraphModelRef& other;
    if (graph.Compare(other) == maxon::COMPARERESULT::EQUAL)
      // Do something when #graph and #other are equal.
    

    Please share a project zip when you require further assistance.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: How to get OutLine of Polygon object

    Hello @chuanzhen,

    Thank you for reaching out to us. Both in the Python and C++ Cinema 4D API there is no function to extract the outline of an object. Your question is therefore an algorithm question and out of scope of support. I have moved your topic to our General Talk forum.

    About your Question

    When I use the word polygon(al) in the section below, I am referring to the mathematical concept. When a polygon in the sense of a mesh element is meant, I will use the words quad(rangle) and tri(angle).

    This is very much an algorithm question, and I am limited in how much help I can provide. The topic also ranges from medium to high complexity, depending on what you want to do exactly.

    First, I am assuming that you want to find the polygonal outline of your object, based on some tri/quad mesh data. If you want to operate on an RGBA or alpha pass in a rendering, you might want to investigate things like the Sobel kernel and how to construct polygons on it.

    Finding the polygonal outline of a (discrete) mesh can be split into three steps:

    1. Project the points of the mesh into a plane, usually the camera plane. There are some helper functions in BaseView which can help you with this, but you can also just do it on your own.
    2. Remove all polygons facing in the opposite direction than the projection plane normal. Doing this is not absolutely necessary but makes the third step less complicated. You could also remove the polygons facing away from the camera before the first step, but doing it after the projection is easier because they then either face you directly or not.
    3. The third step is the hardest, as you must build the complex polygon, the outline, out of your projected tris and quads. First, you should convert the whole mesh into tris, and then you must run a Boolean union of polygons algorithm on them; the Vatti clipping algorithm is very popular. In practice you then iteratively join all tris into your output outline polygon P which starts out as the empty set.
      • In Cinema 4D you could side-step this last point by not implementing the recursive Boolean union yourself, but by using the Spline Mask object. Just construct splines for all your tris and quads (here you would want to avoid converting quads into tris) and add them to the Spline Mask object as input objects. Then evaluate the cache of the Spline mask and you have your outline. The Spline Mask can only evaluate discrete objects though, it cannot join segments within a spline (at least I could not make that work when I tried). So, for an object with one thousand polygons, you would have to construct one thousand splines. This approach is therefore extremely limited when it comes to performance and scalability.
      • If want to do anything akin to what for example our Sketch and Toon or some games do, i.e., generate in real time the outline over animated geometry, you will have to do that in C++ (Python will not even remotely be performant enough) and also implement the recursive Boolean union of polygons yourself.

    We cannot give any further advice on the algorithmic portion of this subject.

    Cheers,
    Ferdinand

    posted in General Talk •
    RE: Add normal button in a dialog that opens a dialog to select a folder

    Hey @danielsian,

    Yes, a tree view could be another solution, another one could be a GeUserArea. I deliberately did not mention both options since they are more complex. Especially TreeViewFunctions, the underlying interface for tree views, tends to be overwhelming for newcomers. I would recommend the dynamic UI workflow hinted at above, as this will result in the least amount of code. But it will of course not be as beautiful as something truly custom.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: MergeDocument under a selected Null

    Hey @pyr,

    Thank you for reaching out to us. The sentence 'if i do the whole thing via obj.InsertUnder(null) i miss the material' is a bit ambiguous, how do you mean that? I assume you mean that when you load two documents A and B and then start copying objects over by either cloning them or removing them from A and inserting them into B, that you then miss material references. Because the call you show us will load both objects and materials found at f into doc.

    A straightforward way to do what you want to do, parent everything that has been imported to a null, is to track the state of the top-level objects before and after the import. The difference between the states then tells you what to move where. In practice, there are some technical hoops to jump through, primarily the problem that you might run into dead references when you try to store objects references over the import process.

    Cheers,
    Ferdinand

    Result:

    Code:

    """Demonstrates how to parent the objects merged into a document to a null object.
    
    Must be run as a script manager script and will import the contents of #FILE into the active 
    document.
    """
    
    import c4d
    
    doc: c4d.documents.BaseDocument # The active document.
    FILE: str = r"file:///e:/test.c4d" # The document to import.
    
    def main() -> None:
        """
        """
        # Get the UUID markers of all top level objects. We get the markers and not the object 
        # references because importing a document bears a good chance that these references will be
        # dead (C4DAtom.IsAlive() == False) when we access them again.
        uuidCollection: list[bytes] = []
        op: c4d.BaseObject = doc.GetFirstObject()
        while op:
            uuidCollection.append(bytes(op.FindUniqueID(c4d.MAXON_CREATOR_ID)))
            op = op.GetNext()
            
        
        # Now we import our document.
        c4d.documents.MergeDocument(doc, FILE, c4d.SCENEFILTER_OBJECTS | 
                                               c4d.SCENEFILTER_MATERIALS | 
                                               c4d.SCENEFILTER_MERGESCENE)
    
        # We create our null object to parent the imported stuff to. We also add its UUID to 
        # #uuidCollection, so that we do not attempt to parent #null to itself ;)
        null: c4d.BaseObject = c4d.BaseObject(c4d.Onull)
        null.SetName(FILE)
        doc.InsertObject(null)
        c4d.EventAdd()
        
        uuidCollection.append(bytes(null.FindUniqueID(c4d.MAXON_CREATOR_ID)))
    
        # Now we iterate again over all top level objects and parent everything that does not appear in 
        # #uuidCollection to #null.
        op: c4d.BaseObject = doc.GetFirstObject()
    
        # We have to do this on two steps, because when we would already move #op in the first step 
        # where we find the things to move, we would sabotage our own loop here (because as soon as
        # we move the first op to the null, op.GetNext() would be None).
        move: list[c4d.BaseObject] = []
        while op:
            if  bytes(op.FindUniqueID(c4d.MAXON_CREATOR_ID)) not in uuidCollection:
                move.append(op)
            op = op.GetNext()
    
        # Actually move the items.
        for op in move:
            op.InsertUnderLast(null)
        c4d.EventAdd()
    
    if __name__ == "__main__":
        main()
    

    posted in Cinema 4D SDK •
    RE: Add normal button in a dialog that opens a dialog to select a folder

    Hello,

    @danielsian said in Add normal button in a dialog that opens a dialog to select a folder:

    I wonder if I could achieve what I've just described by using an InExcludeData list, with the files listed there? If so, could you suggest another example?

    I am not quite sure I understand how you mean that? AnInExcludeData references GeListNode instances, so you cannot drag files into it. Or how do you mean that? I know that we have some specialized UIs which list multiple files/and or directories, but they are (a) not InExcludeData and its associated UI, and (b) also not part of the public API AFAIK. E.g., the plugin path field:

    7a14abd2-b723-4076-8ba6-39281aafd478-image.png

    On top of that, what you see here is not a dialog but a description, i.e., it is a whole other UI concept. If you want something like this, i.e., what is commonly known as a list box, you would have to implement it yourself for dialogs. Primarily because the cycle concept of Cinema 4D (which is closest to the concept of a list box) is always of type long. So, other than in the ListBox of Windows/WPF/WinForms, a cycle stores a selected int value and not a selected index (and whatever data you decided to store in the ListBox, usually a string).

    When you want to have multiple file paths, I would simply add two buttons ("Add" and "Remove") and then dynamically rebuild the UI with the number of Filename UIs you need. I showed once here how to build a simple dynamic dialog UI.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Setting an object to the target section of a Transform Constraint Tag

    Hello @EugeneNUA,

    Welcome to the Plugin Café forum and the Cinema 4D development 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 Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:

    • Support Procedures: Scope of Support: Lines out the things we will do and what we will not do.
    • Support Procedures: Confidential Data: Most questions should be accompanied by code but code cannot always be shared publicly. This section explains how to share code confidentially with Maxon.
    • Forum Structure and Features: Lines out how the forum works.
    • Structure of a Question: Lines out how to ask a good technical question. It is not mandatory to follow this exactly, but you should follow the idea of keeping things short and mentioning your primary question in a clear manner.

    About your First Question

    You mostly got it right, your major oversight was that you assumed the link field to be a static parameter (which it is not). Find out more on how to discover parameter IDs in Python Console Manual: Drag and Drop. Find my example attached below.

    Cheers,
    Ferdinand

    Result:
    07afea20-fe1e-4c3d-bd29-de867c309b58-image.png

    Code:

    """Adds a constraint tag with a PSR transform targeting an object named 'link' to the currently
    selected object.
    
    Must be run as a Script Manager script.
    """
    
    import c4d
    
    doc: c4d.documents.BaseDocument # The active document
    op: c4d.BaseObject # The selected object in #doc, can be #None.
    
    def main() -> None:
        """
        """
        # Search for the object to link in the constraint.
        link: c4d.BaseObject = doc.SearchObject("link")
        if not op or not link:
            print ("Please select an object and provide an object named 'link'.")
            return
    
        tag: c4d.BaseTag = op.MakeTag(c4d.Tcaconstraint)
        if not tag:
            raise MemoryError("Could not allocate tag.")
        
        tag[c4d.ID_CA_CONSTRAINT_TAG_PSR] = True
        # ID_CA_CONSTRAINT_TAG_PSR_LINK is just a stride, i.e., offset, not an actual ID as a constraint
        # can have multiple links and the IDs must be therefore dynamic. If you only want one link 
        # (or even multiple) I would just hardcode the dynamic ID (10001 will be present on the freshly 
        # initialized tag). See
        #   https://developers.maxon.net/docs/py/2024_0_0/manuals/manual_py_console.html#drag-and-drop
        # for how I found out that 10001 is the relevant ID here.
        tag[10001] = link
        tag[c4d.ID_CA_CONSTRAINT_TAG_PSR_P_OFFSET] = c4d.Vector(5,0,0)
        tag[c4d.ID_CA_CONSTRAINT_TAG_PSR_MAINTAIN] = True
    
        c4d.EventAdd()
    
    
    if __name__ == "__main__":
        main()
    
    posted in Cinema 4D SDK •