Global Moderators

Forum wide moderators

Private

Posts

  • 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()
    
  • 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

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

    It did, thanks, I will have a look.

  • 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.

  • 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

  • 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

  • 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'.
  • 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.

  • RE: How to export icons of asset

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

  • 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()