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

MAXON SDK Specialist

MAXON Registered Developer

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.

MAXON SDK Specialist

MAXON Registered Developer

@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

MAXON SDK Specialist

MAXON Registered Developer

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

MAXON SDK Specialist

MAXON Registered Developer

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