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, 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.


Log in to reply