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

__bpydoc__ = """\
The PreSel addon enables mesh element preselection highlighting


Documentation

BEWARE : dont change the name of the addon file from "mesh_presel28.py", or the addon will stop working!

First go to User Preferences->Addons install the mesh_presel28.py file and enable the PreSel addon in the Mesh category.
Then simply click "Start PreSel", located in the Presel tab in the N-panel of the 3D View et voila!
Then click "Stop PreSel" to stop preselection highlighting.

When pressing Ctrl, one can highlight face/edge/vert paths.
When pressing Alt or Alt+Ctrl one can highlight loops/rings.
When proportional editing is on a gradient highlight will be shown.  Proportional highlighting will at the moment not update when changing the radius during tool operation, due to a shortcoming in the current Python API, but a feature is implemented which allows using CTRL+SHIFT+MOUSEWHEEL to interactively set the proportional radius!

This addon replaces the circle select with its own implementation:
toggle circle select on/off with Ckey
use ALT+MOUSEWHEEL to set circle select radius
select using RIGHTMOUSE, deselect using ALT+MIDDLEMOUSE
the reason for using ALT is that the user can now transform the view during circle select (so MIDDLEMOUSE cannot be used for deselecting)
also other tools can be used with circle select still operating


Go to Presel Addon Preferences (UserPreferences->Addons->Mesh->PreSel and click arrow next to it) to change options :
SelColour :	 Change the highlight colour when hovering over a selected element with the picker in the panel.
NoSelColour :  Change the highlight colour when hovering over a non-selected element with the picker in the panel.
Change vertex size and edge thickness.
Set face outline drawing to on or off.
When on, you can set the face outline thickness.
Circle select tweaking: if on, use LeftMouse-drag (or RightMouse if you select with Left) to do a one off circle select
operation, use with Alt to do deselecting.  Will be slower when not in circle select, because coordinates need to be
constantly recalculated when something changes.
Show Normals :	when on, the normals of "single highlight" faces/verts will be drawn.
Normal Length :	 set the draw length of the normals.
Ortho: circle zooms :  sets behaviour of the circle select radius when in orthogonal mode - changes when zooming or not
Circle select starting radius :	 whenever starting the addon, this will be the default circle select radius

"""


bl_info = {
	"name": "PreSel",
	"author": "Gert De Roost",
	"version": (1, 13, 1),
	"blender": (2, 80, 0),
	"location": "View3D > Tools",
	"description": "Enables mesh element preselection highlighting.",
	"warning": "",
	"wiki_url": "",
	"tracker_url": "",
	"category": "Mesh"}


import bpy
from bgl import *
import gpu
from gpu_extras.batch import batch_for_shader
import bmesh
from mathutils import *
import math
import threading
import time
from bpy.app.handlers import persistent


started = 0
last = None
select = 1
mx = 0
my = 0
oldmx = 0
oldmy = 0
viewchange = 0
sleeping = 0
oldregion = None
region = None
dontshift = 0
other = 0
shortest = 0
isview = 0		 
matrix = []
actpol = None
propdict = {}
restoredict = {}
selcount1 = -1
oldselcount = 0
parents = {}
pathlist = []
path = 0
ctrl = 0
shift = 0
alt = 0
circle = 0
vscodict = {}
cvscodict = {}
escodict = {}
fscodict = {}
retrodict = {}
circlelist = []
cselect = 0
selobj = None
zctrl = 0
deselcircle = 0
regions = []
outline = 1
mirrored = 0
mirrored2 = 0
quadlist1 = []
quadlist2 = []
ngonlist1 = []
ngonlist2 = []
edgelist1 = []
edgelist2 = []
vertlist1 = []
vertlist2 = []
transforming = 0
none = 0
timing = 0
totallist = []
viewswitch = 0
viewaction = 0
perspmode = -1
oldtotalelem = 0
bm = None
idletimer = None
selchange = 0
leftpress = 0
oldwidth = {}
oldheight = {}
reset = 0
oldcircum = 0
waitformove = 0
stopped = 0
occlucodeon = 0
copyobj = None
shader = None
savlast = None
onetime = 0
nomoveviewchange = 0


# For Mighty Pea : 
lefttweak = 0




class SimpleMouseOperator(bpy.types.Operator):
	bl_idname = "wm.mouse_position"
	bl_label = "Invoke Mouse Operator"

	def invoke(self, context, event):

		global mx, my, mxa, mya

		mxa = event.mouse_x
		mya = event.mouse_y
		if region:
			mx = mxa - region.x
			my = mya - region.y
		
		return {'FINISHED'}


class PreSelAddonPreferences(bpy.types.AddonPreferences):

	bl_idname = "mesh_presel28"

	VertSize: bpy.props.IntProperty(
			name = "VertSize", 
			description = "Sets the size of the highlighted vert",
			min = 1,
			max = 20,
			default = 4)
	
	EdgeThick: bpy.props.FloatProperty(
			name = "EdgeThickness", 
			description = "Sets the thickness of the highlighted edge",
			min = 1,
			max = 20,
			default = 2)
	
	VertThick: bpy.props.FloatProperty(
			name = "VertLineThickness", 
			description = "Sets the thickness of the highlighted vert lines",
			min = 1,
			max = 20,
			default = 1)
	
	FaceOutline: bpy.props.BoolProperty(
			name = "FaceOutline", 
			description = "Highlighted face outline or not",
			default = True)
	
	FaceThick: bpy.props.FloatProperty(
			name = "FaceOutlineThickness", 
			description = "Sets the thickness of the highlighted face outline",
			min = 1,
			max = 20,
			default = 1)
	
	SelColour: bpy.props.FloatVectorProperty(
			name = "SelColour",
			description = "Set highlight colour",
			default = [0, 1, 0],
			size = 3,
			min = 0.0,
			max = 1.0,
			subtype = "COLOR")
	
	NoSelColour: bpy.props.FloatVectorProperty(
			name = "NoSelColour",
			description = "Set highlight colour",
			default = [1, 0, 0],
			size = 3,
			min = 0.0,
			max = 1.0,
			subtype = "COLOR")

	PropColour: bpy.props.FloatVectorProperty(
			name = "PropColour",
			description = "Set highlight colour for proportional editing",
			default = [0, 0, 1],
			size = 3,
			min = 0.0,
			max = 1.0,
			subtype = "COLOR")

	Normals: bpy.props.BoolProperty(
			name = "Show Normals", 
			description = "Show normals of highlighted elements",
			default = False)
	
	NormLength: bpy.props.FloatProperty(
			name = "Normal Length", 
			description = "Sets the length of the normal highlights",
			min = 0,
			max = 10,
			default = 0.5)
	
	Zoom: bpy.props.BoolProperty(
			name = "Ortho: circle zooms", 
			description = "Sets if circle select radius changes with zooming in orthogonal mode",
			default = True)
			
	Radius: bpy.props.FloatProperty(
			name = "Circle select starting radius", 
			description = "Sets the starting radius for the circle select in pixels",
			min = 1,
			max = 1000,
			default = 40)
	
	PropTrans: bpy.props.BoolProperty(
			name = "Proportional on/off when tranforming view", 
			description = "Turns proportional highlighting on/off when transforming the view",
			default = True)
			
	Paint: bpy.props.BoolProperty(
			name = "Turn on/off circle select tweaking (slower)", 
			description = "Turns on/off painting selection with non-select mousebutton",
			default = False)
			


	def draw(self, context):

		row = self.layout.row()
		row.prop(self, "SelColour")
		row.prop(self, "NoSelColour")
		row.prop(self, "PropColour")
		row = self.layout.row()
		row.prop(self, "VertSize")
		row.prop(self, "VertThick")
		self.layout.prop(self, "EdgeThick")
		row = self.layout.row()
		row.prop(self, "FaceOutline")
		if self.FaceOutline:
			row.prop(self, "FaceThick")
		self.layout.prop(self, "Paint")
		self.layout.prop(self, "PropTrans")
		row = self.layout.row()
		row.prop(self, "Normals")
		row.prop(self, "NormLength")
		self.layout.prop(self, "Zoom")
		self.layout.prop(self, "Radius")


class PreSelStop(bpy.types.Operator):
	bl_idname = "presel.stop"
	bl_label = "Stop PreSel"
	bl_description = "Stop PreSel"
	bl_options = {"REGISTER"}
	
	def invoke(self, context, event):
		
		global started, last, propdict, stopped
		
		scn = bpy.context.scene
		
		stopped = 1
		started = 0
		propdict = {}
		last = {}
		if bm:
			if bm.is_valid:
				bm.free()
		if copyobj in list(scn.collection.objects):
			scn.collection.objects.unlink(copyobj)
			bpy.context.view_layer.update()
			bpy.data.objects.remove(copyobj)
		try:
			bpy.app.handlers.depsgraph_update_post.remove(sceneupdate_handler)
		except:
			pass
		
		return {'FINISHED'}


class PreSelStart(bpy.types.Operator):
	bl_idname = "mesh.presel"
	bl_label = "Start PreSel"
	bl_description = "Enables mesh element preselection highlighting"
	bl_options = {"REGISTER"}
	
	def invoke(self, context, event):
	
		global _handle, main, addonprefs, selobj, region, regions, spaces, translation
		global propmode, region, radius, started, perspmode, occlucodeon
		global retrodict, reset, oldtotalelem, oldselectmode
		global halfwidth, halfheight, oldwidth, oldheight
		global stopped, bm, splash, reset, shader

		print("scale", context.preferences.system.ui_scale)
		main = self
		
		scn = bpy.context.scene

		addonprefs = bpy.context.preferences.addons["mesh_presel28"].preferences

		started = 1
		stopped = 0
		
		getregions()
		region = bpy.context.region
		translation = bpy.context.active_object.matrix_world.to_translation()
		adapt()
		
		clearscos()
		retrodict = {}
		for r in regions:
			retrodict[r] = {}

		perspmode = bpy.context.space_data.region_3d.is_perspective
		propmode = bpy.context.tool_settings.use_proportional_edit
		radius = addonprefs.Radius
		
		bpy.ops.object.editmode_toggle()
		bpy.ops.object.editmode_toggle()
		if context.active_object.type == "MESH":
			if bpy.context.mode == "EDIT_MESH":
				selobj = bpy.context.active_object
				mesh = selobj.data
				reset = 1
				bm = bmesh.from_edit_mesh(mesh)
			enter_presel()
			if addonprefs.Paint:
				reset = 1
				makescodict()
		if not(occlucodeon):
			occlucodeon = 0
			try:
				dummy = bm.verts[0].occlusion_get(bpy.context.space_data.region_3d)
				occlucodeon = 1
			except:
				pass
		bpy.context.region.tag_redraw()
			
		splash = 1

		bpy.app.timers.register(proportional_things)
		preseltimer = windowmanager.event_timer_add(0.001, window=window)
		context.window_manager.modal_handler_add(self)
		shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
		if bm:
			if bm.is_valid:
				oldtotalelem = len(bm.faces) + len(bm.edges) + len(bm.verts)
				oldselectmode = bm.select_mode
		bpy.app.handlers.depsgraph_update_post.append(sceneupdate_handler)
		return {'RUNNING_MODAL'}

	def modal(self, context, event):
	
		global selected, select, viewchange, sleeping, circle, oldregion, region
		global dontshift, other, shortest, ctrl, shift, alt, path
		global cselect, radius, oldmx, zctrl, deselcircle, mirrored, mirrored2
		global transforming, none, translation, timing, timer, savlast
		global viewswitch, viewaction, reset, bm, started
		global retrodict, idletimer, savperspm, selchange, leftpress
		global vscodict, cvscodict, escodict, fscodict, restoredict
		global waitformove, splash, occlucodeon, oldtotalelem, oldselectmode
		global nomoveviewchange, onetime, oldcircum

		scn = bpy.context.scene

		bpy.ops.wm.mouse_position('INVOKE_DEFAULT')
		region = None
		for a in bpy.context.screen.areas:
			if not(a.type == "VIEW_3D"):
				continue
			for r in a.regions:
				if not(r.type == "WINDOW"):
					continue
				if mxa > r.x and mya > r.y and mxa < r.x + r.width and mya < r.y + r.height:
					region = r
					break
		if not(region in regions):
			getregions()
			clearscos()
			reset = 1
		if region:
			oldregion = region
		if not(region):
			cselect = 3
			return {'PASS_THROUGH'}
		#if bpy.context.space_data.type == "VIEW_3D":
		#	if bpy.context.space_data.region_quadview:
		#		return {'PASS_THROUGH'}
			
		if event.type == "INBETWEEN_MOUSEMOVE":
			return {'PASS_THROUGH'}
			
		try:
			if scn.PreSelOff:
				return {'PASS_THROUGH'}
		except:
			pass

		if viewswitch and event.type.count("TIMER"):
			return {"RUNNING_MODAL"}
		
		if event.type in ["UP_ARROW"]:
			if event.ctrl and not(event.shift) and not(event.alt):
				viewswitch = 1
				return {'PASS_THROUGH'}
		viewswitch = 0
		
		if started == 0:
			return {"FINISHED"}
		
		if not(context.active_object):
			return {'PASS_THROUGH'}
		if not(context.active_object.type == 'MESH'):
			return {'PASS_THROUGH'}
	
		if sleeping and bpy.context.mode == "EDIT_MESH":
			if not(selobj == bpy.context.active_object):
				started = 0
				enter_presel()
			sleeping = 0
			translation = selobj.matrix_world.to_translation()
			if not(copyobj in list(scn.collection.objects)):
				scn.collection.objects.link(copyobj)
				bpy.context.view_layer.update()
			bm = bmesh.from_edit_mesh(mesh)
			restoredict = {}
			retrodict = {}
			for r in regions:
				retrodict[r] = {}
			adapt()
			enter_presel()
			clearscos()
			reset = 1
			makescodict()
			oldtotalelem = len(bm.faces) + len(bm.edges) + len(bm.verts)
			oldselectmode = bm.select_mode
			if not(occlucodeon):
				occlucodeon = 0
				try:
					dummy = bm.verts[0].occlusion_get(bpy.context.space_data.region_3d)
					occlucodeon = 1
				except:
					pass
		
		if not(bpy.context.mode == "EDIT_MESH"):
			if copyobj in list(scn.collection.objects):
				scn.collection.objects.unlink(copyobj)
				bpy.context.view_layer.update()
			sleeping = 1
			return {'PASS_THROUGH'}
			
		if event.type in ["TAB"] and bpy.context.mode == "EDIT_MESH":
			if event.value in ["RELEASE"]:
				return {'PASS_THROUGH'}

		unval = 0
		if bm:
			if not(bm.is_valid):
				bm.free()
				unval = 1
		if not(bm) or unval:
			bpy.ops.object.editmode_toggle()
			bpy.ops.object.editmode_toggle()
			bm = bmesh.from_edit_mesh(mesh)
			reset = 1
			restoredict = {}
			enter_presel()
			bm.select_flush_mode()
			clearscos()
			makescodict()


		if event.type.count("TIMER") and timing:
			viewchange = 1
			viewaction = 1
			reset = 1
			clearscos()
			windowmanager.event_timer_remove(timer)
			return {'RUNNING_MODAL'}

		if event.type in ["NONE"]:
			return {'PASS_THROUGH'}
		if event.type.count("TIMER") and nomoveviewchange == 0:
			if not(bm.is_valid):
				return {'PASS_THROUGH'}
			try:
				windowmanager.event_timer_remove(idletimer)
			except:
				pass
			if len(vscodict[region].keys()) == len(bm.verts):
				return {'RUNNING_MODAL'}
			count = 0
			for v in bm.verts:
				if v in vscodict[region].keys():
					continue
				for r in regions:
					(x, y), clip = getscreencoords(v.co, r)
					if clip:
						clipset.add(v)
					vscodict[r][v] = (x, y)
					count += 1
				if count > 100:
					break
			idletimer = windowmanager.event_timer_add(0.01, window=window)
			return {'RUNNING_MODAL'}
			
		if event.type in ["LEFTMOUSE"]:
			if splash:
				splash = 0
				return {"PASS_THROUGH"}
		
		if leftpress:
			leftpress = 0
			selcount3 = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel
			if selcount3 == selcount1:
				other = 1
			
		if other:
			bm.free()
			bpy.ops.object.editmode_toggle()
			bpy.ops.object.editmode_toggle()
			bm = bmesh.from_edit_mesh(mesh)
			reset = 1
			restoredict = {}
			enter_presel()
			bm.select_flush_mode()
			if circle or addonprefs.Paint:
				reset = 1
				clearscos()
				makescodict()
			other = 0
						
		if not(event.type == "MOUSEMOVE"):
			try:
				windowmanager.event_timer_remove(idletimer)
			except:
				pass
				
		if selchange:
			selchange = 0
			makeseldict()
			makepropdict()
		
		
		def docselect():
			for elem, dummy in totallist:
				if elem.is_valid:
					if isinstance(elem, bmesh.types.BMEdge):
						for v in elem.verts:
							v.select_set(True)
					elif isinstance(elem, bmesh.types.BMFace):
						for v in elem.verts:
							v.select_set(True)
						for e in elem.edges:
							e.select_set(True)
						bm.faces.active = elem
					elem.select_set(True)
					bm.select_history.add(elem)
			bm.select_flush_mode()
		def docdeselect():
			for elem, dummy in totallist:
				if elem.is_valid:
					if isinstance(elem, bmesh.types.BMEdge):
						for v in elem.verts:
							v.select_set(False)
					elif isinstance(elem, bmesh.types.BMFace):
						for v in elem.verts:
							v.select_set(False)
						for e in elem.edges:
							state = 0
							for f in e.link_faces:
								if f.select:
									state = 1
									break
							e.select_set(state)
					else:
						for f in elem.link_faces:
							f.select_set(False)
					elem.select_set(False)
			bm.select_flush_mode()
			
		if cselect == 1 or cselect == 4:
			calccircle()
			docselect()
			if region:
				bpy.ops.wm.mouse_position('INVOKE_DEFAULT')
				region.tag_redraw()
		if cselect == 2 or cselect == 5:
			calccircle()
			docdeselect()
			if region:
				bpy.ops.wm.mouse_position('INVOKE_DEFAULT')
				region.tag_redraw()

		if mirrored2:
			mirrored2 = 0
			enter_presel()
			
		if transforming:
			transforming = 0

		if event.type in ["RIGHTMOUSE", "ESC"]:
			splash = 0
			if event.value == "RELEASE":
				if mirrored:
					mirrored = 0

		wm = context.window_manager
		keyconfig = wm.keyconfigs.active
		selector = getattr(keyconfig.preferences, "select_mouse", "LEFT") + "MOUSE"
		if event.type in [selector]:
			if event.value in ["PRESS"]:
				dontshift = 1
			else:
				dontshift = 0
				
		tweakselector = "EVT_TWEAK_" + selector[0]
		if selector == "LEFTMOUSE":
			paintselector = "RIGHTMOUSE"
			if lefttweak:
				paintselector = "LEFTMOUSE"
				painttweak = "EVT_TWEAK_L"
		else:
			paintselector = "LEFTMOUSE"
			if lefttweak:
				paintselector = "RIGHTMOUSE"
				painttweak = "EVT_TWEAK_R"
		if event.type in [selector]:
			if circle and not(lefttweak):
				if len(totallist) == 1 and (event.ctrl or event.alt):
					cselect = 3
					return {'PASS_THROUGH'}
				if event.value in ["PRESS"]:
					cselect = 1
					calccircle()
					docselect()
					mesh.update()
				else:
					bpy.ops.ed.undo_push()
					cselect = 3
				return {'RUNNING_MODAL'}
		if addonprefs.Paint:
			if lefttweak:
				if event.type in [painttweak]:
					return {'RUNNING_MODAL'}
			if not(circle):
				if waitformove and event.type in ["MOUSEMOVE"]:
					waitformove += 1
					if waitformove == 2:
						waitformove = 0
						if event.alt:
							if lefttweak:
								return {"PASS_THROUGH"}
							else:
								cselect = 5
						else:
							cselect = 4
						circle = 1
						if region:
							region.tag_redraw()
						return {'RUNNING_MODAL'}
					else:
						return {"PASS_THROUGH"}	
				if event.type in [paintselector]:
					if event.value == "PRESS":	
						waitformove = 1
						calccircle()
						if region:
							region.tag_redraw()
						if lefttweak:
							return {"PASS_THROUGH"}
						else:
							return {'RUNNING_MODAL'}
			if event.type in [paintselector]:
				if event.value == "RELEASE":
					if waitformove:
						waitformove = 0
						if not(lefttweak):
							bpy.ops.view3d.cursor3d("INVOKE_DEFAULT")
						return {'RUNNING_MODAL'}
					if cselect == 4 or cselect == 5:
						circle = 0
						bpy.ops.ed.undo_push()
						cselect = 3
						return {'RUNNING_MODAL'}
		if event.type in ["MIDDLEMOUSE", "EVT_TWEAK_M"]:
			if circle:
				if event.value in ["PRESS"]:
					deselcircle = 1
					if event.alt and not(event.shift) and not (event.ctrl):
						cselect = 2
						calccircle()
						docdeselect()
						mesh.update()
						return {'RUNNING_MODAL'}
				else:
					if deselcircle:
						deselcircle = 0
						bpy.ops.ed.undo_push()
						cselect = 3
						return {'RUNNING_MODAL'}

		if (cselect == 1) or (cselect == 2) or (cselect == 4) or (cselect == 5):
			return {'RUNNING_MODAL'}
		elif cselect == 3:
			cselect = 0
			if bm.is_valid:
				makeseldict()
				makepropdict()
	
		if not(other):
			selcount3 = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel
			if not(selcount3 == selcount1):
				if bm.is_valid:
					makeseldict()
					makepropdict()

		if event.type in ["LEFTMOUSE"]:
			if event.value in ["PRESS"]:
				leftpress = 1
				return {'PASS_THROUGH'}
			if event.value in ["CLICK"]:
				return {'PASS_THROUGH'}
			if event.value == "RELEASE":
				dontshift = 0
				if mirrored:
					mirrored = 0
					mirrored2 = 1
					return {'PASS_THROUGH'}
				return {'PASS_THROUGH'}
#		if event.type in ["EVT_TWEAK_L"]:
#			return {'PASS_THROUGH'}
					
		if event.type.count("NUMPAD"):
			if event.value == "RELEASE":
				timing = 1
				try:
					windowmanager.event_timer_remove(idletimer)
				except:
					pass
				timer = windowmanager.event_timer_add(1, window=window)
				return {'PASS_THROUGH'}

		if event.type in ["O"]:
			if region:
				region.tag_redraw()
		
		if event.type in ["Z"]:
			if event.ctrl:
				zctrl = 1
				other = 2
				return {'PASS_THROUGH'}
			elif zctrl:
				zctrl = 0
				other = 2
				return {'PASS_THROUGH'}
		
		if event.type in ["LEFT_SHIFT", "RIGHT_SHIFT"]:
			return {'PASS_THROUGH'}
		
		if event.type in ["LEFT_ALT", "RIGHT_ALT"]:
			if circle:
				if region:
					oldmx = mx + 1
					region.tag_redraw()
				
		if event.type in ["MOUSEMOVE"]:
			if circle:
				calccircle()
			if event.value == "RELEASE":
				if viewchange:
					trans = 1
					if viewaction == 2:
						if savperspm == bpy.context.space_data.region_3d.perspective_matrix:
							trans = 0
					if trans:
						viewchange = 0
						viewaction = 0
						for r in regions:
							retrodict[r] = {}
						reset = 1
						clearscos()
						makescodict()

		if nomoveviewchange == 2 or event.type in ["MOUSEMOVE", "INBETWEEN_MOUSEMOVE", "LEFT_CTRL", "RIGHT_CTRL", "LEFT_ALT", "RIGHT_ALT"]:
			nomoveviewchange = 0
			if dontshift:
				return {'PASS_THROUGH'}
			if ctrl:
				if not(event.ctrl):
					makepropdict()
			ctrl = event.ctrl
			if event.type in ["LEFT_CTRL", "RIGHT_CTRL"]:
				if event.value in ["RELEASE", "CLICK"]:
					return {'PASS_THROUGH'}
				ctrl = 1
			shift = event.shift
			if alt:
				if not(event.alt):
					makepropdict()
			alt = event.alt
			if event.type in ["LEFT_ALT", "RIGHT_ALT"]:
				if event.value in ["RELEASE", "CLICK"]:
					return {'PASS_THROUGH'}
				alt = 1
			savlast = last
			findelement()
			bm.select_flush_mode()
			
			if not(savlast == last) or ctrl or alt or circle:
				if region:
					region.tag_redraw()
				region.tag_redraw()

			if idletimer:
				oldtimer = idletimer
			idletimer = windowmanager.event_timer_add(0.01, window=window)
			try:
				windowmanager.event_timer_remove(oldtimer)
			except:
				pass
			
			return {'PASS_THROUGH'}
			
		if not(bpy.context.tool_settings.use_proportional_edit == False):
			if event.type in ["WHEELDOWNMOUSE"]:
				if event.ctrl and event.shift and not(event.alt):
					bpy.context.tool_settings.proportional_size += bpy.context.tool_settings.proportional_size / 10
					if region:
						makepropdict()
						region.tag_redraw()
					return {'RUNNING_MODAL'}
			if event.type in ["WHEELUPMOUSE"]:
				if event.ctrl and event.shift and not(event.alt):
					bpy.context.tool_settings.proportional_size -=	bpy.context.tool_settings.proportional_size / 10
					if region:
						makepropdict()
						region.tag_redraw()
					return {'RUNNING_MODAL'}
				
		if event.type in ["WHEELDOWNMOUSE"]:
			if circle:
				if not(event.ctrl) and not(event.shift) and event.alt:
					radius += radius / 20
					if region:
						region.tag_redraw()
					return {'RUNNING_MODAL'}
				
		if event.type in ["WHEELUPMOUSE"]:
			if circle:
				if not(event.ctrl) and not(event.shift) and event.alt:
					radius -= radius / 20
					if region:
						region.tag_redraw()
					return {'RUNNING_MODAL'}
				
		if event.type in ["MIDDLEMOUSE"]:
			if nomoveviewchange != 2:
				nomoveviewchange = 1
			viewchange = 1
			viewaction = 1
			clearscos()
			return {'PASS_THROUGH'}
		if event.type in ["WHEELUPMOUSE", "WHEELDOWNMOUSE"]:
			if nomoveviewchange != 2:
				nomoveviewchange = 1
			viewchange = 1
			viewaction = 2
			clearscos()
			savperspm = spaces[regions.index(region)].region_3d.perspective_matrix.copy()
			return {'PASS_THROUGH'}

		if event.type in ["G", "R", "S", "E"] or event.type.count("TWEAK"):
			transforming = 1
			return {'PASS_THROUGH'}

		if event.type in ["C"]:
			if event.value in ["RELEASE", "CLICK"]:
				return {'RUNNING_MODAL'}
			else:
				if cselect == 4 or cselect == 5:
					return {'RUNNING_MODAL'}
				circle = not(circle)
				if circle or addonprefs.Paint:
					if not(len(vscodict.keys()) == len(bm.verts)):
						reset = 1
						clearscos()
					makescodict()
					calccircle()
				if region:
					region.tag_redraw()
				return {'RUNNING_MODAL'}
			
		if event.type in ["F"]:
			if event.shift and not(event.ctrl) and not(event.alt):
				clearscos()
				viewchange = 2
				return {'PASS_THROUGH'}

		if event.type in ["M"]:
			if event.ctrl and not(event.shift) and not(event.alt):
				mirrored = 1

		if not(other == 2):
			other = 1

		return {'PASS_THROUGH'}


class PANEL_PT_PreSelPanel(bpy.types.Panel):
	bl_label = "PreSel"
	bl_space_type = 'VIEW_3D'
	bl_region_type = 'UI'
	bl_category = 'PreSel'

	def draw(self, context):

		scn = bpy.context.scene
		if not(started):
			self.layout.operator("mesh.presel", text="Start PreSel")
		else:
			self.layout.operator("presel.stop", text="Stop PreSel")





classes = (
	SimpleMouseOperator,
	PANEL_PT_PreSelPanel,
	PreSelAddonPreferences,
	PreSelStop,
	PreSelStart,
)

def register():

	global _handle, started

	from bpy.utils import register_class
	for cls in classes:
		register_class(cls)
	_handle = bpy.types.SpaceView3D.draw_handler_add(drawhighlight, (), 'WINDOW', 'POST_PIXEL')
	bpy.app.handlers.load_post.append(loadpost_handler)
	started = 0


def unregister():
	from bpy.utils import unregister_class
	for cls in classes:
		unregister_class(cls)
	if not(str(_handle).count("RNA_HANDLE_REMOVED")):
		bpy.types.SpaceView3D.draw_handler_remove(_handle, "WINDOW")
	bpy.app.handlers.load_post.remove(loadpost_handler)


if __name__ == "__main__":
	register()
	
	
	

def getmatrix(obj):

	# Calculate matrix.
	if obj.rotation_mode == "AXIS_ANGLE":
		# object rotationmode axisangle
		ang, x, y, z =	obj.rotation_axis_angle
		mat = Matrix.Rotation(-ang, 4, Vector((x, y, z)))
	elif obj.rotation_mode == "QUATERNION":
		# object rotationmode quaternion
		w, x, y, z = obj.rotation_quaternion
		x = -x						
		y = -y
		z = -z
		quat = Quaternion([w, x, y, z])
		mat = quat.to_matrix()
		mat.resize_4x4()
	else:
		# object rotationmode euler
		ax, ay, az = obj.rotation_euler
		mat_rotX = Matrix.Rotation(-ax, 4, 'X')
		mat_rotY = Matrix.Rotation(-ay, 4, 'Y')
		mat_rotZ = Matrix.Rotation(-az, 4, 'Z')
	if obj.rotation_mode == "XYZ":
		mat = mat_rotX @ mat_rotY @ mat_rotZ
	elif obj.rotation_mode == "XZY":
		mat = mat_rotX @ mat_rotZ @ mat_rotY
	elif obj.rotation_mode == "YXZ":
		mat = mat_rotY @ mat_rotX @ mat_rotZ
	elif obj.rotation_mode == "YZX":
		mat = mat_rotY @ mat_rotZ @ mat_rotX
	elif obj.rotation_mode == "ZXY":
		mat = mat_rotZ @ mat_rotX @ mat_rotY
	elif obj.rotation_mode == "ZYX":
		mat = mat_rotZ @ mat_rotY @ mat_rotX

	# handle object scaling
	sx, sy, sz = obj.scale
	mat_scX = Matrix.Scale(sx, 4, Vector([1, 0, 0]))
	mat_scY = Matrix.Scale(sy, 4, Vector([0, 1, 0]))
	mat_scZ = Matrix.Scale(sz, 4, Vector([0, 0, 1]))
	mat = mat_scX @ mat_scY @ mat_scZ @ mat
	
	return mat

def adapt():
	
	global matrix, npmatrix, parents, oldwidth, oldheight
	
	parents = []
	obj = bpy.context.active_object
	matrix = getmatrix(obj)
	while obj.parent:
		parents.append(getmatrix(obj.parent))
		obj = obj.parent
		
		
def getscreencoords(vector, reg):
	# calculate screencoords of given Vector
	vector = vector @ matrix
	for m in parents:
		vector = vector @ m
	vector = vector + translation
	prj = perspm[reg] @ vector.to_4d()
	clip = 0
	if abs(prj.z) > prj.w:
		clip = 1
	
	return (Vector((halfwidth[reg] + halfwidth[reg] * (prj.x / prj.w), halfheight[reg] + halfheight[reg] * (prj.y / prj.w))), clip)
		

def getscreencoords2(vector):
	# calculate screencoords of given Vector
	vector = vector @ matrix
	for m in parents:
		vector = vector @ m
	vector = (vector + translation).to_4d()
	result = []
	for reg in regions:
		perspm = spaces[regions.index(reg)].region_3d.perspective_matrix
		prj = perspm @ vector
		result.append(Vector((halfwidth[reg] + halfwidth[reg] * (prj.x / prj.w), halfheight[reg] + halfheight[reg] * (prj.y / prj.w))))	
	return result[0]
		

def getregions():

	global oldregions, regions, halfheight, halfwidth, spaces, reset
	global oldwidth, oldheight, perspm
	global vscodict, cvscodict, escodict, fscodict, retrodict

	oldregions = list(regions)
	regions = []
	halfheight = {}
	halfwidth = {}
	spaces = []
	perspm = {}
	for a in bpy.context.screen.areas:
		if not(a.type == "VIEW_3D"):
			continue
		for r in a.regions:
			if not(r.type == "WINDOW"):
				continue
			if not(r in oldregions):
				vscodict[r] = {}
				cvscodict[r] = {}
				escodict[r] = {}
				fscodict[r] = {}
				retrodict[r] = {}
			regions.append(r)
			halfwidth[r] = r.width / 2
			halfheight[r] = r.height / 2
			oldwidth[r] = r.width
			oldheight[r] = r.height
			for sp in a.spaces:
				if sp.type == "VIEW_3D":
					spaces.append(sp)
					perspm[r] = sp.region_3d.perspective_matrix


def enter_presel():

	global bm, mesh, selobj, region, started, selected
	global oldlast, last, select, mx, my, viewchange, sleeping, dontshift
	global other, copyobj, copymesh, windowmanager, window

	scn = bpy.context.scene

	last = None
	select = 1
	mx = 0
	my = 0
	sleeping = 0
	dontshift = 0
	other = 0
	windowmanager = bpy.context.window_manager
	window = bpy.context.window
	# main operation
	context = bpy.context
	selobj = bpy.context.active_object
	mesh = selobj.data
	copyobj = bpy.data.objects.get("preselection_secondary")
	if not(copyobj) or not(started):
		if copyobj:
			if copyobj in list(scn.collection.objects):
				scn.collection.objects.unlink(copyobj)
			bpy.data.objects.remove(copyobj)
		copymesh = mesh.copy()
		copyobj = bpy.data.objects.new(name="preselection_secondary", object_data=copymesh)
		scn.collection.objects.link(copyobj)
		bpy.context.view_layer.update()
		copyobj.rotation_euler = selobj.rotation_euler
		copyobj.rotation_quaternion = selobj.rotation_quaternion
		copyobj.location = selobj.location
		copyobj.scale = selobj.scale
		copyobj.hide_set(True)
	else:
		copymesh = copyobj.data
		copymesh2 = mesh.copy()
		copyobj.data = copymesh2	
		bpy.data.meshes.remove(copymesh)
		copymesh = copymesh2
	
	makeseldict()
	
	makepropdict()
	
	
def clearscos():

	global vscodict, cvscodict, escodict, fscodict, clipset

	clipset = set([])
	vscodict = {}
	cvscodict = {}
	escodict = {}
	fscodict = {}
	for r in regions:
		vscodict[r] = {}
		cvscodict[r] = {}
		escodict[r] = {}
		fscodict[r] = {}


def makeseldict():

	global selected, selcount1, codict, restoredict, oldselcount
	global vscodict, cvscodict, escodict, fscodict, retrodict
	global drawregion, reset

	codict = {}
	selected = {}
	if selcount1 >= 0:
		oldselcount = selcount1
		
	if bm:
		if bm.is_valid:
		
			for v in bm.verts:
				selected[v] = v.select
				codict[v] = v.co[0]
				restoredict[v] = v.co[:]
				
			for e in bm.edges:
				selected[e] = e.select
				codict[e] = (e.verts[0].co[0] + e.verts[1].co[0]) / 2
				
			for f in bm.faces:
				selected[f] = f.select
				codict[f] = f.calc_center_median()[0]
		
	selcount1 = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel


