OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / modals / reset_axis.py

266 lines · python ref:
import bpy, bmesh
from collections import OrderedDict
from ... utility import addon
from ...ui_framework.master import Master
from ...ui_framework.utils.mods_list import get_mods_list
from . import infobar
from ... utility.base_modal_controls import Base_Modal_Controls
from ... utils.objects import set_active


class HOPS_OT_Align_Objs(bpy.types.Operator):
    bl_idname = "hops.align_objs"
    bl_label = "Hops Align Objects"
    bl_description = """Align Objects

    Align Objects

    """
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return context.active_object and len(context.selected_objects) > 1


    def execute(self, context):
        active = context.active_object
        selection = [obj for obj in context.selected_objects if obj != active]

        loc, rot, sca = active.matrix_world.decompose()
        for obj in selection:
            obj.location = loc
            obj.rotation_euler = rot.to_euler()

        return {'FINISHED'}


class HOPS_OT_ResetAxisModal(bpy.types.Operator):
    bl_idname = "hops.reset_axis_modal"
    bl_label = "Hops Reset Axis"
    bl_description = """ Reset / Flatten

    Reset object on selected axis.

    Object - Resets object axis globally
        *two object axis supported*
    Edit - flatten selection to axis or snap to cursor

    """
    bl_options = {"REGISTER", "UNDO"}


    def invoke(self, context, event):

        self.setup(context)

        self.base_controls = Base_Modal_Controls(context, event)
        self.master = Master(context)
        self.master.only_use_fast_ui = True

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


    def setup(self, context):
        if context.active_object.mode == "EDIT":
            self.edit_meshes = []

            for obj in context.objects_in_mode_unique_data:
                bm = bmesh.from_edit_mesh(obj.data)
                bm.verts.ensure_lookup_table()
                selection_back = [(v.index, v.co[:]) for v in bm.verts if v.select]
                self.edit_meshes.append((obj.data, selection_back))

        self.active_obj_original_location = [i for i in context.active_object.matrix_world.translation]
        self.original_locations = [[i for i in obj.matrix_world.translation] for obj in context.selected_objects]
        self.axises = []
        self.set_axis = ""
        self.xyz = ["X", "Y", "Z"]
        self.xyz_index = -1


    def modal(self, context, event):

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

        if self.base_controls.cancel:
            self.reset_object()
            self.master.run_fade()
            infobar.remove(self)
            return {'CANCELLED'}

        elif self.base_controls.confirm:
            self.selection_exit(context)
            self.master.run_fade()
            infobar.remove(self)
            return {'FINISHED'}

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

        elif self.base_controls.scroll:
            self.reset_object()
            self.xyz_index -= self.base_controls.scroll
            self.xyz_index = min(max(-1, self.xyz_index), 2)
            if self.xyz_index == -1:
                self.set_axis = "RESET"
            else:
                self.set_axis = self.xyz[self.xyz_index]

        elif event.type == 'C' and event.value == 'PRESS':
            if event.shift:
                if self.set_axis == "CO":
                    self.set_axis = "RESET"
                else:
                    self.set_axis = "CO"
                    bpy.ops.view3d.snap_selected_to_cursor(use_offset=True)
                    context.view_layer.objects.active.select_set(True)
                    self.report({'INFO'}, F'Snapped to: Cursor (offset)')
            else:
                if self.set_axis == "C":
                    self.set_axis = "RESET"
                else:
                    self.set_axis = "C"
                    bpy.ops.view3d.snap_selected_to_cursor(use_offset=False)
                    context.view_layer.objects.active.select_set(True)
                    self.report({'INFO'}, F'Snapped to: Cursor')

        elif event.type == 'X' and event.value == 'PRESS':
            if self.set_axis == "X":
                self.set_axis = "RESET"
            else:
                self.set_axis = "X"
                self.report({'INFO'}, F'Snapped to: X Axis')
            self.axises.append("X")

        elif event.type == 'Y' and event.value == 'PRESS':
            if self.set_axis == "Y":
                self.set_axis = "RESET"
            else:
                self.set_axis = "Y"
                self.report({'INFO'}, F'Snapped to: Y Axis')
            self.axises.append("Y")

        elif event.type == 'Z' and event.value == 'PRESS':
            if self.set_axis == "Z":
                self.set_axis = "RESET"
            else:
                self.set_axis = "Z"
                self.report({'INFO'}, F'Snapped to: Z Axis')
            self.axises.append("Z")

        elif event.type == 'A' and event.value == 'PRESS':
            if HOPS_OT_Align_Objs.poll(context):
                bpy.ops.hops.align_objs('INVOKE_DEFAULT')
                self.setup(context)

        elif self.base_controls.tilde and event.shift == True:
            bpy.context.space_data.overlay.show_overlays = not bpy.context.space_data.overlay.show_overlays

        if context.active_object.mode == "OBJECT":
            for obj in context.selected_objects:
                reset_to = [0, 0, 0]
                if len(context.selected_objects) > 1:
                    reset_to = self.active_obj_original_location
                if self.set_axis == "X":
                    obj.matrix_world.translation[0] = reset_to[0]
                elif self.set_axis == "Y":
                    obj.matrix_world.translation[1] = reset_to[1]
                elif self.set_axis == "Z":
                    obj.matrix_world.translation[2] = reset_to[2]

        elif context.active_object.mode == "EDIT":
            merge = context.scene.tool_settings.use_mesh_automerge
            context.scene.tool_settings.use_mesh_automerge = False

            if self.set_axis == "X":
                bpy.ops.transform.resize(value=(0, 1, 1), orient_type='GLOBAL', orient_matrix_type='GLOBAL', constraint_axis=(True, False, False))
            elif self.set_axis == "Y":
                bpy.ops.transform.resize(value=(1, 0, 1), orient_type='GLOBAL', orient_matrix_type='GLOBAL', constraint_axis=(False, True, False))
            elif self.set_axis == "Z":
                bpy.ops.transform.resize(value=(1, 1, 0), orient_type='GLOBAL', orient_matrix_type='GLOBAL', constraint_axis=(False, False, True))

            context.scene.tool_settings.use_mesh_automerge = merge

        if self.set_axis == "RESET":
            self.reset_object()

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


    def reset_object(self):
        if bpy.context.active_object.mode == "EDIT":
            for me, back in self.edit_meshes:
                bm = bmesh.from_edit_mesh(me)

                for i, co in back:
                    bm.verts[i].co = co
                bmesh.update_edit_mesh(me)

        else:
            for count, obj in enumerate(bpy.context.selected_objects):
                obj.matrix_world.translation = self.original_locations[count]


    def selection_exit(self, context):
        if context.active_object.mode != "OBJECT": return
        if context.active_object == None: return
        if len(context.selected_objects) != 2: return
        other = [o for o in context.selected_objects if o != context.active_object][0]
        if not other: return
        set_active(other, select=True, only_select=True)


    def draw_master(self, context):
        self.master.setup()
        if not self.master.should_build_fast_ui(): return

        # Main
        win_list = []
        axis = self.set_axis
        if axis == "RESET":
            axis = ""
            self.axises = []
        if len(axis) > 1:
            axis = axis[0]

        if axis == "":
            axis = "None"

        if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1: #Fast Floating
            win_list.append(axis)
            win_list.append(format(", ".join(list(OrderedDict.fromkeys(self.axises)))))
        else:
            win_list.append("Reset Axis")
            win_list.append(axis)
            win_list.append("Axis - {}".format(", ".join(list(OrderedDict.fromkeys(self.axises)))))

        # 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"] = [
            ("A",         "Align Objects"),
            ("X",         "Reset x axis"),
            ("Y",         "Reset y axis"),
            ("Z",         "Reset z axis"),
            ("C",         "Snap to cursor"),
            ("C + Shift", "Snap to cursor offset"),
            ("Scroll",    "Change axis")]

        # 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="Xslap", mods_list=mods_list)
        self.master.finished()