SOLVED Spline modeling commands via OBJECT_MODIFIER (Python)

Hello PluginCafe 🙂

I'm trying to affect Spline Objects via OBJECT_MODIFIER plugin but it has no influence when the modeling commands are taking place.

Let's assume I want to select an arbitrary number of points and delete them by utilizing MCOMMAND_DELETE

class Oresplines(plugins.ObjectData):
    
    def ModifyObject(self, mod, doc, op, op_mg, mod_mg, lod, flags, thread):

        op.GetPointS().SelectAll(5)

        res = c4d.utils.SendModelingCommand(command  = c4d.MCOMMAND_DELETE,
                                            list     = [op],
                                            mode     = 1,
                                            doc      = doc,
                                            flags    = 0)

        return True

When I'm making the plugin a child of some polygonal object, it works as expected.

alt text

But if I attempt to make it a child of the desired spline, nothing changes.

alt text

Let me know if I'm doing something wrong.

Hi @merkvilson, to be honest, I completely overlooked the problem and thought op within ModifyObject would be a splineObject.
To clarify MCOMMAND_DELETE do not work on LineObject (which are the object for spline cache).
So in order to do so here a hacky solution.
Use the actual spline object from the scene, and then Apply the delete into this SplineObject (since there is no way to get a SplineObject from a LineObject).

class Oresplines(plugins.ObjectData):

    # Get obj and all its Children into a list 
    def GetChildren(self, obj, next, modifiedObjs=None):
        if modifiedObjs is None:
            modifiedObjs = list()

        while obj and obj != next:
            modifiedObjs.append(obj)
            self.GetChildren(obj.GetDown(), next, modifiedObjs)
            obj = obj.GetNext()
        return modifiedObjs

    # Compare 2 line object and tell if it's the same one (based on the topology + space position of the object)
    def compareOline(self, a, b):
        if type(a) != type(b): return False
        if not a.CheckType(c4d.Oline): return False
        
        # Do different pass of checks from speedier to slower in order to leave as soon as possible if object does not match.

        # Fast check, check for pt count
        if a.GetPointCount() != b.GetPointCount():
            return False

        # Fast check
        if a.GetMg() != b.GetMg():
            return False

        # slower check
        if a.GetAllPoints() != b.GetAllPoints():
            return False

        tagA = a.GetTag(c4d.Tline)
        tagB = b.GetTag(c4d.Tline)
        if not tagA or not tagB:
            return False

        # Even slower
        if tagA.GetAllHighlevelData() != tagB.GetAllHighlevelData():
            return False

        return True

    def ModifyObject(self, mod, doc, op, op_mg, mod_mg, lod, flags, thread):
        # Handle polygons
        if not op.IsInstanceOf(c4d.Oline):
            op.GetPointS().SelectAll(5)
            res = c4d.utils.SendModelingCommand(command  = c4d.MCOMMAND_DELETE,
                                                list     = [op],
                                                mode     = 1,
                                                doc      = doc,
                                                flags    = 0)
            return True

        # First step is to get all objects modified by our modifier.
        # Modifier works only if they are within another object. And all children of this hierarchy are modified.
        parent = mod.GetUp()
        isInGroup = bool(parent)

        # Should never happen if we are not in a group, nothing is modified, so ModifyObject is never called.
        if not isInGroup: return True
        
        # Get all children of the parent (aka all objects modified by our modifier).
        modifiedObjs = self.GetChildren(parent, mod.GetNext())

        # Iterate over all modified objects, and check which LineObject is currently modified in the curent ModifyObject call.
        spline = None
        for obj in modifiedObjs:
            cache = obj.GetCache()
            if not cache:
                continue
            
            # Comparison of Oline might fail, C4D do not guarantee the execution order of ModifyObject.
            # So if we get A and B identical e.g. 2 c4d.Osplinecircle with same parameters,
            # ModifyObject may currently edit B before A, while the compare will return True to the first object matching the condition
            # So it may return true on A while B was modified. But in the end, it does not really matter since both will be modified just order might get swapped.
            if self.compareOline(cache, op):
                spline = obj
                break

        # Check if our modified object is a spline and nothing else
        if not spline or not spline.GetInfo()&c4d.OBJECT_ISSPLINE:
            return True

        # Then we get a copy of the SplineObject located in the scene
        splineObj = spline.GetRealSpline().GetClone()
        if not splineObj: return True

        # Create a tempo document for our delete operation
        workDoc = c4d.documents.BaseDocument()
        workDoc.InsertObject(splineObj)

        # Select and delet our points
        splineObj.GetPointS().SelectAll(1)
        res = c4d.utils.SendModelingCommand(command  = c4d.MCOMMAND_DELETE,
                                            list     = [splineObj],
                                            mode     = c4d.MODELINGCOMMANDMODE_POINTSELECTION,
                                            doc      = workDoc,
                                            flags    = 0)

        if not res:
            return True

        # Build the cache, in order to generate a Oline object from the resulting SplineObject
        workDoc.ExecutePasses(thread, False, False, True, c4d.BUILDFLAGS_INTERNALRENDERER)

        # Get the Cache (Oline object) of our modified splineObject
        oLineModified = splineObj.GetCache()
        if not oLineModified: return True

        # Copy our modified LineObject to the op LineObject
        oLineModified.CopyTo(op, c4d.COPYFLAGS_NONE)

        return True

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

