Navigation

    • Register
    • Login
    • Search
    1. Home
    2. HerrMay
    3. Posts
    • Profile
    • More
      • Following
      • Followers
      • Topics
      • Posts
      • Best
      • Groups

    Posts made by HerrMay

    RE: Script: Connect + Delete all subdivision surface objects

    Hi @derekheisler,

    there are multiple reasons why your script isn't catching all of you SDS objects. Number one is that you only take the top level of objects into account. doc.GetObjects() does not account for nested objects that live as children under some other objects. So one solution here is to write a custom function that traverses the complete document i.e. not only the top level objects but every child, sibling, grandchild and so on.

    Number two is in Cinema there is no symbol c4d.Osubdiv. The correct symbol is c4d.Osds. ChatGPT knows and can do a lot, but not everything. 😉

    One additional suggestion. While it can be comfortable to simply use c4d.CallCommand, for proper undo handling you should avoid it and instead use e.g. node.Remove() if you want to delete some object.

    Having said all of that, find below a script that finds all SDS objects gets a copy and of them, bakes them down and inserts the new polygon objects into your document.

    I left out the part to set back the objects position back for you to find out.

    Cheers,
    Sebastian

    import c4d
    
    def get_next(node):
        """Return the next node from a tree-like hierarchy."""
        
        if node.GetDown():
            return node.GetDown()
        
        while not node.GetNext() and node.GetUp():
            node = node.GetUp()
            
        return node.GetNext()
    
    def iter_tree(node):
        """Iterate a tree-like hierarchy yielding every object starting at *node*."""
        
        while node:
            yield node
            node = get_next(node)
            
    def iter_sds_objects(doc):
        """Iterate a tree-like hierarchy yielding every +
        SDS object starting at the first object in the document.
        """
        
        is_sds = lambda node: node.CheckType(c4d.Osds)
        node = doc.GetFirstObject()
        
        for obj in filter(is_sds, iter_tree(node)):
            yield obj
    
    
    def join_objects(op, doc):
        """Join a hierarchy of objects and return them as a single polygon."""
        
        settings = c4d.BaseContainer()
    
        res = c4d.utils.SendModelingCommand(
            command=c4d.MCOMMAND_JOIN,
            list=[op],
            mode=c4d.MODELINGCOMMANDMODE_ALL,
            bc=settings,
            doc=doc
        )
        
        if not isinstance(res, list) or not res:
            return
        
        res = res[0]
        return res.GetClone()
    
    
    # Main function
    def main():
        
        doc.StartUndo()
        
        null = c4d.BaseObject(c4d.Onull)
        
        tempdoc = c4d.documents.BaseDocument()
        tempdoc.InsertObject(null)
        
        for sds in iter_sds_objects(doc):
            clone = sds.GetClone()
            clone.InsertUnderLast(null)
            
        joined = join_objects(null, tempdoc)
        
        doc.InsertObject(joined)
        doc.AddUndo(c4d.UNDOTYPE_NEW, joined)
        
        doc.EndUndo()
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    
    posted in Cinema 4D SDK •
    RE: How can I add an offset to the global matrix of an object that is rotated?

    Hello @ferdinand,

    sorry for being a bit nebulous. I wasn't sure how I should explain what I mean in a way that would illustrate it a bit better. 😄

    Actually I hab a look at the matrix manual. But as someone who had no matrix calculation lessons in school, it's a bit overwhelming at first glance. 😄 😄 😄

    Yes, you guessed correctly. That is exactly what I wanted. Thank you once again. 🤗

    Is the multiplication of a matrix with a vector (mg.off = mg * p) essentially the same what mg * c4d.utils.MatrixMove(vec) does?

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Is it possible to intercept rendering to modify the document that is about to render?

    Hi @ferdinand,

    hmm... interesting. So while this is not the intentional way of using MSG_MULTI_RENDERNOTIFICATION it is good to know that it IS possible to act on the document used for rendering.

    I wonder if one could additionally check for data["external"] to modify the render document only? At least in the code example below it works to make the sphere editable and use that one for rendering.

    Cheers,
    Sebastian

    """Demonstrates a Python Programming tag which inserts sphere objects into the documents of upcoming
    renderings.
    
    WHAT IS SHOWN IS NOT AN INTENDED USE OF MSG_MULTI_RENDERNOTIFICATION. The passed document is meant
    to be treated as read only for public API users. Modify at your own risk.
    """
    import c4d
    
    doc: c4d.documents.BaseDocument # The document evaluating this tag
    
    def make_editable(doc, node):
    
        settings = c4d.BaseContainer()
    
        res = c4d.utils.SendModelingCommand(
            command=c4d.MCOMMAND_MAKEEDITABLE,
            list=[node],
            mode=c4d.MODELINGCOMMANDMODE_ALL,
            bc=settings,
            doc=doc
        )
    
        if not res:
            return
    
        obj = res[0]
        return obj.GetClone()
    
    def main() -> None:
        pass
    
    def message(mid: int, data: object) -> bool:
        """Called by Cinema 4D to convey messages to the tag.
        """
        # There is an upcoming rendering.
        if mid == c4d.MSG_MULTI_RENDERNOTIFICATION and data["start"] and data["external"]:
            # Get the render document.
            doc: c4d.documents.BaseDocument = data["doc"]
    
            # Insert a sphere.
            sphere: c4d.BaseObject = c4d.BaseObject(c4d.Osphere)
            if not sphere:
                raise MemoryError()
    
            obj = make_editable(doc, sphere)
            doc.InsertObject(obj)
    
        return True
    
    posted in Cinema 4D SDK •
    How can I add an offset to the global matrix of an object that is rotated?

    Hi guys,

    as the title already suggests I wonder how I can add an arbitrary offset value to the global matrix of an object that is rotated. In that way that the object moves along that angled position plus the given offset?

    I tried adding the offset to the z axis of the 'off' component of the objects global matrix. But without any luck. The object is then of course moving along the world axis by the amount of the offset.

    I hope someone can help me here with these dreadful matrices. 😄

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Python Tag how to hide a mogragh object

    Hi @Dunhou and hi @Manuel,

    so I found that it is actually possible to modify the flags of the clones via a python tag.

    Try the following code on a matrix object. You'll see that the clones will be hidden.

    It still seems to be a bug of some sort as you can observe a refresh issue when you scrub the timeline. And of course, if it works on matrix objects only that's not really an option.

    Setting a cloner to object mode and cloning on that matrix will also not really work as all clones will still be visible at render time. I tried changing the execution priority of the python tag as well but no luck.

    Let's hope @Manuel will come back with good news on that one. Although I'm afraid I already know the answer. 😄

    Cheers,
    Sebastian

    from typing import Optional
    import c4d
    
    doc: c4d.documents.BaseDocument # The document evaluating this tag
    op: c4d.BaseTag # The Python scripting tag
    flags: int # c4d.EXECUTIONFLAGS
    priority: int # c4d.EXECUTIONPRIORITY
    tp: Optional[c4d.modules.thinkingparticles.TP_MasterSystem] # Particle system
    thread: Optional[c4d.threading.BaseThread] # The thread executing this tag
    
    def main() -> None:
        # Called when the tag is executed. It can be called multiple time per frame. Similar to TagData.Execute.
        # Write your code here
    
        obj = op.GetMain()
    
        moData = c4d.modules.mograph.GeGetMoData(obj)
    
        if moData is None:
            return False
    
        farr = moData.GetArray(c4d.MODATA_FLAGS)
        farr = [1 if i < 5 else 0 for i, num in enumerate(farr)]
        moData.SetArray(c4d.MODATA_FLAGS, farr, False)
    
    """
    def message(id: int, data: object) -> bool:
        # Called when the tag receives messages. Similar to TagData.Message.
        # Write your code here
        return super().Message(id, data)
    
    def draw(bd: c4d.BaseDraw) -> bool:
        # Called to display some visual element in the viewport. Similar to TagData.Draw.
        # Write your code here
        return True
    """
    
    posted in Cinema 4D SDK •
    Is it possible to intercept rendering to modify the document that is about to render?

    Hi guys,

    while I know how to retrieve information about when a render notification is invoked - thanks to @m_adam who provided an example in this thread - I wonder if it is possible to intercept the render pipline to modify the document that is about to render.

    The basic idea here would be something like this:

    • Receive the message when rendering starts
    • Run e.g. "Make Editable" on e.g. Subdivision Surfaces BUT for the render document only. I dont't want the SDS to be made editable in the working document.

    I know that c4d clones the document for rendering. Maybe there is a way to get hold of that document and run aforementioned command?

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Python Tag how to hide a mogragh object

    Hi @Dunhou,

    for the time being you can modify the clones from a python tag via the help of a MoSelection tag. 😉

    Cheers,
    Sebastian

    from typing import Optional
    import c4d
    
    doc: c4d.documents.BaseDocument # The document evaluating this tag
    op: c4d.BaseTag # The Python scripting tag
    flags: int # c4d.EXECUTIONFLAGS
    priority: int # c4d.EXECUTIONPRIORITY
    tp: Optional[c4d.modules.thinkingparticles.TP_MasterSystem] # Particle system
    thread: Optional[c4d.threading.BaseThread] # The thread executing this tag
    
    def main() -> None:
        """Example showing a workaround on how to modify clones via a python tag.
        To get this working we need a MoSelection tag as a man in the middle to transfer modified data
        from this python tag to a e.g. cloner object.
        """
    
        moselection = op[c4d.ID_USERDATA,1] # Userdata link filed where you are supposed to drop your MoSelection tag.
        obj = op.GetObject()
    
        if not obj or not moselection: # Bail if there is no host object or no MoSelection tag.
            return
    
        baseselect = c4d.modules.mograph.GeGetMoDataSelection(moselection) # Retrieve a c4d.BaseSelect from the MoSelection tag.
        moData = c4d.modules.mograph.GeGetMoData(obj)
    
        if moData is None:
            return False
    
        farr = moData.GetArray(c4d.MODATA_FLAGS) # Read the MoData array you want to modify.
        farr[4] &= ~ (c4d.MOGENFLAG_CLONE_ON) # Modify the array to your liking.
    
        baseselect.SetAll(states=farr) # Set the c4d.BaseSelect from the modified array data.
        c4d.modules.mograph.GeSetMoDataSelection(op=moselection, selection=baseselect) # Write back the c4d.BaseSelect to your MoSelection tag.
    
    """
    def message(id: int, data: object) -> bool:
        # Called when the tag receives messages. Similar to TagData.Message.
        # Write your code here
        return super().Message(id, data)
    
    def draw(bd: c4d.BaseDraw) -> bool:
        # Called to display some visual element in the viewport. Similar to TagData.Draw.
        # Write your code here
        return True
    """
    
    posted in Cinema 4D SDK •
    RE: How to find out if document is rendering from a python tag

    Hi @m_adam,

    thanks for the explanation and your example code. That will absolutely suffice my needs. 🤗

    Good to know though that additional information may be available in the future.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Un-commenting Message function of python tag results in RuntimeError

    Hi @m_adam,

    alrighty, good to know that this will be looked into and eventually get fixed.

    Yes, I remember I returned True in the past. In the python tag as well as in the TagData equivalent. As returning super didn't work.

    Thanks for the information anyway. 🙂

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    Un-commenting Message function of python tag results in RuntimeError

    Hi guys,

    I don't know if this any news but I think I found a bug in def message(id: int, data: object) -> bool: of the python tag. Un-commenting this method results in the following error.

    Traceback (most recent call last):
      File "Python", line 20, in message
    RuntimeError: super(): __class__ cell not found
    

    I observed this in S26 if that helps.

    Cheers,
    Sebastian

    from typing import Optional
    import c4d
    
    doc: c4d.documents.BaseDocument # The document evaluating this tag
    op: c4d.BaseTag # The Python scripting tag
    flags: int # c4d.EXECUTIONFLAGS
    priority: int # c4d.EXECUTIONPRIORITY
    tp: Optional[c4d.modules.thinkingparticles.TP_MasterSystem] # Particle system
    thread: Optional[c4d.threading.BaseThread] # The thread executing this tag
    
    def main() -> None:
        # Called when the tag is executed. It can be called multiple time per frame. Similar to TagData.Execute.
        # Write your code here
        pass
    
    
    def message(id: int, data: object) -> bool:
        # Called when the tag receives messages. Similar to TagData.Message.
        # Write your code here
        return super().Message(id, data)
    
    """
    def draw(bd: c4d.BaseDraw) -> bool:
        # Called to display some visual element in the viewport. Similar to TagData.Draw.
        # Write your code here
        return True
    """
    
    posted in Cinema 4D SDK •
    RE: How to be sure if an object is a generator/modifier?

    Hello @ferdinand,

    alright I see. I already thought that you would teach me once again that the naive thought I put up here would'nt hold up to the real world. 😄 😄 😄

    Thanks for the explanation and the example you provided. I guess that will do just fine. As always much appreciated! 🙂

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Crash when apply a Tag plugin.

    Hello @Dunhou,

    I'm not super sure about what INCLUDE Texpression means in regards to c4ds deeper resource implementation. I guess it's just the way to say how your tag plugin should be treated. That it should be calculated as an expression in c4ds execution pipeline, if that makes sense.
    Maybe someone from the sdk team can jump in here and provide a little more elaborate answer. 😄

    By the way, a nice idea for a tag plugin. 😉
    I took the freedom to investigate the sticking respectively the refreshing issue that is. And I made the index parameter loop as well. 😉

    Find below a commented version of your code that works like a charm for me.

    Cheers,
    Sebastian

    import os
    import c4d
    from c4d.modules import mograph as mo
    
    # Be sure to use a unique ID obtained from www.plugincafe.com
    PLUGIN_ID = 1060757
    
    
    class LookAtCamera(c4d.plugins.TagData):
        """Look at Camera"""
        
        def Init(self, node: c4d.GeListNode):
            """Called when Cinema 4D Initialize the TagData (used to define, default values).
    
            Args:
                node (c4d.GeListNode): The instance of the TagData.
    
            Returns:
                True on success, otherwise False.
            """
            self.InitAttr(node, int, c4d.CLONER_INDEX)
            self.InitAttr(node, c4d.BaseObject, c4d.LINK_NODE)
            self.InitAttr(node, bool, c4d.STIKY_ON_CLONER)
            
            node[c4d.CLONER_INDEX] = 0
            node[c4d.LINK_NODE] = None
            node[c4d.STIKY_ON_CLONER] = False
    
            return True
        
        def Execute(self, tag, doc, op, bt, priority, flags):
            """Called by Cinema 4D at each Scene Execution, this is the place where calculation should take place.
    
            Args:
                tag (c4d.BaseTag): The instance of the TagData.
                doc (c4d.documents.BaseDocument): The host document of the tag's object.
                op (c4d.BaseObject): The host object of the tag.
                bt (c4d.threading.BaseThread): The Thread that execute the this TagData.
                priority (EXECUTIONPRIORITY): Information about the execution priority of this TagData.
                flags (EXECUTIONFLAGS): Information about when this TagData is executed.
            """
    
            stick = tag[c4d.STIKY_ON_CLONER] # Should the linked node stick?
            link_node = tag[c4d.LINK_NODE] # The linked node
    
            # If there is no linked node or stick is not enabled there is no need for computation. Return early.
            if not link_node or not stick:
                return c4d.EXECUTIONRESULT_OK
    
            # Check whether op is a cloner object
            if not op.CheckType(1018544): # I like to use op.CheckType here over op.GetType() because it directly returns a boolean value I can compare with.
                return c4d.EXECUTIONRESULT_OK
            
            # Get MoData
            md = mo.GeGetMoData(op)
            if not md:
                return c4d.EXECUTIONRESULT_OK
            
            user_index = tag[c4d.CLONER_INDEX] # The index chosen by the user
            count = md.GetCount() # Get the clone count of our cloner
            marr = md.GetArray(c4d.MODATA_MATRIX) # Get the MoData array which holds the matrices of each clone
            index = user_index % count # Make the index loop so that you get back to e.g. index 0 if index is e.g. greater than your clone count. 
    
            cloner_mg = op.GetMg() # Get the global matrix of the cloner
            clone_mg = marr[index] # Get the matrix of the clone we want to stick our linked node to
            link_node.SetMg(cloner_mg*clone_mg) # Multiply both matrices and set the global matrix of the linked node to the matrix of the clone we get over our index
            link_node.Message(c4d.MSG_UPDATE) # Inform the linked node that it should update its matrix
    
            return c4d.EXECUTIONRESULT_OK
    
    if __name__ == "__main__":
        # Retrieves the icon path
        directory, _ = os.path.split(__file__)
        fn = os.path.join(directory, "res", "icon.png")
    
        # Creates a BaseBitmap
        bmp = c4d.bitmaps.BaseBitmap()
        if bmp is None:
            raise MemoryError("Failed to create a BaseBitmap.")
    
        # Init the BaseBitmap with the icon
        if bmp.InitWith(fn)[0] != c4d.IMAGERESULT_OK:
            raise MemoryError("Failed to initialize the BaseBitmap.")
    
        c4d.plugins.RegisterTagPlugin(id=PLUGIN_ID,
                                      str="Taligncloner",
                                      info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE,
                                      g=LookAtCamera,
                                      description="Taligncloner",
                                      icon=bmp)
    
    
    posted in Cinema 4D SDK •
    RE: Crash when apply a Tag plugin.

    Hello @Dunhou,

    I checked the code you provided. There was a wrong line in your res file.

     INCLUDE Taligncloner; 
    

    has to be:

     INCLUDE Texpression;
    
    // The description defintion of the tag Taligncloner.
    CONTAINER Taligncloner
    {
        NAME Taligncloner;
        INCLUDE Texpression;
    
        // The main "Tag" tab of the tag.
        GROUP ID_TAGPROPERTIES
        {
            LINK LINK_NODE { FIT_H; ACCEPT { Obase; } }
            REAL CLONER_INDEX  { UNIT METER; STEP 1; }
            BOOL STIKY_ON_CLONER { }
        }
    }
    

    Besides the fact that the object which is supposed to stick to the cloner jumps around each refresh, it now works for me in S26. 😉

    But you are right, creation and editing of the res file is a royal pain in the a**. 😄

    Hope this helped.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    How to find out if document is rendering from a python tag

    Hello guys,

    how can I find out if a document is rendering? I tried the following code with mixed results.

    What works for me is the following.
    All of these methods return True if their respective task runs.

    c4d.documents.GetBatchRender()
    print(batch.IsRendering())
    
    print(c4d.CheckIsRunning(c4d.CHECKISRUNNING_EXTERNALRENDERING))
    
    print(c4d.modules.takesystem.IsTakeRenderRunning())
    

    What does not work are the following methods which simply return always 0

    print(c4d.CheckIsRunning(c4d.CHECKISRUNNING_EDITORRENDERING))
    
    print(c4d.CheckIsRunning(c4d.CHECKISRUNNING_INTERACTIVERENDERING)
    

    I'm using all of this inside def main() of a python tag if that helps.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    How to be sure if an object is a generator/modifier?

    Hello guys,

    as the title says I'm looking for a reliable way to check if an object in the object manager is actually a generator or modifier.

    I know I can use e.g. c4d.BaseList2D.GetInfo() & c4d.OBJECT_GENERATOR. While that works with e.g. c4d.BaseObject(c4d.Osds) it doesn't work with c4d.BaseObject(c4d.Olight). A light object doesn't seem to be a generator, is it?

    Basically I want a bullet proof way to know if an object is a generator or a modifier and therefore has c4d.ID_BASEOBJECT_GENERATOR_FLAG.

    Can this be done?

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: How to draw on spline points from spline returned by Object Data

    Hello @ferdinand,

    sorry for being a bit ambigous here. Drawing points is actually exactly what I wanted. 😊
    As always thanks for your time and effort.

    Funny enough DrawPoints seems to internally use the "same points" to draw, one gets to see when one edits the points of a polygon object. 😄 In S26 the points are round (thats probably why I was thinking of circles) while in R20 they are squares. No question here, just an observation.

    What is actually weird though is that the color gradient you used here to color the points doesn't work in R20. The points are simply drawn black there. Possibly a Python 2/3 issue caused by integer/float division differences? 🤷

    Allow one follow up question. How can I draw only the control points of the spline? In your example you use a circle primitive which has - when made editable - exactly four points making up the shape via tangents. Where as the drawn points using the intermediate points of the circle as well.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    How to draw on spline points from spline returned by Object Data

    Hi guys,

    following the py-osffset_y_spline example from the sdk examples I wonder how one would draw e.g. circles on the spline points of the returned spline object.

    Can someone help me here. 🤔

    Cheers,
    Sebastian

    """
    Copyright: MAXON Computer GmbH
    Author: Riccardo Gigante, Maxime Adam
    
    Description:
        - Retrieves the first child object and offset all its points on the y-axis by a specific value. Tangents are unaffected.
        - Demonstrates a Spline Generator that requires Input Spline and Outputs a valid Spline everywhere in Cinema 4D.
    
    Note:
        - SplineInputGeneratorHelper is a class holding utility functions
            SplineInputGeneratorHelper.OffsetSpline is the function responsible for moving points.
    
    Class/method highlighted:
        - c4d.SplineObject / c4d.LineObject / c4d.PointObject
        - c4d.plugins.ObjectData
        - ObjectData.Init()
        - ObjectData.GetDimension()
        - ObjectData.CheckDirty()
        - ObjectData.GetVirtualObjects()
        - ObjectData.GetContour()
    
    """
    import c4d
    
    PLUGIN_ID = 98989801
    
    
    class SplineInputGeneratorHelper(object):
    
        @staticmethod
        def FinalSpline(sourceSplineObj):
            """Retrieves the final (deformed) representation of the spline.
    
            Args:
                sourceSplineObj (c4d.BaseObject or c4d.SplineObject or LineObject): A c4d.BaseObject that can be represented as a Spline.
    
            Returns:
                c4d.SplineObject: The final Spline/Line Object, SplineObject should be returned when it's possible
            """
            if sourceSplineObj is None:
                raise TypeError("Expect a spline object got {0}".format(sourceSplineObj.__class__.__name__))
    
            if sourceSplineObj.IsInstanceOf(c4d.Onull):
                return None
    
            # Checks if sourceSplineObj can be treated as a spline
            if not sourceSplineObj.IsInstanceOf(c4d.Oline) and not sourceSplineObj.GetInfo() & c4d.OBJECT_ISSPLINE:
                raise TypeError("Expect an object that can be treated as spline object.")
    
            # If there is a Deformed cache, retrieves it, but it seems it's never the case
            deformedCache = sourceSplineObj.GetDeformCache()
            if deformedCache is not None:
                sourceSplineObj = deformedCache
    
            # Returns the LineObject if it's a LineObject
            if sourceSplineObj.IsInstanceOf(c4d.Oline):
                return sourceSplineObj
    
            # Retrieves the SplineObject
            realSpline = sourceSplineObj.GetRealSpline()
            if realSpline is None:
                raise RuntimeError("Failed to retrieve the real c4d.SplineObject from {0}".format(sourceSplineObj))
    
            return realSpline
    
        @staticmethod
        def OffsetSpline(inputSpline, offsetValue):
            """Performs the Y-Offset of the spline. 
    
            Take care the inputSpline can be sometime a LineObject or a SplineObject depending of the context (called from GVO or GetContour).
    
            Args:
                inputSpline (Union[c4d.LineObject, c4d.SplineObject]): The original LineObject or SplineObject
                offsetValue (float): The amount to offset Y parameter
    
            Returns:
                Union[c4d.LineObject, c4d.SplineObject]: A new Line/Spline instance
            """
            if inputSpline is None:
                raise TypeError("Expect a SplineObject got {0}".format(inputSpline.__class__.__name__))
    
            # Checks if the the input object is a SplineObject or a LineObject
            if not inputSpline.IsInstanceOf(c4d.Ospline) and not inputSpline.IsInstanceOf(c4d.Oline):
                raise TypeError("Expect a SplineObject or a LineObject got {0}".format(inputSpline.__class__.__name__))
    
            # Retrieves a clones of the Splines/LineObject, so tangents and all parameters are kept.
            resSpline = inputSpline.GetClone()
            if resSpline is None:
                raise MemoryError("Failed to create a new Spline Object.")
    
            # Retrieves all points position of the source object
            allPts = inputSpline.GetAllPoints()
            if not allPts:
                return
    
            # Adds the offsetValue in Y for each point (this is done only in memory)
            allPtsOffsets = [c4d.Vector(pt.x, pt.y + offsetValue, pt.z) for pt in allPts]
    
            # Sets all points of the resSpline from the list previously calculated.
            resSpline.SetAllPoints(allPtsOffsets)
    
            # Notifies about the generator update
            resSpline.Message(c4d.MSG_UPDATE)
    
            # Returns the computed spline
            return resSpline
    
        @staticmethod
        def GetCloneSpline(op):
            """Emulates the GetHierarchyClone in the GetContour by using the SendModelingCommand.
    
            Args:
                op (c4d.BaseObject): The Object to clone and retrieve the current state (take care the whole hierarchy is join into one object.
    
            Returns:
                Union[c4d.BaseObject, None]: The merged object or None, if the retrieved object is not a Spline.
            """
            # Copies the original object
            childSpline = op.GetClone(c4d.COPYFLAGS_NO_ANIMATION)
            if childSpline is None:
                raise RuntimeError("Failed to copy the child spline.")
    
            doc = c4d.documents.BaseDocument()
            if not doc:
                raise RuntimeError("Failed to Create a Doc")
    
            doc.InsertObject(childSpline)
    
            # Performs a Current State to Object
            resultCSTO = c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_CURRENTSTATETOOBJECT, list=[childSpline], doc=doc)
            if not isinstance(resultCSTO, list) or not resultCSTO:
                raise RuntimeError("Failed to perform MCOMMAND_CURRENTSTATETOOBJECT.")
    
            childSpline = resultCSTO[0]
    
            # If the results is a Null, performs a Join command to retrieve only one object.
            if childSpline.CheckType(c4d.Onull):
                resultJoin = c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_JOIN, list=[childSpline], doc=doc)
                if not isinstance(resultJoin, list) or not resultJoin:
                    raise RuntimeError("Failed to perform MCOMMAND_JOIN.")
    
                childSpline = resultJoin[0]
    
            if childSpline is None:
                raise RuntimeError("Failed to retrieve cached spline.")
    
            # Checks if childSpline can be interpreted as a Spline.
            if not childSpline.GetInfo() & c4d.OBJECT_ISSPLINE and not childSpline.IsInstanceOf(c4d.Ospline) and not childSpline.IsInstanceOf(c4d.Oline):
                return None
    
            return childSpline
    
        @staticmethod
        def HierarchyIterator(obj):
            """A Generator to iterate over the Hierarchy.
    
            Args:
                obj (c4d.BaseObject): The starting object of the generator (will be the first result)
    
            Returns:
                c4d.BaseObject: All objects under and next of the `obj`
            """
            while obj:
                yield obj
                for opChild in SplineInputGeneratorHelper.HierarchyIterator(obj.GetDown()):
                    yield opChild
                obj = obj.GetNext()
    
    
    class OffsetYSpline(c4d.plugins.ObjectData):
    
        _childContourDirty = 0  # type: int
        _childGVODirty = -1  # type: int
    
        def Init(self, op):
            if op is None:
                raise RuntimeError("Failed to retrieve op.")
    
            self.InitAttr(op, float, [c4d.PY_OFFSETYSPLINE_OFFSET])
            op[c4d.PY_OFFSETYSPLINE_OFFSET] = 100.0
    
            # Defines members variable to store the dirty state of Children Spline
            self._childContourDirty = 0
            self._childGVODirty = -1
    
            return True
    
        def Draw(self, op, drawpass, bd, bh):
            # How to go about drawing e.g. circles on every spline point of the returned spline object?
    
        def GetDimension(self, op, mp, rad):
            """This Method is called automatically when Cinema 4D try to retrieve the boundaries of the object.
    
            Args:
                op (c4d.BaseObject): The Python Generator base object.
                mp (c4d.Vector): Assign the center point of the bounding box to this vector.
                rad (float): Assign the XYZ bounding box radius to this vector.
            """
            if op is None:
                raise RuntimeError("Failed to retrieve op.")
    
            # Initializes default values
            mp, rad = c4d.Vector(), c4d.Vector()
    
            # If there is no child, that means the generator output nothing, so an empty size
            if op.GetDown() is None:
                return
    
            # Assigns value as the child object
            rad = op.GetDown().GetRad()
            mp = op.GetMg().off
    
            # Offsets the bounding box by the Y offset the generator deliver
            mp.y = op.GetMg().off.y + op[c4d.PY_OFFSETYSPLINE_OFFSET]
    
        def CheckDirty(self, op, doc):
            """This Method is called automatically when Cinema 4D ask the object is dirty,
            something changed so a new computation of the generator is needed.
    
            In reality this is only useful for GetContour, GetVirtualObjects is automatically handled by Cinema 4D,
            But since the spline returned by GetContour is cached by Cinema 4D, you have to use CheckDirty
            To define when a new call of GetContour is needed. Moreover CheckDirty is only called in some special event,
            e.g. the Python Spline Generator is under another Python Spline generator.
    
            Args:
                op (c4d.BaseObject): The Python Generator c4d.BaseObject.
                doc (c4d.documents.BaseDocument): The document containing the plugin object.
            """
            if op is None or doc is None:
                raise RuntimeError("Failed to retrieve op or doc.")
    
            # Retrieves the First Child
            child = op.GetDown()
            if child is None:
                self._childContourDirty = -1
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
                return
    
            # Retrieves the dirty count of the first child if there is a spline, otherwise set it to -1
            childDirty = child.GetDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX | c4d.DIRTYFLAGS_CACHE)
    
            # Checks if the dirty changed, if this is the case set op as dirty (it will force GetContour to be called)
            if childDirty != self._childContourDirty:
                self._childContourDirty = childDirty
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
    
        def GetVirtualObjects(self, op, hh):
            """This method is called automatically when Cinema 4D ask for the cache of an object. 
    
            This is also the place where objects have to be marked as input object by touching them (destroy their cache in order to disable them in Viewport)
    
            Args:
                op (c4d.BaseObject): The Python Generator c4d.BaseObject.
                hh (c4d.HierarchyHelp): The helper object.
    
            Returns:
                Union[c4d.LineObject, c4d.SplineObject]: The represented Spline.
            """
            if op is None or hh is None:
                raise RuntimeError("Failed to retrieve op or hh.")
    
            # Retrieves the first enabled child
            child = op.GetDown()
            if child is None:
                self._childGVODirty = -1
                return
    
            # Touches all others children sine we don't want to have them later
            for obj in SplineInputGeneratorHelper.HierarchyIterator(op.GetDown().GetNext()):
                obj.Touch()
    
            # Retrieves the Clones, then mark them as read
            resGHC = op.GetHierarchyClone(hh, child, c4d.HIERARCHYCLONEFLAGS_ASSPLINE)
            if resGHC is None:
                resGHC = op.GetAndCheckHierarchyClone(hh, child, c4d.HIERARCHYCLONEFLAGS_ASSPLINE, False)
            if resGHC is None:
                return None
    
            # Retrieves results
            opDirty = resGHC["dirty"]
            childSpline = resGHC["clone"]
            if childSpline is None:
                return None
    
            # Checks if childSpline can be interpreted as a Spline.
            if not childSpline.GetInfo() & c4d.OBJECT_ISSPLINE and not childSpline.IsInstanceOf(c4d.Ospline) and not childSpline.IsInstanceOf(c4d.Oline):
                return None
    
            # Checks if the dirty of the child changed
            opDirty |= op.IsDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX)
            childDirty = child.GetDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX | c4d.DIRTYFLAGS_CACHE)
    
            # If the dirty count didn't change, return the cache
            if childDirty == self._childGVODirty and not opDirty:
                self._childGVODirty = child.GetDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX | c4d.DIRTYFLAGS_CACHE)
                return op.GetCache()
    
            # Retrieves the deformed Spline/LineObject (most of the time it's a LineObject)
            deformedSpline = SplineInputGeneratorHelper.FinalSpline(childSpline)
            if deformedSpline is None:
                return c4d.BaseObject(c4d.Onull)
    
            # Performs operation on the spline
            resSpline = SplineInputGeneratorHelper.OffsetSpline(deformedSpline, op[c4d.PY_OFFSETYSPLINE_OFFSET])
    
            # Returns the modified spline
            return resSpline
    
        def GetContour(self, op, doc, lod, bt):
            """This method is called automatically when Cinema 4D ask for a SplineObject, it's not called every time,
            only in some conditions like nested Spline Generator.
    
            Args:
                op (c4d.BaseObject): The Python Generator c4d.BaseObject.
                doc (c4d.documents.BaseDocument): The document containing the plugin object.
                lod (int): The level of detail.
                bt (c4d.threading.BaseThread): The executing thread.
    
            Returns:
                The SplineObject representing the final Spline.
            """
            if op is None or doc is None:
                raise RuntimeError("Failed to retrieve op or doc.")
    
            # Retrieves the first spline and set dirtyCount to 0 if the spline does not exists.
            childSpline = op.GetDown()
            if childSpline is None:
                self._childContourDirty = 0
                return None
    
            # Retrieves a Clone working spline.
            childSplineClone = SplineInputGeneratorHelper.GetCloneSpline(childSpline)
            if childSplineClone is None:
                return None
    
            # Retrieves the deformed Spline/LineObject
            deformedSpline = SplineInputGeneratorHelper.FinalSpline(childSplineClone)
            if deformedSpline is None:
                return None
    
            # Performs operation on the spline
            resSpline = SplineInputGeneratorHelper.OffsetSpline(deformedSpline, op[c4d.PY_OFFSETYSPLINE_OFFSET])
    
            # Updates dirtyCount for the child spline
            self._childContourDirty = childSpline.GetDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX | c4d.DIRTYFLAGS_CACHE)
    
            return resSpline
    
    
    if __name__ == "__main__":
        c4d.plugins.RegisterObjectPlugin(id=PLUGIN_ID,
                                         str="Py-OffsetY",
                                         g=OffsetYSpline,
                                         description="py_offsets_y_spline",
                                         icon=None,
                                         info=c4d.OBJECT_GENERATOR | c4d.OBJECT_INPUT | c4d.OBJECT_ISSPLINE)
    
    posted in Cinema 4D SDK •
    RE: How to "permanently" store data?

    Hello @ferdinand,

    my goal here is to store layer states of a document so that I can check if some attribute of a layer changed, restore or update a state.

    At first I thought I simply write the layers dictionary layer.GetLayerData(doc) as a json file to disk. But that appears to me as the wrong way to go. Since then I quickly would end up with a lot of files on the disk for every document I want to store layer states from.

    Thats the reason why I then thought about storing a reference of each layer along with its dictionary in a c4d.BaseContainer and store those containers again in the documents container so that it gets saved automatically by c4d.

    While this will probably work, it feels cumbersome. 🤷 😆

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    How to "permanently" store data?

    Hi guys,

    as the title already suggests I wonder how we are supposed to permanently store data. With permanently I mean storing data from for example a layer that will still be accesible after a document was closed and opend again.

    I know that we can store data in a nodes c4d.BaseContainer. But doing so I found that one can not simply store whatever python object one likes in it. For example the dictionary one gets from layer.GetLayerData(doc) can not be stored in the container as it is, right?

    So, am I really supposed to put every single attribute of given dictionary as a single entry in a c4d.BaseContainer or is there a simpler/better way to do this?

    And how would one store a nodes unique id (C4DAtom.FindUniqueID(c4d.MAXON_CREATOR_ID)) along with the data in that container? As I learned that this unique id is the safest and only way to identify a certain node.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Treeview does not refresh

    Hello @ferdinand,

    sorry for coming back this late. Work kept me quite busy. 🙄 😄

    @ferdinand so, I had a play with your code example. It works like a charm and also seems to have a lot of potential for all kinds of update/dirty checking issues.

    Thanks again @ferdinand for your help, the effort you always put into answering our questions and the eloborate examples you come up with!

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •