i am making a custom tool, i would like to have different behaviour if i use the tool holding Alt, Ctrl, Shift.... in the templates there is an Half/example in the UItool simple. But i cant make it work in my own script.

bl_keymap = (  ("view3d.select_circle", {"type": 'LEFTMOUSE', "value": 'PRESS'},
                   {"properties": [("wait_for_input", False)]}),
               ("view3d.select_circle", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
                   {"properties": [("mode", 'SUB'), ("wait_for_input", False)]}),

These lines keymap the tool 2 times, to the same "operator", But setting the properties: Wait_for_input and mode. So if i understand correctly i can add a property to my operator to choose if create cubes or sphere . and i should be able to set this property from here.

What i am messing around ?

Here the full script, You need to run it you have to add a cube in the scene and 2 circle named GRABBER and GRABBER2 the tool will move the GRABBER to the nearest vertex under mouse.

Sorry for long script, I think that the wrong part is the ( * Tool ) or the ( * modal operator ) The result i get now is a empty string for my_mode and a 0 for my_mode2 both are two property i added in the operator, if i inizialize them in the invoke, i can get a result. (commented now) but i never get the 1 and B i am calling with the tool.

Thanks for your time

import bpy
from bpy.props import (StringProperty,  
from bpy.types import (Panel,

from bpy.types import WorkSpaceTool
from bpy_extras import view3d_utils 

#       TOOL       

class CustomTool(WorkSpaceTool):  
    bl_space_type = 'VIEW_3D'
    bl_context_mode = 'OBJECT'
    bl_idname = "grabber.mycustom_tool"
    bl_label = " GRABBER "
    bl_description = (" move tool ")
    bl_icon =  "ops.gpencil.draw.poly"          
    bl_widget = None 
    bl_keymap = (
        ("wm.modal_grabber", {"type": 'LEFTMOUSE', "value": 'PRESS'},
         {"properties": [("my_mode2", 1 )]}),
        ("wm.modal_grabber", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
         {"properties": [("my_mode", "B")]}),
#  +   SCENE  PROPERTY    

class Custom_PROP(PropertyGroup):                 
    MouseL: BoolProperty() 
bpy.utils.register_class(Custom_PROP)                                      # REGISTER THE PROPERTY SO I CAN USE IT NOW
bpy.types.Scene.CUSTOM_DATA = bpy.props.PointerProperty(type=Custom_PROP)   # ADD THE CUSTOM PROPERTY IN THE SCENE

#       PANEL      

class CUSTOM_Panel(Panel):   
    bl_label = "Mousetester"
    bl_idname = "OBJECT_PT_custom_mouse"
    bl_space_type = "VIEW_3D"   
    bl_region_type = "TOOLS"
    def draw(self, context):         
        layout = self.layout  
        row = layout.row() 
        row.prop( context.scene.CUSTOM_DATA  , "MouseL" ) 
        row = layout.row()
        row.label( text= "Read the print value in console " )
        row.label( text= " Dont know why it dont update" )
class CUSTOM_GRABBER(bpy.types.Operator):
    bl_idname = "wm.modal_grabber"
    bl_label = "test Modal grabber"
    my_mode2: bpy.props.IntProperty(name="MODES_INT")
    my_mode: bpy.props.StringProperty(name="MODES_STR")

    def invoke(self, context, event):
#        self.my_mode2 = 66
#        self.my_mode  = "SEEI"
        return {'RUNNING_MODAL'}
    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':  
            print("Mouse moved to  " + str(event.mouse_region_x) + "  " + str(event.mouse_region_y)  ) 
            print  (bpy.context.scene.CUSTOM_DATA.MouseL)     
            getnearestvertex(context, event)
            if self.my_mode =="A":
            print("MY MODE IS = this")
            print("mode = " + self.my_mode)
            print("mode2 = "+ str(self.my_mode2)    )
        if event.type == "LEFTMOUSE" : 
            if event.value == "PRESS":                    # THIS WILL NOT BE CALLED
                bpy.context.scene.CUSTOM_DATA.MouseL =1       #
                print("MOUSE_LEFT---PRESS")                   #
                print  (bpy.context.scene.CUSTOM_DATA.MouseL) #
            if event.value == "RELEASE":
                bpy.context.scene.CUSTOM_DATA.MouseL =0
                print  (bpy.context.scene.CUSTOM_DATA.MouseL) 
                return {'CANCELLED'}
            if event.value == "CLICK":
        if event.type == 'ESC' :
            return {'CANCELLED'}
        return {'PASS_THROUGH'}
    def execute(self, context):
        wm = context.window_manager       
        print("THIS IS THE FIRST CLICK ")                 # FIRST CLICK
        bpy.context.scene.CUSTOM_DATA.MouseL =1           # So i set the value here 
        print  (bpy.context.scene.CUSTOM_DATA.MouseL) 
        context.area.tag_redraw()   # UPDATE
        return {'RUNNING_MODAL'}


def getnearestvertex(context, event):
    scene = context.scene
    region = context.region
    rv3d = context.region_data
    coord = event.mouse_region_x, event.mouse_region_y
    view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
    ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
    ray_target = ray_origin + view_vector

    def visible_objects_and_duplis():
        depsgraph = context.evaluated_depsgraph_get()
        for dup in depsgraph.object_instances:
            if dup.is_instance:  # Real dupli instance
                obj = dup.instance_object
                yield (obj, dup.matrix_world.copy())
            else:  # Usual object
                obj = dup.object
                yield (obj, obj.matrix_world.copy())

    def obj_ray_cast(obj, matrix):
        matrix_inv = matrix.inverted()
        ray_origin_obj = matrix_inv @ ray_origin
        ray_target_obj = matrix_inv @ ray_target
        ray_direction_obj = ray_target_obj - ray_origin_obj

        success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)

        if success:
            return location, normal, face_index
            return None, None, None

    # cast rays and find the closest object
    best_length_squared = -1.0
    best_obj = None

    for obj, matrix in visible_objects_and_duplis():
        if obj.type == 'MESH':
            hit, normal, face_index = obj_ray_cast(obj, matrix)
            if hit is not None:
                hit_world = matrix @ hit
#                scene.cursor.location = hit_world  # DO NOT MOVE THE CURSOR
                length_squared = (hit_world - ray_origin).length_squared
                if best_obj is None or length_squared < best_length_squared:
                    best_length_squared = length_squared
                    best_obj = obj

    # now we have the object under the mouse cursor,
    # we could do lots of stuff but for the example just select.
    if best_obj is not None:
        # for selection etc. we need the original object,
        # evaluated objects are not in viewlayer
        best_original = best_obj.original
        vertex_n = len( [v for v in QQQ.data.vertices if v.select ] )
        objmatrix = QQQ.matrix_world 
        for v in QQQ.data.vertices:
            world_xyz = objmatrix @ v.co
            vector_diff =  world_xyz - hit_world
            vector_length = vector_diff.length
            if  vector_length < min_dist :
                min_dist = vector_length
                closer_vertex = world_xyz
#        bpy.ops.object.origin_set(type='ORIGIN_CURSOR')

#    Custom_PROP ,     
def register():
    for cls in classes:
    bpy.utils.register_tool( CustomTool , after={"builtin.scale_cage"}, separator=True, group=True)
def unregister():
    for cls in classes:
    bpy.utils.unregister_tool( CustomTool  )

if __name__ == "__main__":

1 Answer 1


Basically , my log sayd me that there is already a "custom tool already exist ", So running the script was updating all the operators and panel but not the tool, and maybe some more things.

UnRegistering the tool or Reloading the the file have solved the issue…

Here an example of custom tool that keymap the mouse_left with alt shift ctrl and oskey and a combo of 2 (oskey + alt) to set a property string , inside the operator. To do different action/ operations based on these input.

remember to load the module for the property you need from bpy.props import (StringProperty…

class CustomTool(WorkSpaceTool):  
    bl_space_type = 'VIEW_3D'
    bl_context_mode = 'OBJECT'
    bl_idname = "grabber.mycustom_tool"
    bl_label = "  TOOL NAME "
    bl_description = ("  your  tool ")
    bl_icon =  "ops.gpencil.draw.poly"          
    bl_widget = None 
    bl_keymap = (
        ("wm.your_operator_name", {"type": 'LEFTMOUSE', "value": 'PRESS'},
         {"properties": [("my_mode", "DEFAULT" )]}),
        ("wm.your_operator_name", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
         {"properties": [("my_mode", "CONTROL")]}),
        ("wm.your_operator_name", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
         {"properties": [("my_mode", "ALT")]}),
        ("wm.your_operator_name", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
         {"properties": [("my_mode", "SHIFT")]}),
        ("wm.your_operator_name", {"type": 'LEFTMOUSE', "value": 'PRESS', "oskey": True},
         {"properties": [("my_mode", "OSKEY")]}),
        ("wm.your_operator_name", {"type": 'LEFTMOUSE', "value": 'PRESS', "oskey" : True , "alt": True},
         {"properties": [("my_mode", "OS+ALT")]}),         

class CUSTOM_OPERATOR(bpy.types.Operator):
    bl_idname = "wm.your_operator_name"
    bl_label = "test operator"
    my_mode: bpy.props.StringProperty(name="MODES_STRING")

    def invoke....... continue with your operator

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .