OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / modals / modifier_scroll.py

371 lines · python ref:
import bpy
from math import radians, degrees
from bpy.types import Operator
from bpy.props import StringProperty, BoolProperty
from . import infobar
from ... utility import addon, modifier
from ... ui_framework.master import Master
from ... ui_framework.utils.mods_list import get_mods_list
from ... utility.base_modal_controls import Base_Modal_Controls
from ... utility.collections import turn_on_parent_collections
from ... utils.toggle_view3d_panels import collapse_3D_view_panels


class HOPS_OT_ModifierScroll(Operator):
    bl_idname = "hops.modifier_scroll"
    bl_label = "Scroll Modifiers"
    bl_description = """Modifier Scroll Modal
    
    Use the scroll wheel to scroll through modifiers on the selected object
    *Essential for troubleshooting*

    CTRL click on close applies to that point
    SHIFT same as ctrl but duplicate
    """

    running: bool = False
    all: BoolProperty()
    type: StringProperty(default='BOOLEAN')
    additive : BoolProperty()


    def invoke(self, context, event):

        self.call_mesh_wire = True
        self.object_name = ''

        self.base_controls = Base_Modal_Controls(context, event)

        if event.shift:
            self.all = True

        if not self.all:
            self.modifiers = [mod for mod in context.active_object.modifiers if mod.type == self.type]
        else:
            self.modifiers = [mod for mod in context.active_object.modifiers]

        self.mods = {None: None}
        self.index = 0
        self.all_mods = False
        self.loop = False
        self.original_obj = context.active_object

        for mod in self.modifiers:
            if self.type == 'BOOLEAN' and not self.all and mod.show_render:
                self.mods.update({
                    mod: {
                        "original_show_viewport": mod.show_viewport,
                        "override": False,
                        "hide": mod.object.hide_viewport}})
            elif mod.show_render:
                self.mods.update({
                    mod: {
                        "original_show_viewport": mod.show_viewport,
                        "override": False,
                        "hide": False}})

        if len(self.mods) == 1:
            return {'CANCELLED'}

        #UI System
        self.master = Master(context=context)
        self.master.only_use_fast_ui = True
        self.original_tool_shelf, self.original_n_panel = collapse_3D_view_panels()

        context.window_manager.modal_handler_add(self)
        infobar.initiate(self)
        return {'RUNNING_MODAL'}


    def modal(self, context, event):

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

        if self.base_controls.pass_through:
            return {'PASS_THROUGH'}

        modtoggle = False
        current_bool = None

        if self.base_controls.scroll:
            if event.shift:
                if self.mods[list(self.mods.keys())[self.index]] is not None:
                    mod = list(self.mods.keys())[self.index]
                    if self.base_controls.scroll ==1:
                        bpy.ops.object.modifier_move_up(modifier=mod.name)
                    else:
                        bpy.ops.object.modifier_move_down(modifier=mod.name)
            else:
                self.index += self.base_controls.scroll

        if self.loop:
            if self.index >= len(self.mods):
                self.index = 0
            if self.index < 0:
                self.index = len(self.mods) - 1

        else:
            self.index = max(min(self.index, len(self.mods) - 1), 0)

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

        if event.type == 'L' and event.value == 'PRESS':
            self.loop = not self.loop

        if event.type == 'A' and event.value == 'RELEASE':
            if self.mods[list(self.mods.keys())[self.index]] is not None:
                override_value = self.mods[list(self.mods.keys())[self.index]]["override"]
                self.mods[list(self.mods.keys())[self.index]]["override"] = not override_value

                mod = list(self.mods.keys())[self.index]
                if mod != None:
                    if mod.type == 'BOOLEAN':
                        if mod.object != None:
                            bpy.ops.hops.draw_wire_mesh_launcher(object_name=mod.object.name)


        self.original_obj.select_set(True)
        context.view_layer.objects.active = self.original_obj

        if event.type == "W" and event.value == "PRESS" and event.shift and self.mods[list(self.mods.keys())[self.index]] is not None:
            mod = list(self.mods.keys())[self.index]
            mod.show_render = not mod.show_render
            self.report({'INFO'}, F'Modifiers Renderability : {mod.show_render}')

        if event.type == "W" and event.value == "PRESS" and event.ctrl and not event.shift and self.mods[list(self.mods.keys())[self.index]] is not None:
            mod = list(self.mods.keys())[self.index]
            mod.show_render = not mod.show_render
            modtoggle = mod.show_render
            for mod in context.object.modifiers:
                mod.show_render = modtoggle
            modtoggle = not modtoggle
            self.report({'INFO'}, F'Modifier Renderability Re-enabled : {len(self.modifiers)}')

        # if event.type == "W" and event.value == "PRESS" and event.alt and not event.shift and not event.ctrl and self.mods[list(self.mods.keys())[self.index]] is not None:
        #     mod = list(self.mods.keys())[self.index]
        #     mod.show_viewport = not mod.show_viewport
        #     modtoggle = mod.show_viewport
        #     for mod in context.object.modifiers:
        #         mod.show_viewport = modtoggle
        #     modtoggle = not modtoggle
        #     self.report({'INFO'}, F'Modifier Visibility Re-enabled : {len(self.modifiers)}')

        if event.type == 'M' and event.value == 'PRESS' and event.shift == True:

            self.all_mods = not self.all_mods
            self.mods = {None: None}
            self.index = 0

            for mod in self.mods:
                if mod is not None:
                    mod.show_viewport = self.mods[mod]["original_show_viewport"]

            for mod in context.object.modifiers:
                if self.all_mods:
                    self.mods.update({mod: {"original_show_viewport": mod.show_viewport, "override": False}})

        if self.additive or event.shift:
            for count, mod in enumerate(self.mods):
                if mod is not None:
                    if count <= self.index:
                        mod.show_viewport = True
                    else:
                        mod.show_viewport = False
                    if self.mods[mod]["override"] or self.mods[list(self.mods.keys())[self.index]] is None:
                        mod.show_viewport = False

        else:
            for mod in self.mods:
                if mod and hasattr(mod, "object") and mod.object:
                    mod.show_viewport = self.mods[mod]["override"]  # hide all mods except for those pressed A on.
                    if not self.all or self.type == 'BOOLEAN':
                        mod.object.select_set(False)
                        mod.object.hide_viewport = True

                    if mod.show_viewport:  # show and select objects that have modifier being shown in viewport.
                        mod.object.hide_viewport = False

                        if not self.all or self.type == 'BOOLEAN':
                            mod.object.select_set(True)

            if self.mods[list(self.mods.keys())[self.index]] is not None:
                current_bool = list(self.mods.keys())[self.index]
                current_bool.show_viewport = True  # show viewport on the current mod.
                if hasattr(current_bool, "object"):
                    turn_on_parent_collections(current_bool.object, context.scene.collection)
                    current_bool.object.hide_viewport = False
                    current_bool.object.select_set(True)

        # Set show wire
        mod = list(self.mods.keys())[self.index]
        if mod != None:
            if mod.type == 'BOOLEAN':
                if mod.object != None:
                    if self.object_name != mod.object.name:
                        self.call_mesh_wire = True
                        self.object_name = mod.object.name
            else:
                self.object_name = ""
        else:
            self.object_name = ""

        if self.call_mesh_wire == True:
            self.call_mesh_wire = False
            bpy.ops.hops.draw_wire_mesh_launcher(object_name=self.object_name)

        if self.base_controls.confirm or event.type in { 'RET', 'NUMPAD_ENTER'}:
            if event.ctrl and self.all:
                modifier.apply(self.original_obj, visible=True)

                for mod in self.original_obj.modifiers:
                    mod.show_viewport = True

                bpy.ops.hops.display_notification(info="Applied visible modifiers")
                bpy.ops.hops.draw_wire_mesh_launcher('INVOKE_DEFAULT', target='SELECTED')
                self.report({'INFO'}, "Applied visible modifiers")

            elif event.shift and self.all:
                duplicate = self.original_obj.copy()
                duplicate.data = self.original_obj.data.copy()

                for col in self.original_obj.users_collection:
                    if col not in duplicate.users_collection:
                        col.objects.link(duplicate)

                modifier.apply(duplicate, visible=True)
                duplicate.modifiers.clear()

                for mod in self.original_obj.modifiers:
                    mod.show_viewport = True

                context.view_layer.objects.active = duplicate
                self.original_obj.select_set(False)
                bpy.ops.hops.draw_wire_mesh_launcher('INVOKE_DEFAULT', target='SELECTED')
                bpy.ops.hops.display_notification(info="Applied visible modifiers on duplicate")
                self.report({'INFO'}, "Applied visible modifiers on duplicate")

            else:
                for mod in self.mods:
                    if mod is not None and hasattr(mod, "object"):
                        if not mod.show_viewport:
                            if not self.all or self.type == 'BOOLEAN' and mod.object:
                                mod.object.hide_viewport = True

                        elif not self.all or self.type == 'BOOLEAN' and mod.object:
                            turn_on_parent_collections(mod.object, context.scene.collection)
                            mod.object.hide_viewport = False
                            mod.object.select_set(True)

                if not self.additive:
                    if hasattr(current_bool, "object") and current_bool.object is not None:
                        turn_on_parent_collections(context.active_object, context.scene.collection)
                        context.active_object.select_set(False)
                        current_bool.object.select_set(True)
                        context.view_layer.objects.active = current_bool.object

            collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
            self.master.run_fade()
            infobar.remove(self)
            return {'FINISHED'}

        if self.base_controls.cancel or event.type in { 'BACK_SPACE'}:

            for mod in self.mods:
                if mod is not None:
                    mod.show_viewport = self.mods[mod]["original_show_viewport"]

            collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
            self.master.run_fade()
            infobar.remove(self)
            return {'CANCELLED'}

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


    def draw_master(self, context):

        # Start
        self.master.setup()
        #   Fast UI
        if self.master.should_build_fast_ui():
            
            # Main
            win_list = []
            active_mod = ""
            if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1:
                #win_list.append("Modifier Scroll")

                if self.index == 0:
                    win_list.append("Unmodified Mesh")
                else:
                    win_list.append(str(self.index))

                if list(self.mods.keys())[self.index] is not None:
                    mod = list(self.mods.keys())[self.index]
                    # Mod name
                    active_mod = mod.name
                    win_list.append("{}".format(mod.name))
                    #if hasattr(mod, "object") and mod.object:
                    #    win_list.append("{}".format(mod.object.name))
                else:
                    win_list.append("Modifiers Disabled")
            else:
                win_list.append("Modifier Scroll (Classic)")

                if self.index == 0:
                    win_list.append("Unmodified Mesh")

                else:
                    win_list.append(str(self.index))

                if list(self.mods.keys())[self.index] is not None:
                    mod = list(self.mods.keys())[self.index]
                    # Mod name
                    active_mod = mod.name
                    win_list.append("{}".format(mod.name))
                    if hasattr(mod, "object") and mod.object:
                        win_list.append("{}".format(mod.object.name))
                    win_list.append("Render Visibility :" + str(mod.show_render))
                else:
                    win_list.append("Modifiers Disabled")

            # 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"] = [
                ("Shift + Scroll", "Move mod up/down"),
                ("Ctrl + W",  "(all) Toggle Renderability / Sort Lock"),
                ("Scroll",    "Change boolean visibility"),
                ("Shift + LMB",    "Apply visible mods on duplicate"),
                ("Ctrl + LMB",     "Apply visible mods"),
                ("A",         "Toggle current visibility"),
                ("M ",        "Use only booleans / all modifiers"),
                ("L",         "Toggle looping"),
                ("Z",         "Wire display"),
                ("W",         "Toggle Renderability / Sort Lock")]

            # Mods
            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="LateParent",
                mods_list=mods_list,
                active_mod_name=active_mod)

        # Finished
        self.master.finished()