def makescodict():

	global selected, selcount1, codict, restoredict, oldselcount, perspm
	global vscodict, cvscodict, escodict, fscodict, retrodict, drawregion, reset

	t = time.time()
	
	getregions()

	if bm:
		if bm.is_valid:
		
			changedf = []
			for v in bm.verts:
				if not(reset):
					if v.co[:] == restoredict[v]:
						continue
					else:
						changedf.extend(v.link_faces[:])
				retro = 0
				try:
					x, y = retrodict[r][v.co[:]]
					retro = 1
				except:
					pass
				for r in regions:
					rv3d = spaces[regions.index(r)].region_3d
					if retro:
						x, y = retrodict[r][v.co[:]]
					else:
						(x, y), clip = getscreencoords(v.co, r)
						if clip:
							clipset.add(v)
						retrodict[r][v.co[:]] = (x, y)
					vscodict[r][v] = (x, y)
					if x < 0:
						continue
					if y < 0:
						continue
					if x > r.width:
						continue
					if y > r.height:
						continue
					cvscodict[r][v] = (x, y)
			
			if "EDGE" in bm.select_mode:
				for e in bm.edges:
					for r in regions:
						rv3d = spaces[regions.index(r)].region_3d
						x1, y1 = vscodict[r][e.verts[0]]
						x2, y2 = vscodict[r][e.verts[1]]
						escodict[r][e] = (x1, y1, x2, y2)
				
			if reset:
				changedf = bm.faces[:]
			if "FACE" in bm.select_mode:
				for f in changedf:
					med = f.calc_center_median()
					for r in regions:
						rv3d = spaces[regions.index(r)].region_3d
						(x, y), clip = getscreencoords(med, r)
						if x < 0:
							continue
						if y < 0:
							continue
						if x > r.width:
							continue
						if y > r.height:
							continue
						fscodict[r][f] = (x, y)
								
			reset = 0
	
	

def makepropdict():

	global quadlist3, ngonlist3, edgelist3, vertlist3, vertlist4
	
	quadlist3 = []
	ngonlist3 = []
	edgelist3 = []
	vertlist3 = []
	vertlist4 = []
	if not(bpy.context.tool_settings.use_proportional_edit == False) and bm:
		# Find proportional alpha for elements
		bpy.ops.transform.translate(value=(0.001, 0, 0), use_proportional_edit=bpy.context.tool_settings.use_proportional_edit, proportional_edit_falloff=bpy.context.tool_settings.proportional_edit_falloff, proportional_size=bpy.context.tool_settings.proportional_size)
		for v in bm.verts:
			if not(selected[v]):
				diff =	v.co[0] - codict[v]
				if diff:
					vertlist3.append((v, diff * 1000))
					vertlist4.append(v)
		if "EDGE" in bm.select_mode:
			for e in bm.edges:
				if not(selected[e]):
					diff =	((e.verts[0].co[0] + e.verts[1].co[0]) / 2) - codict[e]
					if diff:
						edgelist3.append((e, diff * 1000))
		if "FACE" in bm.select_mode:
			for f in bm.faces:
				if not(selected[f]):
					diff =	f.calc_center_median()[0] - codict[f]
					if diff:
						if len(f.verts) == 4:
							quadlist3.append((f, diff * 1000))
						else:
							ngonlist3.append((f, diff * 1000))
		for v in bm.verts:
			v.co = restoredict[v]



def findelement():

	global last, selected, select, viewchange, regions, spaces, reset
	global region, actpol, propdict, pathlist, path, bm
	global halfwidth, halfheight
	global vscodict, cvscodict, escodict, fscodict, retrodict, restoredict
	
	scn = bpy.context.scene
	
	bpy.ops.wm.mouse_position('INVOKE_DEFAULT')
	
	region = None
	for a in bpy.context.screen.areas:
		if not(a.type == "VIEW_3D"):
			continue
		for r in a.regions:
			if not(r.type == "WINDOW"):
				continue
			if mxa > r.x and mya > r.y and mxa < r.x + r.width and mya < r.y + r.height:
				region = r
				break
	if not(region):
		return
	
	contextoverride = {'window': bpy.context.window, 'screen': bpy.context.screen, 'area': bpy.context.area, 'region': region, 'scene': bpy.context.scene, 'edit_object': bpy.context.edit_object, 'active_object': bpy.context.active_object, 'selected_objects': bpy.context.selected_objects}	
		
	if not(bm.is_valid):
		restoredict = {}
		bm = bmesh.from_edit_mesh(mesh)
		reset = 1
		clearscos()
		makeseldict()

	actpol = bm.faces.active
	
	if not(circle) or len(totallist) == 1:
		active = None
		if len(bm.select_history):
			active = bm.select_history[-1]
		pathlist = []
		path = 0
		# Find element under cursor
		if len(bm.select_history):
			oldlast = bm.select_history[-1]
		else:
			oldlast = None
		multi = 0
		special = 0
		if "FACE" in bm.select_mode and ("VERT" in bm.select_mode or "EDGE" in bm.select_mode):
			multi = 1
			bpy.ops.view3d.select(contextoverride, extend=False, toggle=True, location=(mx, my))
			selcount2 = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel
			if selcount1 == selcount2:
				special = 1
				bpy.ops.mesh.select_all(action="INVERT")
				bpy.ops.view3d.select(contextoverride, extend=False, toggle=True, location=(mx, my))
				selcount2 = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel
				if selcount2 == 0:
					bpy.ops.view3d.select(contextoverride, extend=False, toggle=True, location=(mx, my))
					selcount2 = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel
					if selcount2 == 0:
						selcount2 = selcount1
					else:
						bpy.ops.mesh.select_all(action="INVERT")
						selcount2 = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel
				else:
					bpy.ops.mesh.select_all(contextoverride, action="INVERT")
					selcount2 = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel
		else:	
			bpy.ops.view3d.select(contextoverride, extend=False, toggle=True, location=(mx, my))
			selcount2 = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel
					
		if selcount1 == selcount2:
			last = None
			if multi:
				bpy.ops.mesh.select_all(action="DESELECT")
				for k, v in selected.items():
					if v:
						k.select_set(True)
			bm.select_history.clear()
			if active:
				bm.select_history.add(active)
			bm.faces.active = actpol
			return
		case = 0
		if selcount1 > selcount2:
			case = 1
			bpy.ops.view3d.select(contextoverride, extend=False, toggle=True, location=(mx, my))

		if len(bm.select_history):
			last = bm.select_history[-1]
			if alt and not(ctrl):
				bpy.ops.mesh.select_all(action="DESELECT")
				bpy.ops.mesh.loop_select("INVOKE_DEFAULT", ring=False)
				selcount = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel
				bpy.ops.mesh.select_all(action="DESELECT")
				for k, v in selected.items():
					if v:
						k.select_set(True)
				if selcount == 0:
					bm.select_history.clear()
					if active:
						bm.select_history.add(active)
					bm.faces.active = actpol
					return
				bpy.ops.mesh.loop_select("INVOKE_DEFAULT", ring=False)
#				if isinstance(last, bmesh.types.BMEdge):
#					bpy.ops.mesh.loop_select("INVOKE_DEFAULT", ring=False)
				if "FACE" in bm.select_mode:
					for f in bm.faces:
						if f.select:
							pathlist.append(f)
				if "EDGE" in bm.select_mode:
					for e in bm.edges:
						if e.select:
							pathlist.append(e)
				if "VERT" in bm.select_mode:
					for v in bm.verts:
						if v.select:
							pathlist.append(v)
				bpy.ops.mesh.select_all(action="DESELECT")
				for k, v in selected.items():
					if v:
						k.select = True
				bm.select_history.clear()
				if active:
					bm.select_history.add(active)
				bm.faces.active = actpol
				return
				
			if alt and ctrl:
				bpy.ops.mesh.select_all(action="DESELECT")
				bpy.ops.mesh.loop_select("INVOKE_DEFAULT", ring=False)
				selcount = mesh.total_vert_sel + mesh.total_edge_sel + mesh.total_face_sel
				bpy.ops.mesh.select_all(action="DESELECT")
				for k, v in selected.items():
					if v:
						k.select_set(True)
				if selcount == 0:
					bm.select_history.clear()
					if active:
						bm.select_history.add(active)
					bm.faces.active = actpol
					return
				bpy.ops.mesh.loop_select("INVOKE_DEFAULT", ring=True)
				if isinstance(last, bmesh.types.BMEdge):
					bpy.ops.mesh.loop_select("INVOKE_DEFAULT", ring=True)
				if "FACE" in bm.select_mode:
					for f in bm.faces:
						if f.select:
							pathlist.append(f)
				if "EDGE" in bm.select_mode:
					for e in bm.edges:
						if e.select:
							pathlist.append(e)
				if "VERT" in bm.select_mode:
					for v in bm.verts:
						if v.select:
							pathlist.append(v)
				bpy.ops.mesh.select_all(action="DESELECT")
				for k, v in selected.items():
					if v:
						k.select_set(True)
				bm.select_history.clear()
				if active:
					bm.select_history.add(active)
				bm.faces.active = actpol
				return
				
			if not(case):
				if ctrl and not(shift) and not(alt):
					last.select_set(False)
					if active:
						bm.select_history.add(active)
						bm.select_history.validate()
