OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / modals / super_array.py

2078 lines · python ref:
import bpy
import gpu
import math
import copy
import bmesh
import mathutils
from enum import Enum
from mathutils import Vector
from math import cos, sin, pi, radians, degrees
from gpu_extras.batch import batch_for_shader
from bpy.props import IntProperty, FloatProperty
from mathutils import Matrix, Vector, geometry, Quaternion
from ... graphics.drawing2d import draw_text
from ... utility import method_handler
from ... utility import addon
from ... utils.space_3d import get_3D_point_from_mouse, scene_ray_cast #get_3D_raycast_from_mouse
from ... ui_framework.master import Master
from ... ui_framework.utils.mods_list import get_mods_list
from ... utils.toggle_view3d_panels import collapse_3D_view_panels


class Axis(Enum):

    X = 1
    Y = 2
    Z = 3


class Logic_States(Enum):

    VANILLA_ARRAY   = 1
    RADIAL_ARRAY    = 2
    TWIST_ARRAY     = 3


class props():

    #GLOBAL
    intersection_point = (0, 0, 0)
    start_dims_magnitude = .5
    current_axis = Axis.X
    gizmo_normal = (0, 0, 1)
    logic_state = Logic_States.VANILLA_ARRAY

    #RADIAL RENDERING
    use_radial_widget = False


class Vanilla_Behavior():


    def __init__(self, context):

        self.context = context
        self.obj = self.context.active_object
        self.run_setup = True

        self.active_mod = None
        self.use_relative = False
        self.axis = Axis.Z
        self.gizmo_normal = (0, 0, 1)
        self.intersection_point = (0, 0, 0)
        self.offset_vec = Vector((0,0,0))

        #UI Data
        self.ui_data = {
            "mod_name" : "?",
            "offset_vec" : "?",
            "mod_count" : "?",
            "mod_offset" : "?",
            "axis" : "?",
            "count" : "?"}


    def update(self, event):

        if self.run_setup:
            self.setup()

        #Add an array
        if event.type == "A" and event.value == "PRESS" and event.alt != True:
            self.add_array_mod()

        #Remove active array
        elif event.type == "A" and event.value == "PRESS" and event.alt == True:
            self.remove_array()

        #Cycle active array
        elif event.type == "Q" and event.value == "PRESS":
            self.cycle_active_mod()

        #Switch arrays to Relative
        elif event.type == "R" and event.value == "PRESS" and event.alt == False:
            self.use_relative_offset()

        #Switch arrays to Constant
        elif event.type == "C" and event.value == "PRESS" and event.alt == False:
            self.use_constant_offset()

        #Snap to cursor
        elif event.type == "S" and event.value == "PRESS" and event.alt == False:
            self.snap_to_cursor()

        #Count increment
        elif event.type == 'WHEELUPMOUSE' and event.ctrl == True:
            self.add_count(count=1)

        #Count decrement
        elif event.type == 'WHEELDOWNMOUSE' and event.ctrl == True:
            self.add_count(count=-1)

        #Change the axis
        elif event.type == 'X' and event.value == "PRESS" and  event.ctrl == False:
            self.cycle_axis()

        #Clear the axis and toggle
        elif event.type == 'X' and event.value == "PRESS" and  event.ctrl == True:
            self.clear_offsset_axis()

        #Set offset
        elif event.type in {'NUMPAD_0', 'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4', 'NUMPAD_5', 'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9'} and event.value == "PRESS" and event.ctrl == True:

            if(event.type == "NUMPAD_0" and event.value == "PRESS"):
                self.set_offset(0)
            elif(event.type == "NUMPAD_1" and event.value == "PRESS"):
                self.set_offset(1)
            elif(event.type == "NUMPAD_2" and event.value == "PRESS"):
                self.set_offset(2)
            elif(event.type == "NUMPAD_3" and event.value == "PRESS"):
                self.set_offset(3)
            elif(event.type == "NUMPAD_4" and event.value == "PRESS"):
                self.set_offset(4)
            elif(event.type == "NUMPAD_5" and event.value == "PRESS"):
                self.set_offset(5)
            elif(event.type == "NUMPAD_6" and event.value == "PRESS"):
                self.set_offset(6)
            elif(event.type == "NUMPAD_7" and event.value == "PRESS"):
                self.set_offset(7)
            elif(event.type == "NUMPAD_8" and event.value == "PRESS"):
                self.set_offset(9)
            elif(event.type == "NUMPAD_9" and event.value == "PRESS"):
                self.set_offset(9)

        #Adjust the X, Y, Z of the active array
        elif event.type == 'MOUSEMOVE' and event.ctrl == True and event.shift == False:
            self.adjust_axis_offset(event=event)

        #Ray cast onto object
        elif event.type == 'MOUSEMOVE' and event.ctrl == True and event.shift == True:
            self.ray_snap(event=event)

        #Add count
        elif event.type == "NUMPAD_PLUS" and event.value == "PRESS":
            self.set_offset(offset=1, use_additive=True)

        #Add count
        elif event.type == "NUMPAD_MINUS" and event.value == "PRESS":
            self.set_offset(offset=-1, use_additive=True)

        self.finish()


    def setup(self):

        #Rendering
        props.use_radial_widget = False

        self.active_mod = None

        if self.active_mod == None:
            for mod in self.obj.modifiers:
                if mod.type == 'ARRAY':
                    self.active_mod = mod
                    self.axis = self.get_probable_axis()
                    break

        if self.active_mod == None:
            self.add_array_mod()

        else:
            self.active_mod.show_expanded = False


    def finish(self):

        if self.active_mod != None:

            self.ui_data["mod_name"] = self.active_mod.name

            # Set the offset
            if self.axis == Axis.X:
                if self.active_mod.use_relative_offset == True:
                    self.ui_data["offset_vec"] = f'{self.active_mod.relative_offset_displace[0] : .2f}'
                else:
                    self.ui_data["offset_vec"] = f'{self.active_mod.constant_offset_displace[0] : .2f}'

            elif self.axis == Axis.Y:
                if self.active_mod.use_relative_offset == True:
                    self.ui_data["offset_vec"] = f'{self.active_mod.relative_offset_displace[1] : .2f}'
                else:
                    self.ui_data["offset_vec"] = f'{self.active_mod.constant_offset_displace[1] : .2f}'

            elif self.axis == Axis.Z:
                if self.active_mod.use_relative_offset == True:
                    self.ui_data["offset_vec"] = f'{self.active_mod.relative_offset_displace[2] : .2f}'
                else:
                    self.ui_data["offset_vec"] = f'{self.active_mod.constant_offset_displace[2] : .2f}'

            mod_count = 0
            for mod in self.obj.modifiers:

                if mod.type == 'ARRAY':
                    mod_count += 1

            self.ui_data["mod_count"] = mod_count

            if self.active_mod.use_relative_offset and self.active_mod.use_constant_offset:
                self.ui_data["mod_offset"] = "Mixed"

            elif self.active_mod.use_object_offset:
                self.ui_data["mod_offset"] = "Object"

            elif self.active_mod.use_relative_offset:
                self.ui_data["mod_offset"] = "Relative"

            else:
                self.ui_data["mod_offset"] = "Constant"

            self.ui_data["axis"] = self.axis.name

            self.ui_data["count"] = self.active_mod.count

            # Global Props Update
            props.current_axis = self.axis
            props.gizmo_normal = self.gizmo_normal
            props.intersection_point = self.intersection_point


    def snap_to_cursor(self):

        self.obj.location = self.context.scene.cursor.location


    def ray_snap(self, event):

        self.obj.hide_set(True)

        # result, location, normal, index, object, matrix = get_3D_raycast_from_mouse(event=event, context=self.context)
        result, location, normal, index, object, matrix = scene_ray_cast(self.context, event)

        if result and object.name != self.obj.name:

            #Rendering
            self.intersection_point = location

            self.obj.rotation_euler.zero()

            up = Vector((0, 0, 1))
            angle = normal.rotation_difference(up)
            angle.invert()
            rot = angle.to_euler()

            self.obj.rotation_euler = rot

            #Set obj location
            self.obj.location = location

        self.obj.hide_set(False)


    def add_array_mod(self):

        self.active_mod = self.obj.modifiers.new("Array", 'ARRAY')
        self.active_mod.show_expanded = False

        if self.use_relative:
            self.active_mod.use_relative_offset = True
            self.active_mod.use_constant_offset = False
        else:
            self.active_mod.use_relative_offset = False
            self.active_mod.use_constant_offset = True

        self.cycle_axis()
        self.set_offset(offset=2)


    def set_offset(self, offset=0, use_additive=False):

        if self.active_mod != None and self.active_mod.type == 'ARRAY':

            #Kick it out if the array is the Twist array
            if self.active_mod.name[:11] == "Twist Array":
                props.logic_state = Logic_States.TWIST_ARRAY
                return

            #Kick it out if the array is the Radial array
            elif self.active_mod.name[:12] == "Radial Array":
                props.logic_state = Logic_States.RADIAL_ARRAY
                return

            elif self.active_mod.use_relative_offset == True:
                if use_additive:
                    if self.axis == Axis.X:
                        self.active_mod.relative_offset_displace[0] += offset
                    elif self.axis == Axis.Y:
                        self.active_mod.relative_offset_displace[1] += offset
                    elif self.axis == Axis.Z:
                        self.active_mod.relative_offset_displace[2] += offset
                else:
                    if self.axis == Axis.X:
                        self.active_mod.relative_offset_displace[0] = offset
                    elif self.axis == Axis.Y:
                        self.active_mod.relative_offset_displace[1] = offset
                    elif self.axis == Axis.Z:
                        self.active_mod.relative_offset_displace[2] = offset

            elif self.active_mod.use_constant_offset == True:
                if use_additive:
                    if self.axis == Axis.X:
                        self.active_mod.constant_offset_displace[0] += offset
                    elif self.axis == Axis.Y:
                        self.active_mod.constant_offset_displace[1] += offset
                    elif self.axis == Axis.Z:
                        self.active_mod.constant_offset_displace[2] += offset
                else:
                    if self.axis == Axis.X:
                        self.active_mod.constant_offset_displace[0] = offset
                    elif self.axis == Axis.Y:
                        self.active_mod.constant_offset_displace[1] = offset
                    elif self.axis == Axis.Z:
                        self.active_mod.constant_offset_displace[2] = offset


    def remove_array(self):

        #Make sure delete is not called on None
        if self.active_mod == None:

            self.add_array_mod()

        # Delete Radial Array
        elif self.active_mod.name[:12] == "Radial Array":

            array_index = self.obj.modifiers.find(self.active_mod.name)

            displace_index = 0
            for mod in self.obj.modifiers:
                if mod.type == 'DISPLACE':
                    displace_index = self.obj.modifiers.find(mod.name)

            if displace_index == array_index - 1:
                self.obj.modifiers.remove(self.obj.modifiers[displace_index])

                for child in self.obj.children:
                    if child.name == 'Radial Empty':
                        bpy.data.objects.remove(child, do_unlink=True, do_id_user=True, do_ui_user=True)
                        break

            #Delete radial array
            self.obj.modifiers.remove(self.obj.modifiers[self.active_mod.name])

            mod_count = 0
            for mod in self.obj.modifiers:
                if mod.type == 'ARRAY':
                    mod_count += 1

            if mod_count > 1:
                for mod in self.obj.modifiers:
                    if mod.type == 'ARRAY':
                        self.active_mod = mod
                        axis = self.get_probable_axis()
                        if axis != None:
                            props.current_axis = axis

            else:
                self.add_array_mod()


        # Delete Twist Array
        elif self.active_mod.name[:11] == "Twist Array":

            array_index = self.obj.modifiers.find(self.active_mod.name)

            # Get displace
            displace_index = 0
            for mod in self.obj.modifiers:
                if mod.type == 'DISPLACE':
                    displace_index = self.obj.modifiers.find(mod.name)
                    break

            if displace_index == array_index - 1:
                self.obj.modifiers.remove(self.obj.modifiers[displace_index])

            array_index = self.obj.modifiers.find(self.active_mod.name)

            # Get deform
            displace_index = 0
            for mod in self.obj.modifiers:
                if mod.type == 'SIMPLE_DEFORM':
                    displace_index = self.obj.modifiers.find(mod.name)

            if displace_index == array_index + 1:
                self.obj.modifiers.remove(self.obj.modifiers[displace_index])

            array_index = self.obj.modifiers.find(self.active_mod.name)

            # Get deform 2
            displace_index = 0
            for mod in self.obj.modifiers:
                if mod.type == 'DISPLACE':
                    displace_index = self.obj.modifiers.find(mod.name)

            if displace_index == array_index + 1:
                self.obj.modifiers.remove(self.obj.modifiers[displace_index])

            #Delete radial array
            self.obj.modifiers.remove(self.obj.modifiers[self.active_mod.name])

            mod_count = 0
            for mod in self.obj.modifiers:
                if mod.type == 'ARRAY':
                    mod_count += 1

            if mod_count > 1:
                for mod in self.obj.modifiers:
                    if mod.type == 'ARRAY':
                        self.active_mod = mod
                        axis = self.get_probable_axis()
                        if axis != None:
                            props.current_axis = axis

            else:
                self.add_array_mod()

        #Regular delete mod
        else:

            mod_count = 0
            for mod in self.obj.modifiers:
                if mod.type == 'ARRAY':
                    mod_count += 1

            if mod_count > 1:

                self.obj.modifiers.remove(self.active_mod)

                for mod in self.obj.modifiers:
                    if mod.type == 'ARRAY':
                        self.active_mod = mod
                        axis = self.get_probable_axis()
                        if axis != None:
                            props.current_axis = axis


    def adjust_axis_offset(self, event):

        self.offset_vec = Vector((0,0,0))

        mouse_pos = Vector((event.mouse_region_x, event.mouse_region_y))

        #Get mouse offset
        if self.axis == Axis.Z:

            #Get the normal from local space matrix
            object_normal = Vector((0,1,0)) @ self.obj.matrix_local.inverted()
            self.intersection_point = get_3D_point_from_mouse(mouse_pos, self.context, self.obj.location, object_normal)
            self.offset_vec = self.intersection_point - self.obj.location

        else:

            #Get the normal from local space matrix
            object_normal = Vector((0,0,1)) @ self.obj.matrix_local.inverted()
            self.intersection_point = get_3D_point_from_mouse(mouse_pos, self.context, self.obj.location, object_normal)
            self.offset_vec = self.intersection_point - self.obj.location

        #Get magnitude
        magnitude = math.sqrt( ((self.intersection_point[0] - self.obj.location[0]) ** 2) +
                                ((self.intersection_point[1] - self.obj.location[1]) ** 2) +
                                ((self.intersection_point[2] - self.obj.location[2]) ** 2))

        inter_vec = Vector((self.intersection_point[0], self.intersection_point[1], self.intersection_point[2]))
        trans_inter = self.obj.matrix_local.inverted() @  inter_vec

        #Apply Offsets
        if self.axis == Axis.X:

            if trans_inter[0] < 0:
                magnitude *= -1

            if self.active_mod.use_relative_offset == True:
                self.add_relative_offset((magnitude, 0, 0))
            else:
                self.add_constant_offset((magnitude, 0, 0))

        elif self.axis == Axis.Y:

            if trans_inter[1] < 0:
                magnitude *= -1

            if self.active_mod.use_relative_offset == True:
                self.add_relative_offset((0, magnitude, 0))
            else:
                self.add_constant_offset((0, magnitude, 0))

        elif self.axis == Axis.Z:

            if trans_inter[2] < 0:
                magnitude *= -1

            if self.active_mod.use_relative_offset == True:
                self.add_relative_offset((0, 0, magnitude))
            else:
                self.add_constant_offset((0, 0, magnitude))


    def add_offset(self, count=1):
        '''Called from UI Elements'''

        #Apply Offsets
        if self.axis == Axis.X:

            if self.active_mod.use_relative_offset == True:
                self.add_relative_offset((count, 0, 0), additive=True)
            else:
                self.add_constant_offset((count, 0, 0), additive=True)

        elif self.axis == Axis.Y:

            if self.active_mod.use_relative_offset == True:
                self.add_relative_offset((0, count, 0), additive=True)
            else:
                self.add_constant_offset((0, count, 0), additive=True)

        elif self.axis == Axis.Z:

            if self.active_mod.use_relative_offset == True:
                self.add_relative_offset((0, 0, count), additive=True)
            else:
                self.add_constant_offset((0, 0, count), additive=True)


    def get_probable_axis(self):

        if self.active_mod != None and self.active_mod.type == 'ARRAY':
            if self.active_mod.use_relative_offset:
                vec = self.active_mod.relative_offset_displace
                index = 0
                compare = 0
                for i in range(len(vec)):
                    if abs(vec[i]) > abs(compare):
                        compare = vec[i]
                        index = i
                if index == 0:
                    return Axis.X
                if index == 1:
                    return Axis.Y
                if index == 2:
                    return Axis.Z
            elif self.active_mod.use_constant_offset:
                vec = self.active_mod.constant_offset_displace
                index = 0
                compare = 0
                for i in range(len(vec)):
                    if abs(vec[i]) > abs(compare):
                        compare = vec[i]
                        index = i
                if index == 0:
                    return Axis.X
                if index == 1:
                    return Axis.Y
                if index == 2:
                    return Axis.Z
            elif self.active_mod.use_object_offset:
                return self.axis

        else:
            return self.axis


    def cycle_active_mod(self):

        mod_locs = []
        matched_loc = 0

        for i in range(len(self.obj.modifiers)):
            if self.obj.modifiers[i].type == 'ARRAY':
                mod_locs.append(i)
                if self.obj.modifiers[i] == self.active_mod:
                    matched_loc = i

        if len(mod_locs) > 1:
            for i in range(len(mod_locs)):
                if matched_loc == mod_locs[i]:
                    if i == len(mod_locs) - 1:
                        self.active_mod = self.obj.modifiers[mod_locs[0]]
                        self.axis = self.get_probable_axis()
                        self.update_gizmo_normal_from_axis()
                        break
                    else:
                        self.active_mod = self.obj.modifiers[mod_locs[i + 1]]
                        self.axis = self.get_probable_axis()
                        self.update_gizmo_normal_from_axis()
                        break


    def clear_offsset_axis(self):

        #Relative
        if self.active_mod.use_relative_offset == True:
            self.zero_out_relative_offset_axis()
            self.cycle_axis()

        #Constant
        if self.active_mod.use_constant_offset == True:
            self.zero_out_constant_offset_axis()
            self.cycle_axis()


    def toggle_offset_type(self):

        if self.active_mod.use_relative_offset:
            self.use_constant_offset()
        elif self.active_mod.use_constant_offset:
            self.use_relative_offset()


    #RELATIVE OFFSET
    def use_relative_offset(self):

        self.active_mod.use_relative_offset = True
        self.active_mod.use_constant_offset = False
        self.use_relative = True


    def zero_out_relative_offset_axis(self):

        if self.active_mod != None and self.active_mod.type == 'ARRAY':
            if self.axis == Axis.X:
                self.active_mod.relative_offset_displace[0] = 0
            elif self.axis == Axis.Y:
                self.active_mod.relative_offset_displace[1] = 0
            elif self.axis == Axis.Z:
                self.active_mod.relative_offset_displace[2] = 0


    def add_relative_offset(self, offset=(0,0,0), additive=False):

        if self.active_mod != None and self.active_mod.type == 'ARRAY':
            #Kick it out if the array is the Twist array
            if self.active_mod.name[:11] == "Twist Array":
                props.logic_state = Logic_States.TWIST_ARRAY
                return

            #Kick it out if the array is the Radial array
            elif self.active_mod.name[:12] == "Radial Array":
                props.logic_state = Logic_States.RADIAL_ARRAY
                return

            if additive:
                self.active_mod.relative_offset_displace[0] += offset[0]
                self.active_mod.relative_offset_displace[1] += offset[1]
                self.active_mod.relative_offset_displace[2] += offset[2]

            else:
                if abs(offset[0]) > 0:
                    self.active_mod.relative_offset_displace[0] = offset[0] / 5
                elif abs(offset[1]) > 0:
                    self.active_mod.relative_offset_displace[1] = offset[1] / 5
                elif abs(offset[2]) > 0:
                    self.active_mod.relative_offset_displace[2] = offset[2] / 5
        else:
            return

    #CONSTANT OFFSET
    def use_constant_offset(self):

        self.active_mod.use_relative_offset = False
        self.active_mod.use_constant_offset = True
        self.use_relative = False


    def zero_out_constant_offset_axis(self):

        if self.active_mod != None and self.active_mod.type == 'ARRAY':
            if self.axis == Axis.X:
                self.active_mod.constant_offset_displace[0] = 0
            elif self.axis == Axis.Y:
                self.active_mod.constant_offset_displace[1] = 0
            elif self.axis == Axis.Z:
                self.active_mod.constant_offset_displace[2] = 0


    def add_constant_offset(self, offset=(0,0,0), additive=False):

        if self.active_mod != None and self.active_mod.type == 'ARRAY':
            #Kick it out if the array is the Twist array
            if self.active_mod.name[:11] == "Twist Array":
                props.logic_state = Logic_States.TWIST_ARRAY
                return

            #Kick it out if the array is the Radial array
            elif self.active_mod.name[:12] == "Radial Array":
                props.logic_state = Logic_States.RADIAL_ARRAY
                return

            if additive:
                self.active_mod.constant_offset_displace[0] += offset[0]
                self.active_mod.constant_offset_displace[1] += offset[1]
                self.active_mod.constant_offset_displace[2] += offset[2]

            else:
                if abs(offset[0]) > 0:
                    self.active_mod.constant_offset_displace[0] = offset[0]
                elif abs(offset[1]) > 0:
                    self.active_mod.constant_offset_displace[1] = offset[1]
                elif abs(offset[2]) > 0:
                    self.active_mod.constant_offset_displace[2] = offset[2]
        else:
            return


    def add_count(self, count):

        if self.active_mod != None and self.active_mod.type == 'ARRAY':
            #Kick it out if the array is the Twist array
            if self.active_mod.name[:11] == "Twist Array":
                props.logic_state = Logic_States.TWIST_ARRAY
                return
            self.active_mod.count += count


    def update_gizmo_normal_from_axis(self):

        val = self.axis.value

        if(val == 1):
            self.axis = Axis.X
            self.gizmo_normal = (0,0,1)
        elif(val == 2):
            self.axis = Axis.Y
            self.gizmo_normal = (0,0,1)
        elif(val == 3):
            self.axis = Axis.Z
            self.gizmo_normal = (0,0,0)


    def cycle_axis(self):

        val = self.axis.value

        if(val == 1):
            self.axis = Axis.Y
            self.gizmo_normal = (0,0,1)
        elif(val == 2):
            self.axis = Axis.Z
            self.gizmo_normal = (0,0,0)
        elif(val == 3):
            self.axis = Axis.X
            self.gizmo_normal = (0,0,1)


class Radial_Behavior():


    def __init__(self, context):

        self.context = context
        self.obj = self.context.active_object
        self.run_setup = True

        self.radial_array_mod = None
        self.displace_mod = None
        self.radial_empty = None
        self.axis = Axis.Y
        self.displace = 1
        self.segments = 6
        self.intersection_point = (0, 0, 0)
        self.angle_index = 0

        #UI Data
        self.ui_data = {
            "segments" : "?",
            "displace" : "?",
            "direction" : "?",
            "mod_name" : "?"}


    def update(self, event):

        if self.run_setup:
            self.setup()

        #Count increment
        if event.type == 'WHEELUPMOUSE' and event.ctrl == True:
            self.add_count(count=1)

        #Count decrement
        elif event.type == 'WHEELDOWNMOUSE' and event.ctrl == True:
            self.add_count(count=-1)

        #Adjust the X, Y, Z of the active array
        elif event.type == 'MOUSEMOVE' and  event.ctrl == True and event.shift == False:
            self.set_offset(event=event)

        #Snap to cursor
        elif event.type == "S" and event.value == "PRESS" and event.alt == False:
            self.snap_to_cursor()

        #Ray cast onto object
        elif event.type == 'MOUSEMOVE' and  event.ctrl == True and event.shift == True:
            self.ray_snap(event=event)

        #Switch the offset direction
        elif event.type == 'X' and event.value == "PRESS":
            self.switch_axis()

        # Set the radial sweep angle
        elif event.type == 'R' and event.value == 'PRESS':
            self.adjust_angle()

        self.finish()


    def setup(self):

        #Delete twist setup
        for a, b, c, d in zip(self.obj.modifiers[:-3], self.obj.modifiers[1:-2], self.obj.modifiers[2:-1], self.obj.modifiers[3:]):
            if a.type == 'DISPLACE' and b.type == 'ARRAY' and c.type == 'SIMPLE_DEFORM' and d.type == 'DISPLACE':
                self.obj.modifiers.remove(a)
                self.obj.modifiers.remove(b)
                self.obj.modifiers.remove(c)
                self.obj.modifiers.remove(d)
                break

        #Have to reset radial each update
        #If the user switches back to Vanilla and deletes it, then the values ref nothing while still not None value
        self.radial_array_mod = None
        self.displace_mod = None
        self.radial_empty = None

        #Rendering
        props.use_radial_widget = True

        #Check to see if array and dispalce are setup
        if self.radial_array_mod == None and self.displace_mod == None:

            #Check for correct ordering
            for displace, array in zip(self.obj.modifiers[:-1], self.obj.modifiers[1:]):
                if displace.type == 'DISPLACE' and array.type == 'ARRAY':
                    self.displace_mod = displace
                    self.radial_array_mod = array

                #If zip found items, name them
                if self.displace_mod and self.radial_array_mod:
                    self.displace_mod.name = "Radial Displace"
                    self.radial_array_mod.name = "Radial Array"

            #Check for incorrect ordering if zip found nothing
            if self.radial_array_mod == None and self.displace_mod == None:

                for displace, array in zip(self.obj.modifiers[1:], self.obj.modifiers[:-1]):
                    if displace.type == 'DISPLACE' and array.type == 'ARRAY':
                        self.displace_mod = displace
                        self.radial_array_mod = array

                #If zip found items, name them
                if self.displace_mod and self.radial_array_mod:
                    self.displace_mod.name = "Radial Displace"
                    self.radial_array_mod.name = "Radial Array"

                    bpy.ops.object.modifier_move_up(modifier=self.displace_mod.name)

        #Zip did not find radial setup, check for only one array
        if self.radial_array_mod == None and self.displace_mod == None:

            #Check for single array mod
            array_count = 0
            array_temp = None
            array_index = 0

            for mod in self.obj.modifiers:
                if mod.type == 'ARRAY':
                    array_count += 1
                    array_temp = mod
                array_index += 1

            #If only one array is found, make that the radial array and add displace
            if array_count == 1:
                self.radial_array_mod = array_temp

                self.radial_array_mod.name = "Radial Array"
                self.radial_array_mod.use_constant_offset = False
                self.radial_array_mod.use_relative_offset = False
                self.radial_array_mod.use_object_offset = True
                self.segments = self.radial_array_mod.count

                #Add displace, zip would have found it otherwise
                self.displace_mod = self.obj.modifiers.new("Radial Displace", 'DISPLACE')

                self.displace_mod.mid_level = 0.0
                self.displace_mod.direction = self.axis.name
                self.displace_mod.strength = self.displace

                #Move the displace above the array
                error_out = 0
                while self.obj.modifiers.find(self.displace_mod.name) != array_index - 1:
                    bpy.ops.object.modifier_move_up(modifier=self.displace_mod.name)
                    error_out += 1
                    if error_out > 1000:
                        break

        #Zip did not find mods, there was more than one array
        if self.radial_array_mod == None and self.displace_mod == None:

            self.displace_mod = self.obj.modifiers.new("Radial Displace", 'DISPLACE')
            self.displace_mod.mid_level = 0.0
            self.displace_mod.direction = self.axis.name
            self.displace_mod.strength = self.displace

            self.radial_array_mod = self.obj.modifiers.new("Radial Array", 'ARRAY')
            self.radial_array_mod.use_constant_offset = False
            self.radial_array_mod.use_relative_offset = False
            self.radial_array_mod.use_object_offset = True
            self.radial_array_mod.count = self.segments

        #Check for radial empty, create if not found, add drivers
        if self.radial_empty == None:

            #Check to see if there is a child emptpy 'Radial Empty'
            for child in self.obj.children:
                if child.name == 'Radial Empty':
                    self.radial_empty = child
                    break

            #Found radial empty in children
            if self.radial_empty != None:
                self.radial_array_mod.offset_object = self.radial_empty

            #There was no child radial empty
            if self.radial_empty == None:

                self.radial_empty = bpy.data.objects.new("Radial Empty", None)
                self.radial_empty.empty_display_type = 'SPHERE'
                self.radial_empty.parent = self.obj

                for col in self.obj.users_collection:
                    col.objects.link(self.radial_empty)

                self.radial_array_mod.offset_object = self.radial_empty

                #Target the Empty
                self.radial_empty.parent = self.obj

            #Create drivers
            if self.radial_empty:

                #Creat the drivers
                for index in range(3):
                    self.radial_empty.driver_remove("rotation_euler", index)
                    driver = self.radial_empty.driver_add("rotation_euler", index).driver

                    count = driver.variables.new()
                    count.name = "count"
                    count.targets[0].id = self.obj
                    count.targets[0].data_path = "modifiers[\"Radial Array\"].count"

                    direction = driver.variables.new()
                    direction.name = "direction"
                    direction.targets[0].id = self.obj
                    direction.targets[0].data_path = "modifiers[\"Radial Displace\"].direction"

                    driver.expression = f"{math.radians(360)} / count if direction == {[2,0,1][index]} else 0"

        self.segments = self.radial_array_mod.count
        self.displace = self.displace_mod.strength

        self.radial_array_mod.show_render = addon.preference().property.Hops_twist_radial_sort
        self.radial_array_mod.show_in_editmode = not addon.preference().property.Hops_twist_radial_sort

        self.displace_mod.show_render = addon.preference().property.Hops_twist_radial_sort
        self.displace_mod.show_in_editmode = not addon.preference().property.Hops_twist_radial_sort

        self.radial_array_mod.show_expanded = False
        self.displace_mod.show_expanded = False


    def finish(self):

        #UI
        self.ui_data["segments"] = str(self.segments)
        self.ui_data["displace"] = str( "%.4f" % self.displace)
        self.ui_data["direction"] = str(self.axis.name)
        self.ui_data["mod_name"] = str(self.radial_array_mod.name)

        #Rendering
        props.intersection_point = self.intersection_point


    def adjust_angle(self):

        rotations = ['360', '180', '90', '60', '45', '30', '15']
        self.angle_index += 1
        if self.angle_index + 1 == len(rotations):
            self.angle_index = 0
        rotation = int(rotations[self.angle_index])

        #Create drivers
        if self.radial_empty:

            #Creat the drivers
            for index in range(3):
                self.radial_empty.driver_remove("rotation_euler", index)
                driver = self.radial_empty.driver_add("rotation_euler", index).driver

                count = driver.variables.new()
                count.name = "count"
                count.targets[0].id = self.obj
                count.targets[0].data_path = "modifiers[\"Radial Array\"].count"

                direction = driver.variables.new()
                direction.name = "direction"
                direction.targets[0].id = self.obj
                direction.targets[0].data_path = "modifiers[\"Radial Displace\"].direction"

                driver.expression = f"{math.radians(rotation)} / count if direction == {[2,0,1][index]} else 0"


    def snap_to_cursor(self):

        self.obj.location = self.context.scene.cursor.location


    def add_count(self, count):

        self.radial_array_mod.count += count
        self.segments = self.radial_array_mod.count


    def add_offset(self, count):

        if self.displace_mod != None and self.displace_mod.type == 'DISPLACE':

            #Offset object
            self.displace_mod.strength += count
            self.displace = self.displace_mod.strength


    def set_offset(self, event):

        if self.displace_mod != None and self.displace_mod.type == 'DISPLACE':

            mouse_pos = Vector((event.mouse_region_x, event.mouse_region_y))

            #Get the normal from local space matrix
            object_normal = Vector((0,0,1)) @ self.obj.matrix_local.inverted()
            self.intersection_point = get_3D_point_from_mouse(mouse_pos, self.context, self.obj.location, object_normal)

            #Get magnitude between
            magnitude = math.sqrt( ((self.intersection_point[0] - self.obj.location[0]) ** 2) +
                                   ((self.intersection_point[1] - self.obj.location[1]) ** 2) +
                                   ((self.intersection_point[2] - self.obj.location[2]) ** 2))

            #Offset object
            self.displace_mod.strength = magnitude
            self.displace = self.displace_mod.strength


    def ray_snap(self, event):

        self.obj.hide_set(True)

        # result, location, normal, index, object, matrix = get_3D_raycast_from_mouse(event=event, context=self.context)
        result, location, normal, index, object, matrix = scene_ray_cast(self.context, event)

        if result and object.name != self.obj.name:

            #Rendering
            self.intersection_point = location

            self.obj.rotation_euler.zero()

            up = Vector((0, 0, 1))
            angle = normal.rotation_difference(up)
            angle.invert()
            rot = angle.to_euler()

            self.obj.rotation_euler = rot

            #Set obj location
            self.obj.location = location

        self.obj.hide_set(False)


    def switch_axis(self):

        if self.displace_mod != None:

            val = self.axis.value

            if(val == 1):
                self.axis = Axis.Y
            elif(val == 2):
                self.axis = Axis.Z
            elif(val == 3):
                self.axis = Axis.X

            self.displace_mod.direction = self.axis.name


