Navigation

    • Register
    • Login
        No matches found
    • Search
    1. Home
    2. ferdinand
    3. Posts
    • Profile
    • More
      • Following
      • Followers
      • Topics
      • Posts
      • Best
      • Groups

    Posts made by ferdinand

    RE: Osubdivisionsurface ?

    Hey @indexofrefraction,

    Well, that is because Mograph is one of the areas where the type symbols have not been exposed. So, to be clear: There is no cloner object symbol.

    But now that you brought this up again, I just realized that we (the SDK Team) never talked about fixing this and I do not see (fully) why. I have a suspicion why this might have been postponed or refuted in the past, but I will bring it up again. In the end we just must add a few defines to ge_prepass.h and then users do not have to jump through these hoops anymore. It will be difficult to catch all the missing symbols, but we could at least start with the ones we know. I will update the thread with the outcome of our discussion.

    Edit: So, we talked about this, and came to the obvious conclusion, that we should do what we should have done a long time ago, and fix this. I cannot give an ETA, but I will try to give it priority as I think this is a problem which impacts many users.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Osubdivisionsurface ?

    Hello @indexofrefraction,

    thank you for reaching out to us. While it is sometimes true that we do this and I recently hinted at why this this happening, it is not true in this case.

    The symbol for SDS is c4d.Osds. You can find a list of all object symbols here. The bottom line is here: Programmers love acronyms 😉

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    S26 SP1 SDK and API Documentation Release

    Dear Cinema 4D Community,

    On June the 29th, 2022, Maxon Computer released Cinema 4D S26 SP1. Alongside this release, the updated Python and C++ SDK and documentation have been released, reflecting the API changes for S26 SP1. Please visit the release announcement for an overview of the changes in SP1 in the application itself.

    Happy rendering and coding,
    the Maxon SDK Team

    posted in Maxon Announcements •
    RE: Questions about messages (original: 关于消息的问题)

    Hello @未凡fh,

    Thank you for reaching out to us. Support on this forum is only being provided in the English language, as this serves our community best, and because we also simply do not speak all languages of the world. I think the language you posted in is Simplified Chinese, please excuse me when I wrong, but that is what Google said. No-one on the SDK Team speaks that language. And even if we would, we would refuse to answer in this language as it would lock out large parts of the community. As we do for requests which come in German, French, or Spanish (languages we do speak).

    Running your request through Google Translate, I got this:

    Let me ask: How do I detect an object moving and send a message when an object moves?

    Please confirm that this is your request and that we can communicate in English.

    Thank you for your understanding,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: "Cubic Bias" interpolation...

    Hello @noseman,

    well, the problem is that we do not really have a good place for it at the moment (a subject we could list it under). And it is also technically out of our collective domain, i.e., the things we consider putting into our documentation. We only collect things which are directly related to out APIs and this is a generic computer graphics example. Note that you found your example code in the context of the GeUserArea example, so this was only code which sort of was provided "en passant" while explaining something entirely different: How the type GeUserArea works. Which is rather something we like to reduce than increase: Overloading examples with random complexity.

    I made a note that there is some interest in having an example for spline interpolation, e.g., the provided one. I cannot promise more for now.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: How can I change the path of a Redshift shader with Python?

    Hello @conductor,

    Thank you for reaching out to us. I am glad that you could solve your problem yourself. Please do not hesitate to ask further questions you might have.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: I have a problem converting a Osweep object to editable.

    Hello @fss,

    Thank you for reaching out to us. I am struggling a bit with what you mean by "a sinusoid polygon editable". Do you mean that your object is being unintentionally deformed when being made editable, as for example into a wavy, sine-like form? This would indeed be very odd, and we cannot tell you here more without the exact example code and the scene/object you are running it on. Screenshots would also be welcome in this case. For such odd cases we will also need the operating system you are running the code on, as we will try to replicate your problems.

    This all seems very unlikely though, and I would expect some form of user-error to be the cause. But with the provided information, it is impossible to say what you are either doing or understanding wrong.

    PS: Although CallCommand is technically fine, it comes with disadvantages. Which is why we always do recommend using the more complex command functions, c4d.utils.SendModelingCommand in this case. I just recently provided a small selection of modelling related Python examples, including SMC. Find them here. The relevant command would be c4d.MCOMMAND_MAKEEDITABLE in your case.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: SaveWorldPreferences() in Python

    Hello @a_block,

    We have discussed this, and we will add this function at some point, but I cannot give you an ETA. For now, you will have to live with either restarting Cinema 4D or with taking the risk of your data vanishing when Cinema 4D crashes.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: SaveWorldPreferences() in Python

    Hello @a_block,

    Thank you for reaching out to us. We have to discuss if we want to do this, but in the mean-time I would propose c4d.RestartMe() should this not be almost as good? You should be on the main thread when the license stuff is happening, so rebooting Cinema 4D should be no problem.

    Sure, it will take a bit longer, but you can print "Be-ba-boop: Rebooting Cinema 4D ..." to make it cool 😉

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: "Cubic Bias" interpolation...

    Hello @noseman,

    Thank you for reaching out to us. As declared in our Forum Guidelines, we do not share code of Cinema 4D publicly. So, we cannot disclose what the Cubic Bias interpolation is doing exactly. This also applies to the interpolation modes you quoted, while they are technically close, they are not exactly what the gradient type is doing internally.

    Cubic means in this context cubic spline interpolation. We cannot disclose which ones are being used exactly, but Cubic and Cubic Bias use entirely different ones. For Cubic Bias two of the four control points of the polynomial are being constructed with the help of the name-giving bias value. Find an approximation in Python of the what the mode does at the end of the posting.

    Cheers,
    Ferdinand

    The result:
    fbd17333-4081-4497-9f76-1e74ec4df652-image.png

    The code:

    """Demonstrates a Cubic Spline interpolation where the two tangent points are being placed
    dependent on a bias value.
    """
    
    import c4d
    
    def GetCubicInterpolation(a: c4d.Vector, b: c4d.Vector, bias: float, t: float) -> c4d.Vector:
        """Returns a Cubic Spline interpolation between #a and #b at #t where the tangent points #t0
        and #t1 are placed with the help of a #bias value.
    
        All values must lie in the interval [0, 1].
    
        Args:
            a: The first control point.
            b: The second control point.
            bias: The bias value which determines the placement of the two tangent points.
            t: The offset at which to interpolate the spline.
    
        Returns:
            The interpolated point.
        """
        # These values are relatively close to what the gradient data type does internally.
        t0: c4d.Vector = c4d.utils.MixVec(a, b, c4d.utils.MixNum(0., 1. - bias, 0.75))
        t1: c4d.Vector = c4d.utils.MixVec(a, b, c4d.utils.MixNum(1. - bias, 1., 0.25))
    
        # This is not, while CalcSplineV also provides an interpolation with a cubic polynomial, it is 
        # not the one used by the gradient data type. But it will be visually close.
        return c4d.utils.CalcSplineV(t, [a, t0, t1, b])
    
    def main() -> None:
        """
        """
        # Init a canvas we are going to draw into.
        canvas: c4d.bitmaps.GeClipMap = c4d.bitmaps.GeClipMap()
        canvas.Init(1000, 302, 32)
        
        # The two colors/knots we are going to interpolate.
        colorA: c4d.Vector = c4d.Vector(1, 0, 0)
        colorB: c4d.Vector = c4d.Vector(0, 0, 1)
    
        # We are going to draw three vertically separated sections with a bias of .25, .5, and .75
        canvas.BeginDraw()
        for y0, y1, bias in ((0, 100, 0.25), (101, 201, 0.5), (202, 302, 0.75)):
            # Draw a section.
            for i in range(1000):
                # Get the interpolation at #t in [0, 1]
                col: c4d.Vector = GetCubicInterpolation(colorA, colorB, bias, float(i) * 0.001)
    
                # The function returns a value in R[0, 1] but GeClipMap operates in Z[0, 255]
                canvas.SetColor(int(col.x * 255), int(col.y * 255), int(col.z * 255), 0)
                canvas.Line(i, y0, i, y1)
    
            # Draw the bias marker.
            canvas.SetColor(25, 25, 25, 0)
            canvas.FillRect(int(1000 * bias - 2), y0 + 10, int(1000 * bias + 2), y1 - 10)
        canvas.EndDraw()
    
        # Display the drawing.
        bmp = canvas.GetBitmap()
        c4d.bitmaps.ShowBitmap(bmp)
    
    
    if __name__ == '__main__':
        main()
    
    posted in Cinema 4D SDK •
    RE: How to Manually Build the Cache for a Vector Import Instance?

    Hello @interfaceguy,

    Thank you for reaching out to us. Please open a new topic for new questions as lined out in our Forum Guidelines: Rules of Conduct. This is not a follow up question to your initial question. I have forked your question, for future cases we might refuse to answer your questions when this rule is not followed.

    About your question, the following statement is not true:

    calling the reload button and c4d.EventAdd() which makes the hierarchy containing the spline visible in the GUI, C4D does not recognize that the vector_import object now has children.

    Invoking the button and EventAdd will not build the caches for your object. It is only after your script has ended that Cinema 4D will do that automatically for you. Which is probably why you came to that conclusion, but when you call GetDown() on your vector_import, it still has no children. You must execute the passes on an object/document when you want to access/build the caches of it immediately after a parameter change or when the object has been newly instantiated.

    Find an example at the end of this posting.

    Cheers,
    Ferdinand

    The result:

    ecd4127d-5788-4245-9b5b-22298018b8aa-image.png

    The code:

    """Simple example for evaluating the cache of a generator.
    """
    
    import c4d
    import typing
    
    doc: c4d.documents.BaseDocument  # The active document
    op: typing.Optional[c4d.BaseObject]  # The active object, None if unselected
    
    def AssertType(item: any, t: typing.Union[typing.Type, tuple[typing.Type]], lbl: str) -> any:
        """Asserts #item to be of type #t.
    
        When the assertion succeeds, #item is passed through, otherwise a type error with #lbl referring
        to #item is being raised.
        """
        if not isinstance(item, t):
            raise TypeError(f"Expected item of type '{t}' for '{lbl}'. Received: {type(item)}")
        
        return item
    
    def main() -> None:
        """
        """
        # Instantiate the vector import object.
        node: c4d.BaseObject = AssertType(c4d.BaseObject(1057899), c4d.BaseObject, "node")
    
        # Due to a bug in the vector import object, Cinema 4D will crash when attempting to write the
        # ART_FILE parameter without the node being attached to a document. Normally we would set here
        # this parameter, as this would make more sense, now it has been moved to line 44 for this
        # reason.
    
        # The node #node has now no caches, i.e., the generator has not run yet and no amount of button
        # clicking and EventAdd() will change that. We must build the caches by executing the passes
        # on #node. For that #node must be part of a document. We could insert #node into the active 
        # document and execute the passes there, but this has two drawbacks:
        #
        #   1. This cannot be done in any non-main thread context, as it might then crash Cinema 4D.
        #   2. This can also be slow, as it will also revaluate everything else which is in the active
        #      document.
        #
        # So, we are going to do this in a temporary document.
    
        BaseDocument = c4d.documents.BaseDocument
        temp: BaseDocument = AssertType(BaseDocument(), BaseDocument, "temp")
        temp.InsertObject(node)
    
        # Set the parameter for the file.
        node[c4d.ART_FILE] = "E:\myFile.svg"
    
        # Build all caches in the document.
        temp.ExecutePasses(
            bt=None, animation=False, expressions=False, caches=True, flags=c4d.BUILDFLAGS_NONE)
    
        # Node now has a cache, and also its hierarchy would be populated if we had set 
        # ART_SHOW_HIERARCHY to True. But we can also just reach into the cache now when we want
        # to access the output of the generator. We can also remove #node now from the temporary
        # document, as the cache is bound to the node itself. We also must do this if we want to
        # insert #node into another document (or return it in a GVO method), as a node can only
        # be part of one document.
        node.Remove()
    
        # Get the cache.
        cache: c4d.BaseObject = AssertType(node.GetCache(), c4d.BaseObject, "cache")
        print (f"{cache = }")
    
        # Some like this should never be done, as every step in this chained function call can return
        # a null-pointer, i.e., None in Python.
        # spline = vector_import.GetDown().GetDown().GetDownLast()
    
        # Get the sort of thing you wanted to get. It is in general not safe to navigate hierarchies in
        # this predetermined manner but this will at least not stop your code when the assumption
        # fails inevitably.
        if cache.GetDown():
            cache = cache.GetDown()
            if cache.GetDown():
                cache = cache.GetDown()
                if cache.GetDownLast():
                    cache = cache.GetDownLast()
    
        # Do stuff with this part of the cache.
        if isinstance(cache, c4d.PointObject):
            for p in cache.GetAllPoints():
                print (p)
    
        # When we wanted to insert this part of the cache into the active (or any other) document, we
        # must clone it first.
        clone: c4d.BaseObject = AssertType(cache.GetClone(c4d.COPYFLAGS_0), c4d.BaseObject, "clone")
    
        # Insert both the generator and a copy of a part of its cache.
        doc.InsertObject(node)
        doc.InsertObject(clone)
        
        c4d.EventAdd()
    
    if __name__ == '__main__':
        main()
    
    posted in Cinema 4D SDK •
    RE: Implementing a watermark on render

    Hello @dunhou,

    Thank you for reaching out to us. Please read our Forum Guidelines regarding the scope of topics, your question does not seem to be a valid follow-up question to me, and therefore should be its own topic.

    I also struggle to see how this is an API question, as you seem to report an end-user problem which has nothing to do with one of our APIs. Please contact user support via the Support Center for such questions.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Right-mouse click on dialog tab

    Hello @wickedp,

    Thank you for reaching out to us. While I do understand your question, an important detail remains ambiguous for me; what do you mean with 'a group of dialog tabs'?

    1. A tab group opened with GeDialog::TabGroupBegin or the equivalent markup,
    2. or the tab header of a docked dialog?

    For option one you will be able to retrieve command messages in GeDialog::Command when an action has occurred for the tab gadget, this should also include a right click on one of its tabs. If this fails, you could try to resort to BFM_GETCURSORINFO, but I would first try the first option, as it seems likely that it will work.

    If you were talking about the second case, this is unfortunately not possible, at least I would say so. The only way to find out for sure, would be to listen to the message stream of your dialog if a message is being sent to one of the methods when the user right clicks on the dialog tab. But even if it were, you could not really open a menu, as there is already the context menu of Cinema 4D.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Insert a shader into a shader hierarchy

    Hey @indexofrefraction,

    well, this is not surprising, as the LayerShader is not in a hierarchical relation with the GeListHead but a branch relation. You must call GetListHead on a GeListNode to retrieve its head, i.e., the branch which is containing the node.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: How to Set Node Spcace use python?

    Hey @dunhou,

    no need to be sorry 🙂 And on the subject: Well, you said it yourself, usually the active render engine dictates the node space the editor is in.

    My example above provides however all three options: setting the render engine, setting the node editor mode, and finally setting the active node space. I am not sure if you are aware and have a specific use case in mind, but the active node space is rarely something you want to set actively yourself.

    You can create node materials for different nodes spaces than the active one just fine. If you really want to change the active node space, you will find the code in the example above.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: How to change polygon axis

    Hey @neekoe,

    I would recommend having a look at the code example I have posted here, it will show you how your "axis can become vertical like the model". You must for that transform the vertices of the object, not that object itself (or both). SetModelingAxis does not work like you think it does and "axis" are more or less a virtual concept, if you want to the origin/coordinate system to change in relation to the geometry it governs, you must transform the points that make up that geometry and then the object by the invers of that. E.g., when you move all vertices of an object by +100 units on the x-axis in the coordinate system of the object, and then move the object in its own coordinate system by -100 units on the x axis, it will appear as if the "axis" of the object has been moved, while the points have been kept in place.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: How to change polygon axis

    Hello @neekoe,

    Thank you for reaching out to us.

    There is no need to be sorry. I also just realized something, when you say "changing the polygon axis", and with your image you show, you probably want to transform the vertices of a point object and not set the matrix, i.e., transform, of the object itself. So, you want the geometry to be reoriented, but the "axis" of the object should remain the same, right? I have provided a code example and brief screencast for

    • constructing a frame from a vector in C++,
    • setting this frame to be the frame, i.e., orientation, of a BaseObject itself,
    • and transforming all vertices of the object if it is a PointObject

    at the end of this posting.

    Cheers,
    Ferdinand

    The result:
    alt text

    The code:

    #include "c4d_baseobject.h"
    #include "c4d_basedocument.h"
    
    #include "maxon/apibase.h"
    #include "maxon/lib_math.h"
    
    /// Gets the first two objects in the scene and constructs a frame for them to either reorient the
    /// first object itself or its vertices.
    static maxon::Result<void> PC14100(BaseDocument* doc)
    {
      // Get the first and second object in the document.
      BaseObject* const objectA = doc->GetFirstObject();
      if (objectA == nullptr)
        return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not find input objects."_s);
    
      BaseObject* const objectB = objectA->GetNext();
      if (objectB == nullptr)
        return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not find input objects."_s);
    
      // Get their global matrices and compute a normalized delta vector for their offsets from A to B.
      Matrix mgA = objectA->GetMg();
      const Matrix mgB = objectB->GetMg();
      const Vector delta = mgB.off - mgA.off;
    
      // #delta is at this point a vector pointing from the origin of A towards the origin of B. We are
      // now going to construct a frame for this vector and an up-vector, where the delta vector will
      // become the z/k/v3 axis.
    
      // The unit vector of #delta will be the z-axis of our frame.
      const maxon::Vector z = delta.GetNormalized(); // This the frame component z/k/v3
    
      // Choose an up-vector that is not (anti-)parallel to that z-component.
      const maxon::Float64 epsilon = 1E-5;
      const Bool isParallel = 1. - maxon::Abs(Dot(Vector(0., 1., 0.), z)) > epsilon;
      const Vector up = isParallel ? Vector(0, 1, epsilon).GetNormalized() : Vector(0, 1, 0);
    
      // Compute the remaining components with the cross product.
      const Vector x = Cross(up, z).GetNormalized(); // This the frame component x/i/v1
      const Vector y = Cross(z, x).GetNormalized(); // This the frame component y/j/v2
    
      // We could optionally scale here these unit vectors, but we are not going to do this, as we
      // want to maintain a uniform scale of 1 for the object.
    
      // Construct the new transform for the object A with the frame components we did compute. We
      // could give the object also a different offset (i.e., 'position'), but in this case we just
      // want to reorient the object A but maintain its position.
      Matrix newMg(mgA.off, x, y, z);
    
      // If #objectA is a BaseObject only, i.e., an object that cannot be edited on a vertex level,
      // then just set the transform as the global transform of the object. The z-axis of #objectA
      // will point towards #objectB after this.
      if (!objectA->IsInstanceOf(Opoint))
      {
        objectA->SetMg(newMg);
      }
      // ... otherwise we carry out the transform on a vertex level, i.e., we transform each vertex as
      // if the object containing these vertices would have been set to #newMg. The result will be
      // a geometry that is oriented as in the previous condition, but the global transform, i.e., 
      // the "axis" of the object will stay in place.
      else
      {
        // Cast the BaseObject to a point object so that we can manipulate its vertices and get its
        // the writable point array that stores the vertex positions.
        PointObject* pointObject = static_cast<PointObject*>(objectA);
        Vector* points = pointObject->GetPointW();
    
        // We could just multiply each vertex position by #newMg and this would work if #objectA has
        // the identity matrix as its frame, i.e., "has the orientation of the world axis of (0, 0, 0)",
        // but not in other cases. For these cases we first have to compute the delta between the old
        // transform of objectA and the new value. We are also going to zero out the translations of 
        // both transforms, as we do not want to translate the points, but only rotate them.
    
        mgA.off = Vector();
        newMg.off = Vector();
    
        // Multiply the new transform by the inverse of the old transform to get the "difference" 
        // between both transforms.
        const Matrix deltaMg = ~mgA * newMg;
    
        for (int i = 0; i < pointObject->GetPointCount(); i++)
        {
          points[i] = deltaMg * points[i];
        }
      }
    
      // Push an update event to Cinema 4D so that its GUI will update after this function has been
      // exited when this code runs on the main thread (the only place where we are allowed to do this).
      if (GeIsMainThreadAndNoDrawThread())
        EventAdd();
    
      return maxon::OK;
    }
    
    
    posted in Cinema 4D SDK •
    RE: How to Show Nodes API Nodes in the Node Editor?

    Hello @dunhou,

    Thank you for reaching out to us. Could you please clarify what you mean by "expose an inport without an input node"? The command "Show in Node Editor" will reveal the node on which this command has been invoked. When a port has no input, i.e., there is no node, then there is nothing to reveal.

    Maybe I am not aware of a functionality of Cinema 4D here or misunderstanding you?

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: How to Set Node Spcace use python?

    Hello @Dunhou,

    Thank you for reaching out to us. Please follow the forum rule of creating multiple topics for multiple questions and placing all information about the initial question in a topic into a single posting. I have split your topic because of that. Since this is getting out of hand lately and I have already pointed this out before in your case, I must point out that we might enforce this in future more strictly and will not answer questions of topics not following these rules. See Forum Guidelines: Rules of Conduct for details.

    The Nodes API allows to attach multiple node spaces to "things" in the classic API. For material nodes this means a material can have multiple node spaces and graphs attached to it. Which of the attached node spaces is active then depends on the active render engine; unless the user forcibly changes the active node space. At the end of this posting, I have provided an example script which goes over some details.

    Cheers,
    Ferdinand

    The result:
    nodematerial.gif
    The script:

    """Showcases how to add or retrieve node graphs for materials.
    
    The Nodes API allows to attach multiple node spaces to "things" in the classic API. For material 
    nodes this means a material can have multiple node spaces and graphs attached to it. Which of the 
    attached node spaces is active then depends  on the active render engine; unless the user forcibly 
    changes the active node space.
    """
    
    from typing import Optional
    import typing
    import c4d
    import maxon
    
    doc: c4d.documents.BaseDocument  # The active document
    op: Optional[c4d.BaseObject]  # The active object, None if unselected
    
    # The ID of the Redshift render engine.
    ID_REDSHIFT_ENGINE: int = 1036219
    # The ID of the Redshift node space.
    ID_REDSHIFT_NODESPACE: int = maxon.Id("com.redshift3d.redshift4c4d.class.nodespace")
    # The command id of setting the Node Editor to material mode.
    ID_NODE_EDITOR_MODE_MATERIAL: int = 465002360
    
    # For the IDs for the Arnold render engine you must ask Autodesk, as we cannot provide technical 
    # support for third party products.
    
    def AssertType(item: any, t: typing.Union[typing.Type, tuple[typing.Type]], lbl: str) -> any:
        """Asserts #item to be of type #t.
    
        When the assertion succeeds, #item is passed through, otherwise a type error with #lbl referring
        to #item is being raised.
        """
        if not isinstance(item, t):
            raise TypeError(f"Expected item of type '{t}' for '{lbl}'. Received: {type(item)}")
        
        return item
    
    
    def main() -> None:
        """Runs the example.
        """
        # One can make changes to node graphs independently of the active node space of the node editor
        # or the active render engine, but in order to see the changes one made, either the active node
        # space or render engine must be set (setting the render engine will also change the active node
        # space).
    
        # Set the node editor to material space mode.
        if not c4d.IsCommandChecked(ID_NODE_EDITOR_MODE_MATERIAL):
            c4d.CallCommand(ID_NODE_EDITOR_MODE_MATERIAL)
    
        # When one wants the materials to be correctly previewed, this will however not be enough, and
        # then the active render engine must be set. Note that doing what is done here, just setting
        # the render engine ID, will also not be enough when one wanted to immediately render with these
        # render settings in the scope of this script. As in this state the RedShift VideoPostData is 
        # still missing in the RenderData. The example relies on the fact that Cinema 4D will add this 
        # video post node once the script has finished, so there is no need to handle that manually in 
        # this case.
        rdata: c4d.documents.RenderData = AssertType(
            doc.GetActiveRenderData(), c4d.documents.RenderData, "rdata")
        rdata[c4d.RDATA_RENDERENGINE] = ID_REDSHIFT_ENGINE
    
        # If you really wanted to set the active node space manually, you could do it like this for
        # Redshift. But I neither see the point in doing it nor would I recommend it, as users might be
        # unaware of your code having forcibly set a different node space.
        c4d.CallCommand(72000, 5)
    
        # Iterate over all materials in the active document.
        for material in doc.GetMaterials():
            # Get the node material for the BaseMaterial #material with GetNodeMaterialReference(). This
            # reference will exist even when the material is not yet a node material in the Material
            # manger.
            nodeMaterial: c4d.NodeMaterial = AssertType(
                material.GetNodeMaterialReference(), c4d.NodeMaterial, "nodeMaterial")
    
            # The variable we are going to store (the reference to) the Redshift graph of a material.
            graph: maxon.GraphModelInterface = None
    
            # Check if the node material has already a graph for the node space we are interested in,
            # in this case Redshift. If not, add a graph for that space, otherwise retrieve it.
            if not nodeMaterial.HasSpace(ID_REDSHIFT_NODESPACE):
                graph = AssertType(
                    nodeMaterial.AddGraph(ID_REDSHIFT_NODESPACE), maxon.GraphModelInterface, "graph")
            else:
                graph = AssertType(
                    nodeMaterial.GetGraph(ID_REDSHIFT_NODESPACE), maxon.GraphModelInterface, "graph")
    
            # There is also a different way to retrieve the graph of a node space when one already knows
            # for sure that the graph does exist. This is done over the reference to the NimbusBaseRef 
            # of the BaseList2D (i.e., the classic API thing) that is the "connecting piece" to the
            # graph. In case of Material Nodes it is the material, in case of Scene Nodes it is the
            # the Scene Nodes scene hook.
            nimbusRef: maxon.NimbusBaseInterface = AssertType(
                material.GetNimbusRef(ID_REDSHIFT_NODESPACE), maxon.NimbusBaseInterface, "nimbusRef")
            graph = AssertType(nimbusRef.GetGraph(), maxon.GraphModelInterface, "graph")
    
            # The graph can now be modified, we just add a maxon noise node in this case. Graph 
            # modifications must be wrapped into a transaction and this transaction must be committed 
            # for the changes to the graph to be actually carried out.
    
            # Add a maxon noise node to the graph.
            with maxon.GraphTransaction() as transaction:
                transaction: maxon.GraphTransaction
                graph.AddChild(maxon.Id(), maxon.Id("com.redshift3d.redshift4c4d.nodes.core.maxonnoise"), 
                           maxon.DataDictionary())
                transaction.Commit()
    
        # Push an update event to Cinema 4D.
        c4d.EventAdd()
    
    if __name__ == '__main__':
        main()
    
    posted in Cinema 4D SDK •
    RE: R21 and Python Version

    Hey @brucek5,

    Thank you for reaching out to us. The Python interpreter shipped with Cinema 4D is not identical to CPython and it is not intended to be replaced by the user. Technically speaking you could try it, but this would be out of scope of support and will likely also fail. We cannot give instructions on how to do it, you would have to hack your Cinema 4D yourself.

    It also depends on how much you want to change the Python version. Given that you are on R21, i.e., Python 2.7, and likely want to jump to a Python 3.X version, the answer will change from "practically impossible" to "categorically impossible".

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •