diff --git a/mesh_tools/__init__.py b/mesh_tools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5b34e42d3ab99676685cde1eb8f96ab40511358
--- /dev/null
+++ b/mesh_tools/__init__.py
@@ -0,0 +1,1165 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+# Contributed to by:
+# meta-androcto,  Hidesato Ikeya, zmj100, Gert De Roost, TrumanBlending, PKHG, #
+# Oscurart, Greg, Stanislav Blinov, komi3D, BlenderLab, Paul Marshall (brikbot), #
+# metalliandy, macouno, CoDEmanX, dustractor, Liero, lijenstina, Germano Cavalcante #
+# Pistiwique, Jimmy Hazevoet #
+
+bl_info = {
+    "name": "Edit Mesh Tools",
+    "author": "Meta-Androcto",
+    "version": (0, 3, 6),
+    "blender": (2, 80, 0),
+    "location": "View3D > Toolbar and View3D > Context Menu",
+    "warning": "",
+    "description": "Mesh modelling toolkit. Several tools to aid modelling",
+    "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
+                "Py/Scripts/Modeling/Extra_Tools",
+    "category": "Mesh",
+}
+
+# Import From Files
+if "bpy" in locals():
+    import importlib
+    importlib.reload(mesh_offset_edges)
+    importlib.reload(split_solidify)
+    importlib.reload(mesh_filletplus)
+    importlib.reload(mesh_vertex_chamfer)
+    importlib.reload(random_vertices)
+    importlib.reload(mesh_extrude_and_reshape)
+    importlib.reload(mesh_edge_roundifier)
+    importlib.reload(mesh_edgetools)
+    importlib.reload(mesh_edges_floor_plan)
+    importlib.reload(mesh_edges_length)
+    importlib.reload(pkhg_faces)
+    importlib.reload(mesh_cut_faces)
+
+else:
+    from . import mesh_offset_edges
+    from . import split_solidify
+    from . import mesh_filletplus
+    from . import mesh_vertex_chamfer
+    from . import random_vertices
+    from . import mesh_extrude_and_reshape
+    from . import mesh_edge_roundifier
+    from . import mesh_edgetools
+    from . import mesh_edges_floor_plan
+    from . import mesh_edges_length
+    from . import pkhg_faces
+    from . import mesh_cut_faces
+
+
+import bmesh
+import bpy
+import collections
+import mathutils
+import random
+from math import (
+        sin, cos, tan,
+        degrees, radians, pi,
+        )
+from random import gauss
+from mathutils import Matrix, Euler, Vector
+from bpy_extras import view3d_utils
+from bpy.types import (
+        Operator,
+        Menu,
+        Panel,
+        PropertyGroup,
+        AddonPreferences,
+        )
+from bpy.props import (
+        BoolProperty,
+        BoolVectorProperty,
+        EnumProperty,
+        FloatProperty,
+        FloatVectorProperty,
+        IntVectorProperty,
+        PointerProperty,
+        StringProperty,
+        IntProperty
+        )
+
+# ########################################
+# ##### General functions ################
+# ########################################
+
+
+# Multi extrude
+def gloc(self, r):
+    return Vector((self.offx, self.offy, self.offz))
+
+
+def vloc(self, r):
+    random.seed(self.ran + r)
+    return self.off * (1 + gauss(0, self.var1 / 3))
+
+
+def nrot(self, n):
+    return Euler((radians(self.nrotx) * n[0],
+                  radians(self.nroty) * n[1],
+                  radians(self.nrotz) * n[2]), 'XYZ')
+
+
+def vrot(self, r):
+    random.seed(self.ran + r)
+    return Euler((radians(self.rotx) + gauss(0, self.var2 / 3),
+                  radians(self.roty) + gauss(0, self.var2 / 3),
+                  radians(self.rotz) + gauss(0, self.var2 / 3)), 'XYZ')
+
+
+def vsca(self, r):
+    random.seed(self.ran + r)
+    return self.sca * (1 + gauss(0, self.var3 / 3))
+
+
+class ME_OT_MExtrude(Operator):
+    bl_idname = "object.mextrude"
+    bl_label = "Multi Extrude"
+    bl_description = ("Extrude selected Faces with Rotation,\n"
+                      "Scaling, Variation, Randomization")
+    bl_options = {"REGISTER", "UNDO", "PRESET"}
+
+    off : FloatProperty(
+            name="Offset",
+            soft_min=0.001, soft_max=10,
+            min=-100, max=100,
+            default=1.0,
+            description="Translation"
+            )
+    offx : FloatProperty(
+            name="Loc X",
+            soft_min=-10.0, soft_max=10.0,
+            min=-100.0, max=100.0,
+            default=0.0,
+            description="Global Translation X"
+            )
+    offy : FloatProperty(
+            name="Loc Y",
+            soft_min=-10.0, soft_max=10.0,
+            min=-100.0, max=100.0,
+            default=0.0,
+            description="Global Translation Y"
+            )
+    offz : FloatProperty(
+            name="Loc Z",
+            soft_min=-10.0, soft_max=10.0,
+            min=-100.0, max=100.0,
+            default=0.0,
+            description="Global Translation Z"
+            )
+    rotx : FloatProperty(
+            name="Rot X",
+            min=-85, max=85,
+            soft_min=-30, soft_max=30,
+            default=0,
+            description="X Rotation"
+            )
+    roty : FloatProperty(
+            name="Rot Y",
+            min=-85, max=85,
+            soft_min=-30,
+            soft_max=30,
+            default=0,
+            description="Y Rotation"
+            )
+    rotz : FloatProperty(
+            name="Rot Z",
+            min=-85, max=85,
+            soft_min=-30, soft_max=30,
+            default=-0,
+            description="Z Rotation"
+            )
+    nrotx : FloatProperty(
+            name="N Rot X",
+            min=-85, max=85,
+            soft_min=-30, soft_max=30,
+            default=0,
+            description="Normal X Rotation"
+            )
+    nroty : FloatProperty(
+            name="N Rot Y",
+            min=-85, max=85,
+            soft_min=-30, soft_max=30,
+            default=0,
+            description="Normal Y Rotation"
+            )
+    nrotz : FloatProperty(
+            name="N Rot Z",
+            min=-85, max=85,
+            soft_min=-30, soft_max=30,
+            default=-0,
+            description="Normal Z Rotation"
+            )
+    sca : FloatProperty(
+            name="Scale",
+            min=0.01, max=10,
+            soft_min=0.5, soft_max=1.5,
+            default=1.0,
+            description="Scaling of the selected faces after extrusion"
+            )
+    var1 : FloatProperty(
+            name="Offset Var", min=-10, max=10,
+            soft_min=-1, soft_max=1,
+            default=0,
+            description="Offset variation"
+            )
+    var2 : FloatProperty(
+            name="Rotation Var",
+            min=-10, max=10,
+            soft_min=-1, soft_max=1,
+            default=0,
+            description="Rotation variation"
+            )
+    var3 : FloatProperty(
+            name="Scale Noise",
+            min=-10, max=10,
+            soft_min=-1, soft_max=1,
+            default=0,
+            description="Scaling noise"
+            )
+    var4 : IntProperty(
+            name="Probability",
+            min=0, max=100,
+            default=100,
+            description="Probability, chance of extruding a face"
+            )
+    num : IntProperty(
+            name="Repeat",
+            min=1, max=500,
+            soft_max=100,
+            default=1,
+            description="Repetitions"
+            )
+    ran : IntProperty(
+            name="Seed",
+            min=-9999, max=9999,
+            default=0,
+            description="Seed to feed random values"
+            )
+    opt1 : BoolProperty(
+            name="Polygon coordinates",
+            default=True,
+            description="Polygon coordinates, Object coordinates"
+            )
+    opt2 : BoolProperty(
+            name="Proportional offset",
+            default=False,
+            description="Scale * Offset"
+            )
+    opt3 : BoolProperty(
+            name="Per step rotation noise",
+            default=False,
+            description="Per step rotation noise, Initial rotation noise"
+            )
+    opt4 : BoolProperty(
+            name="Per step scale noise",
+            default=False,
+            description="Per step scale noise, Initial scale noise"
+            )
+
+    @classmethod
+    def poll(cls, context):
+        obj = context.object
+        return (obj and obj.type == 'MESH')
+
+    def draw(self, context):
+        layout = self.layout
+        col = layout.column(align=True)
+        col.label(text="Transformations:")
+        col.prop(self, "off", slider=True)
+        col.prop(self, "offx", slider=True)
+        col.prop(self, "offy", slider=True)
+        col.prop(self, "offz", slider=True)
+
+        col = layout.column(align=True)
+        col.prop(self, "rotx", slider=True)
+        col.prop(self, "roty", slider=True)
+        col.prop(self, "rotz", slider=True)
+        col.prop(self, "nrotx", slider=True)
+        col.prop(self, "nroty", slider=True)
+        col.prop(self, "nrotz", slider=True)
+        col = layout.column(align=True)
+        col.prop(self, "sca", slider=True)
+
+        col = layout.column(align=True)
+        col.label(text="Variation settings:")
+        col.prop(self, "var1", slider=True)
+        col.prop(self, "var2", slider=True)
+        col.prop(self, "var3", slider=True)
+        col.prop(self, "var4", slider=True)
+        col.prop(self, "ran")
+        col = layout.column(align=False)
+        col.prop(self, 'num')
+
+        col = layout.column(align=True)
+        col.label(text="Options:")
+        col.prop(self, "opt1")
+        col.prop(self, "opt2")
+        col.prop(self, "opt3")
+        col.prop(self, "opt4")
+
+    def execute(self, context):
+        obj = bpy.context.object
+        om = obj.mode
+        bpy.context.tool_settings.mesh_select_mode = [False, False, True]
+        origin = Vector([0.0, 0.0, 0.0])
+
+        # bmesh operations
+        bpy.ops.object.mode_set()
+        bm = bmesh.new()
+        bm.from_mesh(obj.data)
+        sel = [f for f in bm.faces if f.select]
+
+        after = []
+
+        # faces loop
+        for i, of in enumerate(sel):
+            nro = nrot(self, of.normal)
+            off = vloc(self, i)
+            loc = gloc(self, i)
+            of.normal_update()
+
+            # initial rotation noise
+            if self.opt3 is False:
+                rot = vrot(self, i)
+            # initial scale noise
+            if self.opt4 is False:
+                s = vsca(self, i)
+
+            # extrusion loop
+            for r in range(self.num):
+                # random probability % for extrusions
+                if self.var4 > int(random.random() * 100):
+                    nf = of.copy()
+                    nf.normal_update()
+                    no = nf.normal.copy()
+
+                    # face/obj coordinates
+                    if self.opt1 is True:
+                        ce = nf.calc_center_bounds()
+                    else:
+                        ce = origin
+
+                    # per step rotation noise
+                    if self.opt3 is True:
+                        rot = vrot(self, i + r)
+                    # per step scale noise
+                    if self.opt4 is True:
+                        s = vsca(self, i + r)
+
+                    # proportional, scale * offset
+                    if self.opt2 is True:
+                        off = s * off
+
+                    for v in nf.verts:
+                        v.co -= ce
+                        v.co.rotate(nro)
+                        v.co.rotate(rot)
+                        v.co += ce + loc + no * off
+                        v.co = v.co.lerp(ce, 1 - s)
+
+                    # extrude code from TrumanBlending
+                    for a, b in zip(of.loops, nf.loops):
+                        sf = bm.faces.new((a.vert, a.link_loop_next.vert,
+                                           b.link_loop_next.vert, b.vert))
+                        sf.normal_update()
+                    bm.faces.remove(of)
+                    of = nf
+
+            after.append(of)
+
+        for v in bm.verts:
+            v.select = False
+        for e in bm.edges:
+            e.select = False
+
+        for f in after:
+            if f not in sel:
+                f.select = True
+            else:
+                f.select = False
+
+        bm.to_mesh(obj.data)
+        obj.data.update()
+
+        # restore user settings
+        bpy.ops.object.mode_set(mode=om)
+
+        if not len(sel):
+            self.report({"WARNING"},
+                        "No suitable Face selection found. Operation cancelled")
+            return {'CANCELLED'}
+
+        return {'FINISHED'}
+
+# Face inset fillet
+def edit_mode_out():
+    bpy.ops.object.mode_set(mode='OBJECT')
+
+
+def edit_mode_in():
+    bpy.ops.object.mode_set(mode='EDIT')
+
+
+def angle_rotation(rp, q, axis, angle):
+    # returns the vector made by the rotation of the vector q
+    # rp by angle around axis and then adds rp
+
+    return (Matrix.Rotation(angle, 3, axis) @ (q - rp)) + rp
+
+
+def face_inset_fillet(bme, face_index_list, inset_amount, distance,
+                      number_of_sides, out, radius, type_enum, kp):
+    list_del = []
+
+    for faceindex in face_index_list:
+
+        bme.faces.ensure_lookup_table()
+        # loops through the faces...
+        f = bme.faces[faceindex]
+        f.select_set(False)
+        list_del.append(f)
+        f.normal_update()
+        vertex_index_list = [v.index for v in f.verts]
+        dict_0 = {}
+        orientation_vertex_list = []
+        n = len(vertex_index_list)
+        for i in range(n):
+            # loops through the vertices
+            dict_0[i] = []
+            bme.verts.ensure_lookup_table()
+            p = (bme.verts[vertex_index_list[i]].co).copy()
+            p1 = (bme.verts[vertex_index_list[(i - 1) % n]].co).copy()
+            p2 = (bme.verts[vertex_index_list[(i + 1) % n]].co).copy()
+            # copies some vert coordinates, always the 3 around i
+            dict_0[i].append(bme.verts[vertex_index_list[i]])
+            # appends the bmesh vert of the appropriate index to the dict
+            vec1 = p - p1
+            vec2 = p - p2
+            # vectors for the other corner points to the cornerpoint
+            # corresponding to i / p
+            angle = vec1.angle(vec2)
+
+            adj = inset_amount / tan(angle * 0.5)
+            h = (adj ** 2 + inset_amount ** 2) ** 0.5
+            if round(degrees(angle)) == 180 or round(degrees(angle)) == 0.0:
+                # if the corner is a straight line...
+                # I think this creates some new points...
+                if out is True:
+                    val = ((f.normal).normalized() * inset_amount)
+                else:
+                    val = -((f.normal).normalized() * inset_amount)
+                p6 = angle_rotation(p, p + val, vec1, radians(90))
+            else:
+                # if the corner is an actual corner
+                val = ((f.normal).normalized() * h)
+                if out is True:
+                    # this -(p - (vec2.normalized() * adj))) is just the freaking axis afaik...
+                    p6 = angle_rotation(
+                                p, p + val,
+                                -(p - (vec2.normalized() * adj)),
+                                -radians(90)
+                                )
+                else:
+                    p6 = angle_rotation(
+                                p, p - val,
+                                ((p - (vec1.normalized() * adj)) - (p - (vec2.normalized() * adj))),
+                                -radians(90)
+                                )
+
+                orientation_vertex_list.append(p6)
+
+        new_inner_face = []
+        orientation_vertex_list_length = len(orientation_vertex_list)
+        ovll = orientation_vertex_list_length
+
+        for j in range(ovll):
+            q = orientation_vertex_list[j]
+            q1 = orientation_vertex_list[(j - 1) % ovll]
+            q2 = orientation_vertex_list[(j + 1) % ovll]
+            # again, these are just vectors between somewhat displaced corner vertices
+            vec1_ = q - q1
+            vec2_ = q - q2
+            ang_ = vec1_.angle(vec2_)
+
+            # the angle between them
+            if round(degrees(ang_)) == 180 or round(degrees(ang_)) == 0.0:
+                # again... if it's really a line...
+                v = bme.verts.new(q)
+                new_inner_face.append(v)
+                dict_0[j].append(v)
+            else:
+                # s.a.
+                if radius is False:
+                    h_ = distance * (1 / cos(ang_ * 0.5))
+                    d = distance
+                elif radius is True:
+                    h_ = distance / sin(ang_ * 0.5)
+                    d = distance / tan(ang_ * 0.5)
+                # max(d) is vec1_.magnitude * 0.5
+                # or vec2_.magnitude * 0.5 respectively
+
+                # only functional difference v
+                if d > vec1_.magnitude * 0.5:
+                    d = vec1_.magnitude * 0.5
+
+                if d > vec2_.magnitude * 0.5:
+                    d = vec2_.magnitude * 0.5
+                # only functional difference ^
+
+                q3 = q - (vec1_.normalized() * d)
+                q4 = q - (vec2_.normalized() * d)
+                # these are new verts somewhat offset from the corners
+                rp_ = q - ((q - ((q3 + q4) * 0.5)).normalized() * h_)
+                # reference point inside the curvature
+                axis_ = vec1_.cross(vec2_)
+                # this should really be just the face normal
+                vec3_ = rp_ - q3
+                vec4_ = rp_ - q4
+                rot_ang = vec3_.angle(vec4_)
+                cornerverts = []
+
+                for o in range(number_of_sides + 1):
+                    # this calculates the actual new vertices
+                    q5 = angle_rotation(rp_, q4, axis_, rot_ang * o / number_of_sides)
+                    v = bme.verts.new(q5)
+
+                    # creates new bmesh vertices from it
+                    bme.verts.index_update()
+
+                    dict_0[j].append(v)
+                    cornerverts.append(v)
+
+                cornerverts.reverse()
+                new_inner_face.extend(cornerverts)
+
+        if out is False:
+            f = bme.faces.new(new_inner_face)
+            f.select_set(True)
+        elif out is True and kp is True:
+            f = bme.faces.new(new_inner_face)
+            f.select_set(True)
+
+        n2_ = len(dict_0)
+        # these are the new side faces, those that don't depend on cornertype
+        for o in range(n2_):
+            list_a = dict_0[o]
+            list_b = dict_0[(o + 1) % n2_]
+            bme.faces.new([list_a[0], list_b[0], list_b[-1], list_a[1]])
+            bme.faces.index_update()
+        # cornertype 1 - ngon faces
+        if type_enum == 'opt0':
+            for k in dict_0:
+                if len(dict_0[k]) > 2:
+                    bme.faces.new(dict_0[k])
+                    bme.faces.index_update()
+        # cornertype 2 - triangulated faces
+        if type_enum == 'opt1':
+            for k_ in dict_0:
+                q_ = dict_0[k_][0]
+                dict_0[k_].pop(0)
+                n3_ = len(dict_0[k_])
+                for kk in range(n3_ - 1):
+                    bme.faces.new([dict_0[k_][kk], dict_0[k_][(kk + 1) % n3_], q_])
+                    bme.faces.index_update()
+
+    del_ = [bme.faces.remove(f) for f in list_del]
+
+    if del_:
+        del del_
+
+
+# Operator
+
+class MESH_OT_face_inset_fillet(Operator):
+    bl_idname = "mesh.face_inset_fillet"
+    bl_label = "Face Inset Fillet"
+    bl_description = ("Inset selected and Fillet (make round) the corners \n"
+                     "of the newly created Faces")
+    bl_options = {"REGISTER", "UNDO"}
+
+    # inset amount
+    inset_amount : bpy.props.FloatProperty(
+            name="Inset amount",
+            description="Define the size of the Inset relative to the selection",
+            default=0.04,
+            min=0, max=100.0,
+            step=1,
+            precision=3
+            )
+    # number of sides
+    number_of_sides : bpy.props.IntProperty(
+            name="Number of sides",
+            description="Define the roundness of the corners by specifying\n"
+                        "the subdivision count",
+            default=4,
+            min=1, max=100,
+            step=1
+            )
+    distance : bpy.props.FloatProperty(
+            name="",
+            description="Use distance or radius for corners' size calculation",
+            default=0.04,
+            min=0.00001, max=100.0,
+            step=1,
+            precision=3
+            )
+    out : bpy.props.BoolProperty(
+            name="Outside",
+            description="Inset the Faces outwards in relation to the selection\n"
+                        "Note: depending on the geometry, can give unsatisfactory results",
+            default=False
+            )
+    radius : bpy.props.BoolProperty(
+            name="Radius",
+            description="Use radius for corners' size calculation",
+            default=False
+            )
+    type_enum : bpy.props.EnumProperty(
+            items=[('opt0', "N-gon", "N-gon corners - Keep the corner Faces uncut"),
+                   ('opt1', "Triangle", "Triangulate corners")],
+            name="Corner Type",
+            default="opt0"
+            )
+    kp : bpy.props.BoolProperty(
+            name="Keep faces",
+            description="Do not delete the inside Faces\n"
+                        "Only available if the Out option is checked",
+            default=False
+            )
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.label(text="Corner Type:")
+
+        row = layout.row()
+        row.prop(self, "type_enum", text="")
+
+        row = layout.row(align=True)
+        row.prop(self, "out")
+
+        if self.out is True:
+            row.prop(self, "kp")
+
+        row = layout.row()
+        row.prop(self, "inset_amount")
+
+        row = layout.row()
+        row.prop(self, "number_of_sides")
+
+        row = layout.row()
+        row.prop(self, "radius")
+
+        row = layout.row()
+        dist_rad = "Radius" if self.radius else "Distance"
+        row.prop(self, "distance", text=dist_rad)
+
+    def execute(self, context):
+        # this really just prepares everything for the main function
+        inset_amount = self.inset_amount
+        number_of_sides = self.number_of_sides
+        distance = self.distance
+        out = self.out
+        radius = self.radius
+        type_enum = self.type_enum
+        kp = self.kp
+
+        edit_mode_out()
+        ob_act = context.active_object
+        bme = bmesh.new()
+        bme.from_mesh(ob_act.data)
+        # this
+        face_index_list = [f.index for f in bme.faces if f.select and f.is_valid]
+
+        if len(face_index_list) == 0:
+            self.report({'WARNING'},
+                        "No suitable Face selection found. Operation cancelled")
+            edit_mode_in()
+
+            return {'CANCELLED'}
+
+        elif len(face_index_list) != 0:
+            face_inset_fillet(bme, face_index_list,
+                              inset_amount, distance, number_of_sides,
+                              out, radius, type_enum, kp)
+
+        bme.to_mesh(ob_act.data)
+        edit_mode_in()
+
+        return {'FINISHED'}
+    
+# ********** Edit Multiselect **********
+class VIEW3D_MT_Edit_MultiMET(Menu):
+    bl_label = "Multi Select"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.operator_context = 'INVOKE_REGION_WIN'
+
+        layout.operator("multiedit.allselect", text="All Select Modes", icon='RESTRICT_SELECT_OFF')
+
+
+# Select Tools
+class VIEW3D_MT_Select_Vert(Menu):
+    bl_label = "Select Vert"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.operator_context = 'INVOKE_REGION_WIN'
+
+        layout.operator("multiedit.vertexselect", text="Vertex Select Mode", icon='VERTEXSEL')
+        layout.operator("multiedit.vertedgeselect", text="Vert & Edge Select", icon='EDGESEL')
+        layout.operator("multiedit.vertfaceselect", text="Vert & Face Select", icon='FACESEL')
+
+
+class VIEW3D_MT_Select_Edge(Menu):
+    bl_label = "Select Edge"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.operator_context = 'INVOKE_REGION_WIN'
+
+        layout.operator("multiedit.edgeselect", text="Edge Select Mode", icon='EDGESEL')
+        layout.operator("multiedit.vertedgeselect", text="Edge & Vert Select", icon='VERTEXSEL')
+        layout.operator("multiedit.edgefaceselect", text="Edge & Face Select", icon='FACESEL')
+
+
+class VIEW3D_MT_Select_Face(Menu):
+    bl_label = "Select Face"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.operator_context = 'INVOKE_REGION_WIN'
+
+        layout.operator("multiedit.faceselect", text="Face Select Mode", icon='FACESEL')
+        layout.operator("multiedit.vertfaceselect", text="Face & Vert Select", icon='VERTEXSEL')
+        layout.operator("multiedit.edgefaceselect", text="Face & Edge Select", icon='EDGESEL')
+
+
+ # multiple edit select modes.
+class VIEW3D_OT_multieditvertex(Operator):
+    bl_idname = "multiedit.vertexselect"
+    bl_label = "Vertex Mode"
+    bl_description = "Vert Select Mode On"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def execute(self, context):
+        if context.object.mode != "EDIT":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
+        if bpy.ops.mesh.select_mode != "EDGE, FACE":
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
+            return {'FINISHED'}
+
+
+class VIEW3D_OT_multieditedge(Operator):
+    bl_idname = "multiedit.edgeselect"
+    bl_label = "Edge Mode"
+    bl_description = "Edge Select Mode On"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def execute(self, context):
+        if context.object.mode != "EDIT":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
+        if bpy.ops.mesh.select_mode != "VERT, FACE":
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
+            return {'FINISHED'}
+
+
+class VIEW3D_OT_multieditface(Operator):
+    bl_idname = "multiedit.faceselect"
+    bl_label = "Multiedit Face"
+    bl_description = "Face Select Mode On"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def execute(self, context):
+        if context.object.mode != "EDIT":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
+        if bpy.ops.mesh.select_mode != "VERT, EDGE":
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
+            return {'FINISHED'}
+
+class VIEW3D_OT_multieditvertedge(Operator):
+    bl_idname = "multiedit.vertedgeselect"
+    bl_label = "Multiedit Face"
+    bl_description = "Vert & Edge Select Modes On"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def execute(self, context):
+        if context.object.mode != "EDIT":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
+        if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
+            bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='EDGE')
+            return {'FINISHED'}
+
+class VIEW3D_OT_multieditvertface(Operator):
+    bl_idname = "multiedit.vertfaceselect"
+    bl_label = "Multiedit Face"
+    bl_description = "Vert & Face Select Modes On"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def execute(self, context):
+        if context.object.mode != "EDIT":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
+        if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
+            bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='FACE')
+            return {'FINISHED'}
+
+
+class VIEW3D_OT_multieditedgeface(Operator):
+    bl_idname = "multiedit.edgefaceselect"
+    bl_label = "Mode Face Edge"
+    bl_description = "Edge & Face Select Modes On"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def execute(self, context):
+        if context.object.mode != "EDIT":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
+        if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
+            bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='FACE')
+            return {'FINISHED'}
+
+
+class VIEW3D_OT_multieditall(Operator):
+    bl_idname = "multiedit.allselect"
+    bl_label = "All Edit Select Modes"
+    bl_description = "Vert & Edge & Face Select Modes On"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def execute(self, context):
+        if context.object.mode != "EDIT":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
+        if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
+            bpy.ops.object.mode_set(mode="EDIT")
+            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
+            bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='EDGE')
+            bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='FACE')
+            return {'FINISHED'}
+
+
+# ########################################
+# ##### GUI and registration #############
+# ########################################
+
+# menu containing all tools
+class VIEW3D_MT_edit_mesh_tools(Menu):
+    bl_label = "Mesh Tools"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.operator("mesh.remove_doubles")
+        layout.operator("mesh.dissolve_limited")
+        layout.operator("mesh.flip_normals")
+        props = layout.operator("mesh.quads_convert_to_tris")
+        props.quad_method = props.ngon_method = 'BEAUTY'
+        layout.operator("mesh.tris_convert_to_quads")
+        layout.operator('mesh.vertex_chamfer', text="Vertex Chamfer")
+        layout.operator("mesh.bevel", text="Bevel Vertices").vertex_only = True
+        layout.operator('mesh.offset_edges', text="Offset Edges")
+        layout.operator('mesh.fillet_plus', text="Fillet Edges")
+        layout.operator("mesh.face_inset_fillet",
+                            text="Face Inset Fillet")
+        layout.operator("mesh.extrude_reshape",
+                        text="Push/Pull Faces")
+        layout.operator("object.mextrude",
+                        text="Multi Extrude")
+        layout.operator('mesh.split_solidify', text="Split Solidify")
+
+            
+
+# panel containing all tools
+class VIEW3D_PT_edit_mesh_tools(Panel):
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'UI'
+    bl_category = 'Edit'
+    bl_context = "mesh_edit"
+    bl_label = "Mesh Tools"
+    bl_options = {'DEFAULT_CLOSED'}
+
+    def draw(self, context):
+        layout = self.layout
+        col = layout.column(align=True)
+        et = context.window_manager.edittools
+
+        # vert - first line
+        split = col.split(factor=0.80, align=True)
+        if et.display_vert:
+            split.prop(et, "display_vert", text="Vert Tools", icon='DOWNARROW_HLT')
+        else:
+            split.prop(et, "display_vert", text="Vert tools", icon='RIGHTARROW')
+        split.menu("VIEW3D_MT_Select_Vert", text="", icon='VERTEXSEL')
+        # vert - settings
+        if et.display_vert:
+            box = col.column(align=True).box().column()
+            col_top = box.column(align=True)
+            row = col_top.row(align=True)
+            row.operator('mesh.vertex_chamfer', text="Vertex Chamfer")
+            row = col_top.row(align=True)
+            row.operator("mesh.extrude_vertices_move", text="Extrude Vertices")
+            row = col_top.row(align=True)
+            row.operator("mesh.random_vertices", text="Random Vertices")
+            row = col_top.row(align=True)
+            row.operator("mesh.bevel", text="Bevel Vertices").vertex_only = True
+
+        # edge - first line
+        split = col.split(factor=0.80, align=True)
+        if et.display_edge:
+            split.prop(et, "display_edge", text="Edge Tools", icon='DOWNARROW_HLT')
+        else:
+            split.prop(et, "display_edge", text="Edge Tools", icon='RIGHTARROW')
+        split.menu("VIEW3D_MT_Select_Edge", text="", icon='EDGESEL')
+        # Edge - settings
+        if et.display_edge:
+            box = col.column(align=True).box().column()
+            col_top = box.column(align=True)
+            row = col_top.row(align=True)
+            row.operator('mesh.offset_edges', text="Offset Edges")
+            row = col_top.row(align=True)
+            row.operator('mesh.fillet_plus', text="Fillet Edges")
+            row = col_top.row(align=True)
+            row.operator('mesh.edge_roundifier', text="Edge Roundify")
+            row = col_top.row(align=True)
+            row.operator('object.mesh_edge_length_set', text="Set Edge Length")
+            row = col_top.row(align=True)
+            row.operator('mesh.edges_floor_plan', text="Edges Floor Plan")
+            row = col_top.row(align=True)
+            row.operator("mesh.extrude_edges_move", text="Extrude Edges")
+            row = col_top.row(align=True)
+            row.operator("mesh.bevel", text="Bevel Edges").vertex_only = False
+
+        # face - first line
+        split = col.split(factor=0.80, align=True)
+        if et.display_face:
+            split.prop(et, "display_face", text="Face Tools", icon='DOWNARROW_HLT')
+        else:
+            split.prop(et, "display_face", text="Face Tools", icon='RIGHTARROW')
+        split.menu("VIEW3D_MT_Select_Face", text="", icon='FACESEL')
+        # face - settings
+        if et.display_face:
+            box = col.column(align=True).box().column()
+            col_top = box.column(align=True)
+            row = col_top.row(align=True)
+            row.operator("mesh.face_inset_fillet",
+                            text="Face Inset Fillet")
+            row = col_top.row(align=True)
+            row.operator("mesh.ext_cut_faces",
+                            text="Cut Faces")
+            row = col_top.row(align=True)
+            row.operator("mesh.extrude_reshape",
+                            text="Push/Pull Faces")
+            row = col_top.row(align=True)
+            row.operator("object.mextrude",
+                            text="Multi Extrude")
+            row = col_top.row(align=True)
+            row.operator('mesh.split_solidify', text="Split Solidify")
+            row = col_top.row(align=True)
+            row.operator('mesh.add_faces_to_object', text="Face Shape")
+            row = col_top.row(align=True)
+            row.operator("mesh.inset")
+            row = col_top.row(align=True)
+            row.operator("mesh.extrude_faces_move", text="Extrude Individual Faces")
+
+        # util - first line
+        split = col.split(factor=0.80, align=True)
+        if et.display_util:
+            split.prop(et, "display_util", text="Utility Tools", icon='DOWNARROW_HLT')
+        else:
+            split.prop(et, "display_util", text="Utility Tools", icon='RIGHTARROW')
+        split.menu("VIEW3D_MT_Edit_MultiMET", text="", icon='RESTRICT_SELECT_OFF')
+        # util - settings
+        if et.display_util:
+            box = col.column(align=True).box().column()
+            col_top = box.column(align=True)
+            row = col_top.row(align=True)
+            row.operator("mesh.subdivide")
+            row = col_top.row(align=True)
+            row.operator("mesh.remove_doubles")
+            row = col_top.row(align=True)
+            row.operator("mesh.dissolve_limited")
+            row = col_top.row(align=True)
+            row.operator("mesh.flip_normals")
+            row = col_top.row(align=True)
+            props = row.operator("mesh.quads_convert_to_tris")
+            props.quad_method = props.ngon_method = 'BEAUTY'
+            row = col_top.row(align=True)
+            row.operator("mesh.tris_convert_to_quads")
+            
+# property group containing all properties for the gui in the panel
+class EditToolsProps(PropertyGroup):
+    """
+    Fake module like class
+    bpy.context.window_manager.edittools
+    """
+    # general display properties
+    display_vert: BoolProperty(
+        name="Bridge settings",
+        description="Display settings of the Vert tool",
+        default=False
+        )
+    display_edge: BoolProperty(
+        name="Edge settings",
+        description="Display settings of the Edge tool",
+        default=False
+        )
+    display_face: BoolProperty(
+        name="Face settings",
+        description="Display settings of the Face tool",
+        default=False
+        )
+    display_util: BoolProperty(
+        name="Face settings",
+        description="Display settings of the Face tool",
+        default=False
+        )
+
+# draw function for integration in menus
+def menu_func(self, context):
+    self.layout.menu("VIEW3D_MT_edit_mesh_tools")
+    self.layout.separator()
+
+# Add-ons Preferences Update Panel
+
+# Define Panel classes for updating
+panels = (
+        VIEW3D_PT_edit_mesh_tools,
+        )
+
+
+def update_panel(self, context):
+    message = "LoopTools: Updating Panel locations has failed"
+    try:
+        for panel in panels:
+            if "bl_rna" in panel.__dict__:
+                bpy.utils.unregister_class(panel)
+
+        for panel in panels:
+            panel.bl_category = context.preferences.addons[__name__].preferences.category
+            bpy.utils.register_class(panel)
+
+    except Exception as e:
+        print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
+        pass
+
+
+class EditToolsPreferences(AddonPreferences):
+    # this must match the addon name, use '__package__'
+    # when defining this in a submodule of a python package.
+    bl_idname = __name__
+
+    category: StringProperty(
+            name="Tab Category",
+            description="Choose a name for the category of the panel",
+            default="Edit",
+            update=update_panel
+            )
+
+    def draw(self, context):
+        layout = self.layout
+
+        row = layout.row()
+        col = row.column()
+        col.label(text="Tab Category:")
+        col.prop(self, "category", text="")
+
+
+# define classes for registration
+classes = (
+    VIEW3D_MT_edit_mesh_tools,
+    VIEW3D_PT_edit_mesh_tools,
+    VIEW3D_MT_Edit_MultiMET,
+    VIEW3D_MT_Select_Vert,
+    VIEW3D_MT_Select_Edge,
+    VIEW3D_MT_Select_Face,
+    EditToolsProps,
+    EditToolsPreferences,
+    MESH_OT_face_inset_fillet,
+    ME_OT_MExtrude,
+    VIEW3D_OT_multieditvertex,
+    VIEW3D_OT_multieditedge,
+    VIEW3D_OT_multieditface,
+    VIEW3D_OT_multieditvertedge,
+    VIEW3D_OT_multieditvertface,
+    VIEW3D_OT_multieditedgeface,
+    VIEW3D_OT_multieditall
+    )
+
+
+# registering and menu integration
+def register():
+    for cls in classes:
+        bpy.utils.register_class(cls)
+    bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_func)
+    bpy.types.WindowManager.edittools = PointerProperty(type=EditToolsProps)
+    update_panel(None, bpy.context)
+
+    mesh_filletplus.register()
+    mesh_offset_edges.register()
+    split_solidify.register()
+    mesh_vertex_chamfer.register()
+    random_vertices.register()
+    mesh_extrude_and_reshape.register()
+    mesh_edge_roundifier.register()
+    mesh_edgetools.register()
+    mesh_edges_floor_plan.register()
+    mesh_edges_length.register()
+    pkhg_faces.register()
+    mesh_cut_faces.register()
+
+
+# unregistering and removing menus
+def unregister():
+    for cls in reversed(classes):
+        bpy.utils.unregister_class(cls)
+    bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_func)
+    try:
+        del bpy.types.WindowManager.edittools
+    except Exception as e:
+        print('unregister fail:\n', e)
+        pass
+
+    mesh_filletplus.unregister()
+    mesh_offset_edges.unregister()
+    split_solidify.unregister()
+    mesh_vertex_chamfer.unregister()
+    random_vertices.unregister()
+    mesh_extrude_and_reshape.unregister()
+    mesh_edge_roundifier.unregister()
+    mesh_edgetools.unregister()
+    mesh_edges_floor_plan.unregister()
+    mesh_edges_length.unregister()
+    pkhg_faces.unregister()
+    mesh_cut_faces.unregister()
+
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/face_inset_fillet.py b/mesh_tools/face_inset_fillet.py
new file mode 100644
index 0000000000000000000000000000000000000000..8af709c1812ee5a77d919bb8146f21e41d009795
--- /dev/null
+++ b/mesh_tools/face_inset_fillet.py
@@ -0,0 +1,335 @@
+# -*- coding: utf-8 -*-
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# based completely on addon by zmj100
+# added some distance limits to prevent overlap - max12345
+
+
+import bpy
+import bmesh
+from bpy.types import Operator
+from bpy.props import (
+        FloatProperty,
+        IntProperty,
+        BoolProperty,
+        EnumProperty,
+        )
+from math import (
+        sin, cos, tan,
+        degrees, radians,
+        )
+from mathutils import Matrix
+
+
+def edit_mode_out():
+    bpy.ops.object.mode_set(mode='OBJECT')
+
+
+def edit_mode_in():
+    bpy.ops.object.mode_set(mode='EDIT')
+
+
+def angle_rotation(rp, q, axis, angle):
+    # returns the vector made by the rotation of the vector q
+    # rp by angle around axis and then adds rp
+
+    return (Matrix.Rotation(angle, 3, axis) * (q - rp)) + rp
+
+
+def face_inset_fillet(bme, face_index_list, inset_amount, distance,
+                      number_of_sides, out, radius, type_enum, kp):
+    list_del = []
+
+    for faceindex in face_index_list:
+
+        bme.faces.ensure_lookup_table()
+        # loops through the faces...
+        f = bme.faces[faceindex]
+        f.select_set(False)
+        list_del.append(f)
+        f.normal_update()
+        vertex_index_list = [v.index for v in f.verts]
+        dict_0 = {}
+        orientation_vertex_list = []
+        n = len(vertex_index_list)
+        for i in range(n):
+            # loops through the vertices
+            dict_0[i] = []
+            bme.verts.ensure_lookup_table()
+            p = (bme.verts[vertex_index_list[i]].co).copy()
+            p1 = (bme.verts[vertex_index_list[(i - 1) % n]].co).copy()
+            p2 = (bme.verts[vertex_index_list[(i + 1) % n]].co).copy()
+            # copies some vert coordinates, always the 3 around i
+            dict_0[i].append(bme.verts[vertex_index_list[i]])
+            # appends the bmesh vert of the appropriate index to the dict
+            vec1 = p - p1
+            vec2 = p - p2
+            # vectors for the other corner points to the cornerpoint
+            # corresponding to i / p
+            angle = vec1.angle(vec2)
+
+            adj = inset_amount / tan(angle * 0.5)
+            h = (adj ** 2 + inset_amount ** 2) ** 0.5
+            if round(degrees(angle)) == 180 or round(degrees(angle)) == 0.0:
+                # if the corner is a straight line...
+                # I think this creates some new points...
+                if out is True:
+                    val = ((f.normal).normalized() * inset_amount)
+                else:
+                    val = -((f.normal).normalized() * inset_amount)
+                p6 = angle_rotation(p, p + val, vec1, radians(90))
+            else:
+                # if the corner is an actual corner
+                val = ((f.normal).normalized() * h)
+                if out is True:
+                    # this -(p - (vec2.normalized() * adj))) is just the freaking axis afaik...
+                    p6 = angle_rotation(
+                                p, p + val,
+                                -(p - (vec2.normalized() * adj)),
+                                -radians(90)
+                                )
+                else:
+                    p6 = angle_rotation(
+                                p, p - val,
+                                ((p - (vec1.normalized() * adj)) - (p - (vec2.normalized() * adj))),
+                                -radians(90)
+                                )
+
+                orientation_vertex_list.append(p6)
+
+        new_inner_face = []
+        orientation_vertex_list_length = len(orientation_vertex_list)
+        ovll = orientation_vertex_list_length
+
+        for j in range(ovll):
+            q = orientation_vertex_list[j]
+            q1 = orientation_vertex_list[(j - 1) % ovll]
+            q2 = orientation_vertex_list[(j + 1) % ovll]
+            # again, these are just vectors between somewhat displaced corner vertices
+            vec1_ = q - q1
+            vec2_ = q - q2
+            ang_ = vec1_.angle(vec2_)
+
+            # the angle between them
+            if round(degrees(ang_)) == 180 or round(degrees(ang_)) == 0.0:
+                # again... if it's really a line...
+                v = bme.verts.new(q)
+                new_inner_face.append(v)
+                dict_0[j].append(v)
+            else:
+                # s.a.
+                if radius is False:
+                    h_ = distance * (1 / cos(ang_ * 0.5))
+                    d = distance
+                elif radius is True:
+                    h_ = distance / sin(ang_ * 0.5)
+                    d = distance / tan(ang_ * 0.5)
+                # max(d) is vec1_.magnitude * 0.5
+                # or vec2_.magnitude * 0.5 respectively
+
+                # only functional difference v
+                if d > vec1_.magnitude * 0.5:
+                    d = vec1_.magnitude * 0.5
+
+                if d > vec2_.magnitude * 0.5:
+                    d = vec2_.magnitude * 0.5
+                # only functional difference ^
+
+                q3 = q - (vec1_.normalized() * d)
+                q4 = q - (vec2_.normalized() * d)
+                # these are new verts somewhat offset from the corners
+                rp_ = q - ((q - ((q3 + q4) * 0.5)).normalized() * h_)
+                # reference point inside the curvature
+                axis_ = vec1_.cross(vec2_)
+                # this should really be just the face normal
+                vec3_ = rp_ - q3
+                vec4_ = rp_ - q4
+                rot_ang = vec3_.angle(vec4_)
+                cornerverts = []
+
+                for o in range(number_of_sides + 1):
+                    # this calculates the actual new vertices
+                    q5 = angle_rotation(rp_, q4, axis_, rot_ang * o / number_of_sides)
+                    v = bme.verts.new(q5)
+
+                    # creates new bmesh vertices from it
+                    bme.verts.index_update()
+
+                    dict_0[j].append(v)
+                    cornerverts.append(v)
+
+                cornerverts.reverse()
+                new_inner_face.extend(cornerverts)
+
+        if out is False:
+            f = bme.faces.new(new_inner_face)
+            f.select_set(True)
+        elif out is True and kp is True:
+            f = bme.faces.new(new_inner_face)
+            f.select_set(True)
+
+        n2_ = len(dict_0)
+        # these are the new side faces, those that don't depend on cornertype
+        for o in range(n2_):
+            list_a = dict_0[o]
+            list_b = dict_0[(o + 1) % n2_]
+            bme.faces.new([list_a[0], list_b[0], list_b[-1], list_a[1]])
+            bme.faces.index_update()
+        # cornertype 1 - ngon faces
+        if type_enum == 'opt0':
+            for k in dict_0:
+                if len(dict_0[k]) > 2:
+                    bme.faces.new(dict_0[k])
+                    bme.faces.index_update()
+        # cornertype 2 - triangulated faces
+        if type_enum == 'opt1':
+            for k_ in dict_0:
+                q_ = dict_0[k_][0]
+                dict_0[k_].pop(0)
+                n3_ = len(dict_0[k_])
+                for kk in range(n3_ - 1):
+                    bme.faces.new([dict_0[k_][kk], dict_0[k_][(kk + 1) % n3_], q_])
+                    bme.faces.index_update()
+
+    del_ = [bme.faces.remove(f) for f in list_del]
+
+    if del_:
+        del del_
+
+
+# Operator
+
+class MESH_OT_face_inset_fillet(Operator):
+    bl_idname = "mesh.face_inset_fillet"
+    bl_label = "Face Inset Fillet"
+    bl_description = ("Inset selected and Fillet (make round) the corners \n"
+                     "of the newly created Faces")
+    bl_options = {"REGISTER", "UNDO"}
+
+    # inset amount
+    inset_amount: FloatProperty(
+            name="Inset amount",
+            description="Define the size of the Inset relative to the selection",
+            default=0.04,
+            min=0, max=100.0,
+            step=1,
+            precision=3
+            )
+    # number of sides
+    number_of_sides: IntProperty(
+            name="Number of sides",
+            description="Define the roundness of the corners by specifying\n"
+                        "the subdivision count",
+            default=4,
+            min=1, max=100,
+            step=1
+            )
+    distance: FloatProperty(
+            name="",
+            description="Use distance or radius for corners' size calculation",
+            default=0.04,
+            min=0.00001, max=100.0,
+            step=1,
+            precision=3
+            )
+    out: BoolProperty(
+            name="Outside",
+            description="Inset the Faces outwards in relation to the selection\n"
+                        "Note: depending on the geometry, can give unsatisfactory results",
+            default=False
+            )
+    radius: BoolProperty(
+            name="Radius",
+            description="Use radius for corners' size calculation",
+            default=False
+            )
+    type_enum: EnumProperty(
+            items=(('opt0', "N-gon", "N-gon corners - Keep the corner Faces uncut"),
+                   ('opt1', "Triangle", "Triangulate corners")),
+            name="Corner Type",
+            default="opt0"
+            )
+    kp: BoolProperty(
+            name="Keep faces",
+            description="Do not delete the inside Faces\n"
+                        "Only available if the Out option is checked",
+            default=False
+            )
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.label(text="Corner Type:")
+
+        row = layout.row()
+        row.prop(self, "type_enum", text="")
+
+        row = layout.row(align=True)
+        row.prop(self, "out")
+
+        if self.out is True:
+            row.prop(self, "kp")
+
+        row = layout.row()
+        row.prop(self, "inset_amount")
+
+        row = layout.row()
+        row.prop(self, "number_of_sides")
+
+        row = layout.row()
+        row.prop(self, "radius")
+
+        row = layout.row()
+        dist_rad = "Radius" if self.radius else "Distance"
+        row.prop(self, "distance", text=dist_rad)
+
+    def execute(self, context):
+        # this really just prepares everything for the main function
+        inset_amount = self.inset_amount
+        number_of_sides = self.number_of_sides
+        distance = self.distance
+        out = self.out
+        radius = self.radius
+        type_enum = self.type_enum
+        kp = self.kp
+
+        edit_mode_out()
+        ob_act = context.active_object
+        bme = bmesh.new()
+        bme.from_mesh(ob_act.data)
+        # this
+        face_index_list = [f.index for f in bme.faces if f.select and f.is_valid]
+
+        if len(face_index_list) == 0:
+            self.report({'WARNING'},
+                        "No suitable Face selection found. Operation cancelled")
+            edit_mode_in()
+
+            return {'CANCELLED'}
+
+        elif len(face_index_list) != 0:
+            face_inset_fillet(bme, face_index_list,
+                              inset_amount, distance, number_of_sides,
+                              out, radius, type_enum, kp)
+
+        bme.to_mesh(ob_act.data)
+        edit_mode_in()
+
+        return {'FINISHED'}
diff --git a/mesh_tools/mesh_cut_faces.py b/mesh_tools/mesh_cut_faces.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5297c9fa5b9924a7051dda455e5ca2e97590982
--- /dev/null
+++ b/mesh_tools/mesh_cut_faces.py
@@ -0,0 +1,241 @@
+bl_info = {
+    "name" : "Cut Faces",
+    "author" : "Stanislav Blinov",
+    "version" : (1, 0, 0),
+    "blender" : (2, 80, 0),
+    "description" : "Cut Faces and Deselect Boundary operators",
+    "category" : "Mesh",}
+
+import bpy
+import bmesh
+
+def bmesh_from_object(object):
+    mesh = object.data
+    if object.mode == 'EDIT':
+        bm = bmesh.from_edit_mesh(mesh)
+    else:
+        bm = bmesh.new()
+        bm.from_mesh(mesh)
+    return bm
+
+def bmesh_release(bm, object):
+    mesh = object.data
+    bm.select_flush_mode()
+    if object.mode == 'EDIT':
+        bmesh.update_edit_mesh(mesh, True)
+    else:
+        bm.to_mesh(mesh)
+        bm.free()
+
+def calc_face(face, keep_caps=True):
+
+    assert face.tag
+
+    def radial_loops(loop):
+        next = loop.link_loop_radial_next
+        while next != loop:
+            result, next = next, next.link_loop_radial_next
+            yield result
+
+    result = []
+
+    face.tag = False
+    selected = []
+    to_select = []
+    for loop in face.loops:
+        self_selected = False
+        # Iterate over selected adjacent faces
+        for radial_loop in filter(lambda l: l.face.select, radial_loops(loop)):
+            # Tag the edge if no other face done so already
+            if not loop.edge.tag:
+                loop.edge.tag = True
+                self_selected = True
+
+            adjacent_face = radial_loop.face
+            # Only walk adjacent face if current face tagged the edge
+            if adjacent_face.tag and self_selected:
+                result += calc_face(adjacent_face, keep_caps)
+
+        if loop.edge.tag:
+            (selected, to_select)[self_selected].append(loop)
+
+    for loop in to_select:
+        result.append(loop.edge)
+        selected.append(loop)
+
+    # Select opposite edge in quads
+    if keep_caps and len(selected) == 1 and len(face.verts) == 4:
+        result.append(selected[0].link_loop_next.link_loop_next.edge)
+
+    return result
+
+def get_edge_rings(bm, keep_caps=True):
+
+    def tag_face(face):
+        if face.select:
+            face.tag = True
+            for edge in face.edges: edge.tag = False
+        return face.select
+
+    # fetch selected faces while setting up tags
+    selected_faces = [ f for f in bm.faces if tag_face(f) ]
+
+    edges = []
+
+    try:
+        # generate a list of edges to select:
+        # traversing only tagged faces, since calc_face can walk and untag islands
+        for face in filter(lambda f: f.tag, selected_faces): edges += calc_face(face, keep_caps)
+    finally:
+        # housekeeping: clear tags
+        for face in selected_faces:
+            face.tag = False
+            for edge in face.edges: edge.tag = False
+
+    return edges
+
+class MESH_xOT_deselect_boundary(bpy.types.Operator):
+    """Deselect boundary edges of selected faces"""
+    bl_idname = "mesh.ext_deselect_boundary"
+    bl_label = "Deselect Boundary"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    keep_cap_edges: bpy.props.BoolProperty(
+        name        = "Keep Cap Edges",
+        description = "Keep quad strip cap edges selected",
+        default     = False)
+
+    @classmethod
+    def poll(cls, context):
+        active_object = context.active_object
+        return active_object and active_object.type == 'MESH' and active_object.mode == 'EDIT'
+
+    def execute(self, context):
+        object = context.active_object
+        bm = bmesh_from_object(object)
+
+        try:
+            edges = get_edge_rings(bm, keep_caps = self.keep_cap_edges)
+            if not edges:
+                self.report({'WARNING'}, "No suitable selection found")
+                return {'CANCELLED'}
+
+            bpy.ops.mesh.select_all(action='DESELECT')
+            bm.select_mode = {'EDGE'}
+
+            for edge in edges:
+                edge.select = True
+            context.tool_settings.mesh_select_mode[:] = False, True, False
+
+        finally:
+            bmesh_release(bm, object)
+
+        return {'FINISHED'}
+
+class MESH_xOT_cut_faces(bpy.types.Operator):
+    """Cut selected faces, connecting through their adjacent edges"""
+    bl_idname = "mesh.ext_cut_faces"
+    bl_label = "Cut Faces"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    # from bmesh_operators.h
+    INNERVERT    = 0
+    PATH         = 1
+    FAN          = 2
+    STRAIGHT_CUT = 3
+
+    num_cuts: bpy.props.IntProperty(
+        name    = "Number of Cuts",
+        default = 1,
+        min     = 1,
+        max     = 100,
+        subtype = 'UNSIGNED')
+
+    use_single_edge: bpy.props.BoolProperty(
+        name        = "Quad/Tri Mode",
+        description = "Cut boundary faces",
+        default     = False)
+
+    corner_type: bpy.props.EnumProperty(
+        items = [('INNER_VERT', "Inner Vert", ""),
+                 ('PATH', "Path", ""),
+                 ('FAN', "Fan", ""),
+                 ('STRAIGHT_CUT', "Straight Cut", ""),],
+        name = "Quad Corner Type",
+        description = "How to subdivide quad corners",
+        default = 'STRAIGHT_CUT')
+
+    use_grid_fill: bpy.props.BoolProperty(
+        name        = "Use Grid Fill",
+        description = "Fill fully enclosed faces with a grid",
+        default     = True)
+
+    @classmethod
+    def poll(cls, context):
+        active_object = context.active_object
+        return active_object and active_object.type == 'MESH' and active_object.mode == 'EDIT'
+
+    def cut_edges(self, context):
+        object = context.active_object
+        bm = bmesh_from_object(object)
+
+        try:
+            edges = get_edge_rings(bm, keep_caps = True)
+            if not edges:
+                self.report({'WARNING'}, "No suitable selection found")
+                return False
+
+            result = bmesh.ops.subdivide_edges(
+                bm,
+                edges = edges,
+                cuts = int(self.num_cuts),
+                use_grid_fill = bool(self.use_grid_fill),
+                use_single_edge = bool(self.use_single_edge),
+                quad_corner_type = str(self.corner_type))
+
+            bpy.ops.mesh.select_all(action='DESELECT')
+            bm.select_mode = {'EDGE'}
+
+            inner = result['geom_inner']
+            for edge in filter(lambda e: isinstance(e, bmesh.types.BMEdge), inner):
+                edge.select = True
+
+        finally:
+            bmesh_release(bm, object)
+
+        return True
+
+    def execute(self, context):
+
+        if not self.cut_edges(context):
+            return {'CANCELLED'}
+
+        context.tool_settings.mesh_select_mode[:] = False, True, False
+        # Try to select all possible loops
+        bpy.ops.mesh.loop_multi_select(ring=False)
+        return {'FINISHED'}
+
+def menu_deselect_boundary(self, context):
+    self.layout.operator(MESH_xOT_deselect_boundary.bl_idname)
+
+def menu_cut_faces(self, context):
+    self.layout.operator(MESH_xOT_cut_faces.bl_idname)
+
+def register():
+    bpy.utils.register_class(MESH_xOT_deselect_boundary)
+    bpy.utils.register_class(MESH_xOT_cut_faces)
+
+    if __name__ != "__main__":
+        bpy.types.VIEW3D_MT_select_edit_mesh.append(menu_deselect_boundary)
+        bpy.types.VIEW3D_MT_edit_mesh_faces.append(menu_cut_faces)
+
+def unregister():
+    bpy.utils.unregister_class(MESH_xOT_deselect_boundary)
+    bpy.utils.unregister_class(MESH_xOT_cut_faces)
+
+    if __name__ != "__main__":
+        bpy.types.VIEW3D_MT_select_edit_mesh.remove(menu_deselect_boundary)
+        bpy.types.VIEW3D_MT_edit_mesh_faces.remove(menu_cut_faces)
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/mesh_edge_roundifier.py b/mesh_tools/mesh_edge_roundifier.py
new file mode 100644
index 0000000000000000000000000000000000000000..704a260d3c5682f09475431dbdceb7487bf6bae3
--- /dev/null
+++ b/mesh_tools/mesh_edge_roundifier.py
@@ -0,0 +1,1397 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+    "name": "Edge Roundifier",
+    "category": "Mesh",
+    "author": "Piotr Komisarczyk (komi3D), PKHG",
+    "version": (1, 0, 2),
+    "blender": (2, 80, 0),
+    "location": "SPACE > Edge Roundifier or CTRL-E > "
+                "Edge Roundifier or Tools > Addons > Edge Roundifier",
+    "description": "Mesh editing script allowing edge rounding",
+    "wiki_url": "",
+    "category": "Mesh"
+}
+
+import bpy
+import bmesh
+from bpy.types import Operator
+from bpy.props import (
+        BoolProperty,
+        FloatProperty,
+        EnumProperty,
+        IntProperty,
+        )
+from math import (
+        sqrt, acos, pi,
+        radians, degrees, sin,
+        )
+from mathutils import (
+        Vector, Euler,
+        Quaternion,
+        )
+
+# CONSTANTS
+two_pi = 2 * pi
+XY = "XY"
+XZ = "XZ"
+YZ = "YZ"
+SPIN_END_THRESHOLD = 0.001
+LINE_TOLERANCE = 0.0001
+d_XABS_YABS = False
+d_Edge_Info = False
+d_Plane = False
+d_Radius_Angle = False
+d_Roots = False
+d_RefObject = False
+d_LineAB = False
+d_Selected_edges = False
+d_Rotate_Around_Spin_Center = False
+
+# Enable debug prints
+DEBUG = False
+
+
+# for debugging PKHG #
+def debugPrintNew(debugs, *text):
+    if DEBUG and debugs:
+        tmp = [el for el in text]
+        for row in tmp:
+            print(row)
+
+
+# Geometry and math calculation methods #
+
+class CalculationHelper:
+
+    def __init__(self):
+        """
+        Constructor
+        """
+    def getLineCoefficientsPerpendicularToVectorInPoint(self, point, vector, plane):
+        x, y, z = point
+        xVector, yVector, zVector = vector
+        destinationPoint = (x + yVector, y - xVector, z)
+        if plane == 'YZ':
+            destinationPoint = (x, y + zVector, z - yVector)
+        if plane == 'XZ':
+            destinationPoint = (x + zVector, y, z - xVector)
+        return self.getCoefficientsForLineThrough2Points(point, destinationPoint, plane)
+
+    def getQuadraticRoots(self, coef):
+        if len(coef) != 3:
+            return None  # Replaced NaN with None
+        else:
+            a, b, c = coef
+            delta = b ** 2 - 4 * a * c
+            if delta == 0:
+                x = -b / (2 * a)
+                return (x, x)
+            elif delta < 0:
+                return None
+            else:
+                x1 = (-b - sqrt(delta)) / (2 * a)
+                x2 = (-b + sqrt(delta)) / (2 * a)
+                return (x1, x2)
+
+    def getCoefficientsForLineThrough2Points(self, point1, point2, plane):
+        x1, y1, z1 = point1
+        x2, y2, z2 = point2
+
+        # mapping x1,x2, y1,y2 to proper values based on plane
+        if plane == YZ:
+            x1 = y1
+            x2 = y2
+            y1 = z1
+            y2 = z2
+        if plane == XZ:
+            y1 = z1
+            y2 = z2
+
+        # Further calculations the same as for XY plane
+        xabs = abs(x2 - x1)
+        yabs = abs(y2 - y1)
+        debugPrintNew(d_XABS_YABS, "XABS = " + str(xabs) + " YABS = " + str(yabs))
+
+        if xabs <= LINE_TOLERANCE:
+            return None  # this means line x = edgeCenterX
+        if yabs <= LINE_TOLERANCE:
+            A = 0
+            B = y1
+            return A, B
+        A = (y2 - y1) / (x2 - x1)
+        B = y1 - (A * x1)
+        return (A, B)
+
+    def getLineCircleIntersections(self, lineAB, circleMidPoint, radius):
+        # (x - a)**2 + (y - b)**2 = r**2 - circle equation
+        # y = A*x + B - line equation
+        # f * x**2 + g * x + h = 0 - quadratic equation
+        A, B = lineAB
+        a, b = circleMidPoint
+        f = 1 + (A ** 2)
+        g = -2 * a + 2 * A * B - 2 * A * b
+        h = (B ** 2) - 2 * b * B - (radius ** 2) + (a ** 2) + (b ** 2)
+        coef = [f, g, h]
+        roots = self.getQuadraticRoots(coef)
+        if roots is not None:
+            x1 = roots[0]
+            x2 = roots[1]
+            point1 = [x1, A * x1 + B]
+            point2 = [x2, A * x2 + B]
+            return [point1, point2]
+        else:
+            return None
+
+    def getLineCircleIntersectionsWhenXPerpendicular(self, edgeCenter,
+                                                     circleMidPoint, radius, plane):
+        # (x - a)**2 + (y - b)**2 = r**2 - circle equation
+        # x = xValue - line equation
+        # f * x**2 + g * x + h = 0 - quadratic equation
+        xValue = edgeCenter[0]
+        if plane == YZ:
+            xValue = edgeCenter[1]
+        if plane == XZ:
+            xValue = edgeCenter[0]
+
+        a, b = circleMidPoint
+        f = 1
+        g = -2 * b
+        h = (a ** 2) + (b ** 2) + (xValue ** 2) - 2 * a * xValue - (radius ** 2)
+        coef = [f, g, h]
+        roots = self.getQuadraticRoots(coef)
+        if roots is not None:
+            y1 = roots[0]
+            y2 = roots[1]
+            point1 = [xValue, y1]
+            point2 = [xValue, y2]
+            return [point1, point2]
+        else:
+            return None
+
+    # point1 is the point near 90 deg angle
+    def getAngle(self, point1, point2, point3):
+        distance1 = (Vector(point1) - Vector(point2)).length
+        distance2 = (Vector(point2) - Vector(point3)).length
+        cos = distance1 / distance2
+
+        if abs(cos) > 1:  # prevents Domain Error
+            cos = round(cos)
+
+        alpha = acos(cos)
+        return (alpha, degrees(alpha))
+
+    # get two of three coordinates used for further calculation of spin center
+    # PKHG>nice if rescriction to these 3 types or planes is to be done
+    # komi3D> from 0.0.2 there is a restriction. In future I would like Edge
+    # komi3D> Roundifier to work on Normal and View coordinate systems
+    def getCircleMidPointOnPlane(self, V1, plane):
+        X = V1[0]
+        Y = V1[1]
+        if plane == 'XZ':
+            X = V1[0]
+            Y = V1[2]
+        elif plane == 'YZ':
+            X = V1[1]
+            Y = V1[2]
+        return [X, Y]
+
+    def getEdgeReference(self, edge, edgeCenter, plane):
+        vert1 = edge.verts[1].co
+        V = vert1 - edgeCenter
+        orthoVector = Vector((V[1], -V[0], V[2]))
+        if plane == 'XZ':
+            orthoVector = Vector((V[2], V[1], -V[0]))
+        elif plane == 'YZ':
+            orthoVector = Vector((V[0], V[2], -V[1]))
+        refPoint = edgeCenter + orthoVector
+        return refPoint
+
+
+# Selection Methods #
+
+class SelectionHelper:
+
+    def selectVertexInMesh(self, mesh, vertex):
+        bpy.ops.object.mode_set(mode="OBJECT")
+        for v in mesh.vertices:
+            if v.co == vertex:
+                v.select = True
+                break
+
+        bpy.ops.object.mode_set(mode="EDIT")
+
+    def getSelectedVertex(self, mesh):
+        bpy.ops.object.mode_set(mode="OBJECT")
+        for v in mesh.vertices:
+            if v.select is True:
+                bpy.ops.object.mode_set(mode="EDIT")
+                return v
+
+        bpy.ops.object.mode_set(mode="EDIT")
+        return None
+
+    def refreshMesh(self, bm, mesh):
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bm.to_mesh(mesh)
+        bpy.ops.object.mode_set(mode='EDIT')
+
+
+# Operator
+
+class EdgeRoundifier(Operator):
+    bl_idname = "mesh.edge_roundifier"
+    bl_label = "Edge Roundifier"
+    bl_description = "Mesh modeling tool for building arcs on selected Edges"
+    bl_options = {'REGISTER', 'UNDO', 'PRESET'}
+
+    threshold = 0.0005
+    obj = None
+
+    edgeScaleFactor: FloatProperty(
+            name="",
+            description="Set the Factor of scaling",
+            default=1.0,
+            min=0.00001, max=100000.0,
+            step=0.5,
+            precision=5
+            )
+    r: FloatProperty(
+            name="",
+            description="User Defined arc steepness by a Radius\n"
+                        "Enabled only if Entry mode is set to Radius\n",
+            default=1,
+            min=0.00001, max=1000.0,
+            step=0.1,
+            precision=3
+            )
+    a: FloatProperty(
+            name="",
+            description="User defined arc steepness calculated from an Angle\n"
+                        "Enabled only if Entry mode is set to Angle and\n"
+                        "Angle presets is set Other",
+            default=180.0,
+            min=0.1, max=180.0,
+            step=0.5,
+            precision=1
+            )
+    n: IntProperty(
+            name="",
+            description="Arc subdivision level",
+            default=4,
+            min=1, max=100,
+            step=1
+            )
+    flip: BoolProperty(
+            name="Flip",
+            description="If True, flip the side of the selected edges where the arcs are drawn",
+            default=False
+            )
+    invertAngle: BoolProperty(
+            name="Invert",
+            description="If True, uses an inverted angle to draw the arc (360 degrees - angle)",
+            default=False
+            )
+    fullCircles: BoolProperty(
+            name="Circles",
+            description="If True, uses an angle of 360 degrees to draw the arcs",
+            default=False
+            )
+    bothSides: BoolProperty(
+            name="Both sides",
+            description="If True, draw arcs on both sides of the selected edges",
+            default=False
+            )
+    drawArcCenters: BoolProperty(
+            name="Centers",
+            description="If True, draws a vertex for each spin center",
+            default=False
+            )
+    removeEdges: BoolProperty(
+            name="Edges",
+            description="If True removes the Original selected edges",
+            default=False
+            )
+    removeScaledEdges: BoolProperty(
+            name="Scaled edges",
+            description="If True removes the Scaled edges (not part of the arcs)",
+            default=False
+            )
+    connectArcWithEdge: BoolProperty(
+            name="Arc - Edge",
+            description="Connect Arcs to Edges",
+            default=False
+            )
+    connectArcs: BoolProperty(
+            name="Arcs",
+            description="Connect subsequent Arcs",
+            default=False
+            )
+    connectScaledAndBase: BoolProperty(
+            name="Scaled - Base Edge",
+            description="Connect Scaled to Base Edge",
+            default=False
+            )
+    connectArcsFlip: BoolProperty(
+            name="Flip Arcs",
+            description="Flip the connection of subsequent Arcs",
+            default=False
+            )
+    connectArcWithEdgeFlip: BoolProperty(
+            name="Flip Arc - Edge",
+            description="Flip the connection of the Arcs to Edges",
+            default=False
+            )
+    axisAngle: FloatProperty(
+            name="",
+            description="Rotate Arc around the perpendicular axis",
+            default=0.0,
+            min=-180.0, max=180.0,
+            step=0.5,
+            precision=1
+            )
+    edgeAngle: FloatProperty(
+            name="",
+            description="Rotate Arc around the Edge (Edge acts like as the axis)",
+            default=0.0,
+            min=-180.0, max=180.0,
+            step=0.5,
+            precision=1
+            )
+    offset: FloatProperty(
+            name="",
+            description="Offset Arc perpendicular the Edge",
+            default=0.0,
+            min=-1000000.0, max=1000000.0,
+            step=0.1,
+            precision=5
+            )
+    offset2: FloatProperty(
+            name="",
+            description="Offset Arc in parallel to the Edge",
+            default=0.0,
+            min=-1000000.0, max=1000000.0,
+            step=0.1,
+            precision=5
+            )
+    ellipticFactor: FloatProperty(
+            name="",
+            description="Make Arc elliptic",
+            default=0.0,
+            min=-1000000.0, max=1000000.0,
+            step=0.1,
+            precision=5
+            )
+    workModeItems = [("Normal", "Normal", ""), ("Reset", "Reset", "")]
+    workMode: EnumProperty(
+            items=workModeItems,
+            name="",
+            default='Normal',
+            description="Normal work with the current given parameters set by the user\n"
+                        "Reset - changes back the parameters to their default values"
+            )
+    entryModeItems = [("Radius", "Radius", ""), ("Angle", "Angle", "")]
+    entryMode: EnumProperty(
+            items=entryModeItems,
+            name="",
+            default='Angle',
+            description="Entry mode switch between Angle and Radius\n"
+                        "If Angle is selected, arc radius is calculated from it"
+            )
+    rotateCenterItems = [
+            ("Spin", "Spin", ""), ("V1", "V1", ""),
+            ("Edge", "Edge", ""), ("V2", "V2", "")
+            ]
+    rotateCenter: EnumProperty(
+            items=rotateCenterItems,
+            name="",
+            default='Edge',
+            description="Rotate center for spin axis rotate"
+            )
+    arcModeItems = [("FullEdgeArc", "Full", "Full"), ('HalfEdgeArc', "Half", "Half")]
+    arcMode: EnumProperty(
+            items=arcModeItems,
+            name="",
+            default='FullEdgeArc',
+            description="Arc mode - switch between Full and Half arcs"
+            )
+    angleItems = [
+            ('Other', "Other", "User defined angle"), ('180', "180", "HemiCircle (2 sides)"),
+            ('120', "120", "TriangleCircle (3 sides)"), ('90', "90", "QuadCircle (4 sides)"),
+            ('72', "72", "PentagonCircle (5 sides)"), ('60', "60", "HexagonCircle (6 sides)"),
+            ('45', "45", "OctagonCircle (8 sides)"), ('30', "30", "DodecagonCircle (12 sides)")
+            ]
+    angleEnum: EnumProperty(
+            items=angleItems,
+            name="",
+            default='180',
+            description="Presets prepare standard angles and calculate proper ray"
+            )
+    refItems = [('ORG', "Origin", "Use Origin Location"), ('CUR', "3D Cursor", "Use 3DCursor Location"),
+                ('EDG', "Edge", "Use Individual Edge Reference")]
+    referenceLocation: EnumProperty(
+            items=refItems,
+            name="",
+            default='ORG',
+            description="Reference location used to calculate initial centers of drawn arcs"
+            )
+    planeItems = [
+            (XY, "XY", "XY Plane (Z=0)"),
+            (YZ, "YZ", "YZ Plane (X=0)"),
+            (XZ, "XZ", "XZ Plane (Y=0)")
+            ]
+    planeEnum: EnumProperty(
+            items=planeItems,
+            name="",
+            default='XY',
+            description="Plane used to calculate spin plane of drawn arcs"
+            )
+    edgeScaleCenterItems = [
+            ('V1', "V1", "v1 - First Edge's Vertex"),
+            ('CENTER', "Center", "Center of the Edge"),
+            ('V2', "V2", "v2 - Second Edge's Vertex")
+            ]
+    edgeScaleCenterEnum: EnumProperty(
+            items=edgeScaleCenterItems,
+            name="Edge scale center",
+            default='CENTER',
+            description="Center used for scaling the initial edge"
+            )
+
+    calc = CalculationHelper()
+    sel = SelectionHelper()
+
+    @classmethod
+    def poll(cls, context):
+        obj = context.active_object
+        return (obj and obj.type == 'MESH' and
+                obj.mode == 'EDIT')
+
+    def prepareMesh(self, context):
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bpy.ops.object.mode_set(mode='EDIT')
+
+        mesh = context.view_layer.objects.active.data
+        bm = bmesh.new()
+        bm.from_mesh(mesh)
+
+        edges = [ele for ele in bm.edges if ele.select]
+        return edges, mesh, bm
+
+    def prepareParameters(self):
+        parameters = {"a": "a"}
+        parameters["arcMode"] = self.arcMode
+        parameters["edgeScaleFactor"] = self.edgeScaleFactor
+        parameters["edgeScaleCenterEnum"] = self.edgeScaleCenterEnum
+        parameters["plane"] = self.planeEnum
+        parameters["radius"] = self.r
+        parameters["angle"] = self.a
+        parameters["segments"] = self.n
+        parameters["fullCircles"] = self.fullCircles
+        parameters["invertAngle"] = self.invertAngle
+        parameters["bothSides"] = self.bothSides
+        parameters["angleEnum"] = self.angleEnum
+        parameters["entryMode"] = self.entryMode
+        parameters["workMode"] = self.workMode
+        parameters["refObject"] = self.referenceLocation
+        parameters["flip"] = self.flip
+        parameters["drawArcCenters"] = self.drawArcCenters
+        parameters["removeEdges"] = self.removeEdges
+        parameters["removeScaledEdges"] = self.removeScaledEdges
+        parameters["connectArcWithEdge"] = self.connectArcWithEdge
+        parameters["connectScaledAndBase"] = self.connectScaledAndBase
+        parameters["connectArcs"] = self.connectArcs
+        parameters["connectArcsFlip"] = self.connectArcsFlip
+        parameters["connectArcWithEdgeFlip"] = self.connectArcWithEdgeFlip
+        parameters["axisAngle"] = self.axisAngle
+        parameters["edgeAngle"] = self.edgeAngle
+        parameters["offset"] = self.offset
+        parameters["offset2"] = self.offset2
+        parameters["ellipticFactor"] = self.ellipticFactor
+        parameters["rotateCenter"] = self.rotateCenter
+        return parameters
+
+    def draw(self, context):
+        layout = self.layout
+        box = layout.box()
+        uiPercentage = 0.333
+
+        self.addEnumParameterToUI(box, False, uiPercentage, 'Mode:', 'workMode')
+        self.addEnumParameterToUI(box, False, uiPercentage, 'Plane:', 'planeEnum')
+        self.addEnumParameterToUI(box, False, uiPercentage, 'Reference:', 'referenceLocation')
+
+        box = layout.box()
+        self.addEnumParameterToUI(box, False, uiPercentage, 'Scale base:', 'edgeScaleCenterEnum')
+        self.addParameterToUI(box, False, uiPercentage, 'Scale factor:', 'edgeScaleFactor')
+
+        box = layout.box()
+        self.addEnumParameterToUI(box, False, uiPercentage, 'Entry mode:', 'entryMode')
+
+        row = box.row(align=False)
+        row.prop(self, 'angleEnum', expand=True, text="Angle presets")
+
+        disable_a = bool(self.entryMode == 'Angle' and self.angleEnum == 'Other')
+        disable_r = bool(self.entryMode == 'Radius')
+
+        self.addParameterToUI(box, False, uiPercentage, 'Angle:', 'a', disable_a)
+        self.addParameterToUI(box, False, uiPercentage, 'Radius:', 'r', disable_r)
+        self.addParameterToUI(box, False, uiPercentage, 'Segments:', 'n')
+
+        box = layout.box()
+        self.addCheckboxToUI(box, True, 'Options:', 'flip', 'invertAngle')
+        self.addCheckboxToUI(box, True, '', 'bothSides', 'fullCircles')
+        self.addCheckboxToUI(box, True, '', 'drawArcCenters')
+
+        box = layout.box()
+        self.addCheckboxToUI(box, True, 'Remove:', 'removeEdges', 'removeScaledEdges')
+
+        box = layout.box()
+        self.addCheckboxToUI(box, True, 'Connect:', 'connectArcs', 'connectArcsFlip')
+        self.addCheckboxToUI(box, True, '', 'connectArcWithEdge', 'connectArcWithEdgeFlip')
+        self.addCheckboxToUI(box, True, '', 'connectScaledAndBase')
+
+        box = layout.box()
+        self.addParameterToUI(box, False, uiPercentage, 'Orhto offset:', 'offset')
+        self.addParameterToUI(box, False, uiPercentage, 'Parallel offset:', 'offset2')
+
+        box = layout.box()
+        self.addParameterToUI(box, False, uiPercentage, 'Edge rotate :', 'edgeAngle')
+        self.addEnumParameterToUI(box, False, uiPercentage, 'Axis rotate center:', 'rotateCenter')
+        self.addParameterToUI(box, False, uiPercentage, 'Axis rotate:', 'axisAngle')
+
+        box = layout.box()
+        self.addParameterToUI(box, False, uiPercentage, 'Elliptic factor:', 'ellipticFactor')
+
+    def addParameterToUI(self, layout, alignment, percent, label, properties, disable=True):
+        row = layout.row(align=alignment)
+        split = row.split(factor=percent)
+        col = split.column()
+
+        col.label(text=label)
+        col2 = split.column()
+        row = col2.row(align=alignment)
+        row.enabled = disable
+        row.prop(self, properties)
+
+    def addCheckboxToUI(self, layout, alignment, label, property1, property2=None):
+        if label not in (""):
+            row = layout.row()
+            row.label(text=label)
+        row2 = layout.row(align=alignment)
+        if property2:
+            split = row2.split(factor=0.5)
+            split.prop(self, property1, toggle=True)
+            split.prop(self, property2, toggle=True)
+        else:
+            row2.prop(self, property1, toggle=True)
+            layout.separator()
+
+    def addEnumParameterToUI(self, layout, alignment, percent, label, properties):
+        row = layout.row(align=alignment)
+        split = row.split(factor=percent)
+        col = split.column()
+
+        col.label(text=label)
+        col2 = split.column()
+        row = col2.row(align=alignment)
+        row.prop(self, properties, expand=True, text="a")
+
+    def execute(self, context):
+
+        edges, mesh, bm = self.prepareMesh(context)
+        parameters = self.prepareParameters()
+
+        self.resetValues(parameters["workMode"])
+
+        self.obj = context.view_layer.objects.active
+        scaledEdges = self.scaleDuplicatedEdges(bm, edges, parameters)
+
+        if len(scaledEdges) > 0:
+            self.roundifyEdges(scaledEdges, parameters, bm, mesh)
+
+            if parameters["connectScaledAndBase"]:
+                self.connectScaledEdgesWithBaseEdge(scaledEdges, edges, bm, mesh)
+
+            self.sel.refreshMesh(bm, mesh)
+            self.selectEdgesAfterRoundifier(context, scaledEdges)
+        else:
+            debugPrintNew(True, "No edges selected!")
+
+        if parameters["removeEdges"]:
+            bmesh.ops.delete(bm, geom=edges, context='EDGES')
+
+        if parameters["removeScaledEdges"] and self.edgeScaleFactor != 1.0:
+            bmesh.ops.delete(bm, geom=scaledEdges, context='EDGES')
+
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bm.to_mesh(mesh)
+        bpy.ops.object.mode_set(mode='EDIT')
+        bpy.ops.mesh.select_all(action='SELECT')
+        bpy.ops.mesh.remove_doubles()
+
+        bm.free()
+
+        return {'FINISHED'}
+
+    def resetValues(self, workMode):
+        if workMode == "Reset":
+            self.setAllParamsToDefaults()
+
+    def setAllParamsToDefaults(self):
+        try:
+            self.edgeScaleFactor = 1.0
+            self.r = 1
+            self.a = 180.0
+            self.n = 4
+            self.flip = False
+            self.invertAngle = False
+            self.fullCircles = False
+            self.bothSides = False
+            self.drawArcCenters = False
+            self.removeEdges = False
+            self.removeScaledEdges = False
+
+            self.connectArcWithEdge = False
+            self.connectArcs = False
+            self.connectScaledAndBase = False
+            self.connectArcsFlip = False
+            self.connectArcWithEdgeFlip = False
+
+            self.axisAngle = 0.0
+            self.edgeAngle = 0.0
+            self.offset = 0.0
+            self.offset2 = 0.0
+            self.ellipticFactor = 0.0
+
+            self.workMode = 'Normal'
+            self.entryMode = 'Angle'
+            self.angleEnum = '180'
+            self.referenceLocation = 'ORG'
+            self.planeEnum = 'XY'
+            self.edgeScaleCenterEnum = 'CENTER'
+            self.rotateCenter = 'Edge'
+
+            self.report({'INFO'}, "The parameters have been reset to default values")
+        except Exception as e:
+            self.report({'WARNING'}, "The parameters could not be reset")
+            debugPrintNew(True, "\n[setAllParamsToDefaults]\n parameter reset error\n" + e)
+
+    def scaleDuplicatedEdges(self, bm, edges, parameters):
+        scaleCenter = parameters["edgeScaleCenterEnum"]
+        factor = parameters["edgeScaleFactor"]
+        # this code is based on Zeffi's answer to my question
+        duplicateEdges = []
+        if factor == 1:
+            duplicateEdges = edges
+        else:
+            for e in edges:
+                v1 = e.verts[0].co
+                v2 = e.verts[1].co
+                origin = None
+                if scaleCenter == 'CENTER':
+                    origin = (v1 + v2) * 0.5
+                elif scaleCenter == 'V1':
+                    origin = v1
+                elif scaleCenter == 'V2':
+                    origin = v2
+
+                bmv1 = bm.verts.new(((v1 - origin) * factor) + origin)
+                bmv2 = bm.verts.new(((v2 - origin) * factor) + origin)
+                bme = bm.edges.new([bmv1, bmv2])
+                duplicateEdges.append(bme)
+        return duplicateEdges
+
+    def roundifyEdges(self, edges, parameters, bm, mesh):
+        arcs = []
+        for e in edges:
+            arcVerts = self.roundify(e, parameters, bm, mesh)
+            arcs.append(arcVerts)
+
+        if parameters["connectArcs"]:
+            self.connectArcsTogether(arcs, bm, mesh, parameters)
+
+    def getNormalizedEdgeVector(self, edge):
+        V1 = edge.verts[0].co
+        V2 = edge.verts[1].co
+        edgeVector = V2 - V1
+        normEdge = edgeVector.normalized()
+        return normEdge
+
+    def getEdgePerpendicularVector(self, edge, plane):
+        normEdge = self.getNormalizedEdgeVector(edge)
+
+        edgePerpendicularVector = Vector((normEdge[1], -normEdge[0], 0))
+        if plane == YZ:
+            edgePerpendicularVector = Vector((0, normEdge[2], -normEdge[1]))
+        if plane == XZ:
+            edgePerpendicularVector = Vector((normEdge[2], 0, -normEdge[0]))
+        return edgePerpendicularVector
+
+    def getEdgeInfo(self, edge):
+        V1 = edge.verts[0].co
+        V2 = edge.verts[1].co
+        edgeVector = V2 - V1
+        edgeLength = edgeVector.length
+        edgeCenter = (V2 + V1) * 0.5
+        return V1, V2, edgeVector, edgeLength, edgeCenter
+
+    def roundify(self, edge, parameters, bm, mesh):
+        V1, V2, edgeVector, edgeLength, edgeCenter = self.getEdgeInfo(edge)
+        if self.skipThisEdge(V1, V2, parameters["plane"]):
+            return
+
+        roundifyParams = None
+        arcVerts = None
+        roundifyParams = self.calculateRoundifyParams(edge, parameters, bm, mesh)
+        if roundifyParams is None:
+            return
+
+        arcVerts = self.spinAndPostprocess(edge, parameters, bm, mesh, edgeCenter, roundifyParams)
+        return arcVerts
+
+    def spinAndPostprocess(self, edge, parameters, bm, mesh, edgeCenter, roundifyParams):
+        spinnedVerts, roundifyParamsUpdated = self.drawSpin(
+                                                edge, edgeCenter,
+                                                roundifyParams,
+                                                parameters, bm, mesh
+                                                )
+        postProcessedArcVerts = self.arcPostprocessing(
+                                                edge, parameters, bm, mesh,
+                                                roundifyParamsUpdated,
+                                                spinnedVerts, edgeCenter
+                                                )
+        return postProcessedArcVerts
+
+    def rotateArcAroundEdge(self, bm, mesh, arcVerts, parameters):
+        angle = parameters["edgeAngle"]
+        if angle != 0:
+            self.arc_rotator(arcVerts, angle, parameters)
+
+    # arc_rotator method was created by PKHG, I (komi3D) adjusted it to fit the rest
+    def arc_rotator(self, arcVerts, extra_rotation, parameters):
+        bpy.ops.object.mode_set(mode='OBJECT')
+        old_location = self.obj.location.copy()
+        bpy.ops.transform.translate(
+            value=-old_location,
+            constraint_axis=(False, False, False),
+            orient_type='GLOBAL',
+            mirror=False,
+            use_proportional_edit=False,
+        )
+        bpy.ops.object.mode_set(mode='EDIT')
+        adjust_matrix = self.obj.matrix_parent_inverse
+        bm = bmesh.from_edit_mesh(self.obj.data)
+        lastVert = len(arcVerts) - 1
+        if parameters["drawArcCenters"]:
+            lastVert = lastVert - 1  # center gets added as last vert of arc
+        v0_old = adjust_matrix @ arcVerts[0].co.copy()
+
+        # PKHG>INFO move if necessary v0 to origin such that the axis gos through origin and v1
+        if v0_old != Vector((0, 0, 0)):
+            for i, ele in enumerate(arcVerts):
+                arcVerts[i].co += - v0_old
+
+        axis = arcVerts[0].co - arcVerts[lastVert].co
+        a_mat = Quaternion(axis, radians(extra_rotation)).normalized().to_matrix()
+
+        for ele in arcVerts:
+            ele.co = a_mat @ ele.co
+
+        # PKHG>INFO move back if needed
+        if v0_old != Vector((0, 0, 0)):
+            for i, ele in enumerate(arcVerts):
+                arcVerts[i].co += + v0_old
+
+        bpy.ops.object.mode_set(mode='OBJECT')
+        # PKHG>INFO move origin object back print("old location = " , old_location)
+        bpy.ops.transform.translate(
+            value=old_location,
+            constraint_axis=(False, False, False),
+            orient_type='GLOBAL',
+            mirror=False,
+            use_proportional_edit=False,
+        )
+        bpy.ops.object.mode_set(mode='EDIT')
+
+    def makeElliptic(self, bm, mesh, arcVertices, parameters):
+        if parameters["ellipticFactor"] != 0:  # if 0 then nothing has to be done
+            lastVert = len(arcVertices) - 1
+            if parameters["drawArcCenters"]:
+                lastVert = lastVert - 1  # center gets added as last vert of arc
+            v0co = arcVertices[0].co
+            v1co = arcVertices[lastVert].co
+
+            for vertex in arcVertices:  # range(len(res_list)):
+                # PKHg>INFO compute the base on the edge  of the height-vector
+                top = vertex.co  # res_list[nr].co
+                t = 0
+                if v1co - v0co != 0:
+                    t = (v1co - v0co).dot(top - v0co) / (v1co - v0co).length ** 2
+                h_bottom = v0co + t * (v1co - v0co)
+                height = (h_bottom - top)
+                vertex.co = top + parameters["ellipticFactor"] * height
+
+        return arcVertices
+
+    def arcPostprocessing(self, edge, parameters, bm, mesh, roundifyParams, spinnedVerts, edgeCenter):
+        [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation] = roundifyParams
+        rotatedVerts = []
+        if parameters["rotateCenter"] == 'Edge':
+            rotatedVerts = self.rotateArcAroundSpinAxis(
+                                bm, mesh, spinnedVerts, parameters, edgeCenter
+                                )
+        elif parameters["rotateCenter"] == 'Spin':
+            rotatedVerts = self.rotateArcAroundSpinAxis(
+                                bm, mesh, spinnedVerts, parameters, chosenSpinCenter
+                                )
+        elif parameters["rotateCenter"] == 'V1':
+            rotatedVerts = self.rotateArcAroundSpinAxis(
+                                bm, mesh, spinnedVerts, parameters, edge.verts[0].co
+                                )
+        elif parameters["rotateCenter"] == 'V2':
+            rotatedVerts = self.rotateArcAroundSpinAxis(
+                                bm, mesh, spinnedVerts, parameters, edge.verts[1].co
+                                )
+
+        offsetVerts = self.offsetArcPerpendicular(
+                                bm, mesh, rotatedVerts, edge, parameters
+                                )
+        offsetVerts2 = self.offsetArcParallel(
+                                bm, mesh, offsetVerts, edge, parameters
+                                )
+        ellipticVerts = self.makeElliptic(
+                                bm, mesh, offsetVerts2, parameters
+                                )
+        self.rotateArcAroundEdge(bm, mesh, ellipticVerts, parameters)
+
+        if parameters["connectArcWithEdge"]:
+            self.connectArcTogetherWithEdge(
+                                edge, offsetVerts2, bm, mesh, parameters
+                                )
+        return offsetVerts2
+
+    def connectArcTogetherWithEdge(self, edge, arcVertices, bm, mesh, parameters):
+        lastVert = len(arcVertices) - 1
+        if parameters["drawArcCenters"]:
+            lastVert = lastVert - 1  # center gets added as last vert of arc
+        edgeV1 = edge.verts[0].co
+        edgeV2 = edge.verts[1].co
+        arcV1 = arcVertices[0].co
+        arcV2 = arcVertices[lastVert].co
+
+        bmv1 = bm.verts.new(edgeV1)
+        bmv2 = bm.verts.new(arcV1)
+
+        bmv3 = bm.verts.new(edgeV2)
+        bmv4 = bm.verts.new(arcV2)
+
+        if parameters["connectArcWithEdgeFlip"] is False:
+            bme = bm.edges.new([bmv1, bmv2])
+            bme2 = bm.edges.new([bmv3, bmv4])
+        else:
+            bme = bm.edges.new([bmv1, bmv4])
+            bme2 = bm.edges.new([bmv3, bmv2])
+        self.sel.refreshMesh(bm, mesh)
+
+    def connectScaledEdgesWithBaseEdge(self, scaledEdges, baseEdges, bm, mesh):
+        for i in range(0, len(scaledEdges)):
+            scaledEdgeV1 = scaledEdges[i].verts[0].co
+            baseEdgeV1 = baseEdges[i].verts[0].co
+            scaledEdgeV2 = scaledEdges[i].verts[1].co
+            baseEdgeV2 = baseEdges[i].verts[1].co
+
+            bmv1 = bm.verts.new(baseEdgeV1)
+            bmv2 = bm.verts.new(scaledEdgeV1)
+            bme = bm.edges.new([bmv1, bmv2])
+
+            bmv3 = bm.verts.new(scaledEdgeV2)
+            bmv4 = bm.verts.new(baseEdgeV2)
+            bme = bm.edges.new([bmv3, bmv4])
+        self.sel.refreshMesh(bm, mesh)
+
+    def connectArcsTogether(self, arcs, bm, mesh, parameters):
+        for i in range(0, len(arcs) - 1):
+            # in case on XZ or YZ there are no arcs drawn
+            if arcs[i] is None or arcs[i + 1] is None:
+                return
+
+            lastVert = len(arcs[i]) - 1
+            if parameters["drawArcCenters"]:
+                lastVert = lastVert - 1  # center gets added as last vert of arc
+            # take last vert of arc i and first vert of arc i+1
+
+            V1 = arcs[i][lastVert].co
+            V2 = arcs[i + 1][0].co
+
+            if parameters["connectArcsFlip"]:
+                V1 = arcs[i][0].co
+                V2 = arcs[i + 1][lastVert].co
+
+            bmv1 = bm.verts.new(V1)
+            bmv2 = bm.verts.new(V2)
+            bme = bm.edges.new([bmv1, bmv2])
+
+        # connect last arc and first one
+        lastArcId = len(arcs) - 1
+        lastVertIdOfLastArc = len(arcs[lastArcId]) - 1
+        if parameters["drawArcCenters"]:
+            # center gets added as last vert of arc
+            lastVertIdOfLastArc = lastVertIdOfLastArc - 1
+
+        V1 = arcs[lastArcId][lastVertIdOfLastArc].co
+        V2 = arcs[0][0].co
+        if parameters["connectArcsFlip"]:
+            V1 = arcs[lastArcId][0].co
+            V2 = arcs[0][lastVertIdOfLastArc].co
+
+        bmv1 = bm.verts.new(V1)
+        bmv2 = bm.verts.new(V2)
+        bme = bm.edges.new([bmv1, bmv2])
+
+        self.sel.refreshMesh(bm, mesh)
+
+    def offsetArcPerpendicular(self, bm, mesh, Verts, edge, parameters):
+        perpendicularVector = self.getEdgePerpendicularVector(edge, parameters["plane"])
+        offset = parameters["offset"]
+        translation = offset * perpendicularVector
+
+        try:
+            bmesh.ops.translate(bm, verts=Verts, vec=translation)
+        except ValueError:
+            print("[Edge Roundifier]: Perpendicular translate value error - "
+                  "multiple vertices in list - try unchecking 'Centers'")
+
+        indexes = [v.index for v in Verts]
+        self.sel.refreshMesh(bm, mesh)
+        offsetVertices = [bm.verts[i] for i in indexes]
+        return offsetVertices
+
+    def offsetArcParallel(self, bm, mesh, Verts, edge, parameters):
+        edgeVector = self.getNormalizedEdgeVector(edge)
+        offset = parameters["offset2"]
+        translation = offset * edgeVector
+
+        try:
+            bmesh.ops.translate(bm, verts=Verts, vec=translation)
+        except ValueError:
+            print("[Edge Roundifier]: Parallel translate value error - "
+                  "multiple vertices in list - try unchecking 'Centers'")
+
+        indexes = [v.index for v in Verts]
+        self.sel.refreshMesh(bm, mesh)
+        offsetVertices = [bm.verts[i] for i in indexes]
+        return offsetVertices
+
+    def skipThisEdge(self, V1, V2, plane):
+        # Check If It is possible to spin selected verts on this plane if not exit roundifier
+        if(plane == XY):
+            if (V1[0] == V2[0] and V1[1] == V2[1]):
+                return True
+        elif(plane == YZ):
+            if (V1[1] == V2[1] and V1[2] == V2[2]):
+                return True
+        elif(plane == XZ):
+            if (V1[0] == V2[0] and V1[2] == V2[2]):
+                return True
+        return False
+
+    def calculateRoundifyParams(self, edge, parameters, bm, mesh):
+        # Because all data from mesh is in local coordinates
+        # and spin operator works on global coordinates
+        # We first need to translate all input data by vector equal
+        # to origin position and then perform calculations
+        # At least that is my understanding :) <komi3D>
+
+        # V1 V2 stores Local Coordinates
+        V1, V2, edgeVector, edgeLength, edgeCenter = self.getEdgeInfo(edge)
+
+        debugPrintNew(d_Plane, "PLANE: " + parameters["plane"])
+        lineAB = self.calc.getLineCoefficientsPerpendicularToVectorInPoint(
+                                                edgeCenter, edgeVector,
+                                                parameters["plane"]
+                                                )
+        circleMidPoint = V1
+        circleMidPointOnPlane = self.calc.getCircleMidPointOnPlane(
+                                                V1, parameters["plane"]
+                                                )
+        radius = parameters["radius"]
+
+        angle = 0
+        if (parameters["entryMode"] == 'Angle'):
+            if (parameters["angleEnum"] != 'Other'):
+                radius, angle = self.CalculateRadiusAndAngleForAnglePresets(
+                                                parameters["angleEnum"], radius,
+                                                angle, edgeLength
+                                                )
+            else:
+                radius, angle = self.CalculateRadiusAndAngle(edgeLength)
+        debugPrintNew(d_Radius_Angle, "RADIUS = " + str(radius) + "  ANGLE = " + str(angle))
+        roots = None
+        if angle != pi:  # mode other than 180
+            if lineAB is None:
+                roots = self.calc.getLineCircleIntersectionsWhenXPerpendicular(
+                                                edgeCenter, circleMidPointOnPlane,
+                                                radius, parameters["plane"]
+                                                )
+            else:
+                roots = self.calc.getLineCircleIntersections(
+                                                lineAB, circleMidPointOnPlane, radius
+                                                )
+
+            if roots is None:
+                debugPrintNew(True,
+                             "[Edge Roundifier]: No centers were found. Change radius to higher value")
+                return None
+            roots = self.addMissingCoordinate(roots, V1, parameters["plane"])  # adds X, Y or Z coordinate
+        else:
+            roots = [edgeCenter, edgeCenter]
+        debugPrintNew(d_Roots, "roots=" + str(roots))
+
+        refObjectLocation = None
+        objectLocation = bpy.context.active_object.location  # Origin Location
+
+        if parameters["refObject"] == "ORG":
+            refObjectLocation = [0, 0, 0]
+        elif parameters["refObject"] == "CUR":
+            refObjectLocation = bpy.context.scene.cursor.location - objectLocation
+        else:
+            refObjectLocation = self.calc.getEdgeReference(edge, edgeCenter, parameters["plane"])
+
+        debugPrintNew(d_RefObject, parameters["refObject"], refObjectLocation)
+        chosenSpinCenter, otherSpinCenter = self.getSpinCenterClosestToRefCenter(
+                                                            refObjectLocation, roots
+                                                            )
+
+        if (parameters["entryMode"] == "Radius"):
+            halfAngle = self.calc.getAngle(edgeCenter, chosenSpinCenter, circleMidPoint)
+            angle = 2 * halfAngle[0]  # in radians
+            self.a = degrees(angle)   # in degrees
+
+        spinAxis = self.getSpinAxis(parameters["plane"])
+        steps = parameters["segments"]
+        angle = -angle  # rotate clockwise by default
+
+        return [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation]
+
+    def drawSpin(self, edge, edgeCenter, roundifyParams, parameters, bm, mesh):
+        [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation] = roundifyParams
+
+        v0org, v1org = (edge.verts[0], edge.verts[1])
+
+        if parameters["flip"]:
+            angle = -angle
+            spinCenterTemp = chosenSpinCenter
+            chosenSpinCenter = otherSpinCenter
+            otherSpinCenter = spinCenterTemp
+
+        if(parameters["invertAngle"]):
+            if angle < 0:
+                angle = two_pi + angle
+            elif angle > 0:
+                angle = -two_pi + angle
+            else:
+                angle = two_pi
+
+        if(parameters["fullCircles"]):
+            angle = two_pi
+
+        v0 = bm.verts.new(v0org.co)
+
+        result = bmesh.ops.spin(
+                        bm, geom=[v0], cent=chosenSpinCenter, axis=spinAxis,
+                        angle=angle, steps=steps, use_duplicate=False
+                        )
+
+        # it seems there is something wrong with last index of this spin
+        # I need to calculate the last index manually here
+        vertsLength = len(bm.verts)
+        bm.verts.ensure_lookup_table()
+        lastVertIndex = bm.verts[vertsLength - 1].index
+        lastSpinVertIndices = self.getLastSpinVertIndices(steps, lastVertIndex)
+
+        self.sel.refreshMesh(bm, mesh)
+
+        alternativeLastSpinVertIndices = []
+        bothSpinVertices = []
+        spinVertices = []
+        alternate = False
+
+        if ((angle == pi or angle == -pi) and not parameters["bothSides"]):
+
+            midVertexIndex = lastVertIndex - round(steps / 2)
+            bm.verts.ensure_lookup_table()
+            midVert = bm.verts[midVertexIndex].co
+
+            midVertexDistance = (Vector(refObjectLocation) - Vector(midVert)).length
+            midEdgeDistance = (Vector(refObjectLocation) - Vector(edgeCenter)).length
+
+            if ((parameters["invertAngle"]) or (parameters["flip"])):
+                if (midVertexDistance > midEdgeDistance):
+                    alternativeLastSpinVertIndices = self.alternateSpin(
+                                                        bm, mesh, angle, chosenSpinCenter,
+                                                        spinAxis, steps, v0, v1org, lastSpinVertIndices
+                                                        )
+            else:
+                if (midVertexDistance < midEdgeDistance):
+                    alternativeLastSpinVertIndices = self.alternateSpin(
+                                                        bm, mesh, angle, chosenSpinCenter,
+                                                        spinAxis, steps, v0, v1org, lastSpinVertIndices
+                                                        )
+        elif (angle != two_pi):  # to allow full circles
+            if (result['geom_last'][0].co - v1org.co).length > SPIN_END_THRESHOLD:
+                alternativeLastSpinVertIndices = self.alternateSpin(
+                                                        bm, mesh, angle, chosenSpinCenter,
+                                                        spinAxis, steps, v0, v1org, lastSpinVertIndices
+                                                        )
+                alternate = True
+
+        self.sel.refreshMesh(bm, mesh)
+        if alternativeLastSpinVertIndices != []:
+            lastSpinVertIndices = alternativeLastSpinVertIndices
+
+        if lastSpinVertIndices.stop <= len(bm.verts):  # make sure arc was added to bmesh
+            spinVertices = [bm.verts[i] for i in lastSpinVertIndices]
+            if alternativeLastSpinVertIndices != []:
+                spinVertices = spinVertices + [v0]
+            else:
+                spinVertices = [v0] + spinVertices
+
+        if (parameters["bothSides"]):
+            # do some more testing here!!!
+            if (angle == pi or angle == -pi):
+                alternativeLastSpinVertIndices = self.alternateSpinNoDelete(
+                                                        bm, mesh, -angle, chosenSpinCenter,
+                                                        spinAxis, steps, v0, v1org, []
+                                                        )
+            elif alternate:
+                alternativeLastSpinVertIndices = self.alternateSpinNoDelete(
+                                                        bm, mesh, angle, otherSpinCenter,
+                                                        spinAxis, steps, v0, v1org, []
+                                                        )
+            elif not alternate:
+                alternativeLastSpinVertIndices = self.alternateSpinNoDelete(
+                                                        bm, mesh, -angle, otherSpinCenter,
+                                                        spinAxis, steps, v0, v1org, []
+                                                        )
+            bothSpinVertices = [bm.verts[i] for i in lastSpinVertIndices]
+            alternativeSpinVertices = [bm.verts[i] for i in alternativeLastSpinVertIndices]
+            bothSpinVertices = [v0] + bothSpinVertices + alternativeSpinVertices
+            spinVertices = bothSpinVertices
+
+        if (parameters["fullCircles"]):
+            v1 = bm.verts.new(v1org.co)
+            spinVertices = spinVertices + [v1]
+
+        if (parameters['drawArcCenters']):
+            centerVert = bm.verts.new(chosenSpinCenter)
+            spinVertices.append(centerVert)
+
+        return spinVertices, [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation]
+
+    def deleteSpinVertices(self, bm, mesh, lastSpinVertIndices):
+        verticesForDeletion = []
+        bm.verts.ensure_lookup_table()
+        for i in lastSpinVertIndices:
+            vi = bm.verts[i]
+            vi.select = True
+            debugPrintNew(True, str(i) + ") " + str(vi))
+            verticesForDeletion.append(vi)
+
+        bmesh.ops.delete(bm, geom=verticesForDeletion, context = 'VERTS')
+        bmesh.update_edit_mesh(mesh, True)
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bpy.ops.object.mode_set(mode='EDIT')
+
+    def alternateSpinNoDelete(self, bm, mesh, angle, chosenSpinCenter,
+                              spinAxis, steps, v0, v1org, lastSpinVertIndices):
+        v0prim = v0
+
+        result2 = bmesh.ops.spin(bm, geom=[v0prim], cent=chosenSpinCenter, axis=spinAxis,
+                                 angle=angle, steps=steps, use_duplicate=False)
+        vertsLength = len(bm.verts)
+        bm.verts.ensure_lookup_table()
+        lastVertIndex2 = bm.verts[vertsLength - 1].index
+
+        lastSpinVertIndices2 = self.getLastSpinVertIndices(steps, lastVertIndex2)
+        return lastSpinVertIndices2
+
+    def alternateSpin(self, bm, mesh, angle, chosenSpinCenter,
+                      spinAxis, steps, v0, v1org, lastSpinVertIndices):
+
+        self.deleteSpinVertices(bm, mesh, lastSpinVertIndices)
+        v0prim = v0
+
+        result2 = bmesh.ops.spin(
+                        bm, geom=[v0prim], cent=chosenSpinCenter, axis=spinAxis,
+                        angle=-angle, steps=steps, use_duplicate=False
+                        )
+        # it seems there is something wrong with last index of this spin
+        # I need to calculate the last index manually here
+        vertsLength = len(bm.verts)
+        bm.verts.ensure_lookup_table()
+        lastVertIndex2 = bm.verts[vertsLength - 1].index
+
+        lastSpinVertIndices2 = self.getLastSpinVertIndices(steps, lastVertIndex2)
+        # second spin also does not hit the v1org
+        if (result2['geom_last'][0].co - v1org.co).length > SPIN_END_THRESHOLD:
+
+            self.deleteSpinVertices(bm, mesh, lastSpinVertIndices2)
+            self.deleteSpinVertices(bm, mesh, range(v0.index, v0.index + 1))
+            return []
+        else:
+            return lastSpinVertIndices2
+
+    def getLastSpinVertIndices(self, steps, lastVertIndex):
+        arcfirstVertexIndex = lastVertIndex - steps + 1
+        lastSpinVertIndices = range(arcfirstVertexIndex, lastVertIndex + 1)
+        return lastSpinVertIndices
+
+    def rotateArcAroundSpinAxis(self, bm, mesh, vertices, parameters, edgeCenter):
+        axisAngle = parameters["axisAngle"]
+        plane = parameters["plane"]
+        # compensate rotation center
+        objectLocation = bpy.context.active_object.location
+        center = objectLocation + edgeCenter
+
+        rot = Euler((0.0, 0.0, radians(axisAngle)), 'XYZ').to_matrix()
+        if plane == YZ:
+            rot = Euler((radians(axisAngle), 0.0, 0.0), 'XYZ').to_matrix()
+        if plane == XZ:
+            rot = Euler((0.0, radians(axisAngle), 0.0), 'XYZ').to_matrix()
+
+        indexes = [v.index for v in vertices]
+
+        bmesh.ops.rotate(
+            bm,
+            cent=center,
+            matrix=rot,
+            verts=vertices,
+            space=bpy.context.edit_object.matrix_world
+        )
+        self.sel.refreshMesh(bm, mesh)
+        bm.verts.ensure_lookup_table()
+        rotatedVertices = [bm.verts[i] for i in indexes]
+
+        return rotatedVertices
+
+    def CalculateRadiusAndAngle(self, edgeLength):
+        degAngle = self.a
+        angle = radians(degAngle)
+        self.r = radius = edgeLength / (2 * sin(angle / 2))
+        return radius, angle
+
+    def CalculateRadiusAndAngleForAnglePresets(self, angleEnum, initR, initA, edgeLength):
+        radius = initR
+        angle = initA
+        try:
+            # Note - define an integer string in the angleEnum
+            angle_convert = int(angleEnum)
+            self.a = angle_convert
+        except:
+            self.a = 180  # fallback
+            debugPrintNew(True,
+                          "CalculateRadiusAndAngleForAnglePresets problem with int conversion")
+
+        return self.CalculateRadiusAndAngle(edgeLength)
+
+    def getSpinCenterClosestToRefCenter(self, objLocation, roots):
+        root0Distance = (Vector(objLocation) - Vector(roots[0])).length
+        root1Distance = (Vector(objLocation) - Vector(roots[1])).length
+
+        chosenId = 0
+        rejectedId = 1
+        if (root0Distance > root1Distance):
+            chosenId = 1
+            rejectedId = 0
+        return roots[chosenId], roots[rejectedId]
+
+    def addMissingCoordinate(self, roots, startVertex, plane):
+        if roots is not None:
+            a, b = roots[0]
+            c, d = roots[1]
+            if plane == XY:
+                roots[0] = Vector((a, b, startVertex[2]))
+                roots[1] = Vector((c, d, startVertex[2]))
+            if plane == YZ:
+                roots[0] = Vector((startVertex[0], a, b))
+                roots[1] = Vector((startVertex[0], c, d))
+            if plane == XZ:
+                roots[0] = Vector((a, startVertex[1], b))
+                roots[1] = Vector((c, startVertex[1], d))
+        return roots
+
+    def selectEdgesAfterRoundifier(self, context, edges):
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bpy.ops.object.mode_set(mode='EDIT')
+        mesh = context.view_layer.objects.active.data
+        bmnew = bmesh.new()
+        bmnew.from_mesh(mesh)
+
+        self.deselectEdges(bmnew)
+        for selectedEdge in edges:
+            for e in bmnew.edges:
+                if (e.verts[0].co - selectedEdge.verts[0].co).length <= self.threshold \
+                   and (e.verts[1].co - selectedEdge.verts[1].co).length <= self.threshold:
+                    e.select_set(True)
+
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bmnew.to_mesh(mesh)
+        bmnew.free()
+        bpy.ops.object.mode_set(mode='EDIT')
+
+    def deselectEdges(self, bm):
+        for edge in bm.edges:
+            edge.select_set(False)
+
+    def getSpinAxis(self, plane):
+        axis = (0, 0, 1)
+        if plane == YZ:
+            axis = (1, 0, 0)
+        if plane == XZ:
+            axis = (0, 1, 0)
+        return axis
+
+
+    @classmethod
+    def poll(cls, context):
+        return (context.view_layer.objects.active.type == 'MESH') and (context.view_layer.objects.active.mode == 'EDIT')
+    
+def draw_item(self, context):
+    self.layout.operator_context = 'INVOKE_DEFAULT'
+    self.layout.operator('mesh.edge_roundifier')
+
+    
+classes = (
+           EdgeRoundifier,
+           )
+
+reg_cls, unreg_cls = bpy.utils.register_classes_factory(classes)
+
+
+def register():
+    reg_cls()
+    bpy.types.VIEW3D_MT_edit_mesh_edges.append(draw_item)
+
+
+def unregister():
+    unreg_cls()
+    bpy.types.VIEW3D_MT_edit_mesh_edges.remove(draw_item)
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/mesh_edges_floor_plan.py b/mesh_tools/mesh_edges_floor_plan.py
new file mode 100644
index 0000000000000000000000000000000000000000..1bbd7748fb35a92e8fce71155d8bff817037a890
--- /dev/null
+++ b/mesh_tools/mesh_edges_floor_plan.py
@@ -0,0 +1,384 @@
+# ##### 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; version 2
+#  of the License.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# based upon the functionality of Mesh to wall by luxuy_BlenderCN
+# thanks to meta-androcto
+
+bl_info = {
+    "name": "Edge Floor Plan",
+    "author": "lijenstina",
+    "version": (0, 2),
+    "blender": (2, 78, 0),
+    "location": "View3D > EditMode > Mesh",
+    "description": "Make a Floor Plan from Edges",
+    "wiki_url": "",
+    "category": "Mesh"}
+
+import bpy
+import bmesh
+from bpy.types import Operator
+from bpy.props import (
+        BoolProperty,
+        EnumProperty,
+        FloatProperty,
+        FloatVectorProperty,
+        IntProperty,
+        )
+
+
+# Handle error notifications
+def error_handlers(self, error, reports="ERROR"):
+    if self and reports:
+        self.report({'WARNING'}, reports + " (See Console for more info)")
+
+    print("\n[mesh.edges_floor_plan]\nError: {}\n".format(error))
+
+
+class MESH_OT_edges_floor_plan(Operator):
+    bl_idname = "mesh.edges_floor_plan"
+    bl_label = "Edges Floor Plan"
+    bl_description = "Top View, Extrude Flat Along Edges"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    wid: FloatProperty(
+            name="Wall width:",
+            description="Set the width of the generated walls\n",
+            default=0.1,
+            min=0.001, max=30000
+            )
+    depth: FloatProperty(
+            name="Inner height:",
+            description="Set the height of the inner wall edges",
+            default=0.0,
+            min=0, max=10
+            )
+    connect_ends: BoolProperty(
+            name="Connect Ends",
+            description="Connect the ends of the boundary Edge loops",
+            default=False
+            )
+    repeat_cleanup: IntProperty(
+            name="Recursive Prepare",
+            description="Number of times that the preparation phase runs\n"
+                        "at the start of the script\n"
+                        "If parts of the mesh are not modified, increase this value",
+            min=1, max=20,
+            default=1
+            )
+    fill_items = [
+            ('EDGE_NET', "Edge Net",
+             "Edge Net Method for mesh preparation - Initial Fill\n"
+             "The filled in faces will be Inset individually\n"
+             "Supports simple 3D objects"),
+            ('SINGLE_FACE', "Single Face",
+             "Single Face Method for mesh preparation - Initial Fill\n"
+             "The produced face will be Triangulated before Inset Region\n"
+             "Good for edges forming a circle, avoid 3D objects"),
+            ('SOLIDIFY', "Solidify",
+             "Extrude and Solidify Method\n"
+             "Useful for complex meshes, however works best on flat surfaces\n"
+             "as the extrude direction has to be defined")
+            ]
+    fill_type: EnumProperty(
+            name="Fill Type",
+            items=fill_items,
+            description="Choose the method for creating geometry",
+            default='SOLIDIFY'
+            )
+    keep_faces: BoolProperty(
+            name="Keep Faces",
+            description="Keep or not the fill faces\n"
+                        "Can depend on Remove Ngons state",
+            default=False
+            )
+    tri_faces: BoolProperty(
+            name="Triangulate Faces",
+            description="Triangulate the created fill faces\n"
+                        "Sometimes can lead to unsatisfactory results",
+            default=False
+            )
+    initial_extrude: FloatVectorProperty(
+            name="Initial Extrude",
+            description="",
+            default=(0.0, 0.0, 0.1),
+            min=-20.0, max=20.0,
+            subtype='XYZ',
+            precision=3,
+            size=3
+            )
+    remove_ngons: BoolProperty(
+            name="Remove Ngons",
+            description="Keep or not the Ngon Faces\n"
+                        "Note about limitations:\n"
+                        "Sometimes the kept Faces could be Ngons\n"
+                        "Removing the Ngons can lead to no geometry created",
+            default=True
+            )
+    offset: FloatProperty(
+            name="Wall Offset:",
+            description="Set the offset for the Solidify modifier",
+            default=0.0,
+            min=-1.0, max=1.0
+            )
+    only_rim: BoolProperty(
+            name="Rim Only",
+            description="Solidify Fill Rim only option",
+            default=False
+            )
+
+    @classmethod
+    def poll(cls, context):
+        ob = context.active_object
+        return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def check_edge(self, context):
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bpy.ops.object.mode_set(mode='EDIT')
+        obj = bpy.context.object
+        me_check = obj.data
+        if len(me_check.edges) < 1:
+            return False
+
+        return True
+
+    @staticmethod
+    def ensure(bm):
+        if bm:
+            bm.verts.ensure_lookup_table()
+            bm.edges.ensure_lookup_table()
+            bm.faces.ensure_lookup_table()
+
+    def solidify_mod(self, context, ob, wid, offset, only_rim):
+        try:
+            mods = ob.modifiers.new(
+                        name="_Mesh_Solidify_Wall", type='SOLIDIFY'
+                        )
+            mods.thickness = wid
+            mods.use_quality_normals = True
+            mods.offset = offset
+            mods.use_even_offset = True
+            mods.use_rim = True
+            mods.use_rim_only = only_rim
+            mods.show_on_cage = True
+
+            bpy.ops.object.modifier_apply(
+                        modifier="_Mesh_Solidify_Wall"
+                        )
+        except Exception as e:
+            error_handlers(self, e,
+                           reports="Adding a Solidify Modifier failed")
+            pass
+
+    def draw(self, context):
+        layout = self.layout
+
+        box = layout.box()
+        box.label(text="Choose Method:", icon="NONE")
+        box.prop(self, "fill_type")
+
+        col = box.column(align=True)
+
+        if self.fill_type == 'EDGE_NET':
+            col.prop(self, "repeat_cleanup")
+            col.prop(self, "remove_ngons", toggle=True)
+
+        elif self.fill_type == 'SOLIDIFY':
+            col.prop(self, "offset", slider=True)
+            col.prop(self, "initial_extrude")
+
+        else:
+            col.prop(self, "remove_ngons", toggle=True)
+            col.prop(self, "tri_faces", toggle=True)
+
+        box = layout.box()
+        box.label(text="Settings:", icon="NONE")
+
+        col = box.column(align=True)
+        col.prop(self, "wid")
+
+        if self.fill_type != 'SOLIDIFY':
+            col.prop(self, "depth")
+            col.prop(self, "connect_ends", toggle=True)
+            col.prop(self, "keep_faces", toggle=True)
+        else:
+            col.prop(self, "only_rim", toggle=True)
+
+    def execute(self, context):
+        if not self.check_edge(context):
+            self.report({'WARNING'},
+                        "Operation Cancelled. Needs a Mesh with at least one edge")
+            return {'CANCELLED'}
+
+        wid = self.wid * 0.1
+        depth = self.depth * 0.1
+        offset = self.offset * 0.1
+        store_selection_mode = context.tool_settings.mesh_select_mode
+        # Note: the remove_doubles called after bmesh creation would make
+        # blender crash with certain meshes - keep it in mind for the future
+        bpy.ops.mesh.remove_doubles(threshold=0.003)
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bpy.ops.object.mode_set(mode='EDIT')
+        ob = bpy.context.object
+
+        me = ob.data
+        bm = bmesh.from_edit_mesh(me)
+
+        bmesh.ops.delete(bm, geom=bm.faces, context='FACES_ONLY')
+        self.ensure(bm)
+        context.tool_settings.mesh_select_mode = (False, True, False)
+        original_edges = [edge.index for edge in bm.edges]
+        original_verts = [vert.index for vert in bm.verts]
+        self.ensure(bm)
+        bpy.ops.mesh.select_all(action='DESELECT')
+
+        if self.fill_type == 'EDGE_NET':
+            for i in range(self.repeat_cleanup):
+                bmesh.ops.edgenet_prepare(bm, edges=bm.edges)
+                self.ensure(bm)
+            bmesh.ops.edgenet_fill(bm, edges=bm.edges, mat_nr=0, use_smooth=True, sides=0)
+            self.ensure(bm)
+            if self.remove_ngons:
+                ngons = [face for face in bm.faces if len(face.edges) > 4]
+                self.ensure(bm)
+                bmesh.ops.delete(bm, geom=ngons, context='FACES')  # 5 - delete faces
+                del ngons
+                self.ensure(bm)
+
+        elif self.fill_type == 'SOLIDIFY':
+            for vert in bm.verts:
+                vert.normal_update()
+            self.ensure(bm)
+            bmesh.ops.extrude_edge_only(
+                    bm, edges=bm.edges, use_select_history=False
+                    )
+            self.ensure(bm)
+            verts_extrude = [vert for vert in bm.verts if vert.index in original_verts]
+            self.ensure(bm)
+            bmesh.ops.translate(
+                bm,
+                verts=verts_extrude,
+                vec=(self.initial_extrude)
+                )
+            self.ensure(bm)
+            del verts_extrude
+            self.ensure(bm)
+
+            for edge in bm.edges:
+                if edge.is_boundary:
+                    edge.select = True
+
+            bm = bmesh.update_edit_mesh(ob.data, 1, 1)
+
+            bpy.ops.object.mode_set(mode='OBJECT')
+            self.solidify_mod(context, ob, wid, offset, self.only_rim)
+
+            bpy.ops.object.mode_set(mode='EDIT')
+
+            context.tool_settings.mesh_select_mode = store_selection_mode
+
+            return {'FINISHED'}
+
+        else:
+            bm.faces.new(bm.verts)
+            self.ensure(bm)
+
+            if self.tri_faces:
+                bmesh.ops.triangle_fill(
+                        bm, use_beauty=True, use_dissolve=False, edges=bm.edges
+                        )
+                self.ensure(bm)
+
+        if self.remove_ngons and self.fill_type != 'EDGE_NET':
+            ngons = [face for face in bm.faces if len(face.edges) > 4]
+            self.ensure(bm)
+            bmesh.ops.delete(bm, geom=ngons, context='FACES')  # 5 - delete faces
+            del ngons
+            self.ensure(bm)
+
+        del_boundary = [edge for edge in bm.edges if edge.index not in original_edges]
+        self.ensure(bm)
+
+        del original_edges
+        self.ensure(bm)
+
+        if self.fill_type == 'EDGE_NET':
+            extrude_inner = bmesh.ops.inset_individual(
+                    bm, faces=bm.faces, thickness=wid, depth=depth,
+                    use_even_offset=True, use_interpolate=False,
+                    use_relative_offset=False
+                    )
+        else:
+            extrude_inner = bmesh.ops.inset_region(
+                    bm, faces=bm.faces, faces_exclude=[], use_boundary=True,
+                    use_even_offset=True, use_interpolate=False,
+                    use_relative_offset=False, use_edge_rail=False,
+                    thickness=wid, depth=depth, use_outset=False
+                    )
+        self.ensure(bm)
+
+        del_faces = [faces for faces in bm.faces if faces not in extrude_inner["faces"]]
+        self.ensure(bm)
+        del extrude_inner
+        self.ensure(bm)
+
+        if not self.keep_faces:
+            bmesh.ops.delete(bm, geom=del_faces, context='FACES')  # 5 delete faces
+        del del_faces
+        self.ensure(bm)
+
+        face_del = set()
+        for face in bm.faces:
+            for edge in del_boundary:
+                if isinstance(edge, bmesh.types.BMEdge):
+                    if edge in face.edges:
+                        face_del.add(face)
+        self.ensure(bm)
+        face_del = list(face_del)
+        self.ensure(bm)
+
+        del del_boundary
+        self.ensure(bm)
+
+        if not self.connect_ends:
+            bmesh.ops.delete(bm, geom=face_del, context='FACES')
+            self.ensure(bm)
+
+        del face_del
+        self.ensure(bm)
+
+        for edge in bm.edges:
+            if edge.is_boundary:
+                edge.select = True
+
+        bm = bmesh.update_edit_mesh(ob.data, 1, 1)
+
+        context.tool_settings.mesh_select_mode = store_selection_mode
+
+        return {'FINISHED'}
+
+
+def register():
+    bpy.utils.register_class(MESH_OT_edges_floor_plan)
+
+
+def unregister():
+    bpy.utils.unregister_class(MESH_OT_edges_floor_plan)
+
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/mesh_edges_length.py b/mesh_tools/mesh_edges_length.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4f1d0679ebb42ed9953c3357e1f763bc25fbe19
--- /dev/null
+++ b/mesh_tools/mesh_edges_length.py
@@ -0,0 +1,341 @@
+# gpl author: Giuseppe De Marco [BlenderLab] inspired by NirenYang
+
+bl_info = {
+    "name": "Set edges length",
+    "description": "Edges length",
+    "author": "Giuseppe De Marco [BlenderLab] inspired by NirenYang",
+    "version": (0, 1, 0),
+    "blender": (2, 71, 0),
+    "location": "Toolbar > Tools > Mesh Tools: set Length(Shit+Alt+E)",
+    "warning": "",
+    "wiki_url": "",
+    "category": "Mesh",
+    }
+
+import bpy
+import bmesh
+from mathutils import Vector
+from bpy.types import Operator
+from bpy.props import (
+        FloatProperty,
+        EnumProperty,
+        )
+
+# GLOBALS
+edge_length_debug = False
+_error_message = "Please select at least one edge to fill select history"
+_error_message_2 = "Edges with shared vertices are not allowed. Please, use scale instead"
+
+# Note : Refactor - removed all the operators apart from LengthSet
+#        and merged the other ones as options of length (lijenstina)
+
+
+def get_edge_vector(edge):
+    verts = (edge.verts[0].co, edge.verts[1].co)
+    vector = verts[1] - verts[0]
+
+    return vector
+
+
+def get_selected(bmesh_obj, geometry_type):
+    # geometry type should be edges, verts or faces
+    selected = []
+
+    for i in getattr(bmesh_obj, geometry_type):
+        if i.select:
+            selected.append(i)
+    return tuple(selected)
+
+
+def get_center_vector(verts):
+    # verts = [Vector((x,y,z)), Vector((x,y,z))]
+
+    center_vector = Vector((((verts[1][0] + verts[0][0]) / 2.),
+                           ((verts[1][1] + verts[0][1]) / 2.),
+                           ((verts[1][2] + verts[0][2]) / 2.)))
+    return center_vector
+
+
+class LengthSet(Operator):
+    bl_idname = "object.mesh_edge_length_set"
+    bl_label = "Set edge length"
+    bl_description = ("Change one selected edge length by a specified target,\n"
+                      "existing length and different modes\n"
+                      "Note: works only with Edges that not share a vertex")
+    bl_options = {'REGISTER', 'UNDO'}
+
+    old_length: FloatProperty(
+            name="Original length",
+            options={'HIDDEN'},
+            )
+    set_length_type: EnumProperty(
+            items=[
+                ('manual', "Manual",
+                 "Input manually the desired Target Length"),
+                ('existing', "Existing Length",
+                 "Use existing geometry Edges' characteristics"),
+            ],
+            name="Set Type of Input",
+            )
+    target_length: FloatProperty(
+            name="Target Length",
+            description="Input a value for an Edges Length target",
+            default=1.00,
+            unit='LENGTH',
+            precision=5
+            )
+    existing_length: EnumProperty(
+            items=[
+                ('min', "Shortest",
+                 "Set all to shortest Edge of selection"),
+                ('max', "Longest",
+                 "Set all to the longest Edge of selection"),
+                ('average', "Average",
+                 "Set all to the average Edge length of selection"),
+                ('active', "Active",
+                 "Set all to the active Edge's one\n"
+                 "Needs a selection to be done in Edge Select mode"),
+            ],
+            name="Existing length"
+            )
+    mode: EnumProperty(
+            items=[
+                ('fixed', "Fixed", "Fixed"),
+                ('increment', "Increment", "Increment"),
+                ('decrement', "Decrement", "Decrement"),
+            ],
+            name="Mode"
+            )
+    behaviour: EnumProperty(
+            items=[
+                ('proportional', "Proportional",
+                 "Move vertex locations proportionally to the center of the Edge"),
+                ('clockwise', "Clockwise",
+                "Compute the Edges' vertex locations in a clockwise fashion"),
+                ('unclockwise', "Counterclockwise",
+                "Compute the Edges' vertex locations in a counterclockwise fashion"),
+            ],
+            name="Resize behavior"
+            )
+
+    originary_edge_length_dict = {}
+    edge_lengths = []
+    selected_edges = ()
+
+    @classmethod
+    def poll(cls, context):
+        return (context.edit_object and context.object.type == 'MESH')
+
+    def check(self, context):
+        return True
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.label(text="Original Active length is: {:.3f}".format(self.old_length))
+
+        layout.label(text="Input Mode:")
+        layout.prop(self, "set_length_type", expand=True)
+        if self.set_length_type == 'manual':
+            layout.prop(self, "target_length")
+        else:
+            layout.prop(self, "existing_length", text="")
+
+        layout.label(text="Mode:")
+        layout.prop(self, "mode", text="")
+
+        layout.label(text="Resize Behavior:")
+        layout.prop(self, "behaviour", text="")
+
+    def get_existing_edge_length(self, bm):
+        if self.existing_length != "active":
+            if self.existing_length == "min":
+                return min(self.edge_lengths)
+            if self.existing_length == "max":
+                return max(self.edge_lengths)
+            elif self.existing_length == "average":
+                return sum(self.edge_lengths) / float(len(self.selected_edges))
+        else:
+            bm.edges.ensure_lookup_table()
+            active_edge_length = None
+
+            for elem in reversed(bm.select_history):
+                if isinstance(elem, bmesh.types.BMEdge):
+                    active_edge_length = elem.calc_length()
+                    break
+            return active_edge_length
+
+        return 0.0
+
+    def invoke(self, context, event):
+        wm = context.window_manager
+
+        obj = context.edit_object
+        bm = bmesh.from_edit_mesh(obj.data)
+
+        bpy.ops.mesh.select_mode(type="EDGE")
+        self.selected_edges = get_selected(bm, 'edges')
+
+        if self.selected_edges:
+            vertex_set = []
+
+            for edge in self.selected_edges:
+                vector = get_edge_vector(edge)
+
+                if edge.verts[0].index not in vertex_set:
+                    vertex_set.append(edge.verts[0].index)
+                else:
+                    self.report({'ERROR_INVALID_INPUT'}, _error_message_2)
+                    return {'CANCELLED'}
+
+                if edge.verts[1].index not in vertex_set:
+                    vertex_set.append(edge.verts[1].index)
+                else:
+                    self.report({'ERROR_INVALID_INPUT'}, _error_message_2)
+                    return {'CANCELLED'}
+
+                # warning, it's a constant !
+                verts_index = ''.join((str(edge.verts[0].index), str(edge.verts[1].index)))
+                self.originary_edge_length_dict[verts_index] = vector
+                self.edge_lengths.append(vector.length)
+                self.old_length = vector.length
+        else:
+            self.report({'ERROR'}, _error_message)
+            return {'CANCELLED'}
+
+        if edge_length_debug:
+            self.report({'INFO'}, str(self.originary_edge_length_dict))
+
+        if bpy.context.scene.unit_settings.system == 'IMPERIAL':
+            # imperial to metric conversion
+            vector.length = (0.9144 * vector.length) / 3
+
+        self.target_length = vector.length
+
+        return wm.invoke_props_dialog(self)
+
+    def execute(self, context):
+
+        bpy.ops.mesh.select_mode(type="EDGE")
+        self.context = context
+
+        obj = context.edit_object
+        bm = bmesh.from_edit_mesh(obj.data)
+
+        self.selected_edges = get_selected(bm, 'edges')
+
+        if not self.selected_edges:
+            self.report({'ERROR'}, _error_message)
+            return {'CANCELLED'}
+
+        for edge in self.selected_edges:
+            vector = get_edge_vector(edge)
+            # what we should see in original length dialog field
+            self.old_length = vector.length
+
+            if self.set_length_type == 'manual':
+                vector.length = abs(self.target_length)
+            else:
+                get_lengths = self.get_existing_edge_length(bm)
+                # check for edit mode
+                if not get_lengths:
+                    self.report({'WARNING'},
+                                "Operation Cancelled. "
+                                "Active Edge could not be determined (needs selection in Edit Mode)")
+                    return {'CANCELLED'}
+
+                vector.length = get_lengths
+
+            if vector.length == 0.0:
+                self.report({'ERROR'}, "Operation cancelled. Target length is set to zero")
+                return {'CANCELLED'}
+
+            center_vector = get_center_vector((edge.verts[0].co, edge.verts[1].co))
+
+            verts_index = ''.join((str(edge.verts[0].index), str(edge.verts[1].index)))
+
+            if edge_length_debug:
+                self.report({'INFO'},
+                            ' - '.join(('vector ' + str(vector),
+                                        'originary_vector ' +
+                                        str(self.originary_edge_length_dict[verts_index])
+                                        )))
+            verts = (edge.verts[0].co, edge.verts[1].co)
+
+            if edge_length_debug:
+                self.report({'INFO'},
+                            '\n edge.verts[0].co ' + str(verts[0]) +
+                            '\n edge.verts[1].co ' + str(verts[1]) +
+                            '\n vector.length' + str(vector.length))
+
+            # the clockwise direction have v1 -> v0, unclockwise v0 -> v1
+            if self.target_length >= 0:
+                if self.behaviour == 'proportional':
+                    edge.verts[1].co = center_vector + vector / 2
+                    edge.verts[0].co = center_vector - vector / 2
+
+                    if self.mode == 'decrement':
+                        edge.verts[0].co = (center_vector + vector / 2) - \
+                                            (self.originary_edge_length_dict[verts_index] / 2)
+                        edge.verts[1].co = (center_vector - vector / 2) + \
+                                            (self.originary_edge_length_dict[verts_index] / 2)
+
+                    elif self.mode == 'increment':
+                        edge.verts[1].co = (center_vector + vector / 2) + \
+                                            self.originary_edge_length_dict[verts_index] / 2
+                        edge.verts[0].co = (center_vector - vector / 2) - \
+                                            self.originary_edge_length_dict[verts_index] / 2
+
+                elif self.behaviour == 'unclockwise':
+                    if self.mode == 'increment':
+                        edge.verts[1].co = \
+                                verts[0] + (self.originary_edge_length_dict[verts_index] + vector)
+                    elif self.mode == 'decrement':
+                        edge.verts[0].co = \
+                                verts[1] - (self.originary_edge_length_dict[verts_index] - vector)
+                    else:
+                        edge.verts[1].co = verts[0] + vector
+
+                else:
+                    # clockwise
+                    if self.mode == 'increment':
+                        edge.verts[0].co = \
+                                verts[1] - (self.originary_edge_length_dict[verts_index] + vector)
+                    elif self.mode == 'decrement':
+                        edge.verts[1].co = \
+                                verts[0] + (self.originary_edge_length_dict[verts_index] - vector)
+                    else:
+                        edge.verts[0].co = verts[1] - vector
+
+            if bpy.context.scene.unit_settings.system == 'IMPERIAL':
+                """
+                # yards to metric conversion
+                vector.length = ( 3. * vector.length ) / 0.9144
+                # metric to yards conversion
+                vector.length = ( 0.9144 * vector.length ) / 3.
+                """
+                for mvert in edge.verts:
+                    # school time: 0.9144 : 3 = X : mvert
+                    mvert.co = (0.9144 * mvert.co) / 3
+
+            if edge_length_debug:
+                self.report({'INFO'},
+                            '\n edge.verts[0].co' + str(verts[0]) +
+                            '\n edge.verts[1].co' + str(verts[1]) +
+                            '\n vector' + str(vector) + '\n v1 > v0:' + str((verts[1] >= verts[0]))
+                            )
+            bmesh.update_edit_mesh(obj.data, True)
+
+        return {'FINISHED'}
+
+
+def register():
+    bpy.utils.register_class(LengthSet)
+
+
+def unregister():
+    bpy.utils.unregister_class(LengthSet)
+
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/mesh_edgetools.py b/mesh_tools/mesh_edgetools.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fd98b75f7e93fd1b3f7d9249d2088edd26378d9
--- /dev/null
+++ b/mesh_tools/mesh_edgetools.py
@@ -0,0 +1,1880 @@
+#  The Blender Edgetools is to bring CAD tools to Blender.
+#  Copyright (C) 2012  Paul Marshall
+
+# ##### 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 #####
+
+# <pep8 compliant>
+
+bl_info = {
+    "name": "EdgeTools",
+    "author": "Paul Marshall",
+    "version": (0, 9, 2),
+    "blender": (2, 80, 0),
+    "location": "View3D > Toolbar and View3D > Specials (W-key)",
+    "warning": "",
+    "description": "CAD style edge manipulation tools",
+    "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
+                "Scripts/Modeling/EdgeTools",
+    "category": "Mesh"}
+
+
+import bpy
+import bmesh
+from bpy.types import (
+        Operator,
+        Menu,
+        )
+from math import acos, pi, radians, sqrt
+from mathutils import Matrix, Vector
+from mathutils.geometry import (
+        distance_point_to_plane,
+        interpolate_bezier,
+        intersect_point_line,
+        intersect_line_line,
+        intersect_line_plane,
+        )
+from bpy.props import (
+        BoolProperty,
+        IntProperty,
+        FloatProperty,
+        EnumProperty,
+       )
+
+"""
+Blender EdgeTools
+This is a toolkit for edge manipulation based on mesh manipulation
+abilities of several CAD/CAE packages, notably CATIA's Geometric Workbench
+from which most of these tools have a functional basis.
+
+The GUI and Blender add-on structure shamelessly coded in imitation of the
+LoopTools addon.
+
+Examples:
+- "Ortho" inspired from CATIA's line creation tool which creates a line of a
+   user specified length at a user specified angle to a curve at a chosen
+   point.  The user then selects the plane the line is to be created in.
+- "Shaft" is inspired from CATIA's tool of the same name.  However, instead
+   of a curve around an axis, this will instead shaft a line, a point, or
+   a fixed radius about the selected axis.
+- "Slice" is from CATIA's ability to split a curve on a plane.  When
+   completed this be a Python equivalent with all the same basic
+   functionality, though it will sadly be a little clumsier to use due
+   to Blender's selection limitations.
+
+Notes:
+- Fillet operator and related functions removed as they didn't work
+- Buggy parts have been hidden behind ENABLE_DEBUG global (set it to True)
+   Example: Shaft with more than two edges selected
+
+Paul "BrikBot" Marshall
+Created: January 28, 2012
+Last Modified: October 6, 2012
+
+Coded in IDLE, tested in Blender 2.6.
+Search for "@todo" to quickly find sections that need work
+
+Note: lijenstina - modified this script in preparation for merging
+fixed the needless jumping to object mode for bmesh creation
+causing the crash with the Slice > Rip operator
+Removed the test operator since version 0.9.2
+added general error handling
+"""
+
+# Enable debug
+# Set to True to have the debug prints available
+ENABLE_DEBUG = False
+
+
+# Quick an dirty method for getting the sign of a number:
+def sign(number):
+    return (number > 0) - (number < 0)
+
+
+# is_parallel
+# Checks to see if two lines are parallel
+
+def is_parallel(v1, v2, v3, v4):
+    result = intersect_line_line(v1, v2, v3, v4)
+    return result is None
+
+
+# Handle error notifications
+def error_handlers(self, op_name, error, reports="ERROR", func=False):
+    if self and reports:
+        self.report({'WARNING'}, reports + " (See Console for more info)")
+
+    is_func = "Function" if func else "Operator"
+    print("\n[Mesh EdgeTools]\n{}: {}\nError: {}\n".format(is_func, op_name, error))
+
+
+def flip_edit_mode():
+    bpy.ops.object.editmode_toggle()
+    bpy.ops.object.editmode_toggle()
+
+
+# check the appropriate selection condition
+# to prevent crashes with the index out of range errors
+# pass the bEdges and bVerts based selection tables here
+# types: Edge, Vertex, All
+def is_selected_enough(self, bEdges, bVerts, edges_n=1, verts_n=0, types="Edge"):
+    check = False
+    try:
+        if bEdges and types == "Edge":
+            check = (len(bEdges) >= edges_n)
+        elif bVerts and types == "Vertex":
+            check = (len(bVerts) >= verts_n)
+        elif bEdges and bVerts and types == "All":
+            check = (len(bEdges) >= edges_n and len(bVerts) >= verts_n)
+
+        if check is False:
+            strings = "%s Vertices and / or " % verts_n if verts_n != 0 else ""
+            self.report({'WARNING'},
+                        "Needs at least " + strings + "%s Edge(s) selected. "
+                        "Operation Cancelled" % edges_n)
+            flip_edit_mode()
+
+        return check
+
+    except Exception as e:
+        error_handlers(self, "is_selected_enough", e,
+                      "No appropriate selection. Operation Cancelled", func=True)
+        return False
+
+    return False
+
+
+# is_axial
+# This is for the special case where the edge is parallel to an axis.
+# The projection onto the XY plane will fail so it will have to be handled differently
+
+def is_axial(v1, v2, error=0.000002):
+    vector = v2 - v1
+    # Don't need to store, but is easier to read:
+    vec0 = vector[0] > -error and vector[0] < error
+    vec1 = vector[1] > -error and vector[1] < error
+    vec2 = vector[2] > -error and vector[2] < error
+    if (vec0 or vec1) and vec2:
+        return 'Z'
+    elif vec0 and vec1:
+        return 'Y'
+    return None
+
+
+# is_same_co
+# For some reason "Vector = Vector" does not seem to look at the actual coordinates
+
+def is_same_co(v1, v2):
+    if len(v1) != len(v2):
+        return False
+    else:
+        for co1, co2 in zip(v1, v2):
+            if co1 != co2:
+                return False
+    return True
+
+
+def is_face_planar(face, error=0.0005):
+    for v in face.verts:
+        d = distance_point_to_plane(v.co, face.verts[0].co, face.normal)
+        if ENABLE_DEBUG:
+            print("Distance: " + str(d))
+        if d < -error or d > error:
+            return False
+    return True
+
+
+# other_joined_edges
+# Starts with an edge. Then scans for linked, selected edges and builds a
+# list with them in "order", starting at one end and moving towards the other
+
+def order_joined_edges(edge, edges=[], direction=1):
+    if len(edges) == 0:
+        edges.append(edge)
+        edges[0] = edge
+
+    if ENABLE_DEBUG:
+        print(edge, end=", ")
+        print(edges, end=", ")
+        print(direction, end="; ")
+
+    # Robustness check: direction cannot be zero
+    if direction == 0:
+        direction = 1
+
+    newList = []
+    for e in edge.verts[0].link_edges:
+        if e.select and edges.count(e) == 0:
+            if direction > 0:
+                edges.insert(0, e)
+                newList.extend(order_joined_edges(e, edges, direction + 1))
+                newList.extend(edges)
+            else:
+                edges.append(e)
+                newList.extend(edges)
+                newList.extend(order_joined_edges(e, edges, direction - 1))
+
+    # This will only matter at the first level:
+    direction = direction * -1
+
+    for e in edge.verts[1].link_edges:
+        if e.select and edges.count(e) == 0:
+            if direction > 0:
+                edges.insert(0, e)
+                newList.extend(order_joined_edges(e, edges, direction + 2))
+                newList.extend(edges)
+            else:
+                edges.append(e)
+                newList.extend(edges)
+                newList.extend(order_joined_edges(e, edges, direction))
+
+    if ENABLE_DEBUG:
+        print(newList, end=", ")
+        print(direction)
+
+    return newList
+
+
+# --------------- GEOMETRY CALCULATION METHODS --------------
+
+# distance_point_line
+# I don't know why the mathutils.geometry API does not already have this, but
+# it is trivial to code using the structures already in place. Instead of
+# returning a float, I also want to know the direction vector defining the
+# distance. Distance can be found with "Vector.length"
+
+def distance_point_line(pt, line_p1, line_p2):
+    int_co = intersect_point_line(pt, line_p1, line_p2)
+    distance_vector = int_co[0] - pt
+    return distance_vector
+
+
+# interpolate_line_line
+# This is an experiment into a cubic Hermite spline (c-spline) for connecting
+# two edges with edges that obey the general equation.
+# This will return a set of point coordinates (Vectors)
+#
+# A good, easy to read background on the mathematics can be found at:
+# http://cubic.org/docs/hermite.htm
+#
+# Right now this is . . . less than functional :P
+# @todo
+#   - C-Spline and Bezier curves do not end on p2_co as they are supposed to.
+#   - B-Spline just fails.  Epically.
+#   - Add more methods as I come across them.  Who said flexibility was bad?
+
+def interpolate_line_line(p1_co, p1_dir, p2_co, p2_dir, segments, tension=1,
+                          typ='BEZIER', include_ends=False):
+    pieces = []
+    fraction = 1 / segments
+
+    # Form: p1, tangent 1, p2, tangent 2
+    if typ == 'HERMITE':
+        poly = [[2, -3, 0, 1], [1, -2, 1, 0],
+                [-2, 3, 0, 0], [1, -1, 0, 0]]
+    elif typ == 'BEZIER':
+        poly = [[-1, 3, -3, 1], [3, -6, 3, 0],
+                [1, 0, 0, 0], [-3, 3, 0, 0]]
+        p1_dir = p1_dir + p1_co
+        p2_dir = -p2_dir + p2_co
+    elif typ == 'BSPLINE':
+        # Supposed poly matrix for a cubic b-spline:
+        # poly = [[-1, 3, -3, 1], [3, -6, 3, 0],
+        #         [-3, 0, 3, 0], [1, 4, 1, 0]]
+        # My own invention to try to get something that somewhat acts right
+        # This is semi-quadratic rather than fully cubic:
+        poly = [[0, -1, 0, 1], [1, -2, 1, 0],
+                [0, -1, 2, 0], [1, -1, 0, 0]]
+
+    if include_ends:
+        pieces.append(p1_co)
+
+    # Generate each point:
+    for i in range(segments - 1):
+        t = fraction * (i + 1)
+        if ENABLE_DEBUG:
+            print(t)
+        s = [t ** 3, t ** 2, t, 1]
+        h00 = (poly[0][0] * s[0]) + (poly[0][1] * s[1]) + (poly[0][2] * s[2]) + (poly[0][3] * s[3])
+        h01 = (poly[1][0] * s[0]) + (poly[1][1] * s[1]) + (poly[1][2] * s[2]) + (poly[1][3] * s[3])
+        h10 = (poly[2][0] * s[0]) + (poly[2][1] * s[1]) + (poly[2][2] * s[2]) + (poly[2][3] * s[3])
+        h11 = (poly[3][0] * s[0]) + (poly[3][1] * s[1]) + (poly[3][2] * s[2]) + (poly[3][3] * s[3])
+        pieces.append((h00 * p1_co) + (h01 * p1_dir) + (h10 * p2_co) + (h11 * p2_dir))
+    if include_ends:
+        pieces.append(p2_co)
+
+    # Return:
+    if len(pieces) == 0:
+        return None
+    else:
+        if ENABLE_DEBUG:
+            print(pieces)
+        return pieces
+
+
+# intersect_line_face
+
+# Calculates the coordinate of intersection of a line with a face.  It returns
+# the coordinate if one exists, otherwise None.  It can only deal with tris or
+# quads for a face. A quad does NOT have to be planar
+"""
+Quad math and theory:
+A quad may not be planar. Therefore the treated definition of the surface is
+that the surface is composed of all lines bridging two other lines defined by
+the given four points. The lines do not "cross"
+
+The two lines in 3-space can defined as:
+┌  ┐         ┌   ┐     ┌   ┐  ┌  ┐         ┌   ┐     ┌   ┐
+│x1│         │a11│     │b11│  │x2│         │a21│     │b21│
+│y1│ = (1-t1)│a12│ + t1│b12│, │y2│ = (1-t2)│a22│ + t2│b22│
+│z1│         │a13│     │b13│  │z2│         │a23│     │b23│
+└  ┘         └   ┘     └   ┘  └  ┘         └   ┘     └   ┘
+Therefore, the surface is the lines defined by every point alone the two
+lines with a same "t" value (t1 = t2). This is basically R = V1 + tQ, where
+Q = V2 - V1 therefore R = V1 + t(V2 - V1) -> R = (1 - t)V1 + tV2:
+┌   ┐            ┌                  ┐      ┌                  ┐
+│x12│            │(1-t)a11 + t * b11│      │(1-t)a21 + t * b21│
+│y12│ = (1 - t12)│(1-t)a12 + t * b12│ + t12│(1-t)a22 + t * b22│
+│z12│            │(1-t)a13 + t * b13│      │(1-t)a23 + t * b23│
+└   ┘            └                  ┘      └                  ┘
+Now, the equation of our line can be likewise defined:
+┌  ┐   ┌   ┐     ┌   ┐
+│x3│   │a31│     │b31│
+│y3│ = │a32│ + t3│b32│
+│z3│   │a33│     │b33│
+└  ┘   └   ┘     └   ┘
+Now we just have to find a valid solution for the two equations.  This should
+be our point of intersection. Therefore, x12 = x3 -> x, y12 = y3 -> y,
+z12 = z3 -> z.  Thus, to find that point we set the equation defining the
+surface as equal to the equation for the line:
+        ┌                  ┐      ┌                  ┐   ┌   ┐     ┌   ┐
+        │(1-t)a11 + t * b11│      │(1-t)a21 + t * b21│   │a31│     │b31│
+(1 - t12)│(1-t)a12 + t * b12│ + t12│(1-t)a22 + t * b22│ = │a32│ + t3│b32│
+        │(1-t)a13 + t * b13│      │(1-t)a23 + t * b23│   │a33│     │b33│
+        └                  ┘      └                  ┘   └   ┘     └   ┘
+This leaves us with three equations, three unknowns.  Solving the system by
+hand is practically impossible, but using Mathematica we are given an insane
+series of three equations (not reproduced here for the sake of space: see
+http://www.mediafire.com/file/cc6m6ba3sz2b96m/intersect_line_surface.nb and
+http://www.mediafire.com/file/0egbr5ahg14talm/intersect_line_surface2.nb for
+Mathematica computation).
+
+Additionally, the resulting series of equations may result in a div by zero
+exception if the line in question if parallel to one of the axis or if the
+quad is planar and parallel to either the XY, XZ, or YZ planes. However, the
+system is still solvable but must be dealt with a little differently to avaid
+these special cases. Because the resulting equations are a little different,
+we have to code them differently. 00Hence the special cases.
+
+Tri math and theory:
+A triangle must be planar (three points define a plane). So we just
+have to make sure that the line intersects inside the triangle.
+
+If the point is within the triangle, then the angle between the lines that
+connect the point to the each individual point of the triangle will be
+equal to 2 * PI. Otherwise, if the point is outside the triangle, then the
+sum of the angles will be less.
+"""
+# @todo
+# - Figure out how to deal with n-gons
+# How the heck is a face with 8 verts defined mathematically?
+# How do I then find the intersection point of a line with said vert?
+# How do I know if that point is "inside" all the verts?
+# I have no clue, and haven't been able to find anything on it so far
+# Maybe if someone (actually reads this and) who knows could note?
+
+
+def intersect_line_face(edge, face, is_infinite=False, error=0.000002):
+    int_co = None
+
+    # If we are dealing with a non-planar quad:
+    if len(face.verts) == 4 and not is_face_planar(face):
+        edgeA = face.edges[0]
+        edgeB = None
+        flipB = False
+
+        for i in range(len(face.edges)):
+            if face.edges[i].verts[0] not in edgeA.verts and \
+               face.edges[i].verts[1] not in edgeA.verts:
+
+                edgeB = face.edges[i]
+                break
+
+        # I haven't figured out a way to mix this in with the above. Doing so might remove a
+        # few extra instructions from having to be executed saving a few clock cycles:
+        for i in range(len(face.edges)):
+            if face.edges[i] == edgeA or face.edges[i] == edgeB:
+                continue
+            if ((edgeA.verts[0] in face.edges[i].verts and
+               edgeB.verts[1] in face.edges[i].verts) or
+               (edgeA.verts[1] in face.edges[i].verts and edgeB.verts[0] in face.edges[i].verts)):
+
+                flipB = True
+                break
+
+        # Define calculation coefficient constants:
+        # "xx1" is the x coordinate, "xx2" is the y coordinate, and "xx3" is the z coordinate
+        a11, a12, a13 = edgeA.verts[0].co[0], edgeA.verts[0].co[1], edgeA.verts[0].co[2]
+        b11, b12, b13 = edgeA.verts[1].co[0], edgeA.verts[1].co[1], edgeA.verts[1].co[2]
+
+        if flipB:
+            a21, a22, a23 = edgeB.verts[1].co[0], edgeB.verts[1].co[1], edgeB.verts[1].co[2]
+            b21, b22, b23 = edgeB.verts[0].co[0], edgeB.verts[0].co[1], edgeB.verts[0].co[2]
+        else:
+            a21, a22, a23 = edgeB.verts[0].co[0], edgeB.verts[0].co[1], edgeB.verts[0].co[2]
+            b21, b22, b23 = edgeB.verts[1].co[0], edgeB.verts[1].co[1], edgeB.verts[1].co[2]
+        a31, a32, a33 = edge.verts[0].co[0], edge.verts[0].co[1], edge.verts[0].co[2]
+        b31, b32, b33 = edge.verts[1].co[0], edge.verts[1].co[1], edge.verts[1].co[2]
+
+        # There are a bunch of duplicate "sub-calculations" inside the resulting
+        # equations for t, t12, and t3.  Calculate them once and store them to
+        # reduce computational time:
+        m01 = a13 * a22 * a31
+        m02 = a12 * a23 * a31
+        m03 = a13 * a21 * a32
+        m04 = a11 * a23 * a32
+        m05 = a12 * a21 * a33
+        m06 = a11 * a22 * a33
+        m07 = a23 * a32 * b11
+        m08 = a22 * a33 * b11
+        m09 = a23 * a31 * b12
+        m10 = a21 * a33 * b12
+        m11 = a22 * a31 * b13
+        m12 = a21 * a32 * b13
+        m13 = a13 * a32 * b21
+        m14 = a12 * a33 * b21
+        m15 = a13 * a31 * b22
+        m16 = a11 * a33 * b22
+        m17 = a12 * a31 * b23
+        m18 = a11 * a32 * b23
+        m19 = a13 * a22 * b31
+        m20 = a12 * a23 * b31
+        m21 = a13 * a32 * b31
+        m22 = a23 * a32 * b31
+        m23 = a12 * a33 * b31
+        m24 = a22 * a33 * b31
+        m25 = a23 * b12 * b31
+        m26 = a33 * b12 * b31
+        m27 = a22 * b13 * b31
+        m28 = a32 * b13 * b31
+        m29 = a13 * b22 * b31
+        m30 = a33 * b22 * b31
+        m31 = a12 * b23 * b31
+        m32 = a32 * b23 * b31
+        m33 = a13 * a21 * b32
+        m34 = a11 * a23 * b32
+        m35 = a13 * a31 * b32
+        m36 = a23 * a31 * b32
+        m37 = a11 * a33 * b32
+        m38 = a21 * a33 * b32
+        m39 = a23 * b11 * b32
+        m40 = a33 * b11 * b32
+        m41 = a21 * b13 * b32
+        m42 = a31 * b13 * b32
+        m43 = a13 * b21 * b32
+        m44 = a33 * b21 * b32
+        m45 = a11 * b23 * b32
+        m46 = a31 * b23 * b32
+        m47 = a12 * a21 * b33
+        m48 = a11 * a22 * b33
+        m49 = a12 * a31 * b33
+        m50 = a22 * a31 * b33
+        m51 = a11 * a32 * b33
+        m52 = a21 * a32 * b33
+        m53 = a22 * b11 * b33
+        m54 = a32 * b11 * b33
+        m55 = a21 * b12 * b33
+        m56 = a31 * b12 * b33
+        m57 = a12 * b21 * b33
+        m58 = a32 * b21 * b33
+        m59 = a11 * b22 * b33
+        m60 = a31 * b22 * b33
+        m61 = a33 * b12 * b21
+        m62 = a32 * b13 * b21
+        m63 = a33 * b11 * b22
+        m64 = a31 * b13 * b22
+        m65 = a32 * b11 * b23
+        m66 = a31 * b12 * b23
+        m67 = b13 * b22 * b31
+        m68 = b12 * b23 * b31
+        m69 = b13 * b21 * b32
+        m70 = b11 * b23 * b32
+        m71 = b12 * b21 * b33
+        m72 = b11 * b22 * b33
+        n01 = m01 - m02 - m03 + m04 + m05 - m06
+        n02 = -m07 + m08 + m09 - m10 - m11 + m12 + m13 - m14 - m15 + m16 + m17 - m18 - \
+              m25 + m27 + m29 - m31 + m39 - m41 - m43 + m45 - m53 + m55 + m57 - m59
+        n03 = -m19 + m20 + m33 - m34 - m47 + m48
+        n04 = m21 - m22 - m23 + m24 - m35 + m36 + m37 - m38 + m49 - m50 - m51 + m52
+        n05 = m26 - m28 - m30 + m32 - m40 + m42 + m44 - m46 + m54 - m56 - m58 + m60
+        n06 = m61 - m62 - m63 + m64 + m65 - m66 - m67 + m68 + m69 - m70 - m71 + m72
+        n07 = 2 * n01 + n02 + 2 * n03 + n04 + n05
+        n08 = n01 + n02 + n03 + n06
+
+        # Calculate t, t12, and t3:
+        t = (n07 - sqrt(pow(-n07, 2) - 4 * (n01 + n03 + n04) * n08)) / (2 * n08)
+
+        # t12 can be greatly simplified by defining it with t in it:
+        # If block used to help prevent any div by zero error.
+        t12 = 0
+
+        if a31 == b31:
+            # The line is parallel to the z-axis:
+            if a32 == b32:
+                t12 = ((a11 - a31) + (b11 - a11) * t) / ((a21 - a11) + (a11 - a21 - b11 + b21) * t)
+            # The line is parallel to the y-axis:
+            elif a33 == b33:
+                t12 = ((a11 - a31) + (b11 - a11) * t) / ((a21 - a11) + (a11 - a21 - b11 + b21) * t)
+            # The line is along the y/z-axis but is not parallel to either:
+            else:
+                t12 = -(-(a33 - b33) * (-a32 + a12 * (1 - t) + b12 * t) + (a32 - b32) *
+                        (-a33 + a13 * (1 - t) + b13 * t)) / (-(a33 - b33) *
+                        ((a22 - a12) * (1 - t) + (b22 - b12) * t) + (a32 - b32) *
+                        ((a23 - a13) * (1 - t) + (b23 - b13) * t))
+        elif a32 == b32:
+            # The line is parallel to the x-axis:
+            if a33 == b33:
+                t12 = ((a12 - a32) + (b12 - a12) * t) / ((a22 - a12) + (a12 - a22 - b12 + b22) * t)
+            # The line is along the x/z-axis but is not parallel to either:
+            else:
+                t12 = -(-(a33 - b33) * (-a31 + a11 * (1 - t) + b11 * t) + (a31 - b31) * (-a33 + a13 *
+                      (1 - t) + b13 * t)) / (-(a33 - b33) * ((a21 - a11) * (1 - t) + (b21 - b11) * t) +
+                      (a31 - b31) * ((a23 - a13) * (1 - t) + (b23 - b13) * t))
+        # The line is along the x/y-axis but is not parallel to either:
+        else:
+            t12 = -(-(a32 - b32) * (-a31 + a11 * (1 - t) + b11 * t) + (a31 - b31) * (-a32 + a12 *
+                  (1 - t) + b12 * t)) / (-(a32 - b32) * ((a21 - a11) * (1 - t) + (b21 - b11) * t) +
+                  (a31 - b31) * ((a22 - a21) * (1 - t) + (b22 - b12) * t))
+
+        # Likewise, t3 is greatly simplified by defining it in terms of t and t12:
+        # If block used to prevent a div by zero error.
+        t3 = 0
+        if a31 != b31:
+            t3 = (-a11 + a31 + (a11 - b11) * t + (a11 - a21) *
+                t12 + (a21 - a11 + b11 - b21) * t * t12) / (a31 - b31)
+        elif a32 != b32:
+            t3 = (-a12 + a32 + (a12 - b12) * t + (a12 - a22) *
+                t12 + (a22 - a12 + b12 - b22) * t * t12) / (a32 - b32)
+        elif a33 != b33:
+            t3 = (-a13 + a33 + (a13 - b13) * t + (a13 - a23) *
+                t12 + (a23 - a13 + b13 - b23) * t * t12) / (a33 - b33)
+        else:
+            if ENABLE_DEBUG:
+                print("The second edge is a zero-length edge")
+            return None
+
+        # Calculate the point of intersection:
+        x = (1 - t3) * a31 + t3 * b31
+        y = (1 - t3) * a32 + t3 * b32
+        z = (1 - t3) * a33 + t3 * b33
+        int_co = Vector((x, y, z))
+
+        if ENABLE_DEBUG:
+            print(int_co)
+
+        # If the line does not intersect the quad, we return "None":
+        if (t < -1 or t > 1 or t12 < -1 or t12 > 1) and not is_infinite:
+            int_co = None
+
+    elif len(face.verts) == 3:
+        p1, p2, p3 = face.verts[0].co, face.verts[1].co, face.verts[2].co
+        int_co = intersect_line_plane(edge.verts[0].co, edge.verts[1].co, p1, face.normal)
+
+        # Only check if the triangle is not being treated as an infinite plane:
+        # Math based from http://paulbourke.net/geometry/linefacet/
+        if int_co is not None and not is_infinite:
+            pA = p1 - int_co
+            pB = p2 - int_co
+            pC = p3 - int_co
+            # These must be unit vectors, else we risk a domain error:
+            pA.length = 1
+            pB.length = 1
+            pC.length = 1
+            aAB = acos(pA.dot(pB))
+            aBC = acos(pB.dot(pC))
+            aCA = acos(pC.dot(pA))
+            sumA = aAB + aBC + aCA
+
+            # If the point is outside the triangle:
+            if (sumA > (pi + error) and sumA < (pi - error)):
+                int_co = None
+
+    # This is the default case where we either have a planar quad or an n-gon
+    else:
+        int_co = intersect_line_plane(edge.verts[0].co, edge.verts[1].co,
+                                      face.verts[0].co, face.normal)
+    return int_co
+
+
+# project_point_plane
+# Projects a point onto a plane. Returns a tuple of the projection vector
+# and the projected coordinate
+
+def project_point_plane(pt, plane_co, plane_no):
+    if ENABLE_DEBUG:
+        print("project_point_plane was called")
+    proj_co = intersect_line_plane(pt, pt + plane_no, plane_co, plane_no)
+    proj_ve = proj_co - pt
+    if ENABLE_DEBUG:
+        print("project_point_plane: proj_co is {}\nproj_ve is {}".format(proj_co, proj_ve))
+    return (proj_ve, proj_co)
+
+
+# ------------ CHAMPHER HELPER METHODS -------------
+
+def is_planar_edge(edge, error=0.000002):
+    angle = edge.calc_face_angle()
+    return ((angle < error and angle > -error) or
+            (angle < (180 + error) and angle > (180 - error)))
+
+
+# ------------- EDGE TOOL METHODS -------------------
+
+# Extends an "edge" in two directions:
+#   - Requires two vertices to be selected. They do not have to form an edge
+#   - Extends "length" in both directions
+
+class Extend(Operator):
+    bl_idname = "mesh.edgetools_extend"
+    bl_label = "Extend"
+    bl_description = "Extend the selected edges of vertex pairs"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    di1: BoolProperty(
+            name="Forwards",
+            description="Extend the edge forwards",
+            default=True
+            )
+    di2: BoolProperty(
+            name="Backwards",
+            description="Extend the edge backwards",
+            default=False
+            )
+    length: FloatProperty(
+            name="Length",
+            description="Length to extend the edge",
+            min=0.0, max=1024.0,
+            default=1.0
+            )
+
+    def draw(self, context):
+        layout = self.layout
+
+        row = layout.row(align=True)
+        row.prop(self, "di1", toggle=True)
+        row.prop(self, "di2", toggle=True)
+
+        layout.prop(self, "length")
+
+    @classmethod
+    def poll(cls, context):
+        ob = context.active_object
+        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def invoke(self, context, event):
+        return self.execute(context)
+
+    def execute(self, context):
+        try:
+            me = context.object.data
+            bm = bmesh.from_edit_mesh(me)
+            bm.normal_update()
+
+            bEdges = bm.edges
+            bVerts = bm.verts
+
+            edges = [e for e in bEdges if e.select]
+            verts = [v for v in bVerts if v.select]
+
+            if not is_selected_enough(self, edges, 0, edges_n=1, verts_n=0, types="Edge"):
+                return {'CANCELLED'}
+
+            if len(edges) > 0:
+                for e in edges:
+                    vector = e.verts[0].co - e.verts[1].co
+                    vector.length = self.length
+
+                    if self.di1:
+                        v = bVerts.new()
+                        if (vector[0] + vector[1] + vector[2]) < 0:
+                            v.co = e.verts[1].co - vector
+                            newE = bEdges.new((e.verts[1], v))
+                            bEdges.ensure_lookup_table()
+                        else:
+                            v.co = e.verts[0].co + vector
+                            newE = bEdges.new((e.verts[0], v))
+                            bEdges.ensure_lookup_table()
+                    if self.di2:
+                        v = bVerts.new()
+                        if (vector[0] + vector[1] + vector[2]) < 0:
+                            v.co = e.verts[0].co + vector
+                            newE = bEdges.new((e.verts[0], v))
+                            bEdges.ensure_lookup_table()
+                        else:
+                            v.co = e.verts[1].co - vector
+                            newE = bEdges.new((e.verts[1], v))
+                            bEdges.ensure_lookup_table()
+            else:
+                vector = verts[0].co - verts[1].co
+                vector.length = self.length
+
+                if self.di1:
+                    v = bVerts.new()
+                    if (vector[0] + vector[1] + vector[2]) < 0:
+                        v.co = verts[1].co - vector
+                        e = bEdges.new((verts[1], v))
+                        bEdges.ensure_lookup_table()
+                    else:
+                        v.co = verts[0].co + vector
+                        e = bEdges.new((verts[0], v))
+                        bEdges.ensure_lookup_table()
+                if self.di2:
+                    v = bVerts.new()
+                    if (vector[0] + vector[1] + vector[2]) < 0:
+                        v.co = verts[0].co + vector
+                        e = bEdges.new((verts[0], v))
+                        bEdges.ensure_lookup_table()
+                    else:
+                        v.co = verts[1].co - vector
+                        e = bEdges.new((verts[1], v))
+                        bEdges.ensure_lookup_table()
+
+            bmesh.update_edit_mesh(me)
+
+        except Exception as e:
+            error_handlers(self, "mesh.edgetools_extend", e,
+                           reports="Extend Operator failed", func=False)
+            return {'CANCELLED'}
+
+        return {'FINISHED'}
+
+
+# Creates a series of edges between two edges using spline interpolation.
+# This basically just exposes existing functionality in addition to some
+# other common methods: Hermite (c-spline), Bezier, and b-spline. These
+# alternates I coded myself after some extensive research into spline theory
+#
+# @todo Figure out what's wrong with the Blender bezier interpolation
+
+class Spline(Operator):
+    bl_idname = "mesh.edgetools_spline"
+    bl_label = "Spline"
+    bl_description = "Create a spline interplopation between two edges"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    alg: EnumProperty(
+            name="Spline Algorithm",
+            items=[('Blender', "Blender", "Interpolation provided through mathutils.geometry"),
+                    ('Hermite', "C-Spline", "C-spline interpolation"),
+                    ('Bezier', "Bezier", "Bezier interpolation"),
+                    ('B-Spline', "B-Spline", "B-Spline interpolation")],
+            default='Bezier'
+            )
+    segments: IntProperty(
+            name="Segments",
+            description="Number of segments to use in the interpolation",
+            min=2, max=4096,
+            soft_max=1024,
+            default=32
+            )
+    flip1: BoolProperty(
+            name="Flip Edge",
+            description="Flip the direction of the spline on Edge 1",
+            default=False
+            )
+    flip2: BoolProperty(
+            name="Flip Edge",
+            description="Flip the direction of the spline on Edge 2",
+            default=False
+            )
+    ten1: FloatProperty(
+            name="Tension",
+            description="Tension on Edge 1",
+            min=-4096.0, max=4096.0,
+            soft_min=-8.0, soft_max=8.0,
+            default=1.0
+            )
+    ten2: FloatProperty(
+            name="Tension",
+            description="Tension on Edge 2",
+            min=-4096.0, max=4096.0,
+            soft_min=-8.0, soft_max=8.0,
+            default=1.0
+            )
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.prop(self, "alg")
+        layout.prop(self, "segments")
+
+        layout.label(text="Edge 1:")
+        split = layout.split(factor=0.8, align=True)
+        split.prop(self, "ten1")
+        split.prop(self, "flip1", text="Flip1", toggle=True)
+
+        layout.label(text="Edge 2:")
+        split = layout.split(factor=0.8, align=True)
+        split.prop(self, "ten2")
+        split.prop(self, "flip2", text="Flip2", toggle=True)
+
+    @classmethod
+    def poll(cls, context):
+        ob = context.active_object
+        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def invoke(self, context, event):
+        return self.execute(context)
+
+    def execute(self, context):
+        try:
+            me = context.object.data
+            bm = bmesh.from_edit_mesh(me)
+            bm.normal_update()
+
+            bEdges = bm.edges
+            bVerts = bm.verts
+
+            seg = self.segments
+            edges = [e for e in bEdges if e.select]
+
+            if not is_selected_enough(self, edges, 0, edges_n=2, verts_n=0, types="Edge"):
+                return {'CANCELLED'}
+
+            verts = [edges[v // 2].verts[v % 2] for v in range(4)]
+
+            if self.flip1:
+                v1 = verts[1]
+                p1_co = verts[1].co
+                p1_dir = verts[1].co - verts[0].co
+            else:
+                v1 = verts[0]
+                p1_co = verts[0].co
+                p1_dir = verts[0].co - verts[1].co
+            if self.ten1 < 0:
+                p1_dir = -1 * p1_dir
+                p1_dir.length = -self.ten1
+            else:
+                p1_dir.length = self.ten1
+
+            if self.flip2:
+                v2 = verts[3]
+                p2_co = verts[3].co
+                p2_dir = verts[2].co - verts[3].co
+            else:
+                v2 = verts[2]
+                p2_co = verts[2].co
+                p2_dir = verts[3].co - verts[2].co
+            if self.ten2 < 0:
+                p2_dir = -1 * p2_dir
+                p2_dir.length = -self.ten2
+            else:
+                p2_dir.length = self.ten2
+
+            # Get the interploted coordinates:
+            if self.alg == 'Blender':
+                pieces = interpolate_bezier(
+                                p1_co, p1_dir, p2_dir, p2_co, self.segments
+                                )
+            elif self.alg == 'Hermite':
+                pieces = interpolate_line_line(
+                                p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'HERMITE'
+                                )
+            elif self.alg == 'Bezier':
+                pieces = interpolate_line_line(
+                                p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'BEZIER'
+                                )
+            elif self.alg == 'B-Spline':
+                pieces = interpolate_line_line(
+                                p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'BSPLINE'
+                                )
+
+            verts = []
+            verts.append(v1)
+            # Add vertices and set the points:
+            for i in range(seg - 1):
+                v = bVerts.new()
+                v.co = pieces[i]
+                bVerts.ensure_lookup_table()
+                verts.append(v)
+            verts.append(v2)
+            # Connect vertices:
+            for i in range(seg):
+                e = bEdges.new((verts[i], verts[i + 1]))
+                bEdges.ensure_lookup_table()
+
+            bmesh.update_edit_mesh(me)
+
+        except Exception as e:
+            error_handlers(self, "mesh.edgetools_spline", e,
+                           reports="Spline Operator failed", func=False)
+            return {'CANCELLED'}
+
+        return {'FINISHED'}
+
+
+# Creates edges normal to planes defined between each of two edges and the
+# normal or the plane defined by those two edges.
+#   - Select two edges.  The must form a plane.
+#   - On running the script, eight edges will be created.  Delete the
+#     extras that you don't need.
+#   - The length of those edges is defined by the variable "length"
+#
+# @todo Change method from a cross product to a rotation matrix to make the
+#   angle part work.
+#   --- todo completed 2/4/2012, but still needs work ---
+# @todo Figure out a way to make +/- predictable
+#   - Maybe use angle between edges and vector direction definition?
+#   --- TODO COMPLETED ON 2/9/2012 ---
+
+class Ortho(Operator):
+    bl_idname = "mesh.edgetools_ortho"
+    bl_label = "Angle Off Edge"
+    bl_description = "Creates new edges within an angle from vertices of selected edges"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    vert1: BoolProperty(
+            name="Vertice 1",
+            description="Enable edge creation for Vertice 1",
+            default=True
+            )
+    vert2: BoolProperty(
+            name="Vertice 2",
+            description="Enable edge creation for Vertice 2",
+            default=True
+            )
+    vert3: BoolProperty(
+            name="Vertice 3",
+            description="Enable edge creation for Vertice 3",
+            default=True
+            )
+    vert4: BoolProperty(
+            name="Vertice 4",
+            description="Enable edge creation for Vertice 4",
+            default=True
+            )
+    pos: BoolProperty(
+            name="Positive",
+            description="Enable creation of positive direction edges",
+            default=True
+            )
+    neg: BoolProperty(
+            name="Negative",
+            description="Enable creation of negative direction edges",
+            default=True
+            )
+    angle: FloatProperty(
+            name="Angle",
+            description="Define the angle off of the originating edge",
+            min=0.0, max=180.0,
+            default=90.0
+            )
+    length: FloatProperty(
+            name="Length",
+            description="Length of created edges",
+            min=0.0, max=1024.0,
+            default=1.0
+            )
+    # For when only one edge is selected (Possible feature to be testd):
+    plane: EnumProperty(
+            name="Plane",
+            items=[("XY", "X-Y Plane", "Use the X-Y plane as the plane of creation"),
+                   ("XZ", "X-Z Plane", "Use the X-Z plane as the plane of creation"),
+                   ("YZ", "Y-Z Plane", "Use the Y-Z plane as the plane of creation")],
+            default="XY"
+            )
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.label(text="Creation:")
+        split = layout.split()
+        col = split.column()
+
+        col.prop(self, "vert1", toggle=True)
+        col.prop(self, "vert2", toggle=True)
+
+        col = split.column()
+        col.prop(self, "vert3", toggle=True)
+        col.prop(self, "vert4", toggle=True)
+
+        layout.label(text="Direction:")
+        row = layout.row(align=False)
+        row.alignment = 'EXPAND'
+        row.prop(self, "pos")
+        row.prop(self, "neg")
+
+        layout.separator()
+
+        col = layout.column(align=True)
+        col.prop(self, "angle")
+        col.prop(self, "length")
+
+    @classmethod
+    def poll(cls, context):
+        ob = context.active_object
+        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def invoke(self, context, event):
+        return self.execute(context)
+
+    def execute(self, context):
+        try:
+            me = context.object.data
+            bm = bmesh.from_edit_mesh(me)
+            bm.normal_update()
+
+            bVerts = bm.verts
+            bEdges = bm.edges
+            edges = [e for e in bEdges if e.select]
+            vectors = []
+
+            if not is_selected_enough(self, edges, 0, edges_n=2, verts_n=0, types="Edge"):
+                return {'CANCELLED'}
+
+            verts = [edges[0].verts[0],
+                     edges[0].verts[1],
+                     edges[1].verts[0],
+                     edges[1].verts[1]]
+
+            cos = intersect_line_line(verts[0].co, verts[1].co, verts[2].co, verts[3].co)
+
+            # If the two edges are parallel:
+            if cos is None:
+                self.report({'WARNING'},
+                            "Selected lines are parallel: results may be unpredictable")
+                vectors.append(verts[0].co - verts[1].co)
+                vectors.append(verts[0].co - verts[2].co)
+                vectors.append(vectors[0].cross(vectors[1]))
+                vectors.append(vectors[2].cross(vectors[0]))
+                vectors.append(-vectors[3])
+            else:
+                # Warn the user if they have not chosen two planar edges:
+                if not is_same_co(cos[0], cos[1]):
+                    self.report({'WARNING'},
+                                "Selected lines are not planar: results may be unpredictable")
+
+                # This makes the +/- behavior predictable:
+                if (verts[0].co - cos[0]).length < (verts[1].co - cos[0]).length:
+                    verts[0], verts[1] = verts[1], verts[0]
+                if (verts[2].co - cos[0]).length < (verts[3].co - cos[0]).length:
+                    verts[2], verts[3] = verts[3], verts[2]
+
+                vectors.append(verts[0].co - verts[1].co)
+                vectors.append(verts[2].co - verts[3].co)
+
+                # Normal of the plane formed by vector1 and vector2:
+                vectors.append(vectors[0].cross(vectors[1]))
+
+                # Possible directions:
+                vectors.append(vectors[2].cross(vectors[0]))
+                vectors.append(vectors[1].cross(vectors[2]))
+
+            # Set the length:
+            vectors[3].length = self.length
+            vectors[4].length = self.length
+
+            # Perform any additional rotations:
+            matrix = Matrix.Rotation(radians(90 + self.angle), 3, vectors[2])
+            vectors.append(matrix @ -vectors[3])    # vectors[5]
+            matrix = Matrix.Rotation(radians(90 - self.angle), 3, vectors[2])
+            vectors.append(matrix @ vectors[4])     # vectors[6]
+            vectors.append(matrix @ vectors[3])     # vectors[7]
+            matrix = Matrix.Rotation(radians(90 + self.angle), 3, vectors[2])
+            vectors.append(matrix @ -vectors[4])    # vectors[8]
+
+            # Perform extrusions and displacements:
+            # There will be a total of 8 extrusions.  One for each vert of each edge.
+            # It looks like an extrusion will add the new vert to the end of the verts
+            # list and leave the rest in the same location.
+            # -- EDIT --
+            # It looks like I might be able to do this within "bpy.data" with the ".add" function
+
+            for v in range(len(verts)):
+                vert = verts[v]
+                if ((v == 0 and self.vert1) or (v == 1 and self.vert2) or
+                   (v == 2 and self.vert3) or (v == 3 and self.vert4)):
+
+                    if self.pos:
+                        new = bVerts.new()
+                        new.co = vert.co - vectors[5 + (v // 2) + ((v % 2) * 2)]
+                        bVerts.ensure_lookup_table()
+                        bEdges.new((vert, new))
+                        bEdges.ensure_lookup_table()
+                    if self.neg:
+                        new = bVerts.new()
+                        new.co = vert.co + vectors[5 + (v // 2) + ((v % 2) * 2)]
+                        bVerts.ensure_lookup_table()
+                        bEdges.new((vert, new))
+                        bEdges.ensure_lookup_table()
+
+            bmesh.update_edit_mesh(me)
+        except Exception as e:
+            error_handlers(self, "mesh.edgetools_ortho", e,
+                           reports="Angle Off Edge Operator failed", func=False)
+            return {'CANCELLED'}
+
+        return {'FINISHED'}
+
+
+# Usage:
+# Select an edge and a point or an edge and specify the radius (default is 1 BU)
+# You can select two edges but it might be unpredictable which edge it revolves
+# around so you might have to play with the switch
+
+class Shaft(Operator):
+    bl_idname = "mesh.edgetools_shaft"
+    bl_label = "Shaft"
+    bl_description = "Create a shaft mesh around an axis"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    # Selection defaults:
+    shaftType = 0
+
+    # For tracking if the user has changed selection:
+    last_edge: IntProperty(
+            name="Last Edge",
+            description="Tracks if user has changed selected edges",
+            min=0, max=1,
+            default=0
+            )
+    last_flip = False
+
+    edge: IntProperty(
+            name="Edge",
+            description="Edge to shaft around",
+            min=0, max=1,
+            default=0
+            )
+    flip: BoolProperty(
+            name="Flip Second Edge",
+            description="Flip the perceived direction of the second edge",
+            default=False
+            )
+    radius: FloatProperty(
+            name="Radius",
+            description="Shaft Radius",
+            min=0.0, max=1024.0,
+            default=1.0
+            )
+    start: FloatProperty(
+            name="Starting Angle",
+            description="Angle to start the shaft at",
+            min=-360.0, max=360.0,
+            default=0.0
+            )
+    finish: FloatProperty(
+            name="Ending Angle",
+            description="Angle to end the shaft at",
+            min=-360.0, max=360.0,
+            default=360.0
+            )
+    segments: IntProperty(
+            name="Shaft Segments",
+            description="Number of segments to use in the shaft",
+            min=1, max=4096,
+            soft_max=512,
+            default=32
+            )
+
+    def draw(self, context):
+        layout = self.layout
+
+        if self.shaftType == 0:
+            layout.prop(self, "edge")
+            layout.prop(self, "flip")
+        elif self.shaftType == 3:
+            layout.prop(self, "radius")
+
+        layout.prop(self, "segments")
+        layout.prop(self, "start")
+        layout.prop(self, "finish")
+
+    @classmethod
+    def poll(cls, context):
+        ob = context.active_object
+        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def invoke(self, context, event):
+        # Make sure these get reset each time we run:
+        self.last_edge = 0
+        self.edge = 0
+
+        return self.execute(context)
+
+    def execute(self, context):
+        try:
+            me = context.object.data
+            bm = bmesh.from_edit_mesh(me)
+            bm.normal_update()
+
+            bFaces = bm.faces
+            bEdges = bm.edges
+            bVerts = bm.verts
+
+            active = None
+            edges, verts = [], []
+
+            # Pre-caclulated values:
+            rotRange = [radians(self.start), radians(self.finish)]
+            rads = radians((self.finish - self.start) / self.segments)
+
+            numV = self.segments + 1
+            numE = self.segments
+
+            edges = [e for e in bEdges if e.select]
+
+            # Robustness check: there should at least be one edge selected
+            if not is_selected_enough(self, edges, 0, edges_n=1, verts_n=0, types="Edge"):
+                return {'CANCELLED'}
+
+            # If two edges are selected:
+            if len(edges) == 2:
+                # default:
+                edge = [0, 1]
+                vert = [0, 1]
+
+                # By default, we want to shaft around the last selected edge (it
+                # will be the active edge). We know we are using the default if
+                # the user has not changed which edge is being shafted around (as
+                # is tracked by self.last_edge). When they are not the same, then
+                # the user has changed selection.
+                # We then need to make sure that the active object really is an edge
+                # (robustness check)
+                # Finally, if the active edge is not the initial one, we flip them
+                # and have the GUI reflect that
+                if self.last_edge == self.edge:
+                    if isinstance(bm.select_history.active, bmesh.types.BMEdge):
+                        if bm.select_history.active != edges[edge[0]]:
+                            self.last_edge, self.edge = edge[1], edge[1]
+                            edge = [edge[1], edge[0]]
+                    else:
+                        flip_edit_mode()
+                        self.report({'WARNING'},
+                                    "Active geometry is not an edge. Operation Cancelled")
+                        return {'CANCELLED'}
+                elif self.edge == 1:
+                    edge = [1, 0]
+
+                verts.append(edges[edge[0]].verts[0])
+                verts.append(edges[edge[0]].verts[1])
+
+                if self.flip:
+                    verts = [1, 0]
+
+                verts.append(edges[edge[1]].verts[vert[0]])
+                verts.append(edges[edge[1]].verts[vert[1]])
+
+                self.shaftType = 0
+            # If there is more than one edge selected:
+            # There are some issues with it ATM, so don't expose is it to normal users
+            # @todo Fix edge connection ordering issue
+            elif ENABLE_DEBUG and len(edges) > 2:
+                if isinstance(bm.select_history.active, bmesh.types.BMEdge):
+                    active = bm.select_history.active
+                    edges.remove(active)
+                    # Get all the verts:
+                    # edges = order_joined_edges(edges[0])
+                    verts = []
+                    for e in edges:
+                        if verts.count(e.verts[0]) == 0:
+                            verts.append(e.verts[0])
+                        if verts.count(e.verts[1]) == 0:
+                            verts.append(e.verts[1])
+                else:
+                    flip_edit_mode()
+                    self.report({'WARNING'},
+                                "Active geometry is not an edge. Operation Cancelled")
+                    return {'CANCELLED'}
+                self.shaftType = 1
+            else:
+                verts.append(edges[0].verts[0])
+                verts.append(edges[0].verts[1])
+
+                for v in bVerts:
+                    if v.select and verts.count(v) == 0:
+                        verts.append(v)
+                    v.select = False
+                if len(verts) == 2:
+                    self.shaftType = 3
+                else:
+                    self.shaftType = 2
+
+            # The vector denoting the axis of rotation:
+            if self.shaftType == 1:
+                axis = active.verts[1].co - active.verts[0].co
+            else:
+                axis = verts[1].co - verts[0].co
+
+            # We will need a series of rotation matrices. We could use one which
+            # would be faster but also might cause propagation of error
+            # matrices = []
+            # for i in range(numV):
+            #    matrices.append(Matrix.Rotation((rads * i) + rotRange[0], 3, axis))
+            matrices = [Matrix.Rotation((rads * i) + rotRange[0], 3, axis) for i in range(numV)]
+
+            # New vertice coordinates:
+            verts_out = []
+
+            # If two edges were selected:
+            #  - If the lines are not parallel, then it will create a cone-like shaft
+            if self.shaftType == 0:
+                for i in range(len(verts) - 2):
+                    init_vec = distance_point_line(verts[i + 2].co, verts[0].co, verts[1].co)
+                    co = init_vec + verts[i + 2].co
+                    # These will be rotated about the origin so will need to be shifted:
+                    for j in range(numV):
+                        verts_out.append(co - (matrices[j] @ init_vec))
+            elif self.shaftType == 1:
+                for i in verts:
+                    init_vec = distance_point_line(i.co, active.verts[0].co, active.verts[1].co)
+                    co = init_vec + i.co
+                    # These will be rotated about the origin so will need to be shifted:
+                    for j in range(numV):
+                        verts_out.append(co - (matrices[j] @ init_vec))
+            # Else if a line and a point was selected:
+            elif self.shaftType == 2:
+                init_vec = distance_point_line(verts[2].co, verts[0].co, verts[1].co)
+                # These will be rotated about the origin so will need to be shifted:
+                verts_out = [
+                    (verts[i].co - (matrices[j] @ init_vec)) for i in range(2) for j in range(numV)
+                    ]
+            else:
+                # Else the above are not possible, so we will just use the edge:
+                #  - The vector defined by the edge is the normal of the plane for the shaft
+                #  - The shaft will have radius "radius"
+                if is_axial(verts[0].co, verts[1].co) is None:
+                    proj = (verts[1].co - verts[0].co)
+                    proj[2] = 0
+                    norm = proj.cross(verts[1].co - verts[0].co)
+                    vec = norm.cross(verts[1].co - verts[0].co)
+                    vec.length = self.radius
+                elif is_axial(verts[0].co, verts[1].co) == 'Z':
+                    vec = verts[0].co + Vector((0, 0, self.radius))
+                else:
+                    vec = verts[0].co + Vector((0, self.radius, 0))
+                init_vec = distance_point_line(vec, verts[0].co, verts[1].co)
+                # These will be rotated about the origin so will need to be shifted:
+                verts_out = [
+                    (verts[i].co - (matrices[j] @ init_vec)) for i in range(2) for j in range(numV)
+                    ]
+
+            # We should have the coordinates for a bunch of new verts
+            # Now add the verts and build the edges and then the faces
+
+            newVerts = []
+
+            if self.shaftType == 1:
+                # Vertices:
+                for i in range(numV * len(verts)):
+                    new = bVerts.new()
+                    new.co = verts_out[i]
+                    bVerts.ensure_lookup_table()
+                    new.select = True
+                    newVerts.append(new)
+                # Edges:
+                for i in range(numE):
+                    for j in range(len(verts)):
+                        e = bEdges.new((newVerts[i + (numV * j)], newVerts[i + (numV * j) + 1]))
+                        bEdges.ensure_lookup_table()
+                        e.select = True
+                for i in range(numV):
+                    for j in range(len(verts) - 1):
+                        e = bEdges.new((newVerts[i + (numV * j)], newVerts[i + (numV * (j + 1))]))
+                        bEdges.ensure_lookup_table()
+                        e.select = True
+
+                # Faces: There is a problem with this right now
+                """
+                for i in range(len(edges)):
+                    for j in range(numE):
+                        f = bFaces.new((newVerts[i], newVerts[i + 1],
+                                       newVerts[i + (numV * j) + 1], newVerts[i + (numV * j)]))
+                        f.normal_update()
+                """
+            else:
+                # Vertices:
+                for i in range(numV * 2):
+                    new = bVerts.new()
+                    new.co = verts_out[i]
+                    new.select = True
+                    bVerts.ensure_lookup_table()
+                    newVerts.append(new)
+                # Edges:
+                for i in range(numE):
+                    e = bEdges.new((newVerts[i], newVerts[i + 1]))
+                    e.select = True
+                    bEdges.ensure_lookup_table()
+                    e = bEdges.new((newVerts[i + numV], newVerts[i + numV + 1]))
+                    e.select = True
+                    bEdges.ensure_lookup_table()
+                for i in range(numV):
+                    e = bEdges.new((newVerts[i], newVerts[i + numV]))
+                    e.select = True
+                    bEdges.ensure_lookup_table()
+                # Faces:
+                for i in range(numE):
+                    f = bFaces.new((newVerts[i], newVerts[i + 1],
+                                    newVerts[i + numV + 1], newVerts[i + numV]))
+                    bFaces.ensure_lookup_table()
+                    f.normal_update()
+
+            bmesh.update_edit_mesh(me)
+
+        except Exception as e:
+            error_handlers(self, "mesh.edgetools_shaft", e,
+                           reports="Shaft Operator failed", func=False)
+            return {'CANCELLED'}
+
+        return {'FINISHED'}
+
+
+# "Slices" edges crossing a plane defined by a face
+
+class Slice(Operator):
+    bl_idname = "mesh.edgetools_slice"
+    bl_label = "Slice"
+    bl_description = "Cut edges at the plane defined by a selected face"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    make_copy: BoolProperty(
+            name="Make Copy",
+            description="Make new vertices at intersection points instead of splitting the edge",
+            default=False
+            )
+    rip: BoolProperty(
+            name="Rip",
+            description="Split into two edges that DO NOT share an intersection vertex",
+            default=True
+            )
+    pos: BoolProperty(
+            name="Positive",
+            description="Remove the portion on the side of the face normal",
+            default=False
+            )
+    neg: BoolProperty(
+            name="Negative",
+            description="Remove the portion on the side opposite of the face normal",
+            default=False
+            )
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.prop(self, "make_copy")
+        if not self.make_copy:
+            layout.prop(self, "rip")
+            layout.label(text="Remove Side:")
+            layout.prop(self, "pos")
+            layout.prop(self, "neg")
+
+    @classmethod
+    def poll(cls, context):
+        ob = context.active_object
+        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def invoke(self, context, event):
+        return self.execute(context)
+
+    def execute(self, context):
+        try:
+            me = context.object.data
+            bm = bmesh.from_edit_mesh(me)
+            bm.normal_update()
+
+            bVerts = bm.verts
+            bEdges = bm.edges
+            bFaces = bm.faces
+
+            face, normal = None, None
+
+            # Find the selected face. This will provide the plane to project onto:
+            #  - First check to use the active face. Allows users to just
+            #    select a bunch of faces with the last being the cutting plane
+            #  - If that fails, then use the first found selected face in the BMesh face list
+            if isinstance(bm.select_history.active, bmesh.types.BMFace):
+                face = bm.select_history.active
+                normal = bm.select_history.active.normal
+                bm.select_history.active.select = False
+            else:
+                for f in bFaces:
+                    if f.select:
+                        face = f
+                        normal = f.normal
+                        f.select = False
+                        break
+
+            # If we don't find a selected face exit:
+            if face is None:
+                flip_edit_mode()
+                self.report({'WARNING'},
+                            "Please select a face as the cutting plane. Operation Cancelled")
+                return {'CANCELLED'}
+
+            # Warn the user if they are using an n-gon might lead to some odd results
+            elif len(face.verts) > 4 and not is_face_planar(face):
+                self.report({'WARNING'},
+                            "Selected face is an N-gon.  Results may be unpredictable")
+
+            if ENABLE_DEBUG:
+                dbg = 0
+                print("Number of Edges: ", len(bEdges))
+
+            for e in bEdges:
+                if ENABLE_DEBUG:
+                    print("Looping through Edges - ", dbg)
+                    dbg = dbg + 1
+
+                # Get the end verts on the edge:
+                v1 = e.verts[0]
+                v2 = e.verts[1]
+
+                # Make sure that verts are not a part of the cutting plane:
+                if e.select and (v1 not in face.verts and v2 not in face.verts):
+                    if len(face.verts) < 5:  # Not an n-gon
+                        intersection = intersect_line_face(e, face, True)
+                    else:
+                        intersection = intersect_line_plane(v1.co, v2.co, face.verts[0].co, normal)
+
+                    if ENABLE_DEBUG:
+                        print("Intersection: ", intersection)
+
+                    # If an intersection exists find the distance of each of the end
+                    # points from the plane, with "positive" being in the direction
+                    # of the cutting plane's normal. If the points are on opposite
+                    # side of the plane, then it intersects and we need to cut it
+                    if intersection is not None:
+                        bVerts.ensure_lookup_table()
+                        bEdges.ensure_lookup_table()
+                        bFaces.ensure_lookup_table()
+
+                        d1 = distance_point_to_plane(v1.co, face.verts[0].co, normal)
+                        d2 = distance_point_to_plane(v2.co, face.verts[0].co, normal)
+                        # If they have different signs, then the edge crosses the cutting plane:
+                        if abs(d1 + d2) < abs(d1 - d2):
+                            # Make the first vertex the positive one:
+                            if d1 < d2:
+                                v2, v1 = v1, v2
+
+                            if self.make_copy:
+                                new = bVerts.new()
+                                new.co = intersection
+                                new.select = True
+                                bVerts.ensure_lookup_table()
+                            elif self.rip:
+                                if ENABLE_DEBUG:
+                                    print("Branch rip engaged")
+                                newV1 = bVerts.new()
+                                newV1.co = intersection
+                                bVerts.ensure_lookup_table()
+                                if ENABLE_DEBUG:
+                                    print("newV1 created", end='; ')
+
+                                newV2 = bVerts.new()
+                                newV2.co = intersection
+                                bVerts.ensure_lookup_table()
+
+                                if ENABLE_DEBUG:
+                                    print("newV2 created", end='; ')
+
+                                newE1 = bEdges.new((v1, newV1))
+                                newE2 = bEdges.new((v2, newV2))
+                                bEdges.ensure_lookup_table()
+
+                                if ENABLE_DEBUG:
+                                    print("new edges created", end='; ')
+
+                                if e.is_valid:
+                                    bEdges.remove(e)
+
+                                bEdges.ensure_lookup_table()
+
+                                if ENABLE_DEBUG:
+                                    print("Old edge removed.\nWe're done with this edge")
+                            else:
+                                new = list(bmesh.utils.edge_split(e, v1, 0.5))
+                                bEdges.ensure_lookup_table()
+                                new[1].co = intersection
+                                e.select = False
+                                new[0].select = False
+                                if self.pos:
+                                    bEdges.remove(new[0])
+                                if self.neg:
+                                    bEdges.remove(e)
+                                bEdges.ensure_lookup_table()
+
+            if ENABLE_DEBUG:
+                print("The Edge Loop has exited. Now to update the bmesh")
+                dbg = 0
+
+            bmesh.update_edit_mesh(me)
+
+        except Exception as e:
+            error_handlers(self, "mesh.edgetools_slice", e,
+                           reports="Slice Operator failed", func=False)
+            return {'CANCELLED'}
+
+        return {'FINISHED'}
+
+
+# This projects the selected edges onto the selected plane
+# and/or both points on the selected edge
+
+class Project(Operator):
+    bl_idname = "mesh.edgetools_project"
+    bl_label = "Project"
+    bl_description = ("Projects the selected Vertices/Edges onto a selected plane\n"
+                      "(Active is projected onto the rest)")
+    bl_options = {'REGISTER', 'UNDO'}
+
+    make_copy: BoolProperty(
+            name="Make Copy",
+            description="Make duplicates of the vertices instead of altering them",
+            default=False
+            )
+
+    def draw(self, context):
+        layout = self.layout
+        layout.prop(self, "make_copy")
+
+    @classmethod
+    def poll(cls, context):
+        ob = context.active_object
+        return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def invoke(self, context, event):
+        return self.execute(context)
+
+    def execute(self, context):
+        try:
+            me = context.object.data
+            bm = bmesh.from_edit_mesh(me)
+            bm.normal_update()
+
+            bFaces = bm.faces
+            bVerts = bm.verts
+
+            fVerts = []
+
+            # Find the selected face.  This will provide the plane to project onto:
+            # @todo Check first for an active face
+            for f in bFaces:
+                if f.select:
+                    for v in f.verts:
+                        fVerts.append(v)
+                    normal = f.normal
+                    f.select = False
+                    break
+
+            for v in bVerts:
+                if v.select:
+                    if v in fVerts:
+                        v.select = False
+                        continue
+                    d = distance_point_to_plane(v.co, fVerts[0].co, normal)
+                    if self.make_copy:
+                        temp = v
+                        v = bVerts.new()
+                        v.co = temp.co
+                        bVerts.ensure_lookup_table()
+                    vector = normal
+                    vector.length = abs(d)
+                    v.co = v.co - (vector * sign(d))
+                    v.select = False
+
+            bmesh.update_edit_mesh(me)
+
+        except Exception as e:
+            error_handlers(self, "mesh.edgetools_project", e,
+                           reports="Project Operator failed", func=False)
+
+            return {'CANCELLED'}
+
+        return {'FINISHED'}
+
+
+# Project_End is for projecting/extending an edge to meet a plane
+# This is used be selecting a face to define the plane then all the edges
+# Then move the vertices in the edge that is closest to the
+# plane to the coordinates of the intersection of the edge and the plane
+
+class Project_End(Operator):
+    bl_idname = "mesh.edgetools_project_end"
+    bl_label = "Project (End Point)"
+    bl_description = ("Projects the vertices of the selected\n"
+                      "edges closest to a plane onto that plane")
+    bl_options = {'REGISTER', 'UNDO'}
+
+    make_copy: BoolProperty(
+            name="Make Copy",
+            description="Make a duplicate of the vertice instead of moving it",
+            default=False
+            )
+    keep_length: BoolProperty(
+            name="Keep Edge Length",
+            description="Maintain edge lengths",
+            default=False
+            )
+    use_force: BoolProperty(
+            name="Use opposite vertices",
+            description="Force the usage of the vertices at the other end of the edge",
+            default=False
+            )
+    use_normal: BoolProperty(
+            name="Project along normal",
+            description="Use the plane's normal as the projection direction",
+            default=False
+            )
+
+    def draw(self, context):
+        layout = self.layout
+
+        if not self.keep_length:
+            layout.prop(self, "use_normal")
+        layout.prop(self, "make_copy")
+        layout.prop(self, "use_force")
+
+    @classmethod
+    def poll(cls, context):
+        ob = context.active_object
+        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def invoke(self, context, event):
+        return self.execute(context)
+
+    def execute(self, context):
+        try:
+            me = context.object.data
+            bm = bmesh.from_edit_mesh(me)
+            bm.normal_update()
+
+            bFaces = bm.faces
+            bEdges = bm.edges
+            bVerts = bm.verts
+
+            fVerts = []
+
+            # Find the selected face. This will provide the plane to project onto:
+            for f in bFaces:
+                if f.select:
+                    for v in f.verts:
+                        fVerts.append(v)
+                    normal = f.normal
+                    f.select = False
+                    break
+
+            for e in bEdges:
+                if e.select:
+                    v1 = e.verts[0]
+                    v2 = e.verts[1]
+                    if v1 in fVerts or v2 in fVerts:
+                        e.select = False
+                        continue
+                    intersection = intersect_line_plane(v1.co, v2.co, fVerts[0].co, normal)
+                    if intersection is not None:
+                        # Use abs because we don't care what side of plane we're on:
+                        d1 = distance_point_to_plane(v1.co, fVerts[0].co, normal)
+                        d2 = distance_point_to_plane(v2.co, fVerts[0].co, normal)
+                        # If d1 is closer than we use v1 as our vertice:
+                        # "xor" with 'use_force':
+                        if (abs(d1) < abs(d2)) is not self.use_force:
+                            if self.make_copy:
+                                v1 = bVerts.new()
+                                v1.co = e.verts[0].co
+                                bVerts.ensure_lookup_table()
+                                bEdges.ensure_lookup_table()
+                            if self.keep_length:
+                                v1.co = intersection
+                            elif self.use_normal:
+                                vector = normal
+                                vector.length = abs(d1)
+                                v1.co = v1.co - (vector * sign(d1))
+                            else:
+                                v1.co = intersection
+                        else:
+                            if self.make_copy:
+                                v2 = bVerts.new()
+                                v2.co = e.verts[1].co
+                                bVerts.ensure_lookup_table()
+                                bEdges.ensure_lookup_table()
+                            if self.keep_length:
+                                v2.co = intersection
+                            elif self.use_normal:
+                                vector = normal
+                                vector.length = abs(d2)
+                                v2.co = v2.co - (vector * sign(d2))
+                            else:
+                                v2.co = intersection
+                    e.select = False
+
+            bmesh.update_edit_mesh(me)
+
+        except Exception as e:
+            error_handlers(self, "mesh.edgetools_project_end", e,
+                           reports="Project (End Point) Operator failed", func=False)
+            return {'CANCELLED'}
+
+        return {'FINISHED'}
+
+
+class VIEW3D_MT_edit_mesh_edgetools(Menu):
+    bl_label = "Edge Tools"
+    bl_description = "Various tools for manipulating edges"
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.operator("mesh.edgetools_extend")
+        layout.operator("mesh.edgetools_spline")
+        layout.operator("mesh.edgetools_ortho")
+        layout.operator("mesh.edgetools_shaft")
+        layout.operator("mesh.edgetools_slice")
+        layout.separator()
+
+        layout.operator("mesh.edgetools_project")
+        layout.operator("mesh.edgetools_project_end")
+
+def menu_func(self, context):
+    self.layout.menu("VIEW3D_MT_edit_mesh_edgetools")
+
+# define classes for registration
+classes = (
+    VIEW3D_MT_edit_mesh_edgetools,
+    Extend,
+    Spline,
+    Ortho,
+    Shaft,
+    Slice,
+    Project,
+    Project_End,
+    )
+
+
+# registering and menu integration
+def register():
+    for cls in classes:
+        bpy.utils.register_class(cls)
+    bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_func)
+
+# unregistering and removing menus
+def unregister():
+    for cls in classes:
+        bpy.utils.unregister_class(cls)
+    bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_func)
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/mesh_extrude_and_reshape.py b/mesh_tools/mesh_extrude_and_reshape.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4245ac2e0ee42ad8697f8c983ef88cc59cf3c0c
--- /dev/null
+++ b/mesh_tools/mesh_extrude_and_reshape.py
@@ -0,0 +1,377 @@
+# ##### 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": "Extrude and Reshape",
+    "author": "Germano Cavalcante",
+    "version": (0, 8, 1),
+    "blender": (2, 80, 0),
+    "location": "View3D > UI > Tools > Mesh Tools > Add: > Extrude Menu (Alt + E)",
+    "description": "Extrude face and merge edge intersections "
+                   "between the mesh and the new edges",
+    "wiki_url": "http://blenderartists.org/forum/"
+                "showthread.php?376618-Addon-Push-Pull-Face",
+    "category": "Mesh"}
+
+import bpy
+import bmesh
+from mathutils.geometry import intersect_line_line
+from bpy.types import Operator
+
+
+class BVHco():
+    i = 0
+    c1x = 0.0
+    c1y = 0.0
+    c1z = 0.0
+    c2x = 0.0
+    c2y = 0.0
+    c2z = 0.0
+
+
+def edges_BVH_overlap(bm, edges, epsilon=0.0001):
+    bco = set()
+    for e in edges:
+        bvh = BVHco()
+        bvh.i = e.index
+        b1 = e.verts[0]
+        b2 = e.verts[1]
+        co1 = b1.co.x
+        co2 = b2.co.x
+        if co1 <= co2:
+            bvh.c1x = co1 - epsilon
+            bvh.c2x = co2 + epsilon
+        else:
+            bvh.c1x = co2 - epsilon
+            bvh.c2x = co1 + epsilon
+        co1 = b1.co.y
+        co2 = b2.co.y
+        if co1 <= co2:
+            bvh.c1y = co1 - epsilon
+            bvh.c2y = co2 + epsilon
+        else:
+            bvh.c1y = co2 - epsilon
+            bvh.c2y = co1 + epsilon
+        co1 = b1.co.z
+        co2 = b2.co.z
+        if co1 <= co2:
+            bvh.c1z = co1 - epsilon
+            bvh.c2z = co2 + epsilon
+        else:
+            bvh.c1z = co2 - epsilon
+            bvh.c2z = co1 + epsilon
+        bco.add(bvh)
+    del edges
+    overlap = {}
+    oget = overlap.get
+    for e1 in bm.edges:
+        by = bz = True
+        a1 = e1.verts[0]
+        a2 = e1.verts[1]
+        c1x = a1.co.x
+        c2x = a2.co.x
+        if c1x > c2x:
+            tm = c1x
+            c1x = c2x
+            c2x = tm
+        for bvh in bco:
+            if c1x <= bvh.c2x and c2x >= bvh.c1x:
+                if by:
+                    by = False
+                    c1y = a1.co.y
+                    c2y = a2.co.y
+                    if c1y > c2y:
+                        tm = c1y
+                        c1y = c2y
+                        c2y = tm
+                if c1y <= bvh.c2y and c2y >= bvh.c1y:
+                    if bz:
+                        bz = False
+                        c1z = a1.co.z
+                        c2z = a2.co.z
+                        if c1z > c2z:
+                            tm = c1z
+                            c1z = c2z
+                            c2z = tm
+                    if c1z <= bvh.c2z and c2z >= bvh.c1z:
+                        e2 = bm.edges[bvh.i]
+                        if e1 != e2:
+                            overlap[e1] = oget(e1, set()).union({e2})
+    return overlap
+
+
+def intersect_edges_edges(overlap, precision=4):
+    epsilon = .1**precision
+    fpre_min = -epsilon
+    fpre_max = 1 + epsilon
+    splits = {}
+    sp_get = splits.get
+    new_edges1 = set()
+    new_edges2 = set()
+    targetmap = {}
+    for edg1 in overlap:
+        # print("***", ed1.index, "***")
+        for edg2 in overlap[edg1]:
+            a1 = edg1.verts[0]
+            a2 = edg1.verts[1]
+            b1 = edg2.verts[0]
+            b2 = edg2.verts[1]
+
+            # test if are linked
+            if a1 in {b1, b2} or a2 in {b1, b2}:
+                # print('linked')
+                continue
+
+            aco1, aco2 = a1.co, a2.co
+            bco1, bco2 = b1.co, b2.co
+            tp = intersect_line_line(aco1, aco2, bco1, bco2)
+            if tp:
+                p1, p2 = tp
+                if (p1 - p2).to_tuple(precision) == (0, 0, 0):
+                    v = aco2 - aco1
+                    f = p1 - aco1
+                    x, y, z = abs(v.x), abs(v.y), abs(v.z)
+                    max1 = 0 if x >= y and x >= z else\
+                           1 if y >= x and y >= z else 2
+                    fac1 = f[max1] / v[max1]
+
+                    v = bco2 - bco1
+                    f = p2 - bco1
+                    x, y, z = abs(v.x), abs(v.y), abs(v.z)
+                    max2 = 0 if x >= y and x >= z else\
+                           1 if y >= x and y >= z else 2
+                    fac2 = f[max2] / v[max2]
+
+                    if fpre_min <= fac1 <= fpre_max:
+                        # print(edg1.index, 'can intersect', edg2.index)
+                        ed1 = edg1
+
+                    elif edg1 in splits:
+                        for ed1 in splits[edg1]:
+                            a1 = ed1.verts[0]
+                            a2 = ed1.verts[1]
+
+                            vco1 = a1.co
+                            vco2 = a2.co
+
+                            v = vco2 - vco1
+                            f = p1 - vco1
+                            fac1 = f[max1] / v[max1]
+                            if fpre_min <= fac1 <= fpre_max:
+                                # print(e.index, 'can intersect', edg2.index)
+                                break
+                        else:
+                            # print(edg1.index, 'really does not intersect', edg2.index)
+                            continue
+                    else:
+                        # print(edg1.index, 'not intersect', edg2.index)
+                        continue
+
+                    if fpre_min <= fac2 <= fpre_max:
+                        # print(ed1.index, 'actually intersect', edg2.index)
+                        ed2 = edg2
+
+                    elif edg2 in splits:
+                        for ed2 in splits[edg2]:
+                            b1 = ed2.verts[0]
+                            b2 = ed2.verts[1]
+
+                            vco1 = b1.co
+                            vco2 = b2.co
+
+                            v = vco2 - vco1
+                            f = p2 - vco1
+                            fac2 = f[max2] / v[max2]
+                            if fpre_min <= fac2 <= fpre_max:
+                                # print(ed1.index, 'actually intersect', e.index)
+                                break
+                        else:
+                            # print(ed1.index, 'really does not intersect', ed2.index)
+                            continue
+                    else:
+                        # print(ed1.index, 'not intersect', edg2.index)
+                        continue
+
+                    new_edges1.add(ed1)
+                    new_edges2.add(ed2)
+
+                    if abs(fac1) <= epsilon:
+                        nv1 = a1
+                    elif fac1 + epsilon >= 1:
+                        nv1 = a2
+                    else:
+                        ne1, nv1 = bmesh.utils.edge_split(ed1, a1, fac1)
+                        new_edges1.add(ne1)
+                        splits[edg1] = sp_get(edg1, set()).union({ne1})
+
+                    if abs(fac2) <= epsilon:
+                        nv2 = b1
+                    elif fac2 + epsilon >= 1:
+                        nv2 = b2
+                    else:
+                        ne2, nv2 = bmesh.utils.edge_split(ed2, b1, fac2)
+                        new_edges2.add(ne2)
+                        splits[edg2] = sp_get(edg2, set()).union({ne2})
+
+                    if nv1 != nv2:  # necessary?
+                        targetmap[nv1] = nv2
+
+    return new_edges1, new_edges2, targetmap
+
+
+class ER_OT_Extrude_and_Reshape(Operator):
+    bl_idname = "mesh.extrude_reshape"
+    bl_label = "Extrude and Reshape"
+    bl_description = "Push and pull face entities to sculpt 3d models"
+    bl_options = {'REGISTER', 'GRAB_CURSOR', 'BLOCKING'}
+
+    @classmethod
+    def poll(cls, context):
+        if context.mode=='EDIT_MESH':
+            return True
+
+    def modal(self, context, event):
+        if self.confirm:
+            sface = self.bm.faces.active
+            if not sface:
+                for face in self.bm.faces:
+                    if face.select is True:
+                        sface = face
+                        break
+                else:
+                    return {'FINISHED'}
+            # edges to intersect
+            edges = set()
+            [[edges.add(ed) for ed in v.link_edges] for v in sface.verts]
+
+            overlap = edges_BVH_overlap(self.bm, edges, epsilon=0.0001)
+            overlap = {k: v for k, v in overlap.items() if k not in edges}  # remove repetition
+            """
+            print([e.index for e in edges])
+            for a, b in overlap.items():
+                print(a.index, [e.index for e in b])
+            """
+            new_edges1, new_edges2, targetmap = intersect_edges_edges(overlap)
+            pos_weld = set()
+            for e in new_edges1:
+                v1, v2 = e.verts
+                if v1 in targetmap and v2 in targetmap:
+                    pos_weld.add((targetmap[v1], targetmap[v2]))
+            if targetmap:
+                bmesh.ops.weld_verts(self.bm, targetmap=targetmap)
+            """
+            print([e.is_valid for e in new_edges1])
+            print([e.is_valid for e in new_edges2])
+            sp_faces1 = set()
+            """
+            for e in pos_weld:
+                v1, v2 = e
+                lf1 = set(v1.link_faces)
+                lf2 = set(v2.link_faces)
+                rlfe = lf1.intersection(lf2)
+                for f in rlfe:
+                    try:
+                        nf = bmesh.utils.face_split(f, v1, v2)
+                        # sp_faces1.update({f, nf[0]})
+                    except:
+                        pass
+
+            # sp_faces2 = set()
+            for e in new_edges2:
+                lfe = set(e.link_faces)
+                v1, v2 = e.verts
+                lf1 = set(v1.link_faces)
+                lf2 = set(v2.link_faces)
+                rlfe = lf1.intersection(lf2)
+                for f in rlfe.difference(lfe):
+                    nf = bmesh.utils.face_split(f, v1, v2)
+                    # sp_faces2.update({f, nf[0]})
+
+            bmesh.update_edit_mesh(self.mesh, loop_triangles=True, destructive=True)
+            return {'FINISHED'}
+        if self.cancel:
+            return {'FINISHED'}
+        self.cancel = event.type in {'ESC', 'NDOF_BUTTON_ESC'}
+        self.confirm = event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}
+        return {'PASS_THROUGH'}
+
+    def execute(self, context):
+        self.mesh = context.object.data
+        self.bm = bmesh.from_edit_mesh(self.mesh)
+        try:
+            selection = self.bm.select_history[-1]
+        except:
+            for face in self.bm.faces:
+                if face.select is True:
+                    selection = face
+                    break
+            else:
+                return {'FINISHED'}
+        if not isinstance(selection, bmesh.types.BMFace):
+            bpy.ops.mesh.extrude_region_move('INVOKE_DEFAULT')
+            return {'FINISHED'}
+        else:
+            face = selection
+            # face.select = False
+            bpy.ops.mesh.select_all(action='DESELECT')
+            geom = []
+            for edge in face.edges:
+                if abs(edge.calc_face_angle(0) - 1.5707963267948966) < 0.01:  # self.angle_tolerance:
+                    geom.append(edge)
+
+            ret_dict = bmesh.ops.extrude_discrete_faces(self.bm, faces=[face])
+
+            for face in ret_dict['faces']:
+                self.bm.faces.active = face
+                face.select = True
+                sface = face
+            dfaces = bmesh.ops.dissolve_edges(
+                        self.bm, edges=geom, use_verts=True, use_face_split=False
+                        )
+            bmesh.update_edit_mesh(self.mesh, loop_triangles=True, destructive=True)
+            bpy.ops.transform.translate(
+                        'INVOKE_DEFAULT', constraint_axis=(False, False, True),
+                        orient_type='NORMAL', release_confirm=True
+                        )
+
+        context.window_manager.modal_handler_add(self)
+
+        self.cancel = False
+        self.confirm = False
+        return {'RUNNING_MODAL'}
+
+
+def operator_draw(self, context):
+    layout = self.layout
+    col = layout.column(align=True)
+    col.operator("mesh.extrude_reshape")
+
+
+def register():
+    bpy.utils.register_class(ER_OT_Extrude_and_Reshape)
+
+
+def unregister():
+    bpy.utils.unregister_class(ER_OT_Extrude_and_Reshape)
+
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/mesh_filletplus.py b/mesh_tools/mesh_filletplus.py
new file mode 100644
index 0000000000000000000000000000000000000000..01f2f67e51d3f31eaed44cce9e89c3da54b3fddc
--- /dev/null
+++ b/mesh_tools/mesh_filletplus.py
@@ -0,0 +1,430 @@
+# -*- coding: utf-8 -*-
+
+# ##### END GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+    "name": "FilletPlus",
+    "author": "Gert De Roost - original by zmj100",
+    "version": (0, 4, 3),
+    "blender": (2, 80, 0),
+    "location": "View3D > Tool Shelf",
+    "description": "",
+    "warning": "",
+    "wiki_url": "",
+    "category": "Mesh"}
+
+
+import bpy
+from bpy.props import (
+        FloatProperty,
+        IntProperty,
+        BoolProperty,
+        )
+from bpy.types import Operator
+import bmesh
+from mathutils import Matrix
+from math import (
+        cos, pi, sin,
+        degrees, tan,
+        )
+
+
+def list_clear_(l):
+    if l:
+        del l[:]
+    return l
+
+
+def get_adj_v_(list_):
+    tmp = {}
+    for i in list_:
+        try:
+            tmp[i[0]].append(i[1])
+        except KeyError:
+            tmp[i[0]] = [i[1]]
+        try:
+            tmp[i[1]].append(i[0])
+        except KeyError:
+            tmp[i[1]] = [i[0]]
+    return tmp
+
+class f_buf():
+    # one of the angles was not 0 or 180
+    check = False
+
+
+def fillets(list_0, startv, vertlist, face, adj, n, out, flip, radius):
+    try:
+        dict_0 = get_adj_v_(list_0)
+        list_1 = [[dict_0[i][0], i, dict_0[i][1]] for i in dict_0 if (len(dict_0[i]) == 2)][0]
+        list_3 = []
+        for elem in list_1:
+            list_3.append(bm.verts[elem])
+        list_2 = []
+
+        p_ = list_3[1]
+        p = (list_3[1].co).copy()
+        p1 = (list_3[0].co).copy()
+        p2 = (list_3[2].co).copy()
+
+        vec1 = p - p1
+        vec2 = p - p2
+
+        ang = vec1.angle(vec2, any)
+        check_angle = round(degrees(ang))
+
+        if check_angle == 180 or check_angle == 0.0:
+            return False
+        else:
+            f_buf.check = True
+
+        opp = adj
+
+        if radius is False:
+            h = adj * (1 / cos(ang * 0.5))
+            adj_ = adj
+        elif radius is True:
+            h = opp / sin(ang * 0.5)
+            adj_ = opp / tan(ang * 0.5)
+
+        p3 = p - (vec1.normalized() * adj_)
+        p4 = p - (vec2.normalized() * adj_)
+        rp = p - ((p - ((p3 + p4) * 0.5)).normalized() * h)
+
+        vec3 = rp - p3
+        vec4 = rp - p4
+
+        axis = vec1.cross(vec2)
+
+        if out is False:
+            if flip is False:
+                rot_ang = vec3.angle(vec4)
+            elif flip is True:
+                rot_ang = vec1.angle(vec2)
+        elif out is True:
+            rot_ang = (2 * pi) - vec1.angle(vec2)
+
+        for j in range(n + 1):
+            new_angle = rot_ang * j / n
+            mtrx = Matrix.Rotation(new_angle, 3, axis)
+            if out is False:
+                if flip is False:
+                    tmp = p4 - rp
+                    tmp1 = mtrx @ tmp
+                    tmp2 = tmp1 + rp
+                elif flip is True:
+                    p3 = p - (vec1.normalized() * opp)
+                    tmp = p3 - p
+                    tmp1 = mtrx @ tmp
+                    tmp2 = tmp1 + p
+            elif out is True:
+                p4 = p - (vec2.normalized() * opp)
+                tmp = p4 - p
+                tmp1 = mtrx @ tmp
+                tmp2 = tmp1 + p
+
+            v = bm.verts.new(tmp2)
+            list_2.append(v)
+
+        if flip is True:
+            list_3[1:2] = list_2
+        else:
+            list_2.reverse()
+            list_3[1:2] = list_2
+
+        list_clear_(list_2)
+
+        n1 = len(list_3)
+
+        for t in range(n1 - 1):
+            bm.edges.new([list_3[t], list_3[(t + 1) % n1]])
+
+            v = bm.verts.new(p)
+            bm.edges.new([v, p_])
+
+        bm.edges.ensure_lookup_table()
+
+        if face is not None:
+            for l in face.loops:
+                if l.vert == list_3[0]:
+                    startl = l
+                    break
+            vertlist2 = []
+
+            if startl.link_loop_next.vert == startv:
+                l = startl.link_loop_prev
+                while len(vertlist) > 0:
+                    vertlist2.insert(0, l.vert)
+                    vertlist.pop(vertlist.index(l.vert))
+                    l = l.link_loop_prev
+            else:
+                l = startl.link_loop_next
+                while len(vertlist) > 0:
+                    vertlist2.insert(0, l.vert)
+                    vertlist.pop(vertlist.index(l.vert))
+                    l = l.link_loop_next
+
+            for v in list_3:
+                vertlist2.append(v)
+            bm.faces.new(vertlist2)
+        if startv.is_valid:
+            bm.verts.remove(startv)
+        else:
+            print("\n[Function fillets Error]\n"
+                  "Starting vertex (startv var) couldn't be removed\n")
+            return False
+        bm.verts.ensure_lookup_table()
+        bm.edges.ensure_lookup_table()
+        bm.faces.ensure_lookup_table()
+        list_3[1].select = 1
+        list_3[-2].select = 1
+        bm.edges.get([list_3[0], list_3[1]]).select = 1
+        bm.edges.get([list_3[-1], list_3[-2]]).select = 1
+        bm.verts.index_update()
+        bm.edges.index_update()
+        bm.faces.index_update()
+
+        me.update(calc_edges=True, calc_loop_triangles=True)
+        bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
+
+    except Exception as e:
+        print("\n[Function fillets Error]\n{}\n".format(e))
+        return False
+
+
+def do_filletplus(self, pair):
+    is_finished = True
+    try:
+        startv = None
+        global inaction
+        global flip
+        list_0 = [list([e.verts[0].index, e.verts[1].index]) for e in pair]
+
+        vertset = set([])
+        bm.verts.ensure_lookup_table()
+        bm.edges.ensure_lookup_table()
+        bm.faces.ensure_lookup_table()
+        vertset.add(bm.verts[list_0[0][0]])
+        vertset.add(bm.verts[list_0[0][1]])
+        vertset.add(bm.verts[list_0[1][0]])
+        vertset.add(bm.verts[list_0[1][1]])
+
+        v1, v2, v3 = vertset
+
+        if len(list_0) != 2:
+            self.report({'WARNING'}, "Two adjacent edges must be selected")
+            is_finished = False
+        else:
+            inaction = 1
+            vertlist = []
+            found = 0
+            for f in v1.link_faces:
+                if v2 in f.verts and v3 in f.verts:
+                    found = 1
+            if not found:
+                for v in [v1, v2, v3]:
+                    if v.index in list_0[0] and v.index in list_0[1]:
+                        startv = v
+                face = None
+            else:
+                for f in v1.link_faces:
+                    if v2 in f.verts and v3 in f.verts:
+                        for v in f.verts:
+                            if not(v in vertset):
+                                vertlist.append(v)
+                            if (v in vertset and v.link_loops[0].link_loop_prev.vert in vertset and
+                               v.link_loops[0].link_loop_next.vert in vertset):
+                                startv = v
+                        face = f
+            if out is True:
+                flip = False
+            if startv:
+                fills = fillets(list_0, startv, vertlist, face, adj, n, out, flip, radius)
+                if not fills:
+                    is_finished = False
+            else:
+                is_finished = False
+    except Exception as e:
+        print("\n[Function do_filletplus Error]\n{}\n".format(e))
+        is_finished = False
+    return is_finished
+
+
+def check_is_not_coplanar(bm_data):
+    from mathutils import Vector
+    check = False
+    angles, norm_angle = 0, 0
+    z_vec = Vector((0, 0, 1))
+    try:
+        bm_data.faces.ensure_lookup_table()
+
+        for f in bm_data.faces:
+            norm_angle = f.normal.angle(z_vec)
+            if angles == 0:
+                angles = norm_angle
+            if angles != norm_angle:
+                check = True
+                break
+    except Exception as e:
+        print("\n[Function check_is_not_coplanar Error]\n{}\n".format(e))
+        check = True
+    return check
+
+
+#  Operator
+
+class MESH_OT_fillet_plus(Operator):
+    bl_idname = "mesh.fillet_plus"
+    bl_label = "Fillet Plus"
+    bl_description = ("Fillet adjoining edges\n"
+                      "Note: Works on a mesh whose all faces share the same normal")
+    bl_options = {"REGISTER", "UNDO"}
+
+    adj: FloatProperty(
+            name="",
+            description="Size of the filleted corners",
+            default=0.1,
+            min=0.00001, max=100.0,
+            step=1,
+            precision=3
+            )
+    n: IntProperty(
+            name="",
+            description="Subdivision of the filleted corners",
+            default=3,
+            min=1, max=50,
+            step=1
+            )
+    out: BoolProperty(
+            name="Outside",
+            description="Fillet towards outside",
+            default=False
+            )
+    flip: BoolProperty(
+            name="Flip",
+            description="Flip the direction of the Fillet\n"
+                        "Only available if Outside option is not active",
+            default=False
+            )
+    radius: BoolProperty(
+            name="Radius",
+            description="Use radius for the size of the filleted corners",
+            default=False
+            )
+
+    @classmethod
+    def poll(cls, context):
+        obj = context.active_object
+        return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def draw(self, context):
+        layout = self.layout
+
+        if f_buf.check is False:
+            layout.label(text="Angle is equal to 0 or 180", icon="INFO")
+            layout.label(text="Can not fillet", icon="BLANK1")
+        else:
+            layout.prop(self, "radius")
+            if self.radius is True:
+                layout.label(text="Radius:")
+            elif self.radius is False:
+                layout.label(text="Distance:")
+            layout.prop(self, "adj")
+            layout.label(text="Number of sides:")
+            layout.prop(self, "n")
+
+            if self.n > 1:
+                row = layout.row(align=False)
+                row.prop(self, "out")
+                if self.out is False:
+                    row.prop(self, "flip")
+
+    def execute(self, context):
+        global inaction
+        global bm, me, adj, n, out, flip, radius, f_buf
+
+        adj = self.adj
+        n = self.n
+        out = self.out
+        flip = self.flip
+        radius = self.radius
+
+        inaction = 0
+        f_buf.check = False
+
+        ob_act = context.active_object
+        try:
+            me = ob_act.data
+            bm = bmesh.from_edit_mesh(me)
+            warn_obj = bool(check_is_not_coplanar(bm))
+            if warn_obj is False:
+                tempset = set([])
+                bm.verts.ensure_lookup_table()
+                bm.edges.ensure_lookup_table()
+                bm.faces.ensure_lookup_table()
+                for v in bm.verts:
+                    if v.select and v.is_boundary:
+                        tempset.add(v)
+                for v in tempset:
+                    edgeset = set([])
+                    for e in v.link_edges:
+                        if e.select and e.is_boundary:
+                            edgeset.add(e)
+                        if len(edgeset) == 2:
+                            is_finished = do_filletplus(self, edgeset)
+                            if not is_finished:
+                                break
+
+                if inaction == 1:
+                    bpy.ops.mesh.select_all(action="DESELECT")
+                    for v in bm.verts:
+                        if len(v.link_edges) == 0:
+                            bm.verts.remove(v)
+                    bpy.ops.object.editmode_toggle()
+                    bpy.ops.object.editmode_toggle()
+                else:
+                    self.report({'WARNING'}, "Filletplus operation could not be performed")
+                    return {'CANCELLED'}
+            else:
+                self.report({'WARNING'}, "Mesh is not a coplanar surface. Operation cancelled")
+                return {'CANCELLED'}
+        except:
+            self.report({'WARNING'}, "Filletplus operation could not be performed")
+            return {'CANCELLED'}
+
+        return {'FINISHED'}
+
+# define classes for registration
+classes = (
+    MESH_OT_fillet_plus,
+    )
+
+# registering and menu integration
+def register():
+    for cls in classes:
+        bpy.utils.register_class(cls)
+
+
+# unregistering and removing menus
+def unregister():
+    for cls in reversed(classes):
+        bpy.utils.unregister_class(cls)
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/mesh_mextrude_plus.py b/mesh_tools/mesh_mextrude_plus.py
new file mode 100644
index 0000000000000000000000000000000000000000..5fa2aa2b7636c59c7d25937e932840176784155d
--- /dev/null
+++ b/mesh_tools/mesh_mextrude_plus.py
@@ -0,0 +1,370 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Repeats extrusion + rotation + scale for one or more faces
+# Original code by liero
+# Update by Jimmy Hazevoet 03/2017 for Blender 2.79
+# normal rotation, probability, scaled offset, object coords, initial and per step noise
+
+
+bl_info = {
+    "name": "MExtrude Plus1",
+    "author": "liero, Jimmy Hazevoet",
+    "version": (1, 3, 0),
+    "blender": (2, 77, 0),
+    "location": "View3D > Tool Shelf",
+    "description": "Repeat extrusions from faces to create organic shapes",
+    "warning": "",
+    "wiki_url": "",
+    "category": "Mesh"}
+
+
+import bpy
+import bmesh
+import random
+from bpy.types import Operator
+from random import gauss
+from math import radians
+from mathutils import (
+        Euler, Vector,
+        )
+from bpy.props import (
+        FloatProperty,
+        IntProperty,
+        BoolProperty,
+        )
+
+
+def gloc(self, r):
+    return Vector((self.offx, self.offy, self.offz))
+
+
+def vloc(self, r):
+    random.seed(self.ran + r)
+    return self.off * (1 + gauss(0, self.var1 / 3))
+
+
+def nrot(self, n):
+    return Euler((radians(self.nrotx) * n[0],
+                  radians(self.nroty) * n[1],
+                  radians(self.nrotz) * n[2]), 'XYZ')
+
+
+def vrot(self, r):
+    random.seed(self.ran + r)
+    return Euler((radians(self.rotx) + gauss(0, self.var2 / 3),
+                  radians(self.roty) + gauss(0, self.var2 / 3),
+                  radians(self.rotz) + gauss(0, self.var2 / 3)), 'XYZ')
+
+
+def vsca(self, r):
+    random.seed(self.ran + r)
+    return self.sca * (1 + gauss(0, self.var3 / 3))
+
+
+class MExtrude(Operator):
+    bl_idname = "object.mextrude"
+    bl_label = "Multi Extrude"
+    bl_description = ("Extrude selected Faces with Rotation,\n"
+                      "Scaling, Variation, Randomization")
+    bl_options = {"REGISTER", "UNDO", "PRESET"}
+
+    off: FloatProperty(
+            name="Offset",
+            soft_min=0.001, soft_max=10,
+            min=-100, max=100,
+            default=1.0,
+            description="Translation"
+            )
+    offx: FloatProperty(
+            name="Loc X",
+            soft_min=-10.0, soft_max=10.0,
+            min=-100.0, max=100.0,
+            default=0.0,
+            description="Global Translation X"
+            )
+    offy: FloatProperty(
+            name="Loc Y",
+            soft_min=-10.0, soft_max=10.0,
+            min=-100.0, max=100.0,
+            default=0.0,
+            description="Global Translation Y"
+            )
+    offz: FloatProperty(
+            name="Loc Z",
+            soft_min=-10.0, soft_max=10.0,
+            min=-100.0, max=100.0,
+            default=0.0,
+            description="Global Translation Z"
+            )
+    rotx: FloatProperty(
+            name="Rot X",
+            min=-85, max=85,
+            soft_min=-30, soft_max=30,
+            default=0,
+            description="X Rotation"
+            )
+    roty: FloatProperty(
+            name="Rot Y",
+            min=-85, max=85,
+            soft_min=-30,
+            soft_max=30,
+            default=0,
+            description="Y Rotation"
+            )
+    rotz: FloatProperty(
+            name="Rot Z",
+            min=-85, max=85,
+            soft_min=-30, soft_max=30,
+            default=-0,
+            description="Z Rotation"
+            )
+    nrotx: FloatProperty(
+            name="N Rot X",
+            min=-85, max=85,
+            soft_min=-30, soft_max=30,
+            default=0,
+            description="Normal X Rotation"
+            )
+    nroty: FloatProperty(
+            name="N Rot Y",
+            min=-85, max=85,
+            soft_min=-30, soft_max=30,
+            default=0,
+            description="Normal Y Rotation"
+            )
+    nrotz: FloatProperty(
+            name="N Rot Z",
+            min=-85, max=85,
+            soft_min=-30, soft_max=30,
+            default=-0,
+            description="Normal Z Rotation"
+            )
+    sca: FloatProperty(
+            name="Scale",
+            min=0.01, max=10,
+            soft_min=0.5, soft_max=1.5,
+            default=1.0,
+            description="Scaling of the selected faces after extrusion"
+            )
+    var1: FloatProperty(
+            name="Offset Var", min=-10, max=10,
+            soft_min=-1, soft_max=1,
+            default=0,
+            description="Offset variation"
+            )
+    var2: FloatProperty(
+            name="Rotation Var",
+            min=-10, max=10,
+            soft_min=-1, soft_max=1,
+            default=0,
+            description="Rotation variation"
+            )
+    var3: FloatProperty(
+            name="Scale Noise",
+            min=-10, max=10,
+            soft_min=-1, soft_max=1,
+            default=0,
+            description="Scaling noise"
+            )
+    var4: IntProperty(
+            name="Probability",
+            min=0, max=100,
+            default=100,
+            description="Probability, chance of extruding a face"
+            )
+    num: IntProperty(
+            name="Repeat",
+            min=1, max=500,
+            soft_max=100,
+            default=5,
+            description="Repetitions"
+            )
+    ran: IntProperty(
+            name="Seed",
+            min=-9999, max=9999,
+            default=0,
+            description="Seed to feed random values"
+            )
+    opt1: BoolProperty(
+            name="Polygon coordinates",
+            default=True,
+            description="Polygon coordinates, Object coordinates"
+            )
+    opt2: BoolProperty(
+            name="Proportional offset",
+            default=False,
+            description="Scale * Offset"
+            )
+    opt3: BoolProperty(
+            name="Per step rotation noise",
+            default=False,
+            description="Per step rotation noise, Initial rotation noise"
+            )
+    opt4: BoolProperty(
+            name="Per step scale noise",
+            default=False,
+            description="Per step scale noise, Initial scale noise"
+            )
+
+    @classmethod
+    def poll(cls, context):
+        obj = context.object
+        return (obj and obj.type == 'MESH')
+
+    def draw(self, context):
+        layout = self.layout
+        col = layout.column(align=True)
+        col.label(text="Transformations:")
+        col.prop(self, "off", slider=True)
+        col.prop(self, "offx", slider=True)
+        col.prop(self, "offy", slider=True)
+        col.prop(self, "offz", slider=True)
+
+        col = layout.column(align=True)
+        col.prop(self, "rotx", slider=True)
+        col.prop(self, "roty", slider=True)
+        col.prop(self, "rotz", slider=True)
+        col.prop(self, "nrotx", slider=True)
+        col.prop(self, "nroty", slider=True)
+        col.prop(self, "nrotz", slider=True)
+        col = layout.column(align=True)
+        col.prop(self, "sca", slider=True)
+
+        col = layout.column(align=True)
+        col.label(text="Variation settings:")
+        col.prop(self, "var1", slider=True)
+        col.prop(self, "var2", slider=True)
+        col.prop(self, "var3", slider=True)
+        col.prop(self, "var4", slider=True)
+        col.prop(self, "ran")
+        col = layout.column(align=False)
+        col.prop(self, 'num')
+
+        col = layout.column(align=True)
+        col.label(text="Options:")
+        col.prop(self, "opt1")
+        col.prop(self, "opt2")
+        col.prop(self, "opt3")
+        col.prop(self, "opt4")
+
+    def execute(self, context):
+        obj = bpy.context.object
+        om = obj.mode
+        bpy.context.tool_settings.mesh_select_mode = [False, False, True]
+        origin = Vector([0.0, 0.0, 0.0])
+
+        # bmesh operations
+        bpy.ops.object.mode_set()
+        bm = bmesh.new()
+        bm.from_mesh(obj.data)
+        sel = [f for f in bm.faces if f.select]
+
+        after = []
+
+        # faces loop
+        for i, of in enumerate(sel):
+            nro = nrot(self, of.normal)
+            off = vloc(self, i)
+            loc = gloc(self, i)
+            of.normal_update()
+
+            # initial rotation noise
+            if self.opt3 is False:
+                rot = vrot(self, i)
+            # initial scale noise
+            if self.opt4 is False:
+                s = vsca(self, i)
+
+            # extrusion loop
+            for r in range(self.num):
+                # random probability % for extrusions
+                if self.var4 > int(random.random() * 100):
+                    nf = of.copy()
+                    nf.normal_update()
+                    no = nf.normal.copy()
+
+                    # face/obj coördinates
+                    if self.opt1 is True:
+                        ce = nf.calc_center_bounds()
+                    else:
+                        ce = origin
+
+                    # per step rotation noise
+                    if self.opt3 is True:
+                        rot = vrot(self, i + r)
+                    # per step scale noise
+                    if self.opt4 is True:
+                        s = vsca(self, i + r)
+
+                    # proportional, scale * offset
+                    if self.opt2 is True:
+                        off = s * off
+
+                    for v in nf.verts:
+                        v.co -= ce
+                        v.co.rotate(nro)
+                        v.co.rotate(rot)
+                        v.co += ce + loc + no * off
+                        v.co = v.co.lerp(ce, 1 - s)
+
+                    # extrude code from TrumanBlending
+                    for a, b in zip(of.loops, nf.loops):
+                        sf = bm.faces.new((a.vert, a.link_loop_next.vert,
+                                           b.link_loop_next.vert, b.vert))
+                        sf.normal_update()
+                    bm.faces.remove(of)
+                    of = nf
+
+            after.append(of)
+
+        for v in bm.verts:
+            v.select = False
+        for e in bm.edges:
+            e.select = False
+
+        for f in after:
+            if f not in sel:
+                f.select = True
+            else:
+                f.select = False
+
+        bm.to_mesh(obj.data)
+        obj.data.update()
+
+        # restore user settings
+        bpy.ops.object.mode_set(mode=om)
+
+        if not len(sel):
+            self.report({"WARNING"},
+                        "No suitable Face selection found. Operation cancelled")
+            return {'CANCELLED'}
+
+        return {'FINISHED'}
+
+
+def register():
+    bpy.utils.register_module(__name__)
+
+
+def unregister():
+    bpy.utils.unregister_module(__name__)
+
+
+if __name__ == '__main__':
+    register()
diff --git a/mesh_tools/mesh_offset_edges.py b/mesh_tools/mesh_offset_edges.py
new file mode 100644
index 0000000000000000000000000000000000000000..524076a5b808d075d20483e135d7da9245c5362b
--- /dev/null
+++ b/mesh_tools/mesh_offset_edges.py
@@ -0,0 +1,791 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_info = {
+    "name": "Offset Edges",
+    "author": "Hidesato Ikeya, Veezen fix 2.8 (temporary)",
+	#i tried edit newest version, but got some errors, works only on 0,2,6
+    "version": (0, 2, 6),
+    "blender": (2, 80, 0),
+    "location": "VIEW3D > Edge menu(CTRL-E) > Offset Edges",
+    "description": "Offset Edges",
+    "warning": "",
+    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Modeling/offset_edges",
+    "tracker_url": "",
+    "category": "Mesh"}
+
+import math
+from math import sin, cos, pi, copysign, radians
+import bpy
+from bpy_extras import view3d_utils
+import bmesh
+from mathutils import Vector
+from time import perf_counter
+
+X_UP = Vector((1.0, .0, .0))
+Y_UP = Vector((.0, 1.0, .0))
+Z_UP = Vector((.0, .0, 1.0))
+ZERO_VEC = Vector((.0, .0, .0))
+ANGLE_90 = pi / 2
+ANGLE_180 = pi
+ANGLE_360 = 2 * pi
+
+
+def calc_loop_normal(verts, fallback=Z_UP):
+    # Calculate normal from verts using Newell's method.
+    normal = ZERO_VEC.copy()
+
+    if verts[0] is verts[-1]:
+        # Perfect loop
+        range_verts = range(1, len(verts))
+    else:
+        # Half loop
+        range_verts = range(0, len(verts))
+
+    for i in range_verts:
+        v1co, v2co = verts[i-1].co, verts[i].co
+        normal.x += (v1co.y - v2co.y) * (v1co.z + v2co.z)
+        normal.y += (v1co.z - v2co.z) * (v1co.x + v2co.x)
+        normal.z += (v1co.x - v2co.x) * (v1co.y + v2co.y)
+
+    if normal != ZERO_VEC:
+        normal.normalize()
+    else:
+        normal = fallback
+
+    return normal
+
+def collect_edges(bm):
+    set_edges_orig = set()
+    for e in bm.edges:
+        if e.select:
+            co_faces_selected = 0
+            for f in e.link_faces:
+                if f.select:
+                    co_faces_selected += 1
+                    if co_faces_selected == 2:
+                        break
+            else:
+                set_edges_orig.add(e)
+
+    if not set_edges_orig:
+        return None
+
+    return set_edges_orig
+
+def collect_loops(set_edges_orig):
+    set_edges_copy = set_edges_orig.copy()
+
+    loops = []  # [v, e, v, e, ... , e, v]
+    while set_edges_copy:
+        edge_start = set_edges_copy.pop()
+        v_left, v_right = edge_start.verts
+        lp = [v_left, edge_start, v_right]
+        reverse = False
+        while True:
+            edge = None
+            for e in v_right.link_edges:
+                if e in set_edges_copy:
+                    if edge:
+                        # Overlap detected.
+                        return None
+                    edge = e
+                    set_edges_copy.remove(e)
+            if edge:
+                v_right = edge.other_vert(v_right)
+                lp.extend((edge, v_right))
+                continue
+            else:
+                if v_right is v_left:
+                    # Real loop.
+                    loops.append(lp)
+                    break
+                elif reverse is False:
+                    # Right side of half loop.
+                    # Reversing the loop to operate same procedure on the left side.
+                    lp.reverse()
+                    v_right, v_left = v_left, v_right
+                    reverse = True
+                    continue
+                else:
+                    # Half loop, completed.
+                    loops.append(lp)
+                    break
+    return loops
+
+def get_adj_ix(ix_start, vec_edges, half_loop):
+    # Get adjacent edge index, skipping zero length edges
+    len_edges = len(vec_edges)
+    if half_loop:
+        range_right = range(ix_start, len_edges)
+        range_left = range(ix_start-1, -1, -1)
+    else:
+        range_right = range(ix_start, ix_start+len_edges)
+        range_left = range(ix_start-1, ix_start-1-len_edges, -1)
+
+    ix_right = ix_left = None
+    for i in range_right:
+        # Right
+        i %= len_edges
+        if vec_edges[i] != ZERO_VEC:
+            ix_right = i
+            break
+    for i in range_left:
+        # Left
+        i %= len_edges
+        if vec_edges[i] != ZERO_VEC:
+            ix_left = i
+            break
+    if half_loop:
+        # If index of one side is None, assign another index.
+        if ix_right is None:
+            ix_right = ix_left
+        if ix_left is None:
+            ix_left = ix_right
+
+    return ix_right, ix_left
+
+def get_adj_faces(edges):
+    adj_faces = []
+    for e in edges:
+        adj_f = None
+        co_adj = 0
+        for f in e.link_faces:
+            # Search an adjacent face.
+            # Selected face has precedance.
+            if not f.hide and f.normal != ZERO_VEC:
+                adj_exist = True
+                adj_f = f
+                co_adj += 1
+                if f.select:
+                    adj_faces.append(adj_f)
+                    break
+        else:
+            if co_adj == 1:
+                adj_faces.append(adj_f)
+            else:
+                adj_faces.append(None)
+    return adj_faces
+
+
+def get_edge_rail(vert, set_edges_orig):
+    co_edges = co_edges_selected = 0
+    vec_inner = None
+    for e in vert.link_edges:
+        if (e not in set_edges_orig and
+           (e.select or (co_edges_selected == 0 and not e.hide))):
+            v_other = e.other_vert(vert)
+            vec = v_other.co - vert.co
+            if vec != ZERO_VEC:
+                vec_inner = vec
+                if e.select:
+                    co_edges_selected += 1
+                    if co_edges_selected == 2:
+                        return None
+                else:
+                    co_edges += 1
+    if co_edges_selected == 1:
+        vec_inner.normalize()
+        return vec_inner
+    elif co_edges == 1:
+        # No selected edges, one unselected edge.
+        vec_inner.normalize()
+        return vec_inner
+    else:
+        return None
+
+def get_cross_rail(vec_tan, vec_edge_r, vec_edge_l, normal_r, normal_l):
+    # Cross rail is a cross vector between normal_r and normal_l.
+
+    vec_cross = normal_r.cross(normal_l)
+    if vec_cross.dot(vec_tan) < .0:
+        vec_cross *= -1
+    cos_min = min(vec_tan.dot(vec_edge_r), vec_tan.dot(-vec_edge_l))
+    cos = vec_tan.dot(vec_cross)
+    if cos >= cos_min:
+        vec_cross.normalize()
+        return vec_cross
+    else:
+        return None
+
+def move_verts(width, depth, verts, directions, geom_ex):
+    if geom_ex:
+        geom_s = geom_ex['side']
+        verts_ex = []
+        for v in verts:
+            for e in v.link_edges:
+                if e in geom_s:
+                    verts_ex.append(e.other_vert(v))
+                    break
+        #assert len(verts) == len(verts_ex)
+        verts = verts_ex
+
+    for v, (vec_width, vec_depth) in zip(verts, directions):
+        v.co += width * vec_width + depth * vec_depth
+
+def extrude_edges(bm, edges_orig):
+    extruded = bmesh.ops.extrude_edge_only(bm, edges=edges_orig)['geom']
+    n_edges = n_faces = len(edges_orig)
+    n_verts = len(extruded) - n_edges - n_faces
+
+    geom = dict()
+    geom['verts'] = verts = set(extruded[:n_verts])
+    geom['edges'] = edges = set(extruded[n_verts:n_verts + n_edges])
+    geom['faces'] = set(extruded[n_verts + n_edges:])
+    geom['side'] = set(e for v in verts for e in v.link_edges if e not in edges)
+
+    return geom
+
+def clean(bm, mode, edges_orig, geom_ex=None):
+    for f in bm.faces:
+        f.select = False
+    if geom_ex:
+        for e in geom_ex['edges']:
+            e.select = True
+        if mode == 'offset':
+            lis_geom = list(geom_ex['side']) + list(geom_ex['faces'])
+            bmesh.ops.delete(bm, geom=lis_geom, context='EDGES')
+    else:
+        for e in edges_orig:
+            e.select = True
+
+def collect_mirror_planes(edit_object):
+    mirror_planes = []
+    eob_mat_inv = edit_object.matrix_world.inverted()
+
+
+    for m in edit_object.modifiers:
+        if (m.type == 'MIRROR' and m.use_mirror_merge):
+            merge_limit = m.merge_threshold
+            if not m.mirror_object:
+                loc = ZERO_VEC
+                norm_x, norm_y, norm_z = X_UP, Y_UP, Z_UP
+            else:
+                mirror_mat_local = eob_mat_inv @ m.mirror_object.matrix_world
+                loc = mirror_mat_local.to_translation()
+                norm_x, norm_y, norm_z, _ = mirror_mat_local.adjugated()
+                norm_x = norm_x.to_3d().normalized()
+                norm_y = norm_y.to_3d().normalized()
+                norm_z = norm_z.to_3d().normalized()
+            if m.use_axis[0]:
+                mirror_planes.append((loc, norm_x, merge_limit))
+            if m.use_axis[1]:
+                mirror_planes.append((loc, norm_y, merge_limit))
+            if m.use_axis[2]:
+                mirror_planes.append((loc, norm_z, merge_limit))
+    return mirror_planes
+
+def get_vert_mirror_pairs(set_edges_orig, mirror_planes):
+    if mirror_planes:
+        set_edges_copy = set_edges_orig.copy()
+        vert_mirror_pairs = dict()
+        for e in set_edges_orig:
+            v1, v2 = e.verts
+            for mp in mirror_planes:
+                p_co, p_norm, mlimit = mp
+                v1_dist = abs(p_norm.dot(v1.co - p_co))
+                v2_dist = abs(p_norm.dot(v2.co - p_co))
+                if v1_dist <= mlimit:
+                    # v1 is on a mirror plane.
+                    vert_mirror_pairs[v1] = mp
+                if v2_dist <= mlimit:
+                    # v2 is on a mirror plane.
+                    vert_mirror_pairs[v2] = mp
+                if v1_dist <= mlimit and v2_dist <= mlimit:
+                    # This edge is on a mirror_plane, so should not be offsetted.
+                    set_edges_copy.remove(e)
+        return vert_mirror_pairs, set_edges_copy
+    else:
+        return None, set_edges_orig
+
+def get_mirror_rail(mirror_plane, vec_up):
+    p_norm = mirror_plane[1]
+    mirror_rail = vec_up.cross(p_norm)
+    if mirror_rail != ZERO_VEC:
+        mirror_rail.normalize()
+        # Project vec_up to mirror_plane
+        vec_up = vec_up - vec_up.project(p_norm)
+        vec_up.normalize()
+        return mirror_rail, vec_up
+    else:
+        return None, vec_up
+
+def reorder_loop(verts, edges, lp_normal, adj_faces):
+    for i, adj_f in enumerate(adj_faces):
+        if adj_f is None:
+            continue
+        v1, v2 = verts[i], verts[i+1]
+        e = edges[i]
+        fv = tuple(adj_f.verts)
+        if fv[fv.index(v1)-1] is v2:
+            # Align loop direction
+            verts.reverse()
+            edges.reverse()
+            adj_faces.reverse()
+        if lp_normal.dot(adj_f.normal) < .0:
+            lp_normal *= -1
+        break
+    else:
+        # All elements in adj_faces are None
+        for v in verts:
+            if v.normal != ZERO_VEC:
+                if lp_normal.dot(v.normal) < .0:
+                    verts.reverse()
+                    edges.reverse()
+                    lp_normal *= -1
+                break
+
+    return verts, edges, lp_normal, adj_faces
+
+def get_directions(lp, vec_upward, normal_fallback, vert_mirror_pairs, **options):
+    opt_follow_face = options['follow_face']
+    opt_edge_rail = options['edge_rail']
+    opt_er_only_end = options['edge_rail_only_end']
+    opt_threshold = options['threshold']
+
+    verts, edges = lp[::2], lp[1::2]
+    set_edges = set(edges)
+    lp_normal = calc_loop_normal(verts, fallback=normal_fallback)
+
+    ##### Loop order might be changed below.
+    if lp_normal.dot(vec_upward) < .0:
+        # Make this loop's normal towards vec_upward.
+        verts.reverse()
+        edges.reverse()
+        lp_normal *= -1
+
+    if opt_follow_face:
+        adj_faces = get_adj_faces(edges)
+        verts, edges, lp_normal, adj_faces = \
+            reorder_loop(verts, edges, lp_normal, adj_faces)
+    else:
+        adj_faces = (None, ) * len(edges)
+    ##### Loop order might be changed above.
+
+    vec_edges = tuple((e.other_vert(v).co - v.co).normalized()
+                      for v, e in zip(verts, edges))
+
+    if verts[0] is verts[-1]:
+        # Real loop. Popping last vertex.
+        verts.pop()
+        HALF_LOOP = False
+    else:
+        # Half loop
+        HALF_LOOP = True
+
+    len_verts = len(verts)
+    directions = []
+    for i in range(len_verts):
+        vert = verts[i]
+        ix_right, ix_left = i, i-1
+
+        VERT_END = False
+        if HALF_LOOP:
+            if i == 0:
+                # First vert
+                ix_left = ix_right
+                VERT_END = True
+            elif i == len_verts - 1:
+                # Last vert
+                ix_right = ix_left
+                VERT_END = True
+
+        edge_right, edge_left = vec_edges[ix_right], vec_edges[ix_left]
+        face_right, face_left = adj_faces[ix_right], adj_faces[ix_left]
+
+        norm_right = face_right.normal if face_right else lp_normal
+        norm_left = face_left.normal if face_left else lp_normal
+        if norm_right.angle(norm_left) > opt_threshold:
+            # Two faces are not flat.
+            two_normals = True
+        else:
+            two_normals = False
+
+        tan_right = edge_right.cross(norm_right).normalized()
+        tan_left = edge_left.cross(norm_left).normalized()
+        tan_avr = (tan_right + tan_left).normalized()
+        norm_avr = (norm_right + norm_left).normalized()
+
+        rail = None
+        if two_normals or opt_edge_rail:
+            # Get edge rail.
+            # edge rail is a vector of an inner edge.
+            if two_normals or (not opt_er_only_end) or VERT_END:
+                rail = get_edge_rail(vert, set_edges)
+        if vert_mirror_pairs and VERT_END:
+            if vert in vert_mirror_pairs:
+                rail, norm_avr = \
+                    get_mirror_rail(vert_mirror_pairs[vert], norm_avr)
+        if (not rail) and two_normals:
+            # Get cross rail.
+            # Cross rail is a cross vector between norm_right and norm_left.
+            rail = get_cross_rail(
+                tan_avr, edge_right, edge_left, norm_right, norm_left)
+        if rail:
+            dot = tan_avr.dot(rail)
+            if dot > .0:
+                tan_avr = rail
+            elif dot < .0:
+                tan_avr = -rail
+
+        vec_plane = norm_avr.cross(tan_avr)
+        e_dot_p_r = edge_right.dot(vec_plane)
+        e_dot_p_l = edge_left.dot(vec_plane)
+        if e_dot_p_r or e_dot_p_l:
+            if e_dot_p_r > e_dot_p_l:
+                vec_edge, e_dot_p = edge_right, e_dot_p_r
+            else:
+                vec_edge, e_dot_p = edge_left, e_dot_p_l
+
+            vec_tan = (tan_avr - tan_avr.project(vec_edge)).normalized()
+            # Make vec_tan perpendicular to vec_edge
+            vec_up = vec_tan.cross(vec_edge)
+
+            vec_width = vec_tan - (vec_tan.dot(vec_plane) / e_dot_p) * vec_edge
+            vec_depth = vec_up - (vec_up.dot(vec_plane) / e_dot_p) * vec_edge
+        else:
+            vec_width = tan_avr
+            vec_depth = norm_avr
+
+        directions.append((vec_width, vec_depth))
+
+    return verts, directions
+
+def use_cashes(self, context):
+    self.caches_valid = True
+
+angle_presets = {'0°': 0,
+                 '15°': radians(15),
+                 '30°': radians(30),
+                 '45°': radians(45),
+                 '60°': radians(60),
+                 '75°': radians(75),
+                 '90°': radians(90),}
+def assign_angle_presets(self, context):
+    use_cashes(self, context)
+    self.angle = angle_presets[self.angle_presets]
+
+class OffsetEdges(bpy.types.Operator):
+    """Offset Edges."""
+    bl_idname = "mesh.offset_edges"
+    bl_label = "Offset Edges"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    geometry_mode: bpy.props.EnumProperty(
+        items=[('offset', "Offset", "Offset edges"),
+               ('extrude', "Extrude", "Extrude edges"),
+               ('move', "Move", "Move selected edges")],
+        name="Geometory mode", default='offset',
+        update=use_cashes)
+    width: bpy.props.FloatProperty(
+        name="Width", default=.2, precision=4, step=1, update=use_cashes)
+    flip_width: bpy.props.BoolProperty(
+        name="Flip Width", default=False,
+        description="Flip width direction", update=use_cashes)
+    depth: bpy.props.FloatProperty(
+        name="Depth", default=.0, precision=4, step=1, update=use_cashes)
+    flip_depth: bpy.props.BoolProperty(
+        name="Flip Depth", default=False,
+        description="Flip depth direction", update=use_cashes)
+    depth_mode: bpy.props.EnumProperty(
+        items=[('angle', "Angle", "Angle"),
+               ('depth', "Depth", "Depth")],
+        name="Depth mode", default='angle', update=use_cashes)
+    angle: bpy.props.FloatProperty(
+        name="Angle", default=0, precision=3, step=.1,
+        min=-2*pi, max=2*pi, subtype='ANGLE',
+        description="Angle", update=use_cashes)
+    flip_angle: bpy.props.BoolProperty(
+        name="Flip Angle", default=False,
+        description="Flip Angle", update=use_cashes)
+    follow_face: bpy.props.BoolProperty(
+        name="Follow Face", default=False,
+        description="Offset along faces around")
+    mirror_modifier: bpy.props.BoolProperty(
+        name="Mirror Modifier", default=False,
+        description="Take into account of Mirror modifier")
+    edge_rail: bpy.props.BoolProperty(
+        name="Edge Rail", default=False,
+        description="Align vertices along inner edges")
+    edge_rail_only_end: bpy.props.BoolProperty(
+        name="Edge Rail Only End", default=False,
+        description="Apply edge rail to end verts only")
+    threshold: bpy.props.FloatProperty(
+        name="Flat Face Threshold", default=radians(0.05), precision=5,
+        step=1.0e-4, subtype='ANGLE',
+        description="If difference of angle between two adjacent faces is "
+                    "below this value, those faces are regarded as flat.",
+        options={'HIDDEN'})
+    caches_valid: bpy.props.BoolProperty(
+        name="Caches Valid", default=False,
+        options={'HIDDEN'})
+    angle_presets: bpy.props.EnumProperty(
+        items=[('0°', "0°", "0°"),
+               ('15°', "15°", "15°"),
+               ('30°', "30°", "30°"),
+               ('45°', "45°", "45°"),
+               ('60°', "60°", "60°"),
+               ('75°', "75°", "75°"),
+               ('90°', "90°", "90°"), ],
+        name="Angle Presets", default='0°',
+        update=assign_angle_presets)
+
+    _cache_offset_infos = None
+    _cache_edges_orig_ixs = None
+
+    @classmethod
+    def poll(self, context):
+        return context.mode == 'EDIT_MESH'
+
+    def draw(self, context):
+        layout = self.layout
+        layout.prop(self, 'geometry_mode', text="")
+        #layout.prop(self, 'geometry_mode', expand=True)
+
+        row = layout.row(align=True)
+        row.prop(self, 'width')
+        row.prop(self, 'flip_width', icon='ARROW_LEFTRIGHT', icon_only=True)
+
+        layout.prop(self, 'depth_mode', expand=True)
+        if self.depth_mode == 'angle':
+            d_mode = 'angle'
+            flip = 'flip_angle'
+        else:
+            d_mode = 'depth'
+            flip = 'flip_depth'
+        row = layout.row(align=True)
+        row.prop(self, d_mode)
+        row.prop(self, flip, icon='ARROW_LEFTRIGHT', icon_only=True)
+        if self.depth_mode == 'angle':
+            layout.prop(self, 'angle_presets', text="Presets", expand=True) 
+
+        layout.separator()
+
+        layout.prop(self, 'follow_face')
+
+        row = layout.row()
+        row.prop(self, 'edge_rail')
+        if self.edge_rail:
+            row.prop(self, 'edge_rail_only_end', text="OnlyEnd", toggle=True)
+
+        layout.prop(self, 'mirror_modifier')
+
+        #layout.operator('mesh.offset_edges', text='Repeat')
+
+        if self.follow_face:
+            layout.separator()
+            layout.prop(self, 'threshold', text='Threshold')
+
+
+    def get_offset_infos(self, bm, edit_object):
+        if self.caches_valid and self._cache_offset_infos is not None:
+            # Return None, indicating to use cache.
+            return None, None
+
+        time = perf_counter()
+
+        set_edges_orig = collect_edges(bm)
+        if set_edges_orig is None:
+            self.report({'WARNING'},
+                        "No edges selected.")
+            return False, False
+
+        if self.mirror_modifier:
+            mirror_planes = collect_mirror_planes(edit_object)
+            vert_mirror_pairs, set_edges = \
+                get_vert_mirror_pairs(set_edges_orig, mirror_planes)
+
+            if set_edges:
+                set_edges_orig = set_edges
+            else:
+                #self.report({'WARNING'},
+                #            "All selected edges are on mirror planes.")
+                vert_mirror_pairs = None
+        else:
+            vert_mirror_pairs = None
+
+        loops = collect_loops(set_edges_orig)
+        if loops is None:
+            self.report({'WARNING'},
+                        "Overlap detected. Select non-overlap edge loops")
+            return False, False
+
+        vec_upward = (X_UP + Y_UP + Z_UP).normalized()
+        # vec_upward is used to unify loop normals when follow_face is off.
+        normal_fallback = Z_UP
+        #normal_fallback = Vector(context.region_data.view_matrix[2][:3])
+        # normal_fallback is used when loop normal cannot be calculated.
+
+        follow_face = self.follow_face
+        edge_rail = self.edge_rail
+        er_only_end = self.edge_rail_only_end
+        threshold = self.threshold
+
+        offset_infos = []
+        for lp in loops:
+            verts, directions = get_directions(
+                lp, vec_upward, normal_fallback, vert_mirror_pairs,
+                follow_face=follow_face, edge_rail=edge_rail,
+                edge_rail_only_end=er_only_end,
+                threshold=threshold)
+            if verts:
+                offset_infos.append((verts, directions))
+
+        # Saving caches.
+        self._cache_offset_infos = _cache_offset_infos = []
+        for verts, directions in offset_infos:
+            v_ixs = tuple(v.index for v in verts)
+            _cache_offset_infos.append((v_ixs, directions))
+        self._cache_edges_orig_ixs = tuple(e.index for e in set_edges_orig)
+
+        print("Preparing OffsetEdges: ", perf_counter() - time)
+
+        return offset_infos, set_edges_orig
+
+    def do_offset_and_free(self, bm, me, offset_infos=None, set_edges_orig=None):
+        # If offset_infos is None, use caches.
+        # Makes caches invalid after offset.
+
+        #time = perf_counter()
+
+        if offset_infos is None:
+            # using cache
+            bmverts = tuple(bm.verts)
+            bmedges = tuple(bm.edges)
+            edges_orig = [bmedges[ix] for ix in self._cache_edges_orig_ixs]
+            verts_directions = []
+            for ix_vs, directions in self._cache_offset_infos:
+                verts = tuple(bmverts[ix] for ix in ix_vs)
+                verts_directions.append((verts, directions))
+        else:
+            verts_directions = offset_infos
+            edges_orig = list(set_edges_orig)
+
+        if self.depth_mode == 'angle':
+            w = self.width if not self.flip_width else -self.width
+            angle = self.angle if not self.flip_angle else -self.angle
+            width = w * cos(angle)
+            depth = w * sin(angle)
+        else:
+            width = self.width if not self.flip_width else -self.width
+            depth = self.depth if not self.flip_depth else -self.depth
+
+        # Extrude
+        if self.geometry_mode == 'move':
+            geom_ex = None
+        else:
+            geom_ex = extrude_edges(bm, edges_orig)
+
+        for verts, directions in verts_directions:
+            move_verts(width, depth, verts, directions, geom_ex)
+
+        clean(bm, self.geometry_mode, edges_orig, geom_ex)
+
+        bpy.ops.object.mode_set(mode="OBJECT")
+        bm.to_mesh(me)
+        bpy.ops.object.mode_set(mode="EDIT")
+        bm.free()
+        self.caches_valid = False  # Make caches invalid.
+
+        #print("OffsetEdges offset: ", perf_counter() - time)
+
+    def execute(self, context):
+        # In edit mode
+        edit_object = context.edit_object
+        bpy.ops.object.mode_set(mode="OBJECT")
+
+        me = edit_object.data
+        bm = bmesh.new()
+        bm.from_mesh(me)
+
+        offset_infos, edges_orig = self.get_offset_infos(bm, edit_object)
+        if offset_infos is False:
+            bpy.ops.object.mode_set(mode="EDIT")
+            return {'CANCELLED'}
+
+        self.do_offset_and_free(bm, me, offset_infos, edges_orig)
+
+        return {'FINISHED'}
+
+    def restore_original_and_free(self, context):
+        self.caches_valid = False  # Make caches invalid.
+        context.area.header_text_set()
+
+        me = context.edit_object.data
+        bpy.ops.object.mode_set(mode="OBJECT")
+        self._bm_orig.to_mesh(me)
+        bpy.ops.object.mode_set(mode="EDIT")
+
+        self._bm_orig.free()
+        context.area.header_text_set()
+
+    def invoke(self, context, event):
+        # In edit mode
+        edit_object = context.edit_object
+        me = edit_object.data
+        bpy.ops.object.mode_set(mode="OBJECT")
+        for p in me.polygons:
+            if p.select:
+                self.follow_face = True
+                break
+
+        self.caches_valid = False
+        bpy.ops.object.mode_set(mode="EDIT")
+        return self.execute(context)
+
+class OffsetEdgesMenu(bpy.types.Menu):
+    bl_idname = "VIEW3D_MT_edit_mesh_offset_edges"
+    bl_label = "Offset Edges"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.operator_context = 'INVOKE_DEFAULT'
+
+        off = layout.operator('mesh.offset_edges', text='Offset')
+        off.geometry_mode = 'offset'
+
+        ext = layout.operator('mesh.offset_edges', text='Extrude')
+        ext.geometry_mode = 'extrude'
+
+        mov = layout.operator('mesh.offset_edges', text='Move')
+        mov.geometry_mode = 'move'
+
+classes = (
+OffsetEdges,
+OffsetEdgesMenu,
+)		
+
+def draw_item(self, context):
+    self.layout.menu("VIEW3D_MT_edit_mesh_offset_edges")
+
+
+def register():
+    for cls in classes:
+        bpy.utils.register_class(cls)
+    bpy.types.VIEW3D_MT_edit_mesh_edges.prepend(draw_item)
+
+
+def unregister():
+    for cls in reversed(classes):
+        bpy.utils.unregister_class(cls)
+    bpy.types.VIEW3D_MT_edit_mesh_edges.remove(draw_item)
+
+
+if __name__ == '__main__':
+    register()
diff --git a/mesh_tools/mesh_vertex_chamfer.py b/mesh_tools/mesh_vertex_chamfer.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ac3f81496ed771b66cce714ca43c612b0a8510c
--- /dev/null
+++ b/mesh_tools/mesh_vertex_chamfer.py
@@ -0,0 +1,164 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+bl_info = {
+    "name": "Vertex Chamfer",
+    "author": "Andrew Hale (TrumanBlending)",
+    "version": (0, 1),
+    "blender": (2, 63, 0),
+    "location": "Spacebar Menu",
+    "description": "Chamfer vertex",
+    "wiki_url": "",
+    "category": "Mesh"}
+
+
+import bpy
+import bmesh
+from bpy.types import Operator
+from bpy.props import (
+        BoolProperty,
+        FloatProperty,
+        )
+
+
+class VertexChamfer(Operator):
+    bl_idname = "mesh.vertex_chamfer"
+    bl_label = "Chamfer Vertex"
+    bl_description = "Tri chamfer selected vertices"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    factor: FloatProperty(
+            name="Factor",
+            description="Size of the Champfer",
+            default=0.1,
+            min=0.0,
+            soft_max=1.0
+            )
+    relative: BoolProperty(
+            name="Relative",
+            description="If Relative, Champfer size is relative to the edge length",
+            default=True
+            )
+    dissolve: BoolProperty(
+            name="Remove",
+            description="Remove/keep the original selected vertices\n"
+                        "Remove creates a new triangle face between the Champfer edges,\n"
+                        "similar to the Dissolve Vertices operator",
+            default=True
+            )
+    displace: FloatProperty(
+            name="Displace",
+            description="Active only if Remove option is disabled\n"
+                        "Displaces the original selected vertices along the normals\n"
+                        "defined by the Champfer edges",
+            soft_min=-5.0,
+            soft_max=5.0
+            )
+
+    @classmethod
+    def poll(self, context):
+        return (context.active_object.type == 'MESH' and
+                context.mode == 'EDIT_MESH')
+
+    def draw(self, context):
+        layout = self.layout
+        layout.prop(self, "factor", text="Distance" if self.relative else "Factor")
+        sub = layout.row()
+        sub.prop(self, "relative")
+        sub.prop(self, "dissolve")
+        if not self.dissolve:
+            layout.prop(self, "displace")
+
+    def execute(self, context):
+        ob = context.active_object
+        me = ob.data
+        bm = bmesh.from_edit_mesh(me)
+
+        bm.select_flush(True)
+
+        fac = self.factor
+        rel = self.relative
+        dissolve = self.dissolve
+        displace = self.displace
+
+        for v in bm.verts:
+            v.tag = False
+
+        # Loop over edges to find those with both verts selected
+        for e in bm.edges[:]:
+            e.tag = e.select
+            if not e.select:
+                continue
+            elen = e.calc_length()
+            val = fac if rel else fac / elen
+            val = min(val, 0.5)
+            # Loop over the verts of the edge to split
+            for v in e.verts:
+                # if val == 0.5 and e.other_vert(v).tag:
+                #    continue
+                en, vn = bmesh.utils.edge_split(e, v, val)
+                en.tag = vn.tag = True
+                val = 1.0 if val == 1.0 else val / (1.0 - val)
+
+        # Get all verts which are selected but not created previously
+        verts = [v for v in bm.verts if v.select and not v.tag]
+
+        # Loop over all verts to split their linked edges
+        for v in verts:
+            for e in v.link_edges[:]:
+                if e.tag:
+                    continue
+                elen = e.calc_length()
+                val = fac if rel else fac / elen
+                bmesh.utils.edge_split(e, v, val)
+
+            # Loop over all the loops of the vert
+            for l in v.link_loops:
+                # Split the face
+                bmesh.utils.face_split(
+                            l.face,
+                            l.link_loop_next.vert,
+                            l.link_loop_prev.vert
+                            )
+
+            # Remove the vert or displace otherwise
+            if dissolve:
+                bmesh.utils.vert_dissolve(v)
+            else:
+                v.co += displace * v.normal
+
+        me.calc_loop_triangles()
+
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bpy.ops.object.mode_set(mode='EDIT')
+
+        return {'FINISHED'}
+
+
+def register():
+    bpy.utils.register_class(VertexChamfer)
+
+
+def unregister():
+    bpy.utils.unregister_class(VertexChamfer)
+
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/pkhg_faces.py b/mesh_tools/pkhg_faces.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b93d4d793e19b3a76b4220b750f8dc2a2ecfa9f
--- /dev/null
+++ b/mesh_tools/pkhg_faces.py
@@ -0,0 +1,842 @@
+# gpl author: PHKG
+
+bl_info = {
+    "name": "PKHG faces",
+    "author": "PKHG",
+    "version": (0, 0, 6),
+    "blender": (2, 71, 0),
+    "location": "View3D > Tools > PKHG (tab)",
+    "description": "Faces selected will become added faces of different style",
+    "warning": "",
+    "wiki_url": "",
+    "category": "Mesh",
+}
+
+import bpy
+import bmesh
+from bpy.types import Operator
+from mathutils import Vector
+from bpy.props import (
+        BoolProperty,
+        StringProperty,
+        IntProperty,
+        FloatProperty,
+        EnumProperty,
+        )
+
+
+class MESH_OT_add_faces_to_object(Operator):
+    bl_idname = "mesh.add_faces_to_object"
+    bl_label = "Face Shape"
+    bl_description = "Set parameters and build object with added faces"
+    bl_options = {'REGISTER', 'UNDO', 'PRESET'}
+
+    reverse_faces: BoolProperty(
+            name="Reverse Faces",
+            default=False,
+            description="Revert the normals of selected faces"
+            )
+    name_source_object: StringProperty(
+            name="Mesh",
+            description="Choose a Source Mesh",
+            default="Cube"
+            )
+    remove_start_faces: BoolProperty(
+            name="Remove Start Faces",
+            default=True,
+            description="Make a choice about removal of Original Faces"
+            )
+    base_height: FloatProperty(
+            name="Base Height",
+            min=-20,
+            soft_max=10, max=20,
+            default=0.2,
+            description="Set general Base Height"
+            )
+    use_relative_base_height: BoolProperty(
+            name="Relative Base Height",
+            default=False,
+            description="Relative or absolute Base Height"
+            )
+    second_height: FloatProperty(
+            name="2nd height", min=-5,
+            soft_max=5, max=20,
+            default=0.2,
+            description="Second height for various shapes"
+            )
+    width: FloatProperty(
+            name="Width Faces",
+            min=-20, max=20,
+            default=0.5,
+            description="Set general width"
+            )
+    repeat_extrude: IntProperty(
+            name="Repeat",
+            min=1,
+            soft_max=5, max=20,
+            description="For longer base"
+            )
+    move_inside: FloatProperty(
+            name="Move Inside",
+            min=0.0,
+            max=1.0,
+            default=0.5,
+            description="How much move to inside"
+            )
+    thickness: FloatProperty(
+            name="Thickness",
+            soft_min=0.01, min=0,
+            soft_max=5.0, max=20.0,
+            default=0
+            )
+    depth: FloatProperty(
+            name="Depth",
+            min=-5,
+            soft_max=5.0, max=20.0,
+            default=0
+            )
+    collapse_edges: BoolProperty(
+            name="Make Point",
+            default=False,
+            description="Collapse the vertices of edges"
+            )
+    spike_base_width: FloatProperty(
+            name="Spike Base Width",
+            default=0.4,
+            min=-4.0,
+            soft_max=1, max=20,
+            description="Base width of a spike"
+            )
+    base_height_inset: FloatProperty(
+            name="Base Height Inset",
+            default=0.0,
+            min=-5, max=5,
+            description="To elevate or drop the Base height Inset"
+            )
+    top_spike: FloatProperty(
+            name="Top Spike",
+            default=1.0,
+            min=-10.0, max=10.0,
+            description="The Base Height of a spike"
+            )
+    top_extra_height: FloatProperty(
+            name="Top Extra Height",
+            default=0.0,
+            min=-10.0, max=10.0,
+            description="Add extra height"
+            )
+    step_with_real_spike: BoolProperty(
+            name="Step with Real Spike",
+            default=False,
+            description="In stepped, use a real spike"
+            )
+    use_relative: BoolProperty(
+            name="Use Relative",
+            default=False,
+            description="Change size using area, min or max"
+            )
+    face_types: EnumProperty(
+            name="Face Types",
+            description="Different types of Faces",
+            default="no",
+            items=[
+                ('no', "Pick an Option", "Choose one of the available options"),
+                ('open_inset', "Open Inset", "Inset without closing faces (holes)"),
+                ('with_base', "With Base", "Base and ..."),
+                ('clsd_vertical', "Closed Vertical", "Closed Vertical"),
+                ('open_vertical', "Open Vertical", "Open Vertical"),
+                ('spiked', "Spiked", "Spike"),
+                ('stepped', "Stepped", "Stepped"),
+                ('boxed', "Boxed", "Boxed"),
+                ('bar', "Bar", "Bar"),
+                ]
+            )
+    strange_boxed_effect: BoolProperty(
+            name="Strange Effect",
+            default=False,
+            description="Do not show one extrusion"
+            )
+    use_boundary: BoolProperty(
+            name="Use Boundary",
+            default=True
+            )
+    use_even_offset: BoolProperty(
+            name="Even Offset",
+            default=True
+            )
+    use_relative_offset: BoolProperty(
+            name="Relative Offset",
+            default=True
+            )
+    use_edge_rail: BoolProperty(
+            name="Edge Rail",
+            default=False
+            )
+    use_outset: BoolProperty(
+            name="Outset",
+            default=False
+            )
+    use_select_inset: BoolProperty(
+            name="Inset",
+            default=False
+            )
+    use_interpolate: BoolProperty(
+            name="Interpolate",
+            default=True
+            )
+
+    @classmethod
+    def poll(cls, context):
+        result = False
+        active_object = context.active_object
+        if active_object:
+            mesh_objects_name = [el.name for el in bpy.data.objects if el.type == "MESH"]
+            if active_object.name in mesh_objects_name:
+                result = True
+
+        return result
+
+    def draw(self, context):
+        layout = self.layout
+        col = layout.column()
+
+        col.separator()
+        col.label(text="Using Active Object", icon="INFO")
+        col.separator()
+        col.label(text="Face Types:")
+        col.prop(self, "face_types", text="")
+        col.separator()
+        col.prop(self, "use_relative")
+
+        if self.face_types == "open_inset":
+            col.prop(self, "move_inside")
+            col.prop(self, "base_height")
+
+        elif self.face_types == "with_base":
+            col.prop(self, "move_inside")
+            col.prop(self, "base_height")
+            col.prop(self, "second_height")
+            col.prop(self, "width")
+
+        elif self.face_types == "clsd_vertical":
+            col.prop(self, "base_height")
+
+        elif self.face_types == "open_vertical":
+            col.prop(self, "base_height")
+
+        elif self.face_types == "boxed":
+            col.prop(self, "move_inside")
+            col.prop(self, "base_height")
+            col.prop(self, "top_spike")
+            col.prop(self, "strange_boxed_effect")
+
+        elif self.face_types == "spiked":
+            col.prop(self, "spike_base_width")
+            col.prop(self, "base_height_inset")
+            col.prop(self, "top_spike")
+
+        elif self.face_types == "bar":
+            col.prop(self, "spike_base_width")
+            col.prop(self, "top_spike")
+            col.prop(self, "top_extra_height")
+
+        elif self.face_types == "stepped":
+            col.prop(self, "spike_base_width")
+            col.prop(self, "base_height_inset")
+            col.prop(self, "top_extra_height")
+            col.prop(self, "second_height")
+            col.prop(self, "step_with_real_spike")
+
+    def execute(self, context):
+        obj_name = self.name_source_object
+        face_type = self.face_types
+
+        is_selected = check_is_selected()
+
+        if not is_selected:
+            self.report({'WARNING'},
+                        "Operation Cancelled. No selected Faces found on the Active Object")
+            return {'CANCELLED'}
+
+        if face_type == "spiked":
+            Spiked(spike_base_width=self.spike_base_width,
+                   base_height_inset=self.base_height_inset,
+                   top_spike=self.top_spike, top_relative=self.use_relative)
+
+        elif face_type == "boxed":
+            startinfo = prepare(self, context, self.remove_start_faces)
+            bm = startinfo['bm']
+            top = self.top_spike
+            obj = startinfo['obj']
+            obj_matrix_local = obj.matrix_local
+
+            distance = None
+            base_heights = None
+            t = self.move_inside
+            areas = startinfo['areas']
+            base_height = self.base_height
+
+            if self.use_relative:
+                distance = [min(t * area, 1.0) for i, area in enumerate(areas)]
+                base_heights = [base_height * area for i, area in enumerate(areas)]
+            else:
+                distance = [t] * len(areas)
+                base_heights = [base_height] * len(areas)
+
+            rings = startinfo['rings']
+            centers = startinfo['centers']
+            normals = startinfo['normals']
+            for i in range(len(rings)):
+                make_one_inset(self, context, bm=bm, ringvectors=rings[i],
+                               center=centers[i], normal=normals[i],
+                               t=distance[i], base_height=base_heights[i])
+                bpy.ops.mesh.select_mode(type="EDGE")
+                bpy.ops.mesh.select_more()
+                bpy.ops.mesh.select_more()
+            bpy.ops.object.mode_set(mode='OBJECT')
+            # PKHG>INFO base extrusion done and set to the mesh
+
+            # PKHG>INFO if the extrusion is NOT  done ... it'll look strange soon!
+            if not self.strange_boxed_effect:
+                bpy.ops.object.mode_set(mode='EDIT')
+                obj = context.active_object
+                bm = bmesh.from_edit_mesh(obj.data)
+                bmfaces = [face for face in bm.faces if face.select]
+                res = extrude_faces(self, context, bm=bm, face_l=bmfaces)
+                ring_edges = [face.edges[:] for face in res]
+
+            bpy.ops.object.mode_set(mode='OBJECT')
+
+            # PKHG>INFO now the extruded facec have to move in normal direction
+            bpy.ops.object.mode_set(mode='EDIT')
+            obj = bpy.context.view_layer.objects.active
+            bm = bmesh.from_edit_mesh(obj.data)
+            todo_faces = [face for face in bm.faces if face.select]
+            for face in todo_faces:
+                bmesh.ops.translate(bm, vec=face.normal * top, space=obj_matrix_local,
+                                    verts=face.verts)
+            bpy.ops.object.mode_set(mode='OBJECT')
+
+        elif face_type == "stepped":
+            Stepped(spike_base_width=self.spike_base_width,
+                    base_height_inset=self.base_height_inset,
+                    top_spike=self.second_height,
+                    top_extra_height=self.top_extra_height,
+                    use_relative_offset=self.use_relative, with_spike=self.step_with_real_spike)
+
+        elif face_type == "open_inset":
+            startinfo = prepare(self, context, self.remove_start_faces)
+            bm = startinfo['bm']
+
+            # PKHG>INFO adjust for relative, via areas
+            t = self.move_inside
+            areas = startinfo['areas']
+            base_height = self.base_height
+            base_heights = None
+            distance = None
+            if self.use_relative:
+                distance = [min(t * area, 1.0) for i, area in enumerate(areas)]
+                base_heights = [base_height * area for i, area in enumerate(areas)]
+            else:
+                distance = [t] * len(areas)
+                base_heights = [base_height] * len(areas)
+
+            rings = startinfo['rings']
+            centers = startinfo['centers']
+            normals = startinfo['normals']
+            for i in range(len(rings)):
+                make_one_inset(self, context, bm=bm, ringvectors=rings[i],
+                               center=centers[i], normal=normals[i],
+                               t=distance[i], base_height=base_heights[i])
+            bpy.ops.object.mode_set(mode='OBJECT')
+
+        elif face_type == "with_base":
+            startinfo = prepare(self, context, self.remove_start_faces)
+            bm = startinfo['bm']
+            obj = startinfo['obj']
+            object_matrix = obj.matrix_local
+
+            # PKHG>INFO for relative (using areas)
+            t = self.move_inside
+            areas = startinfo['areas']
+            base_height = self.base_height
+            distance = None
+            base_heights = None
+
+            if self.use_relative:
+                distance = [min(t * area, 1.0) for i, area in enumerate(areas)]
+                base_heights = [base_height * area for i, area in enumerate(areas)]
+            else:
+                distance = [t] * len(areas)
+                base_heights = [base_height] * len(areas)
+
+            next_rings = []
+            rings = startinfo['rings']
+            centers = startinfo['centers']
+            normals = startinfo['normals']
+            for i in range(len(rings)):
+                next_rings.append(make_one_inset(self, context, bm=bm, ringvectors=rings[i],
+                                                 center=centers[i], normal=normals[i],
+                                                 t=distance[i], base_height=base_heights[i]))
+
+            prepare_ring = extrude_edges(self, context, bm=bm, edge_l_l=next_rings)
+
+            second_height = self.second_height
+            width = self.width
+            vectors = [[ele.verts[:] for ele in edge] for edge in prepare_ring]
+            n_ring_vecs = []
+
+            for rings in vectors:
+                v = []
+                for edgv in rings:
+                    v.extend(edgv)
+                # PKHF>INFO no double verts allowed, coming from two adjacents edges!
+                bm.verts.ensure_lookup_table()
+                vv = list(set([ele.index for ele in v]))
+
+                vvv = [bm.verts[i].co for i in vv]
+                n_ring_vecs.append(vvv)
+
+            for i, ring in enumerate(n_ring_vecs):
+                make_one_inset(self, context, bm=bm, ringvectors=ring,
+                               center=centers[i], normal=normals[i],
+                               t=width, base_height=base_heights[i] + second_height)
+            bpy.ops.object.mode_set(mode='OBJECT')
+
+        else:
+            if face_type == "clsd_vertical":
+                obj_name = context.active_object.name
+                ClosedVertical(name=obj_name, base_height=self.base_height,
+                               use_relative_base_height=self.use_relative)
+
+            elif face_type == "open_vertical":
+                obj_name = context.active_object.name
+                OpenVertical(name=obj_name, base_height=self.base_height,
+                             use_relative_base_height=self.use_relative)
+
+            elif face_type == "bar":
+                startinfo = prepare(self, context, self.remove_start_faces)
+
+                result = []
+                bm = startinfo['bm']
+                rings = startinfo['rings']
+                centers = startinfo['centers']
+                normals = startinfo['normals']
+                spike_base_width = self.spike_base_width
+                for i, ring in enumerate(rings):
+                    result.append(make_one_inset(self, context, bm=bm,
+                                                 ringvectors=ring, center=centers[i],
+                                                 normal=normals[i], t=spike_base_width))
+
+                next_ring_edges_list = extrude_edges(self, context, bm=bm,
+                                                     edge_l_l=result)
+                top_spike = self.top_spike
+                fac = top_spike
+                object_matrix = startinfo['obj'].matrix_local
+                for i in range(len(next_ring_edges_list)):
+                    translate_ONE_ring(
+                            self, context, bm=bm,
+                            object_matrix=object_matrix,
+                            ring_edges=next_ring_edges_list[i],
+                            normal=normals[i], distance=fac
+                            )
+                next_ring_edges_list_2 = extrude_edges(self, context, bm=bm,
+                                                       edge_l_l=next_ring_edges_list)
+
+                top_extra_height = self.top_extra_height
+                for i in range(len(next_ring_edges_list_2)):
+                    move_corner_vecs_outside(
+                            self, context, bm=bm,
+                            edge_list=next_ring_edges_list_2[i],
+                            center=centers[i], normal=normals[i],
+                            base_height_erlier=fac + top_extra_height,
+                            distance=fac
+                            )
+                bpy.ops.mesh.select_mode(type="VERT")
+                bpy.ops.mesh.select_more()
+
+                bpy.ops.object.mode_set(mode='OBJECT')
+
+        return {'FINISHED'}
+
+
+def find_one_ring(sel_vertices):
+    ring0 = sel_vertices.pop(0)
+    to_delete = []
+
+    for i, edge in enumerate(sel_vertices):
+        len_nu = len(ring0)
+        if len(ring0 - edge) < len_nu:
+            to_delete.append(i)
+            ring0 = ring0.union(edge)
+
+    to_delete.reverse()
+
+    for el in to_delete:
+        sel_vertices.pop(el)
+
+    return (ring0, sel_vertices)
+
+
+class Stepped:
+    def __init__(self, spike_base_width=0.5, base_height_inset=0.0, top_spike=0.2,
+                 top_relative=False, top_extra_height=0, use_relative_offset=False,
+                 with_spike=False):
+
+        bpy.ops.object.mode_set(mode='EDIT')
+        bpy.ops.mesh.inset(
+                use_boundary=True, use_even_offset=True, use_relative_offset=False,
+                use_edge_rail=False, thickness=spike_base_width, depth=0, use_outset=True,
+                use_select_inset=False, use_individual=True, use_interpolate=True
+                )
+        bpy.ops.mesh.inset(
+                use_boundary=True, use_even_offset=True, use_relative_offset=use_relative_offset,
+                use_edge_rail=False, thickness=top_extra_height, depth=base_height_inset,
+                use_outset=True, use_select_inset=False, use_individual=True, use_interpolate=True
+                )
+        bpy.ops.mesh.inset(
+                use_boundary=True, use_even_offset=True, use_relative_offset=use_relative_offset,
+                use_edge_rail=False, thickness=spike_base_width, depth=0, use_outset=True,
+                use_select_inset=False, use_individual=True, use_interpolate=True
+                )
+        bpy.ops.mesh.inset(
+                use_boundary=True, use_even_offset=True, use_relative_offset=False,
+                use_edge_rail=False, thickness=0, depth=top_spike, use_outset=True,
+                use_select_inset=False, use_individual=True, use_interpolate=True
+                )
+        if with_spike:
+            bpy.ops.mesh.merge(type='COLLAPSE')
+
+        bpy.ops.object.mode_set(mode='OBJECT')
+
+
+class Spiked:
+    def __init__(self, spike_base_width=0.5, base_height_inset=0.0, top_spike=0.2, top_relative=False):
+
+        obj = bpy.context.active_object
+        bpy.ops.object.mode_set(mode='EDIT')
+        bpy.ops.mesh.inset(
+                use_boundary=True, use_even_offset=True, use_relative_offset=False,
+                use_edge_rail=False, thickness=spike_base_width, depth=base_height_inset,
+                use_outset=True, use_select_inset=False, use_individual=True, use_interpolate=True
+                )
+        bpy.ops.mesh.inset(
+                use_boundary=True, use_even_offset=True, use_relative_offset=top_relative,
+                use_edge_rail=False, thickness=0, depth=top_spike, use_outset=True,
+                use_select_inset=False, use_individual=True, use_interpolate=True
+                )
+
+        bm = bmesh.from_edit_mesh(obj.data)
+        bpy.ops.mesh.merge(type='COLLAPSE')
+        bpy.ops.object.mode_set(mode='OBJECT')
+
+
+class ClosedVertical:
+    def __init__(self, name="Plane", base_height=1, use_relative_base_height=False):
+        obj = bpy.data.objects[name]
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bm = bmesh.new()
+        bm.from_mesh(obj.data)
+        # PKHG>INFO deselect chosen faces
+        sel = [f for f in bm.faces if f.select]
+        for f in sel:
+            f.select = False
+        res = bmesh.ops.extrude_discrete_faces(bm, faces=sel)
+        # PKHG>INFO select extruded faces
+        for f in res['faces']:
+            f.select = True
+
+        factor = base_height
+        for face in res['faces']:
+            if use_relative_base_height:
+                area = face.calc_area()
+                factor = area * base_height
+            else:
+                factor = base_height
+            for el in face.verts:
+                tmp = el.co + face.normal * factor
+                el.co = tmp
+
+        me = bpy.data.meshes[name]
+        bm.to_mesh(me)
+        bm.free()
+
+
+class OpenVertical:
+    def __init__(self, name="Plane", base_height=1, use_relative_base_height=False):
+
+        obj = bpy.data.objects[name]
+        bpy.ops.object.mode_set(mode='OBJECT')
+        bm = bmesh.new()
+        bm.from_mesh(obj.data)
+        # PKHG>INFO deselect chosen faces
+        sel = [f for f in bm.faces if f.select]
+        for f in sel:
+            f.select = False
+        res = bmesh.ops.extrude_discrete_faces(bm, faces=sel)
+        # PKHG>INFO select extruded faces
+        for f in res['faces']:
+            f.select = True
+
+        # PKHG>INFO adjust extrusion by a vector
+        factor = base_height
+        for face in res['faces']:
+            if use_relative_base_height:
+                area = face.calc_area()
+                factor = area * base_height
+            else:
+                factor = base_height
+            for el in face.verts:
+                tmp = el.co + face.normal * factor
+                el.co = tmp
+
+        me = bpy.data.meshes[name]
+        bm.to_mesh(me)
+        bm.free()
+
+        bpy.ops.object.editmode_toggle()
+        bpy.ops.mesh.delete(type='FACE')
+        bpy.ops.object.editmode_toggle()
+
+
+class StripFaces:
+    def __init__(self, use_boundary=True, use_even_offset=True, use_relative_offset=False,
+                 use_edge_rail=True, thickness=0.0, depth=0.0, use_outset=False,
+                 use_select_inset=False, use_individual=True, use_interpolate=True):
+
+        bpy.ops.object.mode_set(mode='EDIT')
+        bpy.ops.mesh.inset(
+                use_boundary=use_boundary, use_even_offset=True, use_relative_offset=False,
+                use_edge_rail=True, thickness=thickness, depth=depth, use_outset=use_outset,
+                use_select_inset=use_select_inset, use_individual=use_individual,
+                use_interpolate=use_interpolate
+                )
+
+        bpy.ops.object.mode_set(mode='OBJECT')
+
+        # PKHG>IMFO only 3 parameters inc execution context supported!!
+        if False:
+            bpy.ops.mesh.inset(
+                    use_boundary, use_even_offset, use_relative_offset, use_edge_rail,
+                    thickness, depth, use_outset, use_select_inset, use_individual,
+                    use_interpolate
+                    )
+        elif type == 0:
+            bpy.ops.mesh.inset(
+                    use_boundary=True, use_even_offset=True, use_relative_offset=False,
+                    use_edge_rail=True, thickness=thickness, depth=depth, use_outset=False,
+                    use_select_inset=False, use_individual=True, use_interpolate=True
+                    )
+        elif type == 1:
+            bpy.ops.mesh.inset(
+                    use_boundary=True, use_even_offset=True, use_relative_offset=False,
+                    use_edge_rail=True, thickness=thickness, depth=depth, use_outset=False,
+                    use_select_inset=False, use_individual=True, use_interpolate=False
+                    )
+            bpy.ops.mesh.delete(type='FACE')
+
+        elif type == 2:
+            bpy.ops.mesh.inset(
+                    use_boundary=True, use_even_offset=False, use_relative_offset=True,
+                    use_edge_rail=True, thickness=thickness, depth=depth, use_outset=False,
+                    use_select_inset=False, use_individual=True, use_interpolate=False
+                    )
+
+            bpy.ops.mesh.delete(type='FACE')
+
+        elif type == 3:
+            bpy.ops.mesh.inset(
+                    use_boundary=True, use_even_offset=False, use_relative_offset=True,
+                    use_edge_rail=True, thickness=depth, depth=thickness, use_outset=False,
+                    use_select_inset=False, use_individual=True, use_interpolate=True
+                    )
+            bpy.ops.mesh.delete(type='FACE')
+        elif type == 4:
+            bpy.ops.mesh.inset(
+                    use_boundary=True, use_even_offset=False, use_relative_offset=True,
+                    use_edge_rail=True, thickness=thickness, depth=depth, use_outset=True,
+                    use_select_inset=False, use_individual=True, use_interpolate=True
+                    )
+            bpy.ops.mesh.inset(
+                    use_boundary=True, use_even_offset=False, use_relative_offset=True,
+                    use_edge_rail=True, thickness=thickness, depth=depth, use_outset=True,
+                    use_select_inset=False, use_individual=True, use_interpolate=True
+                    )
+        bpy.ops.mesh.delete(type='FACE')
+
+        bpy.ops.object.mode_set(mode='OBJECT')
+
+
+def check_is_selected():
+    is_selected = False
+    for face in bpy.context.active_object.data.polygons:
+        if face.select:
+            is_selected = True
+            break
+    return is_selected
+
+
+def prepare(self, context, remove_start_faces=True):
+    """
+       Start for a face selected change of faces
+       select an object of type mesh, with activated several (all) faces
+    """
+    obj = bpy.context.view_layer.objects.active
+    bpy.ops.object.mode_set(mode='OBJECT')
+    selectedpolygons = [el for el in obj.data.polygons if el.select]
+
+    # PKHG>INFO copies of the vectors are needed, otherwise Blender crashes!
+    centers = [face.center for face in selectedpolygons]
+    centers_copy = [Vector((el[0], el[1], el[2])) for el in centers]
+    normals = [face.normal for face in selectedpolygons]
+    normals_copy = [Vector((el[0], el[1], el[2])) for el in normals]
+
+    vertindicesofpolgons = [
+            [vert for vert in face.vertices] for face in selectedpolygons
+            ]
+    vertVectorsOfSelectedFaces = [
+            [obj.data.vertices[ind].co for ind in vertIndiceofface] for
+            vertIndiceofface in vertindicesofpolgons
+            ]
+    vertVectorsOfSelectedFaces_copy = [
+            [Vector((el[0], el[1], el[2])) for el in listofvecs] for
+            listofvecs in vertVectorsOfSelectedFaces
+            ]
+
+    bpy.ops.object.mode_set(mode='EDIT')
+    bm = bmesh.from_edit_mesh(obj.data)
+    selected_bm_faces = [ele for ele in bm.faces if ele.select]
+
+    selected_edges_per_face_ind = [
+            [ele.index for ele in face.edges] for face in selected_bm_faces
+            ]
+    indices = [el.index for el in selectedpolygons]
+    selected_faces_areas = [bm.faces[:][i] for i in indices]
+    tmp_area = [el.calc_area() for el in selected_faces_areas]
+
+    # PKHG>INFO, selected faces are removed, only their edges are used!
+    if remove_start_faces:
+        bpy.ops.mesh.delete(type='ONLY_FACE')
+        bpy.ops.object.mode_set(mode='OBJECT')
+        obj.data.update()
+        bpy.ops.object.mode_set(mode='EDIT')
+        bm = bmesh.from_edit_mesh(obj.data)
+        bm.verts.ensure_lookup_table()
+        bm.faces.ensure_lookup_table()
+
+    start_ring_raw = [
+            [bm.verts[ind].index for ind in vertIndiceofface] for
+            vertIndiceofface in vertindicesofpolgons
+            ]
+    start_ring = []
+
+    for el in start_ring_raw:
+        start_ring.append(set(el))
+    bm.edges.ensure_lookup_table()
+
+    bm_selected_edges_l_l = [
+            [bm.edges[i] for i in bm_ind_list] for
+            bm_ind_list in selected_edges_per_face_ind
+            ]
+    result = {
+            'obj': obj, 'centers': centers_copy, 'normals': normals_copy,
+            'rings': vertVectorsOfSelectedFaces_copy, 'bm': bm,
+            'areas': tmp_area, 'startBMRingVerts': start_ring,
+            'base_edges': bm_selected_edges_l_l
+            }
+
+    return result
+
+
+def make_one_inset(self, context, bm=None, ringvectors=None, center=None,
+                   normal=None, t=None, base_height=0):
+    # a face will get 'inserted' faces to create (normally) a hole if t is > 0 and < 1)
+    tmp = []
+
+    for el in ringvectors:
+        tmp.append((el * (1 - t) + center * t) + normal * base_height)
+
+    tmp = [bm.verts.new(v) for v in tmp]  # the new corner bmvectors
+    # PKHG>INFO so to say sentinells, to use ONE for ...
+    tmp.append(tmp[0])
+    vectorsFace_i = [bm.verts.new(v) for v in ringvectors]
+    vectorsFace_i.append(vectorsFace_i[0])
+    myres = []
+    for ii in range(len(vectorsFace_i) - 1):
+        # PKHG>INFO next line: sequence is important! for added edge
+        bmvecs = [vectorsFace_i[ii], vectorsFace_i[ii + 1], tmp[ii + 1], tmp[ii]]
+        res = bm.faces.new(bmvecs)
+        myres.append(res.edges[2])
+        myres[-1].select = True  # PKHG>INFO to be used later selected!
+    return (myres)
+
+
+def extrude_faces(self, context, bm=None, face_l=None):
+    # to make a ring extrusion
+    res = bmesh.ops.extrude_discrete_faces(bm, faces=face_l)['faces']
+
+    for face in res:
+        face.select = True
+    return res
+
+
+def extrude_edges(self, context, bm=None, edge_l_l=None):
+    # to make a ring extrusion
+    all_results = []
+    for edge_l in edge_l_l:
+        for edge in edge_l:
+            edge.select = False
+        res = bmesh.ops.extrude_edge_only(bm, edges=edge_l)
+        tmp = [ele for ele in res['geom'] if isinstance(ele, bmesh.types.BMEdge)]
+        for edge in tmp:
+            edge.select = True
+        all_results.append(tmp)
+    return all_results
+
+
+def translate_ONE_ring(self, context, bm=None, object_matrix=None, ring_edges=None,
+                       normal=(0, 0, 1), distance=0.5):
+    # translate a ring in given (normal?!) direction with given (global) amount
+    tmp = []
+    for edge in ring_edges:
+        tmp.extend(edge.verts[:])
+    # PKHG>INFO no double vertices allowed by bmesh!
+    tmp = set(tmp)
+    tmp = list(tmp)
+    bmesh.ops.translate(bm, vec=normal * distance, space=object_matrix, verts=tmp)
+    # PKHG>INFO relevant edges will stay selected
+    return ring_edges
+
+
+def move_corner_vecs_outside(self, context, bm=None, edge_list=None, center=None,
+                             normal=None, base_height_erlier=0.5, distance=0.5):
+    # move corners (outside meant mostly) dependent on the parameters
+    tmp = []
+    for edge in edge_list:
+        tmp.extend([ele for ele in edge.verts if isinstance(ele, bmesh.types.BMVert)])
+    # PKHG>INFO to remove vertices, they are all used twice in the ring!
+    tmp = set(tmp)
+    tmp = list(tmp)
+
+    for i in range(len(tmp)):
+        vec = tmp[i].co
+        direction = vec + (vec - (normal * base_height_erlier + center)) * distance
+        tmp[i].co = direction
+
+# define classes for registration
+classes = (
+    MESH_OT_add_faces_to_object,
+    )
+
+def register():
+    for cls in classes:
+        bpy.utils.register_class(cls)
+
+
+def unregister():
+    for cls in classes:
+        bpy.utils.unregister_class(cls)
+
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/random_vertices.py b/mesh_tools/random_vertices.py
new file mode 100644
index 0000000000000000000000000000000000000000..51f8be242249fad3b1e7b55828f6450732559b85
--- /dev/null
+++ b/mesh_tools/random_vertices.py
@@ -0,0 +1,140 @@
+# gpl authors: Oscurart, Greg
+
+bl_info = {
+    "name": "Random Vertices",
+    "author": "Oscurart, Greg",
+    "version": (1, 3),
+    "blender": (2, 63, 0),
+    "location": "Object > Transform > Random Vertices",
+    "description": "Randomize selected components of active object",
+    "warning": "",
+    "wiki_url": "",
+    "category": "Mesh"}
+
+
+import bpy
+from bpy.types import Operator
+import random
+import bmesh
+from bpy.props import (
+        BoolProperty,
+        FloatProperty,
+        IntVectorProperty,
+        )
+
+
+def add_object(self, context, valmin, valmax, factor, vgfilter):
+    # select an option with weight map or not
+    mode = bpy.context.active_object.mode
+    # generate variables
+    objact = bpy.context.active_object
+    listver = []
+    warn_message = False
+
+    # switch to edit mode
+    bpy.ops.object.mode_set(mode='OBJECT')
+    bpy.ops.object.mode_set(mode='EDIT')
+
+    # bmesh object
+    odata = bmesh.from_edit_mesh(objact.data)
+    odata.select_flush(False)
+
+    # if the vertex is selected add to the list
+    for vertice in odata.verts[:]:
+        if vertice.select:
+            listver.append(vertice.index)
+
+    # If the minimum value is greater than the maximum,
+    # it adds a value to the maximum
+    if valmin[0] >= valmax[0]:
+        valmax[0] = valmin[0] + 1
+
+    if valmin[1] >= valmax[1]:
+        valmax[1] = valmin[1] + 1
+
+    if valmin[2] >= valmax[2]:
+        valmax[2] = valmin[2] + 1
+
+    odata.verts.ensure_lookup_table()
+
+    random_factor = factor
+    for vertice in listver:
+        odata.verts.ensure_lookup_table()
+        if odata.verts[vertice].select:
+            if vgfilter is True:
+                has_group = getattr(objact.data.vertices[vertice], "groups", None)
+                vertex_group = has_group[0] if has_group else None
+                vertexweight = getattr(vertex_group, "weight", None)
+                if vertexweight:
+                    random_factor = factor * vertexweight
+                else:
+                    random_factor = factor
+                    warn_message = True
+
+            odata.verts[vertice].co = (
+                (((random.randrange(valmin[0], valmax[0], 1)) * random_factor) / 1000) +
+                odata.verts[vertice].co[0],
+                (((random.randrange(valmin[1], valmax[1], 1)) * random_factor) / 1000) +
+                odata.verts[vertice].co[1],
+                (((random.randrange(valmin[2], valmax[2], 1)) * random_factor) / 1000) +
+                odata.verts[vertice].co[2]
+                )
+
+    if warn_message:
+        self.report({'WARNING'},
+                    "Some of the Selected Vertices don't have a Group with Vertex Weight assigned")
+    bpy.ops.object.mode_set(mode=mode)
+
+
+class MESH_OT_random_vertices(Operator):
+    bl_idname = "mesh.random_vertices"
+    bl_label = "Random Vertices"
+    bl_description = ("Randomize the location of vertices by a specified\n"
+                      "Multiplier Factor and random values in the defined range\n"
+                      "or a multiplication of them and the Vertex Weights")
+    bl_options = {'REGISTER', 'UNDO'}
+
+    vgfilter: BoolProperty(
+            name="Vertex Group",
+            description="Use Vertex Weight defined in the Active Group",
+            default=False
+            )
+    factor: FloatProperty(
+            name="Factor",
+            description="Base Multiplier of the randomization effect",
+            default=1
+            )
+    valmin: IntVectorProperty(
+            name="Min XYZ",
+            description="Define the minimum range of randomization values",
+            default=(0, 0, 0)
+            )
+    valmax: IntVectorProperty(
+            name="Max XYZ",
+            description="Define the maximum range of randomization values",
+            default=(1, 1, 1)
+            )
+
+    @classmethod
+    def poll(cls, context):
+        return (context.object and context.object.type == "MESH" and
+               context.mode == "EDIT_MESH")
+
+    def execute(self, context):
+        add_object(self, context, self.valmin, self.valmax, self.factor, self.vgfilter)
+
+        return {'FINISHED'}
+
+
+# Registration
+
+def register():
+    bpy.utils.register_class(MESH_OT_random_vertices)
+
+
+def unregister():
+    bpy.utils.unregister_class(MESH_OT_random_vertices)
+
+
+if __name__ == '__main__':
+    register()
diff --git a/mesh_tools/split_solidify.py b/mesh_tools/split_solidify.py
new file mode 100644
index 0000000000000000000000000000000000000000..690557ed584dc23f4accf3c8d14fc0a391fe68bc
--- /dev/null
+++ b/mesh_tools/split_solidify.py
@@ -0,0 +1,203 @@
+# -*- coding: utf-8 -*-
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+    "name": "Split Solidify",
+    "author": "zmj100, updated by zeffii to BMesh",
+    "version": (0, 1, 2),
+    "blender": (2, 80, 0),
+    "location": "View3D > Tool Shelf",
+    "description": "",
+    "warning": "",
+    "wiki_url": "",
+    "category": "Mesh"}
+
+import bpy
+import bmesh
+from bpy.types import Operator
+from bpy.props import (
+        EnumProperty,
+        FloatProperty,
+        BoolProperty,
+        )
+import random
+from math import cos
+
+
+# define the functions
+def solidify_split(self, list_0):
+
+    loc_random = self.loc_random
+    random_dist = self.random_dist
+    distance = self.distance
+    thickness = self.thickness
+    normal_extr = self.normal_extr
+
+    bm = self.bm
+
+    for fi in list_0:
+        bm.faces.ensure_lookup_table()
+        f = bm.faces[fi]
+        list_1 = []
+        list_2 = []
+
+        if loc_random:
+            d = random_dist * random.randrange(0, 10)
+        elif not loc_random:
+            d = distance
+
+        # add new vertices
+        for vi in f.verts:
+            bm.verts.ensure_lookup_table()
+            v = bm.verts[vi.index]
+
+            if normal_extr == 'opt0':
+                p1 = (v.co).copy() + ((f.normal).copy() * d)                # out
+                p2 = (v.co).copy() + ((f.normal).copy() * (d - thickness))  # in
+            elif normal_extr == 'opt1':
+                ang = ((v.normal).copy()).angle((f.normal).copy())
+                h = thickness / cos(ang)
+                p1 = (v.co).copy() + ((f.normal).copy() * d)
+                p2 = p1 + (-h * (f.normal).copy())
+
+            v1 = bm.verts.new(p1)
+            v2 = bm.verts.new(p2)
+            v1.select = False
+            v2.select = False
+            list_1.append(v1)
+            list_2.append(v2)
+
+        # add new faces, allows faces with more than 4 verts
+        n = len(list_1)
+
+        k = bm.faces.new(list_1)
+        k.select = False
+        for i in range(n):
+            j = (i + 1) % n
+            vseq = list_1[i], list_2[i], list_2[j], list_1[j]
+            k = bm.faces.new(vseq)
+            k.select = False
+
+        list_2.reverse()
+        k = bm.faces.new(list_2)
+        k.select = False
+    bpy.ops.mesh.normals_make_consistent(inside=False)
+
+    bmesh.update_edit_mesh(self.me, True)
+
+
+class MESH_OT_split_solidify(Operator):
+    bl_idname = "mesh.split_solidify"
+    bl_label = "Split Solidify"
+    bl_description = "Split and Solidify selected Faces"
+    bl_options = {"REGISTER", "UNDO"}
+
+    distance: FloatProperty(
+            name="",
+            description="Distance of the splitted Faces to the original geometry",
+            default=0.4,
+            min=-100.0, max=100.0,
+            step=1,
+            precision=3
+            )
+    thickness: FloatProperty(
+            name="",
+            description="Thickness of the splitted Faces",
+            default=0.04,
+            min=-100.0, max=100.0,
+            step=1,
+            precision=3
+            )
+    random_dist: FloatProperty(
+            name="",
+            description="Randomization factor of the splitted Faces' location",
+            default=0.06,
+            min=-10.0, max=10.0,
+            step=1,
+            precision=3
+            )
+    loc_random: BoolProperty(
+            name="Random",
+            description="Randomize the locations of splitted faces",
+            default=False
+            )
+    del_original: BoolProperty(
+            name="Delete original faces",
+            default=True
+            )
+    normal_extr: EnumProperty(
+            items=(('opt0', "Face", "Solidify along Face Normals"),
+                   ('opt1', "Vertex", "Solidify along Vertex Normals")),
+            name="Normal",
+            default='opt0'
+           )
+
+    def draw(self, context):
+        layout = self.layout
+        layout.label(text="Normal:")
+        layout.prop(self, "normal_extr", expand=True)
+        layout.prop(self, "loc_random")
+
+        if not self.loc_random:
+            layout.label(text="Distance:")
+            layout.prop(self, "distance")
+        elif self.loc_random:
+            layout.label(text="Random distance:")
+            layout.prop(self, "random_dist")
+
+        layout.label(text="Thickness:")
+        layout.prop(self, "thickness")
+        layout.prop(self, "del_original")
+
+    def execute(self, context):
+        obj = bpy.context.active_object
+        self.me = obj.data
+        self.bm = bmesh.from_edit_mesh(self.me)
+        self.me.update()
+
+        list_0 = [f.index for f in self.bm.faces if f.select]
+
+        if len(list_0) == 0:
+            self.report({'WARNING'},
+                        "No suitable selection found. Operation cancelled")
+
+            return {'CANCELLED'}
+
+        elif len(list_0) != 0:
+            solidify_split(self, list_0)
+            context.tool_settings.mesh_select_mode = (True, True, True)
+            if self.del_original:
+                bpy.ops.mesh.delete(type='FACE')
+            else:
+                pass
+
+        return {'FINISHED'}
+
+
+def register():
+    bpy.utils.register_class(MESH_OT_split_solidify)
+
+
+def unregister():
+    bpy.utils.unregister_class(MESH_OT_split_solidify)
+
+
+if __name__ == "__main__":
+    register()
diff --git a/mesh_tools/vertex_align.py b/mesh_tools/vertex_align.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb66d747e5192edbc103396472ea30770d45488d
--- /dev/null
+++ b/mesh_tools/vertex_align.py
@@ -0,0 +1,301 @@
+# -*- coding: utf-8 -*-
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Note: Property group was moved to __init__
+
+bl_info = {
+    "name": "Vertex Align",
+    "author": "",
+    "version": (0, 1, 7),
+    "blender": (2, 61, 0),
+    "location": "View3D > Tool Shelf",
+    "description": "",
+    "warning": "",
+    "wiki_url": "",
+    "category": "Mesh"}
+
+
+import bpy
+from bpy.props import (
+        BoolVectorProperty,
+        FloatVectorProperty,
+        )
+from mathutils import Vector
+from bpy.types import Operator
+
+
+# Edit Mode Toggle
+def edit_mode_out():
+    bpy.ops.object.mode_set(mode='OBJECT')
+
+
+def edit_mode_in():
+    bpy.ops.object.mode_set(mode='EDIT')
+
+
+def get_mesh_data_():
+    edit_mode_out()
+    ob_act = bpy.context.active_object
+    me = ob_act.data
+    edit_mode_in()
+    return me
+
+
+def list_clear_(l):
+    l[:] = []
+    return l
+
+
+class va_buf():
+    list_v = []
+    list_0 = []
+
+
+# Store The Vertex coordinates
+class Vertex_align_store(Operator):
+    bl_idname = "vertex_align.store_id"
+    bl_label = "Active Vertex"
+    bl_description = ("Store Selected Vertex coordinates as an align point\n"
+                      "Single Selected Vertex only")
+
+    @classmethod
+    def poll(cls, context):
+        obj = context.active_object
+        return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def execute(self, context):
+        try:
+            me = get_mesh_data_()
+            list_0 = [v.index for v in me.vertices if v.select]
+
+            if len(list_0) == 1:
+                list_clear_(va_buf.list_v)
+                for v in me.vertices:
+                    if v.select:
+                        va_buf.list_v.append(v.index)
+                        bpy.ops.mesh.select_all(action='DESELECT')
+            else:
+                self.report({'WARNING'}, "Please select just One Vertex")
+                return {'CANCELLED'}
+        except:
+            self.report({'WARNING'}, "Storing selection could not be completed")
+            return {'CANCELLED'}
+
+        self.report({'INFO'}, "Selected Vertex coordinates are stored")
+
+        return {'FINISHED'}
+
+
+# Align to original
+class Vertex_align_original(Operator):
+    bl_idname = "vertex_align.align_original"
+    bl_label = "Align to original"
+    bl_description = "Align selection to stored single vertex coordinates"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    @classmethod
+    def poll(cls, context):
+        obj = context.active_object
+        return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def draw(self, context):
+        layout = self.layout
+        layout.label(text="Axis:")
+
+        row = layout.row(align=True)
+        row.prop(context.scene.mesh_extra_tools, "vert_align_axis",
+                 text="X", index=0, toggle=True)
+        row.prop(context.scene.mesh_extra_tools, "vert_align_axis",
+                 text="Y", index=1, toggle=True)
+        row.prop(context.scene.mesh_extra_tools, "vert_align_axis",
+                 text="Z", index=2, toggle=True)
+
+    def execute(self, context):
+        edit_mode_out()
+        ob_act = context.active_object
+        me = ob_act.data
+        cen1 = context.scene.mesh_extra_tools.vert_align_axis
+        list_0 = [v.index for v in me.vertices if v.select]
+
+        if len(va_buf.list_v) == 0:
+            self.report({'INFO'},
+                        "Original vertex not stored in memory. Operation Cancelled")
+            edit_mode_in()
+            return {'CANCELLED'}
+
+        elif len(va_buf.list_v) != 0:
+            if len(list_0) == 0:
+                self.report({'INFO'}, "No vertices selected. Operation Cancelled")
+                edit_mode_in()
+                return {'CANCELLED'}
+
+            elif len(list_0) != 0:
+                vo = (me.vertices[va_buf.list_v[0]].co).copy()
+                if cen1[0] is True:
+                    for i in list_0:
+                        v = (me.vertices[i].co).copy()
+                        me.vertices[i].co = Vector((vo[0], v[1], v[2]))
+                if cen1[1] is True:
+                    for i in list_0:
+                        v = (me.vertices[i].co).copy()
+                        me.vertices[i].co = Vector((v[0], vo[1], v[2]))
+                if cen1[2] is True:
+                    for i in list_0:
+                        v = (me.vertices[i].co).copy()
+                        me.vertices[i].co = Vector((v[0], v[1], vo[2]))
+        edit_mode_in()
+
+        return {'FINISHED'}
+
+
+# Align to custom coordinates
+class Vertex_align_coord_list(Operator):
+    bl_idname = "vertex_align.coord_list_id"
+    bl_label = ""
+    bl_description = "Align to custom coordinates"
+
+    @classmethod
+    def poll(cls, context):
+        obj = context.active_object
+        return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')
+
+    def execute(self, context):
+        edit_mode_out()
+        ob_act = context.active_object
+        me = ob_act.data
+        list_clear_(va_buf.list_0)
+        va_buf.list_0 = [v.index for v in me.vertices if v.select][:]
+
+        if len(va_buf.list_0) == 0:
+            self.report({'INFO'}, "No vertices selected. Operation Cancelled")
+            edit_mode_in()
+            return {'CANCELLED'}
+
+        elif len(va_buf.list_0) != 0:
+            bpy.ops.vertex_align.coord_menu_id('INVOKE_DEFAULT')
+
+        edit_mode_in()
+
+        return {'FINISHED'}
+
+
+# Align to custom coordinates menu
+class Vertex_align_coord_menu(Operator):
+    bl_idname = "vertex_align.coord_menu_id"
+    bl_label = "Tweak custom coordinates"
+    bl_description = "Change the custom coordinates for aligning"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    def_axis_coord: FloatVectorProperty(
+            name="",
+            description="Enter the values of coordinates",
+            default=(0.0, 0.0, 0.0),
+            min=-100.0, max=100.0,
+            step=1, size=3,
+            subtype='XYZ',
+            precision=3
+            )
+    use_axis_coord = BoolVectorProperty(
+            name="Axis",
+            description="Choose Custom Coordinates axis",
+            default=(False,) * 3,
+            size=3,
+            )
+    is_not_undo = False
+
+    @classmethod
+    def poll(cls, context):
+        obj = context.active_object
+        return (obj and obj.type == 'MESH')
+
+    def using_store(self, context):
+        scene = context.scene
+        return scene.mesh_extra_tools.vert_align_use_stored
+
+    def draw(self, context):
+        layout = self.layout
+
+        if self.using_store(context) and self.is_not_undo:
+            layout.label(text="Using Stored Coordinates", icon="INFO")
+
+        row = layout.split(0.25)
+        row.prop(self, "use_axis_coord", index=0, text="X")
+        row.prop(self, "def_axis_coord", index=0)
+
+        row = layout.split(0.25)
+        row.prop(self, "use_axis_coord", index=1, text="Y")
+        row.prop(self, "def_axis_coord", index=1)
+
+        row = layout.split(0.25)
+        row.prop(self, "use_axis_coord", index=2, text="Z")
+        row.prop(self, "def_axis_coord", index=2)
+
+    def invoke(self, context, event):
+        self.is_not_undo = True
+        scene = context.scene
+        if self.using_store(context):
+            self.def_axis_coord = scene.mesh_extra_tools.vert_align_store_axis
+
+        return context.window_manager.invoke_props_dialog(self, width=200)
+
+    def execute(self, context):
+        self.is_not_undo = False
+        edit_mode_out()
+        ob_act = context.active_object
+        me = ob_act.data
+
+        for i in va_buf.list_0:
+            v = (me.vertices[i].co).copy()
+            tmp = Vector((v[0], v[1], v[2]))
+
+            if self.use_axis_coord[0] is True:
+                tmp[0] = self.def_axis_coord[0]
+            if self.use_axis_coord[1] is True:
+                tmp[1] = self.def_axis_coord[1]
+            if self.use_axis_coord[2] is True:
+                tmp[2] = self.def_axis_coord[2]
+            me.vertices[i].co = tmp
+
+        edit_mode_in()
+
+        return {'FINISHED'}
+
+
+#  Register
+classes = (
+    Vertex_align_store,
+    Vertex_align_original,
+    Vertex_align_coord_list,
+    Vertex_align_coord_menu,
+    )
+
+
+def register():
+    for cls in classes:
+        bpy.utils.register_class(cls)
+
+
+def unregister():
+    for cls in classes:
+        bpy.utils.unregister_class(cls)
+
+
+if __name__ == "__main__":
+    register()