1
$\begingroup$

I'm writing a tool that would allow me to modify meshes within a circle of certain radius. I'd like to be able to change this radius with a mouse wheel, just like with proportional editing tool:

enter image description here

Here's the relevant part of my code. It blocks zooming by mouse wheel and use it to change the radius instead. Circle selection is called in the cursor position in order to select faces I want to modify.

import bpy


class ModalWheelTest(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_wheel_test"
    bl_label = "Test mouse wheel"

    _timer = None
    _radius = 10

    def set_wheel_zoom_state(self, state):
        """
        Disable or enable zooming by mouse wheel
        """
        for key_map_item in bpy.context.window_manager.keyconfigs.default.keymaps["3D View"].keymap_items:
            if key_map_item.name == 'Zoom View' and key_map_item.type in {'WHEELINMOUSE', 'WHEELOUTMOUSE'}:
                key_map_item.active = state

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

        if event.type == 'MOUSEMOVE':
            for area in bpy.context.screen.areas:
                if area.type == 'VIEW_3D':

                    override = bpy.context.copy()
                    override['area'] = area
                    override['region'] = area.regions[4]

                    bpy.ops.view3d.select_circle(
                        override,
                        x=event.mouse_region_x,
                        y=event.mouse_region_y,
                        radius=self._radius,
                        gesture_mode = 3
                    )
                    break

        if event.type in {'WHEELUPMOUSE'}:
            self._radius = min(20, self._radius+1)
            print("radius: {}".format(self._radius))

        if event.type in {'WHEELDOWNMOUSE'}:
            self._radius = max(5, self._radius-1) 
            print("radius: {}".format(self._radius))

        return {'PASS_THROUGH'}

    def execute(self, context):
        if context.area.type != 'VIEW_3D':
            print("Must use in a 3d region")
            return {'CANCELLED'}

        self.set_wheel_zoom_state(False)

        wm = context.window_manager
        self._timer = wm.event_timer_add(0.1, context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

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

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


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


if __name__ == "__main__":
    register()

My question is: how can I draw a white circle in the 3D view window so I can actually see what I am selecting? I'm expecting that I'd clear it on every modal method call and then redraw it with actualized radius and cursor position.

$\endgroup$

1 Answer 1

2
$\begingroup$

Thanks to this answer I've figured it out. I am drawing the circle using OpenGL calls. Here's updated code I can use to select faces in edit mode:

import bpy
import bgl
import blf

import math


def draw_circle_2d(color, cx, cy, r, num_segments):
    theta = 2 * 3.1415926 / num_segments
    c = math.cos(theta) #precalculate the sine and cosine
    s = math.sin(theta)
    x = r # we start at angle = 0 
    y = 0
    bgl.glColor4f(*color)
    bgl.glBegin(bgl.GL_LINE_LOOP)
    for i in range (num_segments):
        bgl.glVertex2f(x + cx, y + cy) # output vertex 
        # apply the rotation matrix
        t = x
        x = c * x - s * y
        y = s * t + c * y
    bgl.glEnd()

def draw_callback_px(self, context):

    bgl.glEnable(bgl.GL_BLEND)

    # ...api_current/bpy.types.Area.html?highlight=bpy.types.area
    header_height = context.area.regions[0].height # 26px
    npanel_width = context.area.regions[1].width
    transorm_panel_width = context.area.regions[3].width

    width = context.area.width - npanel_width - transorm_panel_width
    height = context.area.height + header_height

    # draw text
    bgl.glLineWidth(4)
    draw_circle_2d((1.0, 1.0, 1.0, 0.8), self._mouse_pos_x, self._mouse_pos_y, self._radius, 360)

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)



class ModalWheelTest(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_wheel_test"
    bl_label = "Test mouse wheel"

    _timer = None
    _radius = 50

    def set_wheel_zoom_state(self, state):
        """
        Disable or enable zooming by mouse wheel
        """
        for key_map_item in bpy.context.window_manager.keyconfigs.default.keymaps["3D View"].keymap_items:
            if key_map_item.name == 'Zoom View' and key_map_item.type in {'WHEELINMOUSE', 'WHEELOUTMOUSE'}:
                key_map_item.active = state

    def modal(self, context, event):
        context.area.tag_redraw()

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

        if event.type == 'MOUSEMOVE':
            for area in bpy.context.screen.areas:
                if area.type == 'VIEW_3D':
                    self._mouse_pos_x = event.mouse_region_x
                    self._mouse_pos_y = event.mouse_region_y

                    override = bpy.context.copy()
                    override['area'] = area
                    override['region'] = area.regions[4]

                    bpy.ops.view3d.select_circle(
                        override,
                        x=event.mouse_region_x,
                        y=event.mouse_region_y,
                        radius=self._radius,
                        gesture_mode = 3
                    )
                    break

        if event.type in {'WHEELUPMOUSE'}:
            self._radius = min(200, self._radius+5)
            print("radius: {}".format(self._radius))

        if event.type in {'WHEELDOWNMOUSE'}:
            self._radius = max(5, self._radius-5) 
            print("radius: {}".format(self._radius))

        return {'RUNNING_MODAL'}

    def execute(self, context):
        if context.area.type != 'VIEW_3D':
            print("Must use in a 3d region")
            return {'CANCELLED'}

        self.set_wheel_zoom_state(False)

        wm = context.window_manager
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        self.set_wheel_zoom_state(True)
        bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
        wm = context.window_manager

    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
            # the arguments we pass the the callback
            args = (self, context)
            # Add the region OpenGL drawing callback
            # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
            self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
            context.window_manager.modal_handler_add(self)

            self._mouse_pos = []

            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}

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


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


if __name__ == "__main__":
    register()
$\endgroup$

You must log in to answer this question.

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