Dynamic automated handle interface



  • Hi,
    I want to create automated handle interface to controlling the size (width, height and depth) of an object inside the hierarchy of plugin object cache, this child object can be rotated separately and to adapt the HandleInfo.direction to this object depend on his rotation I made the following function "axis(self, op)". The function works, but I'm searching the better way to do the same thing. also to adapt this function for an unlimited rotation.

    def axis(self, op):
        pitch   = int(math.degrees(op[c4d.MY_OBJECT_PITCH]))
        rot     = int(math.degrees(op[c4d.MY_OBJECT_ROTATION]))
        
        if rot in range(-90, -45) or rot in range(225, 270):             
            if pitch in range(-45, 45):
                axis = 0
            elif pitch in range(-225, -135) or pitch in range(135, 225):
                axis = 1
            elif pitch in range(-135, -45):
                axis = 3 
            elif pitch in range(45, 135) or pitch in range(-270, -224):
                axis = 2
            else:
                axis = 0
        elif rot in range(-45, 45):
            if pitch in range(-45, 45):
                axis = 4
            elif pitch in range(-225, -135) or pitch in range(135, 225):
                axis = 5
            elif pitch in range(-135, -45):
                axis = 13  
            elif pitch in range(45, 135) or pitch in range(-270, -225):
                axis = 12
            else:
                axis = 4
        elif rot in range(45, 135):
            if pitch in range(-45, 45):
                axis = 11
            elif pitch in range(-225, -135) or pitch in range(135, 225):
                axis = 10
            elif pitch in range(-135, -45):
                axis = 23 
            elif pitch in range(45, 135) or pitch in range(-270, -225):
                axis = 22
            else:
                axis = 1
        elif rot in range(135, 225):
            if pitch in range(-45, 45):
                axis = 15
            elif pitch in range(-225, -135) or pitch in range(135, 225):
                axis = 14 
            elif pitch in range(-135, -45):
                axis = 33  
            elif pitch in range(45, 135) or pitch in range(-270, -225):
                axis = 32
            else:
                axis = 5
        else:
            axis = 0
    
        return axis
    
    def SwapPoint(self, op, p, axis):
        if axis == 0 :
            return c4d.Vector(-p.z, p.y, p.x)
        elif axis == 1 :
            return c4d.Vector(p.z, -p.y, p.x)
        ...
        return p
    def GetHandle(self, op, handle_index, info):
        axis = self.axis(op)
    
        info.type = c4d.HANDLECONSTRAINTTYPE_LINEAR;
        info.direction = self.SwapPoint(op, info.direction, axis)
    

    Thanks.



  • hi,

    I've created this example :
    The first thing to do is to build an object without handle. Just create your parameters, and use them.
    After that you can implement the handle that will changes those parameters. (and changing the parameter will change the handle).

    I have a Float that will server me to store the rotation of the body, only on the Y axis, that's why I only need one float.
    For the head, i'll store that in a vector.

    The same idea must be done for scaling.

    
    import math
    import c4d
    
    # Be sure to use a unique ID obtained from www.plugincafe.com
    PLUGIN_ID = 1000001
    
    OMINECHARAC_HEAD = 10000
    OMINECHARAC_BODY = 10001
    
    BODY_HEIGHT = 500
    HEAD_HEIGHT = 100
    
    
    class MineCharac(c4d.plugins.ObjectData):
        """CircleObject Generator"""
    
        def Init(self, node):
            """
            Called when Cinema 4D Initialize the ObjectData (used to define, default values)
            :param node: The instance of the ObjectData.
            :type node: c4d.GeListNode
            :return: True on success, otherwise False.
            """
            # Retrieves the BaseContainer Instance to set the default values
            data = node.GetDataInstance()
            if data is None:
                return False
    
            # Defines default values in the BaseContainer
            data.SetVector(OMINECHARAC_HEAD, c4d.Vector(0))
            data.SetFloat(OMINECHARAC_BODY, 0.0)
    
            return True
    
        """========== Begin of Handle Management =========="""
    
        def GetHandleCount(self, op):
            """
            Called by Cinema 4D to retrieve the count of Handle the object will have.
            :param op: The instance of the ObjectData.
            :type op: c4d.BaseObject
            :return: The number of handle
            :rtype: int
            """
            # One handle will be used for this object
            return 2
    
        def GetHandle(self, op, i, info):
            """
            Called by Cinema 4D to retrieve the information of a given handle ID to represent a/some parameter(s).
            :param op: The instance of the ObjectData.
            :type op: c4d.BaseObject
            :param i: The handle index.
            :type i: int
            :param info: The HandleInfo to fill with data.
            :type info: c4d.HandleInfo
            """
            # Retrieves the current BaseContainer
            data = op.GetDataInstance()
            if data is None:
                return
    
            # Defines the position, direction and type of the handle
            radius = 250
            # ID 0 will be head
            if i == 0:
                info.center = c4d.Vector(0, 600, 0)
                position =  data.GetVector(OMINECHARAC_HEAD)
                
                mg = c4d.utils.HPBToMatrix(position)
    
                info.position = info.center + ( mg.v3 * 100)
                info.direction = data.GetVector(OMINECHARAC_HEAD) 
                info.type = c4d.HANDLECONSTRAINTTYPE_SPHERICAL
    
            # ID 1 will be body         
            if i == 1:
                sn, cs = c4d.utils.SinCos(data.GetFloat(OMINECHARAC_BODY))
                info.position = c4d.Vector(-sn * radius, 0, cs * radius)
                info.direction = c4d.Vector(0, 1, 0)
                info.center = c4d.Vector(0, 0, 0)
                info.type = c4d.HANDLECONSTRAINTTYPE_PLANAR
            
    
        def SetHandle(self, op, i, p, info):
            """
            Called by Cinema 4D when the user set the handle.
            This is the place to retrieve the information of a given handle ID and drive your parameter(s).
            :param op: The instance of the ObjectData.
            :type op: c4d.BaseObject
            :param i: The handle index.
            :type i: int
            :param p: The new Handle Position.
            :type p: c4d.Vector
            :param info: The HandleInfo filled with data.
            :type info: c4d.HandleInfo
            """
            data = op.GetDataInstance()
            if data is None:
                return
    
            if i == 0:
                hpb = c4d.utils.VectorToHPB(p - info.center)
                data.SetVector(c4d.OMINECHARAC_HEAD, hpb)
            if i == 1:
                hpb = c4d.utils.VectorToHPB(p)
                value = hpb.x
                data.SetFloat(OMINECHARAC_BODY, value)
    
        def Draw(self, op, drawpass, bd, bh):
            """
            Called by Cinema 4d when the display is updated to display some visual element of your object in the 3D view.
            This is also the place to draw Handle
            :param op: The instance of the ObjectData.
            :type op: c4d.BaseObject
            :param drawpass:
            :param bd: The editor's view.
            :type bd: c4d.BaseDraw
            :param bh: The BaseDrawHelp editor's view.
            :type bh: c4d.plugins.BaseDrawHelp
            :return: The result of the drawing (most likely c4d.DRAWRESULT_OK)
            """
            # If the current draw pass is not the handle, skip this Draw Call.
            if drawpass != c4d.DRAWPASS_HANDLES:
                return c4d.DRAWRESULT_SKIP
    
            # Defines the drawing matrix to the object matrix.
            m = bh.GetMg()
            bd.SetMatrix_Matrix(op, m)
    
            # Checks if one of the handle of the current object is currently hovered by the mouse.
            hitId = op.GetHighlightHandle(bd)
    
            # Defines the color of the handle according of the hovered state of the object.
            hoverColor = c4d.VIEWCOLOR_ACTIVEPOINT if hitId != 0 else c4d.VIEWCOLOR_SELECTION_PREVIEW
            bd.SetPen(c4d.GetViewColor(hoverColor))
    
            # Retrieves the information of the current handle.
            infoHead = c4d.HandleInfo()
            infoBody = c4d.HandleInfo()
    
            self.GetHandle(op, 0, infoHead)
            self.GetHandle(op, 1, infoBody)
    
            # Draw the handle to the correct position
            bd.DrawHandle(infoHead.position, c4d.DRAWHANDLE_BIG, 0)
            bd.SetPen(c4d.GetViewColor( c4d.VIEWCOLOR_ACTIVEPOINT))
            bd.DrawLine(infoHead.position, infoHead.center, 0)
    
    
            self.GetHandle(op, 1, infoBody)
            bd.DrawHandle(infoBody.position, c4d.DRAWHANDLE_BIG, 0)
            bd.SetPen(c4d.GetViewColor( c4d.VIEWCOLOR_ACTIVEPOINT))
            bd.DrawLine(infoBody.position, infoBody.center, 0)
    
            return c4d.DRAWRESULT_OK
    
        """========== End of Handle Management =========="""
    
    
        def GetVirtualObjects(self, op, hh):
    
            dirty = op.CheckCache(hh) or op.IsDirty(c4d.DIRTY_DATA)
            if dirty is False: 
                return op.GetCache(hh)
    
            parent = c4d.BaseObject(c4d.Onull)
    
            data = op.GetDataInstance()
            if data is None:
                return
            body_rotation = c4d.Vector(data.GetFloat(OMINECHARAC_BODY), 0, 0 )
    
    
            body = c4d.BaseObject(c4d.Ocube)
            body[c4d.PRIM_CUBE_LEN,c4d.VECTOR_Y] = BODY_HEIGHT
            body.SetRelPos(c4d.Vector(0, BODY_HEIGHT * 0.5, 0))
            body.SetRelRot(body_rotation)
    
            head_rotation = data.GetVector(OMINECHARAC_HEAD)
            head = c4d.BaseObject(c4d.Ocube)
            head[c4d.PRIM_CUBE_LEN,c4d.VECTOR_Y] = HEAD_HEIGHT
            head.SetRelPos(c4d.Vector(0,BODY_HEIGHT  + HEAD_HEIGHT * 0.5 + 10 , 0))
            head.SetRelRot(head_rotation)
            
    
            head.InsertUnder(parent)
            body.InsertUnder(parent)
    
            return parent
    
    
    if __name__ == "__main__":
    
        # Registers the object plugin
        c4d.plugins.RegisterObjectPlugin(id=PLUGIN_ID,
                                         str="MineCharac",
                                         g=MineCharac,
                                         description="Ominecharac",
                                         icon=None,
                                         info=c4d.OBJECT_GENERATOR)
    
        
    

    Cheers,
    Manuel



  • Hi,

    of an object inside the hierarchy of plugin object cache

    that part is not clear for me, are you talking about the cache of a child of your plugin's objectData or the cache of the object data itself ?

    You seem to have issue with handling the handlers, did you checked our example on github : Round Tube ?
    SetHandle, GetHandle, GetHandleCount,Draw` must be implemented.

    Inside the GetHandle function you can set the HandleInfo to spherical using that symbol : HANDLECONSTRAINTTYPE_SPHERICAL

    About the unlimited rotation, i don't see the point to do that.

    Cheers,
    Manuel



  • @m_magalhaes
    Hi,
    Bellow a simple example of plugin's objectData to illustrate what I want to do:

    def GetVirtualObjects(self, op, hh):
        dirty = op.CheckCache(hh) or op.IsDirty(c4d.DIRTY_DATA)
        
        if not dirty:
            return op.GetCache(hh)
    
        data = op.GetDataInstance()
        if data is None:
            c4d.BaseObject(c4d.Onull)
    
    
        pitch  = data.GetFloat(c4d.MY_OBJECT_PITCH)
        rot    = data.GetFloat(c4d.MY_OBJECT_ROTATION)
    
    
        container = c4d.BaseObject(c4d.Onull)
    
        head_obj = c4d.BaseObject(c4d.Ocube)
        body_obj = c4d.BaseObject(c4d.Ocube)
    
        ...
    
        head_obj.SetParameter(c4d.DescID(c4d.ID_BASEOBJECT_REL_ROTATION), c4d.Vector(math.radians(rot), math.radians(pitch), 0), c4d.DESCFLAGS_SET_0)
        
        head_obj.InsertUnder(container)
        body_obj.InsertUnder(container)
    
        return container
    

    The object in question here is the head_obj, I have successfully created the handle points to control the width, height and depth of the head_obj. and to pin the handle points depending on the head_obj rotation I used the solution from this post:
    https://plugincafe.maxon.net/topic/12347/how-to-calculate-a-rectangle-corners-position-depending-on-her-rotation

    The remaining problem it was to adapting the info.direction Vector depending on the head_obj rotation, so I have created the function axis() (see above in my main question).
    this function works but I'm just searching better way to create same function.



  • Hi,

    I also struggle a bit understanding what you are actually doing, but here are some suggestions anyways:

    1. Do not use conditions like this rot in range(45, 135), because of their horrible time-complexity (which is linear with the range you are testing). Use something like 45 <= rot < 135 [1] instead (which is constant). Considering how often you use that type of condition and how often Cinema will query the handles of an object and the fact that there can be multiple objects of your plugin in a scene, this easily could mean an extra 100 000 instructions per second.
    2. As already stated, I find it hard to untangle all these magic numbers without any explanation, but from what I get, you are interested in the relation of two angles, right?. One way to simplify things could be to interpret your angles as unit rotation vectors an then interpret the dot product between these two. By using operations like abs and mod on your result, you can probably cut down the number of conditions significantly (assuming that there are some symmetries in your conditions).

    Cheers,
    zipit

    [1] But your probably meant 45 <= rot <= 135, which would be rot in range(45, 136) in your form.



  • hi,

    I've created this example :
    The first thing to do is to build an object without handle. Just create your parameters, and use them.
    After that you can implement the handle that will changes those parameters. (and changing the parameter will change the handle).

    I have a Float that will server me to store the rotation of the body, only on the Y axis, that's why I only need one float.
    For the head, i'll store that in a vector.

    The same idea must be done for scaling.

    
    import math
    import c4d
    
    # Be sure to use a unique ID obtained from www.plugincafe.com
    PLUGIN_ID = 1000001
    
    OMINECHARAC_HEAD = 10000
    OMINECHARAC_BODY = 10001
    
    BODY_HEIGHT = 500
    HEAD_HEIGHT = 100
    
    
    class MineCharac(c4d.plugins.ObjectData):
        """CircleObject Generator"""
    
        def Init(self, node):
            """
            Called when Cinema 4D Initialize the ObjectData (used to define, default values)
            :param node: The instance of the ObjectData.
            :type node: c4d.GeListNode
            :return: True on success, otherwise False.
            """
            # Retrieves the BaseContainer Instance to set the default values
            data = node.GetDataInstance()
            if data is None:
                return False
    
            # Defines default values in the BaseContainer
            data.SetVector(OMINECHARAC_HEAD, c4d.Vector(0))
            data.SetFloat(OMINECHARAC_BODY, 0.0)
    
            return True
    
        """========== Begin of Handle Management =========="""
    
        def GetHandleCount(self, op):
            """
            Called by Cinema 4D to retrieve the count of Handle the object will have.
            :param op: The instance of the ObjectData.
            :type op: c4d.BaseObject
            :return: The number of handle
            :rtype: int
            """
            # One handle will be used for this object
            return 2
    
        def GetHandle(self, op, i, info):
            """
            Called by Cinema 4D to retrieve the information of a given handle ID to represent a/some parameter(s).
            :param op: The instance of the ObjectData.
            :type op: c4d.BaseObject
            :param i: The handle index.
            :type i: int
            :param info: The HandleInfo to fill with data.
            :type info: c4d.HandleInfo
            """
            # Retrieves the current BaseContainer
            data = op.GetDataInstance()
            if data is None:
                return
    
            # Defines the position, direction and type of the handle
            radius = 250
            # ID 0 will be head
            if i == 0:
                info.center = c4d.Vector(0, 600, 0)
                position =  data.GetVector(OMINECHARAC_HEAD)
                
                mg = c4d.utils.HPBToMatrix(position)
    
                info.position = info.center + ( mg.v3 * 100)
                info.direction = data.GetVector(OMINECHARAC_HEAD) 
                info.type = c4d.HANDLECONSTRAINTTYPE_SPHERICAL
    
            # ID 1 will be body         
            if i == 1:
                sn, cs = c4d.utils.SinCos(data.GetFloat(OMINECHARAC_BODY))
                info.position = c4d.Vector(-sn * radius, 0, cs * radius)
                info.direction = c4d.Vector(0, 1, 0)
                info.center = c4d.Vector(0, 0, 0)
                info.type = c4d.HANDLECONSTRAINTTYPE_PLANAR
            
    
        def SetHandle(self, op, i, p, info):
            """
            Called by Cinema 4D when the user set the handle.
            This is the place to retrieve the information of a given handle ID and drive your parameter(s).
            :param op: The instance of the ObjectData.
            :type op: c4d.BaseObject
            :param i: The handle index.
            :type i: int
            :param p: The new Handle Position.
            :type p: c4d.Vector
            :param info: The HandleInfo filled with data.
            :type info: c4d.HandleInfo
            """
            data = op.GetDataInstance()
            if data is None:
                return
    
            if i == 0:
                hpb = c4d.utils.VectorToHPB(p - info.center)
                data.SetVector(c4d.OMINECHARAC_HEAD, hpb)
            if i == 1:
                hpb = c4d.utils.VectorToHPB(p)
                value = hpb.x
                data.SetFloat(OMINECHARAC_BODY, value)
    
        def Draw(self, op, drawpass, bd, bh):
            """
            Called by Cinema 4d when the display is updated to display some visual element of your object in the 3D view.
            This is also the place to draw Handle
            :param op: The instance of the ObjectData.
            :type op: c4d.BaseObject
            :param drawpass:
            :param bd: The editor's view.
            :type bd: c4d.BaseDraw
            :param bh: The BaseDrawHelp editor's view.
            :type bh: c4d.plugins.BaseDrawHelp
            :return: The result of the drawing (most likely c4d.DRAWRESULT_OK)
            """
            # If the current draw pass is not the handle, skip this Draw Call.
            if drawpass != c4d.DRAWPASS_HANDLES:
                return c4d.DRAWRESULT_SKIP
    
            # Defines the drawing matrix to the object matrix.
            m = bh.GetMg()
            bd.SetMatrix_Matrix(op, m)
    
            # Checks if one of the handle of the current object is currently hovered by the mouse.
            hitId = op.GetHighlightHandle(bd)
    
            # Defines the color of the handle according of the hovered state of the object.
            hoverColor = c4d.VIEWCOLOR_ACTIVEPOINT if hitId != 0 else c4d.VIEWCOLOR_SELECTION_PREVIEW
            bd.SetPen(c4d.GetViewColor(hoverColor))
    
            # Retrieves the information of the current handle.
            infoHead = c4d.HandleInfo()
            infoBody = c4d.HandleInfo()
    
            self.GetHandle(op, 0, infoHead)
            self.GetHandle(op, 1, infoBody)
    
            # Draw the handle to the correct position
            bd.DrawHandle(infoHead.position, c4d.DRAWHANDLE_BIG, 0)
            bd.SetPen(c4d.GetViewColor( c4d.VIEWCOLOR_ACTIVEPOINT))
            bd.DrawLine(infoHead.position, infoHead.center, 0)
    
    
            self.GetHandle(op, 1, infoBody)
            bd.DrawHandle(infoBody.position, c4d.DRAWHANDLE_BIG, 0)
            bd.SetPen(c4d.GetViewColor( c4d.VIEWCOLOR_ACTIVEPOINT))
            bd.DrawLine(infoBody.position, infoBody.center, 0)
    
            return c4d.DRAWRESULT_OK
    
        """========== End of Handle Management =========="""
    
    
        def GetVirtualObjects(self, op, hh):
    
            dirty = op.CheckCache(hh) or op.IsDirty(c4d.DIRTY_DATA)
            if dirty is False: 
                return op.GetCache(hh)
    
            parent = c4d.BaseObject(c4d.Onull)
    
            data = op.GetDataInstance()
            if data is None:
                return
            body_rotation = c4d.Vector(data.GetFloat(OMINECHARAC_BODY), 0, 0 )
    
    
            body = c4d.BaseObject(c4d.Ocube)
            body[c4d.PRIM_CUBE_LEN,c4d.VECTOR_Y] = BODY_HEIGHT
            body.SetRelPos(c4d.Vector(0, BODY_HEIGHT * 0.5, 0))
            body.SetRelRot(body_rotation)
    
            head_rotation = data.GetVector(OMINECHARAC_HEAD)
            head = c4d.BaseObject(c4d.Ocube)
            head[c4d.PRIM_CUBE_LEN,c4d.VECTOR_Y] = HEAD_HEIGHT
            head.SetRelPos(c4d.Vector(0,BODY_HEIGHT  + HEAD_HEIGHT * 0.5 + 10 , 0))
            head.SetRelRot(head_rotation)
            
    
            head.InsertUnder(parent)
            body.InsertUnder(parent)
    
            return parent
    
    
    if __name__ == "__main__":
    
        # Registers the object plugin
        c4d.plugins.RegisterObjectPlugin(id=PLUGIN_ID,
                                         str="MineCharac",
                                         g=MineCharac,
                                         description="Ominecharac",
                                         icon=None,
                                         info=c4d.OBJECT_GENERATOR)
    
        
    

    Cheers,
    Manuel



  • @m_magalhaes
    Hi,
    Thank you so much for this very interesting work. I'm trying to adapt it on my project and if I have a problem I will return to this topic.



  • hi,

    I will mark this thread as solved tomorrow.

    Cheers,
    Manuel


Log in to reply