Skip to content
Snippets Groups Projects
mesh_snap_utilities_line.py 43.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### 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 = {
    
        "author": "Germano Cavalcante",
    
        "blender": (2, 75, 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",
    
        "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
        "category": "Mesh"}
    
    
    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,
            )
    
    
    
    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 fac_nearest_to_segment_2d(co2v, v2d0, v2d1):
        u = v2d1.xy - v2d0.xy
        h = co2v.xy - v2d0.xy
        return u.dot(h) / u.length_squared
    
    
    def region_2d_to_orig_and_view_vector(region, rv3d, coord, clamp=None):
        viewinv = rv3d.view_matrix.inverted()
        persinv = rv3d.perspective_matrix.inverted()
    
        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)
    
            if clamp != 0.0:
                if rv3d.view_perspective != 'CAMERA':
                    # this value is scaled to the far clip already
                    origin_offset = persinv.col[2].xyz
                    if clamp is not None:
                        if clamp < 0.0:
                            origin_offset.negate()
                            clamp = -clamp
                        if origin_offset.length > clamp:
                            origin_offset.length = clamp
    
                    origin_start -= origin_offset
    
        view_vector.normalize()
        return origin_start, view_vector
    
    
    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)
    
            hit = intersect_ray_tri(v1, v2, Vector(), (-vector), (orig), False)
    
    class SnapCache():
            bvert = None
            vco = None
    
            bedge = None
            v0 = None
            v1 = None
            vmid = None
            vperp = None
            v2d0 = None
            v2d1 = None
            v2dmid = None
            v2dperp = None
    
            bface = None
            fmid = None
            fnor = None
    
            out_obj = None
            out_obmat = None
            out_obimat = None
    
    
    def snap_utilities(
    
                cache, context, obj_matrix_world,
                bm, mcursor,
                outer_verts=False,
                constrain=None,
                previous_vert=None,
                ignore_obj=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 bm.select_history:
            bm.select_history[0].select = False
            bm.select_history.clear()
    
    
        bpy.ops.view3d.select(location=(int(mcursor.x), int(mcursor.y)))
    
    
        if bm.select_history:
            bm_geom = bm.select_history[0]
    
        if isinstance(bm_geom, bmesh.types.BMVert):
            r_type = 'VERT'
    
            if cache.bvert != bm_geom:
                cache.bvert = bm_geom
                cache.vco = obj_matrix_world * cache.bvert.co
    
                # cache.v2d = location_3d_to_region_2d(region, rv3d, cache.vco)
    
    
            if constrain:
                location = intersect_point_line(cache.vco, constrain[0], constrain[1])
    
                r_loc = location[0]
            else:
                r_loc = cache.vco
    
        elif isinstance(bm_geom, bmesh.types.BMEdge):
            if cache.bedge != bm_geom:
                cache.bedge = bm_geom
                cache.v0 = obj_matrix_world * bm_geom.verts[0].co
                cache.v1 = obj_matrix_world * bm_geom.verts[1].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 bm_geom.verts:
                        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])
    
    
    
            if constrain:
                location = intersect_line_line(constrain[0], constrain[1], cache.v0, cache.v1)
    
                    is_increment = True
                    orig, view_vector = region_2d_to_orig_and_view_vector(region, rv3d, mcursor)
                    end = orig + view_vector
                    location = intersect_line_line(constrain[0], constrain[1], orig, end)
                r_loc = location[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'
                fac = fac_nearest_to_segment_2d(mcursor, cache.v2d0, cache.v2d1)
    
                fac *= cache.v2d0.z / cache.v2d1.z  # convert to fac3d
    
                r_loc = cache.v0 + fac * (cache.v1 - cache.v0)
    
        elif isinstance(bm_geom, bmesh.types.BMFace):
            is_increment = True
            r_type = 'FACE'
    
            if cache.bface != bm_geom:
                cache.bface = bm_geom
    
                cache.fmid = obj_matrix_world * bm_geom.calc_center_median()
                cache.fnor = bm_geom.normal * obj_matrix_world.inverted()
    
    
            orig, view_vector = region_2d_to_orig_and_view_vector(region, rv3d, mcursor)
            end = orig + view_vector
            r_loc = intersect_line_plane(orig, end, cache.fmid, cache.fnor, False)
    
            if constrain:
                is_increment = False
                r_loc = intersect_point_line(r_loc, constrain[0], constrain[1])[0]
    
    
            is_increment = True
            r_type = 'OUT'
    
            orig, view_vector = region_2d_to_orig_and_view_vector(region, rv3d, mcursor)
    
            face_index = -1
    
                result, r_loc, normal, face_index, cache.out_obj, cache.out_obmat = scene.ray_cast(orig, view_vector)
                if result:
                    r_type = 'FACE'
                    cache.out_obimat = cache.out_obmat.inverted()
                else:
                    face_index = -1
                    r_loc = None
    
            if cache.out_obj and cache.out_obj != ignore_obj:
                if not r_loc or outer_verts:
                    location = None
                    if face_index == -1:
                        # get the ray relative to the cache.out_obj
                        ray_origin_obj = cache.out_obimat * orig
                        end = orig + view_vector * 1000
                        ray_target_obj = cache.out_obimat * end
                        result, location, normal, face_index = cache.out_obj.ray_cast(ray_origin_obj, ray_target_obj)
    
                    if face_index == -1:
                        cache.out_obj = None
    
                    elif outer_verts:
                        vloc = None
                        try:
                            me = cache.out_obj.data
                            verts = me.polygons[face_index].vertices
                            v_dist = 100
    
                            for i in verts:
                                v_co = cache.out_obmat * me.vertices[i].co
                                v_2d = location_3d_to_region_2d(region, rv3d, v_co)
                                dist = (Vector(mcursor) - v_2d.xy).length_squared
                                if dist < v_dist:
                                    v_dist = dist
                                    vloc = v_co
                        except Exception as e:
                            print('Fail', e)
    
                        if vloc:
                            is_increment = False
                            r_type = 'VERT'
                            r_loc = vloc
    
                    if not r_loc:
                        r_type = 'FACE'
                        r_loc = cache.out_obmat * location
    
            if constrain:
                if r_loc:
                    is_increment = False
                    r_loc = intersect_point_line(r_loc, constrain[0], constrain[1])[0]
                else:
                    r_loc = intersect_line_line(constrain[0], constrain[1], orig, end)[0]
    
            elif not r_loc:
                r_loc = out_Location(rv3d, region, orig, view_vector)
    
        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
    
        return r_loc, r_type, bm_geom, r_len
    
    
    def get_isolated_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, Bmesh, bm_geom, location):
        if not hasattr(self, 'list_verts'):
            self.list_verts = []
    
        if not hasattr(self, 'list_edges'):
            self.list_edges = []
    
        if not hasattr(self, 'list_faces'):
            self.list_faces = []
    
    
            vertices = (bmesh.ops.create_vert(Bmesh, co=(location)))
            self.list_verts.append(vertices['vert'][0])
    
        elif isinstance(bm_geom, bmesh.types.BMVert):
            if (bm_geom.co - location).length < .01:
                if self.list_verts == [] or self.list_verts[-1] != bm_geom:
                    self.list_verts.append(bm_geom)
            else:
                vertices = bmesh.ops.create_vert(Bmesh, co=(location))
                self.list_verts.append(vertices['vert'][0])
    
        elif isinstance(bm_geom, bmesh.types.BMEdge):
            self.list_edges.append(bm_geom)
    
            vector_p0_l = (bm_geom.verts[0].co - location)
            vector_p1_l = (bm_geom.verts[1].co - location)
            cross = vector_p0_l.cross(vector_p1_l) / bm_geom.calc_length()
    
            if cross < Vector((0.001, 0, 0)):  # or round(vector_p0_l.angle(vector_p1_l), 2) == 3.14:
                factor = vector_p0_l.length / bm_geom.calc_length()
    
                vertex0 = bmesh.utils.edge_split(bm_geom, bm_geom.verts[0], factor)
                self.list_verts.append(vertex0[1])
    
                # self.list_edges.append(vertex0[0])
    
                vertices = bmesh.ops.create_vert(Bmesh, co=(location))
                self.list_verts.append(vertices['vert'][0])
    
        elif isinstance(bm_geom, bmesh.types.BMFace):
            self.list_faces.append(bm_geom)
            vertices = (bmesh.ops.create_vert(Bmesh, co=(location)))
            self.list_verts.append(vertices['vert'][0])
    
        # draw, split and create face
        if len(self.list_verts) >= 2:
            V1 = self.list_verts[-2]
            V2 = self.list_verts[-1]
    
            # V2_link_verts = [x for y in [a.verts for a in V2.link_edges] for x in y if x != V2]
    
            for edge in V2.link_edges:
                if V1 in edge.verts:
                    self.list_edges.append(edge)
                    break
    
            else:  # if V1 not in V2_link_verts:
    
                if not V2.link_edges:
                    edge = Bmesh.edges.new([V1, V2])
                    self.list_edges.append(edge)
                else:
                    link_two_faces = V1.link_faces and V2.link_faces
                    if link_two_faces:
                        self.list_faces = [f for f in V2.link_faces if f in V1.link_faces]
    
                    elif not self.list_faces:
                        faces, co2 = (V1.link_faces, V2.co.copy()) if V1.link_faces else (V2.link_faces, 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:
                                    self.list_faces.append(face)
    
                    if self.list_faces:
                        edge = Bmesh.edges.new([V1, V2])
                        self.list_edges.append(edge)
                        ed_list = get_isolated_edges(V2)
                        for face in set(self.list_faces):
                            facesp = bmesh.utils.face_split_edgenet(face, list(set(ed_list)))
                            self.list_faces = []
                    else:
                        if self.intersect:
    
                            facesp = bmesh.ops.connect_vert_pair(Bmesh, verts=[V1, V2], verts_exclude=Bmesh.verts)
                            # print(facesp)
    
                        if not self.intersect or not facesp['edges']:
                            edge = Bmesh.edges.new([V1, V2])
                            self.list_edges.append(edge)
                        else:
                            for edge in facesp['edges']:
                                self.list_edges.append(edge)
                    bmesh.update_edit_mesh(obj.data, tessface=True, destructive=True)
    
            # create face
            if self.create_face:
                ed_list = self.list_edges.copy()
                for edge in V2.link_edges:
                    for vert in edge.verts:
                        if vert in self.list_verts:
                            ed_list.append(edge)
                            for edge in get_isolated_edges(V2):
                                if edge not in ed_list:
                                    ed_list.append(edge)
    
                            bmesh.ops.edgenet_fill(Bmesh, edges=list(set(ed_list)))
    
                            bmesh.update_edit_mesh(obj.data, tessface=True, destructive=True)
                            break
    
                # print('face created')
    
        return [obj.matrix_world * a.co for a in self.list_verts]
    
    
    
    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:]
    
                    self.line_pos = (self.line_pos - 1) % (len(self.length_entered) + 1)
    
                    self.line_pos = (self.line_pos + 1) % (len(self.length_entered) + 1)
    
        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 modal_navigation(self, context, event):
    
            # TO DO:
            # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
    
            if not hasattr(self, 'navigation_cache'):  # or self.navigation_cache == False:
                # print('update navigation')
    
                self.navigation_cache = True
                self.keys_rotate = set()
                self.keys_move = set()
                self.keys_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.keys_rotate.add((key.alt, key.ctrl, key.shift, key.type, key.value))
                    if key.idname == 'view3d.move':
                        self.keys_move.add((key.alt, key.ctrl, key.shift, key.type, key.value))
                    if key.idname == 'view3d.zoom':
    
                        self.keys_zoom.add(
                                        (key.alt, key.ctrl, key.shift, key.type,
                                         key.value, key.properties.delta)
                                        )
    
                            self.keys_zoom.add(
                                        (key.alt, key.ctrl, key.shift, 'WHEELDOWNMOUSE',
                                         key.value, key.properties.delta)
                                        )
    
                            self.keys_zoom.add(
                                        (key.alt, key.ctrl, key.shift, 'WHEELUPMOUSE',
                                         key.value, key.properties.delta)
                                        )
    
    
            evkey = (event.alt, event.ctrl, event.shift, event.type, event.value)
            if evkey in self.keys_rotate:
                bpy.ops.view3d.rotate('INVOKE_DEFAULT')
            elif evkey in self.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')
            else:
                for key in self.keys_zoom:
                    if evkey == key[0:5]:
                        delta = key[5]
                        if delta == 0:
                            bpy.ops.view3d.zoom('INVOKE_DEFAULT')
                        else:
    
                            rv3d.view_distance += delta * rv3d.view_distance / 6
                            rv3d.view_location -= delta * (self.location - rv3d.view_location) / 6
    
                        break
    
        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)
    
            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
    
            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.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.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(self, context, event):
            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.list_faces = []
                self.obj = bpy.context.active_object
                self.obj_matrix = self.obj.matrix_world.copy()
                self.bm = bmesh.from_edit_mesh(self.obj.data)
                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))
    
                if self.list_verts != []:
                    previous_vert = self.list_verts[-1]
                else:
                    previous_vert = None
    
    
                outer_verts = self.outer_verts and not self.keytab  # is this used?
    
                self.location, self.type, self.geom, self.len = snap_utilities(
                        self.cache, context, self.obj_matrix,
                        self.bm, mval,
    
                        outer_verts=self.outer_verts,
                        constrain=self.vector_constrain,
                        previous_vert=previous_vert,
                        ignore_obj=self.obj,
                        increment=self.incremental
    
                )
                if self.snap_to_grid and self.type == 'OUT':
    
                    self.location = Vector((round(loc.x),
                                            round(loc.y),
    
    
                if self.keyf8 and self.list_verts_co:
                    lloc = self.list_verts_co[-1]
                    orig, view_vec = region_2d_to_orig_and_view_vector(self.region, self.rv3d, mval)
    
                    location = intersect_point_line(lloc, orig, (orig + view_vec))
    
                    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:
    
                        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)
    
                                    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':
                    # SNAP 2D
                    snap_3d = self.location
    
                    Lsnap_3d = self.obj_matrix.inverted() * snap_3d
    
                    Snap_2d = location_3d_to_region_2d(self.region, self.rv3d, snap_3d)
    
                    if self.vector_constrain and isinstance(self.geom, bmesh.types.BMVert):  # SELECT FIRST
    
                        bpy.ops.view3d.select(location=(int(Snap_2d[0]), int(Snap_2d[1])))
                        try:
                            geom2 = self.bm.select_history[0]
    
                        except:  # IndexError or AttributeError:
    
                            geom2 = None
                    else:
                        geom2 = self.geom
                    self.vector_constrain = None
                    self.list_verts_co = draw_line(self, self.obj, self.bm, geom2, Lsnap_3d)
                    bpy.ops.ed.undo_push(message="Undo draw line*")
    
                elif event.type == 'TAB':
    
                    self.keytab = self.keytab is False
    
                    if self.keytab:
                        context.tool_settings.mesh_select_mode = (False, False, True)
                    else:
                        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_matrix.inverted() * location
    
                            self.list_verts_co = draw_line(self, self.obj, self.bm, self.geom, G_location)
                            self.length_entered = ""
                            self.vector_constrain = None
    
    
                            self.report({'INFO'}, "Operation not supported yet")
    
                elif event.type in {'RIGHTMOUSE', 'ESC'}:
                    if self.list_verts_co == [] or event.type == 'ESC':
                        bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
                        context.tool_settings.mesh_select_mode = self.select_mode
                        context.area.header_text_set()
                        context.user_preferences.view.use_rotate_around_active = self.use_rotate_around_active
                        if not self.is_editmode:
                            bpy.ops.object.editmode_toggle()
                        return {'FINISHED'}
                    else:
                        self.vector_constrain = None
                        self.list_verts = []
                        self.list_verts_co = []
                        self.list_faces = []
    
            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)
                            )
    
    
            self.modal_navigation(context, event)
            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
                create_new_obj = preferences.create_new_obj
    
                if context.mode == 'OBJECT' and \
                  (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
    
    
                # bgl.glEnable(bgl.GL_POINT_SMOOTH)
    
                self.is_editmode = bpy.context.object.data.is_editmode
                bpy.ops.object.mode_set(mode='EDIT')
                context.space_data.use_occlude_geometry = True
    
                self.scale = context.scene.unit_settings.scale_length
                self.unit_system = context.scene.unit_settings.system
                self.separate_units = context.scene.unit_settings.use_separate
                self.uinfo = get_units_info(self.scale, self.unit_system, self.separate_units)
    
    
                grid = context.scene.unit_settings.scale_length / context.space_data.grid_scale
    
                relative_scale = preferences.relative_scale
    
                self.scale = grid / relative_scale
                self.rd = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(1 / self.scale))
    
    
                incremental = preferences.incremental
                self.incremental = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(incremental))
    
                self.use_rotate_around_active = context.user_preferences.view.use_rotate_around_active
                context.user_preferences.view.use_rotate_around_active = True
    
                self.select_mode = context.tool_settings.mesh_select_mode[:]
                context.tool_settings.mesh_select_mode = (True, True, True)
    
                self.region = context.region
                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.bm = bmesh.from_edit_mesh(self.obj.data)
                self.cache = SnapCache()
    
                self.location = Vector()
                self.list_verts = []
                self.list_verts_co = []
                self.bool_update = False
                self.vector_constrain = ()
                self.keytab = False
                self.keyf8 = False
                self.type = 'OUT'
                self.len = 0
                self.length_entered = ""
                self.line_pos = 0
    
                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)
    
                self.intersect = preferences.intersect
                self.create_face = preferences.create_face
                self.outer_verts = preferences.outer_verts
                self.snap_to_grid = preferences.increments_grid
    
    
                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'}
    
    
        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"
    
            for panel in panels:
                if "bl_rna" in panel.__dict__:
                    bpy.utils.unregister_class(panel)
    
            for panel in panels:
                panel.bl_category = context.user_preferences.addons[__name__].preferences.category
                bpy.utils.register_class(panel)
    
        except Exception as e:
            print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
    
    
    class SnapAddonPreferences(AddonPreferences):
    
        # this must match the addon name, use '__package__'
        # when defining this in a submodule of a python package.
        bl_idname = __name__
    
    
                description="Intersects created line with the existing edges, "
                            "even if the lines do not intersect",
                default=True
                )
        create_new_obj = BoolProperty(
    
                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(
    
                description="Create faces defined by enclosed edges",
                default=False
                )
        outer_verts = BoolProperty(
    
                description="The vertices of the objects are not activated also snapped",
                default=True
                )