Unsolved Can't snap to Generator output spline

Hi guys!
I'm trying to create spline outline generator. All is going good, but the resulted spline is invisible for Snapping tool. Even after placing the result directly under the generator (InsertUnder(op)) . It is only working if I'll select the result spline.
Is it something connected to generator cache?

Hello @Madara ,

Welcome to the Plugin Café forum and the Cinema 4D development community, it is great to have you with us!

Getting Started

Before creating your next postings, we would recommend making yourself accustomed with our Forum and Support Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:

About your First Question

Without your code we cannot do much here, I would recommend having a look at our forum guidelines (as explained above). If I had to take a guess, I would say you are probably overwriting ObjectData.GetVirtualObjects instead of .GetContour or you are missing the OBJECT_ISSPLINE flag when calling RegisterObjectPlugin, causing either your object not being recognized as a spline or having a malformed cache.

When you implement a spline using GetVirtualObjects, the cache of that object will be a SplineObject, or in other words the object itself won't be a spline. When you implement it via .GetContour, the cache will be a LineObject (which is the correct cache node type for a spline).

If you got a bit confused by this, you could easily test this in the console by calling GetCache() on an instance of your plugin. In the example below I compare the cache of a Python generator object which returns a SplineObject (which would be an example for such faux spline implemented using .GetVirtualObjects) and a SplineObject. Although they might look identical in the viewport, they are not the same under the hood.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

Hi @ferdinand !

Thank you for your response. It has to be the issue then. Is it possible to convert SplineObject to LineObject somehow?
Here's my code so far:

def main():
    parent = c4d.BaseObject(c4d.Onull)
    distance= op[c4d.ID_USERDATA,10]
    custom_distance=op[c4d.ID_USERDATA,1]
    link=op[c4d.ID_USERDATA,5]
    spline_color=op[c4d.ID_USERDATA,9]
    outline_only=op[c4d.ID_USERDATA,2]
    hide_input=op[c4d.ID_USERDATA,3]
    outline_color=op[c4d.ID_USERDATA,8]
    final_distance=5
    if distance == 0 :
        final_distance=5
    elif distance == 1 :
        final_distance=8
    elif distance==2:
        final_distance=custom_distance
    link_clone = link.GetClone()

    connect_obj = c4d.BaseObject(c4d.Oconnector)
    link_clone.InsertUnder(connect_obj)

    if link_clone: #Parametric object
        pobj   = u.SendModelingCommand(
            command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
            list    = [connect_obj],
            mode    = c4d.MODELINGCOMMANDMODE_ALL,
            doc     = op.GetMain())
        connect_obj = pobj[0]

    if outline_only:
        bc = c4d.BaseContainer()
        settings = c4d.BaseContainer()  # Settings

        settings[c4d.MDATA_SPLINE_OUTLINE] = final_distance      # Distance
        settings[c4d.MDATA_SPLINE_OUTLINESEPARATE] = True #Crée un nouvel objet


        offspline = u.SendModelingCommand(
            c4d.MCOMMAND_SPLINE_CREATEOUTLINE,
            [connect_obj],
            c4d.MODELINGCOMMANDMODE_ALL,
            bc=settings,
            doc=doc)

        if offspline :

            offspline[0].InsertUnder(parent)
            offspline[0][c4d.ID_BASEOBJECT_USECOLOR] = 2  # Use Object ColorSpline
            offspline[0][c4d.ID_BASELIST_ICON_COLORIZE_MODE]=2
            offspline[0][c4d.ID_BASEOBJECT_COLOR] = outline_color  # Red Color
            # Convert the spline to a line (edges) using a Modeling Command
        
        return offspline[0]
    if not outline_only:
        #settings[c4d.MDATA_SPLINE_OUTLINESEPARATE] = True #Crée un nouvel objet
        #connect_obj = c4d.BaseObject(c4d.Oconnector)
        bc = c4d.BaseContainer()
        bc.SetData(c4d.MDATA_SPLINE_OUTLINE, final_distance)
        offspline = u.SendModelingCommand(
            c4d.MCOMMAND_SPLINE_CREATEOUTLINE,
            [connect_obj],
            c4d.MODELINGCOMMANDMODE_ALL,
            bc)


        return connect_obj.GetClone()

I'm inputting a null with multiple splines as children, then I put it under Connect object, simplify it, run outline command and get the result.

Hey @Madara,

in short, a Python generator object is not meant to generate splines. You can collapse the cache of your constructed spline and return its LineObject cache instead of the spline itself. For an example, see [1]. In your case that would be connect_obj. This will work in the sense that the generator cache will be then like a SplineObject cache and when you 'Current State to Object' your generator, it will behave like a spline and give you the interpolated editable linear spline and not a spline generator.

However, spline snapping not only inspects caches, but also checks if OBJECT_ISSPLINE is true. So, you cannot bamboozle it by faking a SplineObject cache. You will have to implement a proper spline plugin for what you want to do, as you can overwrite there GetContour

Cheers,
Ferdinand

PS: Your code contains multiple elements that can crash Cinema 4D and/or corrupt the loaded document. Specifically, these sections:


    if link_clone: #Parametric object
        pobj   = u.SendModelingCommand(
            command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
            list    = [connect_obj],
            mode    = c4d.MODELINGCOMMANDMODE_ALL,
            doc     = op.GetMain())
        connect_obj = pobj[0]

...

        offspline = u.SendModelingCommand(
            c4d.MCOMMAND_SPLINE_CREATEOUTLINE,
            [connect_obj],
            c4d.MODELINGCOMMANDMODE_ALL,
            bc=settings,
            doc=doc)

A Python generator's main() function is subject to the threading restrictions of Cinema 4D because it is just a wrapper for ObjectData.GetVirtualObjects. Executing SendModelingCommand in a threaded context is fine, but you cannot do it on the active document (both doc and op.GetDocument() are the active document). This is here even worsened by the fact that that the objects in list are not actually part of the document you pass as doc.

To circumvent that you must create your own document just as I have in [1] with temp. Find a more detailed example of that technique in smc_extrude_s26.py.


[1]

import c4d

op: c4d.BaseObject # The Python generator object.

def main() -> c4d.BaseObject:
    """
    """
    # A user data Boolean to toggle collapsing the cache of this generator.
    collapse: bool  = op[c4d.ID_USERDATA, 1]
    spline: c4d.BaseObject = c4d.BaseObject(c4d.Osplinecircle)
    if not spline:
        return c4d.BaseObject(c4d.Onull)
    
    # Just return the circle spline itself when #collapse is false.
    if not collapse:
        return spline
    
    # Otherwise grab the LineObject cache of the spline and return a copy of it.
    temp: c4d.documents.BaseDocument = c4d.documents.BaseDocument()
    temp.InsertObject(spline)

    temp.ExecutePasses(c4d.threading.GeGetCurrentThread(), False, False, True, c4d.BUILDFLAGS_NONE)
    cache: c4d.LineObject | None = spline.GetCache()

    if not isinstance(cache, c4d.LineObject):
        return c4d.BaseObject(c4d.Onull)
    
    return cache.GetClone(c4d.COPYFLAGS_NONE)

MAXON SDK Specialist
developers.maxon.net