OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / modifiers / weld.py

303 lines · python ref:
import bpy, math
from mathutils import Vector
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


DESC = """LMB - Adjust Weld Modifier
LMB + CTRL - Add new Weld Modifier

Press H for help
"""

class HOPS_OT_MOD_Weld(bpy.types.Operator):
    bl_idname = "hops.mod_weld"
    bl_label = "Adjust Weld Modifier"
    bl_options = {"REGISTER", "UNDO", "BLOCKING"}
    bl_description = DESC

    weld_objects = {}

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

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


    def invoke(self, context, event):

        self.objects = [o for o in context.selected_objects if o.type == 'MESH']
        self.object = context.active_object if context.active_object else self.objects[0]
        self.modal_scale = addon.preference().ui.Hops_modal_scale
        self.weld_objects = {}
        self.snap_buffer = 0
        self.snap_break = 0.1

        for object in self.objects:
            self.get_weld_modifier(context, object, event)
            object.show_wire = True
            object.show_all_edges = True

        self.store_values()

        self.active_weld_modifier = context.object.modifiers[self.weld_objects[context.object.name]["modifier"]]

        for object in self.objects:
            modifier.sort(object, types=['WEIGHTED_NORMAL'], last=True)

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

        offset = self.base_controls.mouse / 10
        if not event.ctrl:
            merge_threshold_offset = offset
        else:
            merge_threshold_offset = 0
        context.area.header_text_set("Hardops weld:     merge threshold: {:.4f}".format(self.active_weld_modifier.merge_threshold))

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

            # if event.ctrl:
            #     self.snap_buffer += self.base_controls.mouse
            #     if abs(self.snap_buffer) > self.snap_break:
            #         modifier.max_interactions = snap(modifier.max_interactions, 1) + math.copysign(1, self.snap_buffer)
            #         self.snap_buffer = 0

            # else:
            modifier.merge_threshold = self.weld_objects[object_name]["buffer_threshold"] + merge_threshold_offset
            self.weld_objects[object_name]["buffer_threshold"] = modifier.merge_threshold

            if self.base_controls.scroll:
                if event.shift and event.ctrl:
                    self.cycle_vgroup(object, modifier, forward=self.base_controls.scroll)

                elif 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:
                    modifier.merge_threshold += 0.005 * self.base_controls.scroll
                    self.weld_objects[object_name]["buffer_threshold"] = modifier.merge_threshold

            if event.type == "Z" and event.value == "PRESS":
                object.show_wire = False if object.show_wire else True
                object.show_all_edges = True if object.show_wire else False
                # self.report({'INFO'}, F'Show Wire : {object.show_all_edges}')

            if self.base_controls.cancel:
                object.show_wire = False
                object.show_all_edges = False
                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'}

            if self.base_controls.confirm:
                object.show_wire = False
                object.show_all_edges = False
                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'}

            if event.type == 'A' and event.value == 'PRESS':
                self.toggle_mod_mode(modifier)


        self.active_weld_modifier = self.object.modifiers[self.weld_objects[self.object.name]["modifier"]]

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


    def toggle_mod_mode(self, mod):
        if not hasattr(mod, 'mode'): return
        if mod.mode == 'CONNECTED':
            mod.mode = 'ALL'
        elif mod.mode == 'ALL':
            mod.mode = 'CONNECTED'


    def cycle_vgroup(self, obj, mod, forward=True):

        if not obj.vertex_groups:
            bpy.ops.hops.display_notification(info="No V-Groups")
            return
        groups = [v.name for v in obj.vertex_groups]
        index = 0
        if mod.vertex_group not in groups:
            mod.vertex_group = groups[0]
            bpy.ops.hops.display_notification(info=f"V-Group Assigned : {groups[0]}")
            return
        index = groups.index(mod.vertex_group)
        value = 1 if forward else -1
        group = groups[(index + value) % len(groups)]
        mod.vertex_group = group

        bpy.ops.hops.display_notification(info=f"V-Group Assigned : {group}")


    def get_weld_modifier(self, context, object, event):
        if event.ctrl:
            mod = self.add_weld_modifier(context, object)
        else:
            try: self.weld_objects.setdefault(object.name, {})["modifier"] = self.weld_modifiers(object)[-1].name
            except: self.add_weld_modifier(context, object)


    def add_weld_modifier(self, context, object):

        weld_modifier = object.modifiers.new(name="Weld", type="WELD")
        weld_modifier.merge_threshold = 0.0001
        #weld_modifier.max_interactions = 0

        if context.mode == 'EDIT_MESH':
            vg = object.vertex_groups.new(name='HardOps')
            bpy.ops.object.vertex_group_assign()
            weld_modifier.vertex_group = vg.name

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


    def store_values(self):
        for object_name in self.weld_objects:
            object = bpy.data.objects[object_name]
            modifier = object.modifiers[self.weld_objects[object_name]["modifier"]]
            self.weld_objects[object_name]["show_viewport"] = modifier.show_viewport
            self.weld_objects[object_name]["merge_threshold"] = modifier.merge_threshold
            #self.weld_objects[object_name]["max_interactions"] = modifier.max_interactions
            self.weld_objects[object_name]["buffer_threshold"] = modifier.merge_threshold
            #self.weld_objects[object_name]["buffer_interactions"] = modifier.max_interactions
            self.weld_objects[object_name]["vertex_group"] = modifier.vertex_group


    def restore(self):
        for object_name in self.weld_objects:
            object = bpy.data.objects[object_name]
            if "added_modifier" in self.weld_objects[object_name]:
                object.modifiers.remove(object.modifiers[self.weld_objects[object_name]["modifier"]])
            else:
                modifier = object.modifiers[self.weld_objects[object_name]["modifier"]]
                modifier.show_viewport = self.weld_objects[object_name]["show_viewport"]
                modifier.merge_threshold = self.weld_objects[object_name]["merge_threshold"]
                modifier.vertex_group = self.weld_objects[object_name]["vertex_group"]
                #modifier.max_interactions = self.weld_objects[object_name]["max_interactions"]
        self.original_viewport = bpy.context.space_data.overlay.show_overlays
        self.show_wireframes = bpy.context.space_data.overlay.show_wireframes


    def interface(self, context):
        self.master.setup()

        if self.master.should_build_fast_ui():

            v_group = self.active_weld_modifier.vertex_group

            # Main
            win_list = []
            if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1: #Fast Floating
                win_list.append("{:.4f}".format(self.active_weld_modifier.merge_threshold))
                #win_list.append("{}".format(self.active_weld_modifier.max_interactions))
            else:
                win_list.append("Weld")
                win_list.append("Threshold: {:.4f}".format(self.active_weld_modifier.merge_threshold))
                if v_group:
                    win_list.append(f"V-Group: {v_group}")
                if hasattr(self.active_weld_modifier, 'mode'):
                    win_list.append(f"Mode: {self.active_weld_modifier.mode}")
                #win_list.append("Duplicate Limit: {}".format(self.active_weld_modifier.max_interactions))

            # 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"] = [
                ("Move",           "Set merge threshold"),
                ("Shift + Scroll", "Move mod up/down"),
                ("Shift + Ctrl + Scroll",  "Cycles V-Groups : " + str(v_group) if v_group else "Cycle V-Groups"),
                ("Shift + Q",      "Move mod down"),
                ("Shift + W",      "Move mod up"),
                ("Z",              "Toggle wire display")]

            if hasattr(self.active_weld_modifier, 'mode'):
                help_items["STANDARD"].append(("A", "Toggle Mode"))

            # Mods
            active_mod = ""
            if self.active_weld_modifier != None:
                active_mod = self.active_weld_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="BevelMultiply", mods_list=mods_list, active_mod_name=active_mod)

        self.master.finished()

    # --- SHADER --- #

    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