The Maxon SDK Team is currently short staffed due to the winter holidays. No forum support is being provided between 15/12/2025 and 5/1/2026. For details see Maxon SDK 2025 Winter Holidays.
  • 0 Votes
    2 Posts
    43 Views
    ferdinandF
    Hey @lasselauch, Thank you for reaching out to us. The issue is likely that you do not respect OCIO color management in your document. You have marked this posting as S26, but my hunch would be that you are using a newer version of Cinema 4D and an OCIO enabled document. The first implementation of OCIO showed up with S26 in Cinema 4D, although it was just some internal systems then such as the flag DOCUMENT_COLOR_MANAGEMENT_OCIO and user facing systems arrived with 2024 the earliest I think. In Python, you can only truly deal with this in 2025.0.0 and higher, as that is when we added the OCIO API to Python. When you indeed are testing this on an S26 or lower instance of Cinema 4D, the major question would be if DOCUMENT_LINEARWORKFLOW is enabled or not. Because you cannot just blindly convert colors. Is this the intended behavior? Should we always convert LINEAR→sRGB when drawing colors from ColorField/ColorDialog in a GeUserArea? The question would be what you consider here this ;). But if the question is if it is intended behavior for a scene with DOCUMENT_COLOR_MANAGEMENT_BASIC and DOCUMENT_LINEARWORKFLOW enabled, to have all its scene element and parameter colors expressed as sRGB 1.0, then yes, that is the major gist of the old linear workflow. It is also intended that drawing happens in sRGB 2.2 up this day. Issue 2: Eyedropper Roundtrip Doesn't Preserve Saturated Colors Generally, multi question topics tend to become a mess (which is why we do not allow them). But in short: While roundtrips in the form of sRGB 1.0 -> sRGB 2.2 -> sRGB 1.0 are not absolutely lossless (you are always subject to floating point precision errors), there is no giant loss by default. I am not sure what math TransformColor is using explicitly. A simple pow(color, 2.2) and pow(color, 1/2.2) are the naive way to do this and the loss would be rather small. TransformColor might be respecting tristimulus weights which is a bit more lossy but still in a small range. OCIO roundtrips on the other hand are generally quite lossy, because ACEScg is a very wide gamut color space and converting from ACEScg to sRGB 2.2 and back can lead to significant losses in saturated colors. Some conversion paths in OCIO are even irreversible in a certain sense (depending on what color spaces you have assigned to which transform). OCIO is rather complicated to put it mildly. Is there a known issue with the ColorDialog eyedropper and color space conversion for saturated colors? Not that I am aware of. But you likely just ignored OCIO. And while the default OCIO Render Space of Cinema 4D (ACEScg) is in a certain sense similar to sRGB 1.0 for low saturated colors, it diverges significantly for highly saturated colors. So, your loss of saturation is likely stemming from treating an OCIO document with an ACEScg render space as sRGB 1.0. See also: Python OCIO Example: open_color_io_2025_2.py Python OCIO Example: py-ocio_node_2025.pyp C++ Manual: Color Management C++ Manual: OCIO Last but not least, I attached an example of what you are trying to achieve, get a color from a color gadget in a dialog and draw with it faithfully in your own user area. Cheers, Ferdinand """Demonstrates how to correctly draw with OCIO colors in a dialog. This examples assumes that you are using Cinema 4D 2025+ with an OCIO enabled document. It will also work in other versions and color management modes, but the point of this example is to demonstrate OCIO color conversion for drawing in dialogs (more or less the same what is already shown in other OCIO examples in the SDK). """ import c4d from c4d import gui class ColorArea(gui.GeUserArea): """Draws a color square in a custom UI element for a dialog. """ def __init__(self): self._color: c4d.Vector = c4d.Vector(1, 0, 0) # The color to draw, this is in sRGB 2.2 def GetMinSize(self): return 75, 20 def DrawMsg(self, x1: int, y1: int, x2: int, y2: int, msg: c4d.BaseContainer) -> None: """Draw the color of the area. """ self.OffScreenOn() # Draw the color. self.DrawSetPen(self._color) self.DrawRectangle(x1, y1, x2, y2) class ColorDialog(gui.GeDialog): """Implements a dialog that hosts a color field and chooser as well as our custom color area. The colors in the color field and chooser are in render space, so we have to convert them to sRGB for correct display in our user area. All three color widgets are kept in sync, i.e., changing one updates the others. """ ID_DUMMY_ELEMENT: int = 1000 ID_COLOR_CHOOSER: int = 1001 ID_COLOR_FIELD: int = 1002 ID_COLOR_AREA: int = 1003 SPACING_BORDER: int = (5, 5, 5, 5) SPACING_ELEMENTS: int = (5, 5) DEFAULT_FLAGS: int = c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT def __init__(self) -> None: """Initializes the dialog. """ # Reinitializing the color area each time CreateLayout is called, could cause loosing its # state when this is an async dialog docked in the UI and part of a layout, as CreateLayout # can be called more than once when a dialog must be reinitialized on layout changes. So, # doing it in __init__ or guarding it with a check if it is already created in CreateLayout # is better. self._color_area: ColorArea = ColorArea() def CreateLayout(self) -> None: """Called by Cinema 4D to populate the dialog with elements. """ self.SetTitle("Dialog Color OCIO Demo") # Using the same ID for dummy elements multiple times is fine, using IDs < 1000 is often # not a good idea, as Cinema 4D usually operates in that range, and therefore an ID such # as 0 can lead to issues (0 is AFIAK not actually used but better safe than sorry). if self.GroupBegin(self.ID_DUMMY_ELEMENT, self.DEFAULT_FLAGS, cols=1): self.GroupBorderSpace(*self.SPACING_BORDER) self.GroupSpace(*self.SPACING_ELEMENTS) # Add a color chooser and a color field. self.AddColorChooser(self.ID_COLOR_CHOOSER, c4d.BFH_LEFT) self.AddColorField(self.ID_COLOR_FIELD, c4d.BFH_LEFT) # Add our user area to display the color. self.AddUserArea(self.ID_COLOR_AREA, c4d.BFH_LEFT) self.AttachUserArea(self._color_area, self.ID_COLOR_AREA) self.GroupEnd() return True def InitValues(self) -> bool: """Called by Cinema 4D to initialize the dialog values. """ self.SetColors(c4d.Vector(1, 0, 0)) return True def SetColors(self, color: c4d.Vector, doc: c4d.documents.BaseDocument | None = None) -> None: """Sets the colors of all color widgets to the given render space #color. """ # Just set the two color widgets first, as they expect render space colors. self.SetColorField(self.ID_COLOR_CHOOSER, color, 1.0, 1.0, c4d.DR_COLORFIELD_NO_BRIGHTNESS) self.SetColorField(self.ID_COLOR_FIELD, color, 1.0, 1.0, c4d.DR_COLORFIELD_NO_BRIGHTNESS) # When the call did not provide a document, use the active document. if not isinstance(doc, c4d.documents.BaseDocument): doc = c4d.documents.GetActiveDocument() # Check in which color mode the document is. Explicit OCIO color management exists in this # form since S26 but it really only took off with 2025. isOCIO: bool = False if (c4d.GetC4DVersion() >= 2025000 and doc[c4d.DOCUMENT_COLOR_MANAGEMENT] == c4d.DOCUMENT_COLOR_MANAGEMENT_OCIO): # All colors in a document are render space colors (including the color fields in # dialogs). GUI drawing however still happens in sRGB space, so we need to convert # the render space color to sRGB for correct display. For that we need a document # because it contains the OCIO config and the converted which is derived from it. converter: c4d.modules.render.OcioConverter = doc.GetColorConverter() # Transform a render space color to sRGB space (there are other conversion paths # too, check the docs/examples on OCIO). color: c4d.Vector = converter.TransformColor( color, c4d.COLORSPACETRANSFORMATION_OCIO_RENDERING_TO_SRGB) isOCIO = True elif not isOCIO and doc[c4d.DOCUMENT_LINEARWORKFLOW]: # For non-OCIO documents (older than S26 or DOCUMENT_COLOR_MANAGEMENT_BASIC), the scene # element color space ('render space' in OCIO terms) can either be sRGB 2.2 or sRGB 1.0 # (linear sRGB), depending on whether DOCUMENT_LINEARWORKFLOW is set or not. In that # case, we would have to convert from gamma 1.0 to 2.2. In a modern OCIO document, we # could also use #converter for this, but for legacy reasons I am using here the old # c4d.utils function. It might be better to use the converter when this is a 2025+ # instance of Cinema 4D. #DOCUMENT_LINEARWORKFLOW is really old, it exists at least # since #R21 (I did not check earlier versions), so I am not doing another version check. color = c4d.utils.TransformColor(color, c4d.COLORSPACETRANSFORMATION_LINEAR_TO_SRGB) # Last but not least, in practice you would probably encapsulate this logic in your user # area, similarly to how native color elements operate just in Render Space but draw in # sRGB space. For dialogs (compared to description parameters), this is a bit complicated # by the fact that one cannot unambiguously associate a dialog with a document from which # to take the color management settings. A custom GUI of a description parameter can # always get the node it is hosted by and its document. For dialog GUIs that is not possible. # So, we have to do the active document approach I showed here. # In a super production scenario, you would overwrite CoreMessage() of the user area or # dialog, to catch the active document changing, to then update the color conversion, as # with the document change, also the OCIO config could changed and with that its render # space transform. # # All in all probably a bit overkill, and I would ignore this under the banner of "who # cares, just reopen the dialog and you are fine". Because users will also rarely change # the default render space transform of ACEScg to something else. self._color_area._color = color self._color_area.Redraw() def Command(self, id: int, msg: c4d.BaseContainer) -> bool: """Called by Cinema 4D when the user interacts with a dialog element. """ if id == self.ID_COLOR_CHOOSER: color: c4d.Vector = self.GetColorField(self.ID_COLOR_CHOOSER)["color"] self.SetColors(color) elif id == self.ID_COLOR_FIELD: color: c4d.Vector = self.GetColorField(self.ID_COLOR_FIELD)["color"] self.SetColors(color) return True # Please do not do this hack in production code. ASYNC dialogs should never be opened in a Script # Manager script like this, because this will entail a dangling dialog instance. Use modal dialogs # in Script Manager scripts or implement a plugin such as a command to use async dialogs. dlg: ColorDialog = ColorDialog() if __name__ == '__main__': dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=480, defaulth=400)
  • 0 Votes
    24 Posts
    5k Views
    Y
    Oh, you've edited your previous message! Thanks! I will try it.
  • Image Viewer API

    Cinema 4D SDK 2024 s26 python
    6
    0 Votes
    6 Posts
    995 Views
    K
    @i_mazlov Thanks for the reply. I may have described it wrong. I am talking about the image viewer. It seems that there is currently no API interface to get the number of frames rendered, nor can I get the total number of frames in the rendering settings.
  • 0 Votes
    3 Posts
    612 Views
    B
    Hello, Thanks for your complete answer. I think I will not start in thread management, but opted for your last solution which will still go through an external image but in an automated way by a python script. Thanks again.
  • How to Swap location in Object Manager

    Cinema 4D SDK c++ s26 windows
    3
    2
    0 Votes
    3 Posts
    721 Views
    P
    @ferdinand Thank you very much for your reply
  • 0 Votes
    2 Posts
    590 Views
    ferdinandF
    Hello @pchg, Thank you for reaching out to us. Please note that we normally expect question to be accompanied by code. It is also still unanswered in which context you want to do this: As an effector, as a Script Manager script, etc. Cheers, Ferdinand Result [image: 1730718564670-347fc2f0-a0cd-4583-816b-b3e38fda2280-image.png] Code """Reads out the particle data of a MoGraph Cloner object. Must be run as a Script Manager script in Cinema 4D with a MoGraph Cloner object selected. """ 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 op or not op.CheckType(c4d.Omgcloner): raise ValueError("Please select a MoGraph Cloner object.") # Get the particle data from the cloner object. data: c4d.modules.mograph.MoData = c4d.modules.mograph.GeGetMoData(op) if data is None: return # Get the transform and color data for the particles. matrices: list[c4d.Matrix] = data.GetArray(c4d.MODATA_MATRIX) colors: list[c4d.Vector] = data.GetArray(c4d.MODATA_COLOR) # Iterate over all particle data and do something with it. If we were in an effector, we could # also write back data. Technically, we could also write back data here, but it would be volatile. for i in range(data.GetCount()): matrix: c4d.Matrix = matrices[i] color: c4d.Vector = colors[i] print(f"Particle {i} at {matrix.off} with color {color}") if __name__ == '__main__': main() And here is the 2024.0.0 effector full control mode default code which more or less is very similar but also writes data. """Realizes an effector that attracts MoGraph particles spherically around its origin. Add the example to a Matrix object to understand its effect. In Full Control mode we can realize a true attraction force as we have full control over the particle values. Compare this example to Parameter Control mode to understand the difference. """ import c4d op: c4d.BaseObject # The Python Effector object containing this code. gen: c4d.BaseObject # The MoGraph Generator executing `op`. doc: c4d.documents.BaseDocument # The document `op` and `gen`are contained in. def main() -> bool: """Called by Cinema 4D to evaluate the effector. """ # Get the particle data for the effector #op. Get out when either the data cannot be retrieved. data: c4d.modules.mograph.MoData = c4d.modules.mograph.GeGetMoData(op) if data is None: return 1.0 # The transform of both the generator and the effector, the transforms of all particles, and # finally the position of the effector as if it would live in the coordinate system of the # generator. mgGen: c4d.Matrix = gen.GetMg() mgEff: c4d.Matrix = op.GetMg() matrices: list[c4d.Matrix] = data.GetArray(c4d.MODATA_MATRIX) q: c4d.Vector = ~mgGen * mgEff.off # For each particle compute a weight `w` for how much the particle should be attracted to the # attraction point `q`, and then blend the particle position between the attraction point and # its own position `p`. for i in range(data.GetCount()) : p: c4d.Vector = matrices[i].off w: float = c4d.utils.RangeMap((mgGen * ~mgEff * p).GetLength(), 0., 100., 0., 1., True) ** 3. matrices[i].off = c4d.utils.MixVec(q, p, w) # Write the new data back. data.SetArray(c4d.MODATA_MATRIX, matrices, op[c4d.FIELDS].HasContent()) return True
  • How to modify the index

    Cinema 4D SDK s26 c++ windows
    4
    1
    0 Votes
    4 Posts
    903 Views
    P
    @ferdinand Thank you for your reply
  • 0 Votes
    4 Posts
    861 Views
    ferdinandF
    Oh, my bad, I did overlook that you flagged this as S26. Yes, mxutils is a 2024+ feature. I used it here to carry out type checks, e.g., that a document or bitmap are not null/none. The code will run without them, but it will fail more gracefully with these checks. You could replace these calls with manual checks: bmp: c4d.bitmaps.BaseBitmap = c4d.bitmaps.MultipassBitmap( int(rData[c4d.RDATA_XRES]), int(rData[c4d.RDATA_YRES]), c4d.COLORMODE_RGB)) if bmp is None: # or more precise: if not isinstance(bmp, c4d.bitmaps.BaseBitmap) ... raise MemoryError("Failed to allocate bitmap.") Cheers, Ferdinand
  • 0 Votes
    4 Posts
    916 Views
    O
    And that's good to know about using GetMl() over Get Mg(), mainly for the performance reasons if I understood correctly.
  • Set UVWs for Ngons?

    Cinema 4D SDK s26 python
    4
    1
    0 Votes
    4 Posts
    660 Views
    E
    @ferdinand I figured this issue out so you can mark this as solved/closed. Thank you.
  • Not able to post?

    Cinema 4D SDK s26 python
    2
    0 Votes
    2 Posts
    509 Views
    ferdinandF
    Hey @ELJeffery, Thank you for pointing this out. We are generally aware of this issue. If anyone else is experiencing similar issues, please point them out. While we are aware that the issue exists and we can see the incident reports in the back end, it is not 100% clear to us how frequent this does happen to human users (and how urgent this is issue is). So, when you run into the issue too, please drop us here the Cloudflare Ray ID of your error page. As an FYI, the issue usually rectifies itself after a few minutes. Cheers, Ferdinand
  • 0 Votes
    2 Posts
    755 Views
    ferdinandF
    Hello @Hohlucha, Welcome to the Maxon developers forum and its community, it is great to have you with us! Getting Started Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules. Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment. Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support. Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads. It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions. About your First Question This is a development forum and your question does not seem to be development related. Please read our forum guidelines lined out above. I have moved this topic into General Talk for now. When this is indeed an end user question we must ask you to use our Support Center, the developer forum is not the right place for end user questions. When this is a development question, then please line out the current code you have and provide a meaningful problem description. Cheers, Ferdinand
  • 0 Votes
    8 Posts
    2k Views
    C
    @i_mazlov , Do you speak Russian?
  • How to preserve the animation of a sphere

    Cinema 4D SDK s26 c++ windows
    9
    2
    0 Votes
    9 Posts
    2k Views
    P
    @i_mazlov thank you very much
  • 0 Votes
    3 Posts
    974 Views
    F
    Hello @ferdinand Thank you for your guidance. I'll follow your suggestion and reach out to the support team for assistance with my query. Best regards, Tomasz
  • How to get sphere coordinates

    Cinema 4D SDK s26 c++ windows
    6
    2
    0 Votes
    6 Posts
    1k Views
    i_mazlovI
    Hi @pchg , This thread is almost a full duplicate of your adjacent thread: How to preserve the animation of a sphere. The answer is provided there. Cheers, Ilia
  • 0 Votes
    8 Posts
    2k Views
    F
    Hi @i_mazlov , Thank you for confirming my solution. Best regards, Tomasz
  • Boundary Edges to Spline in Python

    Cinema 4D SDK s26 python 2024 2023
    3
    0 Votes
    3 Posts
    976 Views
    F
    Dear Ilia, I greatly appreciate your assistance; it was precisely what I needed. Thank you for your guidance and I wish you a wonderful day. Best regards, Tomasz
  • How to Get cloner data in C++

    Cinema 4D SDK s26 c++ windows
    2
    1
    0 Votes
    2 Posts
    583 Views
    M
    Hi you can find in read_modata_color_r16.py a Python example doing exactly that. To retrieve the position instead of retrieving the MODATA_COLOR your need to retrieve MODATA_MATRIX. There is no difference for doing it in C++. Cheers, Maxime.
  • 0 Votes
    3 Posts
    800 Views
    H
    Hi @ferdinand, thanks for taking the time to clarify that Okay, I see. So one can view this "behavoiur" as intended since it is the result of the math behind how the circle spline is calculated? Still I wonder why the circle spline primitive with 1 intermediate point, i.e. with four control points doesn't appear like the n-side spline with 8 sides? Wouldn't it be "cleaner" that way? Regading the true values - I am well aware that the repr of c4d.Vector rounds in the GUI. It was only that those values were quite off compared to the values of the points of the n-side spline. That's why my initial question arose. Interesting and good to know that rotating points via sine and cosine is slow and no good option. Thank you for that insight. May I ask why that is? I mean why is c4d.utils.MatrixRotZ better? What does that function do differently in terms of performance? In the end it has to do the math as well, does it not? Sorry for constantly asking all this stupid questions. It is only that I want to understand these things thoroughly. Thanks for your time, Sebastian