Navigation

    • Register
    • Login
    • Search
    1. Home
    2. HerrMay
    H

    HerrMay

    @HerrMay

    4
    Reputation
    33
    Posts
    45
    Profile views
    0
    Followers
    0
    Following
    Joined Last Online

    • Profile
    • More
      • Following
      • Followers
      • Topics
      • Posts
      • Best
      • Groups
    HerrMay Follow

    Best posts made by HerrMay

    RE: Blank appearing dropdown cycle

    Hi @m_adam ,

    of course, stupid me. ๐Ÿคฆ I knew it was something simple Iโ€™m missing here. Seems I didnโ€™t see the elephant in the room. ๐Ÿ˜„

    This works like a charm now. Thank you!

    Allow one follow up question though. If I now want to translate this code to a TagData Plugin Iโ€™m pretty sure I donโ€™t wanna use a global variable, do I? Would it be sufficient to make that variable a member variable inside of def __init__(self) of the Tag Plugin class?

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Unique Material per Object Plugin instance?

    Hi Guys,

    as I'm pretty sure I found a way to achieve what I'm after I thought I update this thread. Maybe this will help others as well. ๐Ÿ˜‰

    After taking some time to make NodeData.CopyTo() work, I ended up not getting it to work at all.

    So I thought about how I could achieve what I'm after a different way. Long story short, I ended up implementing a MessageData plugin as some kind of watchdog for a document. Since its CoreMessage runs on the main thread I can happily insert and delete materials as much as I wish to. (At least I'm hoping so ๐Ÿ˜ฌ ๐Ÿ˜)

    Tl; dr
    The idea behind this goes as follows. I have a timer running and in addition to that I listen for c4d.EVMSG_CHANGE and do some checking to see if the scene needs to update. In my case it's comparing the amount of a specific object against the amount of "specific" materials. If there's a difference I use that difference to delete or insert materials until there's no difference. Once there's no difference I can assign the materials to the objects and let each object control its own material.

    To distinguish between materials responsible for my plugin and the ones that aren't I make sure to put a unique plugin id inside the base container of the material I can then check for.

    Here's a code snippet of that MessageData:

    class Watchdog(c4d.plugins.MessageData):
    
        PLUGIN_ID = "Use your own unique one"
        PLUGIN_NAME = "A MessageData plugin."
        PLUGIN_INFO = 0
    
        def __init__(self):
            self._time = 1000
    
        def GetTimer(self):
            return self._time
    
        def SetTimer(self, time):
            self._time = time
    
    
        @property
        def should_execute(self):
            is_mainthread = c4d.threading.GeIsMainThread()
            check_running = (
                bool(c4d.CheckIsRunning(c4d.CHECKISRUNNING_EDITORRENDERING)),
                bool(c4d.CheckIsRunning(c4d.CHECKISRUNNING_EXTERNALRENDERING)),
                bool(c4d.CheckIsRunning(c4d.CHECKISRUNNING_INTERACTIVERENDERING)),
                bool(c4d.CheckIsRunning(c4d.CHECKISRUNNING_ANIMATIONRUNNING)),
                bool(c4d.CheckIsRunning(c4d.CHECKISRUNNING_VIEWDRAWING))
            )
            is_running = any(item is True for item in check_running)
            return is_mainthread and not is_running
    
    
        def CoreMessage(self, mid, mdata):
    
            if not self.should_execute:
                return False
    
            doc = c4d.documents.GetActiveDocument()
            # SceneHandler is a custom class I delegate the whole creation and comparing stuff to.
            objs, mats = ..., ...
            scene = SceneHandler(objs, mats)
    
            # Check for a change and start the timer again. But only if the scene should update. Otherwise the timer would run all the time.
            if mid == c4d.EVMSG_CHANGE:
                if scene.should_update:
                    self.SetTimer(1000)
    
            # If we get a timer event we update the scene as long as it shouldn't update anymore. We can then stop the timer.
            if mid == c4d.MSG_TIMER:
                if not scene.should_update:
                    self.SetTimer(0)
                scene.update(doc)
    
            return True
    

    Maybe this will help others. Since I found a solution for my problem this thread can be marked solved.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Run a GUI Dialog AFTER C4D Launches not BEFORE?

    Hi @bentraje,

    Iโ€˜m not sure but maybe checking for c4d. C4DPL_PROGRAM_STARTED in def PluginMessage(id, data) could be of any help here.

    Something like below.

    import c4d
    import sys
    
    def PluginMessage(id, data):
        if id == c4d.C4DPL_PROGRAM_STARTED:
            # Do your messaging here. 
            return True
    
        return False
    
    

    Cheers,
    Sebastian!

    posted in Cinema 4D SDK •
    RE: How to Add Child Shaders to a Fusion Shader?

    Hello @ferdinand,

    when trying out your function to traverse shaders I noticed that for two materials in the scene it yields for the first material all the shaders of itself but also all the shaders of the second material. For second material it works like expected and yields only the shaders of itself.

    After fiddling around a bit with some other code of you from this post I think I ended up with a function that is yielding all the shaders from a material iteratively.

    Find the code below. Maybe this will help others. ๐Ÿ™‚

    Cheers,
    Sebastian

    def iter_shaders(node):
        """Credit belongs to Ferdinand from the Plugincafe. I added only the part with the material and First Shader checking.
    
    Yields all descendants of ``node`` in a truly iterative fashion.
    
        The passed node itself is yielded as the first node and the node graph is
        being traversed in depth first fashion.
    
        This will not fail even on the most complex scenes due to truly
        hierarchical iteration. The lookup table to do this, is here solved with
        a dictionary which yields favorable look-up times in especially larger
        scenes but results in a more convoluted code. The look-up could
        also be solved with a list and then searching in the form ``if node in
        lookupTable`` in it, resulting in cleaner code but worse runtime metrics
        due to the difference in lookup times between list and dict collections.
        """
        if not node:
            return
    
        # The lookup dictionary and a terminal node which is required due to the
        # fact that this is truly iterative, and we otherwise would leak into the
        # ancestors and siblings of the input node. The terminal node could be
        # set to a different node, for example ``node.GetUp()`` to also include
        # siblings of the passed in node.
        visisted = {}
        terminator = node
    
        while node:
    
            if isinstance(node, c4d.Material) and not node.GetFirstShader():
               break
            
            if isinstance(node, c4d.Material) and node.GetFirstShader():
                node = node.GetFirstShader()
            
            # C4DAtom is not natively hashable, i.e., cannot be stored as a key
            # in a dict, so we have to hash them by their unique id.
            node_uuid = node.FindUniqueID(c4d.MAXON_CREATOR_ID)
            if not node_uuid:
                raise RuntimeError("Could not retrieve UUID for {}.".format(node))
    
            # Yield the node when it has not been encountered before.
            if not visisted.get(bytes(node_uuid)):
                yield node
                visisted[bytes(node_uuid)] = True
    
            # Attempt to get the first child of the node and hash it.
            child = node.GetDown()
    
            if child:
                child_uuid = child.FindUniqueID(c4d.MAXON_CREATOR_ID)
                if not child_uuid:
                    raise RuntimeError("Could not retrieve UUID for {}.".format(child))
    
            # Walk the graph in a depth first fashion.
            if child and not visisted.get(bytes(child_uuid)):
                node = child
    
            elif node == terminator:
                break
    
            elif node.GetNext():
                node = node.GetNext()
    
            else:
                node = node.GetUp()
    
    posted in Cinema 4D SDK •

    Latest posts made by HerrMay

    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 •