Hi @merkvilson, just a quick reminder I turned on your topic as a Question, Please See Q&A Functionnality.

Regarding your issue, C4D does not let you modify a Spline generator. As you may know, all spline generator hold a c4d.LineObject in their cache. And MCOMMAND_DELETE does fail on a LineObject, it only supports PolygonObject or SplineObject.
So you first need to convert your spline generator as a SplineObject.

import c4d

def MakeEditableClone(obj):
	# We clone the obj, since MakeEditable, modify the passed object and we don't want that.
    oDoc = obj.GetClone()
    doc = c4d.documents.BaseDocument()
    doc.InsertObject(oDoc)
    
    res = c4d.utils.SendModelingCommand(
        command = c4d.MCOMMAND_MAKEEDITABLE,
        list = [obj],
        mode = c4d.MODELINGCOMMANDMODE_ALL,
        doc = doc)
    
    if not res:
        return False
    
    return res[0]

def Delete(obj):
    oDoc = obj.GetClone()
    if oDoc.GetDocument() is None:
        doc.InsertObject(oDoc)

    oDoc.GetPointS().SelectAll(1)
    
    res = c4d.utils.SendModelingCommand(command  = c4d.MCOMMAND_DELETE,
                                            list     = [oDoc],
                                            mode     = 1,
                                            doc      = doc,
                                            flags    = 0)
    
    if res == True:
        retObj = oDoc.GetClone()
        oDoc.Remove()
        return retObj
    
    return False

def main():
    splineObj = MakeEditableClone(op)
    if not splineObj: return
    
    finalSpline = Delete(splineObj)
    if not finalSpline: return
    
    doc.InsertObject(finalSpline)
    
    c4d.EventAdd()

# Execute main()
if __name__=='__main__':
    main()

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

Hello Maxime 🙂
Indeed, I know how to perform this operation in CommandData/Script but my question was about Object Modifier plugins (aka deformers).

Hi @merkvilson, to be honest, I completely overlooked the problem and thought op within ModifyObject would be a splineObject.
To clarify MCOMMAND_DELETE do not work on LineObject (which are the object for spline cache).
So in order to do so here a hacky solution.
Use the actual spline object from the scene, and then Apply the delete into this SplineObject (since there is no way to get a SplineObject from a LineObject).