class Twist_Behavior():


    def __init__(self, context):

        self.context = context
        self.obj = self.context.active_object
        self.run_setup = True

        self.displace_mod_one = None
        self.array_mod = None
        self.deform_mod = None
        self.displace_mod_two = None
        self.intersection_point = (0,0,0)
        self.axis = 'Z'
        self.count = 8
        self.angle = 360

        #UI Data
        self.ui_data = {
            "count" : "?",
            "offset" : "?",
            "axis" : "?",
            "mod_name" : "?",
            "angle" : "?"}


    def update(self, event):

        if self.run_setup:
            self.setup()

        #Count increment
        if event.type == 'WHEELUPMOUSE' and event.ctrl == True:
            self.add_count(count=1)

        #Count decrement
        elif event.type == 'WHEELDOWNMOUSE' and event.ctrl == True:
            self.add_count(count=-1)

        #Switch axis
        elif event.type == "X" and event.value == "PRESS" and event.alt == False:
            self.switch_axis()

        #Snap to cursor
        elif event.type == "S" and event.value == "PRESS" and event.alt == False:
            self.snap_to_cursor()

        #Adjust the offset on displace
        elif event.type == 'MOUSEMOVE' and  event.ctrl == True and event.shift == False:
            self.set_offset(event=event)

        #Ray cast onto object
        elif event.type == 'MOUSEMOVE' and  event.ctrl == True and event.shift == True:
            self.ray_snap(event=event)

        #Adjust sweep angle
        elif event.type == 'MOUSEMOVE' and event.alt == True and  event.ctrl == False and event.shift == True:
            self.adjust_sweep(event=event)

        self.center(self.context)

        self.finish()


    def setup(self):

        #Delete Radial mods and empty
        for displace, array in zip(self.obj.modifiers[:-1], self.obj.modifiers[1:]):
            if displace.type == 'DISPLACE' and displace.name[:15] == "Radial Displace" and array.type == 'ARRAY' and array.name[:12] == "Radial Array":
                self.obj.modifiers.remove(displace)
                self.obj.modifiers.remove(array)

        #Check to see if there is a child emptpy 'Radial Empty'
        for child in self.obj.children:
            if child.name == 'Radial Empty':
                bpy.data.objects.remove(child)
                break


        #Rendering
        props.use_radial_widget = True

        self.displace_mod_one = None
        self.array_mod = None
        self.deform_mod = None
        self.displace_mod_two = None


        #Check for initial setup
        for a, b, c, d in zip(self.obj.modifiers[:-3], self.obj.modifiers[1:-2], self.obj.modifiers[2:-1], self.obj.modifiers[3:]):
            if a.type == 'DISPLACE' and b.type == 'ARRAY' and c.type == 'SIMPLE_DEFORM' and d.type == 'DISPLACE':
                self.displace_mod_one = a
                self.array_mod = b
                self.array_mod.name = "Twist Array"
                self.deform_mod = c
                self.displace_mod_two = d
                break


        if not self.displace_mod_one:
            self.displace_mod_one = self.obj.modifiers.new("Displace One", 'DISPLACE')
            self.displace_mod_one.mid_level = 0.0
            self.displace_mod_one.direction = 'X' if self.axis == 'Y' else 'Y'
            self.displace_mod_one.strength = -2
            self.displace_new_one = True

        if not self.array_mod:
            self.array_mod = self.obj.modifiers.new("Twist Array", 'ARRAY')
            self.array_mod.count = self.count
            self.array_new = True

        if not self.deform_mod:
            self.deform_mod = self.obj.modifiers.new("Simple Deform", 'SIMPLE_DEFORM')
            self.deform_mod.angle = math.radians(self.angle)
            self.deform_mod.deform_method = 'BEND'
            self.deform_mod.deform_axis = self.axis
            self.deform_new = True

        if not self.displace_mod_two:
            self.displace_mod_two = self.obj.modifiers.new("Displace Two", 'DISPLACE')
            self.displace_mod_two.mid_level = 0.0
            self.displace_mod_two.direction = 'X' if self.axis == 'Y' else 'Y'
            self.displace_mod_two.strength = -1.0
            self.displace_new_two = True


        #Check for a single array about the twist stack
        temp_mod = None
        arrays_before_displace = 0

        for mod in self.obj.modifiers:
            if mod.type == 'ARRAY':
                arrays_before_displace += 1
                temp_mod = mod
            if mod.type == 'DISPLACE' and mod.name == "Displace One":
                break

        if arrays_before_displace == 1 and temp_mod != None:
            self.array_mod.count = temp_mod.count
            self.obj.modifiers.remove(temp_mod)


        self.array_mod.use_constant_offset = False
        self.array_mod.use_relative_offset = True
        self.array_mod.use_object_offset = False
        self.array_mod.relative_offset_displace = (1.0, 0.0, 0.0) if self.deform_mod.deform_axis == 'Z' else (0.0, 0.0, 1.0)


        self.axis = self.deform_mod.deform_axis
        self.count = self.array_mod.count
        self.angle = math.degrees(self.deform_mod.angle)

        if self.array_mod.count < 4:
            self.count = 3
            self.array_mod.count = self.count


        self.array_mod.show_render = addon.preference().property.Hops_twist_radial_sort
        self.displace_mod_two.show_render = not addon.preference().property.Hops_twist_radial_sort
        self.array_mod.show_expanded = False

        self.displace_mod_two.show_render = addon.preference().property.Hops_twist_radial_sort
        self.displace_mod_two.show_in_editmode = not addon.preference().property.Hops_twist_radial_sort
        self.displace_mod_two.show_expanded = False

        self.displace_mod_one.show_render = addon.preference().property.Hops_twist_radial_sort
        self.displace_mod_one.show_in_editmode = not addon.preference().property.Hops_twist_radial_sort
        self.displace_mod_one.show_expanded = False

        self.deform_mod.show_render = addon.preference().property.Hops_twist_radial_sort
        self.deform_mod.show_in_editmode = not addon.preference().property.Hops_twist_radial_sort
        self.deform_mod.show_expanded = False


    def finish(self):

        #Rendering
        props.intersection_point = self.intersection_point

        if self.array_mod != None:

            self.ui_data["count"] = str(self.array_mod.count)
            self.ui_data["offset"] = str( "%.4f" % self.displace_mod_one.strength)
            self.ui_data["axis"] = str(self.axis)
            self.ui_data["mod_name"] = self.array_mod.name
            self.ui_data["angle"] = str( "%.2f" % self.angle)


    def center(self, context):

        temp = bpy.data.objects.new("Bounding Box", self.obj.data)
        bb = temp.bound_box[:]
        bpy.data.objects.remove(temp)

        right = left = bb[7][0]
        front = back = bb[7][1]
        top = bottom = bb[7][2]

        for i in range(7):
            x = bb[i][0]
            if x > right:
                right = x
            if x < left:
                left = x

            y = bb[i][1]
            if y > front:
                front = y
            if y < back:
                back = y

            z = bb[i][2]
            if z > top:
                top = z
            if z < bottom:
                bottom = z

        middle_x = (right + left) * 0.5
        middle_y = (front + back) * 0.5
        middle_z = (top + bottom) * 0.5

        verts = [v for v in bb]
        verts.append((right, middle_y, middle_z))
        verts.append((left, middle_y, middle_z))
        verts.append((middle_x, front, middle_z))
        verts.append((middle_x, back, middle_z))
        verts.append((middle_x, middle_y, top))
        verts.append((middle_x, middle_y, bottom))

        bm = bmesh.new()
        bm.from_mesh(self.obj.data)
        verts = [bm.verts.new(v) for v in verts]
        bm.to_mesh(self.obj.data)

        self.displace_mod_two.strength = 0.0
        context.view_layer.update()

        bb = self.obj.bound_box
        axis = 0 if self.axis == 'Y' else 1
        pos = neg = bb[7][axis]

        for i in range(7):
            val = bb[i][axis]
            if val > pos:
                pos = val
            if val < neg:
                neg = val

        offset = -0.5 * (pos + neg)
        self.displace_mod_two.strength = offset

        verts = [bm.verts.remove(v) for v in verts]
        bm.to_mesh(self.obj.data)
        bm.free()


    def add_count(self, count):

        if self.array_mod != None:
            if self.array_mod.count < 4:
                self.array_mod.count = 3
                if count > 0:
                    self.array_mod.count += count
                return
            else:
                self.array_mod.count += count


    def add_angle(self, count):

        if self.deform_mod != None:

            #Offset object
            self.deform_mod.angle += radians(count)
            self.angle = degrees(self.deform_mod.angle)

            if self.deform_mod.angle > radians(360):
                self.deform_mod.angle = radians(360)
                self.angle = 360

            elif self.deform_mod.angle < 0:
                self.deform_mod.angle = 0
                self.angle = 0


    def adjust_sweep(self, event):

        if self.deform_mod != None:

            mouse_pos = Vector((event.mouse_region_x, event.mouse_region_y))

            #Get the normal from local space matrix
            object_normal = Vector((0,0,1)) @ self.obj.matrix_local.inverted()
            self.intersection_point = get_3D_point_from_mouse(mouse_pos, self.context, self.obj.location, object_normal)

            #Get magnitude between
            magnitude = math.sqrt( ((self.intersection_point[0] - self.obj.location[0]) ** 2) +
                                    ((self.intersection_point[1] - self.obj.location[1]) ** 2) +
                                    ((self.intersection_point[2] - self.obj.location[2]) ** 2))

            #Offset object
            self.deform_mod.angle = magnitude * .5 - 1

            if self.deform_mod.angle > radians(360):
                self.deform_mod.angle = radians(360)

            elif self.deform_mod.angle < radians(0):
                self.deform_mod.angle = radians(0)

            self.angle = degrees(self.deform_mod.angle)


    def snap_to_cursor(self):

        self.obj.location = self.context.scene.cursor.location


    def switch_axis(self):

        self.axis = "YZX"["XYZ".find(self.axis)]
        self.displace_mod_one.direction = 'X' if self.axis == 'Y' else 'Y'
        self.array_mod.relative_offset_displace = (1.0, 0.0, 0.0) if self.axis == 'Z' else (0.0, 0.0, 1.0)
        self.deform_mod.deform_axis = self.axis
        self.displace_mod_two.direction = 'X' if self.axis == 'Y' else 'Y'


    def add_offset(self, count):

        if self.displace_mod_one != None and self.displace_mod_one.type == 'DISPLACE':

            #Offset object
            self.displace_mod_one.strength += count


    def set_offset(self, event):

        if self.displace_mod_one != None and self.displace_mod_one.type == 'DISPLACE':

            mouse_pos = Vector((event.mouse_region_x, event.mouse_region_y))

            #Get the normal from local space matrix
            object_normal = Vector((0,0,1)) @ self.obj.matrix_local.inverted()
            self.intersection_point = get_3D_point_from_mouse(mouse_pos, self.context, self.obj.location, object_normal)

            #Get magnitude between
            magnitude = math.sqrt( ((self.intersection_point[0] - self.obj.location[0]) ** 2) +
                                    ((self.intersection_point[1] - self.obj.location[1]) ** 2) +
                                    ((self.intersection_point[2] - self.obj.location[2]) ** 2))

            #Offset object
            self.displace_mod_one.strength = -magnitude - self.displace_mod_two.strength


    def ray_snap(self, event):

        self.obj.hide_set(True)

        # result, location, normal, index, object, matrix = get_3D_raycast_from_mouse(event=event, context=self.context)
        result, location, normal, index, object, matrix = scene_ray_cast(self.context, event)

        if result and object.name != self.obj.name:

            #Rendering
            self.intersection_point = location

            self.obj.rotation_euler.zero()

            up = Vector((0, 0, 1))
            angle = normal.rotation_difference(up)
            angle.invert()
            rot = angle.to_euler()

            self.obj.rotation_euler = rot

            #Set obj location
            self.obj.location = location

        self.obj.hide_set(False)