#					bm.select_flush_mode()
				#	bpy.ops.view3d.select(contextoverride, extend=False, toggle=True, location=(mx, my))
					bm.faces.active = actpol
					if active or isinstance(last, bmesh.types.BMFace):
						bpy.ops.mesh.select_all(action="DESELECT")
						if active:
							bm.select_history.add(active)
						if actpol:
							actpol.select_set(True)
						bm.faces.active = actpol
						bpy.ops.mesh.shortest_path_pick("INVOKE_DEFAULT")
						if not(isinstance(last, bmesh.types.BMFace)):
							bm.select_history.add(active)
						for f in bm.faces:
							if f.select:
								if "FACE" in bm.select_mode:
									pathlist.append(f)
						for e in bm.edges:
							if e.select:
								if "EDGE" in bm.select_mode:
									pathlist.append(e)
						for v in bm.verts:
							if v.select:
								if "VERT" in bm.select_mode:
									pathlist.append(v)
						bpy.ops.mesh.select_all(action="DESELECT")
						for k, v in selected.items():
							if v:
								k.select_set(True)
						bm.select_history.clear()
						if active:
							bm.select_history.add(active)
						bm.faces.active = actpol
						return
									
						
						
			if multi:
				if not(oldlast == last):			
					if isinstance(last, bmesh.types.BMFace):
						bpy.ops.view3d.select(contextoverride, extend=False, toggle=True, location=(mx, my))
					if isinstance(last, bmesh.types.BMEdge):
						if "VERT" in bm.select_mode:
							for v in last.verts:
								if selected[v]:
									v.select_set(True)
					if isinstance(last, bmesh.types.BMFace):
						if "VERT" in bm.select_mode:
							for v in last.verts:
								if selected[v]:
									v.select_set(True)
						if "EDGE" in bm.select_mode:
							for e in last.edges:
								if selected[e]:
									e.select_set(True)
					last.select_set(True)
		else:
			last = None
			if multi:
				for k, v in selected.items():
					if v:
						k.select_set(True)
			bm.select_history.clear()
			if active:
				bm.select_history.add(active)
			bm.faces.active = actpol
			return
		if multi:
			bpy.ops.mesh.select_all(action="DESELECT")
			for k, v in selected.items():
				if v:
					k.select_set(True)
		if not(selected[last]):
			if isinstance(last, bmesh.types.BMEdge):
				for v in last.verts:
					if not(selected[v]):
						v.select_set(False)
			if isinstance(last, bmesh.types.BMFace):
				for v in last.verts:
					if not(selected[v]):
						v.select_set(False)
				for e in last.edges:
					if not(selected[e]):
						e.select_set(False)
			if not(multi):
				last.select_set(False)
					
		bm.select_history.clear()
		if active:
			bm.select_history.add(active)
	bm.faces.active = actpol
		
		
		
def calccircle():

	global circlelist, totallist

	drawregion = region
	rv3d = spaces[regions.index(drawregion)].region_3d
	eyevec = Vector(rv3d.view_matrix[2][:3])
	eyevec.length = 100000 
	eyeloc = Vector(rv3d.view_matrix.inverted().col[3][:3])
	eyeloc = (eyeloc - selobj.location) @ matrix.inverted()
	
	r2 = radius
	xl = mx - r2
	xr = mx + r2
	yt = my + r2
	yb = my - r2
	
	copyobj.hide_set(False)
	
	def docircle(dictio):
	
		global incircle, circlelist, outline
	
		incircle = []
		for k, value in dictio.items():
			if len(value) == 4:
				if k.verts[0] in clipset or k.verts[1] in clipset:
					continue
				x1, y1, x2, y2 = value
				if x1 < xl and x2 < xl:
					continue
				if x1 > xr and x2 > xr:
					continue
				if y1 < yb and y2 < yb:
					continue
				if y1 > yt and y2 > yt:
					continue
				if math.sqrt((x1 - mx)**2 + (y1 - my)**2) <= radius:
					incircle.append(k)
				elif math.sqrt((x2 - mx)**2 + (y2 - my)**2) <= radius:
					incircle.append(k)
				else:
					v1 = Vector((x1, y1))
					v2 = Vector((x2, y2))
					m = Vector((mx, my))
					d = v2 - v1
					f = v1 - m
					a = d.dot(d)
					if (a == 0):
						a = 0.00001
					b = 2*f.dot(d)
					c = f.dot(f) - radius*radius
					discriminant = b*b - 4*a*c
					if discriminant >= 0:
						discriminant = math.sqrt(discriminant)
						t1 = (-b - discriminant)/(2*a)
						t2 = (-b + discriminant)/(2*a)
						if t1 >= 0 and t1 <= 1:
							incircle.append(k)
						elif t2 >= 0 and t2 <= 1:
							incircle.append(k)
					
			else:
				x1, y1 = value
				if x1 < xl:
					continue
				if x1 > xr:
					continue
				if y1 < yb:
					continue
				if y1 > yt:
					continue
				if math.sqrt((x1 - mx)**2 + (y1 - my)**2) <= radius:
					incircle.append(k)


		def oneside(k):
		
			def raycast(elem, locco = None, vno = None):
				if not(vno):
					vno = elem.normal
				vno.length = 0.01
				if not(locco):
					locco = elem.co
				vco = locco + vno
				if rv3d.is_perspective:
					hitnr = 0
					hit = copyobj.ray_cast(vco, eyeloc)
					if not(hit[3] == -1):
						vno = -vno
						vco = locco + vno
						hit = copyobj.ray_cast(vco, eyeloc)
						if hit[3] == -1:
							hitnr = 2
					else:
						hitnr = 1
					return hit, hitnr, eyeloc - vco
				else:
					end = ((vco @ matrix) + eyevec) @ matrix.inverted()
					hitnr = 0
					hit = copyobj.ray_cast(vco, end)
					if not(hit[3] == -1):
						vno = -vno
						vco = locco + vno
						hit = copyobj.ray_cast(vco, end)
						if hit[3] == -1:
							hitnr = 2
					else:
						hitnr = 1
					return hit, hitnr, end
				
			if isinstance(k, bmesh.types.BMEdge):
				hit1, hitnr, direc = raycast(k.verts[0])
				hit2, hitnr, direc = raycast(k.verts[1])
				if not(hit1[3] == -1) or not(hit2[3] == -1):
					return
			elif isinstance(k, bmesh.types.BMFace):
				found = 0
				hit, hitnr, direc = raycast(k, k.calc_center_median(), k.normal)
				if hitnr == 0:
					return
				if hitnr == 1:
					if direc.length == 0 or k.normal.length == 0:
						return
					if math.degrees(k.normal.angle(direc)) > 90:
						return
				else:
					if direc.length == 0 or k.normal.length == 0:
						return
					if math.degrees((-1*k.normal).angle(direc)) > 90:
						return
#						for v in k.verts:
#							hit, hitnr, direc = raycast(v, v.co, v.normal)
#							if hitnr == 0:
#								continue
#							if hitnr == 1:
#								if math.degrees(k.normal.angle(direc)) > 90:
#									return
#							else:
#								if math.degrees((-1*k.normal).angle(direc)) > 90:
#									return
#							if hit[2] == -1:
#								found = 1
						
#						if not(found):
#							return
			else:
				hit, hitnr, direc = raycast(k)
				if not(hit[3] == -1):
					return
			circlelist.append(k)
	
	
		circlelist = []
		for elem in incircle:
			try:
				hit = copyobj.ray_cast((0,0,0), (1,1,1))
				error = 0
			except:
				error = 1
			if not(error) and not(bpy.context.space_data.shading.show_xray):
				if occlucodeon:
					state = elem.occlusion_get(rv3d)
					if state:
						circlelist.append(elem)
				else:
					print ("noterror")
					oneside(elem)
			else:
				circlelist = incircle[:]
				
				
	if "VERT" in bm.select_mode:
		docircle(cvscodict[drawregion])
	if "EDGE" in bm.select_mode:
		docircle(escodict[drawregion])
	if "FACE" in bm.select_mode:
		docircle(fscodict[drawregion])
	totallist = quadlist1 + quadlist2 + ngonlist1 + ngonlist2 + edgelist1 + edgelist2 + vertlist1 + vertlist2

	copyobj.hide_set(True)



