OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / modifiers / simple_deform.py

289 lines · python ref:
import bpy, math
from mathutils import Vector
from ... utility import modifier
from ... utility.base_modal_controls import Base_Modal_Controls
from ... utility import addon
from ... ui_framework.master import Master
from ... ui_framework.utils.mods_list import get_mods_list

# Cursor Warp imports
from ... utils.toggle_view3d_panels import collapse_3D_view_panels
from ... utils.modal_frame_drawing import draw_modal_frame
from ... utils.cursor_warp import mouse_warp
from ... utility import method_handler


class HOPS_OT_MOD_Simple_deform(bpy.types.Operator):
    bl_idname = "hops.mod_simple_deform"
    bl_label = "Adjust Simple Deform Modifier"
    bl_options = {"REGISTER", "UNDO", "BLOCKING"}
    bl_description = """
LMB - Adjust Deform Modifier
LMB + Ctrl - Add new Deform Modifier
LMB + Ctrl + Shift - Apply Deform Modifiers

Press H for help"""

    deform_objects = {}

    axis: bpy.props.EnumProperty(
        name="Axis",
        description="What axis screw / spin",
        items=[
            ('X', "X", "Screw X axis"),
            ('Y', "Y", "Screw Y axis"),
            ('Z', "Z", "Screw Z axis")
            ],
        default='X')

    @classmethod
    def poll(cls, context):
        return any(o.type == 'MESH' for o in context.selected_objects)

    @staticmethod
    def deform_modifiers(object):
        return [modifier for modifier in object.modifiers if modifier.type == "SIMPLE_DEFORM"]

    def invoke(self, context, event):

        self.fisnish = False
        self.deform_objects = {}

        for object in [o for o in context.selected_objects if o.type == 'MESH']:
            self.get_deform_modifier(object, event)

        if self.fisnish:
            return {'FINISHED'}

        self.active_deform_modifier = context.object.modifiers[self.deform_objects[context.object.name]["modifier"]]
        self.store_values()

        self.snap_buffer = 0
        self.snap_break = 0.1

        # Base Systems
        self.master = Master(context=context)
        self.master.only_use_fast_ui = True
        self.base_controls = Base_Modal_Controls(context, event)
        self.original_tool_shelf, self.original_n_panel = collapse_3D_view_panels()
        self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.safe_draw_shader, (context,), 'WINDOW', 'POST_PIXEL')

        context.window_manager.modal_handler_add(self)
        return {"RUNNING_MODAL"}

    def modal(self, context, event):

        # Base Systems
        self.master.receive_event(event=event)
        self.base_controls.update(context, event)
        mouse_warp(context, event)
        
        if self.base_controls.pass_through:
            return {'PASS_THROUGH'}

        context.area.header_text_set("Hardops Simple Deform:        X/Y/Z : axis - {}      ctrl+X : Lock x - {}       ctrl+Y : Lock y: {}      ctrl+Z : Lock Z: {}".format(self.active_deform_modifier.deform_axis, self.active_deform_modifier.lock_x, self.active_deform_modifier.lock_y, self.active_deform_modifier.lock_z))

        for object_name in self.deform_objects:
            object = bpy.data.objects[object_name]
            modifier = object.modifiers[self.deform_objects[object_name]["modifier"]]

            if event.ctrl:
                self.snap_buffer += self.base_controls.mouse
                if abs(self.snap_buffer) > self.snap_break:
                    increment = math.radians(15)
                    modifier.angle = snap(modifier.angle, increment) + math.copysign(increment, self.snap_buffer)
                    self.snap_buffer = 0
            else:
                modifier.angle += self.base_controls.mouse

            if self.base_controls.scroll:
                if event.shift:
                    if self.base_controls.scroll == 1:
                        bpy.ops.object.modifier_move_up(modifier=modifier.name)
                    else:
                        bpy.ops.object.modifier_move_down(modifier=modifier.name)
                else:
                    deform_method_types = ["TWIST", "BEND", "TAPER", "STRETCH"]
                    modifier.deform_method = deform_method_types[(deform_method_types.index(modifier.deform_method) + self.base_controls.scroll) % len(deform_method_types)]
                    if addon.preference().ui.Hops_extra_info:
                        bpy.ops.hops.display_notification(info=f"Method: {modifier.deform_method}" )

            if event.type == "X" and event.value == "PRESS":
                if event.ctrl:
                    modifier.lock_x = not modifier.lock_x
                else:
                    #modifier.deform_axis = "X"
                    self.axis = "YZX"["XYZ".find(self.axis)]
                    modifier.deform_axis = self.axis
                    if addon.preference().ui.Hops_extra_info:
                        bpy.ops.hops.display_notification(info=f"Screw Axis: {modifier.deform_axis}" )
                    self.report({'INFO'}, f"Screw Axis: {modifier.deform_axis}")

            elif event.type == "Y" and event.value == "PRESS":
                if event.ctrl:
                    modifier.lock_y = not modifier.lock_y
                else:
                    modifier.deform_axis = "Y"
            elif event.type == "Z" and event.value == "PRESS":
                if event.ctrl:
                    modifier.lock_z = not modifier.lock_z
                else:
                    modifier.deform_axis = "Z"

            elif self.base_controls.cancel:
                self.restore()
                context.area.header_text_set(text=None)
                self.remove_shader()
                collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
                self.master.run_fade()
                return {'CANCELLED'}

            elif self.base_controls.confirm:
                context.area.header_text_set(text=None)
                self.remove_shader()
                collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
                self.master.run_fade()
                return {'FINISHED'}

            elif event.type == "Q" and event.value == "PRESS":
                bpy.ops.object.modifier_move_up(modifier=modifier.name)

            elif event.type == "W" and event.value == "PRESS":
                bpy.ops.object.modifier_move_down(modifier=modifier.name)

        self.draw_master(context=context)
        context.area.tag_redraw()
        return {"RUNNING_MODAL"}
        

    def get_deform_modifier(self, object, event):
        if event.ctrl:
            if event.shift:
                modifier.apply(object, types={"SIMPLE_DEFORM"})
                self.fisnish = True
            else:
                self.add_deform_modifier(object)
        else:
            try: self.deform_objects.setdefault(object.name, {})["modifier"] = self.deform_modifiers(object)[-1].name
            except: self.add_deform_modifier(object)

    def add_deform_modifier(self, object):
        deform_modifier = object.modifiers.new(name="SimpleDeform", type="SIMPLE_DEFORM")
        deform_modifier.angle = math.radians(45)
        deform_modifier.deform_method = "TWIST"
        deform_modifier.deform_axis = "X"
        deform_modifier.lock_x = False
        deform_modifier.lock_y = False
        deform_modifier.lock_z = False

        self.deform_objects.setdefault(object.name, {})["modifier"] = deform_modifier.name
        self.deform_objects[object.name]["added_modifier"] = True

    def store_values(self):
        for object_name in self.deform_objects:
            object = bpy.data.objects[object_name]
            modifier = object.modifiers[self.deform_objects[object_name]["modifier"]]
            self.deform_objects[object_name]["show_viewport"] = modifier.show_viewport
            self.deform_objects[object_name]["angle"] = modifier.angle
            self.deform_objects[object_name]["deform_method"] = modifier.deform_method
            self.deform_objects[object_name]["deform_axis"] = modifier.deform_axis
            self.deform_objects[object_name]["lock_x"] = modifier.lock_x
            self.deform_objects[object_name]["lock_y"] = modifier.lock_y
            self.deform_objects[object_name]["lock_z"] = modifier.lock_z

    def restore(self):
        for object_name in self.deform_objects:
            object = bpy.data.objects[object_name]
            if "added_modifier" in self.deform_objects[object_name]:
                object.modifiers.remove(object.modifiers[self.deform_objects[object_name]["modifier"]])
            else:
                modifier = object.modifiers[self.deform_objects[object_name]["modifier"]]
                modifier.show_viewport = self.deform_objects[object_name]["show_viewport"]
                modifier.angle = self.deform_objects[object_name]["angle"]
                modifier.deform_method = self.deform_objects[object_name]["deform_method"]
                modifier.deform_axis = self.deform_objects[object_name]["deform_axis"]
                modifier.lock_x = self.deform_objects[object_name]["lock_x"]
                modifier.lock_y = self.deform_objects[object_name]["lock_y"]
                modifier.lock_z = self.deform_objects[object_name]["lock_z"]

    def draw_master(self, context):

        # Start
        self.master.setup()

        ########################
        #   Fast UI
        ########################

        if self.master.should_build_fast_ui():

            # Main
            win_list = []
            if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1: # Fast Floating
                win_list.append("{}".format(self.active_deform_modifier.deform_axis))
                win_list.append("{:.3f}".format(math.degrees(self.active_deform_modifier.angle)))
                win_list.append("{}".format(self.active_deform_modifier.deform_method))
            else:
                win_list.append("Simple Deform")
                win_list.append("{}".format(self.active_deform_modifier.deform_axis))
                win_list.append("Angle: {:.3f}".format(math.degrees(self.active_deform_modifier.angle)))
                win_list.append("Method: {}".format(self.active_deform_modifier.deform_method))

            # Help
            help_items = {"GLOBAL" : [], "STANDARD" : []}

            help_items["GLOBAL"] = [
                ("M", "Toggle mods list"),
                ("H", "Toggle help"),
                ("~", "Toggle UI Display Type"),
                ("O", "Toggle viewport rendering")]

            help_items["STANDARD"] = [
                ("Scroll",         "Set deform method"),
                ("Ctrl",           "Snap to 5*"),
                ("X",              "Set axis/ ctrl - lock axis"),
                ("Y",              "Set axis/ ctrl - lock axis"),
                ("Z",              "Set axis/ ctrl - lock axis"),
                ("Q",              "Move mod DOWN"),
                ("W",              "Move mod UP"),
                ("Shift + Scroll", "Move mod up/down")]

            # Mods
            active_mod = ""
            if self.active_deform_modifier != None:
                active_mod = self.active_deform_modifier.name
            mods_list = get_mods_list(mods=bpy.context.active_object.modifiers)

            self.master.receive_fast_ui(win_list=win_list, help_list=help_items, image="AdjustBevel", mods_list=mods_list, active_mod_name=active_mod)

        # Finished
        self.master.finished()

    ####################################################
    #   CURSOR WARP
    ####################################################

    def safe_draw_shader(self, context):
        method_handler(self.draw_shader,
            arguments = (context,),
            identifier = 'UI Framework',
            exit_method = self.remove_shader)


    def remove_shader(self):
        '''Remove shader handle.'''

        if self.draw_handle:
            self.draw_handle = bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW")


    def draw_shader(self, context):
        '''Draw shader handle.'''

        draw_modal_frame(context)


def snap(value, increment):
        result = round(value / increment) * increment
        return result