Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    GetRealSpline() not returning a result

    Cinema 4D SDK
    c++ r20 sdk
    4
    13
    3.2k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • D
      d_schmidt
      last edited by

      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

      1 Reply Last reply Reply Quote 0
      • M
        m_adam
        last edited by

        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.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        D 1 Reply Last reply Reply Quote 0
        • D
          d_schmidt @m_adam
          last edited by

          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

          1 Reply Last reply Reply Quote 1
          • M
            m_adam
            last edited by m_adam

            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.")
            
                    # Sets the output spline in the same position of the generator
                    resSpline.SetMg(inputSpline.GetUpMg())
            
                    # 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) * inputSpline.GetUpMg() 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.")
            
                    # Retrieves the original document (so all links are kept)
                    doc = op.GetDocument() if op.GetDocument() else c4d.documents.BaseDocument()
                    if not doc:
                        raise RuntimeError("Failed to retrieve a Doc")
            
                    # Inserts the object into the document, this is needed for Current State To Object operation
                    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.")
            
                    # Removes the original clone object
                    childSpline.Remove()
            
                    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
            
                    # Touch all children objects to hide them from the viewport
                    for obj in SplineInputGeneratorHelper.HierarchyIterator(childSpline):
                        obj.Touch()
            
                    # 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.GetClone()
            
            
            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)
            
            

            Cheers,
            Maxime.

            MAXON SDK Specialist

            Development Blog, MAXON Registered Developer

            D 1 Reply Last reply Reply Quote 3
            • D
              d_schmidt @m_adam
              last edited by

              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.

              1 Reply Last reply Reply Quote 0
              • D
                d_schmidt
                last edited by d_schmidt

                Hello, I know its been a while since I last posted on this thread by I've finally had enough time to go over the wonderful example you provided. I assumed I should reply in this thread instead of making a new one though.

                It works in all the circumstances that I've been testing it in, except for one, which is great!

                So I had two followup questions. The current problem I'm running into is when I make it editable it while using a Tracer object(It also happens with connect and spline mask objects) I get an error and it breaks in these lines of code in GetContour().

                # Retrieves a Clone working spline.
                        childSplineClone = SplineInputGeneratorHelper.GetCloneSpline(childSpline)
                        if childSplineClone is None:
                    		return None
                

                When not making it editable and just playing through the timeline it returns it perfectly though. The difference in the two circumstances seems to be whether GVO or GetContour is being used to return the spline. GVO is used when moving frame to frame, but GetContour is used when making it editable.

                So my question is in regards to using both GVO and GetContour. I thought GVO should be used for returning objects and GetContour should be used when returning splines but in this case both are used, although the latter is breaking. Is it acceptable practice to use both of them and should I switch it to always use GVO so I can get the correct output?

                Dan

                1 Reply Last reply Reply Quote 0
                • M
                  m_adam
                  last edited by m_adam

                  Hi, @d_schmidt I' sorry for the time needed, unfortunately, I was pretty busy this week and didn't get the time to look at your post.
                  As you can see in the next gift I'm not able to reproduce your issue, could you please send a c4d file. And let me know which version exactly you are using.
                  spline.gif

                  Cheers,
                  Maxime.

                  MAXON SDK Specialist

                  Development Blog, MAXON Registered Developer

                  D 1 Reply Last reply Reply Quote 0
                  • D
                    d_schmidt @m_adam
                    last edited by

                    Hi, thanks for the reply.

                    I went back and re-tested everything again. It seems specifically to be an issue with using a Tracer and an Emitter as the inputs for OffsetY.

                    Here's a gif showing the result I'm getting. I'm not sure how to embed it like you did.

                    And the scene is just the Emitter and Tracer but here it is: Tracer Emitter.c4d

                    Dan

                    1 Reply Last reply Reply Quote 0
                    • M
                      m_adam
                      last edited by

                      Hi, I updated the previous script to fix the issue.

                      The main problem was in GetCloneSpline in order to make a clone of the object (a Current state to object) you need to insert it into a document, but I inserted it (the tracer) into a new document, so the tracer lost its link with its particle emitter and result in an empty tracer. Inserting the clone directly into the correct document fix the issue (see line 120 and 133 for the cleanup)

                      I also fixed another issue regarding position while making an object editable.
                      There is actually a bug in the Tracer object and the command make object editable, if it's set to global position mode.
                      The last point will be in the generator space so that means it will break the curve if the tracer and op are not in 0,0,0..
                      Unfortunately, nothing can be done here, but editing the tracer object first then the generator makes it works, or you can use Current State To Object command instead.

                      Cheers,
                      Maxime.

                      MAXON SDK Specialist

                      Development Blog, MAXON Registered Developer

                      bacaB 1 Reply Last reply Reply Quote 1
                      • bacaB
                        baca @m_adam
                        last edited by baca

                        Hi @m_adam ,

                        Thanks for the script.
                        I've noticed if child object is a generator object, then on child properties change GetContour is called.
                        For example if position changed by dragging object in the viewport - child object become visible (until mouse button is released).
                        Can you please help to hide child objects as well as GetVirtualObjects do this?

                        Video: http://take.ms/C64gW

                        1 Reply Last reply Reply Quote 0
                        • ManuelM
                          Manuel
                          last edited by

                          hello,

                          @baca
                          To help us keep things clear and organized (and tracked), can you please open your own thread referring to this one and ask your question there.

                          Thanks and Cheers.
                          Manuel

                          MAXON SDK Specialist

                          MAXON Registered Developer

                          1 Reply Last reply Reply Quote 0
                          • First post
                            Last post