@persistent
def drawhighlight():

	global drawregion, propdict, oldmx, oldmy, circlelist, circle
	global outline, region, oldregion, oldcircum, onetime
	global quadlist1, quadlist2, ngonlist1, ngonlist2
	global edgelist1, edgelist2, vertlist1, vertlist2, totallist
	global zoomnum, zoomlength, radius, viewaction
	global vscodict
	global started
	global shader

	if shader:
		shader.bind()
	else:
		return

	if stopped:
		return
		
	if sleeping:
		return
	if viewswitch:
		return
	if not(region):
		return
	#if bpy.context.space_data.type == "VIEW_3D":
	#	if bpy.context.space_data.region_quadview:
	#		return

	scn = bpy.context.scene
	
	try:
		if scn.PreSelOff:
			return
	except:
		pass
		
	if bpy.context.space_data.region_3d == None:
		return
	drawregion = bpy.context.region
	if not(drawregion):
		return
	if not(bpy.context.mode == "EDIT_MESH"):
		return
		
	getregions()

	bpy.ops.wm.mouse_position('INVOKE_DEFAULT')

	region = None
	for a in bpy.context.screen.areas:
		if not(a.type == "VIEW_3D"):
			continue
		for r in a.regions:
			if not(r.type == "WINDOW"):
				continue
			if mxa > r.x and mya > r.y and mxa < r.x + r.width and mya < r.y + r.height:
				region = r
				break
	if region:
		oldregion = region
	else: 
		return
	
	
	def drawverts(vertlist, r, g, b, normal, alphaon):
		glDisable(GL_BLEND)
		if not(alphaon):
			col = (r, g, b, 1)
		else:
			glEnable(GL_BLEND)
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)	
		scodict = {}
		indices = ((0, 1, 2), (2, 1, 3))
		for elem, alpha in vertlist:
			coords = []
			if alphaon:
				col = (r, g, b, alpha)
			try:
				x, y = vscodict[drawregion][elem]
			except:
				(x, y), clip = getscreencoords(elem.co, drawregion)
				if clip:
					clipset.add(elem)
				vscodict[elem] = (x, y)
			offset = addonprefs.VertSize * bpy.context.preferences.system.ui_scale / 2
			coords.append((x-offset, y-offset, 0))
			coords.append((x-offset, y+offset, 0))
			coords.append((x+offset, y+offset, 0))
			coords.append((x+offset, y-offset, 0))
			batch = batch_for_shader(shader, 'TRIS', {"pos": coords, "color": [col, col, col, col]}, indices = indices)
			batch.draw(shader)
		
		if outline:
			glLineWidth(addonprefs.VertThick * bpy.context.preferences.system.ui_scale)
			col = bpy.context.preferences.themes[0].view_3d.wire_edit
			r2 = col[0]
			g2 = col[1]
			b2 = col[2]
			for elem, alpha in vertlist:
				for e in elem.link_edges:
					coords = []
					if alphaon:
						col1 =  (r, g, b, alpha)
					else:
						col1 =  (r, g, b, 1)
					try:
						x, y = vscodict[drawregion][elem]
					except:
						(x, y), clip = getscreencoords(elem.co, drawregion)
						if clip:
							clipset.add(elem)
						vscodict[elem] = (x, y)
					coords.append((x, y, 0))
					try:
						x2, y2 = vscodict[drawregion][e.other_vert(elem)]
					except:
						(x2, y2), clip = getscreencoords(e.other_vert(elem).co, drawregion)
						if clip:
							clipset.add(e.other_vert(elem))
						vscodict[e.other_vert(elem)] = (x2, y2)
					if alphaon:
						col2 =  (r2, g2, b2, alpha)
					else:
						col2 =  (r2, g2, b2, 1)
					coords.append((x2, y2, 0))
					batch = batch_for_shader(shader, 'LINES', {"pos": coords, "color": [col1, col2]})
					batch.draw(shader)

		glLineWidth(1 * bpy.context.preferences.system.ui_scale)
		if alphaon:
			glDisable(GL_BLEND)
		r2 = r + 0.5
		g2 = g + 0.5
		b2 = b + 0.5
		if r2 > 1:
			r2 = 1
		if g2 > 1:
			g2 = 1
		if b2 > 1:
			b2 = 1
		if addonprefs.Normals and normal:
			for elem, alpha in vertlist:
				coords = []
				norm = elem.normal
				norm.length = addonprefs.NormLength
				normco = elem.co + norm
				(x2, y2), clip = getscreencoords(normco, drawregion)
				col =  (r2, g2, b2, 1)
				coords.append((x, y, 0))
				coords.append((x2, y2, 0))
				batch = batch_for_shader(shader, 'LINES', {"pos": coords, "color": [col, col]})
				batch.draw(shader)

	def drawedges(edgelist, r, g, b, normal, alphaon):
		if not(alphaon):
			col =  (r, g, b, 1.0)
		else:
			glEnable(GL_BLEND)
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
		glLineWidth(addonprefs.EdgeThick * bpy.context.preferences.system.ui_scale)
		for elem, alpha in edgelist:
			coords = []
			if alphaon:
				col =  (r, g, b, alpha)
			try:
				x1, y1 = vscodict[drawregion][elem.verts[0]]
			except:
				(x1, y1), clip = getscreencoords(elem.verts[0].co, drawregion)
				if clip:
					clipset.add(elem.verts[0])
				vscodict[elem.verts[0]] = (x1, y1)
			try:
				x2, y2 = vscodict[drawregion][elem.verts[1]]
			except:
				(x2, y2), clip = getscreencoords(elem.verts[1].co, drawregion)
				if clip:
					clipset.add(elem.verts[1])
				vscodict[elem.verts[1]] = (x2, y2)
			coords.append((x1, y1, 0))
			coords.append((x2, y2, 0))
			batch = batch_for_shader(shader, 'LINES', {"pos": coords, "color": [col, col]})
			batch.draw(shader)
		glLineWidth(1)		
		if alphaon:
			glDisable(GL_BLEND)
			
	def drawfaces(quadlist, ngonlist, r, g, b, normal, alphaon):
		glEnable(GL_BLEND)
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
		col =  (r, g, b, 0.5)
		indices = ((0, 1, 2), (0, 2, 3))
		if quadlist:
			for elem, alpha in quadlist:
				coords = []
				if alphaon:
					col =  (r, g, b, alpha/2)
				for v in elem.verts:
					try:
						x, y = vscodict[drawregion][v]
					except:
						(x, y), clip = getscreencoords(v.co, drawregion)
						if clip:
							clipset.add(v)
						vscodict[v] = (x, y)
					coords.append((x, y, 0))
				batch = batch_for_shader(shader, 'TRIS', {"pos": coords, "color": [col, col, col, col]}, indices = indices)
				batch.draw(shader)
		if ngonlist:
			for elem, alpha in ngonlist:
				coords = []  #TRIS
				colors = []
				indices = []
				if alphaon:
					col =  (r, g, b, alpha/2)
				for n, v in enumerate(elem.verts):
					try:
						x, y = vscodict[drawregion][v]
					except:
						(x, y), clip = getscreencoords(v.co, drawregion)
						if clip:
							clipset.add(v)
						vscodict[v] = (x, y)
					coords.append((x, y, 0))
					colors.append(col)
					if n > 1:
						indices.append((0, n - 1, n))
				batch = batch_for_shader(shader, 'TRIS', {"pos": coords, "color": colors}, indices = indices)
				batch.draw(shader)
		
		r += 0.5
		g += 0.5
		b += 0.5
		if r > 1:
			r = 1
		if g > 1:
			g = 1
		if b > 1:
			b = 1
		col =  (r, g, b, 0.5)
		facelist = quadlist + ngonlist
		if addonprefs.FaceOutline and outline:
			glLineWidth(addonprefs.FaceThick * bpy.context.preferences.system.ui_scale)
			for elem, alpha in facelist:
				if alphaon:
					col =  (r, g, b, alpha)
				for e in elem.edges:
					coords = []
					for v in e.verts:
						try:
							x, y = vscodict[drawregion][v]
						except:
							(x, y), clip = getscreencoords(v.co, drawregion)
							if clip:
								clipset.add(v)
							vscodict[v] = (x, y)
						coords.append((x, y, 0))
					batch = batch_for_shader(shader, 'LINES', {"pos": coords, "color": [col, col]})
					batch.draw(shader)

		glLineWidth(1)
		glDisable(GL_BLEND)
			
		if addonprefs.Normals and normal:
			col =  (r, g, b, 1)
			for elem, alpha in facelist:
				coords = []
				norm = elem.normal
				norm.length = addonprefs.NormLength
				normco = elem.calc_center_median() + norm
				(x, y), clip = getscreencoords(elem.calc_center_median(), drawregion)
				(x2, y2), clip = getscreencoords(normco, drawregion)
				coords.append((x, y, 0))
				coords.append((x2, y2, 0))
				batch = batch_for_shader(shader, 'LINES', {"pos": coords, "color": [col, col]})
				batch.draw(shader)

	if onetime or ((not(bpy.context.tool_settings.use_proportional_edit == False) and not(addonprefs.PropTrans == False and viewchange))):
		r = addonprefs.PropColour[0]
		g = addonprefs.PropColour[1]
		b = addonprefs.PropColour[2]
		if "FACE" in bm.select_mode:
			outline = 0
			drawfaces(quadlist3, ngonlist3, r, g, b, 0, 1)
			outline = 1
		if "EDGE" in bm.select_mode:
			drawedges(edgelist3, r, g, b, 0, 1)
		if "VERT" in bm.select_mode:
			outline = 0
			drawverts(vertlist3, r, g, b, 0, 1)
			outline = 1
		return

	
	if (circle) and not(mirrored) and not(transforming) and not(viewchange == 2):
		if drawregion == region:
			rv3d = bpy.context.space_data.region_3d
			if addonprefs.Zoom and (rv3d.is_perspective == 0):
				if viewaction < 2:
					zoomnum = 0
					found = 0
					while not(found):
						sco1 = getscreencoords(bm.edges[zoomnum].verts[0].co, drawregion)[0][0]
						sco2 = getscreencoords(bm.edges[zoomnum].verts[1].co, drawregion)[0][0]
						zoomlength = sco1 - sco2
						if zoomlength == 0:
							zoomnum += 1
						else:
							found = 1
				else:
					sco1 = getscreencoords(bm.edges[zoomnum].verts[0].co, drawregion)[0][0]
					sco2 = getscreencoords(bm.edges[zoomnum].verts[1].co, drawregion)[0][0]
					newlength = sco1 - sco2
					radius = (newlength / zoomlength) * radius
					zoomlength = newlength
			
			if viewaction == 1:
				return
				
			glEnable(GL_BLEND)
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)		
			col =  (1, 1, 1, 0.5)
			coords = []
			colors = []
			for j in range(100):
				angle = j * 2 * math.pi / 100
				coords.append((mx + (math.cos(angle) * radius), my + (math.sin(angle) * radius), 0))
				colors.append(col)
			batch = batch_for_shader(shader, 'LINE_LOOP', {"pos": coords, "color": colors})
			batch.draw(shader)
			glDisable(GL_BLEND)
			
			if viewaction == 2:
				return
				
		if drawregion == region:
			quadlist1 = []
			quadlist2 = []
			ngonlist1 = []
			ngonlist2 = []
			edgelist1 = []
			edgelist2 = []
			vertlist1 = []
			vertlist2 = []
			if circlelist:
				r1 = addonprefs.SelColour[0]
				g1 = addonprefs.SelColour[1]
				b1 = addonprefs.SelColour[2]
				r2 = addonprefs.NoSelColour[0]
				g2 = addonprefs.NoSelColour[1]
				b2 = addonprefs.NoSelColour[2]
				if isinstance(circlelist[0], bmesh.types.BMFace):
					for k in circlelist:
						if len(k.verts) == 4:
							if selected[k]:
								quadlist1.append((k, 1))
							else:
								quadlist2.append((k, 1))
						else:
							if selected[k]:
								ngonlist1.append((k, 1))
							else:
								ngonlist2.append((k, 1))
					outline = 0
					drawfaces(quadlist1, ngonlist1, r1, g1, b1, 0, 0)
					drawfaces(quadlist2, ngonlist2, r2, g2, b2, 0, 0)
					outline = 1
				elif isinstance(circlelist[0], bmesh.types.BMEdge):
					for k in circlelist:
						if selected[k]:
							edgelist1.append((k, 1))
						else:
							edgelist2.append((k, 1))
					drawedges(edgelist1, r1, g1, b1, 0, 0)
					drawedges(edgelist2, r2, g2, b2, 0, 0)
				else:
					for k in circlelist:
						if selected[k]:
							vertlist1.append((k, 1))
						else:
							vertlist2.append((k, 1))
					drawverts(vertlist1, r1, g1, b1, 0, 0)
					drawverts(vertlist2, r2, g2, b2, 0, 0)

		else:
			r1 = addonprefs.SelColour[0]
			g1 = addonprefs.SelColour[1]
			b1 = addonprefs.SelColour[2]
			r2 = addonprefs.NoSelColour[0]
			g2 = addonprefs.NoSelColour[1]
			b2 = addonprefs.NoSelColour[2]
			outline = 0
			drawfaces(quadlist1, ngonlist1, r1, g1, b1, 0, 0)
			drawfaces(quadlist2, ngonlist2, r2, g2, b2, 0, 0)
			outline = 1
			drawverts(vertlist1, r1, g1, b1, 0, 0)
			drawverts(vertlist2, r2, g2, b2, 0, 0)
			drawedges(edgelist1, r1, g1, b1, 0, 0)
			drawedges(edgelist2, r2, g2, b2, 0, 0)
			
	if (not(circle) or (len(totallist) == 1)) and pathlist and not(viewchange == 1):
		r1 = addonprefs.SelColour[0]
		g1 = addonprefs.SelColour[1]
		b1 = addonprefs.SelColour[2]
		r2 = addonprefs.NoSelColour[0]
		g2 = addonprefs.NoSelColour[1]
		b2 = addonprefs.NoSelColour[2]
		quadlist1 = []
		ngonlist1 = []
		edgelist1 = []
		vertlist1 = []
		quadlist2 = []
		ngonlist2 = []
		edgelist2 = []
		vertlist2 = []
		for k in pathlist:
			if isinstance(k, bmesh.types.BMFace):
				if len(k.verts) == 4:
					if selected[k]:
						quadlist1.append((k, 1))
					else:
						quadlist2.append((k, 1))
				else:
					if selected[k]:
						ngonlist1.append((k, 1))
					else:
						ngonlist2.append((k, 1))
			elif isinstance(k, bmesh.types.BMEdge):
				if selected[k]:
					edgelist1.append((k, 1))
				else:
					edgelist2.append((k, 1))
			else:
				if selected[k]:
					vertlist1.append((k, 1))
				else:
					vertlist2.append((k, 1))
		outline = 0
		drawfaces(quadlist1, ngonlist1, r1, g1, b1, 0, 0)
		drawfaces(quadlist2, ngonlist2, r2, g2, b2, 0, 0)
		outline = 1
		drawverts(vertlist1, r1, g1, b1, 0, 0)
		drawverts(vertlist2, r2, g2, b2, 0, 0)
		drawedges(edgelist1, r1, g1, b1, 0, 0)
		drawedges(edgelist2, r2, g2, b2, 0, 0)
	
	if not(transforming) and not(circle) and last and not(viewchange == 1):
		if selected[last]:
			r = addonprefs.SelColour[0]
			g = addonprefs.SelColour[1]
			b = addonprefs.SelColour[2]
		else:
			r = addonprefs.NoSelColour[0]
			g = addonprefs.NoSelColour[1]
			b = addonprefs.NoSelColour[2]
		if last.is_valid:
			if isinstance(last, bmesh.types.BMFace):
				drawfaces([], [(last, 1)], r, g, b, 1, 0)
			elif isinstance(last, bmesh.types.BMEdge):
				drawedges([(last, 1)], r, g, b, 0, 0)
			else:
				drawverts([(last, 1)], r, g, b, 1, 0)



