OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / modifiers / l_smooth.py

316 lines · python ref:
import bpy, bmesh
from ... utility import addon
from ... utility import modifier
from ... utility.base_modal_controls import Base_Modal_Controls
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_LSmooth(bpy.types.Operator):
    bl_idname = "hops.mod_lsmooth"
    bl_label = "Adjust Smooth Modifier"
    bl_options = {'REGISTER', 'UNDO', 'GRAB_CURSOR'}
    bl_description = """
LMB - Adjust Laplacian Smooth Modifier
LMB + Ctrl - Create new Smooth Modifier
LMB + Shift - Auto Vertex Group

Press H for help"""


    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return obj and obj.type == 'MESH' and obj.mode == 'OBJECT'


    def invoke(self, context, event):

        self.create_new = event.ctrl
        self.auto_vgroup = event.shift
        self.modal_scale = addon.preference().ui.Hops_modal_scale
        self.obj = context.active_object
        self.mods = [m for m in self.obj.modifiers if m.type == 'LAPLACIANSMOOTH']

        if not self.mods:
            self.create_new = True

        if self.create_new:
            self.mod = self.obj.modifiers.new("Laplacian Smooth", 'LAPLACIANSMOOTH')
            self.mods.append(self.mod)

        else:
            self.mod = self.mods[-1]

        self.values = {m:{} for m in self.mods}

        for mod in self.mods:
            self.store(mod)

        if self.auto_vgroup:
            self.create_vgroup()

        self.buffer = self.mod.lambda_factor

        # 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'}

        elif event.type == 'Z' and (event.shift or event.alt):
            return {'PASS_THROUGH'}

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

        elif self.base_controls.cancel:
            context.area.header_text_set(text=None)
            self.report({'INFO'}, "Cancelled")
            self.cancel(context)
            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.scroll and event.ctrl:
            self.scroll(scroll)
            self.buffer = self.mod.lambda_factor

        elif event.type == 'MOUSEMOVE':

            self.buffer += self.base_controls.mouse
            self.buffer = max(self.buffer, 0)
            digits = 2 if event.ctrl and event.shift else 1 if event.ctrl else 3
            self.mod.lambda_factor = round(self.buffer, digits)

        elif self.base_controls.scroll:
            if event.shift:
                if self.base_controls.scroll ==1 :
                    bpy.ops.object.modifier_move_up(modifier=self.mod.name)
                else:
                    bpy.ops.object.modifier_move_down(modifier=self.mod.name)

            else:
                self.mod.iterations += self.base_controls.scroll

            self.mod.iterations = max(self.mod.iterations, 0)

        elif event.type in ('X', 'Y', 'Z') and event.ctrl:
            if event.type == 'X' and event.value == 'PRESS':
                self.mod.use_x = not self.mod.use_x
            elif event.type == 'Y' and event.value == 'PRESS':
                self.mod.use_y = not self.mod.use_y
            elif event.type == 'Z' and event.value == 'PRESS':
                self.mod.use_z = not self.mod.use_z

        elif event.type in ('X', 'Y', 'Z'):
            if event.type == 'X' and event.value == 'PRESS':
                self.mod.use_x = True
                self.mod.use_y = False
                self.mod.use_z = False
            elif event.type == 'Y' and event.value == 'PRESS':
                self.mod.use_x = False
                self.mod.use_y = True
                self.mod.use_z = False
            elif event.type == 'Z' and event.value == 'PRESS':
                self.mod.use_x = False
                self.mod.use_y = False
                self.mod.use_z = True

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

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

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


    def cancel(self, context):
        for mod in self.mods:
            self.reset(mod)

        if self.create_new:
            self.obj.modifiers.remove(self.mods[-1])


    def scroll(self, direction):
        index = self.mods.index(self.mod)
        index = (index + direction) % len(self.mods)
        self.mod = self.mods[index]


    def store(self, mod):
        self.values[mod]["lambda_factor"] = mod.lambda_factor
        self.values[mod]["iterations"] = mod.iterations
        self.values[mod]["use_x"] = mod.use_x
        self.values[mod]["use_y"] = mod.use_y
        self.values[mod]["use_z"] = mod.use_z


    def reset(self, mod):
        mod.lambda_factor = self.values[mod]["lambda_factor"]
        mod.iterations = self.values[mod]["iterations"]
        mod.use_x = self.values[mod]["use_x"]
        mod.use_y = self.values[mod]["use_y"]
        mod.use_z = self.values[mod]["use_z"]


    def create_vgroup(self):
        vertex_group = None

        for group in self.obj.vertex_groups:
            if group.name == "HOPS_L_Smooth":
                vertex_group = group

        if not vertex_group:
            vertex_group = self.obj.vertex_groups.new(name="HOPS_L_Smooth")

        verts = list(range(len(self.obj.data.vertices)))
        vertex_group.add(index=verts, weight=1.0, type='REPLACE')

        bm = bmesh.new()
        bm.from_mesh(self.obj.data)

        if bpy.app.version[0] >= 4:
            bevel = bm.edges.layers.float.get('bevel_weight_edge')
            if bevel is None:
                bevel = bm.edges.layers.float.new('bevel_weight_edge')
        else:
            bevel = bm.edges.layers.bevel_weight.verify()  

        if bpy.app.version[0] >= 4:
            crease = bm.edges.layers.float.get('crease_edge')
            if crease is None:
                crease = bm.edges.layers.float.new('crease_edge')
        else:
            crease = bm.edges.layers.crease.verify()

        verts = []
        for v in bm.verts:
            if v.is_boundary:
                verts.append(v.index)
                continue

            for e in v.link_edges:
                if e.seam or not e.smooth or e[bevel] != 0.0 or e[crease] != 0.0:
                    verts.append(v.index)
                    continue

        vertex_group.remove(index=verts)
        bpy.ops.hops.display_notification(info="LSmooth - Auto Vgroup")
        self.mod.vertex_group = "HOPS_L_Smooth"


    def draw_master(self, context):

        # Start
        self.master.setup()


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


        if self.master.should_build_fast_ui():

            # Main
            win_list = []
            mods = []
            if self.mod.use_x:
                mods.append('X')
            if self.mod.use_y:
                mods.append('Y')
            if self.mod.use_z:
                mods.append('Z')

            if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1: #Fast Floating
                win_list.append(f"{self.mod.iterations}")
                win_list.append(f"{self.mod.lambda_factor:.3f}")
                win_list.append(f"{mods}")
            else:
                win_list.append("Laplacian Smooth")
                win_list.append(f"{self.mod.iterations}")
                win_list.append(f"Factor: {self.mod.lambda_factor:.3f}")
                win_list.append(f"Axis: {mods}")

            # 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 iterations"),
                ("Shift + Scroll", "Move mod up/down"),
                ("X, Y, Z",        "Set axis"),
                ("Ctrl + X, Y, Z", "Add axis"),
                ("Q",          "Move mod DOWN"),
                ("W",          "Move mod UP")]
  
            # Mods
            active_mod = ""
            if self.mod != None:
                active_mod = self.mod.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)