diff --git a/mesh_easy_lattice.py b/mesh_easy_lattice.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7c744eb260c70bbb7dbb61660384897b5068697
--- /dev/null
+++ b/mesh_easy_lattice.py
@@ -0,0 +1,735 @@
+# ##### 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": " Easy Lattice",
+            "author": "Kursad Karatas / Mechanical Mustache Labs",
+            "version": ( 1, 0, 1 ),
+            "blender": ( 2, 80,0 ),
+            "location": "View3D > EZ Lattice",
+            "description": "Create a lattice for shape editing",
+            "warning": "",
+            "wiki_url": "",
+            "tracker_url": "",
+            "category": "Mesh"}
+
+import bpy
+import copy
+
+import bmesh
+from bpy.app.handlers import persistent
+from bpy.props import (EnumProperty, FloatProperty, FloatVectorProperty,
+                       IntProperty, StringProperty, BoolProperty)
+from bpy.types import Operator
+from bpy_extras.object_utils import AddObjectHelper, object_data_add
+from mathutils import Matrix, Vector
+import mathutils
+import numpy as np
+
+
+LAT_TYPES= ( ( 'KEY_LINEAR', 'KEY_LINEAR', '' ), ( 'KEY_CARDINAL', 'KEY_CARDINAL', '' ), ( 'KEY_BSPLINE', 'KEY_BSPLINE', '' ) )
+
+OP_TYPES= ( ( 'NEW', 'NEW', '' ), ( 'APPLY', 'APPLY', '' ), ( 'CLEAR', 'CLEAR', '' ) )
+
+def defineSceneProps():
+    bpy.types.Scene.ezlattice_object = StringProperty(name="Object2Operate",
+                                        description="Object to be operated on",
+                                        default="")
+
+    bpy.types.Scene.ezlattice_objects = StringProperty(name="Objects2Operate",
+                                        description="Objects to be operated on",
+                                        default="")
+
+    bpy.types.Scene.ezlattice_mode = StringProperty(name="CurrentMode",
+                                        default="")
+
+    bpy.types.Scene.ezlattice_lattice = StringProperty(name="LatticeObName",
+                                        default="")
+
+    bpy.types.Scene.ezlattice_flag = BoolProperty(name="LatticeFlag", default=False)
+
+def defineObjectProps():
+
+    bpy.types.Object.ezlattice_flag = BoolProperty(name="LatticeFlag", default=False)
+
+    bpy.types.Object.ezlattice_controller = StringProperty(name="LatticeController", default="")
+
+    bpy.types.Object.ezlattice_modifier = StringProperty(name="latticeModifier", default="")
+
+def objectMode():
+
+    if isEditMode():
+        bpy.ops.object.mode_set(mode="OBJECT")
+
+    else:
+        return True
+
+    return
+
+def isEditMode():
+    """Check to see we are in edit  mode
+    """
+
+    try:
+        if bpy.context.object.mode == "EDIT":
+            return True
+
+        else:
+            return False
+
+    except:
+        print("No active mesh object")
+
+
+
+def isObjectMode():
+
+    if bpy.context.object.mode == "OBJECT":
+        return True
+
+    else:
+        return False
+
+
+def setMode(mode=None):
+
+    if mode:
+        bpy.ops.object.mode_set(mode=mode)
+
+def curMode():
+
+    return bpy.context.object.mode
+
+def editMode():
+
+    if not isEditMode():
+        bpy.ops.object.mode_set(mode="EDIT")
+
+    else:
+        return True
+
+    return
+
+
+def setSelectActiveObject(context, obj):
+
+    if context.mode == 'OBJECT':
+
+        bpy.ops.object.select_all(action='DESELECT')
+        context.view_layer.objects.active = obj
+        obj.select_set(True)
+
+
+def getObject(name=None):
+    try:
+        ob=[o for o in bpy.data.objects if o.name == name][0]
+        return ob
+
+    except:
+        return None
+
+
+
+def buildTrnScl_WorldMat( obj ):
+    # This function builds a real world matrix that encodes translation and scale and it leaves out the rotation matrix
+    # The rotation is applied at obejct level if there is any
+    loc,rot,scl=obj.matrix_world.decompose()
+    mat_trans = mathutils.Matrix.Translation( loc)
+
+    mat_scale = mathutils.Matrix.Scale( scl[0], 4, ( 1, 0, 0 ) )
+    mat_scale @= mathutils.Matrix.Scale( scl[1], 4, ( 0, 1, 0 ) )
+    mat_scale @= mathutils.Matrix.Scale( scl[2], 4, ( 0, 0, 1 ) )
+
+    mat_final = mat_trans @ mat_scale
+
+    return mat_final
+
+def getSelectedVerts(context):
+    """
+    https://devtalk.blender.org/t/foreach-get-for-selected-vertex-indices/7712/6
+
+    v_sel = np.empty(len(me.vertices), dtype=bool)
+    me.vertices.foreach_get('select', v_sel)
+
+    sel_idx, = np.where(v_sel)
+    unsel_idx, = np.where(np.invert(v_sel))
+
+    """
+
+    obj = context.active_object
+    m=obj.matrix_world
+
+    count=len(obj.data.vertices)
+    shape = (count, 3)
+
+
+    if obj.type=='MESH':
+        me = obj.data
+        bm = bmesh.from_edit_mesh(me)
+
+        verts=[m@v.co for v in bm.verts if v.select]
+
+    return np.array(verts, dtype=np.float32)
+
+
+def getSelectedVertsNumPy(context):
+    obj = context.active_object
+
+    if obj.type=='MESH':
+
+        obj.update_from_editmode()
+
+        #BMESH
+        me = obj.data
+        bm = bmesh.from_edit_mesh(me)
+
+        verts_selected=np.array([v.select for v in bm.verts])
+
+        count=len(obj.data.vertices)
+        shape = (count, 3)
+
+        co = np.empty(count*3, dtype=np.float32)
+
+        obj.data.vertices.foreach_get("co",co)
+
+
+        co.shape=shape
+
+        return co[verts_selected]
+
+
+def findSelectedVertsBBoxNumPy(context):
+
+
+    verts=getSelectedVerts(context)
+
+    x_min = verts[:,0].min()
+    y_min = verts[:,1].min()
+    z_min = verts[:,2].min()
+
+    x_max = verts[:,0].max()
+    y_max = verts[:,1].max()
+    z_max = verts[:,2].max()
+
+
+    x_avg = verts[:,0].mean()
+    y_avg = verts[:,1].mean()
+    z_avg = verts[:,2].mean()
+
+    middle=Vector( ( (x_min+x_max)/2,
+                    (y_min+y_max)/2,
+                    (z_min+z_max)/2 )
+                )
+
+    bbox= [ np.array([x_max,y_max,z_max], dtype=np.float32),
+            np.array([x_min, y_min, z_min],  dtype=np.float32),
+            np.array([x_avg, y_avg, z_avg],  dtype=np.float32),
+            np.array(middle)
+            ]
+
+    return bbox
+
+
+def addSelected2VertGrp():
+
+    C=bpy.context
+
+    grp=C.active_object.vertex_groups.new(name=".templatticegrp")
+    bpy.ops.object.vertex_group_assign()
+    bpy.ops.object.vertex_group_set_active( group = grp.name )
+
+def removetempVertGrp():
+
+    C=bpy.context
+
+    grp=[g for g in C.active_object.vertex_groups if ".templatticegrp" in g.name]
+
+    if grp:
+        for g in grp:
+            bpy.context.object.vertex_groups.active_index = g.index
+            bpy.ops.object.vertex_group_remove(all=False, all_unlocked=False)
+
+
+def cleanupLatticeObjects(context):
+
+    cur_obj=context.active_object
+
+    try:
+        lats=[l for l in bpy.data.objects if ".latticetemp" in l.name]
+
+        if lats:
+            for l in lats:
+                setSelectActiveObject(context, l)
+                bpy.data.objects.remove(l)
+
+                bpy.ops.ed.undo_push()
+
+        setSelectActiveObject(context, cur_obj)
+
+    except:
+        print("no cleanup")
+
+
+
+
+def cleanupLatticeModifier(context):
+
+    scn=context.scene
+
+    obj_operated_name=scn.ezlattice_object
+    obj_operated=getObject(obj_operated_name)
+
+    curmode=curMode()
+
+    temp_mod=None
+
+    obj=None
+
+    if obj_operated:
+
+        if context.active_object.type=='LATTICE':
+            setMode('OBJECT')
+            setSelectActiveObject(context, obj_operated )
+
+
+        temp_mod=[m for m in obj_operated.modifiers if ".LatticeModTemp" in m.name]
+
+        obj=obj_operated
+
+    else:
+        temp_mod=[m for m in context.object.modifiers if ".LatticeModTemp" in m.name]
+        obj=context.object
+
+    if temp_mod:
+        for m in temp_mod:
+            bpy.ops.object.modifier_remove(modifier=m.name)
+
+        setMode(curmode)
+
+        return True
+
+    return False
+
+def cleanupApplyPre(context):
+
+    scn=context.scene
+
+    obj_operated_name=scn.ezlattice_object
+
+    obj_operated=getObject(obj_operated_name)
+
+    cur_mode=curMode()
+
+
+    if obj_operated:
+
+        if context.active_object.type=='LATTICE':
+            setMode('OBJECT')
+            setSelectActiveObject(context, obj_operated )
+
+
+        temp_mod=[m for m in obj_operated.modifiers if ".LatticeModTemp" in m.name]
+
+
+        lats=[l for l in bpy.data.objects if ".latticetemp" in l.name]
+
+        cur_obj=context.active_object
+
+        curmode=curMode()
+
+
+        if isEditMode():
+            objectMode()
+
+        if temp_mod:
+            for m in temp_mod:
+                if m.object:
+                    bpy.ops.object.modifier_apply(apply_as='DATA', modifier=m.name)
+
+                else:
+                    bpy.ops.object.modifier_remove(modifier=m.name)
+
+        if lats:
+            for l in lats:
+
+                bpy.data.objects.remove(l)
+
+                bpy.ops.ed.undo_push()
+
+        setSelectActiveObject(context, cur_obj)
+
+        setMode(curmode)
+        bpy.ops.object.mode_set(mode=curmode)
+
+
+
+def createLatticeObject(context, loc=Vector((0,0,0)), scale=Vector((1,1,1)),
+                        name=".latticetemp", divisions=[], interp="KEY_BSPLINE"):
+
+    C=bpy.context
+    lat_name=name+"_"+C.object.name
+
+    lat = bpy.data.lattices.new( lat_name )
+    ob = bpy.data.objects.new( lat_name, lat )
+    ob.data.use_outside=True
+    ob.data.points_u=divisions[0]
+    ob.data.points_v=divisions[1]
+    ob.data.points_w=divisions[2]
+
+    ob.data.interpolation_type_u = interp
+    ob.data.interpolation_type_v = interp
+    ob.data.interpolation_type_w = interp
+
+    scene = context.scene
+    scene.collection.objects.link(ob)
+    ob.location=loc
+    ob.scale = scale
+
+    return ob
+
+
+def applyLatticeModifier():
+
+    try:
+        temp_mod=[m for m in bpy.context.object.modifiers if ".LatticeModTemp" in m.name]
+
+        if temp_mod:
+            for m in temp_mod:
+                bpy.ops.object.modifier_apply(apply_as='DATA', modifier=m.name)
+
+    except:
+        print("no modifiers")
+
+
+def addLatticeModifier(context, lat,vrtgrp=""):
+
+    bpy.ops.object.modifier_add(type='LATTICE')
+
+    bpy.context.object.modifiers['Lattice'].name=".LatticeModTemp"
+
+    bpy.context.object.modifiers[".LatticeModTemp"].object=lat
+    bpy.context.object.modifiers[".LatticeModTemp"].vertex_group=vrtgrp
+
+    bpy.context.object.modifiers[".LatticeModTemp"].show_in_editmode = True
+    bpy.context.object.modifiers[".LatticeModTemp"].show_on_cage = True
+
+
+
+def newLatticeOp(obj, context,self):
+
+    scn=context.scene
+
+
+    if scn.ezlattice_flag:
+
+        applyLatticeOp(obj, context)
+
+        scn.ezlattice_flag=False
+        scn.ezlattice_mode=""
+
+        return
+
+    cur_obj=obj
+    scn.ezlattice_object=cur_obj.name
+
+    scn.ezlattice_mode=curMode()
+
+    cleanupApplyPre(context)
+
+    removetempVertGrp()
+
+    addSelected2VertGrp()
+
+    bbox=findSelectedVertsBBoxNumPy(context)
+    scale=bbox[0]-bbox[1]
+
+    loc_crs=Vector((bbox[3][0],bbox[3][1],bbox[3][2]))
+
+    lat=createLatticeObject(context, loc=loc_crs, scale=scale,
+                            divisions=[self.lat_u,self.lat_w,self.lat_m],
+                            interp=self.lat_type )
+
+    scn.ezlattice_lattice=lat.name
+
+
+    setSelectActiveObject(context, cur_obj)
+
+    addLatticeModifier(context, lat=lat, vrtgrp=".templatticegrp")
+
+    objectMode()
+    setSelectActiveObject(context, lat)
+
+    editMode()
+
+    scn.ezlattice_flag=True
+
+
+
+
+    return
+
+def resetHelperAttrbs(context):
+
+    scn=context.scene
+
+    scn.ezlattice_mode=""
+    scn.ezlattice_object=""
+    scn.ezlattice_objects=""
+    scn.ezlattice_lattice=""
+
+
+def applyLatticeOp(obj, context):
+
+    scn=context.scene
+
+    if scn.ezlattice_mode=="EDIT":
+
+
+        obj_operated_name=scn.ezlattice_object
+
+        obj_operated=getObject(obj_operated_name)
+
+        cur_mode=curMode()
+
+        if obj_operated:
+
+            if context.active_object.type=='LATTICE':
+                setMode('OBJECT')
+                setSelectActiveObject(context, obj_operated )
+
+            temp_mod=[m for m in obj_operated.modifiers if ".LatticeModTemp" in m.name]
+
+
+            lats=[l for l in bpy.data.objects if ".latticetemp" in l.name]
+
+            cur_obj=context.active_object
+
+            curmode=curMode()
+
+
+            if isEditMode():
+                objectMode()
+
+            if temp_mod:
+                for m in temp_mod:
+                    if m.object:
+                        bpy.ops.object.modifier_apply(apply_as='DATA', modifier=m.name)
+
+                    else:
+                        bpy.ops.object.modifier_remove(modifier=m.name)
+
+            if lats:
+                for l in lats:
+
+                    bpy.data.objects.remove(l)
+
+                    bpy.ops.ed.undo_push()
+
+            setSelectActiveObject(context, cur_obj)
+
+            setMode(curmode)
+            bpy.ops.object.mode_set(mode=curmode)
+
+
+        removetempVertGrp()
+
+
+    resetHelperAttrbs(context)
+
+    return
+
+def clearLatticeOps(obj, context):
+
+    scn=context.scene
+
+    if scn.ezlattice_mode=="EDIT":
+        cleanupLatticeModifier(context)
+
+
+
+    cleanupLatticeObjects(context)
+
+    resetHelperAttrbs(context)
+    return
+
+
+def objectCheck(context):
+
+    if not [bool(o) for  o in context.selected_objects if o.type!="MESH"]:
+        return True
+
+    else:
+        return False
+
+
+class OBJECT_MT_EZLatticeOperator(bpy.types.Menu):
+    bl_label = "Easy Lattice Menu"
+    bl_idname = "LAT_MT_ezlattice"
+
+    def draw(self, context):
+
+        scn=context.scene
+        layout = self.layout
+
+        layout.operator_context = 'INVOKE_REGION_WIN'
+
+        if scn.ezlattice_flag:
+            op="Apply"
+            layout.operator("object.ezlattice_new", text=op)
+
+        else:
+            op="New"
+            layout.operator("object.ezlattice_new", text=op)
+
+
+
+class OBJECT_OT_EZLatticeCall(Operator):
+    """UV Operator description"""
+    bl_idname = "object.ezlatticecall"
+    bl_label = "Easy Lattice"
+    bl_options = {'REGISTER', 'UNDO'}
+
+
+    @classmethod
+    def poll(cls, context):
+        return bool(context.active_object)
+
+    def execute(self, context):
+        return {'FINISHED'}
+
+class OBJECT_OT_EZLatticeOperatorNew(Operator):
+
+    """Tooltip"""
+    bl_idname = "object.ezlattice_new"
+    bl_label = "Easy Lattice Creator"
+    bl_space_type = "VIEW_3D"
+    bl_region_type = "TOOLS"
+
+    operation: StringProperty(options={'HIDDEN'})
+    isnew: BoolProperty(default=False, options={'HIDDEN'})
+    isobjectmode: BoolProperty(default=False, options={'HIDDEN'})
+
+    op_type: EnumProperty( name = "Operation Type", items = OP_TYPES)
+
+    lat_u: IntProperty( name = "Lattice u", default = 3 )
+    lat_w: IntProperty( name = "Lattice w", default = 3 )
+    lat_m: IntProperty( name = "Lattice m", default = 3 )
+
+
+    lat_type: EnumProperty( name = "Lattice Type", items = LAT_TYPES)
+
+    @classmethod
+    def poll(cls, context):
+        return (context.mode == 'EDIT_MESH') or (context.mode == 'EDIT_LATTICE') or (context.object.type=='LATTICE')
+
+    def execute(self, context):
+
+        cur_obj=context.active_object
+        objs=context.selected_objects
+        scn=context.scene
+
+        if self.isnew and isEditMode():
+
+            self.isobjectmode=False
+            scn.ezlattice_objects=""
+            scn.ezlattice_mode="EDIT"
+
+            newLatticeOp(cur_obj,context,self)
+            self.isnew=False
+
+        else:
+            newLatticeOp(cur_obj,context,self)
+
+        return {'FINISHED'}
+
+    def invoke( self, context, event ):
+        wm = context.window_manager
+
+        cur_obj=context.active_object
+
+        objs=context.selected_objects
+
+        scn=context.scene
+
+        if isEditMode() and cur_obj.type=='MESH':
+
+            self.isnew=True
+            if not scn.ezlattice_flag:
+                return wm.invoke_props_dialog( self )
+
+        else:
+            newLatticeOp(cur_obj,context,self)
+
+        return {'FINISHED'}
+
+
+def menu_draw(self, context):
+    self.layout.separator()
+    self.layout.operator_context = 'INVOKE_REGION_WIN'
+
+    self.layout.menu(OBJECT_MT_EZLatticeOperator.bl_idname)
+
+
+def draw_item(self, context):
+    layout = self.layout
+    layout.menu(OBJECT_MT_EZLatticeOperator.bl_idname)
+
+classes = (
+    OBJECT_OT_EZLatticeOperatorNew,
+    OBJECT_MT_EZLatticeOperator,
+)
+
+
+@persistent
+def resetProps(dummy):
+
+    context=bpy.context
+
+    resetHelperAttrbs(context)
+
+    return
+
+def register():
+
+    defineSceneProps()
+
+    bpy.app.handlers.load_post.append(resetProps)
+
+    for cls in classes:
+        bpy.utils.register_class(cls)
+
+    bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_draw)
+
+    bpy.types.VIEW3D_MT_edit_lattice.prepend(menu_draw)
+    bpy.types.VIEW3D_MT_edit_lattice_context_menu.prepend(menu_draw)
+
+
+def unregister():
+
+
+    for cls in classes:
+        bpy.utils.unregister_class(cls)
+
+    bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_draw)
+
+    bpy.types.VIEW3D_MT_edit_lattice.remove(menu_draw)
+    bpy.types.VIEW3D_MT_edit_lattice_context_menu.remove(menu_draw)
+
+if __name__ == "__main__":
+    register()