SOLVED Spline shape for BaseDraw Handles

Hello Plugincfe! 💙

Drawing simple handle shapes such as arcs, lines, circles, etc., is a pretty simple task but what if I need to use something more complex?

What it the fastest and the most appropriate way of using custom spline shapes (eg. text spline) to define OBJECT_GENERATOR handles?

Can I import them from a virtual doc?

-Merk

I hope I don't have to define each points position manually.
Iterating through the source object's points is also a way to go but there probably is a better option which I don't know 😁

Hi @merkvilson, to draw an object, or a spline you should use the method BaseDraw.DrawPolygonObject don't get it wrong this function can also be used for spline.
In fact, it does work for LineObject (the cache of a SplineObject), PointObject and PolygonObject.

With that's said the method with the flag, DRAWOBJECT_USE_CUSTOM_COLOR normally offers an optional parameter 'col', this is not available in Python only in C++ (I've reported the issue and it will be fixed in future release). So in order to draw and defines a custom color you have to use the object color.

With that's said I've taken the Rounded-Tube GitHub example as base since it provides examples to play with handles and simply modify the draw function as bellow

    # Override method, draw additional stuff in the viewport (e.g. the handles).
    def Draw(self, op, drawpass, bd, bh):
        # Checks if Draw function is call from the Handle Draw Pass overthise leave
        if drawpass != c4d.DRAWPASS_HANDLES: return c4d.DRAWRESULT_SKIP

        # Retrieves parameter from the object
        rad = op[c4d.PY_TUBEOBJECT_RAD]
        iradx = op[c4d.PY_TUBEOBJECT_IRADX]
        irady = op[c4d.PY_TUBEOBJECT_IRADY]
        axis = op[c4d.PRIM_AXIS]

        # Retrives the handle overflown
        hitid = op.GetHighlightHandle(bd)

        # Defines the Drawing Matrix
        bd.SetMatrix_Matrix(op, bh.GetMg())

        # Loops over our handles count to draw them
        for i in xrange(self.HANDLECOUNT):
            # Defines the color of drawing according to the point overflown status
            color = c4d.GetViewColor(c4d.VIEWCOLOR_SELECTION_PREVIEW) if i == hitid else c4d.GetViewColor(c4d.VIEWCOLOR_ACTIVEPOINT)
            bd.SetPen(color)
            
            # Retrieves information of our handles and draw them
            info = c4d.HandleInfo()
            self.GetHandle(op, i, info)
            bd.DrawHandle(info.position, c4d.DRAWHANDLE_BIG, 0)

            # For the 3 first handle draws circle in top of their handle
            if i <= 2:
                # Defines the position and the size of the circle
                circleSettings = bh.GetMg()
                circleSettings.off = info.position
                circleSettings.v1 *= 20
                circleSettings.v2 *= 20
                circleSettings.v3 *= 20

                # Draw the Circle
                bd.DrawCircle(circleSettings)

            # For the 2 others, draws a spline text
            else:
                # Creates a spline Text into the memory
                spline = c4d.BaseObject(c4d.Osplinetext)
                if not spline: return c4d.DRAWRESULT_FAILURE
                
                # Defines some parameters (font size, text, and aligment)
                spline[c4d.PRIM_TEXT_HEIGHT] = 10
                spline[c4d.PRIM_TEXT_TEXT] = "text {0}".format(i)
                spline[c4d.PRIM_TEXT_ALIGN] = c4d.PRIM_TEXT_ALIGN_MIDDLE

                # Creates a Splines Help to retrieves the LineObject (SplineObject or Generator can't be draw, only LineObject, PointObject or PolygonObject)
                sh = c4d.utils.SplineHelp()
                if not sh.InitSplineWith(spline, c4d.SPLINEHELPFLAGS_RETAINLINEOBJECT): return c4d.DRAWRESULT_FAILURE
                
                # Retrieves the LineObject
                lineObj = sh.GetLineObject()
                if not lineObj: return c4d.DRAWRESULT_FAILURE
                
                # Quick hack, since in Python DrawPolygonObject does not expose the 'col' parameter used to defines
                # the color used to draw the object if the DRAWOBJECT_USE_CUSTOM_COLOR is passed;
                # Defines the color of the object that will be used in DrawPolygonObject.
                lineObj[c4d.ID_BASEOBJECT_COLOR] = color

                # Set the object to the location of our object and offset it with our Handle position
                mat = op.GetMg()
                mat.off += info.position + c4d.Vector(0, 2, 0)
                lineObj.SetMg(mat)

                # Draw Our polygon Object, take care DrawPolygonObject changes the drawing Matrix, do not forget to reset later
                bd.DrawPolygonObject(bh, lineObj, c4d.DRAWOBJECT_USE_OBJECT_COLOR)

            # Reset the draw Matrix in order to not screw up later drawing
            bd.SetMatrix_Matrix(op, bh.GetMg())

            # Draw lines beetwen handles
            if i is 0:
                info2 = c4d.HandleInfo()
                self.GetHandle(op, 1, info2)
                bd.DrawLine(info.position, info2.position, 0)
                self.GetHandle(op, 2, info2)
                bd.DrawLine(info.position, info2.position, 0)
            elif i is 3:
                bd.DrawLine(info.position, RoundedTube.SwapPoint(c4d.Vector(rad+iradx, irady, 0.0), axis), 0)
            elif i is 4:
                bd.DrawLine(info.position, RoundedTube.SwapPoint(c4d.Vector(rad+iradx, irady, 0.0), axis), 0)
        
        return c4d.DRAWRESULT_OK

Note for some performance reason I encourage you to cache the LineObject generated in order to avoid to creates it for each draw pass, since it's can really kill your FPS count.

Finally if you need some custom detection (aka be able to select the spline text) you should override ObjectData.DetectHandle in order to detect the mouse position and define the selection state then it will call your ObjectData.MoveHandle method where you should set the handle position, this will then call ObjectData.GetHandle.

If you have any question please let me know.
Cheers,
Maxime.

I love this man! 😂
Thanks, Maxime!
You explained everything in the most precise details despite the fact that you are not obligated to write the entire code for others.
I will take a look.
Thanks for writing it.

Have a good day buddy!

-Merk

Btw, what is the best way of caching the LineObject?
Also, you used SplineHelp() to retrieve line object from the spline. What is an alternative of SplineHelp() for primitive geometry objects? (e.g., platonic)

The best way would be to store directly the LineObject in a member variable of your ObjectData class.

The alternative way to retrieves a LineObject (which is never visible in the ObjectManager) is to call GetCache() from a Spline Object. But a Spline Object can have multiple LineObject and a SplineObject (like any other objects) does not guarantee that a cache is available at any time. So if there is no cache, the best way would have been to clone the SplineObject to a Temporary document, call ExecutePass from this document in order to build the cache. Then retrieves the cached LineObject. But I think SplineHelp, is the best option you have which works in all case, easy to setup/use, is there any drawback to it?

Cheers,
Maxime.