Group Details Private

Global Moderators

Forum wide moderators

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: Import NumPy?

Hi @justinleduc with c4dpy this is pretty straightforward. Run it once to get a license assigned to it, then once it's licensed run the next commands:
c4dpy -m ensurepip to install pip and then c4dpy -m pip install numpy.

Then you can start Cinema 4D and you will be able to use numpy within Cinema 4D.
Final note as said in Python Libraries Manual.

MAXON cannot provide support for third party libraries written for CPython and their compatibility with the Python interpreters shipped with Cinema 4D. While our Python interpreters are derived from the respective CPython builds, they are not identical to them. Popular Python libraries as for example numpy might not work at all or only with a reduced feature set. The Cinema 4D Python interpreters are provided as-is and not meant to be fully CPython compliant.

Cheers,
Maxime.

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:

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