@persistent
def proportional_things():

	global oldcircum, onetime


	if onetime or ((not(bpy.context.tool_settings.use_proportional_edit == False) and not(addonprefs.PropTrans == False and viewchange))):
		if transforming or viewchange or leftpress:
			circum = bpy.context.tool_settings.proportional_size
			print(circum)
			if transforming and not(circum == oldcircum):
				makeseldict()
				makepropdict()
				oldcircum = circum
			for v, dummy in vertlist3:
				(x, y), clip = getscreencoords(v.co, drawregion)
				if clip:
					clipset.add(v)
				vscodict[drawregion][v] = (x, y)
			templist = quadlist3 + ngonlist3 + edgelist3
			for elem, dummy in templist:
				for v in elem.verts:
					if v in vertlist4:
						continue
					(x, y), clip = getscreencoords(v.co, drawregion)
					if clip:
						clipset.add(v)
					vscodict[drawregion][v] = (x, y)

		if onetime:
			onetime = 0
			for elem in pathlist + [last]:
				if elem == None:
					continue
				if isinstance(elem, bmesh.types.BMEdge):
					for v in elem.verts:
						v.select_set(True)
				elif isinstance(elem, bmesh.types.BMFace):
					for v in elem.verts:
						v.select_set(True)
					for e in elem.edges:
						e.select_set(True)
				elem.select_set(True)
			bm.select_flush_mode()
			makepropdict()
			for elem in pathlist + [last]:
				if elem == None:
					continue
				if isinstance(elem, bmesh.types.BMEdge):
					for v in elem.verts:
						v.select_set(selected[v])
				elif isinstance(elem, bmesh.types.BMFace):
					for v in elem.verts:
						v.select_set(selected[v])
					for e in elem.edges:
						e.select_set(selected[e])
				elem.select_set(selected[elem])
			bm.select_flush_mode()
		if region:
			region.tag_redraw()
	return 0.1


@persistent
def sceneupdate_handler(dummy):

	global propmode, perspmode, other, oldtotalelem, onetime
	global viewchange, viewaction, reset, oldselectmode, timing
	global nomoveviewchange

	if not(propmode == bpy.context.tool_settings.use_proportional_edit):
		propmode = bpy.context.tool_settings.use_proportional_edit
		onetime = 1
		makepropdict()
		oldregion.tag_redraw()

	if bpy.context.space_data:
		if bpy.context.space_data.type == "VIEW_3D":
			rv3d = bpy.context.space_data.region_3d
			if not(rv3d.is_perspective == perspmode):
				if circle:
					viewchange = 1
					viewaction = 1
					reset = 1
					clearscos()
				perspmode = rv3d.is_perspective
	
	if regions:
		for r in regions:
			if not(r.width == oldwidth[r]) or not(r.height == oldheight[r]):
				viewchange = 1
				viewaction = 1
				reset = 1
				clearscos()
	
	if bm:
		if bm.is_valid:
			totalelem = len(bm.faces) + len(bm.edges) + len(bm.verts)
			if not(totalelem == oldtotalelem):
				other = 2
				oldtotalelem = totalelem
			if not(bm.select_mode == oldselectmode):
				reset = 1
				clearscos()
				makescodict()
				oldselectmode = bm.select_mode

	if nomoveviewchange == 1:
		nomoveviewchange = 2




@persistent
def loadpost_handler(dummy):

	global started
	
	if stopped:
		return

	started = 0