class Behavior_Controller():


    def __init__(self, context, event, logic_state):

        self.vanilla_behavior = Vanilla_Behavior(context)
        self.radial_behavior = Radial_Behavior(context)
        self.twist_behavior = Twist_Behavior(context)
        #self.update(context, event, logic_state)


    def update(self, context, event, logic_state):

        if logic_state == Logic_States.VANILLA_ARRAY:
            self.vanilla_behavior.update(event)

            self.vanilla_behavior.run_setup = False
            self.radial_behavior.run_setup = True
            self.twist_behavior.run_setup = True

        if logic_state == Logic_States.RADIAL_ARRAY:
            self.radial_behavior.update(event)

            self.vanilla_behavior.run_setup = True
            self.radial_behavior.run_setup = False
            self.twist_behavior.run_setup = True

        if logic_state == Logic_States.TWIST_ARRAY:
            self.twist_behavior.update(event)

            self.vanilla_behavior.run_setup = True
            self.radial_behavior.run_setup = True
            self.twist_behavior.run_setup = False


class HOPS_OT_SuperArray(bpy.types.Operator):

    """Multi Array - V1"""
    bl_idname = "hops.super_array"
    bl_label = "Multi Tool Array"
    bl_options = {"REGISTER", "UNDO"}
    bl_description = """Array V1
Standard, Radial, Twist 360, all in one.

LMB   - Adjust / Create Array
CTRL  - Adjust / Create Radial Array
Shift - Adjust / Create Twist 360 Array

Opt In available for Array V2 via hops dropdown
"""


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


    def invoke(self, context, event):

        # Widget UI
        self.intersection_draw_handle = None

        # Modal UI
        self.master = None

        # Positional Data
        self.start_mouse_position = (0,0)

        # States Data
        self.current_axis = Axis.X

        # Behavior Controller
        self.controller = None

        # For Cancel
        self.start_mods = []

        # For finish
        self.finish_trigger = False
        self.completed = False

        #Get the start dims
        mod_visible = []
        self.start_mods = context.active_object.modifiers[:]

        for mod in self.start_mods:
            mod_visible.append(mod.show_viewport)
            mod.show_viewport = False

        bpy.context.view_layer.update()
        props.start_dims_magnitude = context.active_object.dimensions.to_2d().magnitude

        for i in range(len(self.start_mods)):
            context.active_object.modifiers[i].show_viewport = mod_visible[i]

        if event.ctrl == True:
            props.logic_state = Logic_States.RADIAL_ARRAY
        elif event.shift == True:
            props.logic_state = Logic_States.TWIST_ARRAY
        else:
            props.logic_state = Logic_States.VANILLA_ARRAY

        self.controller = Behavior_Controller(context, event, props.logic_state)

        props.intersection_point = context.active_object.location

        self.mouse_coords = Vector((event.mouse_region_x, event.mouse_region_y))
        self.obj = context.active_object

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

        self.intersection_draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_intersection, (context,), 'WINDOW', 'POST_VIEW')

        context.window_manager.modal_handler_add(self)

        #Set the current state
        if event.type == "ONE" and event.value == "PRESS":
            props.logic_state = Logic_States.VANILLA_ARRAY
        if event.type == "TWO" and event.value == "PRESS":
            props.logic_state = Logic_States.RADIAL_ARRAY
        if event.type == "THREE" and event.value == "PRESS":
            props.logic_state = Logic_States.TWIST_ARRAY

        return {'RUNNING_MODAL'}


    def modal(self, context, event):

        # Fade effect
        self.master.receive_event(event=event)

        #Set the current state
        if event.type in {'ONE', 'TWO', 'THREE'}:
            if event.type == "ONE" and event.value == "PRESS":
                props.logic_state = Logic_States.VANILLA_ARRAY
            if event.type == "TWO" and event.value == "PRESS":
                props.logic_state = Logic_States.RADIAL_ARRAY
            if event.type == "THREE" and event.value == "PRESS":
                props.logic_state = Logic_States.TWIST_ARRAY

        #Set the current state : To help with people that have numpad emulate on
        if event.type in {'V', 'B', 'N'}:
            if event.type == "V" and event.value == "PRESS":
                props.logic_state = Logic_States.VANILLA_ARRAY
            if event.type == "B" and event.value == "PRESS":
                props.logic_state = Logic_States.RADIAL_ARRAY
            if event.type == "N" and event.value == "PRESS":
                props.logic_state = Logic_States.TWIST_ARRAY

        #Update controller with current state
        self.controller.update(context, event, props.logic_state)

        #Navigation
        if (event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'} and event.ctrl != True):

            if not self.master.is_mouse_over_ui():
                return {'PASS_THROUGH'}

        #Confirm
        elif (event.type == 'LEFTMOUSE'):

            if not self.master.is_mouse_over_ui():
                self.remove_intersection()
                self.master.run_fade()
                collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
                return {'FINISHED'}

        #Cancel
        elif (event.type in {'RIGHTMOUSE', 'ESC'}):

            if not self.master.is_mouse_over_ui():
                self.remove_intersection()

                try:
                    self.cancel(context)
                except:
                    print("Error: Could not revert.")

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

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


    def cancel(self, context):

        keep_mod_names = []

        for mod in self.start_mods:
            keep_mod_names.append(mod.name)

        delete_mods = []

        for i in range(len(context.active_object.modifiers)):
            if context.active_object.modifiers[i].name not in keep_mod_names:
                delete_mods.append(context.active_object.modifiers[i])

        for mod in delete_mods:
            context.active_object.modifiers.remove(mod)


    def toggle_arrays_from_ui(self):

        if props.logic_state == Logic_States.VANILLA_ARRAY:
            props.logic_state = Logic_States.RADIAL_ARRAY

        elif props.logic_state == Logic_States.RADIAL_ARRAY:
            props.logic_state = Logic_States.TWIST_ARRAY

        elif props.logic_state == Logic_States.TWIST_ARRAY:
            props.logic_state = Logic_States.VANILLA_ARRAY


    def new_ui(self, context):

        # Start
        self.master.setup()

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

        if self.master.should_build_fast_ui():

            win_list = []
            mods_list = []
            active_mod = ""

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

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

            if props.logic_state == Logic_States.VANILLA_ARRAY:

                # Main
                if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1: #Fast Floating
                    win_list.append(self.controller.vanilla_behavior.ui_data["mod_name"])
                    win_list.append(self.controller.vanilla_behavior.ui_data["count"])
                    win_list.append(self.controller.vanilla_behavior.axis.name)
                    win_list.append(self.controller.vanilla_behavior.ui_data["offset_vec"])
                    win_list.append(self.controller.vanilla_behavior.ui_data["mod_offset"])
                else:
                    #win_list.append("Array")
                    win_list.append(self.controller.vanilla_behavior.ui_data["mod_name"])
                    win_list.append(self.controller.vanilla_behavior.ui_data["count"])
                    win_list.append("Standard")
                    win_list.append(self.controller.vanilla_behavior.ui_data["mod_offset"])
                    win_list.append(self.controller.vanilla_behavior.ui_data["offset_vec"])
                    win_list.append(self.controller.vanilla_behavior.axis.name)

                # Help
                help_items["STANDARD"] = [
                    ("1, 2, 3",           "Numbers across top of keyboard change mode."),
                    ("V, B, N",           "Change the mode."),
                    ("Ctrl + Shift",      "Raycast snap to another object"),
                    ("NUMPAD",            "Set the current offset to the numpad value"),
                    ("Ctrl + Wheel Down", "Minus the count of the array"),
                    ("Ctrl + Wheel Up",   "Add to the count of the array"),
                    ("Numpad +",          "Adjust the offset by + 1"),
                    ("Numpad -",          "Adjust the offset by - 1"),
                    ("S",                 "Snap to the cursor"),
                    ("C",                 "Set array to constant offset"),
                    ("R",                 "Set array to relative offset"),
                    ("X",                 "Change the current axis being modified"),
                    ("Ctrl + X",          "Clear the axis offset"),
                    ("Alt + A",           "Remove current array"),
                    ("A",                 "Add additional array"),
                    ("Q",                 "Cycle active array"),
                    ("Ctrl + Move",       "Adjust array offset")]

                # Mods
                active_mod = self.controller.vanilla_behavior.ui_data["mod_name"]
                mods_list = get_mods_list(mods=bpy.context.active_object.modifiers)

            elif props.logic_state == Logic_States.RADIAL_ARRAY:

                # Main
                if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1: #Fast Floating
                    win_list.append(self.controller.radial_behavior.ui_data["segments"])
                    win_list.append(self.controller.radial_behavior.ui_data["direction"])
                    win_list.append(self.controller.radial_behavior.ui_data["displace"])
                else:
                    win_list.append(self.controller.vanilla_behavior.ui_data["mod_name"])
                    win_list.append(self.controller.radial_behavior.ui_data["segments"])
                    win_list.append("Radial")
                    win_list.append(self.controller.radial_behavior.ui_data["direction"])
                    win_list.append(self.controller.radial_behavior.ui_data["displace"])

                # Help
                help_items["STANDARD"] = [
                    ("1, 2, 3",          "Numbers across top of keyboard change mode."),
                    ("V, B, N",          "Change the mode."),
                    ("Ctrl + MouseWheel", "Adjust the count of the arrays"),
                    ("Ctrl + MouseMove", "Adjust the offset of array"),
                    ("Ctrl + Shift",     "Raycast snap to another object"),
                    ("S",                "Snap to the cursor"),
                    ("R",                "Change radial to 180/90/45/30"),
                    ("X",                "Switch offset direction"),
                    ("Ctrl + Move",      "Adjust displacement offset"),
                    ("1",                 "To remove, switch to standard and press alt + A")]

                # Mods
                active_mod = self.controller.radial_behavior.ui_data["mod_name"]
                mods_list = get_mods_list(mods=bpy.context.active_object.modifiers)

            elif props.logic_state == Logic_States.TWIST_ARRAY:

                # Main
                if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1: #Fast Floating
                    win_list.append(self.controller.twist_behavior.ui_data["count"])
                    win_list.append(self.controller.twist_behavior.ui_data["axis"])
                    win_list.append(self.controller.twist_behavior.ui_data["offset"])
                    win_list.append(self.controller.twist_behavior.ui_data["angle"])
                else:
                    win_list.append(self.controller.vanilla_behavior.ui_data["mod_name"])
                    win_list.append(self.controller.twist_behavior.ui_data["count"])
                    win_list.append("Twist")
                    win_list.append(self.controller.twist_behavior.ui_data["offset"])
                    win_list.append(self.controller.twist_behavior.ui_data["axis"])
                    win_list.append(self.controller.twist_behavior.ui_data["angle"])

                # Help
                help_items["STANDARD"] = [
                    ("1, 2, 3",           "Numbers across top of keyboard change mode."),
                    ("V, B, N",           "Change the mode."),
                    ("Shift + Alt",       "Adjust the angle of simple deform"),
                    ("Ctrl + MouseWheel", "Adjust the count of the arrays"),
                    ("Ctrl + Shift",      "Raycast snap to another object"),
                    ("Ctrl + MouseMove",  "Adjust the offset of array"),
                    ("S",                 "Snap to the cursor"),
                    ("X",                 "Change the current axis being modified"),
                    ("Ctrl + Move",       "Adjust twist displacement offset"),
                    ("1",                 "To remove, switch to standard and press alt + A")]

                # Mods
                active_mod = self.controller.twist_behavior.ui_data["mod_name"]
                mods_list = get_mods_list(mods=bpy.context.active_object.modifiers)

            if props.logic_state == Logic_States.RADIAL_ARRAY:
                self.master.receive_fast_ui(win_list=win_list, help_list=help_items, image="ArrayCircle", mods_list=mods_list, active_mod_name=active_mod)
            elif props.logic_state == Logic_States.TWIST_ARRAY:
                self.master.receive_fast_ui(win_list=win_list, help_list=help_items, image="ATwist360", mods_list=mods_list, active_mod_name=active_mod)
            else:
                self.master.receive_fast_ui(win_list=win_list, help_list=help_items, image="Array", mods_list=mods_list, active_mod_name=active_mod)

        else:

            ########################
            #   Main
            ########################

            main_window = {
                "main_count" : [],
                "header_sub_text" : [],
                "last_col_cell_1" : [],
                "last_col_cell_2" : [],
                "last_col_cell_3" : []
            }

            window_name = ""

            if props.logic_state == Logic_States.VANILLA_ARRAY:
                main_window["main_count"] = [self.controller.vanilla_behavior.ui_data["count"], self.controller.vanilla_behavior.add_count, (1,), (-1,), True]
                #main_window["header_sub_text"] = ["Standard Array", self.toggle_arrays_from_ui]
                main_window["header_sub_text"] = ["Standard Array", self.toggle_arrays_from_ui]

                main_window["last_col_cell_1"] = [self.controller.vanilla_behavior.axis.name, self.controller.vanilla_behavior.cycle_axis]
                main_window["last_col_cell_2"] = [self.controller.vanilla_behavior.ui_data["mod_offset"], self.controller.vanilla_behavior.toggle_offset_type]
                main_window["last_col_cell_3"] = [self.controller.vanilla_behavior.ui_data["offset_vec"], self.controller.vanilla_behavior.add_offset, (.25,), (-.25,), True]

                window_name = "Standard Array"

            elif props.logic_state == Logic_States.RADIAL_ARRAY:
                main_window["main_count"] = [self.controller.radial_behavior.ui_data["segments"], self.controller.radial_behavior.add_count, (1,), (-1,), True]
                main_window["header_sub_text"] = ["Radial Array", self.toggle_arrays_from_ui]
                main_window["last_col_cell_1"] = [self.controller.radial_behavior.ui_data["direction"], self.controller.radial_behavior.switch_axis]
                main_window["last_col_cell_2"] = [self.controller.radial_behavior.ui_data["displace"], self.controller.radial_behavior.add_offset, (1,), (-1,), True]

                window_name = "Radial Array"

            elif props.logic_state == Logic_States.TWIST_ARRAY:
                main_window["main_count"] = [self.controller.twist_behavior.ui_data["count"], self.controller.twist_behavior.add_count, (1,), (-1,), True]
                main_window["header_sub_text"] = ["Twist Array", self.toggle_arrays_from_ui]
                main_window["last_col_cell_1"] = [self.controller.twist_behavior.ui_data["axis"], self.controller.twist_behavior.switch_axis]
                main_window["last_col_cell_2"] = [self.controller.twist_behavior.ui_data["offset"], self.controller.twist_behavior.add_offset, (1,), (-1,), True]
                main_window["last_col_cell_3"] = [self.controller.twist_behavior.ui_data["angle"], self.controller.twist_behavior.add_angle, (15,), (-15,), True]

                window_name = "Twist Array"

            self.master.receive_main(win_dict=main_window, window_name=window_name)


            ########################
            #   Help
            ########################


            hot_keys_dict = {}
            quick_ops_dict = {}

            if props.logic_state == Logic_States.VANILLA_ARRAY:

                hot_keys_dict["V, B, N"] = "Change the mode."
                hot_keys_dict["R"] = ["Set array to relative offset", self.controller.vanilla_behavior.use_relative_offset]
                hot_keys_dict["C"] = ["Set array to constant offset", self.controller.vanilla_behavior.use_constant_offset]
                hot_keys_dict["Ctrl + Shift"] = "Raycast snap to another object"
                hot_keys_dict["Ctrl + Wheel Down"] = ["Minus the count of the array", self.controller.vanilla_behavior.add_count, (-1, )]
                hot_keys_dict["Ctrl + Wheel Up"] = ["Add to the count of the array", self.controller.vanilla_behavior.add_count, (1, )]
                hot_keys_dict["Q"] = ["Cycle active array", self.controller.vanilla_behavior.cycle_active_mod]
                hot_keys_dict["S"] = ["Snap to the cursor", self.controller.vanilla_behavior.snap_to_cursor]
                hot_keys_dict["NUMPAD + CTRL"] = "Set the current offset to the numpad value"
                hot_keys_dict["Numpad +"] = ["Adjust the offset by + 1", self.controller.vanilla_behavior.set_offset, (1, True)]
                hot_keys_dict["Numpad -"] = ["Adjust the offset by - 1", self.controller.vanilla_behavior.set_offset, (-1, True)]
                hot_keys_dict["Ctrl + X"] = ["Clear the axis offset", self.controller.vanilla_behavior.clear_offsset_axis]
                hot_keys_dict["X"] = ["Change the current axis being modified", self.controller.vanilla_behavior.cycle_axis]
                hot_keys_dict["Alt + A"] = ["Remove current array", self.controller.vanilla_behavior.remove_array]
                hot_keys_dict["A"] = ["Add an array", self.controller.vanilla_behavior.add_array_mod]
                hot_keys_dict["1, 2, 3"] = "Numbers across top of keyboard change mode."

            elif props.logic_state == Logic_States.RADIAL_ARRAY:

                hot_keys_dict["1, 2, 3"] = "Numbers across top of keyboard change mode."
                hot_keys_dict["V, B, N"] = "Change the mode."
                hot_keys_dict["Ctrl + MouseWheel"] = ["Adjust the count of the arrays",  self.controller.radial_behavior.add_count, (1,), (-1,)]
                hot_keys_dict["Ctrl + MouseMove"] = ["Adjust the offset of array", self.controller.radial_behavior.add_offset, (1,), (-1,)]
                hot_keys_dict["Ctrl + Shift"] = "Raycast snap to another object"
                hot_keys_dict["S"] = ["Snap to the cursor", self.controller.radial_behavior.snap_to_cursor]
                hot_keys_dict["R"] = ["Change radial to 180/90/45/30", self.controller.radial_behavior.adjust_angle]
                hot_keys_dict["X"] = ["Switch offset direction", self.controller.radial_behavior.switch_axis]

            elif props.logic_state == Logic_States.TWIST_ARRAY:

                hot_keys_dict["1, 2, 3"] = "Numbers across top of keyboard change mode."
                hot_keys_dict["V, B, N"] = "Change the mode."
                hot_keys_dict["Shift + Alt"] = ["Adjust the angle of simple deform", self.controller.twist_behavior.add_angle, (15,), (-15,)]
                hot_keys_dict["Ctrl + MouseWheel"] = ["Adjust the count of the arrays", self.controller.twist_behavior.add_count, (1,), (-1,)]
                hot_keys_dict["Ctrl + Shift"] = "Raycast snap to another object"
                hot_keys_dict["Ctrl + MouseMove"] = ["Adjust the offset of array", self.controller.twist_behavior.add_offset, (1,), (-1,)]
                hot_keys_dict["S"] = ["Snap to the cursor", self.controller.twist_behavior.snap_to_cursor]
                hot_keys_dict["X"] = ["Change the current axis being modified", self.controller.twist_behavior.switch_axis]

            self.master.receive_help(hot_keys_dict=hot_keys_dict, quick_ops_dict={})


            ########################
            #   Mods
            ########################


            win_dict = {}

            for mod in reversed(context.active_object.modifiers):
                win_dict[mod.name] = str(mod.type)

            active_mod = ""
            if props.logic_state == Logic_States.VANILLA_ARRAY:
                active_mod = self.controller.vanilla_behavior.ui_data["mod_name"]

            elif props.logic_state == Logic_States.RADIAL_ARRAY:
                active_mod = self.controller.radial_behavior.ui_data["mod_name"]

            elif props.logic_state == Logic_States.TWIST_ARRAY:
                active_mod = self.controller.twist_behavior.ui_data["mod_name"]

            self.master.receive_mod(win_dict=win_dict, active_mod_name=active_mod)

        # Finished
        self.master.finished()

    #INTERSECTION DRAWING
    def draw_intersection(self, context):

        method_handler(self._draw_intersection,
            arguments = (context, ),
            identifier = 'Array Intersection Shader',
            exit_method = self.remove_intersection)


    def remove_intersection(self):

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


    def _draw_intersection(self, context):

        radius = props.start_dims_magnitude * .5
        if addon.preference().property.array_type == "DOT":
            radius = .0125
            width = 12
        else:
            width = 3
        vertices = []
        indices = []
        segments = 64
        #width = 12
        color = (0,0,0,1)


        #Build ring
        for i in range(segments):
            index = i + 1
            angle = i * 3.14159 * 2 / segments

            x = cos(angle) * radius
            y = sin(angle) * radius
            z = 0
            vert = Vector((x, y, z))

            if props.current_axis == Axis.Z and props.use_radial_widget == False:

                plane_normal = Vector((0,1,0)) @ context.active_object.matrix_local.inverted()
                up = Vector((0,0,1))
                angle = plane_normal.rotation_difference(up)

                rot_mat = angle.to_matrix()

                vert = vert @ rot_mat

            else:

                rot_mat = context.active_object.rotation_euler.to_matrix()
                rot_mat = rot_mat.inverted()
                vert = vert @ rot_mat

            vert[0] = vert[0] + props.intersection_point[0]
            vert[1] = vert[1] + props.intersection_point[1]
            vert[2] = vert[2] + props.intersection_point[2]

            vertices.append(vert)

            if(index == segments):
                indices.append((i, 0))
            else:
                indices.append((i, i + 1))

        if props.use_radial_widget == True:
            color = (1,0,1,1)

        elif props.current_axis == Axis.X:
            color = (1, 0, 0, 1)

        elif props.current_axis == Axis.Y:
            color = (0, 1, 0, 1)

        elif props.current_axis == Axis.Z:
            color = (0, 1, 1, 1)

        built_in_shader = 'UNIFORM_COLOR' if bpy.app.version[0] >=4 else '3D_UNIFORM_COLOR'
        shader = gpu.shader.from_builtin(built_in_shader)
        batch = batch_for_shader(shader, 'LINES', {'pos': vertices}, indices=indices)
        shader.bind()

        shader.uniform_float('color', color)
        #Enable(GL_LINE_SMOOTH)
        gpu.state.blend_set('ALPHA')
        gpu.state.line_width_set(width)
        batch.draw(shader)

        del shader
        del batch


#HELPER FUNCTIONS
def cycle_axis():

    val = props.current_axis.value

    if(val == 1):
        props.current_axis = Axis.Y
        props.gizmo_normal = (0,0,1)
    elif(val == 2):
        props.current_axis = Axis.Z
        props.gizmo_normal = (0,0,0)
    elif(val == 3):
        props.current_axis = Axis.X
        props.gizmo_normal = (0,0,1)