How to constraint object to perspective user viewport (Not render camera) I would like to constraint an object (cube) to the viewport camera, so it will be always in user's field of view.
Is it possible with constraints (I didn't find any way) ?
I also tried with python to get 3d coords :
import bpy
from bpy.props import IntProperty, FloatProperty
import bpy_extras
from mathutils import  Vector, geometry
import mathutils

class ModalOperator(bpy.types.Operator):
    &quot;&quot;&quot;Move an object with the mouse, example&quot;&quot;&quot;
    bl_idname = &quot;object.modal_operator&quot;
    bl_label = &quot;Simple Modal Operator&quot;

    first_mouse_x: IntProperty()
    first_value: FloatProperty()

    def modal(self, context, event):
        if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
            coord = self.get_view_coord(context)
            print(&quot;coord = &quot;, coord)
            return {'PASS_THROUGH'}
        

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            context.object.location.x = self.first_value
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

        
    def get_view_coord(self,context):
        region = bpy.context.region
        for area in bpy.context.screen.areas:
            if area.type == 'VIEW_3D':
                rv3d = area.spaces.active.region_3d
                break

        pos = bpy_extras.view3d_utils.region_2d_to_location_3d( region, rv3d, Vector((100,500)), Vector((0,0,0)) )
        orient = bpy_extras.view3d_utils.region_2d_to_vector_3d( region, rv3d, Vector((100,500)))
        
        bpy.ops.object.empty_add(type='ARROWS', location= pos)
        
        bpy.context.object.rotation_mode = 'QUATERNION'
        bpy.context.object.rotation_quaternion = orient.to_track_quat('Z','Y')
        return pos
        

def register():
    bpy.utils.register_class(ModalOperator)


def unregister():
    bpy.utils.unregister_class(ModalOperator)


if __name__ == &quot;__main__&quot;:
    register()

    # test call
    bpy.ops.object.modal_operator('INVOKE_DEFAULT')

Edit 1 : The final goal is to have several controller objects on the sides of 3d viewport, so I can manipulate them and via handlers, tweaking previously generated curves.
Edit 2 : With this modal example, I can have an empty placed in 3d viewport, but still don't know how to follow camera, how to get height and width of 3d area (to place it at the top left corner for example), plus Z axis is well oriented, but I would like Y axis to be parallel to the border of the screen.
Keep context object centered on largest viewport in screen.

The view is set up similarly to the camera, ie it looks down its $-Z$ axis.
Rotate an object to camera view
Access view orientation from python?
whereas meshes are normally $Z$ up $-Y$ forward.
The view_align method below, looks for the largest 3d view, then using its matrix aligns the context object such that it looks directly at the view, from 5 global units away.
The rest is basically the modal timer operator template, similarly to question
import bpy
from mathutils import Vector, Matrix
from bpy_extras.io_utils import axis_conversion

distance_from_view = 5

A = axis_conversion(
        from_forward='Y',
        from_up='Z',
        to_forward='-Z',
        to_up='Y').to_4x4()

def view_align(context):
    areas =  sorted(
            [
                a 
                for a in context.screen.areas 
                if a.type == 'VIEW_3D'
            ], 
            key=lambda a : a.width * a.height
        )
        
    if areas:
        screen = context.screen
        ob = context.object
        mw = ob.matrix_world
        r3d = areas.pop().spaces.active.region_3d
        M = r3d.view_matrix
        V = M.inverted()
        # maintain global distance from camera
        T = Matrix.Translation(V @ Vector((0, 0, -distance_from_view)))
        # keep original transform align to view
        #T = Matrix.Translation(mw.translation)
        R = M.to_3x3().transposed().to_4x4()
        S = Matrix.Diagonal(mw.to_scale()).to_4x4()
        ob.matrix_world = T @ (R @ A) @ S
        
        
class ModalTimerOperator(bpy.types.Operator):
    &quot;&quot;&quot;Operator which runs its self from a timer&quot;&quot;&quot;
    bl_idname = &quot;wm.modal_timer_operator&quot;
    bl_label = &quot;Modal Timer Operator&quot;

    _timer = None

    def modal(self, context, event):
        if event.type in {'RIGHTMOUSE', 'ESC'}:
            self.cancel(context)
            return {'CANCELLED'}

        if event.type == 'TIMER':
            view_align(context)

        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.01, window=context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)


def register():
    bpy.utils.register_class(ModalTimerOperator)


def unregister():
    bpy.utils.unregister_class(ModalTimerOperator)


if __name__ == &quot;__main__&quot;:
    register()

    # test call
    bpy.ops.wm.modal_timer_operator()