Solved Dynamic HandleInfo.direction

Hello,

I'm searching if there a method to dynamically change the HandleInfo.direction depend on the rotation and pitch value of my object. I created the following object plugin example to illustrate exactly what I want to do.

Screen video:
dynamic_handleInfo.direction.gif

pyp file:

import c4d, os
from c4d import utils, DescID, BaseObject, bitmaps, plugins, Vector, HandleInfo
from math import radians

PLUGIN_ID = 1055615

class Osobject(plugins.ObjectData):

    def Init(self, op):

        self.fs_mg = c4d.Matrix()
        self.head_obj_h = 36

        data = op.GetDataInstance()

        data.SetFloat(c4d.OSOBJECT_SIZEX, 60.0)
        data.SetFloat(c4d.OSOBJECT_SIZEY, 50.0)
        data.SetFloat(c4d.OSOBJECT_DEPTH, 50.0)
        data.SetFloat(c4d.OSOBJECT_PITCH, radians(-30))
        data.SetFloat(c4d.OSOBJECT_ROTATION, radians(90))

        return True

    def GetVirtualObjects(self, op, hh):
        dirty = op.CheckCache(hh) or op.IsDirty(c4d.DIRTY_MATRIX | c4d.DIRTY_DATA)

        if not dirty:
            return op.GetCache(hh)

        data = op.GetDataInstance()

        size_x = data.GetFloat(c4d.OSOBJECT_SIZEX)
        size_y = data.GetFloat(c4d.OSOBJECT_SIZEY)
        depth = data.GetFloat(c4d.OSOBJECT_DEPTH)
        pitch = data.GetFloat(c4d.OSOBJECT_PITCH)
        rotation = data.GetFloat(c4d.OSOBJECT_ROTATION)

        container = BaseObject(c4d.Onull)
        container.SetRelPos(Vector(0, 40, 0))

        bar = BaseObject(c4d.Ocylinder)
        bar.SetName("bar")
        bar.InsertUnder(container)
        bar.SetParameter(DescID(c4d.PRIM_CYLINDER_RADIUS), 1.5, c4d.DESCFLAGS_GET_0)
        bar.SetParameter(DescID(c4d.PRIM_CYLINDER_HEIGHT), 40, c4d.DESCFLAGS_GET_0)
        bar.SetParameter(DescID(c4d.PRIM_AXIS), 2, c4d.DESCFLAGS_GET_0)
        bar.SetRelPos(Vector(0, -20, 0))

        # Rotation Object
        rot_obj = BaseObject(c4d.Onull)
        rot_obj.SetRelRot(Vector(rotation, 0, 0))
        rot_obj.SetName("rotation")
        rot_obj.InsertUnder(container)

        # Pith Object
        pitch_obj = BaseObject(c4d.Onull)
        pitch_obj.SetRelRot(Vector(0, pitch, 0))
        pitch_obj.SetName("pitch")
        pitch_obj.InsertUnder(rot_obj)

        # Head Container
        head_container = BaseObject(c4d.Onull)
        head_container.SetName("head_container")
        head_container.InsertUnder(pitch_obj)

        # Head Object
        head_obj = BaseObject(c4d.Ocylinder)
        head_obj.SetName("head")
        head_obj.InsertUnder(head_container)

        head_obj.SetParameter(DescID(c4d.PRIM_CYLINDER_RADIUS), 10, c4d.DESCFLAGS_GET_0)
        head_obj.SetParameter(DescID(c4d.PRIM_CYLINDER_HEIGHT), self.head_obj_h, c4d.DESCFLAGS_GET_0)
        head_obj.SetParameter(DescID(c4d.PRIM_AXIS), 5, c4d.DESCFLAGS_GET_0)

        # depth Object
        depth_obj = BaseObject(c4d.Ocylinder)
        depth_obj.SetName("cyl")
        depth_obj.InsertUnder(head_container)

        depth_obj.SetParameter(DescID(c4d.PRIM_CYLINDER_RADIUS), 3, c4d.DESCFLAGS_GET_0)
        depth_obj.SetParameter(DescID(c4d.PRIM_CYLINDER_HEIGHT), depth, c4d.DESCFLAGS_GET_0)
        depth_obj.SetParameter(DescID(c4d.PRIM_AXIS), 5, c4d.DESCFLAGS_GET_0)

        depth_obj_pos = self.head_obj_h/2 + depth/2
        depth_obj.SetRelPos(Vector(0, 0, depth_obj_pos))

        # Front Surface Object
        fs_obj = BaseObject(c4d.Oplane)
        fs_obj.SetName("front_surface")
        fs_obj.InsertUnder(head_container)
        self.fs_mg = fs_obj.GetMg()

        fs_obj.SetParameter(DescID(c4d.PRIM_PLANE_WIDTH), size_x, c4d.DESCFLAGS_GET_0)
        fs_obj.SetParameter(DescID(c4d.PRIM_PLANE_HEIGHT), size_y, c4d.DESCFLAGS_GET_0)
        fs_obj.SetParameter(DescID(c4d.PRIM_PLANE_SUBW), 1, c4d.DESCFLAGS_GET_0)
        fs_obj.SetParameter(DescID(c4d.PRIM_PLANE_SUBH), 1, c4d.DESCFLAGS_GET_0)
        fs_obj.SetParameter(DescID(c4d.PRIM_AXIS), 5, c4d.DESCFLAGS_GET_0)

        fs_obj_pos = self.head_obj_h/2 + depth
        fs_obj.SetRelPos(Vector(0, 0, fs_obj_pos))

        return container

    def GetHandleCount(self, op):
        return 4

    def GetHandle(self, op, handle_index, info):

        data = op.GetDataInstance()

        size_x = data.GetFloat(c4d.OSOBJECT_SIZEX)
        size_y = data.GetFloat(c4d.OSOBJECT_SIZEY)
        depth = data.GetFloat(c4d.OSOBJECT_DEPTH)
        pitch = data.GetFloat(c4d.OSOBJECT_PITCH)
        rotation = data.GetFloat(c4d.OSOBJECT_ROTATION)

        size_x = size_x/2
        size_y = size_y/2
        depth = depth+self.head_obj_h/2

        mg = self.fs_mg
        pos = mg.off
        rot = utils.MatrixToHPB(mg, c4d.ROTATIONORDER_DEFAULT)

        rot_mx = utils.HPBToMatrix(rot)
        rot_mx.off += pos

        if handle_index == 0: # Width
            info.position = rot_mx * Vector(-size_x, 0, depth)
            info.direction = Vector(1, 0, 0)
        elif handle_index == 1: # Height
            info.position = rot_mx * Vector(0, size_y, depth)
            info.direction = Vector(0, 1, 0)
        elif handle_index == 2: # Depth
            info.position = rot_mx * Vector(0, 0, depth)
            info.direction = Vector(0, 0, 1)

        info.type = c4d.HANDLECONSTRAINTTYPE_LINEAR

    def SetHandle(self, op, handle_index, handle_position, info):

        handle_origin = HandleInfo()
        self.GetHandle(op, handle_index, handle_origin)

        data = op.GetDataInstance()

        size_x = data.GetFloat(c4d.OSOBJECT_SIZEX)
        size_y = data.GetFloat(c4d.OSOBJECT_SIZEY)
        depth = data.GetFloat(c4d.OSOBJECT_DEPTH)
        pitch = data.GetFloat(c4d.OSOBJECT_PITCH)
        rotation = data.GetFloat(c4d.OSOBJECT_ROTATION)

        size_x = size_x/2
        size_y = size_y/2

        value = (handle_position - handle_origin.position) * info.direction

        if handle_index == 0:
            op[c4d.OSOBJECT_SIZEX] -= value
        elif handle_index == 1:
            op[c4d.OSOBJECT_SIZEY] += value
        elif handle_index == 2:
            op[c4d.OSOBJECT_DEPTH] -= value

    def Draw(self, op, drawpass, bd, bh):

        if drawpass != c4d.DRAWPASS_HANDLES:
            return c4d.DRAWRESULT_SKIP

        data = op.GetDataInstance()

        size_x = data.GetFloat(c4d.OSOBJECT_SIZEX)
        size_y = data.GetFloat(c4d.OSOBJECT_SIZEY)
        depth = data.GetFloat(c4d.OSOBJECT_DEPTH)
        pitch = data.GetFloat(c4d.OSOBJECT_PITCH)
        rotation = data.GetFloat(c4d.OSOBJECT_ROTATION)

        size_x = size_x/2
        size_y = size_y/2

        bd.SetMatrix_Matrix(op, bh.GetMg())
        hitid = op.GetHighlightHandle(bd)

        for i in xrange(self.GetHandleCount(op)):
            bd.SetPen(c4d.GetViewColor(c4d.VIEWCOLOR_ACTIVEPOINT))

            info = HandleInfo()

            self.GetHandle(op, i, info)
            bd.DrawHandle(info.position, c4d.DRAWHANDLE_BIG, 0)
            bd.SetMatrix_Matrix(op, bh.GetMg())

        return c4d.DRAWRESULT_OK

