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
    • Recent
    • Tags
    • Users
    • Register
    • Login
    1. Maxon Developers Forum
    2. ferdinand
    3. Posts
    Offline
    • Profile
    • Following 0
    • Followers 17
    • Topics 58
    • Posts 3,251
    • Groups 2

    Posts

    Recent Best Controversial
    • RE: Thread safety when handling CTrack in TagData.Message() on button click

      @ThomasB

      Reproduction steps just refers to what you do in the GUI as an end user (switch to modeling layout, press play, ...). Do not include any API information, imagine you are someone who knows nothing about our APIs. And if we have or have not your plugin is irrelevant for the report, just state what you do.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Thread safety when handling CTrack in TagData.Message() on button click

      While my UI synchronization from the GeDialog to the Attribute Manager parameters and vice versa runs perfectly safe on the main thread (hope sošŸ™„ :-)) , the crash was triggered inside MSG_DESCRIPTION_COMMAND when clicking the "Convert" button. In that moment, my code overwrites the temporary audio file, temporarily flushes the track path with an empty string "" to force C4D to clear its memory buffer, and reloads the track. Without protection it somehow collided with Redshift or MoGraph trying to read the document's audio properties during a redraw cycle, resulting in the access violation inside the audio driver callback (wdmaud.drv).

      First of all: I am happy that you found your fix.

      But this should not happen, and would imply a severe bug on our side. You should be able to write data as you desire on the MT. I am also not sure if the AI did not misinform you a bit. The stack trace which is found in a bug report contains raw function pointer offsets. Apart from some guesswork based on the DLL names, the offsets are meaningless for a human, even an AI cannot read this. To become meaningful, these stack traces must be resolved by our bug ticket system which then turns the function offset pointers into function and method names and their file and line numbers (it then looks more or less the same as the Python stack traces you know). You can only resolve this when you have access to the debug database /source code of Cinema 4D, which is for obvious reasons not shipped with Cinema 4D.

      Did you submit your crash when Cinema 4D asked you to do so? Because I do not see it in our bug tracker when I search for that stack trace. The strack trace in your crash report must be read bottom to top, i.e., the highest item is the stack frame where it crashed.

      CINEMA_4D_Crash_Report_WINDOWS
      {
      	Call_Stacks
      	{
      		Call_Stack_Thread_57060
      		{
      			VCRUNTIME140.dll:	_NLG_Return2 + 0x6c0 (SP: 0x0000006CAA3FED68, PC: 0x00007FFEB8DB0CF0) // Here is goes 'poof'
      			c4d_base.xdl64:	0x00007FFD9CDB07E6 (SP: 0x0000006CAA3FED70, PC: 0x00007FFD9CDB07E6)
      			c4d_base.xdl64:	0x00007FFD9CDAC864 (SP: 0x0000006CAA3FEDE0, PC: 0x00007FFD9CDAC864)
      			c4d_base.xdl64:	0x00007FFD9CDAC503 (SP: 0x0000006CAA3FEE80, PC: 0x00007FFD9CDAC503)
      			c4d_base.xdl64:	0x00007FFD9CDB480F (SP: 0x0000006CAA3FEF60, PC: 0x00007FFD9CDB480F)
      			c4d_base.xdl64:	0x00007FFD9CDB5914 (SP: 0x0000006CAA3FF500, PC: 0x00007FFD9CDB5914)
      			c4d_base.xdl64:	0x00007FFD9CDB755C (SP: 0x0000006CAA3FF550, PC: 0x00007FFD9CDB755C)
      			c4d_base.xdl64:	0x00007FFD9DA833B4 (SP: 0x0000006CAA3FF590, PC: 0x00007FFD9DA833B4)
      			c4d_base.xdl64:	0x00007FFD9DA82F45 (SP: 0x0000006CAA3FF5F0, PC: 0x00007FFD9DA82F45)
      			winmmbase.dll:	DriverCallback + 0x5a (SP: 0x0000006CAA3FF630, PC: 0x00007FFEB126D38A)
      			wdmaud.drv:	0x00007FFE1D202A35 (SP: 0x0000006CAA3FF670, PC: 0x00007FFE1D202A35)
      			wdmaud.drv:	0x00007FFE1D2020F4 (SP: 0x0000006CAA3FF6E0, PC: 0x00007FFE1D2020F4)
      			wdmaud.drv:	0x00007FFE1D201E45 (SP: 0x0000006CAA3FF970, PC: 0x00007FFE1D201E45)
      			KERNEL32.DLL:	BaseThreadInitThunk + 0x17 (SP: 0x0000006CAA3FF9A0, PC: 0x00007FFED3BAE957)
      			ntdll.dll:	RtlUserThreadStart + 0x2c (SP: 0x0000006CAA3FF9D0, PC: 0x00007FFED52A7C1C)
      			Registers
      			{
      ...
      

      I.e., it crashed in VCRUNTIME140.dll, a Visual C++ Runtime component that is responsible for executing C code. This hints at low level operations such as memory allocation or hardware drivers. And you are right, wdmaud.drv is the Windows Media audio driver. c4d_base.xdl64 is then the base library of the Cinema API, there are a lot of things to be found in there and without resolving the report we cannot know where the problem lies (the stack frame in the Cinema API which then invokes something in VCRUNTIME140.dll). So, this basically goes "Audio Driver -> Cinema Core -> C dinosaur code -> poof".

      Python plugin execution modules do not show up at all (which means that this happens quite a bit after any Python code ran). And Mograph and Redshift modules do not show up in the crashing thread (MG shows up one thread before but that does not mean much).

      It would be great if you could submit the bug report, so that our crash handler can resolve it. I can also technically add your crash dump manually, but that is always a bit of a hassle. This is pretty much guaranteed to be a severe bug somewhere in the audio memory management.

      Cheers,
      Ferdinand

      edit: I edited out the crash report download link in your posting, as it contains some minor sensitive details about your system we do not want to publish to the world when we do not have to šŸ™‚

      PS: And we will of course never shame users for their code, as that would be very counterproductive to what we are trying to do: help developers.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Thread safety when handling CTrack in TagData.Message() on button click

      @ThomasB said in Thread safety when handling CTrack in TagData.Message() on button click:

      I think you misunderstood me. I’m not trying to report a bug here, since the issue isn't with Cinema 4D itself.

      No, I did not misunderstood your situation (at least I think so šŸ˜„ ). We sometimes ask people to report a formal bug report so that we can reproduce an issue. This is not necessarily bound to the bug being in Cinema 4D. Although we often do this when we suspect that the bug is on the Cinema side or at least in a grey area where the plugin does something that is not so great but Cinema also drops the ball in some shape or form.

      Your code, at least the one you shared with us, does not do anything wrong. And even though I know you have put a lot of work in your reply here, a lot of text and diagrams do not help in concretely fixing the issue. To answer a few questions anyway:

      1. NodeData::Message usually runs on the main thread (and you additionally check), so we are fine.
      2. Dialog code also usually runs on the main thread. The only exception can be drawing code (something like GeUseraArea::DrawMsg), although drawing code usually also runs in a special section of the main thread. Hence my hint to use GeIsMainThreadAndNoDrawThread instead of GeIsMainThread in my first posting.
      3. Generally there is no guarantee that any code runs on the MT. As an example, NodeData::Message calls are usually on the MT, especially the message MSG_DESCRIPTION_COMMAND. I.e., when a user clicked a button in your node this should run on the MT as you often want to do GUI things or modify the scene. And Cinema will (should) honor this. But nothing prevents me, Evil Bob the developer, from grabbing your tag, creating a new thread then calling yourTag.Message(MSG_DESCRIPTION_COMMAND, BaseContainer()) from this new thread and with that violate the assumption that description commands alwary run on the MT.
      4. Core messages are also usually sent from the MT, both in a dialog and in a MessageData; so CoreMessage functions should usually run on the MT. Core message (i.e., event) evaluation is also decoupled. When you set a (core) event from a non-MT thread (via SpecialEventAdd), Cinema should properly synchronize and execute the event on the MT. But there could be of course bugs in our code. You should therefore always shield your code with GeIsMainThreadAndNoDrawThread or GeIsMainThread if your code could also run in a drawing context.

      It is good that you have an eye on the threading safety in Cinema as this is something that often trips developers. But I do not think that threading is here the issue. Threading violations usually lead to crashes, corrupted data, or inexplicable behavior. Slowdowns usually mean that either code runs way more often that you think, e.g. some feedback loop between your tag and dialog, or that there is some resource fighting going on. Playing audio means that hardware resources must be allocated. When they are constantly dropped and reallocated, because something else wants to access them too, this can lead to slowdowns. This is still just a guess into the blue of mine, but it would fit the symptoms you are describing.

      Could you please:

      1. Confirm that the issue also happens for your with the simplified plugin.
      2. Provide a step by step bug report to reproducing the issue as described here.
      3. Check that the issue does not happen when you delete your tag (but leave the sound track in the scene). I really want to rule out that Cinema does not have a problem with audio playback on some hardware in general.

      When push comes to shove, you can also share your full plugin in private with us via sdk_support(at)maxon(dot)com. When you can reproduce the issue with the simplified plugin, this is of course sufficient for us to investigate the issue. But if you cannot reproduce the issue with the simplified plugin, then we would need to see the full plugin to understand what is going on.

      Cheers,
      Ferdinand

      edit: I now also tried on my Win 11 machine (Intel chipset, a bit beefier GPU). And I cannot detect any slowdowns in normal scene playback or when using the RS render view. However, when I use the Calculate Fps tool (just press SHIFT + C and type 'FPS'), I experience distorted audio and the view port becomes sightly laggy. This happens with and without your tag, an audio track alone seem to be enough.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: TimeLine DopeSheet not update

      Hello,

      This is a bug. I have fieled it under 'ITEM#649699 Timeline does not react to NBIT selection changes on tracks anymore'. It is unfortunately not a bug in the Python or core C++ API which I could just fix myself, but either a bug in the scene evaluation or the timeline. So, we will have to wait until the owning team tackles it. I notified the owners that timeline automation is a popular scripting task and that they therefore should fix this.

      For now, the only thing I can recommend is to use 2026.2 when you want to carry out such tasks.

      Cheers,
      Ferdinand

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: TimeLine DopeSheet not update

      Hello @chuanzhen,

      Thank you for reporting this. At first glance this looks like a bug/regression in 2026.3.0. I'll have to check in more detail myself before I can say for sure. But your code is also incorrect, the NBIT_TLX_SELECT2 bits are internal and not for what you think they are. You must use NBIT_TLX_SELECT instead. But they are currently malfunctioning for me on Windows in 2026.3.

      Until I am sure, I will not yet classify this as a bug.

      Cheers,
      Ferdinand

      In 2026.2.0, this code randomly selects and deselects tracks in an all timelines for the active object. I.e., you can spam-click the script and see the track selections 'jump around'. In 2026.3 it does exactly nothing (at least on my Windows machine).

      import c4d
      import random
      
      def main() -> None:
          for track in op.GetCTracks():
              bit: int = random.choice([c4d.NBITCONTROL_SET, c4d.NBITCONTROL_CLEAR])
              for tl in (c4d.NBIT_TL1_SELECT, c4d.NBIT_TL2_SELECT, c4d.NBIT_TL3_SELECT, c4d.NBIT_TL4_SELECT):
                  track.ChangeNBit(tl, bit)
          
          c4d.EventAdd()
      
      
      if __name__ == '__main__':
          main()
      
      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Thread safety when handling CTrack in TagData.Message() on button click

      Hey,

      Can you provide a bit more context? I tried your plugin on Cinema 4D 2025.2.1 and macOS 26.5.1. I added your tag to a scene, clicked the "Add Audio" button, and started playback.

      c1dd2914-3795-4dc5-a7c3-a1fba9066410-image.png

      • I experience no slowdowns when I run the scene playback (F8) or the RS render view.
      • I hear the base_1.wav sound playing your plugin adds.

      Can you please:

      • Provide the type and version of operating system you use.
      • Provide the version of Cinema 4D you use.
      • Describe the hardware you use.
      • Provide an example scene.
      • Check if you experience the same slowdowns when your plugin is being removed from the scene (but the sound track it has added is being left in.
      • If necessary, provide a step-by-step instruction to describe your bug. See here for how to formally make a bug report.

      My hunch right now is that your plugin has probably little to do with the slowdowns, but that sound track playback is the culprit which might lead to 'resource fighting' on some systems.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Thread safety when handling CTrack in TagData.Message() on button click

      It did, thanks, I will have a look.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Thread safety when handling CTrack in TagData.Message() on button click

      @ThomasB

      Absolutely, but you have to either grant me access or make the whole folder public.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • GeDialog::GetFilename currently broken in 2026.3.0

      Dear community,

      GeDialog::GetFilename is broken in 2026.3.0 in both the C++ and Python API, returning the empty filename/string, even when a gadget holds a valid string. Plugins using dialogs which use Filename parameters are therefore currently malfunctioning when used in 2026.3.0. The issue does not exist in 2026.2.0 or earlier versions.

      We will deploy the hotfix for this issue in the coming weeks.

      Cheers,
      Ferdinand

      posted in Bugs 2026 python cpp bug windows macos
      ferdinandF
      ferdinand
    • RE: Thread safety when handling CTrack in TagData.Message() on button click

      Hello @ThomasB,

      could you share your plugin via something like dropbox or google drive with us? You upload above seems to have failed.

      Your code looks fine, you do everything correctly at first glance. You only try to modify the scene in the context of MSG_DESCRIPTION_COMMAND (which should only be invoked from the main thread) and shield yourself against rogue actors by also checking c4d.threading.GeIsMainThread() (slightly better would be GeIsMainThreadAndNoDrawThread but that is just semantics). Your node does also not do anything wildly expensive in its Init/__init__.

      But I am sure you are not just imagining your performance issues. Generally speaking, your code should only run when the user clicks the button with the ID PY_ADD_AUDIO in the description of your tag. So, this should not be able to accidently run when you render or while scene playback (which I assume is what you mean with 'running (an) animation').

      PY_TRACK seems to be a parameter of type DTYPE_BASELINK where you just link the newly created track. I cannot really see anything going wrong with this, as this is very harmless. I also checked if there is any 'special sauce' in creating sound tracks, as they are one of the special tracks, but as far as I can see, we are doing this internally exactly like you do it. So, this also does not seem to be a case of an incorrectly allocated node, which then constantly causes internal errors.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to get selected DescId and BaseList2D at the same time

      Hey @ECHekman,

      well, when a node displays an embedded description (what you exemplify at the case of a BaseLink), it uses a DescriptionCustomGui too. Other parameter types aside from a BaseLink which can do this are for example field lists.

      I am quite frankly a bit surprised that this even bubbles up in the form you report it, that you get a DescID which is selected inside a DescriptionCustomGui that is shown by the node in the DescriptionCustomGui you opened yourself. Attribute Manger selection states (and by extension DescriptionCustomGui) are not a public API feature, as we usually keep our GUIs sealed, i.e., inaccessible to third parties. DescriptionCustomGui is here a bit in a grey area, but I am afraid there is no way to do what you want to do. To do what you want to do, you would need access to internal systems.

      And other than in some other cases, where we do not always had the time to expose something (and are open to the idea of changing something), this is for GUIs not the case; they are intentionally sealed as we do not want third parties to change basic UX concepts.

      My general advice would also be to not to try to overcome this boundary (that a plugin reacts to which parameter in a node is being highlighted by the user), as you break UX conventions of Cinema 4D with that.

      Cheers,
      Ferdinand

      You can do this, but it is very hacky:

      1. Get the description of the node(s) you are currently displaying in the your DescriptionCustomGui.
      2. Check if desc is a member via CheckDescID (I think GetParameter would also work and return an empty container when the ID does not exist).
      3. When the ID does not exist, start browsing the container for parameters that are of a DTYPE that implies an underlying node, such as DTYPE_BASELINK.
      4. When you find such parameter, get the node node, and check there.
      5. Rinse and repeat recursively.

      Issues:

      1. You cannot distinguish the case where an object A has two BaseLink parameters which both hold an object of type T, but not the same instances.
      2. You will run into issues with DescID translations which some nodes might do. This is a really big point, which is probably already overlooked in your current code.
      3. Nested node relations can get REALLY complicated. BaseLink is trivial, but stuff like field lists, volume lists, or some special shader stuff can get really complicated. You would probably only have to worry about base links and field lists in your case, as other renderers are irrelevant for you, but field list alone are already complex enough that I would strongly advise against 'just implementing it'.
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • Maxon Cinema 4D 2026.3 SDK Release

      Dear development community,

      On June the 10th, 2026, Maxon Computer released Cinema 4D 2026.3. For an overview of the new features please refer to the end user release notes.

      Alongside this release, existing APIs have been updated. For a detailed overview, please see the Cinema 4D C++ SDK change notes.

      Cinema 4D

      C++ API

      • Added ARM64 support for Windows.
      • Added support for Xcode 26 as a build platform on macOS.

      Python API

      • No changes

      Happy rendering and coding,
      the Maxon SDK Team

      ℹ Cloudflare unfortunately still does interfere with our server cache. You might have to refresh your cache manually to see new data when you read this posting within 24 hours of its release.

      posted in News & Information news cinema 4d c++ python sdk information
      ferdinandF
      ferdinand
    • RE: How to export icons of asset

      Is there still an unanswered question here? If so, please reiterate what you want to achieve.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Reverse direction of multi-segment splines

      Okay,

      I think I was a bit overly cautious in my answer here. You gave my a very broad question, or to be precise, you gave me a video and a scene file, and did not really ask a precise question. I understand that asking good questions can be hard, especially with a language barrier. But when I have nothing to go by, I of course assume the worst case possible.

      The scene you have there is a trivial case, even the best case possible. You have there spline segments which lie in a perfect plane and which have no self intersections. You can treat them just like polygons (in the CG sense, not in the mathematical sense) and simply compute their normal over the first three vertices of each segment. Due to the fundamental property of a polygon - reversing the order of the vertices reverses the normal - you can then easily determine if two segments have opposite or equal winding.

      But all this starts to fall apart, as soon as you cannot make these assumptions. And I cannot help you to write the code for this, as this is then more than just a few lines. Hope this helps, and that I my answer was now less 'overly cautious'.

      Cheers,
      Ferdinand

      Result

      85f415e7-7f45-4c00-9651-b4091ee543e2-image.png
      The code correctly identifies that in this six segment spline are four segments of one winding direction (clockwise in this case) and two of the other winding direction.

      winding_direction.c4d

      Code

      """Treats spline segments as polygons and compares their plane normals to find 
      segments with reversed winding order.
      """
      
      import c4d
      
      op: c4d.SplineObject # The currently active object in the scene.
      
      def main() -> None:
          """Called by Cinema 4D when the script is being executed.
          """
          if not isinstance(op, c4d.SplineObject):
              return c4d.gui.MessageDialog("Please select a spline object.")
          
          # Get all points in the spline and organize them into their segments.
          points: list[c4d.Vector] = op.GetAllPoints()
          segments: list[list[c4d.Vector]] = []
      
          j: int = 0
          for i in [op.GetSegment(i)["cnt"] for i in range(op.GetSegmentCount())]:
              segments.append(points[j:j+i])
              j += i
      
          if len(segments) < 2:
              return c4d.gui.MessageDialog("Please select a spline object with at least 2 segments.") 
      
          # Now build normal data for the spline segments. This assumes:
          #
          #   - A spline where all points lie in a single plane, a '2D' spline in 3D space.
          #   - No self intersections in the spline.
          #   - A piecewise linear spline, i.e., what Cinema 4D calls a 'Linear' spline. When we have a
          #     'Cubic' or 'Bezier' spline, we would have make it linear with 'Current State to Object' 
          #     first.
          #
          # We build the normal for each segment over its three first vertices. The reason why we are doing 
          # this is because of the fundamental identity of a polygon (in a computer graphics sense), 
          # reversing the order of the vertices of a polygon will reverse the normal. So if we have a 
          # spline with two segments with reversed winding order, they will have antiparallel normals (normals 
          # pointing in opposite directions). 
          segmentNormals: list[c4d.Vector] = []
          for segment in segments:
              if len(segment) < 3:
                  print("Segment has less than 3 points, skipping normal calculation.")
                  continue
      
              a, b, c = segment[0], segment[1], segment[2]
              edge1: c4d.Vector = b - a
              edge2: c4d.Vector = c - b
              normal: c4d.Vector = edge1.Cross(edge2).GetNormalized()
              segmentNormals.append(normal)
      
          # Now we just declare one segment as 'ground truth' and check if the other segments have normals 
          # that are parallel or antiparallel to it. When we found a segment with an antiparallel normal, we 
          # know we found a segment with reversed winding order. To check if two normals are parallel or 
          # antiparallel, we just compute their dot product (i.e., spanned angle). When the dot product is 
          # negative, the normals are antiparallel.
          print(f"Establishing the first segment normal {segmentNormals[0]} as ground truth.")
          baseNormal: c4d.Vector = segmentNormals[0]
          for i, normal in enumerate(segmentNormals[1:], start=1):
              isAntiparallel: bool = baseNormal.Dot(normal) < 0
              print(f"Segment {i} normal: {normal} is {'antiparallel' if isAntiparallel else 'parallel'} "
                    f"to the base normal {baseNormal}.")
              
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to export icons of asset

      Hm, okay. So, the situation is, you have a Cinema app/plugin, but since you use a non-native GUI, you must bridge the gap to your GUI and Cinema native image data.

      Please understand that using external UI kits is out of scope of support. Not only because it is a third party library, and because we refuse support for them, but also because we generally do not like it when plugins use other UI tool kits than the native one.

      With that being said:

      The script I have shown you above would be at least a way to cache command icons. You would have to add a mechanism to avoid having to write all bitmaps to disk every time, and to delete unused ones, because as I explained above, not all commands are static.

      For assets I would have to check myself or see some actual code. My base assumption would be that at least bitmap icons of assets can be loaded via BaseBitmap::Init (or InitWith in Python), even when the asset uses an exotic URL such as c4d://[relative:///icon]/5159. Because Python binds to BaseBitmap::Init which expects a cinema::Filename as the first argument, which will be internally converted to a maxon::Url and the Maxon API should then just figure out what you mean with c4d://[relative:///icon]/5159 and load the correct bitmap for you. In asset icons you might also encounter other exotic URL types such as ramdisk urls, zipped urls and more. But in all cases, at least in theory, the unpacking into a BaseBitmap should work from the Python API. For vector icons you will have to wait a bit.

      Cheers,
      Ferdinand

      PS: I am also not sure if serializing to disk is the best way. In C++ it would be for sure not be faster to write everything to disk and then read it back, instead of just converting data in memory. In slow, slow, slow Python, it might actually be faster to write stuff to disk and load it back using the C++ backend, than copying data in Python in memory.

      I.e., I mean something like this. But I did it there in the other direction, our GUI wraps alien image data. And not an alien GUI wraps our image data.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to export icons of asset

      Hm,

      I still do not really understand what you want to do. I suppose you want to support assets dragged into palettes as I have done below with a texture? Because that is the only way how I can bring the terms "asset", "command", and "icon" into a meaningful context.

      40b5df15-7d82-49a6-bb74-bdae7cf90549-image.png

      Most commands, as for example an explicitly implemented CommandData plugin or the CommandData wrapper created by Cinema 4D for a NodeData plugin such as ObjectData or TagData are static, i.e., their ID will never change.

      But Cinema 4D also creates commands dynamically. The Script Manger does this for example (exposde via c4d.GetDynamicScriptID). This might be something you are aware of, and which might have let you to assume that the Asset Browser does the same. Which is unfortunately not the case. The Asset Browser is using a non-public mechanism to create "commands" in palettes which are not really commands. So, you cannot CallCommand them. This little script will walk all commands for you and dump their name, icon, and ID.

      import c4d
      
      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.
          """
          command: c4d.plugins.BasePlugin
          for command in c4d.plugins.FilterPluginList(c4d.PLUGINTYPE_COMMAND, True):
              name: str = command.GetName()
              if not name:
                  continue
      
              cid: int = command.GetID()
              icon: c4d.BaseBitmap | None = c4d.bitmaps.InitResourceBitmap(cid)
              if icon is None:
                  print(f"Found command with name '{name}', id {cid}, and no icon.")
              else:
                  print(f"Found command with name '{name}', id {cid}, and an icon {icon}.")
      
      
      if __name__ == '__main__':
          main()
      

      This will contain:

      • Natively implemented commands ("My Light Manger", "Move")
      • Command wrappers for NodeData plugins ("My Object", "Cube")
      • Command wrappers for scripts ("My Script", "untitled 2")

      But it will not contain asset "commands" as the Asset Browser does not create real commands for the assets dragged into palettes but uses a non-public mechanism to trigger the assets when clicking on them. You can of course also export asset icons to some degree, but these are (a) not commands and (b) there are the hurdles in the way as expressed in my previous post.

      The command IDs discovered in this manner will also contain dynamically created commands such as the ones created by the Script Manager. So, using such a data source for a running instance of Cinema 4D is fine, but you cannot 'cache' such data as the command ID of the "unitled 2" script wrapper will change every time you restart Cinema 4D.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Reverse direction of multi-segment splines

      Hey @Tpaxep,

      please have a look at How to Ask Questions. Your question is very ambigous, and it is impossible to help you like this. I assume the issue for you is that the "holes" in your font spline have another winding direction than the outline?

      4b5168f0-188e-435e-99ce-6a0906d6f706-image.png

      I.e., the 'hole' in the R is winding counter-clockwise/negatively, while the outline is winding clockwise/positively. This is a non-trivial problem from geometry processing, and our API does not offer any ready made tools for this niche problem. One of the many approaches can be to compute the signed area of the segment (or a bit more advanced: the shoelace formula), but this can fail for self-intersecting splines and will not work for 3D splines. You can also try to compute the winding direction of the spline by looking at the cross product of the tangents, but this will also fail for self-intersecting splines and 3D splines. For 3D splines, you can work with the vector area of the spline, but this will also fail for self-intersecting splines.

      There are generic solutions to this problem, but they are not trivial and discussing such problems is out of scope of support, unless the user approaches us with a specific question for an already implemented solution.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Copy res folder without shortcut

      That is of course also a valid option, just pick the scripting language you are most comfortable with.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to export icons of asset

      Hey,

      I am not quite sure I understand the question. In case this is related to your vector file request, let me preface this that internally my merge request for BaseBitmap::InitWithVectorFile is already pending. There are some technical discussions still going on about details of the MR, but a feature like this will come in one form or another. I have implemented this in C++ and the ported it back to Python, so that both APIs have this feature. The feature is targeting the next non-hotfix release after the next non-hotfix release (i.e., it will roughly arrive in Q3, and as an MRD you will have access to as soon as we start publishing tester builds for this release).

      regarding your question, I am not really sure what you are asking for. Let me try to clarify some things about icons in asset databases:

      • Icons for assets can change dynamically, the Dots Preset Asset Example I wrote a long time ago is a good example for this. So, caching icons can lead to stale icons.
      • Icons do not have to be necessarily expressed in an URL scheme that makes much sense without the Maxon API, e.g., you could encounter URLs such as c4d://[relative:///icon]/5159, which would be an asset that is referencing the Cinema API icon resource 5159, i.e., c4d.Ocube.
      • Generally, the raw asset database file scheme, while somewhat human readable, is not meant to be parsed by external tools, and the format can change without much notice. The only way to reliably parse these files is to use the Cinema and Maxon API, which will abstract away from any file format changes.

      So, if you want to parse asset database resources for icon data, you will need to have access to the Cinema and Maxon API. And even then, this can be very much non-trivial.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Reverse direction of multi-segment splines

      Hello @Tpaxep,

      Your link does not work as your file upload seems to have failed. But there are in general two ways to achieve this.

      1. Manually reverse the spline. For a simple non-bezier spline this is trivial, you just reverse both the points and the segments. When you want to also support bezier splines, i.e., tangents, this becomes a bit more work.
      2. Just use the modeling command MCOMMAND_SPLINE_REVERSE. Modeling commands can be undesirable in some contexts, see the extrude example in Modeling Commands for details.

      Cheers,
      Ferdinand

      """Reverses the selected editable spline object.
      """
      
      import c4d
      
      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.
          """
          if not isinstance(op, c4d.SplineObject):
              return c4d.gui.MessageDialog("Please select a spline object.")
          
          doc.StartUndo()
          if not c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_SPLINE_REVERSE, list=[op], 
                                               mode=c4d.MODELINGCOMMANDMODE_ALL, doc=doc):
              c4d.gui.MessageDialog("Failed to reverse the spline.")
      
          doc.EndUndo()
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand