# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####




bl_info = {
        'name': "Kjartans Scripts",
        'author': "Kjartan Tysdal",
        'location': '"Shift+Q" and also in EditMode "W-Specials/ KTools"',
        'description': "Adds my personal collection of small handy scripts (mostly modeling tools)",
        'category': "Mesh",
        'blender': (2, 76, 0),
        'version': (0, 2, 8),
        'wiki_url': 'http://www.kjartantysdal.com/scripts',
}


import bpy, bmesh
from bpy.props import (
        StringProperty,
        IntProperty,
        FloatProperty,
        EnumProperty,
        BoolProperty,
        BoolVectorProperty,
        FloatVectorProperty,
        )


def testPrint():

    print('Hello')


def checkScale(): # check if scale is 0 on any of the axis, if it is then set it to 0.01

    y = -1
    for x in bpy.context.object.scale:
        y += 1
        if x == 0.0:
            bpy.context.object.scale[y] = 0.01


#Adds "Lattice to Selection" to the Addon
class lattice_to_selection(bpy.types.Operator):
        """Add a lattice deformer to the selection"""
        bl_idname = "object.lattice_to_selection"
        bl_label = "Lattice to Selection"
        bl_options = {'REGISTER', 'UNDO'}

        apply_rot: BoolProperty(
                        name = "Local",
                        description = "Orient the lattice to the active object",
                        default = True
                        )
        parent_to: BoolProperty(
                        name = "Parent to Lattice",
                        description = "Parents all the objects to the Lattice",
                        default = False
                        )
        move_first: BoolProperty(name = "First in Modifier Stack", description = "Moves the lattice modifier to be first in the stack", default = False)
        interpolation: bpy.props.EnumProperty(
                                   items= (('KEY_LINEAR', 'Linear', 'Linear Interpolation'),
                                   ('KEY_CARDINAL', 'Cardinal', 'Cardinal Interpolation'),
                                   ('KEY_CATMULL_ROM', 'Catmull Rom', 'Catmull Rom Interpolation'),
                                   ('KEY_BSPLINE', 'BSpline', 'BSpline Interpolation')),
                                   name = "Interpolation", default = 'KEY_BSPLINE')
        seg_u: IntProperty( name = "Lattice U", default = 2, soft_min = 2)
        seg_v: IntProperty( name = "Lattice V", default = 2, soft_min = 2 )
        seg_w: IntProperty( name = "Lattice W", default = 2, soft_min = 2 )

        def execute(self, context):

                apply_rot = not self.apply_rot # Global vs Local
                parent_to = self.parent_to # Parents all the objects to the Lattice
                move_first = self.move_first # moves the lattice modifier to be first in the stack
                interpolation = self.interpolation

                # check if there exists an active object
                if bpy.context.view_layer.objects.active:
                    active_obj = bpy.context.view_layer.objects.active.name
                else:
                    for x in bpy.context.selected_objects:
                        if bpy.data.objects[x.name].type == 'MESH':
                            bpy.context.view_layer.objects.active = bpy.data.objects[x.name]
                            active_obj = bpy.context.view_layer.objects.active.name
                            break



                if bpy.data.objects[active_obj].type != 'MESH':
                    self.report({'ERROR'}, "Make sure the active object is a Mesh")
                    return {'CANCELLED'}

                mode = bpy.context.active_object.mode


                if mode == 'OBJECT':


                    # check if object type is not MESH and then deselect it
                    for x in bpy.context.selected_objects:
                        if bpy.data.objects[x.name].type != 'MESH':
                            bpy.data.objects[x.name].select_set(False)


                    org_objs = bpy.context.selected_objects


                    bpy.ops.object.duplicate()

                    # remove any modifiers
                    if bpy.context.object.modifiers:
                        for x in bpy.context.object.modifiers:
                            bpy.ops.object.modifier_remove(modifier=x.name)

                    if len(bpy.context.selected_objects) > 1:
                        bpy.ops.object.join()

                    # create tmp:object and store its location, rotation and dimensions
                    bpy.ops.object.transform_apply(location=False, rotation=apply_rot, scale=True)
                    bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS')

                    lattice_loc = bpy.context.object.location
                    lattice_rot = bpy.context.object.rotation_euler
                    bbox_size = bpy.context.object.dimensions
                    tmp_obj = bpy.context.object.name

                    # create the lattice object with the lattice_loc and rot
                    bpy.ops.object.add(radius=1, type='LATTICE', view_align=False, enter_editmode=False, location=lattice_loc, rotation=lattice_rot)

                    lattice_obj = bpy.context.object

                    # set dimensions / bounding box size
                    bpy.context.object.scale = bbox_size

                    bpy.ops.object.select_all(action='DESELECT')

                    # select and delete the tmp_object
                    bpy.data.objects[tmp_obj].select_set(True)
                    bpy.ops.object.delete(use_global=False)

                    # select all the original objects and assign the lattice deformer
                    for i in org_objs:
                       if bpy.data.objects[i.name].type == 'MESH' :
                           bpy.context.view_layer.objects.active = bpy.data.objects[i.name]
                           bpy.data.objects[i.name].select_set(True)

                           bpy.ops.object.modifier_add(type='LATTICE')
                           lattice_name = bpy.context.object.modifiers[len(bpy.context.object.modifiers)-1].name
                           bpy.context.object.modifiers[lattice_name].object = lattice_obj
                           if move_first == True:
                               for x in bpy.context.object.modifiers:
                                   bpy.ops.object.modifier_move_up(modifier=lattice_name)
                       else:
                           bpy.data.objects[i.name].select_set(True)


                    if parent_to:

                        bpy.data.objects[lattice_obj.name].select_set(True)
                        bpy.context.view_layer.objects.active = bpy.data.objects[lattice_obj.name]

                        bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
                    else:

                        bpy.ops.object.select_all(action='DESELECT')
                        bpy.data.objects[lattice_obj.name].select_set(True)
                        bpy.context.view_layer.objects.active = bpy.data.objects[lattice_obj.name]


                    bpy.context.object.data.interpolation_type_u = interpolation
                    bpy.context.object.data.interpolation_type_v = interpolation
                    bpy.context.object.data.interpolation_type_w = interpolation

                    bpy.context.object.data.points_u = self.seg_u
                    bpy.context.object.data.points_v = self.seg_v
                    bpy.context.object.data.points_w = self.seg_w

                    checkScale()


                elif mode == 'EDIT':



                    org_objs = bpy.context.selected_objects

                    # Add vertex group and store its name in a variable
                    bpy.ops.object.vertex_group_assign_new()
                    v_id = len(bpy.context.object.vertex_groups)-1
                    bpy.context.object.vertex_groups[v_id].name = 'tmp_lattice_to_selection'
                    v_group = bpy.context.object.vertex_groups[v_id].name


                    bpy.ops.mesh.duplicate()
                    bpy.ops.mesh.separate(type='SELECTED')

                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                    for x in bpy.context.selected_objects:
                        if x not in org_objs:
                            tmp_obj = x.name
                            print(tmp_obj)

                    bpy.ops.object.select_all(action='DESELECT')

                    bpy.context.view_layer.objects.active = bpy.data.objects[tmp_obj]
                    bpy.data.objects[tmp_obj].select_set(True)


                    if bpy.context.object.modifiers:
                        for x in bpy.context.object.modifiers:
                            bpy.ops.object.modifier_remove(modifier=x.name)


                    bpy.ops.object.transform_apply(location=False, rotation=apply_rot, scale=True)
                    bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS')

                    lattice_loc = bpy.context.object.location
                    lattice_rot = bpy.context.object.rotation_euler
                    bbox_size = bpy.context.object.dimensions
                    tmp_obj = bpy.context.object.name


                    bpy.ops.object.add(radius=1, type='LATTICE', view_align=False, enter_editmode=False, location=lattice_loc, rotation=lattice_rot)

                    lattice_obj = bpy.context.object

                    bpy.context.object.scale = bbox_size

                    bpy.ops.object.select_all(action='DESELECT')


                    bpy.data.objects[tmp_obj].select_set(True)

                    bpy.ops.object.delete(use_global=False)

                    bpy.context.view_layer.objects.active = bpy.data.objects[active_obj]
                    bpy.data.objects[active_obj].select_set(True)

                    bpy.ops.object.modifier_add(type='LATTICE')
                    lattice_name = bpy.context.object.modifiers[len(bpy.context.object.modifiers)-1].name
                    bpy.context.object.modifiers[lattice_name].object = lattice_obj
                    bpy.context.object.modifiers[lattice_name].vertex_group = v_group

                    if move_first == True:
                        for x in bpy.context.object.modifiers:
                            bpy.ops.object.modifier_move_up(modifier=lattice_name)


                    bpy.ops.object.select_all(action='DESELECT')

                    bpy.data.objects[lattice_obj.name].select_set(True)
                    bpy.context.view_layer.objects.active = bpy.data.objects[lattice_obj.name]

                    bpy.context.object.data.interpolation_type_u = interpolation
                    bpy.context.object.data.interpolation_type_v = interpolation
                    bpy.context.object.data.interpolation_type_w = interpolation

                    bpy.context.object.data.points_u = self.seg_u
                    bpy.context.object.data.points_v = self.seg_v
                    bpy.context.object.data.points_w = self.seg_w

                    checkScale()




                return {'FINISHED'}

        def invoke( self, context, event ):
            wm = context.window_manager
            return wm.invoke_props_dialog( self )