if __name__ == "__main__":
    icon = bitmaps.BaseBitmap()
    icon.InitWith(os.path.join(os.path.dirname(__file__), 'res', 'icon.tif'))
    plugins.RegisterObjectPlugin(PLUGIN_ID, 'Super Object', Osobject, 'osobject', c4d.OBJECT_GENERATOR, icon)

And here the full example files: osobject.zip

Thanks

hi,

You are overthinking it, your value for the handle should never be something like += It should just reflect what the parameters values are.
We got this thread where you can see it's should be simple.

I didn't dive too much in your code, but something like this works better. You probably other logic that isn't working, this look like an implementation error from your side. Not a problem with the API. If you can't resolve your issue with the linked thread, i'll try to have a better look at it.

    def SetHandle(self, op, handle_index, handle_position, info):

        data = op.GetDataInstance()
        size_x = data.GetFloat(c4d.OSOBJECT_SIZEX)
        size_y = data.GetFloat(c4d.OSOBJECT_SIZEY)
        depth = data.GetFloat(c4d.OSOBJECT_DEPTH)
        pitch = data.GetFloat(c4d.OSOBJECT_PITCH)
        rotation = data.GetFloat(c4d.OSOBJECT_ROTATION)

        size_x = size_x / 2.0
        size_y = size_y / 2.0

        value = (handle_position - info.center) * info.direction.GetLength()
        if handle_index == 0:
            op[c4d.OSOBJECT_SIZEX] = value.x
        elif handle_index == 1:
            op[c4d.OSOBJECT_SIZEY] = value.y
        elif handle_index == 2:
            op[c4d.OSOBJECT_DEPTH] = value.z

this solution isn't perfect yet, but i think you will understand the idea and find out why it's not working perfectly.

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

Hi,

I am not quite sure if this is a question or the sharing of a solution, depending on how one reads your posting. But if you are interested in aligning the travel normal, the direction, of your handles with the transform you apply to your returned objects, you will simply have to multiply it with the respective rotational part of that transform, i.e. the rotation matrix. Something like this for your code:

# That is not really a rotation matrix, since it will also affect
# the translation of a vector, so your naming might be a bit
# confusing.
rot_mx = utils.HPBToMatrix(rot)
rot.mx.off += pos
# The actual rotation only transform.
orientation = utils.HPBToMatrix(rot)

if handle_index == 0: # Width
   info.position = rot_mx * Vector(-size_x, 0, depth)
   info.direction = Vector(1, 0, 0) * orientation 

Cheers,
zipit

MAXON SDK Specialist
developers.maxon.net

Hi, @zipit

Yes I'm searching a solution. I tried your solution but this does not work. the problem is with the handle point that not moving correctly (check the screen video above)

I actually using the following method, but is too long and its works at 90%

def GetAxis(self, pitch, rot):
        pitch = int(math.degrees(pitch))
        rot = int(math.degrees(rot))
        if -90 <= rot < -45 or 225 <= rot < 270:
            if -45 <= pitch < 45:
                axis = 0 # +x
            elif -225 <= pitch < -135 or 135 <= pitch < 225:
                axis = 1 # -z
            elif -135 <= pitch < -45:
                axis = 3 # -y
            elif 45 <= pitch < 135 or -270 <= pitch < -225:
                axis = 2 # +y
            else:
                axis = 0 # +x
        elif -45 <= rot < 45:
            if -45 <= pitch < 45:
                axis = 4 # +z
            elif -225 <= pitch < -135 or 135 <= pitch < 225:
                axis = 5 # -z
            elif -135 <= pitch < -45:
                axis = 13 # -y
            elif 45 <= pitch < 135 or -270 <= pitch < -225:
                axis = 12 # +y
            else:
                axis = 4 # +z
        elif 45 <= rot < 135:
            if -45 <= pitch < 45 or -225 <= pitch < -46:
                axis = 11 # +x
            elif -225 <= pitch < -135 or 135 <= pitch < 225:
                axis = 10 # -z
            elif 135 <= pitch < -45:
                axis = 23 # -y
            elif 45 <= pitch < 135 or -270 <= pitch < -225:
                axis = 22 # +y
            else:
                axis = 1 # +x
        elif 135 <= rot < 225:
            if -45 <= pitch < 45:
                axis = 15 # +x
            elif -225 <= pitch < -135 or 135 <= pitch < 225:
                axis = 14 # -z
            elif -135 <= pitch < -45:
                axis = 33 # -y
            elif 45 <= pitch < 135 or -270 <= pitch < -225:
                axis = 32 # +y
            else:
                axis = 5 # +x
        else:
            axis = 0

        return axis


   def SwapPoint(self, 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)
            elif axis == 2 :
                return c4d.Vector(-p.y, -p.z, p.x)
            elif axis == 3 :
                return c4d.Vector(p.y, p.z, p.x)
            elif axis == 4 :
                return c4d.Vector(-p.x, p.y, -p.z)
            elif axis == 5 :
                return c4d.Vector(-p.x, -p.y, p.z)
            elif axis == 12 :
                return c4d.Vector(-p.x, -p.z, -p.y)
            elif axis == 13 :
                return c4d.Vector(-p.x, p.z, p.y)
            elif axis == 10 :
                return c4d.Vector(-p.z, -p.y, -p.x)
            elif axis == 11 :
                return c4d.Vector(p.z, p.y, -p.x)
            elif axis == 22 :
                return c4d.Vector(p.y, -p.z, -p.x)
            elif axis == 23:
                return c4d.Vector(-p.y, p.z, -p.x)
            elif axis == 14:
                return c4d.Vector(p.x, -p.y, -p.z)
            elif axis == 15:
                return c4d.Vector(p.x, p.y, p.z)
            elif axis == 32 :
                return c4d.Vector(p.x, -p.z, p.y)
            elif axis == 33 :
                return c4d.Vector(p.x, p.z, -p.y)
            return p
axis = self.GetAxis(pitch, rotation)

if handle_index == 0: # Width
    info.position = rot_mx * Vector(-size_x, 0, depth)
    info.direction = self.SwapPoint(info.direction, axis)

Thanks.

hi,

You are overthinking it, your value for the handle should never be something like += It should just reflect what the parameters values are.
We got this thread where you can see it's should be simple.

I didn't dive too much in your code, but something like this works better. You probably other logic that isn't working, this look like an implementation error from your side. Not a problem with the API. If you can't resolve your issue with the linked thread, i'll try to have a better look at it.

    def SetHandle(self, op, handle_index, handle_position, info):

        data = op.GetDataInstance()
        size_x = data.GetFloat(c4d.OSOBJECT_SIZEX)
        size_y = data.GetFloat(c4d.OSOBJECT_SIZEY)
        depth = data.GetFloat(c4d.OSOBJECT_DEPTH)
        pitch = data.GetFloat(c4d.OSOBJECT_PITCH)
        rotation = data.GetFloat(c4d.OSOBJECT_ROTATION)

        size_x = size_x / 2.0
        size_y = size_y / 2.0

        value = (handle_position - info.center) * info.direction.GetLength()
        if handle_index == 0:
            op[c4d.OSOBJECT_SIZEX] = value.x
        elif handle_index == 1:
            op[c4d.OSOBJECT_SIZEY] = value.y
        elif handle_index == 2:
            op[c4d.OSOBJECT_DEPTH] = value.z

this solution isn't perfect yet, but i think you will understand the idea and find out why it's not working perfectly.

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

Hi @m_magalhaes,

I used your suggestion and I replaced the +=. Yes is better, but it's always the same problem. I don't know how to explain the problem, and for that reason I have created this plugin object example. So the best way to understand the problem is by testing the example.

I will try to explain roughly the problem:
The handle point is pinned on child object of the main object plugin and this child object has a dynamic (position and rotation) that depend on the Pitch & Rotation values. So I must to dynamically adapt the info.direction depending on the position and rotation of the child object. And concerning this thread, this helped me to control the Pitch and Rotation via Handle but not the (Width, Height and Depth) values (of child object).

At the moment, I guess I'll just use the SwapPoint() it doesn't have a perfect result but it's acceptable.

Thanks.

Hello @mfersaoui,

without any further questions or replies, we will consider this topic to be solved by Monday and flag it accordingly.

Thank you for your understanding,
Ferdinand

MAXON SDK Specialist
developers.maxon.net