OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / utility / base_modal_controls.py

193 lines · python ref:
import bpy
from mathutils import Vector
from .. utility import addon
from .. utils.blender_ui import get_dpi_factor


keyboard_increment  = {'NUMPAD_PLUS', 'EQUAL', 'UP_ARROW'}
keyboard_decrement  = {'NUMPAD_MINUS', 'DOWN_ARROW', 'MINUS'}
confirm_events      = {'SPACE', 'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}
cancel_events       = {'ESC', 'RIGHTMOUSE'}
pass_through_events = {'MIDDLEMOUSE'}
increment_maps      = {'WHEELUPMOUSE', 'NUMPAD_PLUS', 'EQUAL', 'UP_ARROW'}
decrement_maps      = {'WHEELDOWNMOUSE', 'NUMPAD_MINUS', 'DOWN_ARROW', 'MINUS'}
numpad_types        = {'NUMPAD_0', 'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4',
                        'NUMPAD_5', 'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9',
                        'NUMPAD_MINUS', 'NUMPAD_PLUS'}


class Base_Modal_Controls():

    def __init__(self, context, event, popover_keys=[]):

        # States
        self.scroll       = None
        self.confirm      = None
        self.cancel       = None
        self.mouse        = None
        self.tilde        = None
        self.popover      = None
        self.pass_through = False

        # Props
        self.divisor       = 1000
        self.divisor_shift = 10
        self.mouse_region  = Vector((0,0))
        self.mouse_window  = Vector((0,0))
        self.is_industry_standard = bpy.context.preferences.keymap.active_keyconfig == 'Industry_Compatible'

        # Maps
        self.keyboard_increment  = list(keyboard_increment)
        self.keyboard_decrement  = list(keyboard_decrement)
        self.confirm_events      = list(confirm_events)
        self.cancel_events       = list(cancel_events)
        self.pass_through_events = list(pass_through_events)
        self.increment_maps      = list(increment_maps)
        self.decrement_maps      = list(decrement_maps)
        self.popover_keys        = list(popover_keys)
        self.numpad_types        = list(numpad_types)

        # Initialize
        self.__alter_keymaps()
        self.update(context, event)

    # --- STATIC --- #

    @staticmethod
    def tilde(context, event):
        tilde = context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items['hops.tilde_remap']
        return Base_Modal_Controls.keymap_item_reader(tilde, event)

    @staticmethod
    def keymap_item_reader(kmi, event):

        if event.type == kmi.type and event.value == kmi.value:
            result = [True]

            if kmi.any:
                result = [event.ctrl, event.shift, event.alt, event.oskey]
                return any(result)

            if kmi.ctrl or kmi.oskey:
                result.append(event.ctrl)
            if kmi.shift:
                result.append(event.shift)
            if kmi.alt:
                result.append(event.alt)
            if kmi.oskey:
                result.append(event.oskey)
            if kmi.key_modifier != "NONE":
                result.append(event.type == kmi.key_modifier)

            return all (result)

        return False

    # --- PUBLIC --- #

    def update(self, context, event):
        self.mouse        = self.__mouse(event, divisor=self.divisor, divisor_shift= self.divisor_shift)
        self.confirm      = self.__confirm(event)
        self.cancel       = self.__cancel(event)
        self.pass_through = self.__pass_through(event)
        self.scroll       = self.__scroll(event)
        self.popover      = self.__popover(event)
        self.tilde        = Base_Modal_Controls.tilde(context, event)

        self.mouse_region.x = event.mouse_region_x
        self.mouse_region.y = event.mouse_region_y
        self.mouse_window.x = event.mouse_x
        self.mouse_window.y = event.mouse_y

        # Toggle viewport display
        if event.type == "O" and event.value == "PRESS":
            if hasattr(context, 'space_data') and hasattr(context.space_data, 'shading'):
                types = ['WIREFRAME', 'SOLID', 'MATERIAL', 'RENDERED']
                index = types.index(context.space_data.shading.type)
                context.space_data.shading.type = types[(index + 1) % len(types)]

        # Toggle help
        if event.type == "H" and event.value == "PRESS":
            addon.preference().property.hops_modal_help = not addon.preference().property.hops_modal_help

        # Toggle overlays
        if self.tilde and event.shift == True:
            bpy.context.space_data.overlay.show_overlays = not bpy.context.space_data.overlay.show_overlays

    # --- PRIVATE --- #

    def __alter_keymaps(self):

        # Popover
        if addon.preference().keymap.spacebar_accept:
            if 'SPACE' in self.popover_keys:
                self.popover_keys.remove('SPACE')

        if not addon.preference().keymap.rmb_cancel:
            self.cancel_events.remove('RIGHTMOUSE')
            self.pass_through_events.append('RIGHTMOUSE')


    def __mouse(self, event, divisor=1000, divisor_shift=10):
        if event.type not in {'MOUSEMOVE', 'TRACKPADPAN'}:
            return 0

        modal_scale = addon.preference().ui.Hops_modal_scale
        delta = event.mouse_x - event.mouse_prev_x

        if bpy.app.version > (2, 91, 0):
            divisor = divisor / (4.5 / get_dpi_factor())

        delta = modal_scale * delta / divisor / get_dpi_factor()

        if event.shift:
            delta = modal_scale * delta / divisor_shift / get_dpi_factor()

        if addon.preference().property.modal_handedness == 'LEFT':
            return -delta

        return delta


    def __confirm(self, event):
        keys = [k for k in self.confirm_events if k not in self.popover_keys]
        return event.type in keys and event.value == 'PRESS'


    def __cancel(self, event):
        return event.type in self.cancel_events and event.value == 'PRESS'


    def __pass_through(self, event):
        if bpy.context.preferences.inputs.use_mouse_emulate_3_button and event.alt and event.type == 'LEFTMOUSE':
            return True
        if bpy.context.preferences.keymap.active_keyconfig == 'Industry_Compatible' and event.alt and event.type == 'LEFTMOUSE':
            return True
        return event.type in self.pass_through_events or 'NDOF' in event.type


    def __scroll(self, event):

        if event.type == 'TRACKPADPAN':
            delta = event.mouse_y - event.mouse_prev_y
            if abs(delta) < 5: return 0
            if delta > 0: return 1
            elif delta < 0: return -1
            else: return 0

        if event.value != 'PRESS':
            return 0
        if event.type in increment_maps:
            return 1
        if event.type in decrement_maps:
            return -1

        return 0


    def __popover(self, event):
        if event.type in self.popover_keys and event.value == 'PRESS':
            return True
        return False