#Adds Calculate Normals and Smooth to the Addon
class calc_normals(bpy.types.Operator):
        """Calculates and smooths normals."""
        bl_idname = "mesh.calc_normals"
        bl_label = "Calculate Normals"
        bl_options = {'REGISTER', 'UNDO'}

        invert: BoolProperty(name = "Invert Normals", description = "Inverts the normals.", default = False)

        def execute(self, context):

                invert = self.invert
                mode = bpy.context.active_object.mode

                if mode == 'OBJECT':

                        sel = bpy.context.selected_objects
                        active = bpy.context.view_layer.objects.active.name

                        bpy.ops.object.shade_smooth()


                        for ob in sel:
                                ob = ob.name
                                bpy.context.view_layer.objects.active = bpy.data.objects[ob]
                                bpy.ops.object.editmode_toggle()
                                bpy.ops.mesh.select_all(action='SELECT')
                                bpy.ops.mesh.normals_make_consistent(inside=invert)
                                bpy.ops.object.editmode_toggle()

                        bpy.context.view_layer.objects.active = bpy.data.objects[active]

                elif mode == 'EDIT':
                        bpy.ops.mesh.normals_make_consistent(inside=invert)



                return {'FINISHED'}



#Adds SnapToAxis to the Addon
class snaptoaxis(bpy.types.Operator):
        """Snaps selected vertices to zero on the selected axis."""
        bl_idname = "mesh.snaptoaxis"
        bl_label = "Snap to Axis"
        bl_options = {'REGISTER', 'UNDO'}

        #worldspace = bpy.props.EnumProperty(items= (('OBJECT', 'Object Space', 'Snap to the object axis'),
        #                                                                                         ('WORLD', 'World Space', 'Snap to the global axis')),
        #                                                                                         name = "Object/World", default = 'OBJECT')

        snap_x: BoolProperty(name = "Snap to X", description = "Snaps to zero in X. Also sets the axis for the mirror modifier if that button is turned on", default = True)
        snap_y: BoolProperty(name = "Snap to Y", description = "Snaps to zero in Y. Also sets the axis for the mirror modifier if that button is turned on", default = False)
        snap_z: BoolProperty(name = "Snap to Z", description = "Snaps to zero in Z. Also sets the axis for the mirror modifier if that button is turned on", default = False)

        mirror_add: BoolProperty(name = "Add Mirror Modifier", description = "Adds a mirror modifer", default = False)

        mirror_x: BoolProperty(name = "Mirror on X", description = "Sets the modifier to mirror on X", default = True)
        mirror_y: BoolProperty(name = "Mirror on Y", description = "Sets the modifier to mirror on Y", default = False)
        mirror_z: BoolProperty(name = "Mirror on Z", description = "Sets the modifier to mirror on Z", default = False)
        clipping: BoolProperty(name = "Enable Clipping", description = "Prevents vertices from going through the mirror during transform", default = True)




        def draw(self, context):
            layout = self.layout
            col = layout.column()

            col_move = col.column(align=True)
            row = col_move.row(align=True)
            if self.snap_x:
                row.prop(self, "snap_x", text = "X", icon='CHECKBOX_HLT')
            else:
                row.prop(self, "snap_x", text = "X", icon='CHECKBOX_DEHLT')
            if self.snap_y:
                row.prop(self, "snap_y", text = "Y", icon='CHECKBOX_HLT')
            else:
                row.prop(self, "snap_y", text = "Y", icon='CHECKBOX_DEHLT')
            if self.snap_z:
                row.prop(self, "snap_z", text = "Z", icon='CHECKBOX_HLT')
            else:
                row.prop(self, "snap_z", text = "Z", icon='CHECKBOX_DEHLT')

            col.separator()

            col_move = col.column(align=True)
            col_move.prop(self, "mirror_add", icon = 'MODIFIER')
            row = col_move.row(align=True)

            row = col_move.row(align=True)
            row.active = self.mirror_add
            if self.mirror_x:
                row.prop(self, "mirror_x", text = "X", icon='CHECKBOX_HLT')
            else:
                row.prop(self, "mirror_x", text = "X", icon='CHECKBOX_DEHLT')
            if self.mirror_y:
                row.prop(self, "mirror_y", text = "Y", icon='CHECKBOX_HLT')
            else:
                row.prop(self, "mirror_y", text = "Y", icon='CHECKBOX_DEHLT')
            if self.mirror_z:
                row.prop(self, "mirror_z", text = "Z", icon='CHECKBOX_HLT')
            else:
                row.prop(self, "mirror_z", text = "Z", icon='CHECKBOX_DEHLT')

            col = col.column()
            col.active = self.mirror_add
            col.prop(self, "clipping")


        def execute(self, context):

                mode = bpy.context.active_object.mode
                mirror_find = bpy.context.object.modifiers.find('Mirror')
                run = True


                if mode == 'EDIT':
                    loc = bpy.context.object.location


                    me = bpy.context.object.data
                    bm = bmesh.from_edit_mesh(me)

                    for v in bm.verts:
                            if v.select:
                                if self.snap_x == True:
                                    v.co.x = 0
                                if self.snap_y == True:
                                    v.co.y = 0
                                if self.snap_z == True:
                                    v.co.z = 0

                    bmesh.update_edit_mesh(me, True, False)

                if self.mirror_add == True:

                    if mirror_find <= -1:
                        bpy.ops.object.modifier_add(type='MIRROR')

                        bpy.context.object.modifiers['Mirror'].show_viewport = True

                        run = False

                    bpy.context.object.modifiers["Mirror"].use_clip = self.clipping
                    bpy.context.object.modifiers["Mirror"].use_x = self.mirror_x
                    bpy.context.object.modifiers["Mirror"].use_y = self.mirror_y
                    bpy.context.object.modifiers["Mirror"].use_z = self.mirror_z




                return {'FINISHED'}



#Adds QuickBool to the Addon
class quickbool(bpy.types.Operator):
        """Quickly carves out the selected polygons. Works best with manifold meshes."""
        bl_idname = "mesh.quickbool"
        bl_label = "Quick Bool"
        bl_options = {'REGISTER', 'UNDO'}

        del_bool: BoolProperty(name="Delete BoolMesh", description="Deletes the objects used for the boolean operation.", default= True)
        move_to: BoolProperty(name="Move to layer 10", description="Moves the objects used for the boolean operation to layer 10", default= False)
        operation: EnumProperty(items= (('UNION', 'Union', 'Combines'),
                                                                                                 ('INTERSECT', 'Intersect', 'Keep the part that overlaps'),
                                                                                                 ('DIFFERENCE', 'Difference', 'Cuts out')),
                                                                                                 name = "Operation", default = 'DIFFERENCE')

        def draw(self, context):
            layout = self.layout
            col = layout.column()
            col.prop(self, "del_bool")

            col = col.column()
            col.active = self.del_bool == False
            col.prop(self, "move_to")

            col = layout.column()
            col.prop(self, "operation")




        def execute(self, context):

                del_bool = self.del_bool
                move_to = self.move_to
                mode = bpy.context.active_object.mode



                if mode == 'EDIT':

                    #Boolean From Edit mode
                    bpy.ops.mesh.select_linked()
                    bpy.ops.mesh.separate(type='SELECTED')
                    bpy.ops.object.editmode_toggle()

                    #get name of Original+Bool object
                    original = bpy.context.selected_objects[1].name
                    bool = bpy.context.selected_objects[0].name

                    #perform boolean
                    bpy.ops.object.modifier_add(type='BOOLEAN')
                    bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[bool]
                    bpy.context.object.modifiers["Boolean"].operation = self.operation
                    bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean")

                    #delete Bool object
                    bpy.ops.object.select_all(action='DESELECT')
                    bpy.ops.object.select_pattern(pattern=bool)

                    bpy.context.view_layer.objects.active = bpy.data.objects[bool]

                    #Delete all geo inside Shrink_Object
                    bpy.ops.object.mode_set(mode = 'EDIT', toggle = False)
                    bpy.ops.mesh.select_all(action='SELECT')
                    bpy.ops.mesh.delete(type='VERT')
                    bpy.ops.object.mode_set(mode = 'OBJECT', toggle = False)

                    bpy.ops.object.delete()

                    #re-enter edit mode on Original object
                    bpy.context.view_layer.objects.active = bpy.data.objects[original]
                    bpy.ops.object.select_pattern(pattern=original)
                    bpy.ops.object.editmode_toggle()


                else:

                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                        original = bpy.context.active_object.name
                        bool = bpy.context.selected_objects


                        list = []

                        for x in bool:
                                x = x.name
                                if x != original:
                                        list.append(x)

                        for name in list:
                                #Perform Boolean
                                bpy.ops.object.modifier_add(type='BOOLEAN')
                                bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[name]
                                bpy.context.object.modifiers["Boolean"].operation = self.operation
                                bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean")


                                if del_bool == True:

                                        bpy.ops.object.select_all(action='DESELECT')
                                        bpy.ops.object.select_pattern(pattern=name)
                                        bpy.context.view_layer.objects.active = bpy.data.objects[name]

                            #Delete all geo inside Shrink_Object
                                        bpy.ops.object.mode_set(mode = 'EDIT', toggle = False)
                                        bpy.ops.mesh.select_all(action='SELECT')
                                        bpy.ops.mesh.delete(type='VERT')
                                        bpy.ops.object.mode_set(mode = 'OBJECT', toggle = False)

                                        bpy.ops.object.delete(use_global=False)
                                        bpy.context.view_layer.objects.active = bpy.data.objects[original]
                                else:
                                        bpy.ops.object.select_all(action='DESELECT')
                                        bpy.ops.object.select_pattern(pattern=name)
                                        bpy.context.view_layer.objects.active = bpy.data.objects[name]

                                        bpy.context.object.display_type = 'WIRE'

                                        # Move to garbage layer
                                        if move_to == True:
                                                bpy.ops.object.move_to_layer(layers=(False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False))

                                        bpy.context.view_layer.objects.active = bpy.data.objects[original]


                        bpy.ops.object.mode_set(mode=mode, toggle=False)


                return {'FINISHED'}



