diff --git a/mesh_snap_utilities_line.py b/mesh_snap_utilities_line.py
deleted file mode 100644
index e70e8b52eb7761a9fb09ef5e06d8f31a46eaab5f..0000000000000000000000000000000000000000
--- a/mesh_snap_utilities_line.py
+++ /dev/null
@@ -1,1191 +0,0 @@
-# ##### 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 3
-#  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, see <http://www.gnu.org/licenses/>.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# Contact for more information about the Addon:
-# Email:    germano.costa@ig.com.br
-# Twitter:  wii_mano @mano_wii
-
-
-bl_info = {
-    "name": "Snap Utilities Line",
-    "author": "Germano Cavalcante",
-    "version": (5, 7, 6),
-    "blender": (2, 75, 0),
-    "location": "View3D > TOOLS > Snap Utilities > snap utilities",
-    "description": "Extends Blender Snap controls",
-    "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Modeling/Snap_Utils_Line",
-    "category": "Mesh"}
-
-import bpy
-import bgl
-import bmesh
-from mathutils import Vector
-from mathutils.geometry import (
-        intersect_point_line,
-        intersect_line_line,
-        intersect_line_plane,
-        intersect_ray_tri
-        )
-from bpy.types import (
-        Operator,
-        Panel,
-        AddonPreferences,
-        )
-from bpy.props import (
-        BoolProperty,
-        FloatProperty,
-        FloatVectorProperty,
-        StringProperty,
-        )
-
-##DEBUG = False
-##if DEBUG:
-##    from .snap_framebuffer_debug import screenTexture
-##    from .snap_context import mesh_drawing
-
-
-def get_units_info(scale, unit_system, separate_units):
-    if unit_system == 'METRIC':
-        scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
-            (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
-    elif unit_system == 'IMPERIAL':
-        scale_steps = ((5280, 'mi'), (1, '\''),
-            (1 / 12, '"'), (1 / 12000, 'thou'))
-        scale /= 0.3048  # BU to feet
-    else:
-        scale_steps = ((1, ' BU'),)
-        separate_units = False
-
-    return (scale, scale_steps, separate_units)
-
-
-def convert_distance(val, units_info, precision=5):
-    scale, scale_steps, separate_units = units_info
-    sval = val * scale
-    idx = 0
-    while idx < len(scale_steps) - 1:
-        if sval >= scale_steps[idx][0]:
-            break
-        idx += 1
-    factor, suffix = scale_steps[idx]
-    sval /= factor
-    if not separate_units or idx == len(scale_steps) - 1:
-        dval = str(round(sval, precision)) + suffix
-    else:
-        ival = int(sval)
-        dval = str(round(ival, precision)) + suffix
-        fval = sval - ival
-        idx += 1
-        while idx < len(scale_steps):
-            fval *= scale_steps[idx - 1][0] / scale_steps[idx][0]
-            if fval >= 1:
-                dval += ' ' \
-                    + ("%.1f" % fval) \
-                    + scale_steps[idx][1]
-                break
-            idx += 1
-
-    return dval
-
-
-def location_3d_to_region_2d(region, rv3d, coord):
-    prj = rv3d.perspective_matrix * Vector((coord[0], coord[1], coord[2], 1.0))
-    width_half = region.width / 2.0
-    height_half = region.height / 2.0
-    return Vector((width_half + width_half * (prj.x / prj.w),
-                   height_half + height_half * (prj.y / prj.w),
-                   prj.z / prj.w
-                   ))
-
-
-def out_Location(rv3d, region, orig, vector):
-    view_matrix = rv3d.view_matrix
-    v1 = Vector((int(view_matrix[0][0] * 1.5), int(view_matrix[0][1] * 1.5), int(view_matrix[0][2] * 1.5)))
-    v2 = Vector((int(view_matrix[1][0] * 1.5), int(view_matrix[1][1] * 1.5), int(view_matrix[1][2] * 1.5)))
-
-    hit = intersect_ray_tri(Vector((1, 0, 0)), Vector((0, 1, 0)), Vector(), (vector), (orig), False)
-    if hit is None:
-        hit = intersect_ray_tri(v1, v2, Vector(), (vector), (orig), False)
-    if hit is None:
-        hit = intersect_ray_tri(v1, v2, Vector(), (-vector), (orig), False)
-    if hit is None:
-        hit = Vector()
-    return hit
-
-
-def get_closest_edge(bm, point, dist):
-    r_edge = None
-    for edge in bm.edges:
-        v1 = edge.verts[0].co
-        v2 = edge.verts[1].co
-        # Test the BVH (AABB) first
-        for i in range(3):
-            if v1[i] <= v2[i]:
-                isect = v1[i] - dist <= point[i] <= v2[i] + dist
-            else:
-                isect = v2[i] - dist <= point[i] <= v1[i] + dist
-
-            if not isect:
-                break
-        else:
-            ret = intersect_point_line(point, v1, v2)
-
-            if ret[1] < 0.0:
-                tmp = v1
-            elif ret[1] > 1.0:
-                tmp = v2
-            else:
-                tmp = ret[0]
-
-            new_dist = (point - tmp).length
-            if new_dist <= dist:
-                dist = new_dist
-                r_edge = edge
-
-    return r_edge
-
-
-class SnapCache():
-        bvert = None
-        vco = None
-
-        bedge = None
-        v0 = None
-        v1 = None
-        vmid = None
-        vperp = None
-        v2d0 = None
-        v2d1 = None
-        v2dmid = None
-        v2dperp = None
-
-        bm_geom_selected = None
-
-
-def snap_utilities(
-        sctx, obj,
-        cache, context, obj_matrix_world,
-        bm, mcursor,
-        constrain = None,
-        previous_vert = None,
-        increment = 0.0):
-
-    rv3d = context.region_data
-    region = context.region
-    scene = context.scene
-    is_increment = False
-    r_loc = None
-    r_type = None
-    r_len = 0.0
-    bm_geom = None
-
-    if cache.bm_geom_selected:
-        try:
-           cache.bm_geom_selected.select = False
-        except ReferenceError as e:
-            print(e)
-
-    snp_obj, loc, elem = sctx.snap_get(mcursor)
-    view_vector, orig = sctx.last_ray
-
-    if not snp_obj:
-        is_increment = True
-        if constrain:
-            end = orig + view_vector
-            t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
-            if t_loc is None:
-                t_loc = constrain
-            r_loc = t_loc[0]
-        else:
-            r_type = 'OUT'
-            r_loc = out_Location(rv3d, region, orig, view_vector)
-
-    elif snp_obj.data[0] != obj: #OUT
-        r_loc = loc
-
-        if constrain:
-            is_increment = False
-            r_loc = intersect_point_line(r_loc, constrain[0], constrain[1])[0]
-            if not r_loc:
-                r_loc = out_Location(rv3d, region, orig, view_vector)
-        elif len(elem) == 1:
-            is_increment = False
-            r_type = 'VERT'
-        elif len(elem) == 2:
-            is_increment = True
-            r_type = 'EDGE'
-        else:
-            is_increment = True
-            r_type = 'FACE'
-
-    elif len(elem) == 1:
-        r_type = 'VERT'
-        bm_geom = bm.verts[elem[0]]
-
-        if cache.bvert != bm_geom:
-            cache.bvert = bm_geom
-            cache.vco = loc
-            #cache.v2d = location_3d_to_region_2d(region, rv3d, cache.vco)
-
-        if constrain:
-            r_loc = intersect_point_line(cache.vco, constrain[0], constrain[1])[0]
-        else:
-            r_loc = cache.vco
-
-    elif len(elem) == 2:
-        v1 = bm.verts[elem[0]]
-        v2 = bm.verts[elem[1]]
-        bm_geom = bm.edges.get([v1, v2])
-
-        if cache.bedge != bm_geom:
-            cache.bedge = bm_geom
-            cache.v0 = obj_matrix_world * v1.co
-            cache.v1 = obj_matrix_world * v2.co
-            cache.vmid = 0.5 * (cache.v0 + cache.v1)
-            cache.v2d0 = location_3d_to_region_2d(region, rv3d, cache.v0)
-            cache.v2d1 = location_3d_to_region_2d(region, rv3d, cache.v1)
-            cache.v2dmid = location_3d_to_region_2d(region, rv3d, cache.vmid)
-
-            if previous_vert and previous_vert not in {v1, v2}:
-                pvert_co = obj_matrix_world * previous_vert.co
-                perp_point = intersect_point_line(pvert_co, cache.v0, cache.v1)
-                cache.vperp = perp_point[0]
-                #factor = point_perpendicular[1]
-                cache.v2dperp = location_3d_to_region_2d(region, rv3d, perp_point[0])
-
-            #else: cache.v2dperp = None
-
-        if constrain:
-            t_loc = intersect_line_line(constrain[0], constrain[1], cache.v0, cache.v1)
-            if t_loc is None:
-                is_increment = True
-                end = orig + view_vector
-                t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
-            r_loc = t_loc[0]
-
-        elif cache.v2dperp and\
-            abs(cache.v2dperp[0] - mcursor[0]) < 10 and abs(cache.v2dperp[1] - mcursor[1]) < 10:
-                r_type = 'PERPENDICULAR'
-                r_loc = cache.vperp
-
-        elif abs(cache.v2dmid[0] - mcursor[0]) < 10 and abs(cache.v2dmid[1] - mcursor[1]) < 10:
-            r_type = 'CENTER'
-            r_loc = cache.vmid
-
-        else:
-            if increment and previous_vert in cache.bedge.verts:
-                is_increment = True
-
-            r_type = 'EDGE'
-            r_loc = loc
-
-    elif len(elem) == 3:
-        is_increment = True
-        r_type = 'FACE'
-        tri = [
-            bm.verts[elem[0]],
-            bm.verts[elem[1]],
-            bm.verts[elem[2]],
-        ]
-
-        faces = set(tri[0].link_faces).intersection(tri[1].link_faces, tri[2].link_faces)
-        if len(faces) == 1:
-            bm_geom = faces.pop()
-        else:
-            i = -2
-            edge = None
-            while not edge and i != 1:
-                edge = bm.edges.get([tri[i], tri[i + 1]])
-                i += 1
-            if edge:
-                for l in edge.link_loops:
-                    if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]:
-                        bm_geom = l.face
-                        break
-                else: # This should never happen!!!!
-                    raise
-                    bm_geom = faces.pop()
-
-        r_loc = loc
-
-        if constrain:
-            is_increment = False
-            r_loc = intersect_point_line(r_loc, constrain[0], constrain[1])[0]
-
-    if previous_vert:
-        pv_co = obj_matrix_world * previous_vert.co
-        vec = r_loc - pv_co
-        if is_increment and increment:
-            r_len = round((1 / increment) * vec.length) * increment
-            r_loc = r_len * vec.normalized() + pv_co
-        else:
-            r_len = vec.length
-
-    if bm_geom:
-        bm_geom.select = True
-
-    cache.bm_geom_selected = bm_geom
-
-    return r_loc, r_type, bm_geom, r_len
-
-
-def get_loose_linked_edges(bmvert):
-    linked = [e for e in bmvert.link_edges if not e.link_faces]
-    for e in linked:
-        linked += [le for v in e.verts if not v.link_faces for le in v.link_edges if le not in linked]
-    return linked
-
-
-def draw_line(self, obj, bm, bm_geom, location):
-    split_faces = set()
-
-    drawing_is_dirt = False
-    update_edit_mesh = False
-    loop_triangles = False
-
-    if bm_geom is None:
-        vert = bm.verts.new(location)
-        self.list_verts.append(vert)
-
-    elif isinstance(bm_geom, bmesh.types.BMVert):
-        if (bm_geom.co - location).length_squared < .001:
-            if self.list_verts == [] or self.list_verts[-1] != bm_geom:
-                self.list_verts.append(bm_geom)
-        else:
-            vert = bm.verts.new(location)
-            self.list_verts.append(vert)
-            drawing_is_dirt = True
-
-    elif isinstance(bm_geom, bmesh.types.BMEdge):
-        self.list_edges.append(bm_geom)
-        ret = intersect_point_line(location, bm_geom.verts[0].co, bm_geom.verts[1].co)
-
-        if (ret[0] - location).length_squared < .001:
-            if ret[1] == 0.0:
-                vert = bm_geom.verts[0]
-            elif ret[1] == 1.0:
-                vert = bm_geom.verts[1]
-            else:
-                edge, vert = bmesh.utils.edge_split(bm_geom, bm_geom.verts[0], ret[1])
-                drawing_is_dirt = True
-            self.list_verts.append(vert)
-            # self.list_edges.append(edge)
-
-        else:  # constrain point is near
-            vert = bm.verts.new(location)
-            self.list_verts.append(vert)
-            drawing_is_dirt = True
-
-    elif isinstance(bm_geom, bmesh.types.BMFace):
-        split_faces.add(bm_geom)
-        vert = bm.verts.new(location)
-        self.list_verts.append(vert)
-        drawing_is_dirt = True
-
-    # draw, split and create face
-    if len(self.list_verts) >= 2:
-        v1, v2 = self.list_verts[-2:]
-        # v2_link_verts = [x for y in [a.verts for a in v2.link_edges] for x in y if x != v2]
-        edge = bm.edges.get([v1, v2])
-        if edge:
-                self.list_edges.append(edge)
-
-        else:  # if v1 not in v2_link_verts:
-            if not v2.link_edges:
-                edge = bm.edges.new([v1, v2])
-                self.list_edges.append(edge)
-                drawing_is_dirt = True
-            else:  # split face
-                v1_link_faces = v1.link_faces
-                v2_link_faces = v2.link_faces
-                if v1_link_faces and v2_link_faces:
-                    split_faces.update(set(v1_link_faces).intersection(v2_link_faces))
-
-                else:
-                    if v1_link_faces:
-                        faces = v1_link_faces
-                        co2 = v2.co.copy()
-                    else:
-                        faces = v2_link_faces
-                        co2 = v1.co.copy()
-
-                    for face in faces:
-                        if bmesh.geometry.intersect_face_point(face, co2):
-                            co = co2 - face.calc_center_median()
-                            if co.dot(face.normal) < 0.001:
-                                split_faces.add(face)
-
-                if split_faces:
-                    edge = bm.edges.new([v1, v2])
-                    self.list_edges.append(edge)
-                    ed_list = get_loose_linked_edges(v2)
-                    for face in split_faces:
-                        facesp = bmesh.utils.face_split_edgenet(face, ed_list)
-                    del split_faces
-                    update_edit_mesh = True
-                    loop_triangles = True
-                else:
-                    if self.intersect:
-                        facesp = bmesh.ops.connect_vert_pair(bm, verts=[v1, v2], verts_exclude=bm.verts)
-                        # print(facesp)
-                    if not self.intersect or not facesp['edges']:
-                        edge = bm.edges.new([v1, v2])
-                        self.list_edges.append(edge)
-                        drawing_is_dirt = True
-                    else:
-                        for edge in facesp['edges']:
-                            self.list_edges.append(edge)
-                            update_edit_mesh = True
-                            loop_triangles = True
-
-        # create face
-        if self.create_face:
-            ed_list = set(self.list_edges)
-            for edge in v2.link_edges:
-                for vert in edge.verts:
-                    if vert != v2 and vert in self.list_verts:
-                        ed_list.add(edge)
-                        break
-                else:
-                    continue
-                # Inner loop had a break, break the outer
-                break
-
-            ed_list.update(get_loose_linked_edges(v2))
-
-            bmesh.ops.edgenet_fill(bm, edges=list(ed_list))
-            update_edit_mesh = True
-            loop_triangles = True
-            # print('face created')
-    if update_edit_mesh:
-        bmesh.update_edit_mesh(obj.data, loop_triangles = loop_triangles)
-        self.sctx.update_drawn_snap_object(self.snap_obj)
-        #bm.verts.index_update()
-    elif drawing_is_dirt:
-        self.obj.update_from_editmode()
-        self.sctx.update_drawn_snap_object(self.snap_obj)
-
-    return [obj.matrix_world * v.co for v in self.list_verts]
-
-
-class NavigationKeys:
-    def __init__(self, context):
-        # TO DO:
-        # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
-        self._rotate = set()
-        self._move = set()
-        self._zoom = set()
-        for key in context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items:
-            if key.idname == 'view3d.rotate':
-                #self.keys_rotate[key.id]={'Alt': key.alt, 'Ctrl': key.ctrl, 'Shift':key.shift, 'Type':key.type, 'Value':key.value}
-                self._rotate.add((key.alt, key.ctrl, key.shift, key.type, key.value))
-            if key.idname == 'view3d.move':
-                self._move.add((key.alt, key.ctrl, key.shift, key.type, key.value))
-            if key.idname == 'view3d.zoom':
-                if key.type == 'WHEELINMOUSE':
-                    self._zoom.add((key.alt, key.ctrl, key.shift, 'WHEELUPMOUSE', key.value, key.properties.delta))
-                elif key.type == 'WHEELOUTMOUSE':
-                    self._zoom.add((key.alt, key.ctrl, key.shift, 'WHEELDOWNMOUSE', key.value, key.properties.delta))
-                else:
-                    self._zoom.add((key.alt, key.ctrl, key.shift, key.type, key.value, key.properties.delta))
-
-
-class CharMap:
-    ascii = {
-        ".", ",", "-", "+", "1", "2", "3",
-        "4", "5", "6", "7", "8", "9", "0",
-        "c", "m", "d", "k", "h", "a",
-        " ", "/", "*", "'", "\""
-        # "="
-        }
-    type = {
-        'BACK_SPACE', 'DEL',
-        'LEFT_ARROW', 'RIGHT_ARROW'
-        }
-
-    @staticmethod
-    def modal(self, context, event):
-        c = event.ascii
-        if c:
-            if c == ",":
-                c = "."
-            self.length_entered = self.length_entered[:self.line_pos] + c + self.length_entered[self.line_pos:]
-            self.line_pos += 1
-        if self.length_entered:
-            if event.type == 'BACK_SPACE':
-                self.length_entered = self.length_entered[:self.line_pos - 1] + self.length_entered[self.line_pos:]
-                self.line_pos -= 1
-
-            elif event.type == 'DEL':
-                self.length_entered = self.length_entered[:self.line_pos] + self.length_entered[self.line_pos + 1:]
-
-            elif event.type == 'LEFT_ARROW':
-                self.line_pos = (self.line_pos - 1) % (len(self.length_entered) + 1)
-
-            elif event.type == 'RIGHT_ARROW':
-                self.line_pos = (self.line_pos + 1) % (len(self.length_entered) + 1)
-
-
-class SnapUtilitiesLine(Operator):
-    bl_idname = "mesh.snap_utilities_line"
-    bl_label = "Line Tool"
-    bl_description = "Draw edges. Connect them to split faces"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    constrain_keys = {
-        'X': Vector((1, 0, 0)),
-        'Y': Vector((0, 1, 0)),
-        'Z': Vector((0, 0, 1)),
-        'RIGHT_SHIFT': 'shift',
-        'LEFT_SHIFT': 'shift',
-        }
-
-    @classmethod
-    def poll(cls, context):
-        preferences = context.user_preferences.addons[__name__].preferences
-        return (context.mode in {'EDIT_MESH', 'OBJECT'} and
-                preferences.create_new_obj or
-                (context.object is not None and
-                context.object.type == 'MESH'))
-
-
-    def draw_callback_px(self, context):
-        # draw 3d point OpenGL in the 3D View
-        bgl.glEnable(bgl.GL_BLEND)
-        bgl.glDisable(bgl.GL_DEPTH_TEST)
-        # bgl.glPushMatrix()
-        # bgl.glMultMatrixf(self.obj_glmatrix)
-
-##        if DEBUG:
-##            mesh_drawing._store_current_shader_state(mesh_drawing.PreviousGLState)
-##            self.screen.Draw(self.sctx._texture)
-##            mesh_drawing._restore_shader_state(mesh_drawing.PreviousGLState)
-
-        if self.vector_constrain:
-            vc = self.vector_constrain
-            if hasattr(self, 'preloc') and self.type in {'VERT', 'FACE'}:
-                bgl.glColor4f(1.0, 1.0, 1.0, 0.5)
-                bgl.glPointSize(5)
-                bgl.glBegin(bgl.GL_POINTS)
-                bgl.glVertex3f(*self.preloc)
-                bgl.glEnd()
-            if vc[2] == 'X':
-                Color4f = (self.axis_x_color + (1.0,))
-            elif vc[2] == 'Y':
-                Color4f = (self.axis_y_color + (1.0,))
-            elif vc[2] == 'Z':
-                Color4f = (self.axis_z_color + (1.0,))
-            else:
-                Color4f = self.constrain_shift_color
-        else:
-            if self.type == 'OUT':
-                Color4f = self.out_color
-            elif self.type == 'FACE':
-                Color4f = self.face_color
-            elif self.type == 'EDGE':
-                Color4f = self.edge_color
-            elif self.type == 'VERT':
-                Color4f = self.vert_color
-            elif self.type == 'CENTER':
-                Color4f = self.center_color
-            elif self.type == 'PERPENDICULAR':
-                Color4f = self.perpendicular_color
-            else: # self.type == None
-                Color4f = self.out_color
-
-        bgl.glColor4f(*Color4f)
-        bgl.glPointSize(10)
-        bgl.glBegin(bgl.GL_POINTS)
-        bgl.glVertex3f(*self.location)
-        bgl.glEnd()
-
-        # draw 3d line OpenGL in the 3D View
-        bgl.glEnable(bgl.GL_DEPTH_TEST)
-        bgl.glDepthRange(0, 0.9999)
-        bgl.glColor4f(1.0, 0.8, 0.0, 1.0)
-        bgl.glLineWidth(2)
-        bgl.glEnable(bgl.GL_LINE_STIPPLE)
-        bgl.glBegin(bgl.GL_LINE_STRIP)
-        for vert_co in self.list_verts_co:
-            bgl.glVertex3f(*vert_co)
-        bgl.glVertex3f(*self.location)
-        bgl.glEnd()
-
-        # restore opengl defaults
-        # bgl.glPopMatrix()
-        bgl.glDepthRange(0, 1)
-        bgl.glPointSize(1)
-        bgl.glLineWidth(1)
-        bgl.glDisable(bgl.GL_BLEND)
-        bgl.glDisable(bgl.GL_LINE_STIPPLE)
-        bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
-
-
-    def modal_navigation(self, context, event):
-        evkey = (event.alt, event.ctrl, event.shift, event.type, event.value)
-        if evkey in self.navigation_keys._rotate:
-            bpy.ops.view3d.rotate('INVOKE_DEFAULT')
-            return True
-        elif evkey in self.navigation_keys._move:
-            if event.shift and self.vector_constrain and \
-                self.vector_constrain[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}:
-                self.vector_constrain = None
-            bpy.ops.view3d.move('INVOKE_DEFAULT')
-            return True
-        else:
-            for key in self.navigation_keys._zoom:
-                if evkey == key[0:5]:
-                    if True: #  TODO: Use Zoom to mouse position
-                        v3d = context.space_data
-                        dist_range = (v3d.clip_start, v3d.clip_end)
-                        rv3d = context.region_data
-                        if (key[5] < 0 and rv3d.view_distance < dist_range[1]) or\
-                           (key[5] > 0 and rv3d.view_distance > dist_range[0]):
-                                rv3d.view_location += key[5] * (self.location - rv3d.view_location) / 6
-                                rv3d.view_distance -= key[5] * rv3d.view_distance / 6
-                    else:
-                        bpy.ops.view3d.zoom('INVOKE_DEFAULT', delta = key[5])
-                    return True
-                    #break
-
-        return False
-
-
-    def modal(self, context, event):
-        if self.modal_navigation(context, event):
-            return {'RUNNING_MODAL'}
-
-        context.area.tag_redraw()
-
-        if event.ctrl and event.type == 'Z' and event.value == 'PRESS':
-            bpy.ops.ed.undo()
-            self.vector_constrain = None
-            self.list_verts_co = []
-            self.list_verts = []
-            self.list_edges = []
-            self.obj = bpy.context.active_object
-            self.obj_matrix = self.obj.matrix_world.copy()
-            self.bm = bmesh.from_edit_mesh(self.obj.data)
-            self.sctx.update_drawn_snap_object(self.snap_obj)
-            return {'RUNNING_MODAL'}
-
-        if event.type == 'MOUSEMOVE' or self.bool_update:
-            if self.rv3d.view_matrix != self.rotMat:
-                self.rotMat = self.rv3d.view_matrix.copy()
-                self.bool_update = True
-                self.cache.bedge = None
-            else:
-                self.bool_update = False
-
-            mval = Vector((event.mouse_region_x, event.mouse_region_y))
-
-            self.location, self.type, self.geom, self.len = snap_utilities(
-                    self.sctx, self.obj, self.cache, context, self.obj_matrix,
-                    self.bm, mval,
-                    constrain = self.vector_constrain,
-                    previous_vert = (self.list_verts[-1] if self.list_verts else None),
-                    increment = self.incremental
-            )
-            if self.snap_to_grid and self.type == 'OUT':
-                loc = self.location / self.rd
-                self.location = Vector((round(loc.x),
-                                        round(loc.y),
-                                        round(loc.z))) * self.rd
-
-            if self.keyf8 and self.list_verts_co:
-                lloc = self.list_verts_co[-1]
-                view_vec, orig = self.sctx.last_ray
-                location = intersect_point_line(lloc, orig, (orig + view_vec))
-                vec = (location[0] - lloc)
-                ax, ay, az = abs(vec.x), abs(vec.y), abs(vec.z)
-                vec.x = ax > ay > az or ax > az > ay
-                vec.y = ay > ax > az or ay > az > ax
-                vec.z = az > ay > ax or az > ax > ay
-                if vec == Vector():
-                    self.vector_constrain = None
-                else:
-                    vc = lloc + vec
-                    try:
-                        if vc != self.vector_constrain[1]:
-                            type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift'
-                            self.vector_constrain = [lloc, vc, type]
-                    except:
-                        type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift'
-                        self.vector_constrain = [lloc, vc, type]
-
-        if event.value == 'PRESS':
-            if self.list_verts_co and (event.ascii in CharMap.ascii or event.type in CharMap.type):
-                CharMap.modal(self, context, event)
-
-            elif event.type in self.constrain_keys:
-                self.bool_update = True
-                if self.vector_constrain and self.vector_constrain[2] == event.type:
-                    self.vector_constrain = ()
-
-                else:
-                    if event.shift:
-                        if isinstance(self.geom, bmesh.types.BMEdge):
-                            if self.list_verts:
-                                loc = self.list_verts_co[-1]
-                                self.vector_constrain = (loc, loc + self.geom.verts[1].co -
-                                                         self.geom.verts[0].co, event.type)
-                            else:
-                                self.vector_constrain = [self.obj_matrix * v.co for
-                                                         v in self.geom.verts] + [event.type]
-                    else:
-                        if self.list_verts:
-                            loc = self.list_verts_co[-1]
-                        else:
-                            loc = self.location
-                        self.vector_constrain = [loc, loc + self.constrain_keys[event.type]] + [event.type]
-
-            elif event.type == 'LEFTMOUSE':
-                point = self.obj_matinv * self.location
-                # with constraint the intersection can be in a different element of the selected one
-                if self.vector_constrain and self.geom:
-                    geom2 = get_closest_edge(self.bm, point, 0.001)
-                else:
-                    geom2 = self.geom
-
-                self.vector_constrain = None
-                self.list_verts_co = draw_line(self, self.obj, self.bm, geom2, point)
-                bpy.ops.ed.undo_push(message="Undo draw line*")
-
-            elif event.type == 'TAB':
-                self.keytab = self.keytab is False
-                if self.keytab:
-                    self.sctx.set_snap_mode(False, False, True)
-                    context.tool_settings.mesh_select_mode = (False, False, True)
-                else:
-                    self.sctx.set_snap_mode(True, True, True)
-                    context.tool_settings.mesh_select_mode = (True, True, True)
-
-            elif event.type == 'F8':
-                self.vector_constrain = None
-                self.keyf8 = self.keyf8 is False
-
-        elif event.value == 'RELEASE':
-            if event.type in {'RET', 'NUMPAD_ENTER'}:
-                if self.length_entered != "" and self.list_verts_co:
-                    try:
-                        text_value = bpy.utils.units.to_value(self.unit_system, 'LENGTH', self.length_entered)
-                        vector = (self.location - self.list_verts_co[-1]).normalized()
-                        location = (self.list_verts_co[-1] + (vector * text_value))
-                        G_location = self.obj_matinv * location
-                        self.list_verts_co = draw_line(self, self.obj, self.bm, self.geom, G_location)
-                        self.length_entered = ""
-                        self.vector_constrain = None
-
-                    except:  # ValueError:
-                        self.report({'INFO'}, "Operation not supported yet")
-
-            elif event.type in {'RIGHTMOUSE', 'ESC'}:
-                if self.list_verts_co == [] or event.type == 'ESC':
-                    del self.bm
-                    del self.list_edges
-                    del self.list_verts
-                    del self.list_verts_co
-
-                    bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
-                    context.area.header_text_set("")
-                    self.sctx.free()
-
-                    #restore initial state
-                    context.user_preferences.view.use_rotate_around_active = self.use_rotate_around_active
-                    context.tool_settings.mesh_select_mode = self.select_mode
-                    if not self.is_editmode:
-                        bpy.ops.object.editmode_toggle()
-
-                    return {'FINISHED'}
-                else:
-                    self.vector_constrain = None
-                    self.list_edges = []
-                    self.list_verts = []
-                    self.list_verts_co = []
-
-        a = ""
-        if self.list_verts_co:
-            if self.length_entered:
-                pos = self.line_pos
-                a = 'length: ' + self.length_entered[:pos] + '|' + self.length_entered[pos:]
-            else:
-                length = self.len
-                length = convert_distance(length, self.uinfo)
-                a = 'length: ' + length
-
-        context.area.header_text_set(
-                        "hit: %.3f %.3f %.3f %s" % (self.location[0],
-                        self.location[1], self.location[2], a)
-                        )
-
-        return {'RUNNING_MODAL'}
-
-    def invoke(self, context, event):
-        if context.space_data.type == 'VIEW_3D':
-            # print('name', __name__, __package__)
-            preferences = context.user_preferences.addons[__name__].preferences
-
-            #Store the preferences that will be used in modal
-            self.intersect = preferences.intersect
-            self.create_face = preferences.create_face
-            self.outer_verts = preferences.outer_verts
-            self.snap_to_grid = preferences.increments_grid
-
-            self.out_color = preferences.out_color
-            self.face_color = preferences.face_color
-            self.edge_color = preferences.edge_color
-            self.vert_color = preferences.vert_color
-            self.center_color = preferences.center_color
-            self.perpendicular_color = preferences.perpendicular_color
-            self.constrain_shift_color = preferences.constrain_shift_color
-
-            self.axis_x_color = tuple(context.user_preferences.themes[0].user_interface.axis_x)
-            self.axis_y_color = tuple(context.user_preferences.themes[0].user_interface.axis_y)
-            self.axis_z_color = tuple(context.user_preferences.themes[0].user_interface.axis_z)
-
-            if context.mode == 'OBJECT' and \
-              (preferences.create_new_obj or context.object is None or context.object.type != 'MESH'):
-
-                mesh = bpy.data.meshes.new("")
-                obj = bpy.data.objects.new("", mesh)
-                context.scene.objects.link(obj)
-                context.scene.objects.active = obj
-
-            #Store current state
-            self.is_editmode = context.object.data.is_editmode
-            self.use_rotate_around_active = context.user_preferences.view.use_rotate_around_active
-            self.select_mode = context.tool_settings.mesh_select_mode[:]
-
-            #Modify the current state
-            bpy.ops.object.mode_set(mode='EDIT')
-            bpy.ops.mesh.select_all(action='DESELECT')
-            context.user_preferences.view.use_rotate_around_active = True
-            context.tool_settings.mesh_select_mode = (True, True, True)
-            context.space_data.use_occlude_geometry = True
-
-            #Configure the unit of measure
-            scale = context.scene.unit_settings.scale_length
-            self.unit_system = context.scene.unit_settings.system
-            separate_units = context.scene.unit_settings.use_separate
-            self.uinfo = get_units_info(scale, self.unit_system, separate_units)
-
-            scale /= context.space_data.grid_scale * preferences.relative_scale
-            self.rd = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(1 / scale))
-
-            self.incremental = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(preferences.incremental))
-
-            #Store values from 3d view context
-            self.rv3d = context.region_data
-            self.rotMat = self.rv3d.view_matrix.copy()
-            self.obj = bpy.context.active_object
-            self.obj_matrix = self.obj.matrix_world.copy()
-            self.obj_matinv = self.obj_matrix.inverted()
-            # self.obj_glmatrix = bgl.Buffer(bgl.GL_FLOAT, [4, 4], self.obj_matrix.transposed())
-            self.bm = bmesh.from_edit_mesh(self.obj.data) #remove at end
-            self.cache = SnapCache()
-
-            #init these variables to avoid errors
-            self.prevloc = Vector()
-            self.location = Vector()
-            self.list_verts = []
-            self.list_edges = []
-            self.list_verts_co = []
-            self.bool_update = False
-            self.vector_constrain = ()
-            self.navigation_keys = NavigationKeys(context)
-            self.type = 'OUT'
-            self.len = 0
-            self.length_entered = ""
-            self.line_pos = 0
-            self.bm_geom_selected = None
-
-            #Init event variables
-            self.keytab = False
-            self.keyf8 = False
-
-            #Init Snap Context
-            from snap_context import SnapContext
-
-            self.sctx = SnapContext(context.region, context.space_data)
-            self.sctx.set_pixel_dist(12)
-            self.sctx.use_clip_planes(True)
-
-            act_base = context.active_base
-
-            if self.outer_verts:
-                for base in context.visible_bases:
-                    if base != act_base:
-                        self.sctx.add_obj(base.object, base.object.matrix_world)
-
-            self.snap_obj = self.sctx.add_obj(act_base.object, act_base.object.matrix_world)
-
-            self.snap_face = context.space_data.viewport_shade not in {'BOUNDBOX', 'WIREFRAME'}
-            self.sctx.set_snap_mode(True, True, self.snap_face)
-
-            #modals
-            self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, (context,), 'WINDOW', 'POST_VIEW')
-            context.window_manager.modal_handler_add(self)
-
-            return {'RUNNING_MODAL'}
-        else:
-            self.report({'WARNING'}, "Active space must be a View3d")
-            return {'CANCELLED'}
-
-
-class PanelSnapUtilities(Panel):
-    bl_space_type = "VIEW_3D"
-    bl_region_type = "TOOLS"
-    bl_category = "Snap Utilities"
-    bl_label = "Snap Utilities"
-
-    @classmethod
-    def poll(cls, context):
-        preferences = context.user_preferences.addons[__name__].preferences
-        return (context.mode in {'EDIT_MESH', 'OBJECT'} and
-                preferences.create_new_obj or
-                (context.object is not None and
-                context.object.type == 'MESH'))
-
-    def draw(self, context):
-        layout = self.layout
-        TheCol = layout.column(align=True)
-        TheCol.operator("mesh.snap_utilities_line", text="Line", icon="GREASEPENCIL")
-
-        addon_prefs = context.user_preferences.addons[__name__].preferences
-        expand = addon_prefs.expand_snap_settings
-        icon = "TRIA_DOWN" if expand else "TRIA_RIGHT"
-
-        box = layout.box()
-        box.prop(addon_prefs, "expand_snap_settings", icon=icon,
-            text="Settings:", emboss=False)
-        if expand:
-            box.prop(addon_prefs, "outer_verts")
-            box.prop(addon_prefs, "incremental")
-            box.prop(addon_prefs, "increments_grid")
-            if addon_prefs.increments_grid:
-                box.prop(addon_prefs, "relative_scale")
-            box.label(text="Line Tool:")
-            box.prop(addon_prefs, "intersect")
-            box.prop(addon_prefs, "create_face")
-            box.prop(addon_prefs, "create_new_obj")
-
-
-# Add-ons Preferences Update Panel
-
-# Define Panel classes for updating
-panels = (
-        PanelSnapUtilities,
-        )
-
-
-def update_panel(self, context):
-    message = "Snap Utilities Line: Updating Panel locations has failed"
-    addon_prefs = context.user_preferences.addons[__name__].preferences
-    try:
-        for panel in panels:
-            if addon_prefs.category != panel.bl_category:
-                if "bl_rna" in panel.__dict__:
-                    bpy.utils.unregister_class(panel)
-
-                panel.bl_category = addon_prefs.category
-                bpy.utils.register_class(panel)
-
-    except Exception as e:
-        print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
-        pass
-
-
-class SnapAddonPreferences(AddonPreferences):
-    # this must match the addon name, use '__package__'
-    # when defining this in a submodule of a python package.
-    bl_idname = __name__
-
-    intersect = BoolProperty(
-            name="Intersect",
-            description="Intersects created line with the existing edges, "
-                        "even if the lines do not intersect",
-            default=True
-            )
-    create_new_obj = BoolProperty(
-            name="Create a new object",
-            description="If have not a active object, or the active object "
-                        "is not in edit mode, it creates a new object",
-            default=False
-            )
-    create_face = BoolProperty(
-            name="Create faces",
-            description="Create faces defined by enclosed edges",
-            default=False
-            )
-    outer_verts = BoolProperty(
-            name="Snap to outer vertices",
-            description="The vertices of the objects are not activated also snapped",
-            default=True
-            )
-    expand_snap_settings = BoolProperty(
-            name="Expand",
-            description="Expand, to display the settings",
-            default=False
-            )
-    expand_color_settings = BoolProperty(
-            name="Color Settings",
-            description="Expand, to display the color settings",
-            default=False
-            )
-    increments_grid = BoolProperty(
-            name="Increments of Grid",
-            description="Snap to increments of grid",
-            default=False
-            )
-    category = StringProperty(
-            name="Category",
-            description="Choose a name for the category of the panel",
-            default="Snap Utilities",
-            update=update_panel
-            )
-    incremental = FloatProperty(
-            name="Incremental",
-            description="Snap in defined increments",
-            default=0,
-            min=0,
-            step=1,
-            precision=3
-            )
-    relative_scale = FloatProperty(
-            name="Relative Scale",
-            description="Value that divides the global scale",
-            default=1,
-            min=0,
-            step=1,
-            precision=3
-            )
-    out_color = FloatVectorProperty(
-            name="OUT",
-            default=(0.0, 0.0, 0.0, 0.5),
-            size=4,
-            subtype="COLOR",
-            min=0, max=1
-            )
-    face_color = FloatVectorProperty(
-            name="FACE",
-            default=(1.0, 0.8, 0.0, 1.0),
-            size=4,
-            subtype="COLOR",
-            min=0, max=1
-            )
-    edge_color = FloatVectorProperty(
-            name="EDGE",
-            default=(0.0, 0.8, 1.0, 1.0),
-            size=4,
-            subtype="COLOR",
-            min=0, max=1
-            )
-    vert_color = FloatVectorProperty(
-            name="VERT",
-            default=(1.0, 0.5, 0.0, 1.0),
-            size=4,
-            subtype="COLOR",
-            min=0, max=1
-            )
-    center_color = FloatVectorProperty(
-            name="CENTER",
-            default=(1.0, 0.0, 1.0, 1.0),
-            size=4,
-            subtype="COLOR",
-            min=0, max=1
-            )
-    perpendicular_color = FloatVectorProperty(
-            name="PERPENDICULAR",
-            default=(0.1, 0.5, 0.5, 1.0),
-            size=4,
-            subtype="COLOR",
-            min=0, max=1
-            )
-    constrain_shift_color = FloatVectorProperty(
-            name="SHIFT CONSTRAIN",
-            default=(0.8, 0.5, 0.4, 1.0),
-            size=4,
-            subtype="COLOR",
-            min=0, max=1
-            )
-
-    def draw(self, context):
-        layout = self.layout
-        icon = "TRIA_DOWN" if self.expand_color_settings else "TRIA_RIGHT"
-
-        box = layout.box()
-        box.prop(self, "expand_color_settings", icon=icon, toggle=True, emboss=False)
-        if self.expand_color_settings:
-            split = box.split()
-
-            col = split.column()
-            col.prop(self, "out_color")
-            col.prop(self, "constrain_shift_color")
-            col = split.column()
-            col.prop(self, "face_color")
-            col.prop(self, "center_color")
-            col = split.column()
-            col.prop(self, "edge_color")
-            col.prop(self, "perpendicular_color")
-            col = split.column()
-            col.prop(self, "vert_color")
-
-        row = layout.row()
-
-        col = row.column(align=True)
-        box = col.box()
-        box.label(text="Snap Items:")
-        box.prop(self, "incremental")
-        box.prop(self, "outer_verts")
-        box.prop(self, "increments_grid")
-        if self.increments_grid:
-            box.prop(self, "relative_scale")
-        else:
-            box.separator()
-            box.separator()
-
-        col = row.column(align=True)
-        box = col.box()
-        box.label(text="Line Tool:")
-        box.prop(self, "intersect")
-        box.prop(self, "create_face")
-        box.prop(self, "create_new_obj")
-        box.separator()
-        box.separator()
-
-        row = layout.row()
-        col = row.column()
-        col.label(text="Tab Category:")
-        col.prop(self, "category", text="")
-
-
-def register():
-    bpy.utils.register_class(SnapAddonPreferences)
-    bpy.utils.register_class(SnapUtilitiesLine)
-    bpy.utils.register_class(PanelSnapUtilities)
-    update_panel(None, bpy.context)
-
-
-def unregister():
-    bpy.utils.unregister_class(PanelSnapUtilities)
-    bpy.utils.unregister_class(SnapUtilitiesLine)
-    bpy.utils.unregister_class(SnapAddonPreferences)
-
-
-if __name__ == "__main__":
-    __name__ = "mesh_snap_utilities_line"
-    register()
diff --git a/mesh_snap_utilities_line/__init__.py b/mesh_snap_utilities_line/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..390cbcd0f8d71ca93b83920d134bb456f0afede4
--- /dev/null
+++ b/mesh_snap_utilities_line/__init__.py
@@ -0,0 +1,103 @@
+### 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 3
+#  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, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Contact for more information about the Addon:
+# Email:    germano.costa@ig.com.br
+# Twitter:  wii_mano @mano_wii
+
+bl_info = {
+    "name": "Snap_Utilities_Line",
+    "author": "Germano Cavalcante",
+    "version": (5, 8, 22),
+    "blender": (2, 80, 0),
+    "location": "View3D > TOOLS > Snap Utilities > snap utilities",
+    "description": "Extends Blender Snap controls",
+    "wiki_url" : "http://blenderartists.org/forum/showthread.php?363859-Addon-CAD-Snap-Utilities",
+    "category": "Mesh"}
+
+if "bpy" in locals():
+    import importlib
+    importlib.reload(preferences)
+    importlib.reload(ops_line)
+else:
+    from . import preferences
+    from . import ops_line
+
+import bpy
+from bpy.utils.toolsystem import ToolDef
+
+@ToolDef.from_fn
+def tool_make_line():
+    import os
+    def draw_settings(context, layout, tool):
+        addon_prefs = context.user_preferences.addons["mesh_snap_utilities_line"].preferences
+
+        layout.prop(addon_prefs, "incremental")
+        layout.prop(addon_prefs, "increments_grid")
+        if addon_prefs.increments_grid:
+            layout.prop(addon_prefs, "relative_scale")
+        layout.prop(addon_prefs, "create_face")
+        layout.prop(addon_prefs, "outer_verts")
+        #props = tool.operator_properties("mesh.snap_utilities_line")
+        #layout.prop(props, "radius")
+
+    icons_dir = os.path.join(os.path.dirname(__file__), "icons")
+
+    return dict(
+        text="Make Line",
+        description=(
+            "Make Lines\n"
+            "Connect them to split faces"
+        ),
+        icon=os.path.join(icons_dir, "ops.mesh.make_line"),
+        widget=None,
+        operator="mesh.make_line",
+        keymap=(
+            ("mesh.make_line", None, dict(type='ACTIONMOUSE', value='PRESS')),
+        ),
+        draw_settings=draw_settings,
+    )
+
+
+def get_tool_list(space_type, context_mode):
+    from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
+    cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
+    return cls._tools[context_mode]
+
+def register():
+    bpy.utils.register_class(preferences.SnapUtilitiesLinePreferences)
+    bpy.utils.register_class(ops_line.SnapUtilitiesLine)
+
+    bpy.utils.register_tool('VIEW_3D', 'EDIT_MESH', tool_make_line)
+
+    # Move tool to after 'Add Cube'
+    tools = get_tool_list('VIEW_3D', 'EDIT_MESH')
+    for index, tool in enumerate(tools):
+        if isinstance(tool, ToolDef) and tool.text == "Add Cube":
+            break
+    tools.insert(index + 1, tools.pop(-1))
+
+def unregister():
+    bpy.utils.unregister_tool('VIEW_3D', 'EDIT_MESH', tool_make_line)
+
+    bpy.utils.unregister_class(ops_line.SnapUtilitiesLine)
+    bpy.utils.unregister_class(preferences.SnapUtilitiesLinePreferences)
+
+if __name__ == "__main__":
+    __name__ = "mesh_snap_utilities_line"
+    __package__ = "mesh_snap_utilities_line"
+    register()
diff --git a/mesh_snap_utilities_line/common_classes.py b/mesh_snap_utilities_line/common_classes.py
new file mode 100644
index 0000000000000000000000000000000000000000..09fc3a0283bc0ffd96cd1a2a393af9eb90163f25
--- /dev/null
+++ b/mesh_snap_utilities_line/common_classes.py
@@ -0,0 +1,334 @@
+### 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 3
+#  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, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import bgl
+import gpu
+import numpy as np
+
+
+class SnapDrawn():
+    def __init__(self, out_color, face_color,
+                 edge_color, vert_color, center_color,
+                 perpendicular_color, constrain_shift_color,
+                 axis_x_color, axis_y_color, axis_z_color):
+
+        self.out_color = out_color
+        self.face_color = face_color
+        self.edge_color = edge_color
+        self.vert_color = vert_color
+        self.center_color = center_color
+        self.perpendicular_color = perpendicular_color
+        self.constrain_shift_color = constrain_shift_color
+
+        self.axis_x_color = axis_x_color
+        self.axis_y_color = axis_y_color
+        self.axis_z_color = axis_z_color
+
+        self._format_pos = gpu.types.GPUVertFormat()
+        self._format_pos.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
+
+        self._format_pos_and_color = gpu.types.GPUVertFormat()
+        self._format_pos_and_color.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
+        self._format_pos_and_color.attr_add(id="color", comp_type='F32', len=4, fetch_mode='FLOAT')
+
+        self._program_unif_col = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
+        self._program_smooth_col = gpu.shader.from_builtin("3D_SMOOTH_COLOR")
+
+        self._batch_point = None
+        self._batch_circle = None
+        self._batch_vector = None
+
+
+    def batch_line_strip_create(self, coords):
+        vbo = gpu.types.GPUVertBuf(self._format_pos, len = len(coords))
+        vbo.attr_fill(0, data = coords)
+        batch_lines = gpu.types.GPUBatch(type = "LINE_STRIP", buf = vbo)
+        return batch_lines
+
+    def batch_lines_smooth_color_create(self, coords, colors):
+        vbo = gpu.types.GPUVertBuf(self._format_pos_and_color, len = len(coords))
+        vbo.attr_fill(0, data = coords)
+        vbo.attr_fill(1, data = colors)
+        batch_lines = gpu.types.GPUBatch(type = "LINES", buf = vbo)
+        return batch_lines
+
+    def batch_triangles_create(self, coords):
+        vbo = gpu.types.GPUVertBuf(self._format_pos, len = len(coords))
+        vbo.attr_fill(0, data = coords)
+        batch_tris = gpu.types.GPUBatch(type = "TRIS", buf = vbo)
+        return batch_tris
+
+    def batch_point_get(self):
+        if self._batch_point is None:
+            vbo = gpu.types.GPUVertBuf(self._format_pos, len = 1)
+            vbo.attr_fill(0, ((0.0, 0.0, 0.0),))
+            self._batch_point = gpu.types.GPUBatch(type = "POINTS", buf = vbo)
+        return self._batch_point
+
+    def draw(self, type, location, list_verts_co, vector_constrain, prevloc):
+        # draw 3d point OpenGL in the 3D View
+        bgl.glEnable(bgl.GL_BLEND)
+        gpu.matrix.push()
+        self._program_unif_col.bind()
+
+        if list_verts_co:
+            # draw 3d line OpenGL in the 3D View
+            bgl.glDepthRange(0, 0.9999)
+            bgl.glLineWidth(3.0)
+
+            batch = self.batch_line_strip_create([v.to_tuple() for v in list_verts_co] + [location.to_tuple()])
+
+            self._program_unif_col.uniform_float("color", (1.0, 0.8, 0.0, 0.5))
+            batch.draw(self._program_unif_col)
+            del batch
+
+        bgl.glDisable(bgl.GL_DEPTH_TEST)
+
+        point_batch = self.batch_point_get()
+        if vector_constrain:
+            if prevloc:
+                bgl.glPointSize(5)
+                gpu.matrix.translate(prevloc)
+                self._program_unif_col.uniform_float("color", (1.0, 1.0, 1.0, 0.5))
+                point_batch.draw(self._program_unif_col)
+                gpu.matrix.translate(-prevloc)
+
+            if vector_constrain[2] == 'X':
+                Color4f = self.axis_x_color
+            elif vector_constrain[2] == 'Y':
+                Color4f = self.axis_y_color
+            elif vector_constrain[2] == 'Z':
+                Color4f = self.axis_z_color
+            else:
+                Color4f = self.constrain_shift_color
+        else:
+            if type == 'OUT':
+                Color4f = self.out_color
+            elif type == 'FACE':
+                Color4f = self.face_color
+            elif type == 'EDGE':
+                Color4f = self.edge_color
+            elif type == 'VERT':
+                Color4f = self.vert_color
+            elif type == 'CENTER':
+                Color4f = self.center_color
+            elif type == 'PERPENDICULAR':
+                Color4f = self.perpendicular_color
+            else: # type == None
+                Color4f = self.out_color
+
+        bgl.glPointSize(10)
+
+        gpu.matrix.translate(location)
+        self._program_unif_col.uniform_float("color", Color4f)
+        point_batch.draw(self._program_unif_col)
+
+        # restore opengl defaults
+        bgl.glDepthRange(0.0, 1.0)
+        bgl.glPointSize(1.0)
+        bgl.glLineWidth(1.0)
+        bgl.glEnable(bgl.GL_DEPTH_TEST)
+        bgl.glDisable(bgl.GL_BLEND)
+
+        gpu.matrix.pop()
+
+    def draw_elem(self, snap_obj, bm, elem):
+        from bmesh.types import(
+            BMVert,
+            BMEdge,
+            BMFace,
+        )
+        # draw 3d point OpenGL in the 3D View
+        bgl.glEnable(bgl.GL_BLEND)
+        bgl.glDisable(bgl.GL_DEPTH_TEST)
+
+        gpu.matrix.push()
+        gpu.matrix.multiply_matrix(snap_obj.mat)
+
+        if isinstance(elem, BMVert):
+            color = self.vert_color
+            edges = np.empty((len(elem.link_edges), 2), [("pos", "f4", 3), ("color", "f4", 4)])
+            edges["pos"][:, 0] = elem.co
+            edges["pos"][:, 1] = [e.other_vert(elem).co for e in elem.link_edges]
+            edges["color"][:, 0] = color
+            edges["color"][:, 1] = (color[0], color[1], color[2], 0.0)
+            edges.shape = -1
+
+            self._program_smooth_col.bind()
+            bgl.glLineWidth(3.0)
+            batch = self.batch_lines_smooth_color_create(edges["pos"], edges["color"])
+            batch.draw(self._program_smooth_col)
+            bgl.glLineWidth(1.0)
+        else:
+            self._program_unif_col.bind()
+
+            if isinstance(elem, BMEdge):
+                self._program_unif_col.uniform_float("color", self.edge_color)
+
+                bgl.glLineWidth(3.0)
+                batch = self.batch_line_strip_create([v.co for v in elem.verts])
+                batch.draw(self._program_unif_col)
+                bgl.glLineWidth(1.0)
+
+            elif isinstance(elem, BMFace):
+                face_color = self.face_color[0], self.face_color[1], self.face_color[2], self.face_color[3] * 0.2
+                self._program_unif_col.uniform_float("color", face_color)
+
+                tris = snap_obj.data[1].get_loop_tri_co_by_bmface(bm, elem)
+                tris.shape = (-1, 3)
+                batch = self.batch_triangles_create(tris)
+                batch.draw(self._program_unif_col)
+
+        # restore opengl defaults
+        bgl.glEnable(bgl.GL_DEPTH_TEST)
+        bgl.glDisable(bgl.GL_BLEND)
+
+        gpu.matrix.pop()
+
+
+class SnapNavigation():
+    @staticmethod
+    def debug_key(key):
+        for member in dir(key):
+            print(member, getattr(key, member))
+
+    @staticmethod
+    def convert_to_flag(shift, ctrl, alt):
+        return (shift << 0) | (ctrl << 1) | (alt << 2)
+
+    def __init__(self, context, use_ndof):
+        # TO DO:
+        # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
+        self.use_ndof = use_ndof and context.user_preferences.inputs.use_ndof
+
+        self._rotate = set()
+        self._move = set()
+        self._zoom = set()
+
+        if self.use_ndof:
+            self._ndof_all = set()
+            self._ndof_orbit = set()
+            self._ndof_orbit_zoom = set()
+            self._ndof_pan = set()
+
+        for key in context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items:
+            if key.idname == 'view3d.rotate':
+                self._rotate.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value))
+            elif key.idname == 'view3d.move':
+                self._move.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value))
+            elif key.idname == 'view3d.zoom':
+                if key.type == 'WHEELINMOUSE':
+                    self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), 'WHEELUPMOUSE', key.value, key.properties.delta))
+                elif key.type == 'WHEELOUTMOUSE':
+                    self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), 'WHEELDOWNMOUSE', key.value, key.properties.delta))
+                else:
+                    self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value, key.properties.delta))
+
+            elif self.use_ndof:
+                if key.idname == 'view3d.ndof_all':
+                    self._ndof_all.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+                elif key.idname == 'view3d.ndof_orbit':
+                    self._ndof_orbit.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+                elif key.idname == 'view3d.ndof_orbit_zoom':
+                    self._ndof_orbit_zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+                elif key.idname == 'view3d.ndof_pan':
+                    self._ndof_pan.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+
+
+    def run(self, context, event, snap_location):
+        evkey = (self.convert_to_flag(event.shift, event.ctrl, event.alt), event.type, event.value)
+
+        if evkey in self._rotate:
+            bpy.ops.view3d.rotate('INVOKE_DEFAULT')
+            return True
+
+        if evkey in self._move:
+            #if event.shift and self.vector_constrain and \
+            #    self.vector_constrain[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}:
+            #    self.vector_constrain = None
+            bpy.ops.view3d.move('INVOKE_DEFAULT')
+            return True
+
+        for key in self._zoom:
+            if evkey == key[0:3]:
+                if snap_location:
+                    v3d = context.space_data
+                    dist_range = (v3d.clip_start, v3d.clip_end)
+                    rv3d = context.region_data
+                    if (key[3] < 0 and rv3d.view_distance < dist_range[1]) or\
+                       (key[3] > 0 and rv3d.view_distance > dist_range[0]):
+                            rv3d.view_location += key[3] * (snap_location - rv3d.view_location) / 6
+                            rv3d.view_distance -= key[3] * rv3d.view_distance / 6
+                    context.area.tag_redraw()
+                else:
+                    bpy.ops.view3d.zoom('INVOKE_DEFAULT', delta = key[3])
+                return True
+
+        if self.use_ndof:
+            ndofkey = evkey[:2]
+            if evkey in self._ndof_all:
+                bpy.ops.view3d.ndof_all('INVOKE_DEFAULT')
+                return True
+            if evkey in self._ndof_orbit:
+                bpy.ops.view3d.ndof_orbit('INVOKE_DEFAULT')
+                return True
+            if evkey in self._ndof_orbit_zoom:
+                bpy.ops.view3d.ndof_orbit_zoom('INVOKE_DEFAULT')
+                return True
+            if evkey in self._ndof_pan:
+                bpy.ops.view3d.ndof_pan('INVOKE_DEFAULT')
+                return True
+
+        return False
+
+
+class CharMap:
+    ascii = {
+        ".", ",", "-", "+", "1", "2", "3",
+        "4", "5", "6", "7", "8", "9", "0",
+        "c", "m", "d", "k", "h", "a",
+        " ", "/", "*", "'", "\""
+        # "="
+        }
+    type = {
+        'BACK_SPACE', 'DEL',
+        'LEFT_ARROW', 'RIGHT_ARROW'
+        }
+
+    @staticmethod
+    def modal(self, context, event):
+        c = event.ascii
+        if c:
+            if c == ",":
+                c = "."
+            self.length_entered = self.length_entered[:self.line_pos] + c + self.length_entered[self.line_pos:]
+            self.line_pos += 1
+        if self.length_entered:
+            if event.type == 'BACK_SPACE':
+                self.length_entered = self.length_entered[:self.line_pos - 1] + self.length_entered[self.line_pos:]
+                self.line_pos -= 1
+
+            elif event.type == 'DEL':
+                self.length_entered = self.length_entered[:self.line_pos] + self.length_entered[self.line_pos + 1:]
+
+            elif event.type == 'LEFT_ARROW':
+                self.line_pos = (self.line_pos - 1) % (len(self.length_entered) + 1)
+
+            elif event.type == 'RIGHT_ARROW':
+                self.line_pos = (self.line_pos + 1) % (len(self.length_entered) + 1)
+
diff --git a/mesh_snap_utilities_line/common_utilities.py b/mesh_snap_utilities_line/common_utilities.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fc0ce6d590ca3c04e55d40f871ad5e3f085a15f
--- /dev/null
+++ b/mesh_snap_utilities_line/common_utilities.py
@@ -0,0 +1,269 @@
+### 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 3
+#  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, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+#python tip: from-imports don't save memory.
+#They execute and cache the entire module just like a regular import.
+
+import bpy
+import bmesh
+from .snap_context_l import SnapContext
+from mathutils import Vector
+from mathutils.geometry import (
+        intersect_point_line,
+        intersect_line_line,
+        intersect_line_plane,
+        intersect_ray_tri,
+        )
+
+
+def get_units_info(scale, unit_system, separate_units):
+    if unit_system == 'METRIC':
+        scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
+            (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
+    elif unit_system == 'IMPERIAL':
+        scale_steps = ((5280, 'mi'), (1, '\''),
+            (1 / 12, '"'), (1 / 12000, 'thou'))
+        scale /= 0.3048  # BU to feet
+    else:
+        scale_steps = ((1, ' BU'),)
+        separate_units = False
+
+    return (scale, scale_steps, separate_units)
+
+
+def convert_distance(val, units_info, precision=5):
+    scale, scale_steps, separate_units = units_info
+    sval = val * scale
+    idx = 0
+    while idx < len(scale_steps) - 1:
+        if sval >= scale_steps[idx][0]:
+            break
+        idx += 1
+    factor, suffix = scale_steps[idx]
+    sval /= factor
+    if not separate_units or idx == len(scale_steps) - 1:
+        dval = str(round(sval, precision)) + suffix
+    else:
+        ival = int(sval)
+        dval = str(round(ival, precision)) + suffix
+        fval = sval - ival
+        idx += 1
+        while idx < len(scale_steps):
+            fval *= scale_steps[idx - 1][0] / scale_steps[idx][0]
+            if fval >= 1:
+                dval += ' ' \
+                    + ("%.1f" % fval) \
+                    + scale_steps[idx][1]
+                break
+            idx += 1
+
+    return dval
+
+
+def location_3d_to_region_2d(region, rv3d, coord):
+    prj = rv3d.perspective_matrix @ Vector((coord[0], coord[1], coord[2], 1.0))
+    width_half = region.width / 2.0
+    height_half = region.height / 2.0
+    return Vector((width_half + width_half * (prj.x / prj.w),
+                   height_half + height_half * (prj.y / prj.w),
+                   prj.z / prj.w
+                   ))
+
+
+def out_Location(rv3d, orig, vector):
+    view_matrix = rv3d.view_matrix
+    v1 = (int(view_matrix[0][0]*1.5), int(view_matrix[0][1]*1.5), int(view_matrix[0][2]*1.5))
+    v2 = (int(view_matrix[1][0]*1.5), int(view_matrix[1][1]*1.5), int(view_matrix[1][2]*1.5))
+
+    hit = intersect_ray_tri((1,0,0), (0,1,0), (0,0,0), (vector), (orig), False)
+    if hit is None:
+        hit = intersect_ray_tri(v1, v2, (0,0,0), (vector), (orig), False)
+    if hit is None:
+        hit = intersect_ray_tri(v1, v2, (0,0,0), (-vector), (orig), False)
+    if hit is None:
+        hit = Vector()
+    return hit
+
+
+def get_snap_bm_geom(sctx, main_snap_obj, mcursor):
+
+    r_snp_obj, r_loc, r_elem, r_elem_co = sctx.snap_get(mcursor, main_snap_obj)
+    r_view_vector, r_orig = sctx.last_ray
+    r_bm = None
+    r_bm_geom = None
+
+    if r_snp_obj is not None:
+        obj = r_snp_obj.data[0]
+
+        if obj.type == 'MESH' and obj.data.is_editmode:
+            r_bm = bmesh.from_edit_mesh(obj.data)
+            if len(r_elem) == 1:
+                r_bm_geom = r_bm.verts[r_elem[0]]
+
+            elif len(r_elem) == 2:
+                try:
+                    v1 = r_bm.verts[r_elem[0]]
+                    v2 = r_bm.verts[r_elem[1]]
+                    r_bm_geom = r_bm.edges.get([v1, v2])
+                except IndexError:
+                    r_bm.verts.ensure_lookup_table()
+
+            elif len(r_elem) == 3:
+                tri = [
+                    r_bm.verts[r_elem[0]],
+                    r_bm.verts[r_elem[1]],
+                    r_bm.verts[r_elem[2]],
+                ]
+
+                faces = set(tri[0].link_faces).intersection(tri[1].link_faces, tri[2].link_faces)
+                if len(faces) == 1:
+                    r_bm_geom = faces.pop()
+                else:
+                    i = -2
+                    edge = None
+                    while not edge and i != 1:
+                        edge = r_bm.edges.get([tri[i], tri[i + 1]])
+                        i += 1
+                    if edge:
+                        for l in edge.link_loops:
+                            if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]:
+                                r_bm_geom = l.face
+                                break
+
+    return r_snp_obj, r_loc, r_elem, r_elem_co, r_view_vector, r_orig, r_bm, r_bm_geom
+
+
+class SnapCache:
+    snp_obj = None
+    elem = None
+
+    v0 = None
+    v1 = None
+    vmid = None
+    vperp = None
+
+    v2d0 = None
+    v2d1 = None
+    v2dmid = None
+    v2dperp = None
+
+    is_increment = False
+
+
+def snap_utilities(
+        sctx, main_snap_obj,
+        mcursor,
+        constrain = None,
+        previous_vert = None,
+        increment = 0.0):
+
+    snp_obj, loc, elem, elem_co, view_vector, orig, bm, bm_geom = get_snap_bm_geom(sctx, main_snap_obj, mcursor)
+
+    is_increment = False
+    r_loc = None
+    r_type = None
+    r_len = 0.0
+
+    if not snp_obj:
+        is_increment = True
+        if constrain:
+            end = orig + view_vector
+            t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
+            if t_loc is None:
+                t_loc = constrain
+            r_loc = t_loc[0]
+        else:
+            r_type = 'OUT'
+            r_loc = out_Location(sctx.rv3d, orig, view_vector)
+
+    elif len(elem) == 1:
+        r_type = 'VERT'
+        if constrain:
+            r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
+        else:
+            r_loc = loc
+
+    elif len(elem) == 2:
+        if SnapCache.snp_obj is not snp_obj or not (elem == SnapCache.elem).all():
+            SnapCache.snp_obj = snp_obj
+            SnapCache.elem = elem
+
+            SnapCache.v0 = elem_co[0]
+            SnapCache.v1 = elem_co[1]
+            SnapCache.vmid = 0.5 * (SnapCache.v0 + SnapCache.v1)
+            SnapCache.v2d0 = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.v0)
+            SnapCache.v2d1 = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.v1)
+            SnapCache.v2dmid = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.vmid)
+
+            if previous_vert and (not bm_geom or previous_vert not in bm_geom.verts):
+                pvert_co = main_snap_obj.mat @ previous_vert.co
+                perp_point = intersect_point_line(pvert_co, SnapCache.v0, SnapCache.v1)
+                SnapCache.vperp = perp_point[0]
+                #factor = point_perpendicular[1]
+                SnapCache.v2dperp = location_3d_to_region_2d(sctx.region, sctx.rv3d, perp_point[0])
+                SnapCache.is_increment = False
+            else:
+                SnapCache.is_increment = True
+
+            #else: SnapCache.v2dperp = None
+
+        if constrain:
+            t_loc = intersect_line_line(constrain[0], constrain[1], SnapCache.v0, SnapCache.v1)
+
+            if t_loc is None:
+                is_increment = True
+                end = orig + view_vector
+                t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
+            r_loc = t_loc[0]
+
+        elif SnapCache.v2dperp and\
+            abs(SnapCache.v2dperp[0] - mcursor[0]) < 10 and abs(SnapCache.v2dperp[1] - mcursor[1]) < 10:
+                r_type = 'PERPENDICULAR'
+                r_loc = SnapCache.vperp
+
+        elif abs(SnapCache.v2dmid[0] - mcursor[0]) < 10 and abs(SnapCache.v2dmid[1] - mcursor[1]) < 10:
+            r_type = 'CENTER'
+            r_loc = SnapCache.vmid
+
+        else:
+            is_increment = SnapCache.is_increment
+
+            r_type = 'EDGE'
+            r_loc = loc
+
+    elif len(elem) == 3:
+        r_type = 'FACE'
+
+        if constrain:
+            is_increment = False
+            r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
+        else:
+            is_increment = True
+            r_loc = loc
+
+    if previous_vert:
+        pv_co = main_snap_obj.mat @ previous_vert.co
+        vec = r_loc - pv_co
+        if is_increment and increment:
+            r_len = round((1 / increment) * vec.length) * increment
+            r_loc = r_len * vec.normalized() + pv_co
+        else:
+            r_len = vec.length
+
+    return snp_obj, loc, r_loc, r_type, bm, bm_geom, r_len
+
+snap_utilities.cache = SnapCache
diff --git a/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat b/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat
new file mode 100644
index 0000000000000000000000000000000000000000..fa738db992d5896349a19ece83e62b15e32eac5f
Binary files /dev/null and b/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat differ
diff --git a/mesh_snap_utilities_line/ops_line.py b/mesh_snap_utilities_line/ops_line.py
new file mode 100644
index 0000000000000000000000000000000000000000..427b0b303fd1b8af014f4b84f1cce2881458cbb9
--- /dev/null
+++ b/mesh_snap_utilities_line/ops_line.py
@@ -0,0 +1,551 @@
+### 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 3
+#  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, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy, bmesh
+
+from bpy.props import FloatProperty
+
+from mathutils import Vector
+
+from mathutils.geometry import intersect_point_line
+
+from .common_classes import (
+    SnapDrawn,
+    CharMap,
+    SnapNavigation,
+    )
+
+from .common_utilities import (
+    get_units_info,
+    convert_distance,
+    snap_utilities,
+    )
+
+if not __package__:
+    __package__ = "mesh_snap_utilities"
+
+
+def Bpy_Area_header_text_clear(area):
+    #HACK
+    import ctypes
+    func = area.header_text_set
+    c_func = ctypes.c_void_p.from_address(id(func) + 56).value
+    c_param = ctypes.c_void_p.from_address(c_func + 24).value
+    flag_parameter = ctypes.c_void_p.from_address(c_param + 40)
+    previous_value = flag_parameter.value
+    flag_parameter.value = 0
+    func()
+    flag_parameter.value = previous_value
+
+
+def get_closest_edge(bm, point, dist):
+    r_edge = None
+    for edge in bm.edges:
+        v1 = edge.verts[0].co
+        v2 = edge.verts[1].co
+        # Test the BVH (AABB) first
+        for i in range(3):
+            if v1[i] <= v2[i]:
+                isect = v1[i] - dist <= point[i] <= v2[i] + dist
+            else:
+                isect = v2[i] - dist <= point[i] <= v1[i] + dist
+
+            if not isect:
+                break
+        else:
+            ret = intersect_point_line(point, v1, v2)
+
+            if ret[1] < 0.0:
+                tmp = v1
+            elif ret[1] > 1.0:
+                tmp = v2
+            else:
+                tmp = ret[0]
+
+            new_dist = (point - tmp).length
+            if new_dist <= dist:
+                dist = new_dist
+                r_edge = edge
+
+    return r_edge
+
+
+def get_loose_linked_edges(bmvert):
+    linked = [e for e in bmvert.link_edges if not e.link_faces]
+    for e in linked:
+        linked += [le for v in e.verts if not v.link_faces for le in v.link_edges if le not in linked]
+    return linked
+
+
+def draw_line(self, bm_geom, location):
+    obj = self.main_snap_obj.data[0]
+    bm = self.main_bm
+    split_faces = set()
+
+    drawing_is_dirt = False
+    update_edit_mesh = False
+
+    if bm_geom is None:
+        vert = bm.verts.new(location)
+        self.list_verts.append(vert)
+
+    elif isinstance(bm_geom, bmesh.types.BMVert):
+        if (bm_geom.co - location).length_squared < .001:
+            if self.list_verts == [] or self.list_verts[-1] != bm_geom:
+                self.list_verts.append(bm_geom)
+        else:
+            vert = bm.verts.new(location)
+            self.list_verts.append(vert)
+            drawing_is_dirt = True
+
+    elif isinstance(bm_geom, bmesh.types.BMEdge):
+        self.list_edges.append(bm_geom)
+        ret = intersect_point_line(location, bm_geom.verts[0].co, bm_geom.verts[1].co)
+
+        if (ret[0] - location).length_squared < .001:
+            if ret[1] == 0.0:
+                vert = bm_geom.verts[0]
+            elif ret[1] == 1.0:
+                vert = bm_geom.verts[1]
+            else:
+                edge, vert = bmesh.utils.edge_split(bm_geom, bm_geom.verts[0], ret[1])
+                drawing_is_dirt = True
+            self.list_verts.append(vert)
+            self.geom = vert # hack to highlight in the drawing
+            # self.list_edges.append(edge)
+
+        else:  # constrain point is near
+            vert = bm.verts.new(location)
+            self.list_verts.append(vert)
+            drawing_is_dirt = True
+
+    elif isinstance(bm_geom, bmesh.types.BMFace):
+        split_faces.add(bm_geom)
+        vert = bm.verts.new(location)
+        self.list_verts.append(vert)
+        drawing_is_dirt = True
+
+    # draw, split and create face
+    if len(self.list_verts) >= 2:
+        v1, v2 = self.list_verts[-2:]
+        # v2_link_verts = [x for y in [a.verts for a in v2.link_edges] for x in y if x != v2]
+        edge = bm.edges.get([v1, v2])
+        if edge:
+                self.list_edges.append(edge)
+
+        else:  # if v1 not in v2_link_verts:
+            if not v2.link_edges:
+                edge = bm.edges.new([v1, v2])
+                self.list_edges.append(edge)
+                drawing_is_dirt = True
+            else:  # split face
+                v1_link_faces = v1.link_faces
+                v2_link_faces = v2.link_faces
+                if v1_link_faces and v2_link_faces:
+                    split_faces.update(set(v1_link_faces).intersection(v2_link_faces))
+
+                else:
+                    if v1_link_faces:
+                        faces = v1_link_faces
+                        co2 = v2.co.copy()
+                    else:
+                        faces = v2_link_faces
+                        co2 = v1.co.copy()
+
+                    for face in faces:
+                        if bmesh.geometry.intersect_face_point(face, co2):
+                            co = co2 - face.calc_center_median()
+                            if co.dot(face.normal) < 0.001:
+                                split_faces.add(face)
+
+                if split_faces:
+                    edge = bm.edges.new([v1, v2])
+                    self.list_edges.append(edge)
+                    ed_list = get_loose_linked_edges(v2)
+                    for face in split_faces:
+                        facesp = bmesh.utils.face_split_edgenet(face, ed_list)
+                    del split_faces
+                    update_edit_mesh = True
+                else:
+                    if self.intersect:
+                        facesp = bmesh.ops.connect_vert_pair(bm, verts=[v1, v2], verts_exclude=bm.verts)
+                        # print(facesp)
+                    if not self.intersect or not facesp['edges']:
+                        edge = bm.edges.new([v1, v2])
+                        self.list_edges.append(edge)
+                        drawing_is_dirt = True
+                    else:
+                        for edge in facesp['edges']:
+                            self.list_edges.append(edge)
+                            update_edit_mesh = True
+
+        # create face
+        if self.create_face:
+            ed_list = set(self.list_edges)
+            for edge in v2.link_edges:
+                for vert in edge.verts:
+                    if vert != v2 and vert in self.list_verts:
+                        ed_list.add(edge)
+                        break
+                else:
+                    continue
+                # Inner loop had a break, break the outer
+                break
+
+            ed_list.update(get_loose_linked_edges(v2))
+
+            bmesh.ops.edgenet_fill(bm, edges=list(ed_list))
+            update_edit_mesh = True
+            # print('face created')
+    if update_edit_mesh or drawing_is_dirt:
+        obj.data.update_gpu_tag()
+        obj.data.update_tag()
+        obj.update_from_editmode()
+        obj.update_tag()
+        bmesh.update_edit_mesh(obj.data)
+        self.sctx.tag_update_drawn_snap_object(self.main_snap_obj)
+        #bm.verts.index_update()
+
+    return [obj.matrix_world @ v.co for v in self.list_verts]
+
+
+class SnapUtilitiesLine(bpy.types.Operator):
+    """Make Lines. Connect them to split faces"""
+    bl_idname = "mesh.make_line"
+    bl_label = "Line Tool"
+    bl_options = {'REGISTER'}
+
+    constrain_keys = {
+        'X': Vector((1,0,0)),
+        'Y': Vector((0,1,0)),
+        'Z': Vector((0,0,1)),
+        'RIGHT_SHIFT': 'shift',
+        'LEFT_SHIFT': 'shift',
+    }
+
+    def modal(self, context, event):
+        if self.navigation_ops.run(context, event, self.prevloc if self.vector_constrain else self.location):
+            return {'RUNNING_MODAL'}
+
+        context.area.tag_redraw()
+
+        if event.ctrl and event.type == 'Z' and event.value == 'PRESS':
+            del self.bm
+            del self.main_bm
+            bpy.ops.ed.undo()
+            self.vector_constrain = None
+            self.list_verts_co = []
+            self.list_verts = []
+            self.list_edges = []
+            bpy.ops.object.mode_set(mode='EDIT') # just to be sure
+            self.main_bm = bmesh.from_edit_mesh(self.main_snap_obj.data[0])
+            self.sctx.tag_update_drawn_snap_object(self.main_snap_obj)
+            return {'RUNNING_MODAL'}
+
+        is_making_lines = bool(self.list_verts_co)
+
+        if event.type == 'MOUSEMOVE' or self.bool_update:
+            if self.rv3d.view_matrix != self.rotMat:
+                self.rotMat = self.rv3d.view_matrix.copy()
+                self.bool_update = True
+                snap_utilities.cache.snp_obj = None # hack for snap edge elemens update
+            else:
+                self.bool_update = False
+
+            mval = Vector((event.mouse_region_x, event.mouse_region_y))
+
+            self.snap_obj, self.prevloc, self.location, self.type, self.bm, self.geom, self.len = snap_utilities(
+                    self.sctx,
+                    self.main_snap_obj,
+                    mval,
+                    constrain=self.vector_constrain,
+                    previous_vert=(self.list_verts[-1] if self.list_verts else None),
+                    increment=self.incremental
+            )
+
+            if self.snap_to_grid and self.type == 'OUT':
+                loc = self.location / self.rd
+                self.location = Vector((round(loc.x),
+                                        round(loc.y),
+                                        round(loc.z))) * self.rd
+
+            if self.keyf8 and is_making_lines:
+                lloc = self.list_verts_co[-1]
+                view_vec, orig = self.sctx.last_ray
+                location = intersect_point_line(lloc, orig, (orig + view_vec))
+                vec = (location[0] - lloc)
+                ax, ay, az = abs(vec.x), abs(vec.y), abs(vec.z)
+                vec.x = ax > ay > az or ax > az > ay
+                vec.y = ay > ax > az or ay > az > ax
+                vec.z = az > ay > ax or az > ax > ay
+                if vec == Vector():
+                    self.vector_constrain = None
+                else:
+                    vc = lloc + vec
+                    try:
+                        if vc != self.vector_constrain[1]:
+                            type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift'
+                            self.vector_constrain = [lloc, vc, type]
+                    except:
+                        type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift'
+                        self.vector_constrain = [lloc, vc, type]
+
+        if event.value == 'PRESS':
+            if is_making_lines and (event.ascii in CharMap.ascii or event.type in CharMap.type):
+                CharMap.modal(self, context, event)
+
+            elif event.type in self.constrain_keys:
+                self.bool_update = True
+                self.keyf8 = False
+
+                if self.vector_constrain and self.vector_constrain[2] == event.type:
+                    self.vector_constrain = ()
+
+                else:
+                    if event.shift:
+                        if isinstance(self.geom, bmesh.types.BMEdge):
+                            if is_making_lines:
+                                loc = self.list_verts_co[-1]
+                                self.vector_constrain = (loc, loc + self.geom.verts[1].co -
+                                                         self.geom.verts[0].co, event.type)
+                            else:
+                                self.vector_constrain = [self.main_snap_obj.mat @ v.co for
+                                                         v in self.geom.verts] + [event.type]
+                    else:
+                        if is_making_lines:
+                            loc = self.list_verts_co[-1]
+                        else:
+                            loc = self.location
+                        self.vector_constrain = [loc, loc + self.constrain_keys[event.type], event.type]
+
+            elif event.type == 'LEFTMOUSE':
+                if not is_making_lines and self.bm:
+                    self.main_snap_obj = self.snap_obj
+                    self.main_bm = self.bm
+
+                mat_inv = self.main_snap_obj.mat.inverted_safe()
+                point = mat_inv @ self.location
+                # with constraint the intersection can be in a different element of the selected one
+                geom2 = self.geom
+                if geom2:
+                    geom2.select = False
+
+                if self.vector_constrain:
+                    geom2 = get_closest_edge(self.main_bm, point, .001)
+
+                self.vector_constrain = None
+                self.list_verts_co = draw_line(self, geom2, point)
+                bpy.ops.ed.undo_push(message="Undo draw line*")
+
+            elif event.type == 'TAB':
+                self.keytab = self.keytab is False
+                if self.keytab:
+                    self.sctx.set_snap_mode(False, False, True)
+                    context.tool_settings.mesh_select_mode = (False, False, True)
+                else:
+                    self.sctx.set_snap_mode(True, True, True)
+                    context.tool_settings.mesh_select_mode = (True, True, self.snap_face)
+
+            elif event.type == 'F8':
+                self.vector_constrain = None
+                self.keyf8 = self.keyf8 is False
+
+        elif event.value == 'RELEASE':
+            if event.type in {'RET', 'NUMPAD_ENTER'}:
+                if self.length_entered != "" and self.list_verts_co:
+                    try:
+                        text_value = bpy.utils.units.to_value(self.unit_system, 'LENGTH', self.length_entered)
+                        vector = (self.location - self.list_verts_co[-1]).normalized()
+                        location = (self.list_verts_co[-1] + (vector * text_value))
+
+                        mat_inv = self.main_snap_obj.mat.inverted_safe()
+                        self.list_verts_co = draw_line(self, self.geom, mat_inv @ location)
+                        self.length_entered = ""
+                        self.vector_constrain = None
+
+                    except:  # ValueError:
+                        self.report({'INFO'}, "Operation not supported yet")
+
+            elif event.type in {'RIGHTMOUSE', 'ESC'}:
+                if not is_making_lines or event.type == 'ESC':
+                    del self.main_bm #avoids unpredictable crashs
+                    del self.bm
+                    del self.list_edges
+                    del self.list_verts
+                    del self.list_verts_co
+
+                    bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+                    Bpy_Area_header_text_clear(context.area)
+                    #context.area.header_text_set(text = "")
+                    self.sctx.free()
+                    del self.draw_cache
+
+                    #Restore initial state
+                    context.tool_settings.mesh_select_mode = self.select_mode
+                    context.user_preferences.view.use_rotate_around_active = self.use_rotate_around_active
+                    context.space_data.overlay.show_face_center = self.show_face_center
+                    if not self.is_editmode:
+                        bpy.ops.object.editmode_toggle()
+
+                    return {'FINISHED'}
+                else:
+                    snap_utilities.cache.snp_obj = None # hack for snap edge elemens update
+                    self.vector_constrain = None
+                    self.list_edges = []
+                    self.list_verts = []
+                    self.list_verts_co = []
+
+        a = ""
+        if is_making_lines:
+            if self.length_entered:
+                pos = self.line_pos
+                a = 'length: ' + self.length_entered[:pos] + '|' + self.length_entered[pos:]
+            else:
+                length = self.len
+                length = convert_distance(length, self.uinfo)
+                a = 'length: ' + length
+
+        context.area.header_text_set(text = "hit: %.3f %.3f %.3f %s" % (*self.location, a))
+
+        if True or is_making_lines:
+            return {'RUNNING_MODAL'}
+
+        return {'PASS_THROUGH'}
+
+    def draw_callback_px(self):
+        if self.bm:
+            self.draw_cache.draw_elem(self.snap_obj, self.bm, self.geom)
+        self.draw_cache.draw(self.type, self.location, self.list_verts_co, self.vector_constrain, self.prevloc)
+
+    def invoke(self, context, event):
+        if context.space_data.type == 'VIEW_3D':
+            # print('name', __name__, __package__)
+            preferences = context.user_preferences.addons[__package__].preferences
+
+            #Store the preferences that will be used in modal
+            self.intersect = preferences.intersect
+            self.create_face = preferences.create_face
+            self.outer_verts = preferences.outer_verts
+            self.snap_to_grid = preferences.increments_grid
+
+            self.draw_cache = SnapDrawn(
+                preferences.out_color,
+                preferences.face_color,
+                preferences.edge_color,
+                preferences.vert_color,
+                preferences.center_color,
+                preferences.perpendicular_color,
+                preferences.constrain_shift_color,
+                tuple(context.user_preferences.themes[0].user_interface.axis_x) + (1.0,),
+                tuple(context.user_preferences.themes[0].user_interface.axis_y) + (1.0,),
+                tuple(context.user_preferences.themes[0].user_interface.axis_z) + (1.0,)
+            )
+
+            obj = context.active_object
+
+            #Create a new object
+            if obj is None or obj.type != 'MESH':
+                mesh = bpy.data.meshes.new("")
+                obj = bpy.data.objects.new("", mesh)
+                context.scene.objects.link(obj)
+                context.scene.objects.active = obj
+            else:
+                mesh = obj.data
+
+            #Store current state
+            self.is_editmode = mesh.is_editmode
+            self.use_rotate_around_active = context.user_preferences.view.use_rotate_around_active
+            self.select_mode = context.tool_settings.mesh_select_mode[:]
+            self.show_face_center = context.space_data.overlay.show_face_center
+
+            #Modify the current state
+            bpy.ops.object.mode_set(mode='EDIT')
+            bpy.ops.mesh.select_all(action='DESELECT')
+            context.user_preferences.view.use_rotate_around_active = True
+            context.tool_settings.mesh_select_mode = (True, True, True)
+            context.space_data.overlay.show_face_center = True
+            context.scene.update_tag()
+
+            #Configure the unit of measure
+            scale = context.scene.unit_settings.scale_length
+            self.unit_system = context.scene.unit_settings.system
+            separate_units = context.scene.unit_settings.use_separate
+            self.uinfo = get_units_info(scale, self.unit_system, separate_units)
+
+            scale /= context.space_data.overlay.grid_scale * preferences.relative_scale
+            self.rd = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(1 / scale))
+
+            self.incremental = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(preferences.incremental))
+
+            #Store values from 3d view context
+            self.rv3d = context.region_data
+            self.rotMat = self.rv3d.view_matrix.copy()
+            # self.obj_glmatrix = bgl.Buffer(bgl.GL_FLOAT, [4, 4], self.obj_matrix.transposed())
+            self.main_bm = self.bm = bmesh.from_edit_mesh(mesh) #remove at end
+
+            #init these variables to avoid errors
+            self.prevloc = Vector()
+            self.location = Vector()
+            self.list_verts = []
+            self.list_edges = []
+            self.list_verts_co = []
+            self.bool_update = False
+            self.vector_constrain = ()
+            self.navigation_ops = SnapNavigation(context, True)
+            self.type = 'OUT'
+            self.len = 0
+            self.length_entered = ""
+            self.line_pos = 0
+            self.geom = None
+
+            #Init event variables
+            self.keytab = False
+            self.keyf8 = False
+
+            #Init Snap Context
+            from .snap_context_l import SnapContext
+
+            self.sctx = SnapContext(context.region, context.space_data)
+            self.sctx.set_pixel_dist(12)
+            self.sctx.use_clip_planes(True)
+
+            act_base = context.active_base
+
+            if self.outer_verts:
+                for base in context.visible_bases:
+                    if base != act_base:
+                        self.sctx.add_obj(base.object, base.object.matrix_world)
+
+            self.main_snap_obj = self.snap_obj = self.sctx.add_obj(act_base.object, act_base.object.matrix_world)
+
+            self.snap_face = True
+            self.sctx.set_snap_mode(True, True, self.snap_face)
+
+            #modals
+            self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, (), 'WINDOW', 'POST_VIEW')
+            context.window_manager.modal_handler_add(self)
+
+            return {'RUNNING_MODAL'}
+        else:
+            self.report({'WARNING'}, "Active space must be a View3d")
+            return {'CANCELLED'}
+
+def register():
+    bpy.utils.register_class(SnapUtilitiesLine)
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_snap_utilities_line/preferences.py b/mesh_snap_utilities_line/preferences.py
new file mode 100644
index 0000000000000000000000000000000000000000..37b6baf600aeeb16b51a2db1cb9103136f58b98d
--- /dev/null
+++ b/mesh_snap_utilities_line/preferences.py
@@ -0,0 +1,125 @@
+### 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 3
+#  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, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+from bpy.props import (
+    EnumProperty,
+    StringProperty,
+    BoolProperty,
+    IntProperty,
+    FloatVectorProperty,
+    FloatProperty,
+    )
+
+
+def update_panel(self, context):
+    try:
+        panel = bpy.types.VIEW3D_PT_snap_utilities
+        panel.bl_category = context.user_preferences.addons[__package__].preferences.category
+        bpy.utils.unregister_class(panel)
+        bpy.utils.register_class(panel)
+    except:
+        print('not update')
+        pass
+
+
+class SnapUtilitiesLinePreferences(bpy.types.AddonPreferences):
+    # this must match the addon name, use '__package__'
+    # when defining this in a submodule of a python package.
+    bl_idname = __package__
+
+    intersect: BoolProperty(
+            name="Intersect",
+            description="intersects created line with the existing edges, even if the lines do not intersect",
+            default=True)
+
+    create_face: BoolProperty(
+            name="Create faces",
+            description="Create faces defined by enclosed edges",
+            default=False)
+
+    outer_verts: BoolProperty(
+            name="Snap to outer vertices",
+            description="The vertices of the objects are not activated also snapped",
+            default=True)
+
+    increments_grid: BoolProperty(
+            name="Increments of Grid",
+            description="Snap to increments of grid",
+            default=False)
+
+    incremental: FloatProperty(
+            name="Incremental",
+            description="Snap in defined increments",
+            default=0,
+            min=0,
+            step=1,
+            precision=3)
+
+    relative_scale: FloatProperty(
+            name="Relative Scale",
+            description="Value that divides the global scale",
+            default=1,
+            min=0,
+            step=1,
+            precision=3)
+
+    out_color: FloatVectorProperty(name="OUT", default=(0.0, 0.0, 0.0, 0.5), size=4, subtype="COLOR", min=0, max=1)
+    face_color: FloatVectorProperty(name="FACE", default=(1.0, 0.8, 0.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+    edge_color: FloatVectorProperty(name="EDGE", default=(0.0, 0.8, 1.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+    vert_color: FloatVectorProperty(name="VERT", default=(1.0, 0.5, 0.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+    center_color: FloatVectorProperty(name="CENTER", default=(1.0, 0.0, 1.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+    perpendicular_color: FloatVectorProperty(name="PERPENDICULAR", default=(0.1, 0.5, 0.5, 1.0), size=4, subtype="COLOR", min=0, max=1)
+    constrain_shift_color: FloatVectorProperty(name="SHIFT CONSTRAIN", default=(0.8, 0.5, 0.4, 1.0), size=4, subtype="COLOR", min=0, max=1)
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.label(text="Snap Colors:")
+        split = layout.split()
+
+        col = split.column()
+        col.prop(self, "out_color")
+        col.prop(self, "constrain_shift_color")
+        col = split.column()
+        col.prop(self, "face_color")
+        col = split.column()
+        col.prop(self, "edge_color")
+        col = split.column()
+        col.prop(self, "vert_color")
+        col = split.column()
+        col.prop(self, "center_color")
+        col = split.column()
+        col.prop(self, "perpendicular_color")
+
+        row = layout.row()
+
+        col = row.column()
+        #col.label(text="Snap Items:")
+        col.prop(self, "incremental")
+        col.prop(self, "increments_grid")
+        if self.increments_grid:
+            col.prop(self, "relative_scale")
+
+        col.prop(self, "outer_verts")
+        row.separator()
+
+        col = row.column()
+        col.label(text="Line Tool:")
+        col.prop(self, "intersect")
+        col.prop(self, "create_face")
+        col.prop(self, "create_new_obj")
diff --git a/mesh_snap_utilities_line/snap_context_l/__init__.py b/mesh_snap_utilities_line/snap_context_l/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..584f7ecb4387c05882a64d7b7adafce6969cac82
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/__init__.py
@@ -0,0 +1,439 @@
+# ##### 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 3
+#  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, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__all__ = (
+    "SnapContext",
+    )
+
+import bgl
+from mathutils import Vector
+
+VERT = 1
+EDGE = 2
+FACE = 4
+
+
+class _Internal:
+    from .mesh_drawing import (
+        gpu_Indices_enable_state,
+        gpu_Indices_restore_state,
+        gpu_Indices_use_clip_planes,
+        gpu_Indices_set_ProjectionMatrix,
+        )
+
+    from .utils_projection import (
+        region_2d_to_orig_and_view_vector,
+        intersect_boundbox_threshold,
+        intersect_ray_segment_fac,
+        project_co_v3,
+        )
+
+    from mathutils.geometry import intersect_line_plane
+
+
+class _SnapObjectData():
+    __slots__ = ('data', 'mat')
+    def __init__(self, data, omat):
+        self.data = data
+        self.mat = omat
+
+
+class _SnapOffscreen():
+    bound = None
+    def __init__(self, width, height):
+        import ctypes
+
+        self.freed = False
+        self.is_bound = False
+
+        self.width = width
+        self.height = height
+
+        self.fbo = bgl.Buffer(bgl.GL_INT, 1)
+        self.buf_color = bgl.Buffer(bgl.GL_INT, 1)
+        self.buf_depth = bgl.Buffer(bgl.GL_INT, 1)
+
+        self.cur_fbo = bgl.Buffer(bgl.GL_INT, 1)
+        self.cur_viewport = bgl.Buffer(bgl.GL_INT, 4)
+
+        bgl.glGenRenderbuffers(1, self.buf_depth)
+        bgl.glBindRenderbuffer(bgl.GL_RENDERBUFFER, self.buf_depth[0])
+        bgl.glRenderbufferStorage(bgl.GL_RENDERBUFFER, bgl.GL_DEPTH_COMPONENT, width, height)
+
+        bgl.glGenTextures(1, self.buf_color)
+        bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.buf_color[0])
+        NULL = bgl.Buffer(bgl.GL_INT, 1, (ctypes.c_int32 * 1).from_address(0))
+        bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_R32UI, width, height, 0, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, NULL)
+        del NULL
+        bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST)
+        bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST)
+
+        bgl.glGetIntegerv(bgl.GL_FRAMEBUFFER_BINDING, self.cur_fbo)
+
+        bgl.glGenFramebuffers(1, self.fbo)
+        bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.fbo[0])
+        bgl.glFramebufferRenderbuffer(bgl.GL_FRAMEBUFFER, bgl.GL_DEPTH_ATTACHMENT, bgl.GL_RENDERBUFFER, self.buf_depth[0])
+        bgl.glFramebufferTexture(bgl.GL_FRAMEBUFFER, bgl.GL_COLOR_ATTACHMENT0, self.buf_color[0], 0)
+
+        bgl.glDrawBuffers(1, bgl.Buffer(bgl.GL_INT, 1, [bgl.GL_COLOR_ATTACHMENT0]))
+
+        status = bgl.glCheckFramebufferStatus(bgl.GL_FRAMEBUFFER)
+        if status != bgl.GL_FRAMEBUFFER_COMPLETE:
+            print("Framebuffer Invalid", status)
+
+        bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.cur_fbo[0])
+
+    def bind(self):
+        if self is not _SnapOffscreen.bound:
+            if _SnapOffscreen.bound is None:
+                bgl.glGetIntegerv(bgl.GL_FRAMEBUFFER_BINDING, self.cur_fbo)
+                bgl.glGetIntegerv(bgl.GL_VIEWPORT, self.cur_viewport)
+
+            bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.fbo[0])
+            bgl.glViewport(0, 0, self.width, self.height)
+            _SnapOffscreen.bound = self
+
+    def unbind(self):
+        if self is _SnapOffscreen.bound:
+            bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.cur_fbo[0])
+            bgl.glViewport(*self.cur_viewport)
+            _SnapOffscreen.bound = None
+
+    def clear(self):
+        is_bound =  self is _SnapOffscreen.bound
+        if not is_bound:
+            self.bind()
+
+        bgl.glColorMask(bgl.GL_TRUE, bgl.GL_TRUE, bgl.GL_TRUE, bgl.GL_TRUE)
+        bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
+
+        bgl.glDepthMask(bgl.GL_TRUE)
+        bgl.glClearDepth(1.0);
+
+        bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT)
+
+        if not is_bound:
+            self.unbind()
+
+    def __del__(self):
+        if not self.freed:
+            bgl.glDeleteFramebuffers(1, self.fbo)
+            bgl.glDeleteRenderbuffers(1, self.buf_depth)
+            bgl.glDeleteTextures(1, self.buf_color)
+            del self.fbo
+            del self.buf_color
+            del self.buf_depth
+
+            del self.cur_fbo
+            del self.cur_viewport
+
+    def free(self):
+        self.__del__()
+        self.freed = True
+
+
+class SnapContext():
+    """
+    Initializes the snap context with the region and space where the snap objects will be added.
+
+    .. note::
+        After the context has been created, add the objects with the `add_obj` method.
+
+    :arg region: region of the 3D viewport, typically bpy.context.region.
+    :type region: :class:`bpy.types.Region`
+    :arg space: 3D region data, typically bpy.context.space_data.
+    :type space: :class:`bpy.types.SpaceView3D`
+    """
+
+    def __init__(self, region, space):
+        #print('Render:', bgl.glGetString(bgl.GL_RENDERER))
+        #print('OpenGL Version:', bgl.glGetString(bgl.GL_VERSION))
+
+        self.freed = False
+        self.snap_objects = []
+        self.drawn_count = 0
+        self._offset_cur = 1 # Starts with index 1
+        self.region = region
+        self.rv3d = space.region_3d
+
+        if self.rv3d.is_perspective:
+            self.depth_range = Vector((space.clip_start, space.clip_end))
+        else:
+            self.depth_range = Vector((-space.clip_end, space.clip_end))
+
+        self.proj_mat = None
+        self.mval = Vector((0, 0))
+        self._snap_mode = VERT | EDGE | FACE
+
+        self.set_pixel_dist(12)
+
+        self._offscreen = _SnapOffscreen(self.region.width, self.region.height)
+
+        self.winsize = Vector((self._offscreen.width, self._offscreen.height))
+
+        self._offscreen.clear()
+
+    ## PRIVATE ##
+
+    def _get_snap_obj_by_index(self, index):
+        if index:
+            for snap_obj in self.snap_objects[:self.drawn_count]:
+                data = snap_obj.data[1]
+                if index < data.first_index + data.get_tot_elems():
+                    return snap_obj
+        return None
+
+    def _get_nearest_index(self):
+        r_snap_obj = None
+        r_value = 0
+
+        loc = [self._dist_px, self._dist_px]
+        d = 1
+        m = self.threshold
+        max_val = 2 * m - 1
+        last_value = -1
+        find_next_index = self._snap_mode & FACE and self._snap_mode & (VERT | EDGE)
+        while m < max_val:
+            for i in range(2):
+                while 2 * loc[i] * d < m:
+                    value = int(self._snap_buffer[loc[0]][loc[1]])
+                    loc[i] += d
+                    if value != last_value:
+                        r_value = value
+                        if find_next_index:
+                            last_value = value
+                            find_next_index = False
+                            r_snap_obj = self._get_snap_obj_by_index(value)
+                            if (r_snap_obj is None) or value < (r_snap_obj.data[1].first_index + len(r_snap_obj.data[1].tri_verts)):
+                                continue
+                        elif (r_snap_obj is None) or\
+                            (value < r_snap_obj.data[1].first_index) or\
+                            (value >= (r_snap_obj.data[1].first_index + r_snap_obj.data[1].get_tot_elems())):
+                                r_snap_obj = self._get_snap_obj_by_index(value)
+                        return r_snap_obj, r_value
+            d = -d
+            m += 4 * self._dist_px * d + 1
+
+        return r_snap_obj, r_value
+
+    def _get_loc(self, snap_obj, index):
+        index -= snap_obj.data[1].first_index
+        gpu_data = snap_obj.data[1]
+
+        if gpu_data.draw_tris:
+            num_tris = len(snap_obj.data[1].tri_verts)
+            if index < num_tris:
+                tri_verts = gpu_data.get_tri_verts(index)
+                tri_co = [snap_obj.mat @ Vector(v) for v in gpu_data.get_tri_co(index)]
+                nor = (tri_co[1] - tri_co[0]).cross(tri_co[2] - tri_co[0])
+                return _Internal.intersect_line_plane(self.last_ray[1], self.last_ray[1] + self.last_ray[0], tri_co[0], nor), tri_verts, tri_co
+
+            index -= num_tris
+
+        if gpu_data.draw_edges:
+            num_edges = len(snap_obj.data[1].edge_verts)
+            if index < num_edges:
+                edge_verts = gpu_data.get_edge_verts(index)
+                edge_co = [snap_obj.mat @ Vector(v) for v in gpu_data.get_edge_co(index)]
+                fac = _Internal.intersect_ray_segment_fac(*edge_co, *self.last_ray)
+
+                if (self._snap_mode) & VERT and (fac < 0.25 or fac > 0.75):
+                    co = edge_co[0] if fac < 0.5 else edge_co[1]
+                    proj_co = _Internal.project_co_v3(self, co)
+                    dist = self.mval - proj_co
+                    if abs(dist.x) < self._dist_px and abs(dist.y) < self._dist_px:
+                        return co, (edge_verts[0] if fac < 0.5 else edge_verts[1],), co
+
+                if fac <= 0.0:
+                    co = edge_co[0]
+                elif fac >= 1.0:
+                    co = edge_co[1]
+                else:
+                    co = edge_co[0] + fac * (edge_co[1] - edge_co[0])
+
+                return co, edge_verts, edge_co
+
+            index -= num_edges
+
+        if gpu_data.draw_verts:
+            if index < len(snap_obj.data[1].looseverts):
+                co = snap_obj.mat @ Vector(gpu_data.get_loosevert_co(index))
+                return co, (gpu_data.get_loosevert_index(index),), co
+
+        return None, None, None
+
+
+    def _get_snap_obj_by_obj(self, obj):
+        for snap_obj in self.snap_objects:
+            if obj == snap_obj.data[0]:
+                return snap_obj
+
+    def __del__(self):
+        if not self.freed:
+            self._offscreen.free()
+            # Some objects may still be being referenced
+            for snap_obj in self.snap_objects:
+                del snap_obj.data
+                del snap_obj.mat
+                del snap_obj
+            del self.snap_objects
+
+    ## PUBLIC ##
+
+    def update_all(self):
+        self.drawn_count = 0
+        self._offset_cur = 1
+        self._offscreen.clear()
+
+    def tag_update_drawn_snap_object(self, snap_obj):
+        if len(snap_obj.data) > 1:
+            del snap_obj.data[1:]
+            #self.update_all()
+            # Update on next snap_get call #
+            self.proj_mat = None
+
+    def update_drawn_snap_object(self, snap_obj):
+        if len(snap_obj.data) > 1:
+            _Internal.gpu_Indices_enable_state()
+
+            from .mesh_drawing import GPU_Indices_Mesh
+            snap_vert = self._snap_mode & VERT != 0
+            snap_edge = self._snap_mode & EDGE != 0
+            snap_face = self._snap_mode & FACE != 0
+            snap_obj.data[1] = GPU_Indices_Mesh(snap_obj.data[0], snap_face, snap_edge, snap_vert)
+
+            _Internal.gpu_Indices_restore_state()
+
+    def use_clip_planes(self, value):
+        _Internal.gpu_Indices_use_clip_planes(self.rv3d, value)
+
+    def set_pixel_dist(self, dist_px):
+        self._dist_px = int(dist_px)
+        self._dist_px_sq = self._dist_px ** 2
+        self.threshold = 2 * self._dist_px + 1
+        self._snap_buffer = bgl.Buffer(bgl.GL_INT, (self.threshold, self.threshold))
+
+    def set_snap_mode(self, snap_to_vert, snap_to_edge, snap_to_face):
+        snap_mode = 0
+        if snap_to_vert:
+            snap_mode |= VERT
+        if snap_to_edge:
+            snap_mode |= EDGE
+        if snap_to_face:
+            snap_mode |= FACE
+
+        if snap_mode != self._snap_mode:
+            self._snap_mode = snap_mode
+            self.update_all()
+
+    def add_obj(self, obj, matrix):
+        matrix = matrix.copy()
+        snap_obj = self._get_snap_obj_by_obj(obj)
+        if not snap_obj:
+            self.snap_objects.append(_SnapObjectData([obj], matrix))
+        else:
+            self.snap_objects.append(_SnapObjectData(snap_obj.data, matrix))
+
+        return self.snap_objects[-1]
+
+    def get_ray(self, mval):
+        self.last_ray = _Internal.region_2d_to_orig_and_view_vector(self.region, self.rv3d, mval)
+        return self.last_ray
+
+    def snap_get(self, mval, main_snap_obj = None):
+        ret = None, None, None
+        self.mval[:] = mval
+        snap_vert = self._snap_mode & VERT != 0
+        snap_edge = self._snap_mode & EDGE != 0
+        snap_face = self._snap_mode & FACE != 0
+
+        _Internal.gpu_Indices_enable_state()
+        self._offscreen.bind()
+
+        #bgl.glDisable(bgl.GL_DITHER) # dithering and AA break color coding, so disable #
+        #multisample_enabled = bgl.glIsEnabled(bgl.GL_MULTISAMPLE)
+        #bgl.glDisable(bgl.GL_MULTISAMPLE)
+        bgl.glEnable(bgl.GL_DEPTH_TEST)
+
+        proj_mat = self.rv3d.perspective_matrix.copy()
+        if self.proj_mat != proj_mat:
+            self.proj_mat = proj_mat
+            _Internal.gpu_Indices_set_ProjectionMatrix(self.proj_mat)
+            self.update_all()
+
+        ray_dir, ray_orig = self.get_ray(mval)
+        for i, snap_obj in enumerate(self.snap_objects[self.drawn_count:], self.drawn_count):
+            obj = snap_obj.data[0]
+            try:
+                bbmin = Vector(obj.bound_box[0])
+                bbmax = Vector(obj.bound_box[6])
+            except ReferenceError:
+                self.snap_objects.remove(snap_obj)
+                continue
+
+            if bbmin != bbmax:
+                MVP = proj_mat @ snap_obj.mat
+                mat_inv = snap_obj.mat.inverted()
+                ray_orig_local = mat_inv @ ray_orig
+                ray_dir_local = mat_inv.to_3x3() @ ray_dir
+                in_threshold = _Internal.intersect_boundbox_threshold(
+                        self, MVP, ray_orig_local, ray_dir_local, bbmin, bbmax)
+            else:
+                proj_co = _Internal.project_co_v3(self, snap_obj.mat.translation)
+                dist = self.mval - proj_co
+                in_threshold = abs(dist.x) < self._dist_px and abs(dist.y) < self._dist_px
+                #snap_obj.data[1] = primitive_point
+
+            if in_threshold:
+                if len(snap_obj.data) == 1:
+                    from .mesh_drawing import GPU_Indices_Mesh
+                    snap_obj.data.append(GPU_Indices_Mesh(obj, snap_face, snap_edge, snap_vert))
+                snap_obj.data[1].set_draw_mode(snap_face, snap_edge, snap_vert)
+                snap_obj.data[1].set_ModelViewMatrix(snap_obj.mat)
+
+                if snap_obj == main_snap_obj:
+                    snap_obj.data[1].Draw(self._offset_cur, -0.0001)
+                else:
+                    snap_obj.data[1].Draw(self._offset_cur)
+                self._offset_cur += snap_obj.data[1].get_tot_elems()
+
+                tmp = self.snap_objects[self.drawn_count]
+                self.snap_objects[self.drawn_count] = self.snap_objects[i]
+                self.snap_objects[i] = tmp
+
+                self.drawn_count += 1
+
+        bgl.glReadBuffer(bgl.GL_COLOR_ATTACHMENT0)
+        bgl.glReadPixels(
+                int(self.mval[0]) - self._dist_px, int(self.mval[1]) - self._dist_px,
+                self.threshold, self.threshold, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, self._snap_buffer)
+        #bgl.glReadBuffer(bgl.GL_BACK)
+
+        snap_obj, index = self._get_nearest_index()
+        #print("index:", index)
+        if snap_obj:
+            ret = self._get_loc(snap_obj, index)
+
+        bgl.glDisable(bgl.GL_DEPTH_TEST)
+        self._offscreen.unbind()
+        _Internal.gpu_Indices_restore_state()
+
+        return (snap_obj, *ret)
+
+    def free(self):
+        self.__del__()
+        self.freed = True
diff --git a/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py b/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ef0b0a0943e8c4912f3ff2aa1ab92a15ee36f8f
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py
@@ -0,0 +1,412 @@
+# ##### 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 3
+#  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, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+import bgl
+import bmesh
+import numpy as np
+from mathutils import Matrix
+import gpu
+
+_Hash = {}
+
+def load_shader(shadername):
+    from os import path
+    with open(path.join(path.dirname(__file__), 'shaders', shadername), 'r') as f:
+        return f.read()
+
+def get_mesh_vert_co_array(me):
+    tot_vco = len(me.vertices)
+    if tot_vco:
+        verts_co = np.empty(len(me.vertices) * 3, 'f4')
+        me.vertices.foreach_get("co", verts_co)
+        verts_co.shape = (-1, 3)
+        return verts_co
+    return None
+
+
+def get_bmesh_vert_co_array(bm):
+    tot_vco = len(bm.verts)
+    if tot_vco:
+        return np.array([v.co for v in bm.verts], 'f4')
+    return None
+
+
+def get_mesh_tri_verts_array(me):
+    me.calc_loop_triangles()
+    len_triangles = len(me.loop_triangles)
+    if len_triangles:
+        tris = np.empty(len_triangles * 3, 'i4')
+        me.loop_triangles.foreach_get("vertices", tris)
+        tris.shape = (-1, 3)
+        return tris
+    return None
+
+
+def get_bmesh_tri_verts_array(bm):
+    l_tri_layer = bm.faces.layers.int.get("l_tri")
+    if l_tri_layer is None:
+        l_tri_layer = bm.faces.layers.int.new("l_tri")
+
+    ltris = bm.calc_loop_triangles()
+    tris = np.empty((len(ltris), 3), 'i4')
+    i = 0
+    last_face = bm.faces[-1]
+    for ltri in ltris:
+        face = ltri[0].face
+        if not face.hide:
+            tris[i] = ltri[0].vert.index, ltri[1].vert.index, ltri[2].vert.index
+            if last_face != face:
+                last_face = face
+                face[l_tri_layer] = i
+            i += 1
+    if i:
+        tris.resize((i, 3), refcheck=False)
+        return tris
+    return None
+
+
+def get_mesh_edge_verts_array(me):
+    tot_edges = len(me.edges)
+    if tot_edges:
+        edge_verts = np.empty(tot_edges * 2, 'i4')
+        me.edges.foreach_get("vertices", edge_verts)
+        edge_verts.shape = tot_edges, 2
+        return edge_verts
+    return None
+
+
+def get_bmesh_edge_verts_array(bm):
+    bm.edges.ensure_lookup_table()
+    edges = [[e.verts[0].index, e.verts[1].index] for e in bm.edges if not e.hide]
+    if edges:
+        return np.array(edges, 'i4')
+    return None
+
+
+def get_mesh_loosevert_array(me, edges):
+    verts = np.arange(len(me.vertices))
+
+    mask = np.in1d(verts, edges, invert=True)
+
+    verts = verts[mask]
+    if len(verts):
+        return verts
+    return None
+
+
+def get_bmesh_loosevert_array(bm):
+    looseverts = [v.index for v in bm.verts if not (v.link_edges or v.hide)]
+    if looseverts:
+        return np.array(looseverts, 'i4')
+    return None
+
+
+class _Mesh_Arrays():
+    def __init__(self, obj, create_tris, create_edges, create_looseverts):
+        self.tri_verts = self.edge_verts = self.looseverts = ()
+        if obj.type == 'MESH':
+            me = obj.data
+            if me.is_editmode:
+                bm = bmesh.from_edit_mesh(me)
+                bm.verts.ensure_lookup_table()
+
+                self.verts_co = get_bmesh_vert_co_array(bm)
+
+                if create_tris:
+                    self.tri_verts = get_bmesh_tri_verts_array(bm)
+                if create_edges:
+                    self.edge_verts = get_bmesh_edge_verts_array(bm)
+                if create_looseverts:
+                    self.looseverts = get_bmesh_loosevert_array(bm)
+
+                del bm
+            else:
+                import bpy
+                self.verts_co = get_mesh_vert_co_array(me)
+
+                if create_tris:
+                    self.tri_verts = get_mesh_tri_verts_array(me)
+                if create_edges:
+                    self.edge_verts = get_mesh_edge_verts_array(me)
+                if create_looseverts:
+                    edge_verts = self.edge_verts
+                    if edge_verts is None:
+                        edge_verts = get_mesh_edge_verts_array(me)
+                    self.looseverts = get_mesh_loosevert_array(me, edge_verts)
+                    del edge_verts
+
+
+        else: #TODO
+            self.verts_co = np.zeros((1,3), 'f4')
+            self.looseverts = np.zeros(1, 'i4')
+
+    def __del__(self):
+        del self.tri_verts, self.edge_verts, self.looseverts
+        del self.verts_co
+
+
+class GPU_Indices_Mesh():
+    __slots__ = (
+        "obj",
+        "draw_tris",
+        "draw_edges",
+        "draw_verts",
+        "batch_tris",
+        "batch_edges",
+        "batch_lverts",
+        "verts_co",
+        "tri_verts",
+        "edge_verts",
+        "looseverts",
+        "first_index",
+        "users"
+    )
+
+    shader = None
+
+    @classmethod
+    def end_opengl(cls):
+        del cls.shader
+        del cls.P
+
+        del cls
+
+    @staticmethod
+    def init_opengl():
+        cls = GPU_Indices_Mesh
+        # OpenGL was already initialized, nothing to do here.
+        if cls.shader is not None:
+            return
+
+        import atexit
+
+        # Make sure we only registered the callback once.
+        atexit.unregister(cls.end_opengl)
+        atexit.register(cls.end_opengl)
+
+        cls.shader = gpu.types.GPUShader(
+                load_shader("ID_color_vert.glsl"),
+                load_shader("ID_color_frag.glsl"),
+                )
+        #cls.unif_use_clip_planes = cls.shader.uniform_from_name('use_clip_planes')
+        #cls.unif_clip_plane = cls.shader.uniform_from_name('clip_plane')
+        cls.unif_offset = cls.shader.uniform_from_name('offset')
+
+        cls.P = Matrix()
+
+
+    @staticmethod
+    def set_ModelViewMatrix(MV):
+        gpu.matrix.load_matrix(MV)
+
+
+    def __init__(self, obj, draw_tris, draw_edges, draw_verts):
+        self.obj = obj
+
+        if obj.data in _Hash:
+            src = _Hash[obj.data]
+            dst = self
+
+            dst.draw_tris    = src.draw_tris
+            dst.draw_edges   = src.draw_edges
+            dst.draw_verts   = src.draw_verts
+            dst.batch_tris   = src.batch_tris
+            dst.batch_edges  = src.batch_edges
+            dst.batch_lverts = src.batch_lverts
+            dst.verts_co     = src.verts_co
+            dst.tri_verts    = src.tri_verts
+            dst.edge_verts   = src.edge_verts
+            dst.looseverts   = src.looseverts
+            dst.users        = src.users
+            dst.users.append(self)
+
+            update = obj.type == 'MESH' and obj.data.is_editmode
+
+        else:
+            _Hash[obj.data] = self
+            self.users = [self]
+            update = True;
+
+        if update:
+            self.draw_tris = draw_tris
+            self.draw_edges = draw_edges
+            self.draw_verts = draw_verts
+
+            GPU_Indices_Mesh.init_opengl()
+
+            ## Init Array ##
+            mesh_arrays = _Mesh_Arrays(obj, draw_tris, draw_edges, draw_verts)
+
+            if mesh_arrays.verts_co is None:
+                self.draw_tris = False
+                self.draw_edges = False
+                self.draw_verts = False
+                self.tri_verts = None
+                self.edge_verts = None
+                self.looseverts = None
+                return
+
+            ## Create VBO for vertices ##
+            self.verts_co = mesh_arrays.verts_co
+            self.tri_verts = mesh_arrays.tri_verts
+            self.edge_verts = mesh_arrays.edge_verts
+            self.looseverts = mesh_arrays.looseverts
+            del mesh_arrays
+
+            format = gpu.types.GPUVertFormat()
+            format.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
+
+            vbo = gpu.types.GPUVertBuf(format, len = len(self.verts_co))
+
+            vbo.attr_fill(0, data = self.verts_co)
+
+            ## Create Batch for Tris ##
+            if self.tri_verts is not None:
+                ebo = gpu.types.GPUIndexBuf(type = "TRIS", seq = self.tri_verts)
+                self.batch_tris = gpu.types.GPUBatch(type = "TRIS", buf = vbo, elem = ebo)
+                self.batch_tris.program_set(self.shader)
+            else:
+                self.draw_tris = False
+                self.batch_tris = None
+
+            ## Create Batch for Edges ##
+            if self.edge_verts is not None:
+                ebo = gpu.types.GPUIndexBuf(type = "LINES", seq = self.edge_verts)
+                self.batch_edges = gpu.types.GPUBatch(type = "LINES", buf = vbo, elem = ebo)
+                self.batch_edges.program_set(self.shader)
+            else:
+                self.draw_edges = False
+                self.batch_edges = None
+
+            ## Create Batch for Loose Verts ##
+            if self.looseverts is not None:
+                ebo = gpu.types.GPUIndexBuf(type = "POINTS", seq = self.looseverts)
+                self.batch_lverts = gpu.types.GPUBatch(type = "POINTS", buf = vbo, elem = ebo)
+                self.batch_lverts.program_set(self.shader)
+            else:
+                self.draw_verts = False
+                self.batch_lverts = None
+
+
+    def get_tot_elems(self):
+        tot = 0
+        if self.draw_tris:
+            tot += len(self.tri_verts)
+
+        if self.draw_edges:
+            tot += len(self.edge_verts)
+
+        if self.draw_verts:
+            tot += len(self.looseverts)
+
+        return tot
+
+
+    def set_draw_mode(self, draw_tris, draw_edges, draw_verts):
+        self.draw_tris = draw_tris and self.tri_verts is not None
+        self.draw_edges = draw_edges and self.edge_verts is not None
+        self.draw_verts = draw_verts and self.looseverts is not None
+
+
+    def Draw(self, index_offset, depth_offset = -0.00005):
+        self.first_index = index_offset
+        if self.draw_tris:
+            self.shader.uniform_int("offset", (index_offset,))
+            self.batch_tris.draw(self.shader)
+            index_offset += len(self.tri_verts)
+            bgl.glDepthRange(depth_offset, 1 + depth_offset)
+
+        if self.draw_edges:
+            self.shader.uniform_int("offset", (index_offset,))
+            #bgl.glLineWidth(3.0)
+            self.batch_edges.draw(self.shader)
+            #bgl.glLineWidth(1.0)
+            index_offset += len(self.edge_verts)
+
+        if self.draw_verts:
+            self.shader.uniform_int("offset", (index_offset,))
+            self.batch_lverts.draw(self.shader)
+
+        bgl.glDepthRange(0.0, 1.0)
+
+
+    def get_tri_co(self, index):
+        return self.verts_co[self.tri_verts[index]]
+
+    def get_edge_co(self, index):
+        return self.verts_co[self.edge_verts[index]]
+
+    def get_loosevert_co(self, index):
+        return self.verts_co[self.looseverts[index]]
+
+    def get_loop_tri_co_by_bmface(self, bm, bmface):
+        l_tri_layer = bm.faces.layers.int["l_tri"]
+        tri = bmface[l_tri_layer]
+        return self.verts_co[self.tri_verts[tri : tri + len(bmface.verts) - 2]]
+
+
+    def get_tri_verts(self, index):
+        return self.tri_verts[index]
+
+    def get_edge_verts(self, index):
+        return self.edge_verts[index]
+
+    def get_loosevert_index(self, index):
+        return self.looseverts[index]
+
+
+    def __del__(self):
+        if len(self.users) == 1:
+            self.free_gl()
+            _Hash.pop(obj.data)
+
+        self.user.remove(self)
+        #print('mesh_del', self.obj.name)
+
+
+def gpu_Indices_enable_state():
+    GPU_Indices_Mesh.init_opengl()
+    gpu.matrix.push()
+    gpu.matrix.push_projection()
+    gpu.matrix.load_projection_matrix(GPU_Indices_Mesh.P)
+    GPU_Indices_Mesh.shader.bind()
+
+
+def gpu_Indices_restore_state():
+    gpu.matrix.pop()
+    gpu.matrix.pop_projection()
+
+
+def gpu_Indices_use_clip_planes(rv3d, value):
+    pass #TODO
+    #if rv3d.use_clip_planes:
+        #planes = bgl.Buffer(bgl.GL_FLOAT, (6, 4), rv3d.clip_planes)
+
+        #_store_current_shader_state(PreviousGLState)
+        #GPU_Indices_Mesh.init_opengl()
+        #bgl.glUseProgram(GPU_Indices_Mesh.shader.program)
+        #bgl.glUniform1i(GPU_Indices_Mesh.unif_use_clip_planes, value)
+
+        #bgl.glUniform4fv(GPU_Indices_Mesh.unif_clip_plane, 4, planes)
+
+        #_restore_shader_state(PreviousGLState)
+
+
+def gpu_Indices_set_ProjectionMatrix(P):
+    gpu.matrix.load_projection_matrix(P)
+    GPU_Indices_Mesh.P[:] = P
diff --git a/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..3e01f7b09ca9cef423b7c15faae8d0d9f144bd96
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl
@@ -0,0 +1,24 @@
+uniform int offset;
+
+#ifdef USE_CLIP_PLANES
+uniform bool use_clip_planes;
+in vec4 clip_distance;
+#endif
+
+out uint FragColor;
+
+void main()
+{
+#ifdef USE_CLIP_PLANES
+	if (use_clip_planes &&
+	   ((clip_distance[0] < 0) ||
+	    (clip_distance[1] < 0) ||
+	    (clip_distance[2] < 0) ||
+	    (clip_distance[3] < 0)))
+	{
+		discard;
+	}
+#endif
+
+	FragColor = uint(gl_PrimitiveID + offset);
+}
diff --git a/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..fa0afec6bf9d7152e2ecca5977eaa03a370c0511
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl
@@ -0,0 +1,25 @@
+uniform mat4 ModelViewProjectionMatrix;
+
+#ifdef USE_CLIP_PLANES
+uniform mat4 ModelViewMatrix;
+uniform bool use_clip_planes;
+uniform vec4 clip_plane[4];
+out vec4 clip_distance;
+#endif
+
+in vec3 pos;
+
+void main()
+{
+#ifdef USE_CLIP_PLANES
+	if (use_clip_planes) {
+		vec4 g_pos = ModelViewMatrix * vec4(pos, 1.0);
+
+		for (int i = 0; i != 4; i++) {
+			clip_distance[i] = dot(clip_plane[i], g_pos);
+		}
+	}
+#endif
+
+	gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
+}
diff --git a/mesh_snap_utilities_line/snap_context_l/utils_projection.py b/mesh_snap_utilities_line/snap_context_l/utils_projection.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc17aa230837d9d47a2399f0ce93deaee2358a02
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/utils_projection.py
@@ -0,0 +1,216 @@
+# ##### 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 3
+#  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, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+from mathutils import Vector
+from mathutils.geometry import intersect_point_line
+
+
+def depth_get(co, ray_start, ray_dir):
+    dvec = co - ray_start
+    return dvec.dot(ray_dir)
+
+
+def region_2d_to_orig_and_view_vector(region, rv3d, coord):
+    viewinv = rv3d.view_matrix.inverted_safe()
+    persinv = rv3d.perspective_matrix.inverted_safe()
+
+    dx = (2.0 * coord[0] / region.width) - 1.0
+    dy = (2.0 * coord[1] / region.height) - 1.0
+
+    if rv3d.is_perspective:
+        origin_start = viewinv.translation.copy()
+
+        out = Vector((dx, dy, -0.5))
+
+        w = out.dot(persinv[3].xyz) + persinv[3][3]
+
+        view_vector = ((persinv @ out) / w) - origin_start
+    else:
+        view_vector = -viewinv.col[2].xyz
+
+        origin_start = ((persinv.col[0].xyz * dx) +
+                        (persinv.col[1].xyz * dy) +
+                        viewinv.translation)
+
+    view_vector.normalize()
+    return view_vector, origin_start
+
+
+def project_co_v3(sctx, co):
+    proj_co = sctx.proj_mat @ co.to_4d()
+    try:
+        proj_co.xy /= proj_co.w
+    except Exception as e:
+        print(e)
+
+    win_half = sctx.winsize * 0.5
+    proj_co[0] = (proj_co[0] + 1.0) * win_half[0]
+    proj_co[1] = (proj_co[1] + 1.0) * win_half[1]
+
+    return proj_co.xy
+
+
+
+def intersect_boundbox_threshold(sctx, MVP, ray_origin_local, ray_direction_local, bbmin, bbmax):
+    local_bvmin = Vector()
+    local_bvmax = Vector()
+    tmin = Vector()
+    tmax = Vector()
+
+    if (ray_direction_local[0] < 0.0):
+        local_bvmin[0] = bbmax[0]
+        local_bvmax[0] = bbmin[0]
+    else:
+        local_bvmin[0] = bbmin[0]
+        local_bvmax[0] = bbmax[0]
+
+    if (ray_direction_local[1] < 0.0):
+        local_bvmin[1] = bbmax[1]
+        local_bvmax[1] = bbmin[1]
+    else:
+        local_bvmin[1] = bbmin[1]
+        local_bvmax[1] = bbmax[1]
+
+    if (ray_direction_local[2] < 0.0):
+        local_bvmin[2] = bbmax[2]
+        local_bvmax[2] = bbmin[2]
+    else:
+        local_bvmin[2] = bbmin[2]
+        local_bvmax[2] = bbmax[2]
+
+    if (ray_direction_local[0]):
+        tmin[0] = (local_bvmin[0] - ray_origin_local[0]) / ray_direction_local[0]
+        tmax[0] = (local_bvmax[0] - ray_origin_local[0]) / ray_direction_local[0]
+    else:
+        tmin[0] = tmax[0] = sctx.depth_range[1]
+
+    if (ray_direction_local[1]):
+        tmin[1] = (local_bvmin[1] - ray_origin_local[1]) / ray_direction_local[1]
+        tmax[1] = (local_bvmax[1] - ray_origin_local[1]) / ray_direction_local[1]
+    else:
+        tmin[1] = tmax[1] = sctx.depth_range[1]
+
+    if (ray_direction_local[2]):
+        tmin[2] = (local_bvmin[2] - ray_origin_local[2]) / ray_direction_local[2]
+        tmax[2] = (local_bvmax[2] - ray_origin_local[2]) / ray_direction_local[2]
+    else:
+        tmin[2] = tmax[2] = sctx.depth_range[1]
+
+    # `va` and `vb` are the coordinates of the AABB edge closest to the ray #
+    va = Vector()
+    vb = Vector()
+    # `rtmin` and `rtmax` are the minimum and maximum distances of the ray hits on the AABB #
+
+    if ((tmax[0] <= tmax[1]) and (tmax[0] <= tmax[2])):
+        rtmax = tmax[0]
+        va[0] = vb[0] = local_bvmax[0]
+        main_axis = 3
+    elif ((tmax[1] <= tmax[0]) and (tmax[1] <= tmax[2])):
+        rtmax = tmax[1]
+        va[1] = vb[1] = local_bvmax[1]
+        main_axis = 2
+    else:
+        rtmax = tmax[2]
+        va[2] = vb[2] = local_bvmax[2]
+        main_axis = 1
+
+    if ((tmin[0] >= tmin[1]) and (tmin[0] >= tmin[2])):
+        rtmin = tmin[0]
+        va[0] = vb[0] = local_bvmin[0]
+        main_axis -= 3
+
+    elif ((tmin[1] >= tmin[0]) and (tmin[1] >= tmin[2])):
+        rtmin = tmin[1]
+        va[1] = vb[1] = local_bvmin[1]
+        main_axis -= 1
+
+    else:
+        rtmin = tmin[2]
+        va[2] = vb[2] = local_bvmin[2]
+        main_axis -= 2
+
+    if (main_axis < 0):
+        main_axis += 3
+
+#ifdef IGNORE_BEHIND_RAY
+    depth_max = depth_get(local_bvmax, ray_origin_local, ray_direction_local)
+    if (depth_max < sctx.depth_range[0]):
+        return False
+#endif
+
+    if (rtmin <= rtmax):
+        # if rtmin < rtmax, ray intersect `AABB` #
+        return True
+
+    if (ray_direction_local[main_axis] < 0.0):
+        va[main_axis] = local_bvmax[main_axis]
+        vb[main_axis] = local_bvmin[main_axis]
+
+    else:
+        va[main_axis] = local_bvmin[main_axis]
+        vb[main_axis] = local_bvmax[main_axis]
+
+    win_half = sctx.winsize * 0.5
+
+    scale = abs(local_bvmax[main_axis] - local_bvmin[main_axis])
+
+    va2d = Vector((
+        (MVP[0].xyz.dot(va) + MVP[0][3]),
+        (MVP[1].xyz.dot(va) + MVP[1][3]),
+    ))
+
+    vb2d = Vector((
+        (va2d[0] + MVP[0][main_axis] * scale),
+        (va2d[1] + MVP[1][main_axis] * scale),
+    ))
+
+    depth_a = MVP[3].xyz.dot(va) + MVP[3][3]
+    depth_b = depth_a + MVP[3][main_axis] * scale
+
+    va2d /= depth_a
+    vb2d /= depth_b
+
+    va2d[0] = (va2d[0] + 1.0) * win_half[0]
+    va2d[1] = (va2d[1] + 1.0) * win_half[1]
+    vb2d[0] = (vb2d[0] + 1.0) * win_half[0]
+    vb2d[1] = (vb2d[1] + 1.0) * win_half[1]
+
+    p, fac = intersect_point_line(sctx.mval, va2d, vb2d)
+    if fac < 0.0:
+        return (sctx.mval - va2d).length_squared < sctx._dist_px_sq
+    elif fac > 1.0:
+        return (sctx.mval - vb2d).length_squared < sctx._dist_px_sq
+    else:
+        return (sctx.mval - p).length_squared < sctx._dist_px_sq
+
+
+def intersect_ray_segment_fac(v0, v1, ray_direction, ray_origin):
+    a = v1 - v0
+    t = v0 - ray_origin
+    n = a.cross(ray_direction)
+    nlen = n.length_squared
+
+    # if (nlen == 0.0f) the lines are parallel, has no nearest point, only distance squared.*/
+    if nlen == 0.0:
+        # Calculate the distance to the nearest point to origin then #
+        return a.dot(ray_direction) < 0
+    else:
+        c = n - t
+        cray = c.cross(ray_direction)
+        return cray.dot(n) / nlen
+