OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / sculpt / sculpt_window.py

209 lines · python ref:
import bpy
from ... utils.addons import addon_exists
from ... ui_framework.master import Master
from ... utility import addon
from ... utility.base_modal_controls import Base_Modal_Controls


class Brush_Form:

    def __init__(self):
        self.name = ""
        self.hot_key_display = ""
        self.call_args = None
        self.call_back = None


class HOPS_OT_Sculpt_Ops_Window(bpy.types.Operator):
    """Sculpt Ops Window"""
    bl_idname = "view3d.sculpt_ops_window"
    bl_label = "Sculpt Ops Window"
    bl_options = {"REGISTER", "UNDO"}
    bl_description = """Sculpt Ops"""


    @classmethod
    def poll(cls, context):
        if context.active_object != None:
            if context.active_object.type == 'MESH':
                return True
        return False


    def invoke(self, context, event):

        if context.mode != 'SCULPT':
            bpy.ops.object.mode_set(mode='SCULPT', toggle=False)

        self.exit = False
        self.brushes = []

        # KEY -> Name of brush to call on self.set_op_call
        # VAL -> HotkeyDisplay, Location, Shift, Ctrl, Keys

        self.builtin_brushes = {
            'Draw Sharp'         : ['1',        'builtin_brush', False, False, {'NUMPAD_1', 'ONE'}],
            'Clay'               : ['C',        'builtin_brush', False, False, {'C'}],
            'Clay Thumb'         : ['3',        'builtin_brush', False, False, {'NUMPAD_3', 'THREE'}],
            'Layer'              : ['L',        'builtin_brush', False, False, {'L'}],
            'Blob'               : ['4',        'builtin_brush', False, False, {'NUMPAD_4', 'FOUR'}],
            'Crease'             : ['Shift C',  'builtin_brush', True,  False, {'C'}],
            'Smooth'             : ['S',        'builtin_brush', False, False, {'S'}],
            'Flatten'            : ['Shift T',  'builtin_brush', True,  False, {'T'}],
            'Fill'               : ['5',        'builtin_brush', False, False, {'NUMPAD_5', 'FIVE'}],
            'Multi-plane Scrape' : ['7',        'builtin_brush', False, False, {'NUMPAD_7', 'SEVEN'}],
            'Elastic Deform'     : ['8',        'builtin_brush', False, False, {'NUMPAD_8', 'EIGHT'}],
            'Snake Hook'         : ['K',        'builtin_brush', False, False, {'K'}],
            'Thumb'              : ['9',        'builtin_brush', False, False, {'NUMPAD_9', 'NINE'}],
            'Pose'               : ['0',        'builtin_brush', False, False, {'NUMPAD_0', 'ZERO'}],
            'Nudge'              : ['Shift 1',  'builtin_brush', True,  False, {'NUMPAD_1', 'ONE'}],
            'Rotate'             : ['Shift 2',  'builtin_brush', True,  False, {'NUMPAD_2', 'TWO'}],
            'Slide Relax'        : ['Shift 3',  'builtin_brush', True,  False, {'NUMPAD_3', 'THREE'}],
            'Mask'               : ['Shift 6',  'builtin_brush', True,  False, {'NUMPAD_6', 'SIX'}],
            'Draw Face Sets'     : ['Shift 7',  'builtin_brush', True,  False, {'NUMPAD_7', 'SEVEN'}],
            'box_mask'           : ['B',        'builtin',       False, False, {'B'}],
            'box_hide'           : ['Shift 9',  'builtin',       True,  False, {'NUMPAD_9', 'NINE'}],
            'Clay Strips'        : ['2',        'builtin_brush', False, False, {'NUMPAD_2', 'TWO'}],
            'Cloth'              : ['Shift 4',  'builtin_brush', True,  False, {'NUMPAD_4', 'FOUR'}],
            'Draw'               : ['X',        'builtin_brush', False, False, {'X'}],
            'Grab'               : ['G',        'builtin_brush', False, False, {'G'}],
            'Pinch'              : ['P',        'builtin_brush', False, False, {'P'}],
            'Scrape'             : ['6',        'builtin_brush', False, False, {'NUMPAD_6', 'SIX'}],
            'Inflate'            : ['I',        'builtin_brush', False, False, {'I'}],
            'mesh_filter'        : ['Shift 0',  'builtin',       True,  False, {'NUMPAD_0', 'ZERO'}],
            'cloth_filter'       : ['Y',        'builtin',       True,  False, {'Y'}],
            'Simplify'           : ['Shift 5',  'builtin_brush', True,  False, {'NUMPAD_5', 'FIVE'}],
            'move'               : ['Ctrl 1',   'builtin',       False, True,  {'NUMPAD_1', 'ONE'}],
            'rotate'             : ['R',        'builtin',       False, False, {'R'}],
            'scale'              : ['Ctrl 2',   'builtin',       False, True,  {'NUMPAD_2', 'TWO'}],
            'transform'          : ['T',        'builtin',       False, False, {'T'}],
            'lasso_mask'         : ['Shift B',  'builtin',       True,  False, {'B'}],
            'annotate'           : ['D',        'builtin',       False, False, {'D'}]
            }

        for key, val in self.builtin_brushes.items():
            brush_form = Brush_Form()
            brush_form.name = key.replace('_', ' ').title()
            brush_form.hot_key_display = val[0]
            brush_form.call_args = (val[1], key)
            brush_form.call_back = self.set_op_call
            self.brushes.append(brush_form)

        # Operations
        self.ops = [
            ['Toggle Floor', self.toggle_floor, None],
            ['Toggle Dyno', self.toggle_dyno, None],
            ['Hide Unselected', self.hide_unselected, None],
            ['Unhide All', self.unhide, None]
        ]

        # Base Systems
        self.master = Master(context=context, custom_preset="brush_ops", show_fast_ui=False)
        self.base_controls = Base_Modal_Controls(context, event)
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


    def modal(self, context, event):

        if self.exit == True:
            self.master.run_fade()
            return {'FINISHED'}        

        #######################
        #   Base Systems
        #######################

        self.master.receive_event(event=event)
        self.base_controls.update(context, event)

        #######################
        #   Base Controls
        #######################

        if self.base_controls.pass_through:
            if not self.master.is_mouse_over_ui():
                return {'PASS_THROUGH'}
        elif self.base_controls.cancel:
            if not self.master.is_mouse_over_ui():
                self.master.run_fade()
                return {'CANCELLED'}
        elif self.base_controls.confirm:
            if not self.master.is_mouse_over_ui():
                self.master.run_fade()
                return {'FINISHED'}

        #######################
        #   Brush Events
        #######################
        if event.type != 'TIMER':
            for key, val in self.builtin_brushes.items():
                if event.value == 'PRESS':
                    if event.type in val[4]:
                        if event.shift == val[2]:
                            if event.ctrl == val[3]:
                                self.set_op_call(val[1], key)
                                break

        self.draw_window(context=context)
        context.area.tag_redraw()
        return {'RUNNING_MODAL'}


    def draw_window(self, context):

        self.master.setup()

        menus = {
            'brushes' : self.brushes,
            'header' : [self.toggle_modes, (context,)],
            'ops' : self.ops
        }

        self.master.receive_main(win_dict=menus)
        self.master.finished()


    def set_op_call(self, location="", brush_name=""):
        '''Calls the wm op and tells modal to exit.'''

        call_string = f"{location}.{brush_name}"
        bpy.ops.wm.tool_set_by_id('INVOKE_DEFAULT', name=call_string)
        self.exit = True


    def toggle_modes(self, context):

        if context.mode == 'SCULPT':
            bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
        else:
            bpy.ops.object.mode_set(mode='SCULPT', toggle=False)

    ########################
    #   Operations
    ########################

    def toggle_floor(self):
        if bpy.context.space_data.overlay.show_floor == True:
            bpy.context.space_data.overlay.show_floor = False
            bpy.context.space_data.overlay.show_axis_x = False
            bpy.context.space_data.overlay.show_axis_y = False
        else:
            bpy.context.space_data.overlay.show_floor = True
            bpy.context.space_data.overlay.show_axis_x = True
            bpy.context.space_data.overlay.show_axis_y = True


    def toggle_dyno(self):
        bpy.ops.sculpt.dynamic_topology_toggle()


    def hide_unselected(self):
        bpy.ops.object.hide_view_set(unselected=True)


    def unhide(self):
        bpy.ops.object.hide_view_clear()