#Adds Autotubes to the Addon
class autotubes(bpy.types.Operator):
        """Creates a spline tube based on selected edges"""
        bl_idname = "mesh.autotubes"
        bl_label = "Auto Tubes"
        bl_options = {'REGISTER', 'UNDO'}

        bevel: FloatProperty(name="Tube Width", description="Change width of the tube.", default=0.1, min = 0)
        res: IntProperty(name="Tube Resolution", description="Change resolution of the tube.", default=2, min = 0, max = 20)


        def execute(self, context):

                mode = bpy.context.active_object.mode
                type = bpy.context.active_object.type
                bevel = self.bevel
                res = self.res


                if mode == 'EDIT' and type == 'MESH':
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        bpy.ops.object.duplicate()

                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                        bpy.ops.mesh.select_all(action='INVERT')
                        bpy.ops.mesh.delete(type='EDGE')

                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        bpy.ops.object.subdivision_set(level=0)
                        bpy.ops.object.convert(target='CURVE')
                        bpy.context.object.data.fill_mode = 'FULL'
                        bpy.context.object.data.bevel_depth = 0.1
                        bpy.context.object.data.splines[0].use_smooth = True
                        bpy.context.object.data.bevel_resolution = 2
                        bpy.ops.object.shade_smooth()

                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                        bpy.ops.curve.spline_type_set(type='BEZIER')

                        bpy.context.object.data.bevel_depth = bevel
                        bpy.context.object.data.bevel_resolution = res

                        #bpy.ops.transform.transform(('INVOKE_DEFAULT'), mode='CURVE_SHRINKFATTEN')



                elif type == 'CURVE':

                        bpy.context.object.data.bevel_depth = bevel
                        bpy.context.object.data.bevel_resolution = res

                elif mode != 'EDIT' and type == 'MESH':
                        self.report({'ERROR'}, "This one only works in Edit mode")
                        return {'CANCELLED'}


                return {'FINISHED'}



#Adds basicRename to the Addon

class basicRename(bpy.types.Operator):
        """Renames everything to Banana"""
        bl_idname = "object.basic_rename"
        bl_label = "Basic Renamer"
        bl_options = {'REGISTER', 'UNDO'}

        name: StringProperty(name="Rename", description="Rename selected objects", default="banana")
        padding: IntProperty(name = "Number Padding", description = "Adds how many padded numbers", default = 3, min = 1, max = 8)
        prefix:    StringProperty(name="Pre Fix", description="Adds a Prefix to the name", default="")
        post_ob: StringProperty(name="Post Fix Object", description="Adds ending to object name", default="_MDL")
        post_data: StringProperty(name="Post Fix Data", description="Adds ending to data name", default="_DATA")


        def execute(self, context):


                # The original script
                obj = bpy.context.selected_objects
                name = self.name
                padding = self.padding
                prefix = self.prefix
                post_ob = self.post_ob
                post_data = self.post_data
                number = 0
                for item in obj:
                        number += 1
                        item.name = "%s%s_%s%s" %(str(prefix), str(name), str(number).zfill(padding), str(post_ob))
                        item.data.name = "%s%s_%s%s" %(str(prefix), str(name), str(number).zfill(padding), str(post_data))


                return {'FINISHED'}



class cut_tool(bpy.types.Operator):
        """Context sensitive cut tool"""
        bl_idname = "mesh.cut_tool"
        bl_label = "Cut Tool"
        bl_options = {'REGISTER', 'UNDO'}

        cuts: IntProperty(name="Number of Cuts", description="Change the number of cuts.", default=1, min = 1, soft_max = 10)
        loopcut: BoolProperty(name="Insert LoopCut", description="Makes a loop cut based on the selected edges", default= False)
        smoothness: FloatProperty(name="Smoothness", description="Change the smoothness.", default=0, min = 0, soft_max = 1)
        quad_corners: bpy.props.EnumProperty(items= (('INNERVERT', 'Inner Vert', 'How to subdivide quad corners'),
                                                                                                 ('PATH', 'Path', 'How to subdivide quad corners'),
                                                                                                 ('STRAIGHT_CUT', 'Straight Cut', 'How to subdivide quad corners'),
                                                                                                 ('FAN', 'Fan', 'How to subdivide quad corners')),
                                                                                                 name = "Quad Corner Type", default = 'STRAIGHT_CUT')

        def execute(self, context):

                quad_corners = self.quad_corners
                cuts = self.cuts
                loopcut = self.loopcut
                smoothness = self.smoothness
                mode = bpy.context.active_object.mode

                if mode == 'EDIT':

                        sel_mode = bpy.context.tool_settings.mesh_select_mode[:]

                                                        #Checks and stores if any Vert, Edge or Face is selected.
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                        me = bpy.context.object.data
                        bm = bmesh.new()     # create an empty BMesh
                        bm.from_mesh(me)     # fill it in from a Mesh
                        sel = []
                        edge_sel = []
                        vert_sel = []

                        for v in bm.faces:
                                if v.select:
                                        sel.append(v.index)
                        for v in bm.edges:
                                if v.select:
                                        edge_sel.append(v.index)
                        for v in bm.verts:
                                if v.select:
                                        vert_sel.append(v.index)


                        bm.to_mesh(me)
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)


                        """
                        if len(sel) == 0 and len(edge_sel) == 0 and len(vert_sel) == 0 :
                                bpy.ops.mesh.knife_tool("INVOKE_DEFAULT")
                        """


                        if sel_mode[2] == True and len(sel) > 1:

                                vgrp = bpy.context.object.vertex_groups.active_index


                                #Store the Hidden Polygons
                                bpy.ops.mesh.select_all(action='SELECT')
                                bpy.ops.object.vertex_group_assign_new()
                                tmp_hidden = bpy.context.object.vertex_groups.active_index
                                bpy.ops.mesh.select_all(action='DESELECT')

                                #Select faces to be cut
                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                                mesh = bpy.context.active_object.data.polygons

                                for f in sel:
                                        mesh[f].select = True

                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                                bpy.ops.mesh.hide(unselected=True)
                                bpy.ops.mesh.select_all(action='SELECT')
                                bpy.ops.mesh.region_to_loop()

                                #Store Boundry Edges
                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                                me = bpy.context.object.data
                                bm = bmesh.new()
                                bm.from_mesh(me)
                                boundry_edge = []

                                for v in bm.edges:
                                        if v.select:
                                                boundry_edge.append(v.index)

                                bm.to_mesh(me)

                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                                #Store Cut Edges
                                bpy.ops.mesh.select_all(action='INVERT')
                                bpy.ops.mesh.loop_multi_select(ring=True)

                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                                me = bpy.context.object.data
                                bm = bmesh.new()
                                bm.from_mesh(me)
                                cut_edges = []

                                for v in bm.edges:
                                        if v.select:
                                                cut_edges.append(v.index)

                                bm.to_mesh(me)

                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                                #Store Intersection edges
                                bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
                                bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')

                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                                me = bpy.context.object.data
                                bm = bmesh.new()
                                bm.from_mesh(me)
                                int_edges = []

                                for v in bm.edges:
                                        if v.select:
                                                int_edges.append(v.index)

                                bm.to_mesh(me)

                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                                #Modify Lists
                                for x in int_edges:
                                        if x in boundry_edge:
                                                cut_edges.remove(x)

                                bpy.ops.mesh.select_all(action='DESELECT')

                                #Select the new edges to cut
                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                                mesh = bpy.context.active_object.data.edges

                                for f in cut_edges:
                                        mesh[f].select = True

                                #Perform cut and select the cut line.
                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                                bpy.ops.mesh.subdivide(number_cuts = cuts, smoothness = smoothness, quadcorner = quad_corners)
                                bpy.ops.mesh.select_all(action='SELECT')
                                bpy.ops.mesh.region_to_loop()
                                bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
                                bpy.ops.mesh.select_all(action='INVERT')
                                bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
                                bpy.ops.mesh.loop_multi_select(ring=False)

                                #Store cut line.
                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                                me = bpy.context.object.data
                                bm = bmesh.new()
                                bm.from_mesh(me)
                                cut_line = []

                                for v in bm.edges:
                                        if v.select:
                                                cut_line.append(v.index)

                                bm.to_mesh(me)

                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                                bpy.ops.mesh.reveal()
                                bpy.ops.mesh.select_all(action='DESELECT')

                                bpy.context.object.vertex_groups.active_index = tmp_hidden
                                bpy.ops.object.vertex_group_select()
                                bpy.ops.mesh.hide(unselected=True)


                                bpy.ops.mesh.select_all(action='DESELECT')


                                #Select Cutline
                                if cuts <= 1:
                                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                                        mesh = bpy.context.active_object.data.edges

                                        for f in cut_line:
                                                mesh[f].select = True

                                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                                bpy.ops.object.vertex_group_remove(all=False)
                                bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='FACE')

                        elif sel_mode[0] == True and len(vert_sel) >= 2:
                                bpy.ops.mesh.vert_connect_path()

                        elif sel_mode[1] == True and loopcut == False and len(edge_sel) != 0:
                                bpy.ops.mesh.subdivide(number_cuts = cuts, smoothness = smoothness, quadcorner = quad_corners)

                        elif sel_mode[1] == True and loopcut == True and len(edge_sel) != 0:
                                bpy.ops.mesh.loop_multi_select(ring=True)
                                bpy.ops.mesh.subdivide(number_cuts = cuts, smoothness = smoothness, quadcorner = quad_corners)
                        else:
                            bpy.ops.mesh.select_all(action='DESELECT')
                            bpy.ops.mesh.knife_tool("INVOKE_DEFAULT")

                else:
                        self.report({'ERROR'}, "This one only works in Edit mode")

                return {'FINISHED'}



