Trouble with GetAndCheckHierarchyClone



  • Hey folks,

    I could need your help on building a Python Generator version of the Chamfer Tool. It's based on the Py-Parametric tools build by Andreas back in days. There were very helpful for setting up the generator. But here comes the issue:
    Using GetAndCheckHierarchyClone does not work well with the MCOMMAND_CURRENTSTATETOOBJECT (at least this is what I think). When I test the plugin and use a Cloner with Splines under a Connect Object, it only affects the first clone. When I make the Connect Object editable, it works on all objects. See images.
    When I bypass the dirty check and add replace "clone" by "op" in line 63 in the "GetVirtualObjects" method, than it works, but very badly.

    I would appreciate any kind of help :)

    import os
    import c4d
    from c4d import plugins, utils, bitmaps
    
    ###############################################################################################################
    #Based on the Py-Parametric Tools by Andreas Block
    ###############################################################################################################
    
    class ModelingCommandGeneratorModifier(plugins.ObjectData):
        _toolId = -1
        _doSelection = False
        _selectId = -1
    
        def InitCommon(self, toolId, doSelection, selectId):
            self._toolId = toolId
            self._doSelection = doSelection
            self._selectId = selectId
    
        def ExecModelingCommand(self, doc, opCtrl, op, parent):
            if op is None:
                return
            
            splineObjects = []
            if op.GetDown().CheckType(c4d.Ospline) is False:
                splineObjects = utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                    list = [op.GetDown()],
                                    mode = c4d.MODELINGCOMMANDMODE_ALL,
                                    bc = c4d.BaseContainer(),
                                    doc = doc)
            else:
                splineObjects.append(op.GetDown())
    
            if not splineObjects:
                return
                
            if splineObjects[0].CheckType(c4d.Ospline) is True:
                res = utils.SendModelingCommand(command = self._toolId,
                                                list = splineObjects,
                                                mode = c4d.MODELINGCOMMANDMODE_ALL,
                                                bc = opCtrl.GetDataInstance(), # settings,
                                                doc = doc)
                if res is True:
                    splineObjects[0].InsertUnderLast(parent)
            
        def GetVirtualObjects(self, op, hh):
            doc = op.GetDocument()
            objInput = op.GetDown()
            
            if objInput is None:
                return None
            
            objRet = c4d.BaseObject(c4d.Onull)
            if doc is None or objRet is None:
                return None
            hierarchyClone = op.GetAndCheckHierarchyClone(hh, objInput, c4d.HIERARCHYCLONEFLAGS_NONE, True)
            if hierarchyClone["dirty"] is False:
                return hierarchyClone["clone"]
            clone = hierarchyClone["clone"]
            if clone is None:
                return objRet
            
            #When I replace "clone" to "op" and bypass the dirty checks, than it works but very sluggish
            self.ExecModelingCommand(doc, op, clone, objRet)
            return objRet
    
    class ChamferGen(ModelingCommandGeneratorModifier):
        def Init(self, op):
            ModelingCommandGeneratorModifier.InitCommon(self, c4d.ID_MODELING_SPLINE_CHAMFER_TOOL, False, -1)
            InitChamferDesc(self, op)
            return True
    
    def InitChamferDesc(inst, op):
        inst.InitAttr(op, bool, [c4d.MDATA_SPLINE_CHAMFERFLAT])
        inst.InitAttr(op, float, [c4d.MDATA_SPLINE_CHAMFERRADIUS])
        
        op[c4d.MDATA_SPLINE_CHAMFERRADIUS] = 5.0
    
    
    ###############################################################################################################
    # Plugin Registration
    ###############################################################################################################
    
    PLUGIN_ID_GENERATOR = 9036026
    
    
    
    def RegisterObjectData(id, name, bmpPath, objData, desc, flags):
        bmp = bitmaps.BaseBitmap()
        bmp.InitWith(os.path.join(bmpPath, "res", "icon.tif"))
        plugins.RegisterObjectPlugin(id=id, str=name,
                                    g=objData,
                                    description=desc, icon=bmp,
                                    info=flags)
    
    if __name__ == "__main__":
        path, fn = os.path.split(__file__)
    
        RegisterObjectData(PLUGIN_ID_GENERATOR, "ChamferGen", path, ChamferGen, "ochamfergen", c4d.OBJECT_GENERATOR | c4d.OBJECT_INPUT)
    
        print "ChamferGen 1.0 successfully initialized"
    
    

    Screenshot 2020-02-13 at 18.43.51.png
    Screenshot 2020-02-13 at 18.44.03.png



  • hello,
    please use our forum functionalities :)

    as said in the documentation, Current State to Object should be use on a clone. And using a temporary document can help.

    If you were not using a cloner, you could use HIERARCHYCLONEFLAGS_ASSPLINE. But with a cloner, the objects are returned as lineObject. And the chamfer only accept SplineObject (it checks for Ospline type).

    To avoid to run through the hierarchy, i've used a Join command and using the chamfer on the result.

        def GetVirtualObjects(self, op, hh):
        
            objRet = c4d.BaseObject(c4d.Onull)
            doc = op.GetDocument()
            if doc is None:
                return objRet
            objInput = op.GetDown()
            
            if objInput is None:
                return objRet
    
            # If we set the flags to HIERARCHYCLONEFLAGS_ASSPLINE the result will be a lineObject and not a SplineObject. This will not pass the Chamfer tool.
            hierarchyClone = op.GetAndCheckHierarchyClone(hh, objInput, c4d.HIERARCHYCLONEFLAGS_NONE , True)
            if hierarchyClone["dirty"] is False:
                return hierarchyClone["clone"]
    
            # Retrieves the cloner
            cloner = op.GetDown()
            # Creates a temporary document.
            tempDoc = c4d.documents.BaseDocument()
            # Use a clone of the cloner so we can send it to a temporary document.
            cloned = cloner.GetClone(c4d.COPYFLAGS_NONE)
            # Insert the cloned object to the temporary document.
            tempDoc.InsertObject(cloned)
            # Retrieves the currect states.
            result = utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                    list = [cloned]
                                    , doc= tempDoc)
    
            spline = None
            if result:
                spline = result[0].GetDown()
            else:
                return objRet
            # Use the join command so we only have one spline object. (that help in case of a hierarchy)
            result = utils.SendModelingCommand(command = c4d.MCOMMAND_JOIN,
                                    list = result
                                    , doc= tempDoc)
    
            # Champher the spline
            if result[0].IsInstanceOf(c4d.Ospline):
                bc = c4d.BaseContainer()
                bc[c4d.MDATA_SPLINE_CHAMFERFLAT] = True
                bc[c4d.MDATA_SPLINE_CHAMFERRADIUS] = 19.0
                
                res = utils.SendModelingCommand(command = c4d.ID_MODELING_SPLINE_CHAMFER_TOOL,
                                        list = result,
                                        bc = bc)
    
            if res:
                return result[0]
            
            return objRet
    

    Cheers,
    Manuel.



  • hello,
    please use our forum functionalities :)

    as said in the documentation, Current State to Object should be use on a clone. And using a temporary document can help.

    If you were not using a cloner, you could use HIERARCHYCLONEFLAGS_ASSPLINE. But with a cloner, the objects are returned as lineObject. And the chamfer only accept SplineObject (it checks for Ospline type).

    To avoid to run through the hierarchy, i've used a Join command and using the chamfer on the result.

        def GetVirtualObjects(self, op, hh):
        
            objRet = c4d.BaseObject(c4d.Onull)
            doc = op.GetDocument()
            if doc is None:
                return objRet
            objInput = op.GetDown()
            
            if objInput is None:
                return objRet
    
            # If we set the flags to HIERARCHYCLONEFLAGS_ASSPLINE the result will be a lineObject and not a SplineObject. This will not pass the Chamfer tool.
            hierarchyClone = op.GetAndCheckHierarchyClone(hh, objInput, c4d.HIERARCHYCLONEFLAGS_NONE , True)
            if hierarchyClone["dirty"] is False:
                return hierarchyClone["clone"]
    
            # Retrieves the cloner
            cloner = op.GetDown()
            # Creates a temporary document.
            tempDoc = c4d.documents.BaseDocument()
            # Use a clone of the cloner so we can send it to a temporary document.
            cloned = cloner.GetClone(c4d.COPYFLAGS_NONE)
            # Insert the cloned object to the temporary document.
            tempDoc.InsertObject(cloned)
            # Retrieves the currect states.
            result = utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                    list = [cloned]
                                    , doc= tempDoc)
    
            spline = None
            if result:
                spline = result[0].GetDown()
            else:
                return objRet
            # Use the join command so we only have one spline object. (that help in case of a hierarchy)
            result = utils.SendModelingCommand(command = c4d.MCOMMAND_JOIN,
                                    list = result
                                    , doc= tempDoc)
    
            # Champher the spline
            if result[0].IsInstanceOf(c4d.Ospline):
                bc = c4d.BaseContainer()
                bc[c4d.MDATA_SPLINE_CHAMFERFLAT] = True
                bc[c4d.MDATA_SPLINE_CHAMFERRADIUS] = 19.0
                
                res = utils.SendModelingCommand(command = c4d.ID_MODELING_SPLINE_CHAMFER_TOOL,
                                        list = result,
                                        bc = bc)
    
            if res:
                return result[0]
            
            return objRet
    

    Cheers,
    Manuel.



  • @m_magalhaes

    Thank you for your help! It works. I really like the tip of the temporary document. Does it make sense to use c4d.documents.KillDocument(doc) after the process is complete to free up resources?



  • I also have another question. How should I change the setup to support multiple children? (see image) I tried to insert multiple objects in the tempDoc via a loop but nothing really worked.

    Screenshot 2020-02-16 at 15.32.47.png



  • hello,

    you can retrieve the hierarchy clone "asis" with

     hierarchyClone = op.GetAndCheckHierarchyClone(hh, objInput, c4d.HIERARCHYCLONEFLAGS_ASIS , True)
    

    Than use Currentstate to object and join the result as with the cloner.

    in my previous code you can just change this line to see the result.

     cloner = hierarchyClone["clone"]
    

    The problem is that if you have a polygon object, the join command will return a polygon object and a spline.
    One workaround would be to run through the hierarchy and remove the polygon objects.

    Just an idea, have you tried a deformer instead of a generator ? (just like our bevel deformer for polygon object)

    Cheers,
    Manuel



  • Hey Manuel,

    I tried this method but I encountered another issue. The copy to another document does not work in a setup with a Mograph Effector. Therefore, I am inserting the object to the original document and try to remove the original. But that does not work. When I make ChamferGen editable, it creates the original Connect+Children objects as well as the editable Spline Object. I tried stuff like cloner.Remove() but that did not work. The setup is linked below.

    Screenshot 2020-02-18 at 19.00.21.png



  • hello,

    funny i've learned something (once again)

    I told that setting the flag to HIERARCHYCLONEFLAGS_ASSPLINE will return a LineObject and the chamfer tool want SplineObject.
    The fun fact is that the join command will transform that line object to a SplineObject.

    So no need to use the CSTO to a temp document. Just use the Join command on the result of the GetAndCheckHierarchyClone

    def GetVirtualObjects(self, op, hh):
        
            objRet = c4d.BaseObject(c4d.Onull)
            doc = op.GetDocument()
            if doc is None:
                return objRet
            objInput = op.GetDown()
            
            if objInput is None:
                return objRet
    
            # If we set the flags to HIERARCHYCLONEFLAGS_ASSPLINE the result will be a lineObject and not a SplineObject. This will not pass the Chamfer tool.
            hierarchyClone = op.GetAndCheckHierarchyClone(hh, objInput, c4d.HIERARCHYCLONEFLAGS_ASSPLINE , True)
            if hierarchyClone["dirty"] is False:
                print "return cache"
                return hierarchyClone["clone"]
    
            
            spline = hierarchyClone["clone"]
            
            # Use the join command so we only have one spline object. (that help in case of a hierarchy)
            result = utils.SendModelingCommand(command = c4d.MCOMMAND_JOIN,
                                    list = [spline],
                                    doc= doc)
    
            res = None
            # Champher the spline
            if result[0].IsInstanceOf(c4d.Ospline):
                bc = c4d.BaseContainer()
                bc[c4d.MDATA_SPLINE_CHAMFERFLAT] = True
                bc[c4d.MDATA_SPLINE_CHAMFERRADIUS] = 19.0
                
                res = utils.SendModelingCommand(command = c4d.ID_MODELING_SPLINE_CHAMFER_TOOL,
                                        list = result,
                                        bc = bc)
            print res
            if res:
                return result[0]
            
            return objRet
    

    Cheers,
    Manuel



  • Amazing! Thank you Manuel! It works with multiple objects, Cloners etc. Without your help I would not make it ;)


Log in to reply