Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login
    1. Maxon Developers Forum
    2. ferdinand
    3. Posts
    Offline
    • Profile
    • Following 0
    • Followers 15
    • Topics 54
    • Posts 3,117
    • Groups 2

    Posts

    Recent Best Controversial
    • RE: Is it possible to hide the Basic Tab in the attribute viewer?

      Hey @ECHekman,

      it depends a bit on what you do in your description, which bases you include, and what you consider NodeData instances in your case. Are you actually talking about plain NodeData hooks or are you talking about derived types such as ObjectData?

      When we look for example into Ocube.res, we can see it includes Obase:

      CONTAINER Ocube
      {
      	NAME Ocube;
      	INCLUDE Obase;  // includes the description base for all objects Obase.res
      

      Which among other things contains the Obaselist group, which is the group you mean.
      e972d7aa-5434-40d2-b219-25703f65c2d8-image.png

      You could either not include the base description for your node type, or use a modified base which for example hides all elements in that group. But I personally would say that none of these approaches are really a good idea, as Cinema 4D is designed with the assumption that each basic scene element (object, tag, etc.) includes their base description.

      Falling to do so, will either make your plugin outright not work, or - when using more 'clever' approaches such as hiding all parameters, or hiding the whole group via GetDDescription or a modified base - severely impact how users can interact with your scene element.

      When you implement a plain NodeData hook, you should not see any extra tabs, as plain node hooks should not include any bases.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: set GvNode value via python

      Hey @Dunhou,

      Thank you for reaching out to us. This is probably possible, but I'll have to check with a debugger myself. These are dynamically generated ports which tend to be a pain in the butt in Xpresso, as pretty much every node type handles them slightly differently, and even by looking at our source code you often do not get any wiser. My guess would have been that it is something like c4d.DescID(c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()), c4d.DescLevel(0, c4d.DTYPE_REAL, 0)) or the same but other values for the second desc level such as 1000, 2000, etc. instead of 0. GV_CONDITION_INPUT is defined in the description as the input group.

      I will have to attach a debugger to see what is going on, but where I am currently working from, I have no compiled version of Cinema 4D. Will check in the next days from my Mac, where I do have a compiled version. You can check this thread for context, the function PythonNodeLayerShaderAccess goes into some details (a Python node also has a dynamic/variadic number of in- and output ports).

      Cheers,
      Ferdinand

      """My attempt in setting the dynamic ID_OPERATOR_CONDITION operator in ports (does not work). 
      """
      
      import c4d
      
      from c4d.modules.graphview import GvNodeMaster, GvNode, GvPort, XPressoTag
      from mxutils import CheckType
      
      doc: c4d.documents.BaseDocument  # The currently active document.
      op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
      
      
      def main() -> None:
          """Called by Cinema 4D when the script is being executed.
          """
          null: c4d.BaseObject = CheckType(c4d.BaseObject(c4d.Onull))
          tag: XPressoTag = CheckType(null.MakeTag(c4d.Texpresso))
          master: GvNodeMaster = CheckType(tag.GetNodeMaster())
          doc.InsertObject(null)
      
          root: GvNode = CheckType(master.GetRoot())
          condNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_CONDITION, x=200, y=200))
      
          # Set values:
          did_1: c4d.DescID = c4d.DescID(
              c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()),
              c4d.DescLevel(0, c4d.DTYPE_REAL, 0)
          )
          did_2: c4d.DescID = c4d.DescID(
              c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()),
              c4d.DescLevel(1, c4d.DTYPE_REAL, 0)
          )
      
          a = condNode.SetParameter(did_1, 10.0, c4d.DESCFLAGS_SET_0)
          b = condNode.SetParameter(did_2, 20.0, c4d.DESCFLAGS_SET_0)
          print(a, b)
      
          c4d.EventAdd()
      
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Python tag or Python node in Xpresso crash Cinema 4D

      FYI, there has been a verified fix for the Booleans crash, it will be included in an upcoming release of Cinema 4D.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: The value of 'porgress' during the use of RenderDocument() is greater than 1.0

      I have moved this into bugs.

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: The value of 'porgress' during the use of RenderDocument() is greater than 1.0

      Hey @chuanzhen,

      So, first of all, please excuse the delay. And thank you for the exemplary problem description, I almost booked this under "cannot reproduce" but you really hit the nail on the head with the conditions.

      There is currently a bug in how the viewport renderer handles sending data to the progress hook. But this only happens when rendering a document from another frame than its starting frame. I am not yet 100% certain, but I am pretty sure this is a bug in the progress hook itself and not the viewport renderer. So, the bug could also appear in other contexts where a specific function of the progress hook is used. But so far the viewport renderer is the only renderer with which I could reproduce this.

      There is not really anything you can do as a user, because there is a concrete bug. But the viewport renderer calls the progress hook twice each frame: Once with the correct progress, and once with a progress that is shifted by renderFrameRange/documentFrameRange percent. With some internal code knowledge we can simply say by the call index if the given progress is correct or not. But please understand that this is very much a hack.

      I will probably fix this, but I cannot fix this retroactively in 2025. This workaround could be useful for you but it is also a bit risky.

      I have moved this into bugs.

      Cheers,
      Ferdinand

      """Provides a workaround to get correct progress values when rendering a document with the viewport 
      renderer.
      
      This workaround is specific to the case of this thread (viewport renderer + start frame other than
      the first frame). I know what causes the bug, but I am not yet sure if it is the viewport renderer
      which feeds the thing with wrong data, or of the thing itself is buggy.
      
      THIS IS A HACK USE AT YOUR OWN RISK! DO NOT REMOVE THIS WARNING.
      
      See: https://developers.maxon.net/forum/topic/16341
      """
      import c4d
      import mxutils
      
      doc: c4d.documents.BaseDocument
      
      # What basically happens for the preview renderer, is that it calls the progress hook twice for each
      # frame. The second call happens just two lines after the first call. Each first call provides 
      # "correct" data, while the second does not. However, the first call is only made when RDATA_FASTAFX
      # is set to false in the render data (is the case by default). The workaround is then simply to build
      # history data so that we can know if the current call is an even (correct) or odd (incorrect) call.
      PROGRESS_STACK: list[float] = []
      
      def PythonCallBack(progress, progress_type):
          """
          """
          # Just some stuff I used for debugging which only works in 2026 and above.
          # symbol: str = mxutils.g_c4d_symbol_translation_cache.Get(progress_type, "RENDERPROGRESSTYPE_")[0]
          # with open("/Users/f_hoppe/Documents/temp/render_progress.txt", "a") as f:
          #     f.write(f"{symbol}: {progress:.2f}%\n")
      
          PROGRESS_STACK.append(progress)
          if len(PROGRESS_STACK) % 2 == 0:
              # Even call: correct data
              print(f"Render Progress: {progress:.2f}%")
          else:
              # Odd call: incorrect data, ignore, this is the same context as the previous one.
              pass
      
      def main() -> None:
          """
          """
          # c4d.ClearPythonConsole()
      
          rd = doc.GetActiveRenderData()
          rd[c4d.RDATA_FASTAFX] = False # REALLY make sure that RDATA_FASTAFX is disabled
      
          bmp = c4d.bitmaps.MultipassBitmap(int(rd[c4d.RDATA_XRES]), int(rd[c4d.RDATA_YRES]), c4d.COLORMODE_RGB)
          if bmp is None:
              raise RuntimeError("Failed to create the bitmap.")
      
          bmp.AddChannel(True, True)
          if c4d.documents.RenderDocument(doc, rd.GetDataInstance(), bmp, c4d.RENDERFLAGS_EXTERNAL, prog=PythonCallBack,
                                      ) != c4d.RENDERRESULT_OK:
              raise RuntimeError("Failed to render the temporary document.")
      
      if __name__ == "__main__":
          main()
      
      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Python tag or Python node in Xpresso crash Cinema 4D

      Hey @SmetK,

      so I am back, and I have good and bad news 🙂

      The bad news is that I could not reproduce the crash with your scene on my Win machine with 2026_0_0_db12fb68d6ba_2004155263. I today also asked the users in our beta forum, and multiple users could not reproduce it either.

      The "good" news is that without me noticing, in the last days a similar crash report (it terminates into the exact same lines as yours) has been attached to the issue I created for your crash. This crash came from a Korean user on macOS and with this users scene I could reproduce your crash (I just got the same exact stack trace). The Korean user does not have any Python in his/her scene, just some Boolean setup not unsimilar to yours.

      I have grouped everything and added some comments in your ticket, and will in moment briefly talk with the Boolean developer. Python might be here a component in your case (which so far no one go to crash), but there seems to be a base case which happens independently of Python. Which makes sense given how harmless your code is.

      When you want to follow this case, I would recommend reaching out to end user support, feel free to mention the ticket ITEM#623378 [Python] Booleans crash when building output. When there is a fundamental development which impacts API users, I will post it here for all developers to see, but we should not let end user issues bleed into developer issues.

      So, I do not have a super satisfying answer, but we can at least reproduce this, which increases the chances of this being fixed.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: The value of 'porgress' during the use of RenderDocument() is greater than 1.0

      FYI: I have not forgotten you. Will get to your topic tomorrow, hopefully 🙂

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Several Asset Browser Issues

      Hey Ben,

      Thank your for your reply. It always pains me a bit to be that upfront when a user has clearly put effort into a problem description. But it is very hard to follow your description and videos when looking at it from a developers standpoint, what to pinpoint and/or fix a bug.

      Please follow our support procedures regarding reporting bugs and crashes. When need a written step by step instruction, scene files (or in your case a zip database), and what you consider wrong. I would recommend to:

      • Prune all stuff that is not directly relevant to the crash or bug. When you create a zip database from one of our built-in assets, you can just share the zip and say "this is toony-123" as zip database to demonstrate my problem.
      • Then either provide reproduction steps (in which case this would be an end user issue) or the code which triggers the issue and describe in text what leads up to your problem and equally important, what you consider wrong about the outcome.

      But I watched your two videos, and read your posting and got a sense of what you are doing. A few points:

      • The toon rig uses some dark magic Python code (event notifications, something you better keep away from), where I would not be too surprised if they break. And just for clarity: The Toon rig is not part of the SDK or owned by the SDK. That does not mean we won't help you, but I cannot fix the toon rig for you, that would have to be done by the animation team.
      • On some level this does also seem to involve exchange topics, i.e., File IO when you import things.
      • The toon rig might need an extra nudge in your script to attach its event handlers or something like that. But I can only evaluate this once I have a file/database to work on and a script to run.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Usage of SplineHelp.InitSplineWith() regarding flags -> returned data

      Hey @mogh,

      Yes, a new topic would be better if we move on to debugging your plugin. But I'll briefly answer here.

      1. That the comb curve flips between 'above' and 'below' is normal and exactly what I talked about before with mathematical vs 'what artists want' tangents. I once spend here some time on explaining this.
      2. The problem with your screens is a bit that you do not explain what you would consider incorrect. I assume that you understand why the flipping happens and are okay with it and are concerned about the jaggedness of your combs? Without debugging your code this is very hard to evaluate.

      C4D can not get more precise than a 0° degree line object ?

      Splines are what is often called smooth geometry, i.e., a parametric curve that is mathematically defined and has infinite precision (you can think of it as infinite points). This is opposed by discrete geometry such as a LineObject which has a finite set of data points. The interpolation settings of a spline are the parameters with which the smooth form ('the spline') is brought into a discrete representation ('the line object') for display and other purposes. So, asking if Cinema 4D could discretize with 0° (or less) does not make too much sense, since discretization with 0° means infinite precision, i.e., the smooth form. For practical purposes, 0.1° should be more than enough. And that is probably already more than order of precision more than you actually need, as humans cannot see that level of detail.

      When you do not want to only control the discretization over the angle of change (a.k.a. curvature), but also over the distance of points, you can also set a maximum length for the segments. For that you must change the spline interpolation from 'Adaptive' to 'Subdivided'. This way, you can ensure that even in straight areas, the segments do not become too long.

      But the general problem is probably that you do not normalize your data. It makes a difference if the last point is 10 units away from the second last or 1000 units. To approximate the mean curvature between two vertices in some discrete geometry, the formula shown below exists; which is a hack compared to more fancy approaches such as this one. It will approximate the mean curvature between two vertices p and q with the normals np and nq:

              (nq - np) * (q - p)
        c_i = -------------------
                   |q - p|
      

      For a mesh vertex you do this with all neighboring vertices and average the result. My guess would be that you are missing this exact normalization step (/|q - p|, i.e., the value divided by the Euclidean distance between the two points) in your comb calculation. Because curvature is inherently a smooth property, the 'neighbor' of a point in a smooth curve/geometry is always exactly 1/infinity units away. So, you need to ensure that the influence of a neighbor vertex in the discrete world is weighted by its distance to the current vertex.

      For splines you would probably have to do this for the left and right tangent of each vertex and then take the mean value. But curvature discretization is like all computational geometry subjects not super trivial, and there might be extra/expert steps required for splines I am not aware of right now.

      Hope this helps,
      Ferdinand

      Here is a script I wrote a long time ago where I used that 'formula' to approximate the mean curvature for a mesh. This is non-public code, so it has no comments except for me pointing out the formula.

      """Computes the mean curvature of the selected mesh and stores it in a vertex map.
      """
      
      import c4d
      
      
      def GetNormal(pid, points, polygons, neighbor):
          """
          """
          connected = neighbor.GetPointPolys(pid)
          vertexNormals = []
      
          for cpoly in [polygons[pid] for pid in connected]:
              if pid == cpoly.a:
                  o, p, q = points[cpoly.d], points[cpoly.a], points[cpoly.b]
              elif pid == cpoly.b:
                  o, p, q = points[cpoly.a], points[cpoly.b], points[cpoly.c]
              elif pid == cpoly.c and cpoly.IsTriangle():
                  o, p, q = points[cpoly.b], points[cpoly.c], points[cpoly.a]
              elif pid == cpoly.c and not cpoly.IsTriangle():
                  o, p, q = points[cpoly.b], points[cpoly.c], points[cpoly.d]
              elif pid == cpoly.d:
                  o, p, q = points[cpoly.c], points[cpoly.d], points[cpoly.a]
              vNormal = ~((o-p) % (q-p))
              vertexNormals.append(vNormal)
          factor = 1./len(vertexNormals)
          return ~(sum(vertexNormals) * factor)
      
      
      def GetCurvature(node):
          """
          """
          neighbor = c4d.utils.Neighbor()
          neighbor.Init(node)
          points = node.GetAllPoints()
          polygons = node.GetAllPolygons()
      
          def getNeighborVertices(pid):
      
              ids = [pid]
              for i in neighbor.GetPointPolys(pid):
                  cpoly = polygons[i]
                  pnts = [cpoly.a, cpoly.b, cpoly.c]
                  if not cpoly.IsTriangle():
                      pnts.append(cpoly.d)
      
                  fi = cpoly.Find(pid)
                  cnt = len(pnts) - 1
      
                  prev = pnts[fi - 1] if fi > 0 else pnts[-1]
                  nxt = pnts[fi + 1] if fi < cnt else pnts[0]
                  if prev not in ids:
                      ids.append(prev)
                  if nxt not in ids:
                      ids.append(nxt)
              for i in ids[1:]:
                  yield i, points[i]
      
          data = []
          for ip, p in enumerate(points):
              np = GetNormal(ip, points, polygons, neighbor)
              temp = []
              for iq, q in getNeighborVertices(ip):
                  nq = GetNormal(iq, points, polygons, neighbor)
                  # This is just a very cheap
                  #
                  #        (nq - np) * (q - p)
                  #  c_i = -------------------
                  #             |q - p|
                  #             
                  temp.append((nq - np) * (q - p) / (q - p).GetLength())
              c = sum(temp) * (1. / len(temp)) * .5 + .5
              data.append(c)
      
          neighbor.Flush()
          return data
      
      def CreateVertexMap(node, data):
          """
          """
          tag = c4d.VariableTag(c4d.Tvertexmap, len(data))
          tag.SetAllHighlevelData(data)
          tag.SetBit(c4d.BIT_ACTIVE)
          node.InsertTag(tag)
          c4d.EventAdd()
      
      def main():
          """
          """
          data = GetCurvature(op)
          CreateVertexMap(op, data)
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Usage of SplineHelp.InitSplineWith() regarding flags -> returned data

      Hey @mogh,

      Thank you for reaching out to us. I agree that it is a bit unclear, but the TLDR is that this is just a minor niche feature. I first thought it would be a way to treat a spline as a close splined, with the description a bit weirdly put, but that is not the case. There is only once place where this flag is used, and it is here:

      cf94c215-834c-406e-b398-eda5e49e79ea-image.png

      I.e., it seems to try to continue the curvature of the last segment. How this works in detail and how sensible this is, I cannot really tell you either. From the code we can see here, it seems to try to parallel transport the last tangent. But there is also something else going on, as it not only takes the last point but the point before that into account (i - 2). It then uses the tangent of m2 * (~m1 * m2), which is also super hand-wavy for me. It basically takes the orientation of the last tangent twice but for A * B, makes B relative to the second last tangent (m1). Or a bit incorrect but more readable: 'It takes the orientation of the last point twice, but also undoes the orientation of the second last point.'

      How this is relevant I have absolutely no idea, probably some MoGraph related 'custom logic'. My advice: Leave it as its default and otherwise ignore it.

      As a warning: All the spline helpers implement parallel transport, which can make their output for curvature tasks undesirable (as it messes with what is the mathematical tangent of a spline in favour of what humans would expect. When you are at a loss about what I am talking about, search the forum for parallel transport and my username, the topic has come up multiple times in the past). It might be better to get the LineObject of a spline, i.e., its current discrete form, and do all the math based on its vertices.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Python tag or Python node in Xpresso crash Cinema 4D

      Hey @SmetK,

      Thank you for the added information; that is really helpful! I unfortunately did not find the time to try this again on Windows, but I'll try next week if I can reproduce your crash. But I am a bit doubtful that jumping from my late-late beta version to the customer RC will make any difference. I will post here an update when I found anything out.

      There exists already a ticket for the crash report I uploaded for you, I will add your explanations there. But I have two questions:

      • May I upload your example file into the ticket?
      • May I share your file in our beta community, to see if anyone else can reproduce the crash?

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Ctr+drag copy vs internal copy in CopyTo function in C++

      Hey @aghiad322,

      Thank you for your code. It is still very unclear to me what you are doing on a higher more abstract level, and on a concrete level, where exactly you want to detect something. Find below your commented code and at the end a few shots into the dark from me regarding what you are trying to do.

      I also just saw now that you posted in the wrong forum. I moved your topic and marked it as C++ and 2025 since you said you are not using the 2026 SDK.

      I hope this help and cheers,
      Ferdinand

      // I wrote this blind without compiling it. So ,there might be some syntax issues in the code I wrote,
      // but it should still illustrate the concepts well enough.
      
      #include "maxon/weakrawptr.h"
      
      using namespace cinema;
      using namespace maxon;
      
      struct NexusMember
      {
          // While we could consider a pointer somewhat conceptually a weak reference, it is factually 
          // not one, as the pointed object being deleted does not invalidate the pointer. This can lead 
          // to dangling pointers and access violation crashes.
          BaseObject* object = nullptr; // weak ref, don’t own it
          Bool isChild = false;         // "include children" flag
      
          // This is better.
          WeakRawPtr<BaseTag> _tagWeakPtr;
      
          // Gets the tag pointed to by this member, or nullptr if it has been deleted.
          BaseTag* GetTag() const
          {
              return _tagWeakPtr.Get();
          }
      
          // Sets the tag for this member.
          void Set(const BaseTag* tag)
          {
              _tagWeakPtr = _tagWeakPtr.Set(tag);
          }
      };
      
      struct NexusGroup
      {
          Vector color;
      
          // No, do not use the std library. Cinema 4D modules are by default compiled without exception 
          // handling for performance reasons, and the standard library uses exceptions for error handling,
          // making its behavior undefined in such builds. std::vector::push_back() can throw 
          // std::bad_alloc, for example.
      
          std::vector<String> links;
          std::vector<NexusMember> members;
          Int32 dirtyLevel = 0;
      
          // MUST be:
          BaseArray<String> links;
          BaseArray<NexusMember> members;
      };
      
      class NexusRegistry : public SceneHookData
      {
      public:
          static NexusRegistry* Get(BaseDocument* doc);
          static NodeData* Alloc();
          virtual Bool Message(GeListNode* node, Int32 type, void* data);
          virtual EXECUTIONRESULT Execute(BaseSceneHook* node, BaseDocument* doc,
                                          BaseThread* bt, Int32 priority, EXECUTIONFLAGS flags);
         
          // The same applies here, the Maxon API alternatives to unordered_map are:
          //
          //   * HashSet (just a hash set of fixed type values without keys)
          //   * HashMap(a hash map of fixed key and value type),
          //   * DataDictionary (a hash map of arbitrary key and value types).
      
          std::unordered_map<Int32, NexusGroup> groups;
          maxon::BaseArray<BaseTag*> pendingRegistration;
      
          void ProcessPendingTags(BaseDocument* doc);
          void RebuildRegistry(BaseDocument* doc);
          void UpdateGroupFromTag(BaseDocument* doc, BaseTag* tag);
          void RemoveObjectFromGroups(BaseObject* obj);
          void RemoveTagFromGroup(BaseTag* tag);
          const NexusGroup* GetGroup(Int32 hash) const;
      };
      
      // -------------------------------------------------------------------------------------------------
      
      void NexusRegistry::UpdateGroupFromTag(BaseDocument* doc, BaseTag* tag)
      {
          if (!tag) return;
          BaseObject* obj = tag->GetObject();
          if (!obj) return;
          BaseContainer* tagContainer = tag->GetDataInstance();
      
          String id = tagContainer->GetString(ID);
      
          // I do not know of which nature this HashID is, but our API has multiple hash functions. 
          Int32 hashedID = HashID(id);
          
          // There is for once StringTemplate::GetHashCode(). I.e., you could do this:
          const HashInt = id.GetHashCode();
      
          // But all scene elements already have a marker on them which uniquely identifies them (if that
          // is what you are after).
      
          // Uniquely identifies over its time of creation and the machine it was created on. I.e., this
          // value is persistent across sessions and unique across machines.
          const GeMarker uuid = tag->GetMarker();
      
          Bool includeChildren = tagContainer->GetBool(CHILDREN);
      
          Int32 oldIdHash = tagContainer->GetInt32(NEXUS_TAG_PREV_ID, NOTOK);
          if (oldIdHash != NOTOK && oldIdHash != hashedID)
          {
              auto it = groups.find(oldIdHash);
              if (it != groups.end())
              {
                  auto& members = it->second.members;
                  // std::vector::erase is not noexcept, this can crash Cinema 4D, unless you compile your
                  // module with exception handling enabled (which we do not recommend for performance
                  // reasons). I am not going to repeat this comment in similar places below.
                  members.erase(std::remove_if(members.begin(), members.end(),
                      [obj](const NexusMember& m) { return m.object == obj; }),
                      members.end());
      
                  it->second.dirtyLevel++;
                  if ((Int32)members.size() == 0)
                      groups.erase(it);
              }
          }
      
          // Update new group
          NexusGroup& group = groups[hashedID];
      
          // Remove duplicates of this object first
          group.members.erase(std::remove_if(group.members.begin(), group.members.end(),
              [obj](const NexusMember& m) { return m.object == obj; }),
              group.members.end());
      
          group.members.push_back({ obj, includeChildren });
      
          ((Nexus*)tag)->UpdateInfo(doc, tag);
      
          // Store current ID for next update
          tagContainer->SetInt32(NEXUS_TAG_PREV_ID, hashedID);
      }
      
      // -------------------------------------------------------------------------------------------------
      
      
      void NexusRegistry::ProcessPendingTags(BaseDocument* doc)
      {
          if (pendingRegistration.IsEmpty())
              return;
      
          Int32 i = 0;
          while (i < pendingRegistration.GetCount())
          {
              BaseTag* tag = pendingRegistration[i];
              BaseObject* op = tag->GetObject();
      
              if (op)
              {
                  UpdateGroupFromTag(doc, tag);
      
                  // This is not how our error system works. Functions of type Result<T> are our exception
                  // handling equivalent. You need iferr statements to catch and/or propagate errors. See
                  // code below.
                  maxon::ResultRef eraseresult = pendingRegistration.Erase(i, 1);
              }
              else
              {
                  i++;
              }
          }
      }
      
      // -------------------------------------------------------------------------------------------------
      
      // See https://developers.maxon.net/docs/cpp/2026_0_0/page_maxonapi_error_overview.html for a more in detail
      // explanation of our error handling system.
      
      // So we have some class with a field whose type has a function of return type Result<T>, e.g., your
      // NexusRegistry. We have now three ways to handle errors when calling such functions:
      
      // Ignoring errors (not recommended):
      void NexusRegistry::AddItem(const String name)
      {
          links.Append(name) iferr_ignore("I do not care about errors here."); // Append is of type Result<void>
      }
      
      // Handling errors locally, i.e., within a function that itself is not of type Result<T>.
      Bool NexusRegistry::RemoveItem(const String name)
      {
          // The scope handler for this function so that we can terminate errors when the are thrown.
          iferr_scope_handler
          {
              // Optional, print some debug output to the console, #err is the error object.
              DiagnosticOutput("Error in @: @", MAXON_FUNCTIONNAME, err);
      
              // We just return false to indicate failure. If we would have to do cleanup/unwinding, we
              // would do it here.
              return false;
          };
      
          const Int32 i = links.FindIndex(name);
      
          // We call our Result<T> function and propagate any error to the scope handler if an error 
          // occurs. The iferr_return keyword basically unpacks a Result<T> into its T value, or jumps 
          // to the error handler in the current or higher scope and propagates the error.
          const String item = links.Erase(i) iferr_return;
          
          return true;
      }
      
      // And the same thing in green but we propagate errors further up the call chain, i.e., our function
      // is itself of type Result<T>. It now also does not make too much sense to return a Bool, so our 
      // return type is now Result<void>.
      Result<void> NexusRegistry::RemoveItem(const String name)
      {
          // Here we just use the default handler which will just return the #err object to the caller.
          iferr_scope;
      
          const Int32 i = links.FindIndex(name);
          const String item = links.Erase(i) iferr_return;
      
          return OK; // Result<void> functions return OK on success.
      }
      
      // -------------------------------------------------------------------------------------------------
      
      
      EXECUTIONRESULT NexusRegistry::Execute(BaseSceneHook* node, BaseDocument* doc,
                                             BaseThread* bt, Int32 priority, EXECUTIONFLAGS flags)
      {
          ProcessPendingTags(doc);
          return EXECUTIONRESULT::OK;
      }
      
      
      // -------------------------------------------------------------------------------------------------
      
      // So, all in all, this does not shed too much light on what you are doing for me :) The main questions 
      // is if you implement your tags yourself, i.e., the items stored in your NexusGroup::members.
      
      // When you implement a node yourself, you can override its ::Read, ::Write, and ::CopyTo functions
      // to handle the node serialization and copying yourself. See https://tinyurl.com/2v4ajn58 for a
      // modern example for that. So for your, let's call it NexusTag, you would do something like this:
      class NexusTag : public TagData
      {
          Bool CopyTo(NodeData* dest, const GeListNode* snode, GeListNode* dnode, COPYFLAGS flags, 
                      AliasTrans* trn) const
          {
              // This is a copy and paste event. There is no dedicated event for CTRL + DRAG you seem
              // to after.
              if (flags & PRIVATE_CLIPBOARD_COPY)
              {
                  // Do something special with the destination node #dnode or call home to you registry.
              }
              else
              {
                  // Do something different.
              }
      
              // We should always call the base class implementation, unless we want interrupt the copy.
              return SUPER::CopyTo(dest, snode, dnode, flags, trn);
          }
      };
      
      // ---
      
      // Another way could using a MessageData hook and monitoring the EVMSG_CHANGE events, i.e., when
      // something in a scene changed. This is usually how render engines synchronize scene graphs. I am
      // not going to exemplify this here, as this is a lot of work.But you can have a look at this thread
      // which is the most simple example we have for this (in Python, but is more or less the same in C++):
      // https://developers.maxon.net/forum/topic/14124/how-to-detect-a-new-light-and-pram-change
      
      // Here you do not have to own the tag implementation. But you could not detect how something has
      // been inserted, only that it has been inserted.
      
      // ---
      
      // Yet another thing which could help are event notifications. I.e., you hook yourself into the copy
      // event of any node (you do not have to own the node implementation for this) and get notified when
      // a copy occurs. But event notifications are private for a reason, as you can easily crash Cinema
      // with them. You will find some material on the forum, but they are intentionally not documented.
      
      // https://tinyurl.com/2jj8xa6s
      
      // ---
      
      // Finally, with NodeData::Init you can also react in your node to it being cloned.
      Bool NexusTag::Init(GeListNode* node, Bool isCloneInit)
      {
          if (isCloneInit)
          {
              // Do something special when this is a clone operation.
          }
      
          return true;
      }
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: The value of 'porgress' during the use of RenderDocument() is greater than 1.0

      Hey @chuanzhen,

      Thank you for reaching out to us. I will have a look at this problem, at the latest by the end of next week (31/10). We are aware that RendeDocument keeps regressing, because there are a lot of render technology changes happening behind the scenes in the Cinema in the last releases.

      Hopefully, this is just a smaller bug, or a user error. But this could also be a more complicated thing. I will let you know at the latest by the end of next week.

      Cheers,
      Ferdinand

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Ctr+drag copy vs internal copy in CopyTo function in C++

      Hey @aghiad322,

      Thank you for your reply. NodeData::Init is always being called when a node is initialized, explicitly also when a node is being copied (that is what the argument isCloneInit is for). But scene hooks only very rarely get copied since they are effectively singletons.

      All I can tell you from your description, is that what you are doing in general - "have a scenehook and it has an array member variable pointing to all tags in the scene with this tag type", sounds like a bad idea. It sounds like you have a BaseArray<BaseTag*> - or even worse, a std::vector<BaseTag*> - on your scene hook as a field; and this is just asking for crashes. You must use either smart pointers (BaseArray<WeakRawPointer<const BaseTag>>) or BaseLink's. But even then this is not such a great idea, your scene hook should gather its inputs each time it needs them. And when you end up constantly scanning the scene, you are doing something wrong design-wise. There are niche scenarios where this cannot be avoided, but I cannot really evaluate that if I have absolutely no idea what you are doing.

      As already lined out, we cannot help you if you do not show us what you are doing. You are likely talking about your tags being copied, but you do not show us if you implement the tag yourself, what your code is, and what you are trying to achieve in general. How are we supposed to help you, when you do not tell us/show us what you are doing?

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Python tag or Python node in Xpresso crash Cinema 4D

      Hey @SmetK,

      Thank you for the reproduction steps, this makes it much easier for us. The added information is that you must play back the timeline. I am, however, still unable to reproduce your crash. But I am still using on Windows a very late beta version of the 2026.0.0 RC, I will try to squeeze in some time this week to install the actual customer RC. I also checked macOS, where I have already 2026_0_0_db12fb68d6ba_2004155263 installed, i.e., your build, and the build the crash report was resolved for, and nothing is crashing there either.

      I also had a look at your scene setup and all in all this is all very tame, especially Python-wise; this should not crash. What is a bit iffy, is that you use both a Python tag and a (Python) Xpresso setup to set scene parameters, and have them both set to the same priority. But the worst this should be able to lead to is race conditions. But since you seem to be able to reproduce this reliably, while we cannot, I would ask you to:

      1. Test if this also crashes when you (a) disable the Python tag, (b) disable the Python tag AND the Xpresso tag, and (c) if it still crashes if you only disable the Xpresso tag.
      2. Test if this still crashes if your make the priority of the Python and Xpresso tag more verbose, i.e., set your Python tag to something like this:

      74db7781-44d4-495c-b328-8aca94233229-image.png

      Overall, this does not strike me as something that is Python induced, as all you do is set parameters.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Python tag or Python node in Xpresso crash Cinema 4D

      Hey @SmetK,

      hm, weird, I could not find your submitted crash report. Neither when I search by date nor by Call_Stack_Thread_24992. But I did insert your crash report myself and below are the last lines of the stack trace for the crashing thread (this is being read bottom to top, i.e., SplitIntoNgons is where it crashed). I did resolve this for the build 2026_0_0_db12fb68d6ba_2004155263. Which is not unimportant information, as using a wrong build to resolve crash offsets can lead to wildly inaccurate results. You can find out the build you are using by opening the Help > About window and there right clicking the build number.

      Call_Stack_Thread_24992
          c4d_objects.xdl64 = maxon::boolean::OutlineSplitter::SplitIntoNgons
          c4d_objects.xdl64 = maxon::boolean::OutlineSplitter::SplitOutline
          c4d_objects.xdl64 = maxon::boolean::CutObject::<lambda_0>::operator()
          ... internal API gibberish, nothing semantically relevant here ...
          c4d_objects.xdl64 = maxon::boolean::PreProcessInput
          c4d_objects.xdl64 = cinema::BooleanGenerator::GetVirtualObjects
          c4d_base.xdl64 = cinema::PluginObject::GetVirtualObjectsNew
          c4d_base.xdl64 = cinema::BaseObject::CreateVirtualObjects
          ... (more lines) ...
      

      So, as already indicated by your bug_report.txt itself, this is crashing in one of major modules of Cinema 4D (c4d_objects.xdl64) and not one of our Python modules (python.xdl64 or python_vm.xdl64). So neither your direct Python code nor the Python VM are the culprit. But it could still be that you are indirectly causing the crash, by for example deleting a scene element off main thread. And SplitIntoNgons then tries to access that object and everything goes up in flames.

      The crash happens while trying to build the output of the new Boolean object. I briefly talked with the dev of the new Booleans, and he said he did not see crash stack trace yet, so this is not a known bug. It seems to choke on some n-gon input in particular. Maybe this does ring a bell for you, in the context of what you are doing?

      Your scene does not crash in my local Cinema 4D 2026.0 (Win) nor the one from the Booleans developer, are you sure you are using the most recent build? Maybe you could try to manually recrate one of your setups (i.e., without Python) and see if that crashes too? Without us being able to reproduce the crash, it will be impossible to help you. See our Support Procedures: Examples for how to report crashes in a more effective way.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Ctr+drag copy vs internal copy in CopyTo function in C++

      Hey @aghiad322,

      Thank you for reaching out to us. Your question is very hard to answer like this. Please familiarize yourself with out support procedures and what makes a good technical question. I have effectively no idea what you are doing.

      When you are talking about a NodeData::CopyTo call, its context is expressed in the flags, including if this was a clipboard event. I would assume that a drag and drop operation, especially for tags, does not entail the tag being copied, but actually rather be moved (i.e., being removed and then re-inserted). If it is being copied, or at least being copied in your case, I do not see how this should then be distinguished from an 'internal' call, since they are all 'internal'.

      I would just look at the flags and see if they change in a manner that is helpful for your task. Although what you are trying to do does not sound like a good idea, but I more or less know nothing about what you are doing and am guessing mostly.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Python tag or Python node in Xpresso crash Cinema 4D

      Just to be clear: I did not want to imply that we do not want to help you. But we need a little bit more from you. I would suggest to try to upload the crash report again. What is even better, is when you tell me a date and time (and your timezone) when you did submit a crash report (or a unqiue message you have put into the crash message).

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Several Asset Browser Issues

      Hey @blkmsk,

      I did not say that your posting was messy 😉 I of course understand that one sometimes can get a bit overwhelmed when in the middle of things, and that it is then quite hard to be super organized. As I said, it was clearly visible that you put effort into this.

      Regarding your bug isssue. In general, I do not like interfering too much with other devs work, and something in our bug tracker being marked as fixed, does not necessarily mean that it will make it into a release. But I pulled up your bug issue #604301 and there is long discussion between devs and QAs going on. Our build system made a comment roughly 72 hours ago, that a fix has been submitted, and shortly after that that is has been fixed (i.e., the build suceeded).

      This is however only a fix, not a verified fix (i.e., no QA has yet confirmed that what the devs came up with actually fixes the problem), it could still be rolled back. For details you better talk with end user support when this is urgent for you. Alin from end user supports seems to have handeled this.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: C4D Prototype To Plugin Converter -> Where did it go?

      Hey @CJtheTiger,

      Thank you for reaching out to us. That seems to be part of what the Cinversity team did before I joined Maxon. This was never a project of the SDK team. And more over, this seems to have been a private project of Niklas Rosenstein, an ex Maxon employe, because the link points to his GitHub. Niklas is still on GitHub, maybe you can send him a message if he still has his old code? But this looks like an R25 or even older project, so this will for sure require some update work.

      There are also the Cinversity legacy downloads on Maxon's downloads page, but it does not seem to contain it either.

      Cheers,
      Ferdinand

      posted in General Talk
      ferdinandF
      ferdinand