#Adds customAutoSmooth to the Addon

class customAutoSmooth(bpy.types.Operator):
        """Set AutoSmooth angle"""
        bl_idname = "object.custom_autosmooth"
        bl_label = "Autosmooth"
        bl_options = {'REGISTER', 'UNDO'}

        angle: FloatProperty(name="AutoSmooth Angle", description="Set AutoSmooth angle", default= 30.0, min = 0.0, max = 180.0)


        def execute(self, context):

                mode = bpy.context.active_object.mode

                if mode != 'OBJECT':
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                ob = bpy.context.selected_objects
                angle = self.angle
                angle = angle * (3.14159265359/180)

                bpy.ops.object.shade_smooth()

                for x in ob:
                        x = x.name
                        bpy.data.objects[x].data.use_auto_smooth = True
                        bpy.data.objects[x].data.auto_smooth_angle = angle

                bpy.ops.object.mode_set(mode=mode, toggle=False)

                return {'FINISHED'}

#Adds shrinkwrapSmooth to the Addon

class shrinkwrapSmooth(bpy.types.Operator):
        """Smooths the selected vertices while trying to keep the original shape with a shrinkwrap modifier. """
        bl_idname = "mesh.shrinkwrap_smooth"
        bl_label = "Shrinkwrap Smooth"
        bl_options = {'REGISTER', 'UNDO'}

        pin: BoolProperty(name="Pin Selection Border", description="Pins the outer edge of the selection.", default = True)
        subsurf: IntProperty(name="Subsurf Levels", description="More reliable, but slower results", default = 0, min = 0, soft_max = 4)


        def execute(self, context):

                iterate = 6
                pin = self.pin
                data = bpy.context.object.data.name

                # Set up for vertex weight
                bpy.context.scene.tool_settings.vertex_group_weight = 1
                v_grps = len(bpy.context.object.vertex_groups.items())

                bpy.ops.object.mode_set(mode = 'OBJECT', toggle = False)
                org_ob = bpy.context.object.name

                # Create intermediate object
                bpy.ops.object.mode_set(mode = 'OBJECT', toggle = False)
                bpy.ops.mesh.primitive_plane_add(radius=1, view_align=False, enter_editmode=False)
                bpy.context.object.data = bpy.data.meshes[data]
                tmp_ob = bpy.context.object.name


                bpy.ops.object.duplicate(linked=False)
                shrink_ob = bpy.context.object.name

                bpy.ops.object.select_all(action='DESELECT')
                bpy.ops.object.select_pattern(pattern=tmp_ob)
                bpy.context.view_layer.objects.active = bpy.data.objects[tmp_ob]

                bpy.ops.object.mode_set(mode = 'EDIT', toggle = False)
                bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')

                if v_grps >= 1:
                    for x in range(v_grps):
                        bpy.ops.object.vertex_group_add()


                if pin == True:
                        bpy.ops.object.vertex_group_assign_new()
                        org_id = bpy.context.object.vertex_groups.active_index

                        bpy.ops.object.vertex_group_assign_new()
                        sel = bpy.context.object.vertex_groups.active.name
                        sel_id = bpy.context.object.vertex_groups.active_index

                        bpy.ops.mesh.region_to_loop()
                        bpy.ops.object.vertex_group_remove_from(use_all_groups=False, use_all_verts=False)

                        bpy.ops.mesh.select_all(action='SELECT')
                        bpy.ops.mesh.region_to_loop()
                        bpy.ops.object.vertex_group_remove_from(use_all_groups=False, use_all_verts=False)

                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.object.vertex_group_select(sel_id)


                else:
                        bpy.ops.object.vertex_group_assign_new()
                        sel = bpy.context.object.vertex_groups.active.name


                for x in range(iterate):
                        bpy.ops.object.modifier_add(type='SHRINKWRAP')
                        mod_id = (len(bpy.context.object.modifiers)-1)
                        shrink_name = bpy.context.object.modifiers[mod_id].name

                        bpy.context.object.modifiers[shrink_name].target = bpy.data.objects[shrink_ob]
                        bpy.context.object.modifiers[shrink_name].vertex_group = sel

                        bpy.context.object.modifiers[shrink_name].wrap_method = 'PROJECT'
                        bpy.context.object.modifiers[shrink_name].use_negative_direction = True
                        bpy.context.object.modifiers[shrink_name].subsurf_levels = self.subsurf


                        bpy.ops.mesh.vertices_smooth(factor=1, repeat=1)


                        bpy.ops.object.mode_set(mode = 'OBJECT', toggle = False)
                        bpy.ops.object.convert(target='MESH')
                        bpy.ops.object.mode_set(mode = 'EDIT', toggle = False)


                bpy.ops.object.mode_set(mode = 'OBJECT', toggle = False)



                bpy.ops.object.vertex_group_remove(all = False)
                bpy.ops.object.modifier_remove(modifier=shrink_name)

                bpy.ops.object.select_all(action='DESELECT')
                bpy.ops.object.select_pattern(pattern=shrink_ob)
                bpy.context.view_layer.objects.active = bpy.data.objects[shrink_ob]

                #Delete all geo inside Shrink_Object
                bpy.ops.object.mode_set(mode = 'EDIT', toggle = False)
                bpy.ops.mesh.select_all(action='SELECT')
                bpy.ops.mesh.delete(type='VERT')
                bpy.ops.object.mode_set(mode = 'OBJECT', toggle = False)

                bpy.ops.object.delete(use_global=True)

                bpy.ops.object.select_pattern(pattern=tmp_ob)
                bpy.context.view_layer.objects.active = bpy.data.objects[tmp_ob]


                bpy.ops.object.mode_set(mode = 'EDIT', toggle = False)

                if pin == True:
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.object.vertex_group_select(org_id)

                bpy.ops.object.mode_set(mode = 'OBJECT', toggle = False)

                bpy.ops.object.delete(use_global=False)


                bpy.ops.object.select_pattern(pattern=org_ob)
                bpy.context.view_layer.objects.active = bpy.data.objects[org_ob]

                bpy.ops.object.mode_set(mode = 'EDIT', toggle = False)

                # Fix for Blender remembering the previous selection
                bpy.ops.object.vertex_group_assign_new()
                bpy.ops.object.vertex_group_remove(all = False)


                return {'FINISHED'}

#Adds buildCorner to the Addon

class buildCorner(bpy.types.Operator):
        """Builds corner topology. Good for converting ngons"""
        bl_idname = "mesh.build_corner"
        bl_label = "Build Corner"
        bl_options = {'REGISTER', 'UNDO'}

        offset: IntProperty()

        def modal(self, context, event):

                if event.type == 'MOUSEMOVE':

                        delta = self.offset - event.mouse_x

                        if delta >= 0:
                                offset = 1
                        else:
                                offset = 0

                        bpy.ops.mesh.edge_face_add()

                        bpy.ops.mesh.poke()
                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
                        bpy.ops.object.vertex_group_assign_new()
                        sel_id = bpy.context.object.vertex_groups.active_index

                        bpy.ops.mesh.region_to_loop()
                        bpy.ops.object.vertex_group_remove_from()

                        bpy.ops.mesh.select_nth(nth=2, skip=1, offset=offset)

                        bpy.ops.object.vertex_group_select(sel_id)

                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
                        bpy.ops.mesh.dissolve_mode(use_verts=False)

                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
                        bpy.ops.object.vertex_group_select()
                        bpy.ops.mesh.select_more()

                        bpy.ops.object.vertex_group_remove(all = False)
                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')


                elif event.type == 'LEFTMOUSE':
                        return {'FINISHED'}

                elif event.type in {'RIGHTMOUSE', 'ESC'}:
                    bpy.ops.ed.undo()
                    return {'CANCELLED'}

                return {'RUNNING_MODAL'}

        def invoke(self, context, event):
                if context.object:

                        # Check selection

                        bpy.ops.mesh.edge_face_add()
                        bpy.ops.mesh.region_to_loop()

                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                        me = bpy.context.object.data
                        bm = bmesh.new()     # create an empty BMesh
                        bm.from_mesh(me)     # fill it in from a Mesh

                        face_sel = []
                        edge_sel = []


                        for v in bm.faces:
                                if v.select:
                                        face_sel.append(v.index)
                        for v in bm.edges:
                                if v.select:
                                        edge_sel.append(v.index)



                        bm.to_mesh(me)
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                        bpy.ops.mesh.loop_to_region()


                        ###################################

                        edge_sel = len(edge_sel)

                        if edge_sel == 4:
                                return {'FINISHED'}

                        elif edge_sel%2 == 0:
                                self.offset = event.mouse_x
                                context.window_manager.modal_handler_add(self)
                                return {'RUNNING_MODAL'}

                        #elif edge_sel == 5:
                        #    bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY')
                        #    bpy.ops.mesh.tris_convert_to_quads(face_threshold=3.14159, shape_threshold=3.14159)
                        #    return {'FINISHED'}

                        else:
                                bpy.ops.mesh.poke()
                                bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY')
                                bpy.ops.mesh.tris_convert_to_quads(face_threshold=3.14159, shape_threshold=3.14159)
                                return {'FINISHED'}

                else:
                        self.report({'WARNING'}, "No active object, could not finish")
                        return {'CANCELLED'}


class drawPoly(bpy.types.Operator):
    """Draw a polygon"""
    bl_idname = "mesh.draw_poly"
    bl_label = "Draw Poly"

    cursor_co: FloatVectorProperty()
    vert_count = 0
    manip: BoolProperty()
    vgrp: IntProperty()
    sel_mode = BoolVectorProperty()
    cursor_depth: BoolProperty()
    snap: BoolProperty()


    def modal(self, context, event):

        # set header gui
        context.area.tag_redraw()
        context.area.header_text_set("LMB = Create New Point, SHIFT = Flip Normals, RMB / ENTER = Accept NGon, MMB = Accept QuadFill")


        mesh = bpy.context.active_object.data

        if event.type == 'LEFTMOUSE':
            if event.value == 'PRESS':


                bpy.ops.view3d.cursor3d('INVOKE_DEFAULT')

                obj = bpy.context.active_object
                vert_co = bpy.context.scene.cursor.location
                world = obj.matrix_world.inverted_safe()

                bpy.ops.mesh.select_all(action='DESELECT')
                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                me = bpy.context.object.data
                bm = bmesh.new()
                bm.from_mesh(me)

                # Add new vert
                new_vert = bm.verts.new(vert_co)
                new_vert.co = world*new_vert.co
                new_vert_id = new_vert.index
                self.vert_count += 1

                if self.vert_count >= 2:
                    bm.verts.ensure_lookup_table()
                    set_of_verts = set(bm.verts[i] for i in range(-2,0))
                    bm.edges.new(set_of_verts)

                # Get index of first and last vertex
                first_index = len(bm.verts)-self.vert_count
                second_index = first_index+1
                third_index = first_index+2
                second_to_last_index = len(bm.verts)-2


                bm.to_mesh(me)
                bm.free()

                mesh.vertices[new_vert_id].select = True
                bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                bpy.context.scene.cursor.location = self.cursor_co


                if self.vert_count >= 4:
                    bpy.ops.object.vertex_group_assign()

                if self.vert_count == 3:
                    # remove second vertex from group
                    bpy.ops.mesh.select_all(action='DESELECT')
                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                    mesh.vertices[first_index].select = True
                    mesh.vertices[third_index].select = True
                    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                    bpy.ops.object.vertex_group_assign()
                    bpy.ops.mesh.select_more()

                if self.vert_count == 2:
                    bpy.ops.mesh.select_more()

                if self.vert_count >= 4:
                    # make core poly
                    bpy.ops.mesh.select_all(action='DESELECT')
                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                    mesh.vertices[first_index].select = True
                    mesh.vertices[second_index].select = True
                    mesh.vertices[third_index].select = True
                    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                    bpy.ops.mesh.edge_face_add()

                if self.vert_count == 4:
                    bpy.ops.mesh.select_all(action='DESELECT')
                    bpy.ops.object.vertex_group_select(self.vgrp)
                    bpy.ops.mesh.edge_face_add()

                    # Remove remaining core edge
                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                    mesh.vertices[second_index].select = True
                    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                    bpy.ops.mesh.edge_face_add()


                if self.vert_count >= 5:
                    #bpy.ops.object.vertex_group_assign()
                    bpy.ops.mesh.select_all(action='DESELECT')

                    # Remove Last Edge
                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                    mesh.vertices[first_index].select = True
                    mesh.vertices[second_to_last_index].select = True
                    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                    bpy.ops.mesh.delete(type='EDGE')

                    # Fill in rest of face
                    bpy.ops.object.vertex_group_select(self.vgrp)
                    bpy.ops.mesh.edge_face_add()


                    # Remove remaining core edge
                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                    mesh.vertices[second_index].select = True
                    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                    bpy.ops.mesh.edge_face_add()
                    bpy.ops.mesh.flip_normals()


            #return {'FINISHED'}
        elif event.type == 'MIDDLEMOUSE':

            # reset header gui
            context.area.tag_redraw()
            context.area.header_text_set(None)

            # Convert to Quads
            bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY')
            bpy.ops.mesh.tris_convert_to_quads()
            bpy.ops.mesh.tris_convert_to_quads(face_threshold=3.14159, shape_threshold=3.14159)


            # restore selection mode and manipulator
            bpy.context.tool_settings.mesh_select_mode = self.sel_mode
            bpy.context.space_data.show_manipulator = self.manip
            bpy.context.preferences.input.use_mouse_depth_cursor = self.cursor_depth
            bpy.context.scene.tool_settings.use_snap = self.snap


            # Remove and make sure vertex group data is gone
            bpy.ops.object.vertex_group_remove_from(use_all_verts=True)
            bpy.ops.object.vertex_group_remove()
            bpy.ops.object.vertex_group_assign_new()
            bpy.context.object.vertex_groups.active.name = "drawPoly_temp"
            bpy.ops.object.vertex_group_remove()


            return {'CANCELLED'}

        elif event.type in {'RIGHTMOUSE', 'ESC', 'SPACE'}:

            # reset header gui
            context.area.tag_redraw()
            context.area.header_text_set(None)


            # restore selection mode and manipulator
            bpy.context.tool_settings.mesh_select_mode = self.sel_mode
            bpy.context.space_data.show_manipulator = self.manip
            bpy.context.preferences.input.use_mouse_depth_cursor = self.cursor_depth
            bpy.context.scene.tool_settings.use_snap = self.snap


            # Remove and make sure vertex group data is gone
            bpy.ops.object.vertex_group_remove_from(use_all_verts=True)
            bpy.ops.object.vertex_group_remove()
            bpy.ops.object.vertex_group_assign_new()
            bpy.context.object.vertex_groups.active.name = "drawPoly_temp"
            bpy.ops.object.vertex_group_remove()


            return {'CANCELLED'}

        elif event.type == 'LEFT_SHIFT' or event.type == 'RIGHT_SHIFT':
            bpy.ops.mesh.flip_normals()

            return {'PASS_THROUGH'}

        elif event.type == 'LEFT_CTRL' or event.type == 'RIGHT_CTRL' :
            if bpy.context.preferences.input.use_mouse_depth_cursor == True:
                bpy.context.preferences.input.use_mouse_depth_cursor = False
                bpy.context.scene.tool_settings.use_snap = False
            else:
                bpy.context.preferences.input.use_mouse_depth_cursor = True
                bpy.context.scene.tool_settings.use_snap = True
            return {'PASS_THROUGH'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):


        sel_ob = len(bpy.context.selected_objects)


        if sel_ob >= 1:
            sel_type = bpy.context.object.type

            if sel_type == 'MESH':
                bpy.ops.object.mode_set(mode='EDIT', toggle=False)

            else:
                self.report({'WARNING'}, "Active object is not a mesh.")
                return {'CANCELLED'}


        elif sel_ob == 0:
            bpy.ops.mesh.primitive_plane_add()
            bpy.context.selected_objects[0].name = "polyDraw"
            bpy.context.selected_objects[0].data.name = "polyDraw"
            bpy.ops.object.mode_set(mode='EDIT', toggle=False)
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.delete(type='VERT')




        # Store selection mode, snap and manipulator settings
        self.sel_mode = bpy.context.tool_settings.mesh_select_mode[:]
        bpy.context.tool_settings.mesh_select_mode = True, False, False
        self.manip = bpy.context.space_data.show_manipulator
        bpy.context.space_data.show_manipulator = False
        self.cursor_depth = bpy.context.preferences.input.use_mouse_depth_cursor
        bpy.context.preferences.input.use_mouse_depth_cursor = False
        self.snap = bpy.context.scene.tool_settings.use_snap
        bpy.context.scene.tool_settings.use_snap = False

        bpy.ops.object.mode_set(mode='EDIT', toggle=False)

        bpy.ops.mesh.select_all(action='DESELECT')
        bpy.ops.object.vertex_group_assign_new()
        self.vgrp = bpy.context.object.vertex_groups.active_index
        bpy.context.object.vertex_groups.active.name = "drawPoly_temp"

        self.cursor_co = bpy.context.scene.cursor.location


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

class toggleSilhouette(bpy.types.Operator):
    """Turns everything black so that you can evaluate the overall shape. Useful when designing"""
    bl_idname = "object.toggle_silhouette"
    bl_label = "Toggle Silhouette"


    diff_col: FloatVectorProperty(default = (0.226, 0.179, 0.141))
    disp_mode: StringProperty(default = 'SOLID')
    matcap: BoolProperty(default = False)
    only_render: BoolProperty(default = False)

    def execute(self, context):


        light_check = bpy.context.preferences.system.solid_lights[0].use

        if light_check == True:
            # Set Lights to Off
            bpy.context.preferences.system.solid_lights[0].use = False
            bpy.context.preferences.system.solid_lights[1].use = False

            # Store variables
            self.diff_col = bpy.context.preferences.system.solid_lights[2].diffuse_color
            self.disp_mode = bpy.context.space_data.viewport_shade
            self.matcap = bpy.context.space_data.use_matcap
            self.only_render = bpy.context.space_data.show_only_render

            bpy.context.preferences.system.solid_lights[2].diffuse_color = 0,0,0
            bpy.context.space_data.viewport_shade = 'SOLID'
            bpy.context.space_data.use_matcap = False
            bpy.context.space_data.show_only_render = True

        else:
            bpy.context.preferences.system.solid_lights[0].use = True
            bpy.context.preferences.system.solid_lights[1].use = True
            bpy.context.preferences.system.solid_lights[2].diffuse_color = self.diff_col
            bpy.context.space_data.viewport_shade = self.disp_mode
            bpy.context.space_data.use_matcap = self.matcap
            bpy.context.space_data.show_only_render = self.only_render

        return {'FINISHED'}



#Adds growLoop to the Addon

class growLoop(bpy.types.Operator):
        """Grows the selected edges in both directions """
        bl_idname = "mesh.grow_loop"
        bl_label = "Grow Loop"
        bl_options = {'REGISTER', 'UNDO'}

        grow: IntProperty(name="Grow Selection", description="How much to grow selection", default= 1, min=1, soft_max=10)

        def execute(self, context):

                grow = self.grow
                sel_mode = bpy.context.tool_settings.mesh_select_mode[:]

                for x in range(grow):
                        if sel_mode[2] == True:

                                edge_sel = []
                                border = []
                                interior = []
                                face_org = []
                                face_loop = []
                                face_grow = []
                                face_sel = []
                                mesh_edges = bpy.context.active_object.data.edges
                                mesh_faces = bpy.context.active_object.data.polygons

                                bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')

                                me = bpy.context.object.data
                                bm = bmesh.from_edit_mesh(me)

                                for e in bm.edges:
                                        if e.select:
                                                edge_sel.append(e.index)

                                for f in bm.faces:
                                        if f.select:
                                                face_org.append(f.index)

                                bpy.ops.mesh.region_to_loop()

                                for e in bm.edges:
                                        if e.select:
                                                border.append(e.index)



                                for e in edge_sel:
                                        if e not in border:
                                                interior.append(e)

                                bmesh.update_edit_mesh(me, True, False)


                                bpy.ops.mesh.select_all(action='DESELECT')

                                #Select the interior edges
                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)


                                for e in interior:
                                        mesh_edges[e].select = True

                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                                bpy.ops.mesh.loop_multi_select(ring=True)
                                bpy.ops.mesh.select_mode(use_extend=False, use_expand=True, type='FACE')

                                me = bpy.context.object.data
                                bm = bmesh.from_edit_mesh(me)

                                for f in bm.faces:
                                        if f.select:
                                                face_loop.append(f.index)

                                bmesh.update_edit_mesh(me, True, False)

                                bpy.ops.mesh.select_all(action='DESELECT')


                                # Select original faces
                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                                for x in face_org:
                                        mesh_faces[x].select = True
                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)


                                bpy.ops.mesh.select_more(use_face_step=False)

                                me = bpy.context.object.data
                                bm = bmesh.from_edit_mesh(me)

                                for f in bm.faces:
                                        if f.select:
                                                face_grow.append(f.index)

                                for f in face_grow:
                                        if f in face_loop:
                                                face_sel.append(f)

                                for f in face_org:
                                        face_sel.append(f)

                                bmesh.update_edit_mesh(me, True, False)

                                bpy.ops.mesh.select_all(action='DESELECT')

                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                                for f in face_sel:
                                        mesh_faces[f].select = True

                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                        else:
                                mesh = bpy.context.active_object.data.edges

                                me = bpy.context.object.data
                                bm = bmesh.from_edit_mesh(me)
                                org_sel = []
                                grow_sel = []
                                loop_sel = []
                                sel = []

                                bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')

                                for e in bm.edges:
                                        if e.select:
                                                org_sel.append(e.index)

                                bpy.ops.mesh.select_more(use_face_step=False)

                                for e in bm.edges:
                                        if e.select:
                                                grow_sel.append(e.index)

                                bpy.ops.mesh.select_all(action='DESELECT')

                                bmesh.update_edit_mesh(me, True, False)

                                # Select the original edges
                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                                for e in org_sel:
                                        mesh[e].select = True
                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)


                                me = bpy.context.object.data
                                bm = bmesh.from_edit_mesh(me)
                                bpy.ops.mesh.loop_multi_select(ring=False)

                                for e in bm.edges:
                                        if e.select:
                                                loop_sel.append(e.index)

                                bmesh.update_edit_mesh(me, True, False)

                                bpy.ops.mesh.select_all(action='DESELECT')

                                for x in loop_sel:
                                        if x in grow_sel:
                                                sel.append(x)

                                bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                                for e in sel:
                                        mesh[e].select = True
                                bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                        bpy.context.tool_settings.mesh_select_mode = sel_mode

                return {'FINISHED'}

#Adds extendLoop to the Addon

class extendLoop(bpy.types.Operator):
        """Uses the active face or edge to extends the selection in one direction"""
        bl_idname = "mesh.extend_loop"
        bl_label = "Extend Loop"
        bl_options = {'REGISTER', 'UNDO'}

        extend: IntProperty(name="Extend Selection", description="How much to extend selection", default= 1, min=1, soft_max=10)

        def execute(self, context):

                sel_mode = bpy.context.tool_settings.mesh_select_mode[:]
                extend = self.extend

                for x in range(extend):
                    if sel_mode[2] == True:

                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        active_face = bpy.context.object.data.polygons.active # find active face
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                        edge_sel = []
                        interior = []
                        face_org = []
                        face_loop = []
                        face_grow = []
                        face_sel = []
                        active_edges = []

                        # Get face selection
                        me = bpy.context.object.data
                        bm = bmesh.from_edit_mesh(me)

                        for f in bm.faces:
                                if f.select:
                                        face_org.append(f.index)

                        face_org.remove(active_face)


                        bmesh.update_edit_mesh(me, True, False)

                        bpy.ops.mesh.select_all(action='DESELECT')
                        mesh = bpy.context.active_object.data.polygons

                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        for x in face_org:
                                mesh[x].select = True
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)



                        # Get edge selection
                        me = bpy.context.object.data
                        bm = bmesh.from_edit_mesh(me)

                        for e in bm.edges:
                                if e.select:
                                        edge_sel.append(e.index)


                        bmesh.update_edit_mesh(me, True, False)


                        # Select Active Face
                        bpy.ops.mesh.select_all(action='DESELECT')
                        mesh = bpy.context.active_object.data.polygons

                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        mesh[active_face].select = True
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')

                        me = bpy.context.object.data
                        bm = bmesh.from_edit_mesh(me)


                        # Store the interior edge

                        for e in bm.edges:
                                if e.select:
                                        active_edges.append(e.index)


                        for e in active_edges:
                                if e in edge_sel:
                                        interior.append(e)

                        bmesh.update_edit_mesh(me, True, False)


                        bpy.ops.mesh.select_all(action='DESELECT')

                        #Select the interior edges
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                        mesh = bpy.context.active_object.data.edges

                        for e in interior:
                                mesh[e].select = True

                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)


                        bpy.ops.mesh.loop_multi_select(ring=True)
                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=True, type='FACE')


                        me = bpy.context.object.data
                        bm = bmesh.from_edit_mesh(me)

                        for f in bm.faces:
                                if f.select:
                                        face_loop.append(f.index)


                        bmesh.update_edit_mesh(me, True, False)

                        bpy.ops.mesh.select_all(action='DESELECT')

                        # Select active face
                        mesh = bpy.context.active_object.data.polygons

                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        mesh[active_face].select = True
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                        bpy.ops.mesh.select_more(use_face_step=False)


                        face_org.append(active_face)

                        me = bpy.context.object.data
                        bm = bmesh.from_edit_mesh(me)

                        for f in bm.faces:
                                if f.select:
                                        face_grow.append(f.index)

                        for f in face_grow:
                                if f in face_loop:
                                        face_sel.append(f)

                        for f in face_sel:
                                if f not in face_org:
                                        active_face = f

                        for f in face_org:
                                face_sel.append(f)

                        bmesh.update_edit_mesh(me, True, False)

                        bpy.ops.mesh.select_all(action='DESELECT')

                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                        for f in face_sel:
                                mesh[f].select = True
                        bpy.context.object.data.polygons.active = active_face

                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                    elif sel_mode[1] == True:

                        mesh = bpy.context.active_object.data
                        org_sel = []
                        grow_sel = []
                        loop_sel = []
                        sel = []
                        org_verts = []
                        active_verts = []

                        # Get active edge
                        me = bpy.context.object.data
                        bm = bmesh.from_edit_mesh(me)

                        for x in reversed(bm.select_history):
                                if isinstance(x, bmesh.types.BMEdge):
                                        active_edge = x.index
                                        break

                        # Store the originally selected edges
                        for e in bm.edges:
                                if e.select:
                                        org_sel.append(e.index)


                        bmesh.update_edit_mesh(me, True, False)

                        # Select active edge
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        mesh.edges[active_edge].select = True
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                        # Get verts of active edge
                        bm = bmesh.from_edit_mesh(me)

                        for v in bm.verts:
                                if v.select:
                                        active_verts.append(v.index)

                        bmesh.update_edit_mesh(me, True, False)

                        # Select original selection minus active edge
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        for x in org_sel:
                            mesh.edges[x].select = True
                        mesh.edges[active_edge].select = False
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                        bm = bmesh.from_edit_mesh(me)

                        # Store the original vertices minus active edge
                        for v in bm.verts:
                            if v.select:
                                org_verts.append(v.index)


                        # Compare verts
                        for x in active_verts:
                            if x in org_verts:
                                active_verts.remove(x)

                        bmesh.update_edit_mesh(me, True, False)

                        # Select end vertex
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        mesh.vertices[active_verts[0]].select = True
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)


                        # Grow the end vertex and store the edges
                        bpy.ops.mesh.select_more(use_face_step=False)
                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
                        bm = bmesh.from_edit_mesh(me)

                        for e in bm.edges:
                                if e.select:
                                        grow_sel.append(e.index)

                        bmesh.update_edit_mesh(me, True, False)
                        bpy.ops.mesh.select_all(action='DESELECT')

                        # Run loop of the active edges and store it
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        mesh.edges[active_edge].select = True
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                        bpy.ops.mesh.loop_multi_select(ring=False)

                        me = bpy.context.object.data
                        bm = bmesh.from_edit_mesh(me)

                        for e in bm.edges:
                                if e.select:
                                        loop_sel.append(e.index)

                        bmesh.update_edit_mesh(me, True, False)
                        bpy.ops.mesh.select_all(action='DESELECT')

                        # Compare loop_sel vs grow_sel
                        for x in loop_sel:
                                if x in grow_sel:
                                        sel.append(x)


                        # Add original selection to new selection

                        for x in org_sel:
                            if x not in sel:
                                sel.append(x)


                        # Compare org_sel with sel to get the active edge
                        for x in sel:
                            if x not in org_sel:
                                active_edge = x

                        # Select the resulting edges
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        for e in sel:
                                mesh.edges[e].select = True
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                        # Set the new active edge
                        bm = bmesh.from_edit_mesh(me)

                        bm.edges.ensure_lookup_table()
                        bm.select_history.add(bm.edges[active_edge])
                        bmesh.update_edit_mesh(me, True, False)


                return {'FINISHED'}


#Adds extendLoop to the Addon

class shrinkLoop(bpy.types.Operator):
        """Shrink the selected loop"""
        bl_idname = "mesh.shrink_loop"
        bl_label = "Shrink Loop"
        bl_options = {'REGISTER', 'UNDO'}

        shrink: IntProperty(name="Shrink Selection", description="How much to shrink selection", default= 1, min=1, soft_max=15)

        def execute(self, context):

                sel_mode = bpy.context.tool_settings.mesh_select_mode[:]
                shrink = self.shrink

                for x in range(shrink):
                    if sel_mode[2] == True:
                        me = bpy.context.object.data
                        bm = bmesh.from_edit_mesh(me)
                        mesh = bpy.context.active_object.data

                        sel = []
                        edge_dic = {}
                        vert_list = []
                        end_verts = []
                        org_faces = []
                        cur_faces = []
                        new_faces = []

                        # Store edges and verts
                        for e in bm.edges:
                            if e.select:
                                sel.append(e.index)

                                # Populate vert_list
                                vert_list.append(e.verts[0].index)
                                vert_list.append(e.verts[1].index)

                                # Store dictionary
                                edge_dic[e.index] = [e.verts[0].index, e.verts[1].index]

                        # Store original faces
                        for f in bm.faces:
                            if f.select:
                                org_faces.append(f.index)

                        # Store end verts
                        for v in vert_list:
                            if vert_list.count(v) == 2:
                                end_verts.append(v)

                        # Check verts in dictionary
                        for key, value in edge_dic.items():
                            if value[0] in end_verts:
                                sel.remove(key)
                                continue
                            if value[1] in end_verts:
                                sel.remove(key)


                        bmesh.update_edit_mesh(me, True, False)

                        # Select the resulting edges
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                        for e in sel:
                            mesh.edges[e].select = True
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')

                        bm = bmesh.from_edit_mesh(me)

                        # Store current faces
                        for f in bm.faces:
                            if f.select:
                                cur_faces.append(f.index)

                        # Compare current and original faces
                        for x in org_faces:
                            if x in cur_faces:
                                new_faces.append(x)

                        bmesh.update_edit_mesh(me, True, False)

                        # Select the resulting faces
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

                        for e in new_faces:
                            mesh.polygons[e].select = True
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                    else:
                        me = bpy.context.object.data
                        bm = bmesh.from_edit_mesh(me)

                        sel = []
                        edge_dic = {}
                        vert_list = []
                        end_verts = []

                        # Store edges and verts in dictionary
                        for e in bm.edges:
                            if e.select:
                                sel.append(e.index)

                                # Populate vert_list
                                vert_list.append(e.verts[0].index)
                                vert_list.append(e.verts[1].index)

                                # Store dictionary
                                edge_dic[e.index] = [e.verts[0].index, e.verts[1].index]

                        # Store end verts
                        for v in vert_list:
                            if vert_list.count(v) == 1:
                                end_verts.append(v)

                        # Check verts in dictionary
                        for key, value in edge_dic.items():
                            if value[0] in end_verts:
                                sel.remove(key)
                                continue
                            if value[1] in end_verts:
                                sel.remove(key)


                        bmesh.update_edit_mesh(me, True, False)

                        # Select the resulting edges
                        bpy.ops.mesh.select_all(action='DESELECT')

                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                        mesh = bpy.context.active_object.data.edges
                        for e in sel:
                            mesh[e].select = True
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)


                return {'FINISHED'}

class paintSelect(bpy.types.Operator):
    """Click and drag to select"""
    bl_idname = "view3d.select_paint"
    bl_label = "Paint Select"
    bl_options = {'REGISTER', 'UNDO'}

    deselect: BoolProperty(default = False, description = 'Deselect objects, polys, edges or verts')
    toggle: BoolProperty(default = False, description = 'Toggles the selection. NOTE: this option can be slow on heavy meshes')
    sel_before: IntProperty(description = 'Do Not Touch', options = {'HIDDEN'})
    sel_after: IntProperty(description = 'Do Not Touch', options = {'HIDDEN'})

    def modal(self, context, event):

        #if event.type == 'MOUSEMOVE':
        refresh = event.mouse_x


        if self.deselect == False:
            bpy.ops.view3d.select('INVOKE_DEFAULT', extend = True, deselect = False)
        else:
            bpy.ops.view3d.select('INVOKE_DEFAULT', extend = False, deselect = True, toggle = True)


        if event.value == 'RELEASE':
            return {'FINISHED'}


        return {'RUNNING_MODAL'}

    def invoke(self, context, event):

        if self.toggle:
            sel_ob = len(bpy.context.selected_objects)

            if sel_ob >= 1:
                mode = bpy.context.object.mode

                if mode == 'EDIT':
                    sel_mode = bpy.context.tool_settings.mesh_select_mode[:]

                    # Get Selection before
                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                    ob = bpy.context.object.data
                    # check verts
                    if sel_mode[0]:
                        for v in ob.vertices:
                            if v.select:
                                self.sel_before += 1
                    # check edges
                    elif sel_mode[1]:
                        for e in ob.edges:
                            if e.select:
                                self.sel_before += 1
                    # check polys
                    else:
                        for p in ob.polygons:
                            if p.select:
                                self.sel_before += 1

                    # Toggle Selection
                    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                    bpy.ops.view3d.select('INVOKE_DEFAULT', extend = False, toggle = True)

                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
                    ob = bpy.context.object.data
                    # check verts after
                    if sel_mode[0]:
                        for v in ob.vertices:
                            if v.select:
                                self.sel_after += 1

                    # check edges after
                    elif sel_mode[1]:
                        for e in ob.edges:
                            if e.select:
                                self.sel_after += 1
                    # check polys after
                    else:
                        for p in ob.polygons:
                            if p.select:
                                self.sel_after += 1


                    if self.sel_after > self.sel_before:
                        self.deselect = False
                    elif self.sel_after == self.sel_before:
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
                        bpy.ops.mesh.select_all(action='DESELECT')
                        return {'FINISHED'}
                    else:
                        self.deselect = True

                    bpy.ops.object.mode_set(mode='EDIT', toggle=False)

                elif mode == 'OBJECT':
                    bpy.ops.view3d.select('INVOKE_DEFAULT', extend = False, toggle = True)

                    sel_ob_after = len(bpy.context.selected_objects)

                    if sel_ob_after < sel_ob:
                        self.deselect = True


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


