GetRealSpline() not returning a result



  • Hello, I'm trying to get the Spline Objects from the child objects of a Object Plugin I'm making, then I want to modify them and return them vie GetContour(). I've been using GetRealSpline() and that has been working pretty well, but there are a couple cases that it doesn't work.

    If I have a Instance Object then GetRealSpline() won't correctly get the referenced spline object. I'm trying to solve this in c++ but it's really easy to show in Python. Illustrated by the Python sdk example and a super simple C4D Scene :

    import c4d
    from c4d import gui
    
    def main():
      realSpline = op.GetRealSpline()               # Active object is a primitive spline object
      if realSpline is not None:
        doc.InsertObject(realSpline.GetClone())     # Insert the clone of the created spline object in the document
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    Circle returns a spline, but Circle Instance doesn't and I get the same results in C++. I've been looking into GetAndCheckHierarchyClone() and it seems like that would get me the proper object to use GetRealSpline(). on. But GetAndCheckHierarchyClone() needs HierarchyHelp which is in GVO(), but since I'm working with a spline I've been using GetContour() for my work.

    So my question is in C++, if I should be using GetAndCheckHierarchyClone(), and if so how would I get access to HierarchyHelp help or if there is a different way to reliably get the Spline Object from a child object.

    Thanks for taking a look,
    Dan



  • Hi Daniel,

    For the instance object you have to retrieve the link then call GetRealSpline on the linked object since as the name says, it's an instance so the geometry does not really exist.

    import c4d
    
    def main():
        op = doc.GetActiveObject()
        if op is None:
            raise TypeError("Failed to retrieves op")
        
        if isinstance(op, c4d.InstanceObject):
            op = op.GetReferenceObject(op.GetDocument())
            
        realSpline = op.GetRealSpline()               # Active object is a primitive spline object
        if realSpline is not None:
            doc.InsertObject(realSpline.GetClone())     # Insert the clone of the created spline object in the document
            c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    Regarding your GetAndCheckHierarchyClone questions, I have to do more tests, I will do tomorrow and be back to you. But I'm afraid you will need to manage manually the dirtiness/touching stuff.

    Cheers,
    Maxime.



  • Hi Maxime,

    Thanks for taking a look. I was hoping there would be a way to avoid doing a bunch of cases to cover exceptions such as the Instance object. There would be other objects that could take a spline in that I would have to find the reference to as well, and while I could locate them by hand it seems like something that would be possible via Cinema.

    If GetAndCheckHierarchyClone() turns up anything I would be interested in hearing it.

    Thanks for the help,
    Dan



  • Hi, Daniel, I'm wondering what's your final attend. So you want to create a generator asking for input spline but does LineObject is sufficient or you need the actual SplineObject, same things for the output, do you want to output a SplineObject or a PolygonObject?

    Cheers,
    Maxime.



  • Hi, Maxime, I'm trying to be able to browse through every point, the tangents of those points, and the segments they belong to. It seems like a LineObject wouldn't help me get the tangents or the spline point locations so I think I would need the actual SplineObject.

    I want to output a SplineObject, taking in the input splines points, tangents, and segments to be able modify it and return something different.

    I hope that clears it up,
    Dan



  • Hi Daniel, sorry for the time needed, it's indeed more complicated than my first thought since Cinema 4D is not really designed to have a Spline Generator based on Spline Input.

    Here a complete example so the generated spline can be used as well as a spline in all places of Cinema 4D (you can also chain your Spline Generator). I know it's not as straightforward as it could initially sound to be, but this is how it is.
    If you want to simply perform an operation, simply copy paste the code and modify the SplineInputGeneratorHelper.OffsetSpline function to your goal.

    """
    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()
    
    Compatible:
        - Win / Mac
        - R16, R17, R18, R19, R20
    """
    import c4d
    
    PLUGIN_ID = 98989801
    
    
    class SplineInputGeneratorHelper(object):
    
        @staticmethod
        def FinalSpline(sourceSplineObj):
            """
            Retrieves the final (deformed) representation of the spline
            :param sourceSplineObj: A c4d.BaseObject that can be represented as a Spline.
            :type sourceSplineObj: c4d.BaseObject or c4d.SplineObject or LineObject
            :return: 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 threaded as a spline
            if not sourceSplineObj.IsInstanceOf(c4d.Oline) and not sourceSplineObj.GetInfo() & c4d.OBJECT_ISSPLINE:
                raise TypeError("Expect an object that can be threaded 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 retrieves 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).
            :param inputSpline: The original LineObject or SplineObject
            :param offsetValue: The amount to offset Y parameter
            :return: 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 creates a new Spline Object.")
    
            # Retrieves all points position of the src 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)
    
            # Notify 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
            :param op: The Object to clones and retrieves the current state (take care the whole hierarchy is join into one object.
            :return: 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 retrieves 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 retrieves 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
            :param obj: The starting object of the generator (will be the first result)
            :return: 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 retrieves 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 GetDimension(self, op, mp, rad):
            """
            This Method is called automatically when Cinema 4D try to retrieve the boundaries of the object.
            :param op: The Python Generator base object.
            :param mp: Assign the center point of the bounding box to this vector.
            :param rad: Assign the XYZ bounding box radius to this vector.
            """
            if op is None:
                raise RuntimeError("Failed to retrieves 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.
            :param op: The Python Generator c4d.BaseObject.
            :param doc: The c4d.documents.BaseDocument containing the plugin object.
            """
            if op is None or doc is None:
                raise RuntimeError("Failed to retrieves 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)
            :param op: The Python Generator c4d.BaseObject.
            :param hh:The Python Generator c4d.BaseObject.
            :return: The Representing Spline (c4d.LineObject or SplineObject)
            """
            if op is None or hh is None:
                raise RuntimeError("Failed to retrieves 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.
            :param op: The Python Generator c4d.BaseObject.
            :param doc: The c4d.documents.BaseDocument containing the plugin object.
            :return: The SplineObject representing the final Spline.
            """
            if op is None or doc is None:
                raise RuntimeError("Failed to retrieves 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="OffsetY",
                                         g=OffsetYSpline,
                                         description="py_offsets_y_spline",
                                         icon=None,
                                         info=c4d.OBJECT_GENERATOR | c4d.OBJECT_INPUT | c4d.OBJECT_ISSPLINE)
    
    

    Cheers,
    Maxime.



  • No worries about the wait, Maxime. I'll have to spend some time looking into all of it! Thanks you for such a through response.


Log in to reply