class Oresplines(plugins.ObjectData):

    # Get obj and all its Children into a list 
    def GetChildren(self, obj, next, modifiedObjs=None):
        if modifiedObjs is None:
            modifiedObjs = list()

        while obj and obj != next:
            modifiedObjs.append(obj)
            self.GetChildren(obj.GetDown(), next, modifiedObjs)
            obj = obj.GetNext()
        return modifiedObjs

    # Compare 2 line object and tell if it's the same one (based on the topology + space position of the object)
    def compareOline(self, a, b):
        if type(a) != type(b): return False
        if not a.CheckType(c4d.Oline): return False
        
        # Do different pass of checks from speedier to slower in order to leave as soon as possible if object does not match.

        # Fast check, check for pt count
        if a.GetPointCount() != b.GetPointCount():
            return False

        # Fast check
        if a.GetMg() != b.GetMg():
            return False

        # slower check
        if a.GetAllPoints() != b.GetAllPoints():
            return False

        tagA = a.GetTag(c4d.Tline)
        tagB = b.GetTag(c4d.Tline)
        if not tagA or not tagB:
            return False

        # Even slower
        if tagA.GetAllHighlevelData() != tagB.GetAllHighlevelData():
            return False

        return True

    def ModifyObject(self, mod, doc, op, op_mg, mod_mg, lod, flags, thread):
        # Handle polygons
        if not op.IsInstanceOf(c4d.Oline):
            op.GetPointS().SelectAll(5)
            res = c4d.utils.SendModelingCommand(command  = c4d.MCOMMAND_DELETE,
                                                list     = [op],
                                                mode     = 1,
                                                doc      = doc,
                                                flags    = 0)
            return True

        # First step is to get all objects modified by our modifier.
        # Modifier works only if they are within another object. And all children of this hierarchy are modified.
        parent = mod.GetUp()
        isInGroup = bool(parent)

        # Should never happen if we are not in a group, nothing is modified, so ModifyObject is never called.
        if not isInGroup: return True
        
        # Get all children of the parent (aka all objects modified by our modifier).
        modifiedObjs = self.GetChildren(parent, mod.GetNext())

        # Iterate over all modified objects, and check which LineObject is currently modified in the curent ModifyObject call.
        spline = None
        for obj in modifiedObjs:
            cache = obj.GetCache()
            if not cache:
                continue
            
            # Comparison of Oline might fail, C4D do not guarantee the execution order of ModifyObject.
            # So if we get A and B identical e.g. 2 c4d.Osplinecircle with same parameters,
            # ModifyObject may currently edit B before A, while the compare will return True to the first object matching the condition
            # So it may return true on A while B was modified. But in the end, it does not really matter since both will be modified just order might get swapped.
            if self.compareOline(cache, op):
                spline = obj
                break

        # Check if our modified object is a spline and nothing else
        if not spline or not spline.GetInfo()&c4d.OBJECT_ISSPLINE:
            return True

        # Then we get a copy of the SplineObject located in the scene
        splineObj = spline.GetRealSpline().GetClone()
        if not splineObj: return True

        # Create a tempo document for our delete operation
        workDoc = c4d.documents.BaseDocument()
        workDoc.InsertObject(splineObj)

        # Select and delet our points
        splineObj.GetPointS().SelectAll(1)
        res = c4d.utils.SendModelingCommand(command  = c4d.MCOMMAND_DELETE,
                                            list     = [splineObj],
                                            mode     = c4d.MODELINGCOMMANDMODE_POINTSELECTION,
                                            doc      = workDoc,
                                            flags    = 0)

        if not res:
            return True

        # Build the cache, in order to generate a Oline object from the resulting SplineObject
        workDoc.ExecutePasses(thread, False, False, True, c4d.BUILDFLAGS_INTERNALRENDERER)

        # Get the Cache (Oline object) of our modified splineObject
        oLineModified = splineObj.GetCache()
        if not oLineModified: return True

        # Copy our modified LineObject to the op LineObject
        oLineModified.CopyTo(op, c4d.COPYFLAGS_NONE)

        return True

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

@m_adam said in Spline modeling commands via OBJECT_MODIFIER (Python):

Maxime

Thank you, Maxime for your explicit and informative answer! ♥
I'll take a look into it.