class pathSelectRing(bpy.types.Operator):
    """Selects the shortest edge ring path"""
    bl_idname = "mesh.path_select_ring"
    bl_label = "Path Select Ring"
    bl_options = {'REGISTER', 'UNDO'}

    pick: BoolProperty(name = "Pick Mode", description = "Pick Mode", default = False)
    collapse: BoolProperty(name = "Collapse", description = "Collapses everything between your two selected edges", default = False)

    def draw(self, context):
        layout = self.layout


    def execute(self, context):

        me = bpy.context.object.data
        bm = bmesh.from_edit_mesh(me)
        mesh = bpy.context.active_object.data
        sel_mode = bpy.context.tool_settings.mesh_select_mode[:]

        org_sel = []
        start_end = []
        active_edge = []
        border_sel = []
        vert_sel = []
        face_sel = []

        if sel_mode[1]:

            bpy.context.tool_settings.mesh_select_mode = [False, True, False]

            if self.pick:
                bpy.ops.view3d.select('INVOKE_DEFAULT', extend=True, deselect=False, toggle=False)


            # Store the Start and End edges
            iterate = 0
            for e in reversed(bm.select_history):
                if isinstance(e, bmesh.types.BMEdge):
                    iterate += 1
                    start_end.append(e)
                    if iterate >= 2:
                        break

            if len(start_end) <= 1:
                if self.collapse:
                    bpy.ops.mesh.merge(type='COLLAPSE', uvs=True)
                    return{'FINISHED'}
                return{'CANCELLED'}

            # Store active edge
            for e in reversed(bm.select_history):
                if isinstance(e, bmesh.types.BMEdge):
                    active_edge = e.index
                    break

            # Store original edges
            for e in bm.edges:
                if e.select:
                    org_sel.append(e)

            # Store visible faces
            bpy.ops.mesh.select_all(action='SELECT')
            for f in bm.faces:
                if f.select:
                    face_sel.append(f)


            # Store boundry edges
            bpy.ops.mesh.region_to_loop()

            for e in bm.edges:
                if e.select:
                    border_sel.append(e)

            bpy.ops.mesh.select_all(action='DESELECT')

            # Select Start and End edges
            for e in start_end:
                e.select = True

            # Hide trick
            bpy.ops.mesh.loop_multi_select(ring=True)

            bpy.ops.mesh.select_mode(use_extend=False, use_expand=True, type='FACE')
            bpy.ops.mesh.hide(unselected=True)

            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
            bpy.ops.mesh.select_all(action='DESELECT')
            for e in start_end:
                e.select = True
            bpy.ops.mesh.shortest_path_select()
            bpy.ops.mesh.select_more()
            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')

            bpy.ops.mesh.select_all(action='INVERT')
            bpy.ops.mesh.reveal()
            bpy.ops.mesh.select_all(action='INVERT')
            bpy.ops.mesh.select_mode(use_extend=False, use_expand=True, type='EDGE')

            # Deselect border edges
            for e in border_sel:
                e.select = False

            # Add to original selection
            for e in bm.edges:
                if e.select:
                    org_sel.append(e)

            # Restore hidden polygons
            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
            for f in face_sel:
                f.select = True
            bpy.ops.mesh.hide(unselected=True)


            # Reselect original selection
            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
            bpy.ops.mesh.select_all(action='DESELECT')
            for e in org_sel:
                e.select = True

            # Set active edge
            bm.select_history.add(bm.edges[active_edge])


            if self.collapse:
                bpy.ops.mesh.merge(type='COLLAPSE', uvs=True)

            bmesh.update_edit_mesh(me, True, False)

            return {'FINISHED'}

        else:
            self.report({'WARNING'}, "This tool only workins in edge mode.")
            return {'CANCELLED'}




#Draws the Custom Menu in Object Mode
class ktools_menu(bpy.types.Menu):
        bl_label = "KTools - Object Mode"
        bl_idname = "OBJECT_MT_ktools_menu"

        def draw(self, context):

                layout = self.layout
                layout.operator_context = 'INVOKE_DEFAULT'
                layout.operator("mesh.draw_poly")
                layout.operator("object.toggle_silhouette")
                layout.operator("mesh.quickbool")
                layout.operator("mesh.calc_normals")
                layout.operator("object.custom_autosmooth")
                layout.operator("object.basic_rename")
                layout.operator("object.lattice_to_selection")


#Draws the Custom Menu in Edit Mode
class VIEW3D_MT_edit_mesh_ktools_menuEdit(bpy.types.Menu):
        bl_label = "KTools - Edit Mode"
        bl_idname = "VIEW3D_MT_edit_mesh_ktools_menuEdit"

        def draw(self, context):



                layout = self.layout

                layout.operator("mesh.cut_tool")
                layout.operator("mesh.snaptoaxis")
                layout.operator("mesh.autotubes")
                layout.operator("mesh.shrinkwrap_smooth")

                layout.operator_context = 'INVOKE_DEFAULT'
                layout.operator("mesh.build_corner")
                layout.operator("object.lattice_to_selection")

                layout.separator()

                layout.operator("mesh.path_select_ring")
                layout.operator("mesh.grow_loop")
                layout.operator("mesh.shrink_loop")
                layout.operator("mesh.extend_loop")

                layout.separator()

                layout.operator("mesh.draw_poly")
                layout.operator("object.toggle_silhouette")
                layout.operator("mesh.quickbool")
                layout.operator("object.custom_autosmooth")
                layout.operator("mesh.calc_normals")
                layout.operator("object.basic_rename")



#Calls the KTools Object Menu
class ktools(bpy.types.Operator): #Namesuggestion: K-Tools or K-Mac
        """Calls the KTools Menu"""
        bl_idname = "object.ktools"
        bl_label = "KTools Object Menu"
        #bl_options = {'REGISTER', 'UNDO'}

        def execute(self, context):


            bpy.ops.wm.call_menu(name=ktools_menu.bl_idname)
            return {'FINISHED'}

            """
            sel_ob = bpy.context.object


            if sel_ob:

                mode = bpy.context.active_object.mode

                if mode == 'EDIT':
                        bpy.ops.wm.call_menu(name=VIEW3D_MT_edit_mesh_ktools_menuEdit.bl_idname)

                else:
                        bpy.ops.wm.call_menu(name=ktools_menu.bl_idname)

                return {'FINISHED'}

            else:
                bpy.ops.wm.call_menu(name=ktools_menu.bl_idname)
                return {'FINISHED'}
                #self.report({'WARNING'}, "Active object is not a mesh.")
                #return {'CANCELLED'}
                """

#Calls the KTools Edit Menu
class ktools_mesh(bpy.types.Operator): #Namesuggestion: K-Tools or K-Mac
        """Calls the KTools Edit Menu"""
        bl_idname = "mesh.ktools_mesh"
        bl_label = "KTools Mesh Menu"
        #bl_options = {'REGISTER', 'UNDO'}

        def execute(self, context):


            bpy.ops.wm.call_menu(name=VIEW3D_MT_edit_mesh_ktools_menuEdit.bl_idname)
            return {'FINISHED'}


# draw function for integration in menus
def menu_func(self, context):
    self.layout.separator()
    self.layout.menu("VIEW3D_MT_edit_mesh_ktools_menuEdit", text = "KTools")
def menu_func_ob(self, context):
    self.layout.separator()
    self.layout.menu("OBJECT_MT_ktools_menu", text = "KTools")

#Register and Unregister all the operators
def register():
        bpy.utils.register_class(lattice_to_selection)
        bpy.utils.register_class(calc_normals)
        bpy.utils.register_class(snaptoaxis)
        bpy.utils.register_class(quickbool)
        bpy.utils.register_class(autotubes)
        bpy.utils.register_class(basicRename)
        bpy.utils.register_class(cut_tool)
        bpy.utils.register_class(customAutoSmooth)
        bpy.utils.register_class(shrinkwrapSmooth)
        bpy.utils.register_class(buildCorner)
        bpy.utils.register_class(drawPoly)
        bpy.utils.register_class(toggleSilhouette)
        bpy.utils.register_class(growLoop)
        bpy.utils.register_class(extendLoop)
        bpy.utils.register_class(shrinkLoop)
        bpy.utils.register_class(paintSelect)
        bpy.utils.register_class(pathSelectRing)
        bpy.utils.register_class(ktools_menu)
        bpy.utils.register_class(VIEW3D_MT_edit_mesh_ktools_menuEdit)
        bpy.utils.register_class(ktools)
        bpy.utils.register_class(ktools_mesh)

        bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_func)
        bpy.types.VIEW3D_MT_object_context_menu.prepend(menu_func_ob)

        kc = bpy.context.window_manager.keyconfigs.addon
        if kc:
            # Add paint select to CTRL+SHIFT+ALT+LeftMouse
            km = kc.keymaps.new(name="3D View", space_type="VIEW_3D")
            kmi = km.keymap_items.new('view3d.select_paint', 'LEFTMOUSE', 'PRESS', shift=True, ctrl=True, alt=True)



def unregister():
        bpy.utils.unregister_class(lattice_to_selection)
        bpy.utils.unregister_class(calc_normals)
        bpy.utils.unregister_class(snaptoaxis)
        bpy.utils.unregister_class(quickbool)
        bpy.utils.unregister_class(autotubes)
        bpy.utils.unregister_class(basicRename)
        bpy.utils.unregister_class(cut_tool)
        bpy.utils.unregister_class(customAutoSmooth)
        bpy.utils.unregister_class(shrinkwrapSmooth)
        bpy.utils.unregister_class(buildCorner)
        bpy.utils.unregister_class(drawPoly)
        bpy.utils.unregister_class(toggleSilhouette)
        bpy.utils.unregister_class(growLoop)
        bpy.utils.unregister_class(extendLoop)
        bpy.utils.unregister_class(shrinkLoop)
        bpy.utils.unregister_class(paintSelect)
        bpy.utils.unregister_class(pathSelectRing)
        bpy.utils.unregister_class(ktools_menu)
        bpy.utils.unregister_class(VIEW3D_MT_edit_mesh_ktools_menuEdit)
        bpy.utils.unregister_class(ktools)
        bpy.utils.unregister_class(ktools_mesh)

        bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_func)
        bpy.types.VIEW3D_MT_object_context_menu.remove(menu_func_ob)

        kc = bpy.context.window_manager.keyconfigs.addon
        if kc:
            km = kc.keymaps["3D View"]
            for kmi in km.keymap_items:
                if kmi.idname == 'view3d.select_paint':
                    km.keymap_items.remove(kmi)
                    break



# This allows you to run the script directly from blenders text editor
# to test the addon without having to install it.
if __name__ == "__main__":
        register()
