diff --git a/add_camera_rigs/__init__.py b/add_camera_rigs/__init__.py
index 878d455528bf2bd9427fd976299d53ba28df67fb..cbf83d95ec4fcf2a1998da00604c9a60b2a5f888 100644
--- a/add_camera_rigs/__init__.py
+++ b/add_camera_rigs/__init__.py
@@ -3,8 +3,8 @@
 bl_info = {
     "name": "Add Camera Rigs",
     "author": "Wayne Dixon, Brian Raschko, Kris Wittig, Damien Picard, Flavio Perez",
-    "version": (1, 4, 4),
-    "blender": (2, 80, 0),
+    "version": (1, 5, 0),
+    "blender": (3, 3, 0),
     "location": "View3D > Add > Camera > Dolly or Crane Rig",
     "description": "Adds a Camera Rig with UI",
     "doc_url": "{BLENDER_MANUAL_URL}/addons/camera/camera_rigs.html",
diff --git a/add_camera_rigs/operators.py b/add_camera_rigs/operators.py
index 18300e0855057f47f72d30b6a18173ecc1a00b01..29e77e3bce15aa174bd377a23fe909f27f49a48c 100644
--- a/add_camera_rigs/operators.py
+++ b/add_camera_rigs/operators.py
@@ -73,30 +73,16 @@ class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator, CameraRigMixin):
         return {'FINISHED'}
 
 
-class ADD_CAMERA_RIGS_OT_add_dof_object(Operator, CameraRigMixin):
-    bl_idname = "add_camera_rigs.add_dof_object"
-    bl_label = "Add DOF Object"
-    bl_description = "Create Empty and add as DOF Object"
+class ADD_CAMERA_RIGS_OT_set_dof_bone(Operator, CameraRigMixin):
+    bl_idname = "add_camera_rigs.set_dof_bone"
+    bl_label = "Set DOF Bone"
+    bl_description = "Set the Aim bone as a DOF target"
 
     def execute(self, context):
         rig, cam = get_rig_and_cam(context.active_object)
-        bone = rig.data.bones['Aim_shape_rotation-MCH']
 
-        # Add Empty
-        empty_obj = bpy.data.objects.new("EmptyDOF", None)
-        context.scene.collection.objects.link(empty_obj)
-
-        # Parent to Aim Child bone
-        empty_obj.parent = rig
-        empty_obj.parent_type = "BONE"
-        empty_obj.parent_bone = "Aim_shape_rotation-MCH"
-
-        # Move to bone head
-        empty_obj.location = bone.head
-
-        # Make this new empty the dof_object
-        cam.data.dof.use_dof = True
-        cam.data.dof.focus_object = empty_obj
+        cam.data.dof.focus_object = rig
+        cam.data.dof.focus_subtarget = 'Aim_shape_rotation-MCH'
 
         return {'FINISHED'}
 
@@ -104,7 +90,7 @@ class ADD_CAMERA_RIGS_OT_add_dof_object(Operator, CameraRigMixin):
 classes = (
     ADD_CAMERA_RIGS_OT_set_scene_camera,
     ADD_CAMERA_RIGS_OT_add_marker_bind,
-    ADD_CAMERA_RIGS_OT_add_dof_object,
+    ADD_CAMERA_RIGS_OT_set_dof_bone,
 )
 
 
diff --git a/add_camera_rigs/ui_panels.py b/add_camera_rigs/ui_panels.py
index c6066147ad69457db0abc062ba3a0ad251a54fc2..5d545d3ad0563242ff2681d6ff1b190c68620807 100644
--- a/add_camera_rigs/ui_panels.py
+++ b/add_camera_rigs/ui_panels.py
@@ -32,20 +32,23 @@ class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin):
         layout.prop(cam_data, "type")
 
         # DoF
-        col = layout.column(align=True)
+        col = layout.column(align=False)
         col.prop(cam_data.dof, "use_dof")
         if cam_data.dof.use_dof:
-            if rig["rig_id"].lower() in ("crane_rig", "dolly_rig"):
-                if cam_data.dof.focus_object is None:
-                    col.operator("add_camera_rigs.add_dof_object",
-                                 text="Add DOF Empty", icon="OUTLINER_OB_EMPTY")
-            else:
-                col.prop(cam_data.dof, "focus_object")
-            row = col.row(align=True)
+            sub = col.column(align=True)
+            if cam_data.dof.focus_object is None:
+                sub.operator("add_camera_rigs.set_dof_bone")
+            sub.prop(cam_data.dof, "focus_object")
+            if (cam_data.dof.focus_object is not None
+                    and cam_data.dof.focus_object.type == 'ARMATURE'):
+                sub.prop_search(cam_data.dof, "focus_subtarget",
+                                cam_data.dof.focus_object.data, "bones")
+            sub = col.column(align=True)
+            row = sub.row(align=True)
             row.active = cam_data.dof.focus_object is None
             row.prop(pose_bones["Camera"],
                      '["focus_distance"]', text="Focus Distance")
-            col.prop(pose_bones["Camera"],
+            sub.prop(pose_bones["Camera"],
                      '["aperture_fstop"]', text="F-Stop")
 
         # Viewport display
@@ -74,9 +77,15 @@ class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin):
         if rig["rig_id"].lower() in ("dolly_rig", "crane_rig"):
             # Track to Constraint
             col = layout.column(align=True)
-            col.label(text="Tracking:")
-            col.prop(pose_bones["Camera"].constraints["Track To"],
-                     'influence', text="Aim Lock", slider=True)
+            track_to_constraint = None
+            for con in pose_bones["Camera"].constraints:
+                if con.type == 'TRACK_TO':
+                    track_to_constraint = con
+                    break
+            if track_to_constraint is not None:
+                col.label(text="Tracking:")
+                col.prop(track_to_constraint, 'influence',
+                         text="Aim Lock", slider=True)
 
             # Crane arm stuff
             if rig["rig_id"].lower() == "crane_rig":
diff --git a/add_mesh_extra_objects/Blocks.py b/add_mesh_extra_objects/Blocks.py
index 226766708c9a5d863941cd6a4a458706816ddad3..c3dcd0d56085802a31208ddfbe70877d1533e493 100644
--- a/add_mesh_extra_objects/Blocks.py
+++ b/add_mesh_extra_objects/Blocks.py
@@ -269,7 +269,7 @@ def MakeABlock(bounds, segsize, vll=0, Offsets=None, FaceExclude=[],
     bounds: a list of boundary positions:
         0:left, 1:right, 2:bottom, 3:top, 4:back, 5:front
     segsize: the maximum size before lengthwise subdivision occurs
-    vll: the number of vertexes already in the mesh. len(mesh.verts) should
+    vll: the number of vertices already in the mesh. len(mesh.verts) should
             give this number.
     Offsets: list of coordinate delta values.
         Offsets are lists, [x,y,z] in
@@ -365,7 +365,7 @@ def MakeAKeystone(xpos, width, zpos, ztop, zbtm, thick, bevel, vll=0, FaceExclud
     zbtm: distance from zpos to the bottom
     thick: thickness
     bevel: the amount to raise the back vertex to account for arch beveling
-    vll: the number of vertexes already in the mesh. len(mesh.verts) should give this number
+    vll: the number of vertices already in the mesh. len(mesh.verts) should give this number
     faceExclude: list of faces to exclude from the faces list.
                  0:left, 1:right, 2:bottom, 3:top, 4:back, 5:front
     xBevScl: how much to divide the end (+- x axis) bevel dimensions.
@@ -779,7 +779,7 @@ class rowOb:
 
 def arch(ra, rt, x, z, archStart, archEnd, bevel, bevAngle, vll):
     __doc__ = """\
-    Makes a list of faces and vertexes for arches.
+    Makes a list of faces and vertices for arches.
     ra: the radius of the arch, to the center of the bricks
     rt: the thickness of the arch
     x: x center location of the circular arc, as if the arch opening were centered on x = 0
diff --git a/amaranth/__init__.py b/amaranth/__init__.py
index 06a42c8eca0703360e622dacc603ef29ae07d931..55871cc16ef622f1180485cb9dd5f279a6b3054e 100644
--- a/amaranth/__init__.py
+++ b/amaranth/__init__.py
@@ -74,7 +74,7 @@ from amaranth.misc import (
 bl_info = {
     "name": "Amaranth Toolset",
     "author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin, Lukas Tönne, Cesar Saez, CansecoGPC",
-    "version": (1, 0, 10),
+    "version": (1, 0, 14),
     "blender": (3, 2, 0),
     "location": "Everywhere!",
     "description": "A collection of tools and settings to improve productivity",
diff --git a/amaranth/scene/current_blend.py b/amaranth/scene/current_blend.py
index 7ab855c40d9632d99689ce7672f5336108043465..11ac36798b30af32e37343a36994d1180bdd4f90 100644
--- a/amaranth/scene/current_blend.py
+++ b/amaranth/scene/current_blend.py
@@ -36,7 +36,7 @@ class FILEBROWSER_PT_amaranth(bpy.types.Panel):
 
     @classmethod
     def poll(cls, context):
-        return panel_poll_is_upper_region(context.region)
+        return context.area.ui_type == 'FILES' and panel_poll_is_upper_region(context.region)
 
     def draw(self, context):
       layout = self.layout
diff --git a/amaranth/scene/debug.py b/amaranth/scene/debug.py
index 38692743cf590714224b358aebd48b4b473aeb07..4f702d08f8a75139aa47714002852e863fcb27df 100755
--- a/amaranth/scene/debug.py
+++ b/amaranth/scene/debug.py
@@ -65,7 +65,8 @@ class AMTH_store_data():
         'TEXTURE': [],             # Textures (Psys, Brushes)
         'MODIFIER': [],            # Modifiers
         'MESH_DATA': [],           # Vertex Colors
-        'VIEW3D': [],              # Background Images
+        'OUTLINER_OB_CAMERA': [],  # Background Images in Cameras
+        'OUTLINER_OB_EMPTY': [],   # Empty type Image
         'NODETREE': [],            # Compositor
         }
     libraries = []                 # Libraries x type
@@ -632,6 +633,7 @@ class AMTH_SCENE_OT_list_users_for_x(Operator):
 
                                     if name not in AMTH_store_data.users['MATERIAL']:
                                         AMTH_store_data.users['MATERIAL'].append(name)
+
             # Check Lights
             for la in d.lights:
                 # Cycles
@@ -643,6 +645,7 @@ class AMTH_SCENE_OT_list_users_for_x(Operator):
                                    no.image and no.image.name == x:
                                 if la.name not in AMTH_store_data.users['LIGHT']:
                                     AMTH_store_data.users['LIGHT'].append(la.name)
+
             # Check World
             for wo in d.worlds:
                 # Cycles
@@ -654,6 +657,7 @@ class AMTH_SCENE_OT_list_users_for_x(Operator):
                                    no.image and no.image.name == x:
                                 if wo.name not in AMTH_store_data.users['WORLD']:
                                     AMTH_store_data.users['WORLD'].append(wo.name)
+
             # Check Textures
             for te in d.textures:
                 if te and te.type == 'IMAGE' and te.image:
@@ -662,6 +666,7 @@ class AMTH_SCENE_OT_list_users_for_x(Operator):
                     if name == x and \
                             name not in AMTH_store_data.users['TEXTURE']:
                         AMTH_store_data.users['TEXTURE'].append(te.name)
+
             # Check Modifiers in Objects
             for ob in d.objects:
                 for mo in ob.modifiers:
@@ -672,21 +677,31 @@ class AMTH_SCENE_OT_list_users_for_x(Operator):
                             name = '"{0}" modifier in {1}'.format(mo.name, ob.name)
                             if name not in AMTH_store_data.users['MODIFIER']:
                                 AMTH_store_data.users['MODIFIER'].append(name)
-            # Check Background Images in Viewports
-            for scr in d.screens:
-                for ar in scr.areas:
-                    if ar.type == 'VIEW_3D':
-                        if ar.spaces and \
-                               ar.spaces.active and \
-                               ar.spaces.active.background_images:
-                            for bg in ar.spaces.active.background_images:
-                                image = bg.image
-
-                                if bg and image and image.name == x:
-                                    name = 'Background for 3D Viewport in Screen "{0}"'\
-                                            .format(scr.name)
-                                    if name not in AMTH_store_data.users['VIEW3D']:
-                                        AMTH_store_data.users['VIEW3D'].append(name)
+
+            # Check Background Images in Cameras
+            for ob in d.objects:
+                if ob and ob.type == 'CAMERA' and ob.data.background_images:
+                    for bg in ob.data.background_images:
+                        image = bg.image
+
+                        if bg and image and image.name == x:
+                            name = 'Used as background for Camera "{0}"'\
+                                    .format(ob.name)
+                            if name not in AMTH_store_data.users['OUTLINER_OB_CAMERA']:
+                                AMTH_store_data.users['OUTLINER_OB_CAMERA'].append(name)
+
+            # Check Empties type Image
+            for ob in d.objects:
+                if ob and ob.type == 'EMPTY' and ob.image_user:
+                    if ob.image_user.id_data.data:
+                        image = ob.image_user.id_data.data
+
+                        if image and image.name == x:
+                            name = 'Used in Empty "{0}"'\
+                                    .format(ob.name)
+                            if name not in AMTH_store_data.users['OUTLINER_OB_EMPTY']:
+                                AMTH_store_data.users['OUTLINER_OB_EMPTY'].append(name)
+
             # Check the Compositor
             for sce in d.scenes:
                 if sce.node_tree and sce.node_tree.nodes:
diff --git a/amaranth/scene/goto_library.py b/amaranth/scene/goto_library.py
index b1ea9e5b353f595d24c568c4e6711935553d5198..d38a62d3cb4181a6cbbf28313df2d64e5622fe6c 100644
--- a/amaranth/scene/goto_library.py
+++ b/amaranth/scene/goto_library.py
@@ -19,6 +19,10 @@ class AMTH_FILE_PT_libraries(bpy.types.Panel):
     bl_category = "Bookmarks"
     bl_label = "Libraries"
 
+    @classmethod
+    def poll(cls, context):
+        return context.area.ui_type == 'FILES'
+
     def draw(self, context):
         layout = self.layout
 
diff --git a/amaranth/scene/save_reload.py b/amaranth/scene/save_reload.py
index f3bedb5e17162796ff63d6772606686a990cf722..ece61246a15ec9de246f67c369794a7a7cfb4497 100644
--- a/amaranth/scene/save_reload.py
+++ b/amaranth/scene/save_reload.py
@@ -13,6 +13,23 @@ import bpy
 
 KEYMAPS = list()
 
+def check_for_unsaved_images(self):
+    im_unsaved = []
+
+    for im in bpy.data.images:
+        if im.is_dirty:
+            im_unsaved.append(im.name)
+
+    if im_unsaved:
+        report_text = 'There are unsaved changes in {0} image(s), check console for details.'\
+                        .format(len(im_unsaved))
+        self.report({"WARNING"}, report_text)
+
+        print("\nAmaranth found unsaved images when trying to save and reload.")
+        for im in im_unsaved:
+            print('* Image: "' + im + '" has unsaved changes.')
+        return True
+    return
 
 class AMTH_WM_OT_save_reload(bpy.types.Operator):
     """Save and Reload the current blend file"""
@@ -23,6 +40,10 @@ class AMTH_WM_OT_save_reload(bpy.types.Operator):
         if not path:
             bpy.ops.wm.save_as_mainfile("INVOKE_AREA")
             return
+
+        if check_for_unsaved_images(self):
+            return
+
         bpy.ops.wm.save_mainfile()
         self.report({"INFO"}, "Saved & Reloaded")
         bpy.ops.wm.open_mainfile("EXEC_DEFAULT", filepath=path)
diff --git a/animation_animall.py b/animation_animall/__init__.py
similarity index 66%
rename from animation_animall.py
rename to animation_animall/__init__.py
index a7d76f53c379c5c2d1bc6561898dd9d92ca108d5..d679c97011613ad79de771030557faebca5c4b90 100644
--- a/animation_animall.py
+++ b/animation_animall/__init__.py
@@ -2,8 +2,8 @@
 
 bl_info = {
     "name": "AnimAll",
-    "author": "Daniel Salazar <zanqdo@gmail.com>",
-    "version": (0, 9, 1),
+    "author": "Daniel Salazar (ZanQdo), Damien Picard (pioverfour)",
+    "version": (0, 9, 6),
     "blender": (3, 3, 0),
     "location": "3D View > Toolbox > Animation tab > AnimAll",
     "description": "Allows animation of mesh, lattice, curve and surface data",
@@ -12,86 +12,77 @@ bl_info = {
     "category": "Animation",
 }
 
-"""
-Thanks to Campbell Barton and Joshua Leung for hes API additions and fixes
-Daniel 'ZanQdo' Salazar
-"""
-
 import bpy
-from bpy.types import (
-        Operator,
-        Panel,
-        AddonPreferences,
-        )
-from bpy.props import (
-        BoolProperty,
-        StringProperty,
-        )
+from bpy.types import (Operator, Panel, AddonPreferences)
+from bpy.props import (BoolProperty, StringProperty)
 from bpy.app.handlers import persistent
+from bpy.app.translations import (pgettext_iface as iface_,
+                                  pgettext_data as data_)
+from . import translations
 
 
 # Property Definitions
 class AnimallProperties(bpy.types.PropertyGroup):
     key_selected: BoolProperty(
-        name="Selected Only",
+        name="Key Selected Only",
         description="Insert keyframes only on selected elements",
-        default=True
-    )
-    key_shape: BoolProperty(
-        name="Shape",
+        default=False)
+
+    # Generic attributes
+    key_point_location: BoolProperty(
+        name="Location",
+        description="Insert keyframes on point locations",
+        default=False)
+    key_shape_key: BoolProperty(
+        name="Shape Key",
         description="Insert keyframes on active Shape Key layer",
-        default=False
-    )
-    key_uvs: BoolProperty(
-        name="UVs",
-        description="Insert keyframes on active UV coordinates",
-        default=False
-    )
-    key_ebevel: BoolProperty(
-        name="E-Bevel",
-        description="Insert keyframes on edge bevel weight",
-        default=False
-    )
-    key_vbevel: BoolProperty(
-        name="V-Bevel",
+        default=False)
+    key_material_index: BoolProperty(
+        name="Material Index",
+        description="Insert keyframes on face material indices",
+        default=False)
+
+    # Mesh attributes
+    key_vertex_bevel: BoolProperty(
+        name="Vertex Bevel",
         description="Insert keyframes on vertex bevel weight",
-        default=False
-    )
-    key_crease: BoolProperty(
-        name="Crease",
+        default=False)
+    # key_vertex_crease: BoolProperty(
+    #     name="Vertex Crease",
+    #     description="Insert keyframes on vertex crease weight",
+    #     default=False)
+    key_vertex_group: BoolProperty(
+        name="Vertex Group",
+        description="Insert keyframes on active vertex group values",
+        default=False)
+
+    key_edge_bevel: BoolProperty(
+        name="Edge Bevel",
+        description="Insert keyframes on edge bevel weight",
+        default=False)
+    key_edge_crease: BoolProperty(
+        name="Edge Crease",
         description="Insert keyframes on edge creases",
-        default=False
-    )
-    key_vgroups: BoolProperty(
-        name="V-groups",
-        description="Insert keyframes on active Vertex group values",
-        default=False
-    )
+        default=False)
+
     key_attribute: BoolProperty(
-        name="Active Attribute",
+        name="Attribute",
         description="Insert keyframes on active attribute values",
-        default=False
-    )
-    key_points: BoolProperty(
-        name="Points",
-        description="Insert keyframes on point locations",
-        default=False
-    )
-    key_handle_type: BoolProperty(
-        name="Handle Types",
-        description="Insert keyframes on Bezier point types",
-        default=False
-    )
+        default=False)
+    key_uvs: BoolProperty(
+        name="UV Map",
+        description="Insert keyframes on active UV coordinates",
+        default=False)
+
+    # Curve and surface attributes
     key_radius: BoolProperty(
         name="Radius",
         description="Insert keyframes on point radius (Shrink/Fatten)",
-        default=False
-    )
+        default=False)
     key_tilt: BoolProperty(
         name="Tilt",
         description="Insert keyframes on point tilt",
-        default=False
-    )
+        default=False)
 
 
 # Utility functions
@@ -134,111 +125,118 @@ class VIEW3D_PT_animall(Panel):
     bl_space_type = 'VIEW_3D'
     bl_region_type = 'UI'
     bl_category = "Animate"
-    bl_label = 'AnimAll'
-    bl_options = {'DEFAULT_CLOSED'}
+    bl_label = ''
 
     @classmethod
     def poll(self, context):
         return context.active_object and context.active_object.type in {'MESH', 'LATTICE', 'CURVE', 'SURFACE'}
 
+    def draw_header(self, context):
+
+        layout = self.layout
+        row = layout.row()
+        row.label (text = 'AnimAll', icon = 'ARMATURE_DATA')
+
     def draw(self, context):
         obj = context.active_object
-        animall_properties = context.window_manager.animall_properties
+        animall_properties = context.scene.animall_properties
 
         layout = self.layout
-        col = layout.column(align=True)
-        row = col.row()
-        row.prop(animall_properties, "key_selected")
-        col.separator()
 
-        row = col.row()
+        layout.label(text='Key:')
+
+        layout.use_property_split = True
+        layout.use_property_decorate = False
 
         if obj.type == 'LATTICE':
-            row.prop(animall_properties, "key_points")
-            row.prop(animall_properties, "key_shape")
+            col = layout.column(heading="Points", align=True)
+            col.prop(animall_properties, "key_point_location")
 
-        elif obj.type == 'MESH':
-            row.prop(animall_properties, "key_points")
-            row.prop(animall_properties, "key_shape")
-            row = col.row()
-            row.prop(animall_properties, "key_ebevel")
-            row.prop(animall_properties, "key_vbevel")
-            row = col.row()
-            row.prop(animall_properties, "key_crease")
-            row.prop(animall_properties, "key_uvs")
-            row = col.row()
-            row.prop(animall_properties, "key_attribute")
-            row.prop(animall_properties, "key_vgroups")
-
-        # Vertex group update operator
-        if (context.active_object is not None
-                and context.active_object.type == 'MESH'
-                and context.active_object.data.animation_data is not None
-                and context.active_object.data.animation_data.action is not None):
-            for fcurve in context.active_object.data.animation_data.action.fcurves:
-                if fcurve.data_path.startswith("vertex_colors"):
-                    layout.separator()
-                    row = layout.row()
-                    row.label(text="Object includes old-style vertex colors. Consider updating them.", icon="ERROR")
-                    row = layout.row()
-                    row.operator("anim.update_vertex_color_animation_animall", icon="FILE_REFRESH")
-                    break
-
-        elif obj.type == 'CURVE':
-            row.prop(animall_properties, "key_points")
-            row.prop(animall_properties, "key_shape")
-            row = col.row()
-            row.prop(animall_properties, "key_radius")
-            row.prop(animall_properties, "key_tilt")
-            row = col.row()
-            row.prop(animall_properties, "key_handle_type")
-
-        elif obj.type == 'SURFACE':
-            row.prop(animall_properties, "key_points")
-            row.prop(animall_properties, "key_shape")
-            row = col.row()
-            row.prop(animall_properties, "key_radius")
-            row.prop(animall_properties, "key_tilt")
+            col = layout.column(heading="Others", align=True)
+            col.prop(animall_properties, "key_shape_key")
 
-        layout.separator()
-        row = layout.row(align=True)
-        row.operator("anim.insert_keyframe_animall", icon="KEY_HLT")
-        row.operator("anim.delete_keyframe_animall", icon="KEY_DEHLT")
-        row = layout.row()
-        row.operator("anim.clear_animation_animall", icon="X")
-
-        if animall_properties.key_shape:
+        elif obj.type == 'MESH':
+            col = layout.column(heading="Points", align=True)
+            col.prop(animall_properties, "key_point_location")
+            col.prop(animall_properties, "key_vertex_bevel", text="Bevel")
+            col.prop(animall_properties, "key_vertex_group")
+
+            col = layout.column(heading="Edges", align=True)
+            col.prop(animall_properties, "key_edge_bevel", text="Bevel")
+            col.prop(animall_properties, "key_edge_crease", text="Crease")
+
+            col = layout.column(heading="Faces", align=True)
+            col.prop(animall_properties, "key_material_index")
+
+            col = layout.column(heading="Others", align=True)
+            col.prop(animall_properties, "key_attribute")
+            col.prop(animall_properties, "key_uvs")
+            col.prop(animall_properties, "key_shape_key")
+
+            # Vertex group update operator
+            if (obj.data.animation_data is not None
+                    and obj.data.animation_data.action is not None):
+                for fcurve in context.active_object.data.animation_data.action.fcurves:
+                    if fcurve.data_path.startswith("vertex_colors"):
+                        col = layout.column(align=True)
+                        col.label(text="Object includes old-style vertex colors. Consider updating them.", icon="ERROR")
+                        col.operator("anim.update_vertex_color_animation_animall", icon="FILE_REFRESH")
+                        break
+
+        elif obj.type in {'CURVE', 'SURFACE'}:
+            col = layout.column(align=True)
+            col = layout.column(heading="Points", align=True)
+            col.prop(animall_properties, "key_point_location")
+            col.prop(animall_properties, "key_radius")
+            col.prop(animall_properties, "key_tilt")
+
+            col = layout.column(heading="Splines", align=True)
+            col.prop(animall_properties, "key_material_index")
+
+            col = layout.column(heading="Others", align=True)
+            col.prop(animall_properties, "key_shape_key")
+
+        if animall_properties.key_shape_key:
             shape_key = obj.active_shape_key
             shape_key_index = obj.active_shape_key_index
 
-            split = layout.split()
-            row = split.row()
-
             if shape_key_index > 0:
-                row.label(text=shape_key.name, icon="SHAPEKEY_DATA")
-                row.prop(shape_key, "value", text="")
+                col = layout.column(align=True)
+                row = col.row(align=True)
+                row.prop(shape_key, "value", text=shape_key.name, icon="SHAPEKEY_DATA")
                 row.prop(obj, "show_only_shape_key", text="")
                 if shape_key.value < 1:
-                    row = layout.row()
-                    row.label(text='Maybe set "%s" to 1.0?' % shape_key.name, icon="INFO")
-            elif shape_key:
-                row.label(text="Cannot key on Basis Shape", icon="ERROR")
+                    col.label(text=iface_('Maybe set "%s" to 1.0?') % shape_key.name, icon="INFO")
+            elif shape_key is not None:
+                col = layout.column(align=True)
+                col.label(text="Cannot key on Basis Shape", icon="ERROR")
             else:
-                row.label(text="No active Shape Key", icon="ERROR")
+                col = layout.column(align=True)
+                col.label(text="No active Shape Key", icon="ERROR")
+
+            if animall_properties.key_point_location:
+                col.label(text='"Location" and "Shape Key" are redundant?', icon="INFO")
+
+        layout.use_property_split = False
+        layout.separator()
+        row = layout.row()
+        row.prop(animall_properties, "key_selected")
 
-        if animall_properties.key_points and animall_properties.key_shape:
-            row = layout.row()
-            row.label(text='"Points" and "Shape" are redundant?', icon="INFO")
+        row = layout.row(align=True)
+        row.operator("anim.insert_keyframe_animall", icon="KEY_HLT")
+        row.operator("anim.delete_keyframe_animall", icon="KEY_DEHLT")
+        row = layout.row()
+        row.operator("anim.clear_animation_animall", icon="CANCEL")
 
 
 class ANIM_OT_insert_keyframe_animall(Operator):
-    bl_label = "Insert"
+    bl_label = "Insert Key"
     bl_idname = "anim.insert_keyframe_animall"
     bl_description = "Insert a Keyframe"
     bl_options = {'REGISTER', 'UNDO'}
 
-    def execute(op, context):
-        animall_properties = context.window_manager.animall_properties
+    def execute(self, context):
+        animall_properties = context.scene.animall_properties
 
         if context.mode == 'OBJECT':
             objects = context.selected_objects
@@ -254,19 +252,26 @@ class ANIM_OT_insert_keyframe_animall(Operator):
             data = obj.data
 
             if obj.type == 'LATTICE':
-                if animall_properties.key_shape:
+                if animall_properties.key_shape_key:
                     if obj.active_shape_key_index > 0:
                         sk_name = obj.active_shape_key.name
                         for p_i, point in enumerate(obj.active_shape_key.data):
                             if not animall_properties.key_selected or data.points[p_i].select:
-                                insert_key(point, 'co', group="%s Point %s" % (sk_name, p_i))
+                                insert_key(point, 'co', group=data_("%s Point %s") % (sk_name, p_i))
 
-                if animall_properties.key_points:
+                if animall_properties.key_point_location:
                     for p_i, point in enumerate(data.points):
                         if not animall_properties.key_selected or point.select:
-                            insert_key(point, 'co_deform', group="Point %s" % p_i)
+                            insert_key(point, 'co_deform', group=data_("Point %s") % p_i)
 
             else:
+                if animall_properties.key_material_index:
+                    for s_i, spline in enumerate(data.splines):
+                        if (not animall_properties.key_selected
+                                or any(point.select for point in spline.points)
+                                or any(point.select_control_point for point in spline.bezier_points)):
+                            insert_key(spline, 'material_index', group=data_("Spline %s") % s_i)
+
                 for s_i, spline in enumerate(data.splines):
                     if spline.type == 'BEZIER':
                         for v_i, CV in enumerate(spline.bezier_points):
@@ -274,76 +279,68 @@ class ANIM_OT_insert_keyframe_animall(Operator):
                                     or CV.select_control_point
                                     or CV.select_left_handle
                                     or CV.select_right_handle):
-                                if animall_properties.key_points:
-                                    insert_key(CV, 'co', group="Spline %s CV %s" % (s_i, v_i))
-                                    insert_key(CV, 'handle_left', group="Spline %s CV %s" % (s_i, v_i))
-                                    insert_key(CV, 'handle_right', group="Spline %s CV %s" % (s_i, v_i))
+                                if animall_properties.key_point_location:
+                                    insert_key(CV, 'co', group=data_("Spline %s CV %s") % (s_i, v_i))
+                                    insert_key(CV, 'handle_left', group=data_("Spline %s CV %s") % (s_i, v_i))
+                                    insert_key(CV, 'handle_right', group=data_("Spline %s CV %s") % (s_i, v_i))
 
                                 if animall_properties.key_radius:
-                                    insert_key(CV, 'radius', group="Spline %s CV %s" % (s_i, v_i))
-
-                                if animall_properties.key_handle_type:
-                                    insert_key(CV, 'handle_left_type', group="spline %s CV %s" % (s_i, v_i))
-                                    insert_key(CV, 'handle_right_type', group="spline %s CV %s" % (s_i, v_i))
+                                    insert_key(CV, 'radius', group=data_("Spline %s CV %s") % (s_i, v_i))
 
                                 if animall_properties.key_tilt:
-                                    insert_key(CV, 'tilt', group="Spline %s CV %s" % (s_i, v_i))
+                                    insert_key(CV, 'tilt', group=data_("Spline %s CV %s") % (s_i, v_i))
 
                     elif spline.type in ('POLY', 'NURBS'):
                         for v_i, CV in enumerate(spline.points):
                             if not animall_properties.key_selected or CV.select:
-                                if animall_properties.key_points:
-                                    insert_key(CV, 'co', group="Spline %s CV %s" % (s_i, v_i))
+                                if animall_properties.key_point_location:
+                                    insert_key(CV, 'co', group=data_("Spline %s CV %s") % (s_i, v_i))
 
                                 if animall_properties.key_radius:
-                                    insert_key(CV, 'radius', group="Spline %s CV %s" % (s_i, v_i))
+                                    insert_key(CV, 'radius', group=data_("Spline %s CV %s") % (s_i, v_i))
 
                                 if animall_properties.key_tilt:
-                                    insert_key(CV, 'tilt', group="Spline %s CV %s" % (s_i, v_i))
+                                    insert_key(CV, 'tilt', group=data_("Spline %s CV %s") % (s_i, v_i))
 
         bpy.ops.object.mode_set(mode='OBJECT')
 
         for obj in [o for o in objects if o.type in {'MESH', 'CURVE', 'SURFACE'}]:
             data = obj.data
             if obj.type == 'MESH':
-                if animall_properties.key_points:
+                if animall_properties.key_point_location:
                     for v_i, vert in enumerate(data.vertices):
                         if not animall_properties.key_selected or vert.select:
-                            insert_key(vert, 'co', group="Vertex %s" % v_i)
+                            insert_key(vert, 'co', group=data_("Vertex %s") % v_i)
 
-                if animall_properties.key_vbevel:
+                if animall_properties.key_vertex_bevel:
                     for v_i, vert in enumerate(data.vertices):
                         if not animall_properties.key_selected or vert.select:
-                            insert_key(vert, 'bevel_weight', group="Vertex %s" % v_i)
+                            insert_key(vert, 'bevel_weight', group=data_("Vertex %s") % v_i)
+                # if animall_properties.key_vertex_crease:
+                #     for v_i, vert in enumerate(data.vertices):
+                #         if not animall_properties.key_selected or vert.select:
+                #             insert_key(vert, 'crease', group=data_("Vertex %s") % v_i)
 
-                if animall_properties.key_vgroups:
+                if animall_properties.key_vertex_group:
                     for v_i, vert in enumerate(data.vertices):
                         if not animall_properties.key_selected or vert.select:
                             for group in vert.groups:
-                                insert_key(group, 'weight', group="Vertex %s" % v_i)
+                                insert_key(group, 'weight', group=data_("Vertex %s") % v_i)
 
-                if animall_properties.key_ebevel:
+                if animall_properties.key_edge_bevel:
                     for e_i, edge in enumerate(data.edges):
                         if not animall_properties.key_selected or edge.select:
-                            insert_key(edge, 'bevel_weight', group="Edge %s" % e_i)
+                            insert_key(edge, 'bevel_weight', group=data_("Edge %s") % e_i)
 
-                if animall_properties.key_crease:
+                if animall_properties.key_edge_crease:
                     for e_i, edge in enumerate(data.edges):
                         if not animall_properties.key_selected or edge.select:
-                            insert_key(edge, 'crease', group="Edge %s" % e_i)
+                            insert_key(edge, 'crease', group=data_("Edge %s") % e_i)
 
-                if animall_properties.key_shape:
-                    if obj.active_shape_key_index > 0:
-                        sk_name = obj.active_shape_key.name
-                        for v_i, vert in enumerate(obj.active_shape_key.data):
-                            if not animall_properties.key_selected or data.vertices[v_i].select:
-                                insert_key(vert, 'co', group="%s Vertex %s" % (sk_name, v_i))
-
-                if animall_properties.key_uvs:
-                    if data.uv_layers.active is not None:
-                        for uv_i, uv in enumerate(data.uv_layers.active.data):
-                            if not animall_properties.key_selected or uv.select:
-                                insert_key(uv, 'uv', group="UV layer %s" % uv_i)
+                if animall_properties.key_material_index:
+                    for p_i, polygon in enumerate(data.polygons):
+                        if not animall_properties.key_selected or polygon.select:
+                            insert_key(polygon, 'material_index', group=data_("Face %s") % p_i)
 
                 if animall_properties.key_attribute:
                     if data.attributes.active is not None:
@@ -358,13 +355,13 @@ class ANIM_OT_insert_keyframe_animall(Operator):
                                 attribute_key = "vector"
 
                             if attribute.domain == 'POINT':
-                                group = "Vertex %s"
+                                group = data_("Vertex %s")
                             elif attribute.domain == 'EDGE':
-                                group = "Edge %s"
+                                group = data_("Edge %s")
                             elif attribute.domain == 'FACE':
-                                group = "Face %s"
+                                group = data_("Face %s")
                             elif attribute.domain == 'CORNER':
-                                group = "Loop %s"
+                                group = data_("Loop %s")
 
                             for e_i, _attribute_data in enumerate(attribute.data):
                                 if (not animall_properties.key_selected
@@ -375,9 +372,22 @@ class ANIM_OT_insert_keyframe_animall(Operator):
                                     insert_key(data, f'attributes["{attribute.name}"].data[{e_i}].{attribute_key}',
                                             group=group % e_i)
 
+                if animall_properties.key_uvs:
+                    if data.uv_layers.active is not None:
+                        for uv_i, uv in enumerate(data.uv_layers.active.data):
+                            if not animall_properties.key_selected or uv.select:
+                                insert_key(uv, 'uv', group=data_("UV layer %s") % uv_i)
+
+                if animall_properties.key_shape_key:
+                    if obj.active_shape_key_index > 0:
+                        sk_name = obj.active_shape_key.name
+                        for v_i, vert in enumerate(obj.active_shape_key.data):
+                            if not animall_properties.key_selected or data.vertices[v_i].select:
+                                insert_key(vert, 'co', group=data_("%s Vertex %s") % (sk_name, v_i))
+
             elif obj.type in {'CURVE', 'SURFACE'}:
                 # Shape key keys have to be inserted in object mode for curves...
-                if animall_properties.key_shape:
+                if animall_properties.key_shape_key:
                     sk_name = obj.active_shape_key.name
                     global_spline_index = 0  # numbering for shape keys, which have flattened indices
                     for s_i, spline in enumerate(data.splines):
@@ -389,11 +399,11 @@ class ANIM_OT_insert_keyframe_animall(Operator):
                                         or CV.select_right_handle):
                                     if obj.active_shape_key_index > 0:
                                         CV = obj.active_shape_key.data[global_spline_index]
-                                        insert_key(CV, 'co', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
-                                        insert_key(CV, 'handle_left', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
-                                        insert_key(CV, 'handle_right', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
-                                        insert_key(CV, 'radius', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
-                                        insert_key(CV, 'tilt', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
+                                        insert_key(CV, 'co', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i))
+                                        insert_key(CV, 'handle_left', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i))
+                                        insert_key(CV, 'handle_right', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i))
+                                        insert_key(CV, 'radius', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i))
+                                        insert_key(CV, 'tilt', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i))
                                 global_spline_index += 1
 
                         elif spline.type in ('POLY', 'NURBS'):
@@ -401,9 +411,9 @@ class ANIM_OT_insert_keyframe_animall(Operator):
                                 if not animall_properties.key_selected or CV.select:
                                     if obj.active_shape_key_index > 0:
                                         CV = obj.active_shape_key.data[global_spline_index]
-                                        insert_key(CV, 'co', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
-                                        insert_key(CV, 'radius', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
-                                        insert_key(CV, 'tilt', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
+                                        insert_key(CV, 'co', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i))
+                                        insert_key(CV, 'radius', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i))
+                                        insert_key(CV, 'tilt', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i))
                                 global_spline_index += 1
 
         bpy.ops.object.mode_set(mode=mode)
@@ -413,14 +423,13 @@ class ANIM_OT_insert_keyframe_animall(Operator):
 
 
 class ANIM_OT_delete_keyframe_animall(Operator):
-    bl_label = "Delete"
+    bl_label = "Delete Key"
     bl_idname = "anim.delete_keyframe_animall"
     bl_description = "Delete a Keyframe"
     bl_options = {'REGISTER', 'UNDO'}
 
-
-    def execute(op, context):
-        animall_properties = context.window_manager.animall_properties
+    def execute(self, context):
+        animall_properties = context.scene.animall_properties
 
         if context.mode == 'OBJECT':
             objects = context.selected_objects
@@ -432,33 +441,38 @@ class ANIM_OT_delete_keyframe_animall(Operator):
         for obj in objects:
             data = obj.data
             if obj.type == 'MESH':
-                if animall_properties.key_points:
+                if animall_properties.key_point_location:
                     for vert in data.vertices:
                         if not animall_properties.key_selected or vert.select:
                             delete_key(vert, 'co')
 
-                if animall_properties.key_vbevel:
+                if animall_properties.key_vertex_bevel:
                     for vert in data.vertices:
                         if not animall_properties.key_selected or vert.select:
                             delete_key(vert, 'bevel_weight')
 
-                if animall_properties.key_vgroups:
+                if animall_properties.key_vertex_group:
                     for vert in data.vertices:
                         if not animall_properties.key_selected or vert.select:
                             for group in vert.groups:
                                 delete_key(group, 'weight')
 
-                if animall_properties.key_ebevel:
+                # if animall_properties.key_vertex_crease:
+                #     for vert in data.vertices:
+                #         if not animall_properties.key_selected or vert.select:
+                #             delete_key(vert, 'crease')
+
+                if animall_properties.key_edge_bevel:
                     for edge in data.edges:
                         if not animall_properties.key_selected or edge.select:
                             delete_key(edge, 'bevel_weight')
 
-                if animall_properties.key_crease:
+                if animall_properties.key_edge_crease:
                     for edge in data.edges:
                         if not animall_properties.key_selected or vert.select:
                             delete_key(edge, 'crease')
 
-                if animall_properties.key_shape:
+                if animall_properties.key_shape_key:
                     if obj.active_shape_key:
                         for v_i, vert in enumerate(obj.active_shape_key.data):
                             if not animall_properties.key_selected or data.vertices[v_i].select:
@@ -491,19 +505,19 @@ class ANIM_OT_delete_keyframe_animall(Operator):
                                     delete_key(data, f'attributes["{attribute.name}"].data[{e_i}].{attribute_key}')
 
             elif obj.type == 'LATTICE':
-                if animall_properties.key_shape:
+                if animall_properties.key_shape_key:
                     if obj.active_shape_key:
                         for point in obj.active_shape_key.data:
                             delete_key(point, 'co')
 
-                if animall_properties.key_points:
+                if animall_properties.key_point_location:
                     for point in data.points:
                         if not animall_properties.key_selected or point.select:
                             delete_key(point, 'co_deform')
 
             elif obj.type in {'CURVE', 'SURFACE'}:
-                # run this outside the splines loop (only once)
-                if animall_properties.key_shape:
+                # Run this outside the splines loop (only once)
+                if animall_properties.key_shape_key:
                     if obj.active_shape_key_index > 0:
                         for CV in obj.active_shape_key.data:
                             delete_key(CV, 'co')
@@ -517,13 +531,10 @@ class ANIM_OT_delete_keyframe_animall(Operator):
                                     or CV.select_control_point
                                     or CV.select_left_handle
                                     or CV.select_right_handle):
-                                if animall_properties.key_points:
+                                if animall_properties.key_point_location:
                                     delete_key(CV, 'co')
                                     delete_key(CV, 'handle_left')
                                     delete_key(CV, 'handle_right')
-                                if animall_properties.key_handle_type:
-                                    delete_key(CV, 'handle_left_type')
-                                    delete_key(CV, 'handle_right_type')
                                 if animall_properties.key_radius:
                                     delete_key(CV, 'radius')
                                 if animall_properties.key_tilt:
@@ -532,7 +543,7 @@ class ANIM_OT_delete_keyframe_animall(Operator):
                     elif spline.type in ('POLY', 'NURBS'):
                         for CV in spline.points:
                             if not animall_properties.key_selected or CV.select:
-                                if animall_properties.key_points:
+                                if animall_properties.key_point_location:
                                     delete_key(CV, 'co')
                                 if animall_properties.key_radius:
                                     delete_key(CV, 'radius')
@@ -642,28 +653,21 @@ class AnimallAddonPreferences(AddonPreferences):
         col.label(text="Tab Category:")
         col.prop(self, "category", text="")
 
+register_classes, unregister_classes = bpy.utils.register_classes_factory(
+    (AnimallProperties, VIEW3D_PT_animall, ANIM_OT_insert_keyframe_animall,
+     ANIM_OT_delete_keyframe_animall, ANIM_OT_clear_animation_animall,
+     ANIM_OT_update_vertex_color_animation_animall, AnimallAddonPreferences))
 
 def register():
-    bpy.utils.register_class(AnimallProperties)
-    bpy.types.WindowManager.animall_properties = bpy.props.PointerProperty(type=AnimallProperties)
-    bpy.utils.register_class(VIEW3D_PT_animall)
-    bpy.utils.register_class(ANIM_OT_insert_keyframe_animall)
-    bpy.utils.register_class(ANIM_OT_delete_keyframe_animall)
-    bpy.utils.register_class(ANIM_OT_clear_animation_animall)
-    bpy.utils.register_class(ANIM_OT_update_vertex_color_animation_animall)
-    bpy.utils.register_class(AnimallAddonPreferences)
+    register_classes()
+    bpy.types.Scene.animall_properties = bpy.props.PointerProperty(type=AnimallProperties)
     update_panel(None, bpy.context)
-
+    bpy.app.translations.register(__name__, translations.translations_dict)
 
 def unregister():
-    del bpy.types.WindowManager.animall_properties
-    bpy.utils.unregister_class(AnimallProperties)
-    bpy.utils.unregister_class(VIEW3D_PT_animall)
-    bpy.utils.unregister_class(ANIM_OT_insert_keyframe_animall)
-    bpy.utils.unregister_class(ANIM_OT_delete_keyframe_animall)
-    bpy.utils.unregister_class(ANIM_OT_clear_animation_animall)
-    bpy.utils.unregister_class(ANIM_OT_update_vertex_color_animation_animall)
-    bpy.utils.unregister_class(AnimallAddonPreferences)
+    bpy.app.translations.unregister(__name__)
+    del bpy.types.Scene.animall_properties
+    unregister_classes()
 
 if __name__ == "__main__":
     register()
diff --git a/animation_animall/translations.py b/animation_animall/translations.py
new file mode 100644
index 0000000000000000000000000000000000000000..03b77e0b03790e44635f5629ccd9e4322c169b82
--- /dev/null
+++ b/animation_animall/translations.py
@@ -0,0 +1,364 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# ##### BEGIN AUTOGENERATED I18N SECTION #####
+# NOTE: You can safely move around this auto-generated block (with the begin/end markers!),
+#       and edit the translations by hand.
+#       Just carefully respect the format of the tuple!
+
+# Tuple of tuples:
+# ((msgctxt, msgid), (sources, gen_comments), (lang, translation, (is_fuzzy, comments)), ...)
+translations_tuple = (
+    (("*", ""),
+     ((), ()),
+     ("fr_FR", "Project-Id-Version: AnimAll 0.9.6 (0)\n",
+               (False,
+                ("Blender's translation file (po format).",
+                 "Copyright (C) 2022 The Blender Foundation.",
+                 "This file is distributed under the same license as the Blender package.",
+                 "Damien Picard <dam.pic@free.fr>, 2022."))),
+    ),
+    (("*", "Tab Category"),
+     (("bpy.types.AnimallAddonPreferences.category",),
+      ()),
+     ("fr_FR", "Catégorie d’onglet",
+               (False, ())),
+    ),
+    (("*", "Choose a name for the category of the panel"),
+     (("bpy.types.AnimallAddonPreferences.category",),
+      ()),
+     ("fr_FR", "Choisir un nom pour la catégorie du panneau",
+               (False, ())),
+    ),
+    (("Operator", "Insert Key"),
+     (("bpy.types.ANIM_OT_insert_keyframe_animall",
+       "bpy.types.ANIM_OT_insert_keyframe_animall"),
+      ()),
+     ("fr_FR", "Insérer une clé",
+               (False, ())),
+    ),
+    (("Operator", "Clear Animation"),
+     (("bpy.types.ANIM_OT_clear_animation_animall",),
+      ()),
+     ("fr_FR", "Effacer l’animation",
+               (False, ())),
+    ),
+    (("*", "Delete all keyframes for this object\nIf in a specific case it doesn't work\ntry to delete the keys manually"),
+     (("bpy.types.ANIM_OT_clear_animation_animall",),
+      ()),
+     ("fr_FR", "Supprimer toutes les images clés pour cet objet.\n"
+               "En cas d’échec, essayez de les supprimer manuellement",
+               (False, ())),
+    ),
+    (("*", "Insert a Keyframe"),
+     (("bpy.types.ANIM_OT_insert_keyframe_animall",),
+      ()),
+     ("fr_FR", "Insérer une image clé",
+               (False, ())),
+    ),
+    (("Operator", "Delete Key"),
+     (("bpy.types.ANIM_OT_delete_keyframe_animall",),
+      ()),
+     ("fr_FR", "Supprimer image clé",
+               (False, ())),
+    ),
+    (("*", "Delete a Keyframe"),
+     (("bpy.types.ANIM_OT_delete_keyframe_animall",),
+      ()),
+     ("fr_FR", "Supprimer une image clé",
+               (False, ())),
+    ),
+    (("*", "Animate"),
+     (("bpy.types.VIEW3D_PT_animall",),
+      ()),
+     ("fr_FR", "Animer",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on active attribute values"),
+     (("bpy.types.AnimallProperties.key_attribute",),
+      ()),
+     ("fr_FR", "Insérer des clés sur l’attribut actif",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on edge bevel weight"),
+     (("bpy.types.AnimallProperties.key_edge_bevel",),
+      ()),
+     ("fr_FR", "Insérer des clés sur les poids de biseau d’arête",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on edge creases"),
+     (("bpy.types.AnimallProperties.key_edge_crease",),
+      ()),
+     ("fr_FR", "Insérer des clés sur les plis d’arête",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on face material indices"),
+     (("bpy.types.AnimallProperties.key_material_index",),
+      ()),
+     ("fr_FR", "Insérer des clés sur les indices de matériaux",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on point locations"),
+     (("bpy.types.AnimallProperties.key_point_location",),
+      ()),
+     ("fr_FR", "Insérer des clés sur les positions des points",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on point radius (Shrink/Fatten)"),
+     (("bpy.types.AnimallProperties.key_radius",),
+      ()),
+     ("fr_FR", "Insérer des clés sur le rayon de rayon de point (épaisseur de la courbe)",
+               (False, ())),
+    ),
+    (("*", "Key Selected Only"),
+     (("bpy.types.AnimallProperties.key_selected",),
+      ()),
+     ("fr_FR", "Sélection uniquement",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes only on selected elements"),
+     (("bpy.types.AnimallProperties.key_selected",),
+      ()),
+     ("fr_FR", "Insérer des images clés seulement sur les éléments sélectionnés",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on active Shape Key layer"),
+     (("bpy.types.AnimallProperties.key_shape_key",),
+      ()),
+     ("fr_FR", "Insérer des clés sur le calque de clé de forme actif",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on point tilt"),
+     (("bpy.types.AnimallProperties.key_tilt",),
+      ()),
+     ("fr_FR", "Insérer des clés sur l’inclinaison des points",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on active UV coordinates"),
+     (("bpy.types.AnimallProperties.key_uvs",),
+      ()),
+     ("fr_FR", "Insérer des clés sur les coordonnées UV actives",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on vertex bevel weight"),
+     (("bpy.types.AnimallProperties.key_vertex_bevel",),
+      ()),
+     ("fr_FR", "Insérer des clés sur les poids de biseau des sommets",
+               (False, ())),
+    ),
+    (("*", "Insert keyframes on active vertex group values"),
+     (("bpy.types.AnimallProperties.key_vertex_group",),
+      ()),
+     ("fr_FR", "Insérer des clés sur les valeurs des groupes de sommets",
+               (False, ())),
+    ),
+    (("*", "AnimAll"),
+     (("scripts/addons/animation_animall/__init__.py:138",
+       "Add-on AnimAll info: name"),
+      ()),
+     ("fr_FR", "AnimAll",
+               (False, ())),
+    ),
+    (("*", "Key:"),
+     (("scripts/addons/animation_animall/__init__.py:146",),
+      ()),
+     ("fr_FR", "Insérer :",
+               (False, ())),
+    ),
+    (("*", "Tab Category:"),
+     (("scripts/addons/animation_animall/__init__.py:653",),
+      ()),
+     ("fr_FR", "Catégorie d’onglet :",
+               (False, ())),
+    ),
+    (("*", "Points"),
+     (("scripts/addons/animation_animall/__init__.py:152",
+       "scripts/addons/animation_animall/__init__.py:159",
+       "scripts/addons/animation_animall/__init__.py:188"),
+      ()),
+     ("fr_FR", "Points",
+               (False, ())),
+    ),
+    (("*", "Others"),
+     (("scripts/addons/animation_animall/__init__.py:155",
+       "scripts/addons/animation_animall/__init__.py:171",
+       "scripts/addons/animation_animall/__init__.py:196"),
+      ()),
+     ("fr_FR", "Autres",
+               (False, ())),
+    ),
+    (("*", "Bevel"),
+     (("scripts/addons/animation_animall/__init__.py:161",
+       "scripts/addons/animation_animall/__init__.py:165"),
+      ()),
+     ("fr_FR", "Biseau",
+               (False, ())),
+    ),
+    (("*", "Edges"),
+     (("scripts/addons/animation_animall/__init__.py:164",),
+      ()),
+     ("fr_FR", "Arêtes",
+               (False, ())),
+    ),
+    (("*", "Crease"),
+     (("scripts/addons/animation_animall/__init__.py:166",),
+      ()),
+     ("fr_FR", "Plis",
+               (False, ())),
+    ),
+    (("*", "Faces"),
+     (("scripts/addons/animation_animall/__init__.py:168",),
+      ()),
+     ("fr_FR", "Faces",
+               (False, ())),
+    ),
+    (("*", "\"Location\" and \"Shape Key\" are redundant?"),
+     (("scripts/addons/animation_animall/__init__.py:218",),
+      ()),
+     ("fr_FR", "\"Position\" et \"Clé de forme\" sont redondants ?",
+               (False, ())),
+    ),
+    (("*", "Splines"),
+     (("scripts/addons/animation_animall/__init__.py:193",),
+      ()),
+     ("fr_FR", "Splines",
+               (False, ())),
+    ),
+    (("*", "Maybe set \"%s\" to 1.0?"),
+     (("scripts/addons/animation_animall/__init__.py:209",
+       "scripts/addons/animation_animall/__init__.py:209"),
+      ()),
+     ("fr_FR", "Essayez de mettre « %s » à 1.0 ?",
+               (False, ())),
+    ),
+    (("*", "Cannot key on Basis Shape"),
+     (("scripts/addons/animation_animall/__init__.py:212",),
+      ()),
+     ("fr_FR", "Impossible d’ajouter une clé sur la forme de base",
+               (False, ())),
+    ),
+    (("*", "No active Shape Key"),
+     (("scripts/addons/animation_animall/__init__.py:215",),
+      ()),
+     ("fr_FR", "Pas de clé de forme active",
+               (False, ())),
+    ),
+    (("*", "Clear Animation could not be performed"),
+     (("scripts/addons/animation_animall/__init__.py:581",),
+      ()),
+     ("fr_FR", "La suppression de l’animation n’a pas pu aboutir",
+               (False, ())),
+    ),
+    (("*", "Object includes old-style vertex colors. Consider updating them."),
+     (("scripts/addons/animation_animall/__init__.py:182",),
+      ()),
+     ("fr_FR", "L’objet contient des couleurs de sommets à l’ancien format. Veuillez les mettre à jour",
+               (False, ())),
+    ),
+    (("*", "Vertex %s"),
+     (("scripts/addons/animation_animall/__init__.py:358",
+       "scripts/addons/animation_animall/__init__.py:313",
+       "scripts/addons/animation_animall/__init__.py:318",
+       "scripts/addons/animation_animall/__init__.py:328"),
+      ()),
+     ("fr_FR", "Sommet %s",
+               (False, ())),
+    ),
+    (("*", "Edge %s"),
+     (("scripts/addons/animation_animall/__init__.py:360",
+       "scripts/addons/animation_animall/__init__.py:333",
+       "scripts/addons/animation_animall/__init__.py:338"),
+      ()),
+     ("fr_FR", "Arête %s",
+               (False, ())),
+    ),
+    (("*", "Point %s"),
+     (("scripts/addons/animation_animall/__init__.py:265",),
+      ()),
+     ("fr_FR", "Point %s",
+               (False, ())),
+    ),
+    (("*", "Spline %s"),
+     (("scripts/addons/animation_animall/__init__.py:273",),
+      ()),
+     ("fr_FR", "Spline %s",
+               (False, ())),
+    ),
+    (("*", "Face %s"),
+     (("scripts/addons/animation_animall/__init__.py:343",
+       "scripts/addons/animation_animall/__init__.py:362"),
+      ()),
+     ("fr_FR", "Face %s",
+               (False, ())),
+    ),
+    (("*", "%s Point %s"),
+     (("scripts/addons/animation_animall/__init__.py:260",),
+      ()),
+     ("fr_FR", "%s Point %s",
+               (False, ())),
+    ),
+    (("*", "Loop %s"),
+     (("scripts/addons/animation_animall/__init__.py:364",),
+      ()),
+     ("fr_FR", "Boucle %s",
+               (False, ())),
+    ),
+    (("*", "UV layer %s"),
+     (("scripts/addons/animation_animall/__init__.py:379",),
+      ()),
+     ("fr_FR", "Calque UV %s",
+               (False, ())),
+    ),
+    (("*", "%s Vertex %s"),
+     (("scripts/addons/animation_animall/__init__.py:386",),
+      ()),
+     ("fr_FR", "%s Sommet %s",
+               (False, ())),
+    ),
+    (("*", "Spline %s CV %s"),
+     (("scripts/addons/animation_animall/__init__.py:283",
+       "scripts/addons/animation_animall/__init__.py:284",
+       "scripts/addons/animation_animall/__init__.py:285",
+       "scripts/addons/animation_animall/__init__.py:288",
+       "scripts/addons/animation_animall/__init__.py:291",
+       "scripts/addons/animation_animall/__init__.py:297",
+       "scripts/addons/animation_animall/__init__.py:300",
+       "scripts/addons/animation_animall/__init__.py:303"),
+      ()),
+     ("fr_FR", "Spline %s Point %s",
+               (False, ())),
+    ),
+    (("*", "%s Spline %s CV %s"),
+     (("scripts/addons/animation_animall/__init__.py:402",
+       "scripts/addons/animation_animall/__init__.py:403",
+       "scripts/addons/animation_animall/__init__.py:404",
+       "scripts/addons/animation_animall/__init__.py:405",
+       "scripts/addons/animation_animall/__init__.py:406",
+       "scripts/addons/animation_animall/__init__.py:414",
+       "scripts/addons/animation_animall/__init__.py:415",
+       "scripts/addons/animation_animall/__init__.py:416"),
+      ()),
+     ("fr_FR", "%s Spline %s Point %s",
+               (False, ())),
+    ),
+    (("*", "3D View > Toolbox > Animation tab > AnimAll"),
+     (("Add-on AnimAll info: location",),
+      ()),
+     ("fr_FR", "Vue 3D > Panneau N > Onglet Animer > AnimAll",
+               (False, ())),
+    ),
+    (("*", "Allows animation of mesh, lattice, curve and surface data"),
+     (("Add-on AnimAll info: description",),
+      ()),
+     ("fr_FR", "Permet d’animer les données de maillages, de lattices, de courbes et de surfaces",
+               (False, ())),
+    ),
+)
+
+translations_dict = {}
+for msg in translations_tuple:
+    key = msg[0]
+    for lang, trans, (is_fuzzy, comments) in msg[2:]:
+        if trans and not is_fuzzy:
+            translations_dict.setdefault(lang, {})[key] = trans
+
+# ##### END AUTOGENERATED I18N SECTION #####
diff --git a/ant_landscape/__init__.py b/ant_landscape/__init__.py
index 295793b9b89faaf92b85f37d0898d10f4a3992df..c330ad5eeaec65d836df908891b02938c241c675 100644
--- a/ant_landscape/__init__.py
+++ b/ant_landscape/__init__.py
@@ -30,23 +30,24 @@ else:
 import bpy
 
 from bpy.props import (
-        BoolProperty,
-        FloatProperty,
-        IntProperty,
-        StringProperty,
-        PointerProperty,
-        EnumProperty,
-        )
+    BoolProperty,
+    FloatProperty,
+    IntProperty,
+    StringProperty,
+    PointerProperty,
+    EnumProperty,
+)
 from .ant_functions import (
-        draw_ant_refresh,
-        draw_ant_main,
-        draw_ant_noise,
-        draw_ant_displace,
-        )
+    draw_ant_refresh,
+    draw_ant_main,
+    draw_ant_noise,
+    draw_ant_displace,
+)
 
 # ------------------------------------------------------------
 # Menu's and panels
 
+
 def menu_func_eroder(self, context):
     ob = bpy.context.active_object
     if ob and (ob.ant_landscape.keys() and not ob.ant_landscape['sphere_mesh']):
@@ -129,7 +130,7 @@ class AntMainSettingsPanel(bpy.types.Panel):
         if ant.sphere_mesh:
             split.prop(ant, "remove_double", toggle=True, text="Remove Doubles", icon='MESH_DATA')
         box.prop(ant, "ant_terrain_name")
-        box.prop_search(ant, "land_material",  bpy.data, "materials")
+        box.prop_search(ant, "land_material", bpy.data, "materials")
         col = box.column(align=True)
         col.prop(ant, "subdivision_x")
         col.prop(ant, "subdivision_y")
@@ -398,131 +399,131 @@ class AntDisplaceSettingsPanel(bpy.types.Panel):
 class AntLandscapePropertiesGroup(bpy.types.PropertyGroup):
 
     ant_terrain_name: StringProperty(
-            name="Name",
-            default="Landscape"
-            )
+        name="Name",
+        default="Landscape"
+    )
     land_material: StringProperty(
-            name='Material',
-            default="",
-            description="Terrain material"
-            )
+        name='Material',
+        default="",
+        description="Terrain material"
+    )
     water_material: StringProperty(
-            name='Material',
-            default="",
-            description="Water plane material"
-            )
+        name='Material',
+        default="",
+        description="Water plane material"
+    )
     texture_block: StringProperty(
-            name="Texture",
-            default=""
-            )
+        name="Texture",
+        default=""
+    )
     at_cursor: BoolProperty(
-            name="Cursor",
-            default=True,
-            description="Place at cursor location",
-            )
+        name="Cursor",
+        default=True,
+        description="Place at cursor location",
+    )
     smooth_mesh: BoolProperty(
-            name="Smooth",
-            default=True,
-            description="Shade smooth"
-            )
+        name="Smooth",
+        default=True,
+        description="Shade smooth"
+    )
     tri_face: BoolProperty(
-            name="Triangulate",
-            default=False,
-            description="Triangulate faces"
-            )
+        name="Triangulate",
+        default=False,
+        description="Triangulate faces"
+    )
     sphere_mesh: BoolProperty(
-            name="Sphere",
-            default=False,
-            description="Generate uv sphere - remove doubles when ready"
-            )
+        name="Sphere",
+        default=False,
+        description="Generate uv sphere - remove doubles when ready"
+    )
     subdivision_x: IntProperty(
-            name="Subdivisions X",
-            default=128,
-            min=4,
-            max=6400,
-            description="Mesh X subdivisions"
-            )
+        name="Subdivisions X",
+        default=128,
+        min=4,
+        max=6400,
+        description="Mesh X subdivisions"
+    )
     subdivision_y: IntProperty(
-            default=128,
-            name="Subdivisions Y",
-            min=4,
-            max=6400,
-            description="Mesh Y subdivisions"
-            )
+        default=128,
+        name="Subdivisions Y",
+        min=4,
+        max=6400,
+        description="Mesh Y subdivisions"
+    )
     mesh_size: FloatProperty(
-            default=2.0,
-            name="Mesh Size",
-            min=0.01,
-            max=100000.0,
-            description="Mesh size"
-            )
+        default=2.0,
+        name="Mesh Size",
+        min=0.01,
+        max=100000.0,
+        description="Mesh size"
+    )
     mesh_size_x: FloatProperty(
-            default=2.0,
-            name="Mesh Size X",
-            min=0.01,
-            description="Mesh x size"
-            )
+        default=2.0,
+        name="Mesh Size X",
+        min=0.01,
+        description="Mesh x size"
+    )
     mesh_size_y: FloatProperty(
-            name="Mesh Size Y",
-            default=2.0,
-            min=0.01,
-            description="Mesh y size"
-            )
+        name="Mesh Size Y",
+        default=2.0,
+        min=0.01,
+        description="Mesh y size"
+    )
 
     random_seed: IntProperty(
-            name="Random Seed",
-            default=0,
-            min=0,
-            description="Randomize noise origin"
-            )
+        name="Random Seed",
+        default=0,
+        min=0,
+        description="Randomize noise origin"
+    )
     noise_offset_x: FloatProperty(
-            name="Offset X",
-            default=0.0,
-            description="Noise X Offset"
-            )
+        name="Offset X",
+        default=0.0,
+        description="Noise X Offset"
+    )
     noise_offset_y: FloatProperty(
-            name="Offset Y",
-            default=0.0,
-            description="Noise Y Offset"
-            )
+        name="Offset Y",
+        default=0.0,
+        description="Noise Y Offset"
+    )
     noise_offset_z: FloatProperty(
-            name="Offset Z",
-            default=0.0,
-            description="Noise Z Offset"
-            )
+        name="Offset Z",
+        default=0.0,
+        description="Noise Z Offset"
+    )
     noise_size_x: FloatProperty(
-            default=1.0,
-            name="Size X",
-            min=0.01,
-            max=1000.0,
-            description="Noise x size"
-            )
+        default=1.0,
+        name="Size X",
+        min=0.01,
+        max=1000.0,
+        description="Noise x size"
+    )
     noise_size_y: FloatProperty(
-            name="Size Y",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Noise y size"
-            )
+        name="Size Y",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Noise y size"
+    )
     noise_size_z: FloatProperty(
-            name="Size Z",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Noise Z size"
-            )
+        name="Size Z",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Noise Z size"
+    )
     noise_size: FloatProperty(
-            name="Noise Size",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Noise size"
-            )
+        name="Noise Size",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Noise size"
+    )
     noise_type: EnumProperty(
-            name="Noise Type",
-            default='hetero_terrain',
-            description="Noise type",
-            items = [
+        name="Noise Type",
+        default='hetero_terrain',
+        description="Noise type",
+        items=[
                 ('multi_fractal', "Multi Fractal", "Blender: Multi Fractal algorithm", 0),
                 ('ridged_multi_fractal', "Ridged MFractal", "Blender: Ridged Multi Fractal", 1),
                 ('hybrid_multi_fractal', "Hybrid MFractal", "Blender: Hybrid Multi Fractal", 2),
@@ -542,110 +543,110 @@ class AntLandscapePropertiesGroup(bpy.types.PropertyGroup):
                 ('slick_rock', "Slick Rock", "A.N.T: slick rock", 16),
                 ('planet_noise', "Planet Noise", "Planet Noise by: Farsthary", 17),
                 ('blender_texture', "Blender Texture - Texture Nodes", "Blender texture data block", 18)]
-            )
+    )
     basis_type: EnumProperty(
-            name="Noise Basis",
-            default=ant_noise.noise_basis_default,
-            description="Noise basis algorithms",
-            items = ant_noise.noise_basis
-            )
+        name="Noise Basis",
+        default=ant_noise.noise_basis_default,
+        description="Noise basis algorithms",
+        items=ant_noise.noise_basis
+    )
     vl_basis_type: EnumProperty(
-            name="vlNoise Basis",
-            default=ant_noise.noise_basis_default,
-            description="VLNoise basis algorithms",
-            items = ant_noise.noise_basis
-            )
+        name="vlNoise Basis",
+        default=ant_noise.noise_basis_default,
+        description="VLNoise basis algorithms",
+        items=ant_noise.noise_basis
+    )
     distortion: FloatProperty(
-            name="Distortion",
-            default=1.0,
-            min=0.01,
-            max=100.0,
-            description="Distortion amount"
-            )
+        name="Distortion",
+        default=1.0,
+        min=0.01,
+        max=100.0,
+        description="Distortion amount"
+    )
     hard_noise: EnumProperty(
-            name="Soft Hard",
-            default="0",
-            description="Soft Noise, Hard noise",
-            items = [
+        name="Soft Hard",
+        default="0",
+        description="Soft Noise, Hard noise",
+        items=[
                 ("0", "Soft", "Soft Noise", 0),
                 ("1", "Hard", "Hard noise", 1)]
-            )
+    )
     noise_depth: IntProperty(
-            name="Depth",
-            default=8,
-            min=0,
-            max=16,
-            description="Noise Depth - number of frequencies in the fBm"
-            )
+        name="Depth",
+        default=8,
+        min=0,
+        max=16,
+        description="Noise Depth - number of frequencies in the fBm"
+    )
     amplitude: FloatProperty(
-            name="Amp",
-            default=0.5,
-            min=0.01,
-            max=1.0,
-            description="Amplitude"
-            )
+        name="Amp",
+        default=0.5,
+        min=0.01,
+        max=1.0,
+        description="Amplitude"
+    )
     frequency: FloatProperty(
-            name="Freq",
-            default=2.0,
-            min=0.01,
-            max=5.0,
-            description="Frequency"
-            )
+        name="Freq",
+        default=2.0,
+        min=0.01,
+        max=5.0,
+        description="Frequency"
+    )
     dimension: FloatProperty(
-            name="Dimension",
-            default=1.0,
-            min=0.01,
-            max=2.0,
-            description="H - fractal dimension of the roughest areas"
-            )
+        name="Dimension",
+        default=1.0,
+        min=0.01,
+        max=2.0,
+        description="H - fractal dimension of the roughest areas"
+    )
     lacunarity: FloatProperty(
-            name="Lacunarity",
-            min=0.01,
-            max=6.0,
-            default=2.0,
-            description="Lacunarity - gap between successive frequencies"
-            )
+        name="Lacunarity",
+        min=0.01,
+        max=6.0,
+        default=2.0,
+        description="Lacunarity - gap between successive frequencies"
+    )
     offset: FloatProperty(
-            name="Offset",
-            default=1.0,
-            min=0.01,
-            max=6.0,
-            description="Offset - raises the terrain from sea level"
-            )
+        name="Offset",
+        default=1.0,
+        min=0.01,
+        max=6.0,
+        description="Offset - raises the terrain from sea level"
+    )
     gain: FloatProperty(
-            name="Gain",
-            default=1.0,
-            min=0.01,
-            max=6.0,
-            description="Gain - scale factor"
-            )
+        name="Gain",
+        default=1.0,
+        min=0.01,
+        max=6.0,
+        description="Gain - scale factor"
+    )
     marble_bias: EnumProperty(
-            name="Bias",
-            default="0",
-            description="Marble bias",
-            items = [
+        name="Bias",
+        default="0",
+        description="Marble bias",
+        items=[
                 ("0", "Sin", "Sin", 0),
                 ("1", "Cos", "Cos", 1),
                 ("2", "Tri", "Tri", 2),
                 ("3", "Saw", "Saw", 3)]
-            )
+    )
     marble_sharp: EnumProperty(
-            name="Sharp",
-            default="0",
-            description="Marble sharpness",
-            items = [
+        name="Sharp",
+        default="0",
+        description="Marble sharpness",
+        items=[
                 ("0", "Soft", "Soft", 0),
                 ("1", "Sharp", "Sharp", 1),
                 ("2", "Sharper", "Sharper", 2),
                 ("3", "Soft inv.", "Soft", 3),
                 ("4", "Sharp inv.", "Sharp", 4),
                 ("5", "Sharper inv.", "Sharper", 5)]
-            )
+    )
     marble_shape: EnumProperty(
-            name="Shape",
-            default="0",
-            description="Marble shape",
-            items= [
+        name="Shape",
+        default="0",
+        description="Marble shape",
+        items=[
                 ("0", "Default", "Default", 0),
                 ("1", "Ring", "Ring", 1),
                 ("2", "Swirl", "Swirl", 2),
@@ -654,38 +655,38 @@ class AntLandscapePropertiesGroup(bpy.types.PropertyGroup):
                 ("5", "Z", "Z", 5),
                 ("6", "Y", "Y", 6),
                 ("7", "X", "X", 7)]
-        )
+    )
     height: FloatProperty(
-            name="Height",
-            default=0.5,
-            min=-10000.0,
-            max=10000.0,
-            description="Noise intensity scale"
-            )
+        name="Height",
+        default=0.5,
+        min=-10000.0,
+        max=10000.0,
+        description="Noise intensity scale"
+    )
     height_invert: BoolProperty(
-            name="Invert",
-            default=False,
-            description="Height invert",
-            )
+        name="Invert",
+        default=False,
+        description="Height invert",
+    )
     height_offset: FloatProperty(
-            name="Offset",
-            default=0.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Height offset"
-            )
+        name="Offset",
+        default=0.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Height offset"
+    )
     fx_mixfactor: FloatProperty(
-            name="Mix Factor",
-            default=0.0,
-            min=-1.0,
-            max=1.0,
-            description="Effect mix factor: -1.0 = Noise, +1.0 = Effect"
-            )
+        name="Mix Factor",
+        default=0.0,
+        min=-1.0,
+        max=1.0,
+        description="Effect mix factor: -1.0 = Noise, +1.0 = Effect"
+    )
     fx_mix_mode: EnumProperty(
-            name="Effect Mix",
-            default="0",
-            description="Effect mix mode",
-            items = [
+        name="Effect Mix",
+        default="0",
+        description="Effect mix mode",
+        items=[
                 ("0", "Mix", "Mix", 0),
                 ("1", "Add", "Add", 1),
                 ("2", "Sub", "Subtract", 2),
@@ -695,13 +696,13 @@ class AntLandscapePropertiesGroup(bpy.types.PropertyGroup):
                 ("6", "Mod", "Modulo", 6),
                 ("7", "Min", "Minimum", 7),
                 ("8", "Max", "Maximum", 8)
-                ]
-            )
+        ]
+    )
     fx_type: EnumProperty(
-            name="Effect Type",
-            default="0",
-            description="Effect type",
-            items = [
+        name="Effect Type",
+        default="0",
+        description="Effect type",
+        items=[
                 ("0", "None", "No effect", 0),
                 ("1", "Gradient", "Gradient", 1),
                 ("2", "Waves", "Waves - Bumps", 2),
@@ -724,183 +725,184 @@ class AntLandscapePropertiesGroup(bpy.types.PropertyGroup):
                 ("19", "Stone", "Stone", 19),
                 ("20", "Flat Turb", "Flat turbulence", 20),
                 ("21", "Flat Voronoi", "Flat voronoi", 21)
-                ]
-            )
+        ]
+    )
     fx_bias: EnumProperty(
-            name="Effect Bias",
-            default="0",
-            description="Effect bias type",
-            items = [
+        name="Effect Bias",
+        default="0",
+        description="Effect bias type",
+        items=[
                 ("0", "Sin", "Sin", 0),
                 ("1", "Cos", "Cos", 1),
                 ("2", "Tri", "Tri", 2),
                 ("3", "Saw", "Saw", 3),
                 ("4", "None", "None", 4)]
-            )
+    )
     fx_turb: FloatProperty(
-            name="Distortion",
-            default=0.0,
-            min=0.0,
-            max=1000.0,
-            description="Effect turbulence distortion"
-            )
+        name="Distortion",
+        default=0.0,
+        min=0.0,
+        max=1000.0,
+        description="Effect turbulence distortion"
+    )
     fx_depth: IntProperty(
-            name="Depth",
-            default=0,
-            min=0,
-            max=16,
-            description="Effect depth - number of frequencies"
-            )
+        name="Depth",
+        default=0,
+        min=0,
+        max=16,
+        description="Effect depth - number of frequencies"
+    )
     fx_amplitude: FloatProperty(
-            name="Amp",
-            default=0.5,
-            min=0.01,
-            max=1.0,
-            description="Amplitude"
-            )
+        name="Amp",
+        default=0.5,
+        min=0.01,
+        max=1.0,
+        description="Amplitude"
+    )
     fx_frequency: FloatProperty(
-            name="Freq",
-            default=2.0,
-            min=0.01,
-            max=5.0,
-            description="Frequency"
-            )
+        name="Freq",
+        default=2.0,
+        min=0.01,
+        max=5.0,
+        description="Frequency"
+    )
     fx_size: FloatProperty(
-            name="Effect Size",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Effect size"
-            )
+        name="Effect Size",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Effect size"
+    )
     fx_loc_x: FloatProperty(
-            name="Offset X",
-            default=0.0,
-            description="Effect x offset"
-            )
+        name="Offset X",
+        default=0.0,
+        description="Effect x offset"
+    )
     fx_loc_y: FloatProperty(
-            name="Offset Y",
-            default=0.0,
-            description="Effect y offset"
-            )
+        name="Offset Y",
+        default=0.0,
+        description="Effect y offset"
+    )
     fx_height: FloatProperty(
-            name="Intensity",
-            default=1.0,
-            min=-1000.0,
-            max=1000.0,
-            description="Effect intensity scale"
-            )
+        name="Intensity",
+        default=1.0,
+        min=-1000.0,
+        max=1000.0,
+        description="Effect intensity scale"
+    )
     fx_invert: BoolProperty(
-            name="Invert",
-            default=False,
-            description="Effect invert"
-            )
+        name="Invert",
+        default=False,
+        description="Effect invert"
+    )
     fx_offset: FloatProperty(
-            name="Offset",
-            default=0.0,
-            min=-1000.0,
-            max=1000.0,
-            description="Effect height offset"
-            )
+        name="Offset",
+        default=0.0,
+        min=-1000.0,
+        max=1000.0,
+        description="Effect height offset"
+    )
 
     edge_falloff: EnumProperty(
-            name="Falloff",
-            default="3",
-            description="Flatten edges",
-            items = [
+        name="Falloff",
+        default="3",
+        description="Flatten edges",
+        items=[
                 ("0", "None", "None", 0),
                 ("1", "Y", "Y Falloff", 1),
                 ("2", "X", "X Falloff", 2),
                 ("3", "X Y", "X Y Falloff", 3)]
-            )
+    )
     falloff_x: FloatProperty(
-            name="Falloff X",
-            default=4.0,
-            min=0.1,
-            max=100.0,
-            description="Falloff x scale"
-            )
+        name="Falloff X",
+        default=4.0,
+        min=0.1,
+        max=100.0,
+        description="Falloff x scale"
+    )
     falloff_y: FloatProperty(
-            name="Falloff Y",
-            default=4.0,
-            min=0.1,
-            max=100.0,
-            description="Falloff y scale"
-            )
+        name="Falloff Y",
+        default=4.0,
+        min=0.1,
+        max=100.0,
+        description="Falloff y scale"
+    )
     edge_level: FloatProperty(
-            name="Edge Level",
-            default=0.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Edge level, sealevel offset"
-            )
+        name="Edge Level",
+        default=0.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Edge level, sealevel offset"
+    )
     maximum: FloatProperty(
-            name="Maximum",
-            default=1.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Maximum, flattens terrain at plateau level"
-            )
+        name="Maximum",
+        default=1.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Maximum, flattens terrain at plateau level"
+    )
     minimum: FloatProperty(
-            name="Minimum",
-            default=-1.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Minimum, flattens terrain at seabed level"
-            )
+        name="Minimum",
+        default=-1.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Minimum, flattens terrain at seabed level"
+    )
     vert_group: StringProperty(
-            name="Vertex Group",
-            default=""
-            )
+        name="Vertex Group",
+        default=""
+    )
     strata: FloatProperty(
-            name="Amount",
-            default=5.0,
-            min=0.01,
-            max=1000.0,
-            description="Strata layers / terraces"
-            )
+        name="Amount",
+        default=5.0,
+        min=0.01,
+        max=1000.0,
+        description="Strata layers / terraces"
+    )
     strata_type: EnumProperty(
-            name="Strata",
-            default="0",
-            description="Strata types",
-            items = [
+        name="Strata",
+        default="0",
+        description="Strata types",
+        items=[
                 ("0", "None", "No strata", 0),
                 ("1", "Smooth", "Smooth transitions", 1),
                 ("2", "Sharp Sub", "Sharp subtract transitions", 2),
                 ("3", "Sharp Add", "Sharp add transitions", 3),
                 ("4", "Quantize", "Quantize", 4),
                 ("5", "Quantize Mix", "Quantize mixed", 5)]
-            )
+    )
     water_plane: BoolProperty(
-            name="Water Plane",
-            default=False,
-            description="Add water plane"
-            )
+        name="Water Plane",
+        default=False,
+        description="Add water plane"
+    )
     water_level: FloatProperty(
-            name="Level",
-            default=0.01,
-            min=-10000.0,
-            max=10000.0,
-            description="Water level"
-            )
+        name="Level",
+        default=0.01,
+        min=-10000.0,
+        max=10000.0,
+        description="Water level"
+    )
     remove_double: BoolProperty(
-            name="Remove Doubles",
-            default=False,
-            description="Remove doubles"
-            )
+        name="Remove Doubles",
+        default=False,
+        description="Remove doubles"
+    )
     refresh: BoolProperty(
-            name="Refresh",
-            default=False,
-            description="Refresh"
-            )
+        name="Refresh",
+        default=False,
+        description="Refresh"
+    )
     auto_refresh: BoolProperty(
-            name="Auto",
-            default=True,
-            description="Automatic refresh"
-            )
+        name="Auto",
+        default=True,
+        description="Automatic refresh"
+    )
 
 # ------------------------------------------------------------
 # Register:
 
+
 classes = (
     AntLandscapeAddPanel,
     AntLandscapeToolsPanel,
@@ -916,12 +918,16 @@ classes = (
     ant_functions.Eroder,
 )
 
+
 def register():
     for cls in classes:
         bpy.utils.register_class(cls)
 
     bpy.types.VIEW3D_MT_mesh_add.append(menu_func_landscape)
-    bpy.types.Object.ant_landscape = PointerProperty(type=AntLandscapePropertiesGroup, name="ANT_Landscape", description="Landscape properties")
+    bpy.types.Object.ant_landscape = PointerProperty(
+        type=AntLandscapePropertiesGroup,
+        name="ANT_Landscape",
+        description="Landscape properties")
     bpy.types.VIEW3D_MT_paint_weight.append(menu_func_eroder)
 
 
diff --git a/ant_landscape/add_mesh_ant_landscape.py b/ant_landscape/add_mesh_ant_landscape.py
index a148f448155023248f02029aef53516bcd473070..e12561bc9ec68afc1437e891d1de17c17cbe309d 100644
--- a/ant_landscape/add_mesh_ant_landscape.py
+++ b/ant_landscape/add_mesh_ant_landscape.py
@@ -6,30 +6,32 @@
 # import modules
 import bpy
 from bpy.props import (
-        BoolProperty,
-        EnumProperty,
-        FloatProperty,
-        IntProperty,
-        StringProperty,
-        FloatVectorProperty,
-        )
+    BoolProperty,
+    EnumProperty,
+    FloatProperty,
+    IntProperty,
+    StringProperty,
+    FloatVectorProperty,
+)
 
 from .ant_functions import (
-        grid_gen,
-        sphere_gen,
-        create_mesh_object,
-        store_properties,
-        draw_ant_refresh,
-        draw_ant_main,
-        draw_ant_noise,
-        draw_ant_displace,
-        draw_ant_water,
-        )
+    grid_gen,
+    sphere_gen,
+    create_mesh_object,
+    store_properties,
+    draw_ant_refresh,
+    draw_ant_main,
+    draw_ant_noise,
+    draw_ant_displace,
+    draw_ant_water,
+)
 
 from ant_landscape import ant_noise
 
 # ------------------------------------------------------------
 # Add landscape
+
+
 class AntAddLandscape(bpy.types.Operator):
     bl_idname = "mesh.landscape_add"
     bl_label = "Another Noise Tool - Landscape"
@@ -37,131 +39,131 @@ class AntAddLandscape(bpy.types.Operator):
     bl_options = {'REGISTER', 'UNDO', 'PRESET'}
 
     ant_terrain_name: StringProperty(
-            name="Name",
-            default="Landscape"
-            )
+        name="Name",
+        default="Landscape"
+    )
     land_material: StringProperty(
-            name='Material',
-            default="",
-            description="Terrain material"
-            )
+        name='Material',
+        default="",
+        description="Terrain material"
+    )
     water_material: StringProperty(
-            name='Material',
-            default="",
-            description="Water plane material"
-            )
+        name='Material',
+        default="",
+        description="Water plane material"
+    )
     texture_block: StringProperty(
-            name="Texture",
-            default=""
-            )
+        name="Texture",
+        default=""
+    )
     at_cursor: BoolProperty(
-            name="Cursor",
-            default=True,
-            description="Place at cursor location",
-            )
+        name="Cursor",
+        default=True,
+        description="Place at cursor location",
+    )
     smooth_mesh: BoolProperty(
-            name="Smooth",
-            default=True,
-            description="Shade smooth"
-            )
+        name="Smooth",
+        default=True,
+        description="Shade smooth"
+    )
     tri_face: BoolProperty(
-            name="Triangulate",
-            default=False,
-            description="Triangulate faces"
-            )
+        name="Triangulate",
+        default=False,
+        description="Triangulate faces"
+    )
     sphere_mesh: BoolProperty(
-            name="Sphere",
-            default=False,
-            description="Generate uv sphere - remove doubles when ready"
-            )
+        name="Sphere",
+        default=False,
+        description="Generate uv sphere - remove doubles when ready"
+    )
     subdivision_x: IntProperty(
-            name="Subdivisions X",
-            default=128,
-            min=4,
-            max=6400,
-            description="Mesh X subdivisions"
-            )
+        name="Subdivisions X",
+        default=128,
+        min=4,
+        max=6400,
+        description="Mesh X subdivisions"
+    )
     subdivision_y: IntProperty(
-            default=128,
-            name="Subdivisions Y",
-            min=4,
-            max=6400,
-            description="Mesh Y subdivisions"
-            )
+        default=128,
+        name="Subdivisions Y",
+        min=4,
+        max=6400,
+        description="Mesh Y subdivisions"
+    )
     mesh_size: FloatProperty(
-            default=2.0,
-            name="Mesh Size",
-            min=0.01,
-            max=100000.0,
-            description="Mesh size"
-            )
+        default=2.0,
+        name="Mesh Size",
+        min=0.01,
+        max=100000.0,
+        description="Mesh size"
+    )
     mesh_size_x: FloatProperty(
-            default=2.0,
-            name="Mesh Size X",
-            min=0.01,
-            description="Mesh x size"
-            )
+        default=2.0,
+        name="Mesh Size X",
+        min=0.01,
+        description="Mesh x size"
+    )
     mesh_size_y: FloatProperty(
-            name="Mesh Size Y",
-            default=2.0,
-            min=0.01,
-            description="Mesh y size"
-            )
+        name="Mesh Size Y",
+        default=2.0,
+        min=0.01,
+        description="Mesh y size"
+    )
 
     random_seed: IntProperty(
-            name="Random Seed",
-            default=0,
-            min=0,
-            description="Randomize noise origin"
-            )
+        name="Random Seed",
+        default=0,
+        min=0,
+        description="Randomize noise origin"
+    )
     noise_offset_x: FloatProperty(
-            name="Offset X",
-            default=0.0,
-            description="Noise X Offset"
-            )
+        name="Offset X",
+        default=0.0,
+        description="Noise X Offset"
+    )
     noise_offset_y: FloatProperty(
-            name="Offset Y",
-            default=0.0,
-            description="Noise Y Offset"
-            )
+        name="Offset Y",
+        default=0.0,
+        description="Noise Y Offset"
+    )
     noise_offset_z: FloatProperty(
-            name="Offset Z",
-            default=0.0,
-            description="Noise Z Offset"
-            )
+        name="Offset Z",
+        default=0.0,
+        description="Noise Z Offset"
+    )
     noise_size_x: FloatProperty(
-            default=1.0,
-            name="Size X",
-            min=0.01,
-            max=1000.0,
-            description="Noise x size"
-            )
+        default=1.0,
+        name="Size X",
+        min=0.01,
+        max=1000.0,
+        description="Noise x size"
+    )
     noise_size_y: FloatProperty(
-            name="Size Y",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Noise y size"
-            )
+        name="Size Y",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Noise y size"
+    )
     noise_size_z: FloatProperty(
-            name="Size Z",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Noise Z size"
-            )
+        name="Size Z",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Noise Z size"
+    )
     noise_size: FloatProperty(
-            name="Noise Size",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Noise size"
-            )
+        name="Noise Size",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Noise size"
+    )
     noise_type: EnumProperty(
-            name="Noise Type",
-            default='hetero_terrain',
-            description="Noise type",
-            items = [
+        name="Noise Type",
+        default='hetero_terrain',
+        description="Noise type",
+        items=[
                 ('multi_fractal', "Multi Fractal", "Blender: Multi Fractal algorithm", 0),
                 ('ridged_multi_fractal', "Ridged MFractal", "Blender: Ridged Multi Fractal", 1),
                 ('hybrid_multi_fractal', "Hybrid MFractal", "Blender: Hybrid Multi Fractal", 2),
@@ -181,110 +183,110 @@ class AntAddLandscape(bpy.types.Operator):
                 ('slick_rock', "Slick Rock", "A.N.T: slick rock", 16),
                 ('planet_noise', "Planet Noise", "Planet Noise by: Farsthary", 17),
                 ('blender_texture', "Blender Texture - Texture Nodes", "Blender texture data block", 18)]
-            )
+    )
     basis_type: EnumProperty(
-            name="Noise Basis",
-            default=ant_noise.noise_basis_default,
-            description="Noise basis algorithms",
-            items = ant_noise.noise_basis
-            )
+        name="Noise Basis",
+        default=ant_noise.noise_basis_default,
+        description="Noise basis algorithms",
+        items=ant_noise.noise_basis
+    )
     vl_basis_type: EnumProperty(
-            name="vlNoise Basis",
-            default=ant_noise.noise_basis_default,
-            description="VLNoise basis algorithms",
-            items = ant_noise.noise_basis
-            )
+        name="vlNoise Basis",
+        default=ant_noise.noise_basis_default,
+        description="VLNoise basis algorithms",
+        items=ant_noise.noise_basis
+    )
     distortion: FloatProperty(
-            name="Distortion",
-            default=1.0,
-            min=0.01,
-            max=100.0,
-            description="Distortion amount"
-            )
+        name="Distortion",
+        default=1.0,
+        min=0.01,
+        max=100.0,
+        description="Distortion amount"
+    )
     hard_noise: EnumProperty(
-            name="Soft Hard",
-            default="0",
-            description="Soft Noise, Hard noise",
-            items = [
+        name="Soft Hard",
+        default="0",
+        description="Soft Noise, Hard noise",
+        items=[
                 ("0", "Soft", "Soft Noise", 0),
                 ("1", "Hard", "Hard noise", 1)]
-            )
+    )
     noise_depth: IntProperty(
-            name="Depth",
-            default=8,
-            min=0,
-            max=16,
-            description="Noise Depth - number of frequencies in the fBm"
-            )
+        name="Depth",
+        default=8,
+        min=0,
+        max=16,
+        description="Noise Depth - number of frequencies in the fBm"
+    )
     amplitude: FloatProperty(
-            name="Amp",
-            default=0.5,
-            min=0.01,
-            max=1.0,
-            description="Amplitude"
-            )
+        name="Amp",
+        default=0.5,
+        min=0.01,
+        max=1.0,
+        description="Amplitude"
+    )
     frequency: FloatProperty(
-            name="Freq",
-            default=2.0,
-            min=0.01,
-            max=5.0,
-            description="Frequency"
-            )
+        name="Freq",
+        default=2.0,
+        min=0.01,
+        max=5.0,
+        description="Frequency"
+    )
     dimension: FloatProperty(
-            name="Dimension",
-            default=1.0,
-            min=0.01,
-            max=2.0,
-            description="H - fractal dimension of the roughest areas"
-            )
+        name="Dimension",
+        default=1.0,
+        min=0.01,
+        max=2.0,
+        description="H - fractal dimension of the roughest areas"
+    )
     lacunarity: FloatProperty(
-            name="Lacunarity",
-            min=0.01,
-            max=6.0,
-            default=2.0,
-            description="Lacunarity - gap between successive frequencies"
-            )
+        name="Lacunarity",
+        min=0.01,
+        max=6.0,
+        default=2.0,
+        description="Lacunarity - gap between successive frequencies"
+    )
     offset: FloatProperty(
-            name="Offset",
-            default=1.0,
-            min=0.01,
-            max=6.0,
-            description="Offset - raises the terrain from sea level"
-            )
+        name="Offset",
+        default=1.0,
+        min=0.01,
+        max=6.0,
+        description="Offset - raises the terrain from sea level"
+    )
     gain: FloatProperty(
-            name="Gain",
-            default=1.0,
-            min=0.01,
-            max=6.0,
-            description="Gain - scale factor"
-            )
+        name="Gain",
+        default=1.0,
+        min=0.01,
+        max=6.0,
+        description="Gain - scale factor"
+    )
     marble_bias: EnumProperty(
-            name="Bias",
-            default="0",
-            description="Marble bias",
-            items = [
+        name="Bias",
+        default="0",
+        description="Marble bias",
+        items=[
                 ("0", "Sin", "Sin", 0),
                 ("1", "Cos", "Cos", 1),
                 ("2", "Tri", "Tri", 2),
                 ("3", "Saw", "Saw", 3)]
-            )
+    )
     marble_sharp: EnumProperty(
-            name="Sharp",
-            default="0",
-            description="Marble sharpness",
-            items = [
+        name="Sharp",
+        default="0",
+        description="Marble sharpness",
+        items=[
                 ("0", "Soft", "Soft", 0),
                 ("1", "Sharp", "Sharp", 1),
                 ("2", "Sharper", "Sharper", 2),
                 ("3", "Soft inv.", "Soft", 3),
                 ("4", "Sharp inv.", "Sharp", 4),
                 ("5", "Sharper inv.", "Sharper", 5)]
-            )
+    )
     marble_shape: EnumProperty(
-            name="Shape",
-            default="0",
-            description="Marble shape",
-            items= [
+        name="Shape",
+        default="0",
+        description="Marble shape",
+        items=[
                 ("0", "Default", "Default", 0),
                 ("1", "Ring", "Ring", 1),
                 ("2", "Swirl", "Swirl", 2),
@@ -293,38 +295,38 @@ class AntAddLandscape(bpy.types.Operator):
                 ("5", "Z", "Z", 5),
                 ("6", "Y", "Y", 6),
                 ("7", "X", "X", 7)]
-        )
+    )
     height: FloatProperty(
-            name="Height",
-            default=0.5,
-            min=-10000.0,
-            max=10000.0,
-            description="Noise intensity scale"
-            )
+        name="Height",
+        default=0.5,
+        min=-10000.0,
+        max=10000.0,
+        description="Noise intensity scale"
+    )
     height_invert: BoolProperty(
-            name="Invert",
-            default=False,
-            description="Height invert",
-            )
+        name="Invert",
+        default=False,
+        description="Height invert",
+    )
     height_offset: FloatProperty(
-            name="Offset",
-            default=0.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Height offset"
-            )
+        name="Offset",
+        default=0.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Height offset"
+    )
     fx_mixfactor: FloatProperty(
-            name="Mix Factor",
-            default=0.0,
-            min=-1.0,
-            max=1.0,
-            description="Effect mix factor: -1.0 = Noise, +1.0 = Effect"
-            )
+        name="Mix Factor",
+        default=0.0,
+        min=-1.0,
+        max=1.0,
+        description="Effect mix factor: -1.0 = Noise, +1.0 = Effect"
+    )
     fx_mix_mode: EnumProperty(
-            name="Effect Mix",
-            default="0",
-            description="Effect mix mode",
-            items = [
+        name="Effect Mix",
+        default="0",
+        description="Effect mix mode",
+        items=[
                 ("0", "Mix", "Mix", 0),
                 ("1", "Add", "Add", 1),
                 ("2", "Sub", "Subtract", 2),
@@ -334,13 +336,13 @@ class AntAddLandscape(bpy.types.Operator):
                 ("6", "Mod", "Modulo", 6),
                 ("7", "Min", "Minimum", 7),
                 ("8", "Max", "Maximum", 8)
-                ]
-            )
+        ]
+    )
     fx_type: EnumProperty(
-            name="Effect Type",
-            default="0",
-            description="Effect type",
-            items = [
+        name="Effect Type",
+        default="0",
+        description="Effect type",
+        items=[
                 ("0", "None", "No effect", 0),
                 ("1", "Gradient", "Gradient", 1),
                 ("2", "Waves", "Waves - Bumps", 2),
@@ -363,194 +365,194 @@ class AntAddLandscape(bpy.types.Operator):
                 ("19", "Stone", "Stone", 19),
                 ("20", "Flat Turb", "Flat turbulence", 20),
                 ("21", "Flat Voronoi", "Flat voronoi", 21)
-                ]
-            )
+        ]
+    )
     fx_bias: EnumProperty(
-            name="Effect Bias",
-            default="0",
-            description="Effect bias type",
-            items = [
+        name="Effect Bias",
+        default="0",
+        description="Effect bias type",
+        items=[
                 ("0", "Sin", "Sin", 0),
                 ("1", "Cos", "Cos", 1),
                 ("2", "Tri", "Tri", 2),
                 ("3", "Saw", "Saw", 3),
                 ("4", "None", "None", 4)]
-            )
+    )
     fx_turb: FloatProperty(
-            name="Distortion",
-            default=0.0,
-            min=0.0,
-            max=1000.0,
-            description="Effect turbulence distortion"
-            )
+        name="Distortion",
+        default=0.0,
+        min=0.0,
+        max=1000.0,
+        description="Effect turbulence distortion"
+    )
     fx_depth: IntProperty(
-            name="Depth",
-            default=0,
-            min=0,
-            max=16,
-            description="Effect depth - number of frequencies"
-            )
+        name="Depth",
+        default=0,
+        min=0,
+        max=16,
+        description="Effect depth - number of frequencies"
+    )
     fx_amplitude: FloatProperty(
-            name="Amp",
-            default=0.5,
-            min=0.01,
-            max=1.0,
-            description="Amplitude"
-            )
+        name="Amp",
+        default=0.5,
+        min=0.01,
+        max=1.0,
+        description="Amplitude"
+    )
     fx_frequency: FloatProperty(
-            name="Freq",
-            default=2.0,
-            min=0.01,
-            max=5.0,
-            description="Frequency"
-            )
+        name="Freq",
+        default=2.0,
+        min=0.01,
+        max=5.0,
+        description="Frequency"
+    )
     fx_size: FloatProperty(
-            name="Effect Size",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Effect size"
-            )
+        name="Effect Size",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Effect size"
+    )
     fx_loc_x: FloatProperty(
-            name="Offset X",
-            default=0.0,
-            description="Effect x offset"
-            )
+        name="Offset X",
+        default=0.0,
+        description="Effect x offset"
+    )
     fx_loc_y: FloatProperty(
-            name="Offset Y",
-            default=0.0,
-            description="Effect y offset"
-            )
+        name="Offset Y",
+        default=0.0,
+        description="Effect y offset"
+    )
     fx_height: FloatProperty(
-            name="Intensity",
-            default=1.0,
-            min=-1000.0,
-            max=1000.0,
-            description="Effect intensity scale"
-            )
+        name="Intensity",
+        default=1.0,
+        min=-1000.0,
+        max=1000.0,
+        description="Effect intensity scale"
+    )
     fx_invert: BoolProperty(
-            name="Invert",
-            default=False,
-            description="Effect invert"
-            )
+        name="Invert",
+        default=False,
+        description="Effect invert"
+    )
     fx_offset: FloatProperty(
-            name="Offset",
-            default=0.0,
-            min=-1000.0,
-            max=1000.0,
-            description="Effect height offset"
-            )
+        name="Offset",
+        default=0.0,
+        min=-1000.0,
+        max=1000.0,
+        description="Effect height offset"
+    )
 
     edge_falloff: EnumProperty(
-            name="Falloff",
-            default="3",
-            description="Flatten edges",
-            items = [
+        name="Falloff",
+        default="3",
+        description="Flatten edges",
+        items=[
                 ("0", "None", "None", 0),
                 ("1", "Y", "Y Falloff", 1),
                 ("2", "X", "X Falloff", 2),
                 ("3", "X Y", "X Y Falloff", 3)]
-            )
+    )
     falloff_x: FloatProperty(
-            name="Falloff X",
-            default=4.0,
-            min=0.1,
-            max=100.0,
-            description="Falloff x scale"
-            )
+        name="Falloff X",
+        default=4.0,
+        min=0.1,
+        max=100.0,
+        description="Falloff x scale"
+    )
     falloff_y: FloatProperty(
-            name="Falloff Y",
-            default=4.0,
-            min=0.1,
-            max=100.0,
-            description="Falloff y scale"
-            )
+        name="Falloff Y",
+        default=4.0,
+        min=0.1,
+        max=100.0,
+        description="Falloff y scale"
+    )
     edge_level: FloatProperty(
-            name="Edge Level",
-            default=0.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Edge level, sealevel offset"
-            )
+        name="Edge Level",
+        default=0.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Edge level, sealevel offset"
+    )
     maximum: FloatProperty(
-            name="Maximum",
-            default=1.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Maximum, flattens terrain at plateau level"
-            )
+        name="Maximum",
+        default=1.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Maximum, flattens terrain at plateau level"
+    )
     minimum: FloatProperty(
-            name="Minimum",
-            default=-1.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Minimum, flattens terrain at seabed level"
-            )
+        name="Minimum",
+        default=-1.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Minimum, flattens terrain at seabed level"
+    )
     vert_group: StringProperty(
-            name="Vertex Group",
-            default=""
-            )
+        name="Vertex Group",
+        default=""
+    )
     strata: FloatProperty(
-            name="Amount",
-            default=5.0,
-            min=0.01,
-            max=1000.0,
-            description="Strata layers / terraces"
-            )
+        name="Amount",
+        default=5.0,
+        min=0.01,
+        max=1000.0,
+        description="Strata layers / terraces"
+    )
     strata_type: EnumProperty(
-            name="Strata",
-            default="0",
-            description="Strata types",
-            items = [
+        name="Strata",
+        default="0",
+        description="Strata types",
+        items=[
                 ("0", "None", "No strata", 0),
                 ("1", "Smooth", "Smooth transitions", 1),
                 ("2", "Sharp Sub", "Sharp subtract transitions", 2),
                 ("3", "Sharp Add", "Sharp add transitions", 3),
                 ("4", "Quantize", "Quantize", 4),
                 ("5", "Quantize Mix", "Quantize mixed", 5)]
-            )
+    )
     water_plane: BoolProperty(
-            name="Water Plane",
-            default=False,
-            description="Add water plane"
-            )
+        name="Water Plane",
+        default=False,
+        description="Add water plane"
+    )
     water_level: FloatProperty(
-            name="Level",
-            default=0.01,
-            min=-10000.0,
-            max=10000.0,
-            description="Water level"
-            )
+        name="Level",
+        default=0.01,
+        min=-10000.0,
+        max=10000.0,
+        description="Water level"
+    )
     remove_double: BoolProperty(
-            name="Remove Doubles",
-            default=False,
-            description="Remove doubles"
-            )
+        name="Remove Doubles",
+        default=False,
+        description="Remove doubles"
+    )
     show_main_settings: BoolProperty(
-            name="Main Settings",
-            default=True,
-            description="Show settings"
-            )
+        name="Main Settings",
+        default=True,
+        description="Show settings"
+    )
     show_noise_settings: BoolProperty(
-            name="Noise Settings",
-            default=True,
-            description="Show noise settings"
-            )
+        name="Noise Settings",
+        default=True,
+        description="Show noise settings"
+    )
     show_displace_settings: BoolProperty(
-            name="Displace Settings",
-            default=True,
-            description="Show displace settings"
-            )
+        name="Displace Settings",
+        default=True,
+        description="Show displace settings"
+    )
     refresh: BoolProperty(
-            name="Refresh",
-            default=False,
-            description="Refresh"
-            )
+        name="Refresh",
+        default=False,
+        description="Refresh"
+    )
     auto_refresh: BoolProperty(
-            name="Auto",
-            default=True,
-            description="Automatic refresh"
-            )
+        name="Auto",
+        default=True,
+        description="Automatic refresh"
+    )
 
     @classmethod
     def poll(self, context):
@@ -567,12 +569,10 @@ class AntAddLandscape(bpy.types.Operator):
         draw_ant_displace(self, context, generate=True)
         draw_ant_water(self, context)
 
-
     def invoke(self, context, event):
         self.refresh = True
         return self.execute(context)
 
-
     def execute(self, context):
         if not self.refresh:
             return {'PASS_THROUGH'}
@@ -652,7 +652,7 @@ class AntAddLandscape(bpy.types.Operator):
             self.fx_height,
             self.fx_offset,
             self.fx_invert
-            ]
+        ]
 
         scene = context.scene
         vl = context.view_layer
@@ -661,37 +661,37 @@ class AntAddLandscape(bpy.types.Operator):
         if self.ant_terrain_name != "":
             new_name = self.ant_terrain_name
         else:
-            new_name  = "Landscape"
+            new_name = "Landscape"
 
         if self.sphere_mesh:
             # sphere
             verts, faces = sphere_gen(
-                    self.subdivision_y,
-                    self.subdivision_x,
-                    self.tri_face,
-                    self.mesh_size,
-                    ant_props,
-                    False,
-                    0.0
-                    )
+                self.subdivision_y,
+                self.subdivision_x,
+                self.tri_face,
+                self.mesh_size,
+                ant_props,
+                False,
+                0.0,
+            )
             new_ob = create_mesh_object(context, verts, [], faces, new_name)
             if self.remove_double:
                 new_ob.select_set(True)
-                bpy.ops.object.mode_set(mode = 'EDIT')
+                bpy.ops.object.mode_set(mode='EDIT')
                 bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
-                bpy.ops.object.mode_set(mode = 'OBJECT')
+                bpy.ops.object.mode_set(mode='OBJECT')
         else:
             # grid
             verts, faces = grid_gen(
-                    self.subdivision_x,
-                    self.subdivision_y,
-                    self.tri_face,
-                    self.mesh_size_x,
-                    self.mesh_size_y,
-                    ant_props,
-                    False,
-                    0.0
-                    )
+                self.subdivision_x,
+                self.subdivision_y,
+                self.tri_face,
+                self.mesh_size_x,
+                self.mesh_size_y,
+                ant_props,
+                False,
+                0.0,
+            )
             new_ob = create_mesh_object(context, verts, [], faces, new_name)
 
         new_ob.select_set(True)
@@ -712,33 +712,33 @@ class AntAddLandscape(bpy.types.Operator):
             if self.sphere_mesh:
                 # sphere
                 verts, faces = sphere_gen(
-                        self.subdivision_y,
-                        self.subdivision_x,
-                        self.tri_face,
-                        self.mesh_size,
-                        ant_props,
-                        self.water_plane,
-                        self.water_level
-                        )
-                wobj = create_mesh_object(context, verts, [], faces, new_name+"_plane")
+                    self.subdivision_y,
+                    self.subdivision_x,
+                    self.tri_face,
+                    self.mesh_size,
+                    ant_props,
+                    self.water_plane,
+                    self.water_level,
+                )
+                wobj = create_mesh_object(context, verts, [], faces, new_name + "_plane")
                 if self.remove_double:
                     wobj.select_set(True)
-                    bpy.ops.object.mode_set(mode = 'EDIT')
+                    bpy.ops.object.mode_set(mode='EDIT')
                     bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
-                    bpy.ops.object.mode_set(mode = 'OBJECT')
+                    bpy.ops.object.mode_set(mode='OBJECT')
             else:
                 # grid
                 verts, faces = grid_gen(
-                        2,
-                        2,
-                        self.tri_face,
-                        self.mesh_size_x,
-                        self.mesh_size_y,
-                        ant_props,
-                        self.water_plane,
-                        self.water_level
-                        )
-                wobj = create_mesh_object(context, verts, [], faces, new_name+"_plane")
+                    2,
+                    2,
+                    self.tri_face,
+                    self.mesh_size_x,
+                    self.mesh_size_y,
+                    ant_props,
+                    self.water_plane,
+                    self.water_level,
+                )
+                wobj = create_mesh_object(context, verts, [], faces, new_name + "_plane")
 
             wobj.select_set(True)
 
diff --git a/ant_landscape/ant_functions.py b/ant_landscape/ant_functions.py
index f63747aa6cb1210932637c796c7d620433842bf9..1337533cd916eb99f3d5dd94e18d329b691e23b4 100644
--- a/ant_landscape/ant_functions.py
+++ b/ant_landscape/ant_functions.py
@@ -9,16 +9,16 @@
 # import modules
 import bpy
 from bpy.props import (
-        BoolProperty,
-        FloatProperty,
-        StringProperty,
-        EnumProperty,
-        IntProperty,
-        PointerProperty,
-        )
+    BoolProperty,
+    FloatProperty,
+    StringProperty,
+    EnumProperty,
+    IntProperty,
+    PointerProperty,
+)
 from math import (
-        sin, cos, pi,
-        )
+    sin, cos, pi,
+)
 from .ant_noise import noise_gen
 
 # ------------------------------------------------------------
@@ -29,6 +29,7 @@ from .ant_noise import noise_gen
 
 from bpy_extras import object_utils
 
+
 def create_mesh_object(context, verts, edges, faces, name):
     # Create new mesh
     mesh = bpy.data.meshes.new(name)
@@ -45,7 +46,7 @@ def grid_gen(sub_d_x, sub_d_y, tri, meshsize_x, meshsize_y, props, water_plane,
     faces = []
     vappend = verts.append
     fappend = faces.append
-    for i in range (0, sub_d_x):
+    for i in range(0, sub_d_x):
         x = meshsize_x * (i / (sub_d_x - 1) - 1 / 2)
         for j in range(0, sub_d_y):
             y = meshsize_y * (j / (sub_d_y - 1) - 1 / 2)
@@ -53,7 +54,7 @@ def grid_gen(sub_d_x, sub_d_y, tri, meshsize_x, meshsize_y, props, water_plane,
                 z = noise_gen((x, y, 0), props)
             else:
                 z = water_level
-            vappend((x,y,z))
+            vappend((x, y, z))
 
             if i > 0 and j > 0:
                 A = i * sub_d_y + (j - 1)
@@ -89,8 +90,8 @@ def sphere_gen(sub_d_x, sub_d_y, tri, meshsize, props, water_plane, water_level)
             vappend(((u + u * h), (v + v * h), (w + w * h)))
 
     count = 0
-    for i in range (0, sub_d_y * (sub_d_x - 1)):
-        if count < sub_d_y - 1 :
+    for i in range(0, sub_d_y * (sub_d_x - 1)):
+        if count < sub_d_y - 1:
             A = i + 1
             B = i
             C = (i + sub_d_y)
@@ -115,7 +116,6 @@ class AntLandscapeRefresh(bpy.types.Operator):
     bl_description = "Refresh landscape with current settings"
     bl_options = {'REGISTER', 'UNDO'}
 
-
     @classmethod
     def poll(cls, context):
         ob = bpy.context.active_object
@@ -125,8 +125,8 @@ class AntLandscapeRefresh(bpy.types.Operator):
         # ant object items
         obj = bpy.context.active_object
 
-        bpy.ops.object.mode_set(mode = 'EDIT')
-        bpy.ops.object.mode_set(mode = 'OBJECT')
+        bpy.ops.object.mode_set(mode='EDIT')
+        bpy.ops.object.mode_set(mode='OBJECT')
 
         keys = obj.ant_landscape.keys()
         if keys:
@@ -158,13 +158,14 @@ class AntLandscapeRefresh(bpy.types.Operator):
 
 # ------------------------------------------------------------
 # Do regenerate
+
+
 class AntLandscapeRegenerate(bpy.types.Operator):
     bl_idname = "mesh.ant_landscape_regenerate"
     bl_label = "Regenerate"
     bl_description = "Regenerate landscape with current settings"
     bl_options = {'REGISTER', 'UNDO'}
 
-
     @classmethod
     def poll(cls, context):
         ob = bpy.context.active_object
@@ -172,7 +173,6 @@ class AntLandscapeRegenerate(bpy.types.Operator):
             return False
         return ob.ant_landscape
 
-
     def execute(self, context):
 
         view_layer = bpy.context.view_layer
@@ -192,32 +192,32 @@ class AntLandscapeRegenerate(bpy.types.Operator):
             if ob['sphere_mesh']:
                 # sphere
                 verts, faces = sphere_gen(
-                        ob['subdivision_y'],
-                        ob['subdivision_x'],
-                        ob['tri_face'],
-                        ob['mesh_size'],
-                        ant_props,
-                        False,
-                        0.0
-                        )
+                    ob['subdivision_y'],
+                    ob['subdivision_x'],
+                    ob['tri_face'],
+                    ob['mesh_size'],
+                    ant_props,
+                    False,
+                    0.0,
+                )
                 new_ob = create_mesh_object(context, verts, [], faces, new_name)
                 if ob['remove_double']:
                     new_ob.select_set(True)
-                    bpy.ops.object.mode_set(mode = 'EDIT')
+                    bpy.ops.object.mode_set(mode='EDIT')
                     bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
-                    bpy.ops.object.mode_set(mode = 'OBJECT')
+                    bpy.ops.object.mode_set(mode='OBJECT')
             else:
                 # grid
                 verts, faces = grid_gen(
-                        ob['subdivision_x'],
-                        ob['subdivision_y'],
-                        ob['tri_face'],
-                        ob['mesh_size_x'],
-                        ob['mesh_size_y'],
-                        ant_props,
-                        False,
-                        0.0
-                        )
+                    ob['subdivision_x'],
+                    ob['subdivision_y'],
+                    ob['tri_face'],
+                    ob['mesh_size_x'],
+                    ob['mesh_size_y'],
+                    ant_props,
+                    False,
+                    0.0,
+                )
                 new_ob = create_mesh_object(context, verts, [], faces, new_name)
 
             new_ob.select_set(True)
@@ -235,33 +235,33 @@ class AntLandscapeRegenerate(bpy.types.Operator):
                 if ob['sphere_mesh']:
                     # sphere
                     verts, faces = sphere_gen(
-                            ob['subdivision_y'],
-                            ob['subdivision_x'],
-                            ob['tri_face'],
-                            ob['mesh_size'],
-                            ant_props,
-                            ob['water_plane'],
-                            ob['water_level']
-                            )
-                    wobj = create_mesh_object(context, verts, [], faces, new_name+"_plane")
+                        ob['subdivision_y'],
+                        ob['subdivision_x'],
+                        ob['tri_face'],
+                        ob['mesh_size'],
+                        ant_props,
+                        ob['water_plane'],
+                        ob['water_level'],
+                    )
+                    wobj = create_mesh_object(context, verts, [], faces, new_name + "_plane")
                     if ob['remove_double']:
                         wobj.select_set(True)
-                        bpy.ops.object.mode_set(mode = 'EDIT')
+                        bpy.ops.object.mode_set(mode='EDIT')
                         bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
-                        bpy.ops.object.mode_set(mode = 'OBJECT')
+                        bpy.ops.object.mode_set(mode='OBJECT')
                 else:
                     # grid
                     verts, faces = grid_gen(
-                            2,
-                            2,
-                            ob['tri_face'],
-                            ob['mesh_size_x'],
-                            ob['mesh_size_y'],
-                            ant_props,
-                            ob['water_plane'],
-                            ob['water_level']
-                            )
-                    wobj = create_mesh_object(context, verts, [], faces, new_name+"_plane")
+                        2,
+                        2,
+                        ob['tri_face'],
+                        ob['mesh_size_x'],
+                        ob['mesh_size_y'],
+                        ant_props,
+                        ob['water_plane'],
+                        ob['water_level'],
+                    )
+                    wobj = create_mesh_object(context, verts, [], faces, new_name + "_plane")
 
                 wobj.select_set(True)
 
@@ -310,41 +310,39 @@ class AntVgSlopeMap(bpy.types.Operator):
     bl_options = {'REGISTER', 'UNDO'}
 
     z_method: EnumProperty(
-            name="Method:",
-            default='SLOPE_Z',
-            items=[
+        name="Method:",
+        default='SLOPE_Z',
+        items=[
                 ('SLOPE_Z', "Z Slope", "Slope for planar mesh"),
                 ('SLOPE_XYZ', "Sphere Slope", "Slope for spherical mesh")
-                ])
+        ])
     group_name: StringProperty(
-            name="Vertex Group Name:",
-            default="Slope",
-            description="Name"
-            )
+        name="Vertex Group Name:",
+        default="Slope",
+        description="Name"
+    )
     select_flat: BoolProperty(
-            name="Vert Select:",
-            default=True,
-            description="Select vertices on flat surface"
-            )
+        name="Vert Select:",
+        default=True,
+        description="Select vertices on flat surface"
+    )
     select_range: FloatProperty(
-            name="Vert Select Range:",
-            default=0.0,
-            min=0.0,
-            max=1.0,
-            description="Increase to select more vertices on slopes"
-            )
+        name="Vert Select Range:",
+        default=0.0,
+        min=0.0,
+        max=1.0,
+        description="Increase to select more vertices on slopes"
+    )
 
     @classmethod
     def poll(cls, context):
         ob = context.object
         return (ob and ob.type == 'MESH')
 
-
     def invoke(self, context, event):
         wm = context.window_manager
         return wm.invoke_props_dialog(self)
 
-
     def execute(self, context):
         message = "Popup Values: %d, %f, %s, %s" % \
             (self.select_flat, self.select_range, self.group_name, self.z_method)
@@ -421,7 +419,7 @@ def draw_ant_main(self, context, generate=True):
                 split.prop(self, "remove_double", toggle=True)
 
             box.prop(self, "ant_terrain_name")
-            box.prop_search(self, "land_material",  bpy.data, "materials")
+            box.prop_search(self, "land_material", bpy.data, "materials")
 
         col = box.column(align=True)
         col.prop(self, "subdivision_x")
@@ -450,11 +448,11 @@ def draw_ant_noise(self, context, generate=True):
         col = box.column(align=True)
         col.prop(self, "noise_offset_x")
         col.prop(self, "noise_offset_y")
-        if self.sphere_mesh == True or generate == False:
+        if self.sphere_mesh or generate == False:
             col.prop(self, "noise_offset_z")
         col.prop(self, "noise_size_x")
         col.prop(self, "noise_size_y")
-        if self.sphere_mesh == True or generate == False:
+        if self.sphere_mesh or generate == False:
             col.prop(self, "noise_size_z")
 
         col = box.column(align=True)
@@ -647,7 +645,7 @@ def draw_ant_displace(self, context, generate=True):
 
         if not generate:
             col = box.column(align=False)
-            col.prop_search(self, "vert_group",  bpy.context.object, "vertex_groups")
+            col.prop_search(self, "vert_group", bpy.context.object, "vertex_groups")
 
 
 def draw_ant_water(self, context):
@@ -657,7 +655,7 @@ def draw_ant_water(self, context):
     col.prop(self, "water_plane", toggle=True)
     if self.water_plane:
         col = box.column(align=True)
-        col.prop_search(self, "water_material",  bpy.data, "materials")
+        col.prop_search(self, "water_material", bpy.data, "materials")
         col = box.column()
         col.prop(self, "water_level")
 
@@ -744,8 +742,8 @@ from .utils import numexpr_available
 
 
 def availableVertexGroupsOrNone(self, context):
-    groups = [ ('None', 'None', 'None', 1) ]
-    return groups + [(name, name, name, n+1) for n,name in enumerate(context.active_object.vertex_groups.keys())]
+    groups = [('None', 'None', 'None', 1)]
+    return groups + [(name, name, name, n + 1) for n, name in enumerate(context.active_object.vertex_groups.keys())]
 
 
 class Eroder(bpy.types.Operator):
@@ -755,214 +753,190 @@ class Eroder(bpy.types.Operator):
     bl_options = {'REGISTER', 'UNDO', 'PRESET'}
 
     Iterations: IntProperty(
-            name="Iterations",
-            description="Number of overall iterations",
-            default=1,
-            min=1,
-            soft_max=100
-            )
+        name="Iterations",
+        description="Number of overall iterations",
+        default=1,
+        min=1,
+        soft_max=100
+    )
     IterRiver: IntProperty(
-            name="River Iterations",
-            description="Number of river iterations",
-            default=30,
-            min=1,
-            soft_max=1000
-            )
+        name="River Iterations",
+        description="Number of river iterations",
+        default=30,
+        min=1,
+        soft_max=1000
+    )
     IterAva: IntProperty(
-            name="Avalanche Iterations",
-            description="Number of avalanche iterations",
-            default=5,
-            min=1,
-            soft_max=10
-            )
+        name="Avalanche Iterations",
+        description="Number of avalanche iterations",
+        default=5,
+        min=1,
+        soft_max=10
+    )
     IterDiffuse: IntProperty(
-            name="Diffuse Iterations",
-            description="Number of diffuse iterations",
-            default=5,
-            min=1,
-            soft_max=10
-            )
+        name="Diffuse Iterations",
+        description="Number of diffuse iterations",
+        default=5,
+        min=1,
+        soft_max=10
+    )
     Ef: FloatProperty(
-            name="Rain on Plains",
-            description="1 gives equal rain across the terrain, 0 rains more at the mountain tops",
-            default=0.0,
-            min=0,
-            max=1
-            )
+        name="Rain on Plains",
+        description="1 gives equal rain across the terrain, 0 rains more at the mountain tops",
+        default=0.0,
+        min=0,
+        max=1
+    )
     Kd: FloatProperty(
-            name="Kd",
-            description="Thermal diffusion rate (1.0 is a fairly high rate)",
-            default=0.1,
-            min=0,
-            soft_max=100
-            )
+        name="Kd",
+        description="Thermal diffusion rate (1.0 is a fairly high rate)",
+        default=0.1,
+        min=0,
+        soft_max=100
+    )
     Kt: FloatProperty(
-            name="Kt",
-            description="Maximum stable talus angle",
-            default=radians(60),
-            min=0,
-            max=radians(90),
-            subtype='ANGLE'
-            )
+        name="Kt",
+        description="Maximum stable talus angle",
+        default=radians(60),
+        min=0,
+        max=radians(90),
+        subtype='ANGLE'
+    )
     Kr: FloatProperty(
-            name="Rain amount",
-            description="Total Rain amount",
-            default=.01,
-            min=0,
-            soft_max=1,
-            precision=3
-            )
+        name="Rain amount",
+        description="Total Rain amount",
+        default=.01,
+        min=0,
+        soft_max=1,
+        precision=3
+    )
     Kv: FloatProperty(
-            name="Rain variance",
-            description="Rain variance (0 is constant, 1 is uniform)",
-            default=0,
-            min=0,
-            max=1
-            )
+        name="Rain variance",
+        description="Rain variance (0 is constant, 1 is uniform)",
+        default=0,
+        min=0,
+        max=1
+    )
     userainmap: BoolProperty(
-            name="Use rain map",
-            description="Use active vertex group as a rain map",
-            default=True
-            )
+        name="Use rain map",
+        description="Use active vertex group as a rain map",
+        default=True
+    )
     Ks: FloatProperty(
-            name="Soil solubility",
-            description="Soil solubility - how quickly water quickly reaches saturation point",
-            default=0.5,
-            min=0,
-            soft_max=1
-            )
+        name="Soil solubility",
+        description="Soil solubility - how quickly water quickly reaches saturation point",
+        default=0.5,
+        min=0,
+        soft_max=1
+    )
     Kdep: FloatProperty(
-            name="Deposition rate",
-            description="Sediment deposition rate - how quickly silt is laid down once water stops flowing quickly",
-            default=0.1,
-            min=0,
-            soft_max=1
-            )
-    Kz: FloatProperty(name="Fluvial Erosion Rate",
-            description="Amount of sediment moved each main iteration - if 0, then rivers are formed but the mesh is not changed",
-            default=0.3,
-            min=0,
-            soft_max=20
-            )
+        name="Deposition rate",
+        description="Sediment deposition rate - how quickly silt is laid down once water stops flowing quickly",
+        default=0.1,
+        min=0,
+        soft_max=1
+    )
+    Kz: FloatProperty(
+        name="Fluvial Erosion Rate",
+        description="Amount of sediment moved each main iteration - if 0, then rivers are formed but the mesh is not changed",
+        default=0.3,
+        min=0,
+        soft_max=20)
     Kc: FloatProperty(
-            name="Carrying capacity",
-            description="Base sediment carrying capacity",
-            default=0.9,
-            min=0,
-            soft_max=1
-            )
+        name="Carrying capacity",
+        description="Base sediment carrying capacity",
+        default=0.9,
+        min=0,
+        soft_max=1
+    )
     Ka: FloatProperty(
-            name="Slope dependence",
-            description="Slope dependence of carrying capacity (not used)",
-            default=1.0,
-            min=0,
-            soft_max=2
-            )
+        name="Slope dependence",
+        description="Slope dependence of carrying capacity (not used)",
+        default=1.0,
+        min=0,
+        soft_max=2
+    )
     Kev: FloatProperty(
-            name="Evaporation",
-            description="Evaporation Rate per grid square in % - causes sediment to be dropped closer to the hills",
-            default=.5,
-            min=0,
-            soft_max=2
-            )
+        name="Evaporation",
+        description="Evaporation Rate per grid square in % - causes sediment to be dropped closer to the hills",
+        default=.5,
+        min=0,
+        soft_max=2
+    )
     numexpr: BoolProperty(
-            name="Numexpr",
-            description="Use numexpr module (if available)",
-            default=True
-            )
+        name="Numexpr",
+        description="Use numexpr module (if available)",
+        default=True
+    )
     Pd: FloatProperty(
-            name="Diffusion Amount",
-            description="Diffusion probability",
-            default=0.2,
-            min=0,
-            max=1
-            )
+        name="Diffusion Amount",
+        description="Diffusion probability",
+        default=0.2,
+        min=0,
+        max=1
+    )
     Pa: FloatProperty(
-            name="Avalanche Amount",
-            description="Avalanche amount",
-            default=0.5,
-            min=0,
-            max=1
-            )
+        name="Avalanche Amount",
+        description="Avalanche amount",
+        default=0.5,
+        min=0,
+        max=1
+    )
     Pw: FloatProperty(
-            name="River Amount",
-            description="Water erosion probability",
-            default=1,
-            min=0,
-            max=1
-            )
+        name="River Amount",
+        description="Water erosion probability",
+        default=1,
+        min=0,
+        max=1
+    )
     smooth: BoolProperty(
-            name="Smooth",
-            description="Set smooth shading",
-            default=True
-            )
+        name="Smooth",
+        description="Set smooth shading",
+        default=True
+    )
     showiterstats: BoolProperty(
-            name="Iteration Stats",
-            description="Show iteraration statistics",
-            default=False
-            )
-    showmeshstats: BoolProperty(name="Mesh Stats",
-            description="Show mesh statistics",
-            default=False
-            )
+        name="Iteration Stats",
+        description="Show iteraration statistics",
+        default=False
+    )
+    showmeshstats: BoolProperty(
+        name="Mesh Stats",
+        description="Show mesh statistics",
+        default=False
+    )
 
     stats = Stats()
-    counts= {}
+    counts = {}
+    maps = {
+        'rainmap': lambda g, r, c: g.rainmap[r, c],
+        'scree': lambda g, r, c: g.avalanced[r, c],
+        'avalanced': lambda g, r, c: -g.avalanced[r, c],
+        'water': lambda g, r, c: g.water[r, c] / g.watermax,
+        'scour': lambda g, r, c: g.scour[r, c] / max(g.scourmax, -g.scourmin),
+        'deposit': lambda g, r, c: g.scour[r, c] / min(-g.scourmax, g.scourmin),
+        'flowrate': lambda g, r, c: g.flowrate[r, c],
+        'sediment': lambda g, r, c: g.sediment[r, c],
+        'sedimentpct': lambda g, r, c: g.sedimentpct[r, c],
+        'capacity': lambda g, r, c: g.capacity[r, c]
+    }
 
     def execute(self, context):
 
         ob = context.active_object
-        me = ob.data
+        oldMesh = ob.data
         self.stats.reset()
-        try:
-            vgActive = ob.vertex_groups.active.name
-        except:
-            vgActive = "capacity"
-        print("ActiveGroup", vgActive)
-        try:
-            vg=ob.vertex_groups["rainmap"]
-        except:
-            vg=ob.vertex_groups.new(name="rainmap")
-        try:
-            vgscree=ob.vertex_groups["scree"]
-        except:
-            vgscree=ob.vertex_groups.new(name="scree")
-        try:
-            vgavalanced=ob.vertex_groups["avalanced"]
-        except:
-            vgavalanced=ob.vertex_groups.new(name="avalanced")
-        try:
-            vgw=ob.vertex_groups["water"]
-        except:
-            vgw=ob.vertex_groups.new(name="water")
-        try:
-            vgscour=ob.vertex_groups["scour"]
-        except:
-            vgscour=ob.vertex_groups.new(name="scour")
-        try:
-            vgdeposit=ob.vertex_groups["deposit"]
-        except:
-            vgdeposit=ob.vertex_groups.new(name="deposit")
-        try:
-            vgflowrate=ob.vertex_groups["flowrate"]
-        except:
-            vgflowrate=ob.vertex_groups.new(name="flowrate")
-        try:
-            vgsediment=ob.vertex_groups["sediment"]
-        except:
-            vgsediment=ob.vertex_groups.new(name="sediment")
-        try:
-            vgsedimentpct=ob.vertex_groups["sedimentpct"]
-        except:
-            vgsedimentpct=ob.vertex_groups.new(name="sedimentpct")
-        try:
-            vgcapacity=ob.vertex_groups["capacity"]
-        except:
-            vgcapacity=ob.vertex_groups.new(name="capacity")
-
-        g = Grid.fromBlenderMesh(me, vg, self.Ef)
-
-        me = bpy.data.meshes.new(me.name)
+        index_to_name = {}
+
+        for name in self.maps:
+            try:
+                ob.vertex_groups[name]
+            except:
+                ob.vertex_groups.new(name=name)
+            # Save a mapping from index to name, in case,
+            # the next iteration is different.
+            index_to_name[ob.vertex_groups[name].index] = name
+
+        g = Grid.fromBlenderMesh(oldMesh, ob.vertex_groups['rainmap'], self.Ef)
 
         self.counts['diffuse'] = 0
         self.counts['avalanche'] = 0
@@ -970,80 +944,60 @@ class Eroder(bpy.types.Operator):
         for i in range(self.Iterations):
             if self.IterRiver > 0:
                 for i in range(self.IterRiver):
-                    g.rivergeneration(self.Kr, self.Kv, self.userainmap, self.Kc, self.Ks, self.Kdep, self.Ka, self.Kev/100, 0,0,0,0, self.numexpr)
+                    g.rivergeneration(
+                        self.Kr,
+                        self.Kv,
+                        self.userainmap,
+                        self.Kc,
+                        self.Ks,
+                        self.Kdep,
+                        self.Ka,
+                        self.Kev / 100,
+                        0,
+                        0,
+                        0,
+                        0,
+                        self.numexpr,
+                    )
 
             if self.Kd > 0.0:
                 for k in range(self.IterDiffuse):
                     g.diffuse(self.Kd / 5, self.IterDiffuse, self.numexpr)
-                    self.counts['diffuse']+=1
+                    self.counts['diffuse'] += 1
 
             if self.Kt < radians(90) and self.Pa > 0:
                 for k in range(self.IterAva):
                     # since dx and dy are scaled to 1, tan(Kt) is the height for a given angle
                     g.avalanche(tan(self.Kt), self.IterAva, self.Pa, self.numexpr)
-                    self.counts['avalanche']+=1
+                    self.counts['avalanche'] += 1
             if self.Kz > 0:
-                g.fluvial_erosion(self.Kr, self.Kv, self.userainmap, self.Kc, self.Ks, self.Kz*50, self.Ka, 0,0,0,0, self.numexpr)
-                self.counts['water']+=1
-
-        g.toBlenderMesh(me)
-        ob.data = me
-
-        if vg:
-            for row in range(g.rainmap.shape[0]):
-                for col in range(g.rainmap.shape[1]):
-                    i = row * g.rainmap.shape[1] + col
-                    vg.add([i],g.rainmap[row,col],'ADD')
-        if vgscree:
-            for row in range(g.rainmap.shape[0]):
-                for col in range(g.rainmap.shape[1]):
-                    i = row * g.rainmap.shape[1] + col
-                    vgscree.add([i],g.avalanced[row,col],'ADD')
-        if vgavalanced:
-            for row in range(g.rainmap.shape[0]):
-                for col in range(g.rainmap.shape[1]):
-                    i = row * g.rainmap.shape[1] + col
-                    vgavalanced.add([i],-g.avalanced[row,col],'ADD')
-        if vgw:
-            for row in range(g.rainmap.shape[0]):
-                for col in range(g.rainmap.shape[1]):
-                    i = row * g.rainmap.shape[1] + col
-                    vgw.add([i],g.water[row,col]/g.watermax,'ADD')
-        if vgscour:
-            for row in range(g.rainmap.shape[0]):
-                for col in range(g.rainmap.shape[1]):
-                    i = row * g.rainmap.shape[1] + col
-                    vgscour.add([i],g.scour[row,col]/max(g.scourmax, -g.scourmin),'ADD')
-        if vgdeposit:
-            for row in range(g.rainmap.shape[0]):
-                for col in range(g.rainmap.shape[1]):
-                    i = row * g.rainmap.shape[1] + col
-                    vgdeposit.add([i],g.scour[row,col]/min(-g.scourmax, g.scourmin),'ADD')
-        if vgflowrate:
-            for row in range(g.rainmap.shape[0]):
-                for col in range(g.rainmap.shape[1]):
-                    i = row * g.rainmap.shape[1] + col
-                    vgflowrate.add([i],g.flowrate[row,col],'ADD')
-        if vgsediment:
-            for row in range(g.rainmap.shape[0]):
-                for col in range(g.rainmap.shape[1]):
-                    i = row * g.rainmap.shape[1] + col
-                    vgsediment.add([i],g.sediment[row,col],'ADD')
-        if vgsedimentpct:
-            for row in range(g.rainmap.shape[0]):
-                for col in range(g.rainmap.shape[1]):
-                    i = row * g.rainmap.shape[1] + col
-                    vgsedimentpct.add([i],g.sedimentpct[row,col],'ADD')
-        if vgcapacity:
-            for row in range(g.rainmap.shape[0]):
-                for col in range(g.rainmap.shape[1]):
-                    i = row * g.rainmap.shape[1] + col
-                    vgcapacity.add([i],g.capacity[row,col],'ADD')
-        try:
-            vg = ob.vertex_groups["vgActive"]
-        except:
-            vg = vgcapacity
-        ob.vertex_groups.active = vg
+                g.fluvial_erosion(self.Kr, self.Kv, self.userainmap, self.Kc, self.Ks,
+                                  self.Kz * 50, self.Ka, 0, 0, 0, 0, self.numexpr)
+                self.counts['water'] += 1
+
+        newMesh = bpy.data.meshes.new(oldMesh.name)
+        g.toBlenderMesh(newMesh)
+
+        # This empties ob.vertex_groups.
+        ob.data = newMesh
+
+        # Copy vertex groups from the old mesh.
+        for name in self.maps:
+            ob.vertex_groups.new(name=name)
+        for vert in oldMesh.vertices:
+            for group in vert.groups:
+                name = index_to_name[group.group]
+                if name:
+                    ob.vertex_groups[name].add([vert.index], group.weight, 'REPLACE')
+
+        # Add the new data.
+        for row in range(g.rainmap.shape[0]):
+            for col in range(g.rainmap.shape[1]):
+                i = row * g.rainmap.shape[1] + col
+                for name, fn in self.maps.items():
+                    ob.vertex_groups[name].add([i], fn(g, row, col), 'ADD')
+
+        ob.vertex_groups.active = ob.vertex_groups['capacity']
 
         if self.smooth:
             bpy.ops.object.shade_smooth()
@@ -1054,11 +1008,10 @@ class Eroder(bpy.types.Operator):
 
         return {'FINISHED'}
 
-
-    def draw(self,context):
+    def draw(self, context):
         layout = self.layout
 
-        layout.operator('screen.repeat_last', text="Repeat", icon='FILE_REFRESH' )
+        layout.operator('screen.repeat_last', text="Repeat", icon='FILE_REFRESH')
 
         layout.prop(self, 'Iterations')
 
@@ -1089,4 +1042,4 @@ class Eroder(bpy.types.Operator):
 
         col.prop(self, 'Ef')
 
-        layout.prop(self,'smooth')
+        layout.prop(self, 'smooth')
diff --git a/ant_landscape/ant_noise.py b/ant_landscape/ant_noise.py
index 7eec29b66fe761fa3e9e02929c930e0772d8da6f..9385d4b43935fdc98081183baa3ee840990bc337 100644
--- a/ant_landscape/ant_noise.py
+++ b/ant_landscape/ant_noise.py
@@ -5,23 +5,23 @@
 
 import bpy
 from mathutils.noise import (
-        seed_set,
-        noise,
-        turbulence,
-        turbulence_vector,
-        fractal,
-        hybrid_multi_fractal,
-        multi_fractal,
-        ridged_multi_fractal,
-        hetero_terrain,
-        random_unit_vector,
-        variable_lacunarity,
-        voronoi,
-        )
+    seed_set,
+    noise,
+    turbulence,
+    turbulence_vector,
+    fractal,
+    hybrid_multi_fractal,
+    multi_fractal,
+    ridged_multi_fractal,
+    hetero_terrain,
+    random_unit_vector,
+    variable_lacunarity,
+    voronoi,
+)
 from math import (
-        floor, sqrt,
-        sin, cos, pi,
-        )
+    floor, sqrt,
+    sin, cos, pi,
+)
 
 noise_basis_default = "BLENDER"
 noise_basis = [
@@ -39,6 +39,8 @@ noise_basis = [
 
 # ------------------------------------------------------------
 # Height scale:
+
+
 def Height_Scale(input, iscale, offset, invert):
     if invert != 0:
         return (1.0 - input) * iscale + offset
@@ -176,14 +178,15 @@ def vlnTurbMode(coords, distort, basis, vlbasis, hardnoise):
 def vl_noise_turbulence(coords, distort, depth, basis, vlbasis, hardnoise, amp, freq):
     x, y, z = coords
     value = vlnTurbMode(coords, distort, basis, vlbasis, hardnoise)
-    i=0
+    i = 0
     for i in range(depth):
-        i+=1
-        value += vlnTurbMode((x * (freq * i), y * (freq * i), z * (freq * i)), distort, basis, vlbasis, hardnoise) * (amp * 0.5 / i)
+        i += 1
+        value += vlnTurbMode((x * (freq * i), y * (freq * i), z * (freq * i)),
+                             distort, basis, vlbasis, hardnoise) * (amp * 0.5 / i)
     return value
 
 
-## duo_multiFractal:
+# duo_multiFractal:
 def double_multiFractal(coords, H, lacunarity, octaves, offset, gain, basis, vlbasis):
     x, y, z = coords
     n1 = multi_fractal((x * 1.5 + 1, y * 1.5 + 1, z * 1.5 + 1), 1.0, 1.0, 1.0, noise_basis=basis) * (offset * 0.5)
@@ -191,27 +194,27 @@ def double_multiFractal(coords, H, lacunarity, octaves, offset, gain, basis, vlb
     return (n1 * n1 + n2 * n2) * 0.5
 
 
-## distorted_heteroTerrain:
+# distorted_heteroTerrain:
 def distorted_heteroTerrain(coords, H, lacunarity, octaves, offset, distort, basis, vlbasis):
     x, y, z = coords
     h1 = (hetero_terrain((x, y, z), 1.0, 2.0, 1.0, 1.0, noise_basis=basis) * 0.5)
-    d =  h1 * distort
+    d = h1 * distort
     h2 = (hetero_terrain((x + d, y + d, z + d), H, lacunarity, octaves, offset, noise_basis=vlbasis) * 0.25)
     return (h1 * h1 + h2 * h2) * 0.5
 
 
-## SlickRock:
+# SlickRock:
 def slick_rock(coords, H, lacunarity, octaves, offset, gain, distort, basis, vlbasis):
     x, y, z = coords
-    n = multi_fractal((x,y,z), 1.0, 2.0, 2.0, noise_basis=basis) * distort * 0.25
+    n = multi_fractal((x, y, z), 1.0, 2.0, 2.0, noise_basis=basis) * distort * 0.25
     r = ridged_multi_fractal((x + n, y + n, z + n), H, lacunarity, octaves, offset + 0.1, gain * 2, noise_basis=vlbasis)
     return (n + (n * r)) * 0.5
 
 
-## vlhTerrain
+# vlhTerrain
 def vl_hTerrain(coords, H, lacunarity, octaves, offset, basis, vlbasis, distort):
     x, y, z = coords
-    ht = hetero_terrain((x, y, z), H, lacunarity, octaves, offset, noise_basis=basis ) * 0.25
+    ht = hetero_terrain((x, y, z), H, lacunarity, octaves, offset, noise_basis=basis) * 0.25
     vl = ht * variable_lacunarity((x, y, z), distort, noise_type1=basis, noise_type2=vlbasis) * 0.5 + 0.5
     return vl * ht
 
@@ -219,13 +222,14 @@ def vl_hTerrain(coords, H, lacunarity, octaves, offset, basis, vlbasis, distort)
 # another turbulence
 def ant_turbulence(coords, depth, hardnoise, nbasis, amp, freq, distortion):
     x, y, z = coords
-    t = turbulence_vector((x/2, y/2, z/2), depth, 0, noise_basis=nbasis, amplitude_scale=amp, frequency_scale=freq) * 0.5 * distortion
+    t = turbulence_vector((x / 2, y / 2, z / 2), depth, 0, noise_basis=nbasis,
+                          amplitude_scale=amp, frequency_scale=freq) * 0.5 * distortion
     return turbulence((t[0], t[1], t[2]), 2, hardnoise, noise_basis="VORONOI_F1") * 0.5 + 0.5
 
 
 # rocks noise
 def rocks_noise(coords, depth, hardnoise, nbasis, distortion):
-    x,y,z = coords
+    x, y, z = coords
     p = turbulence((x, y, z), 4, 0, noise_basis='BLENDER') * 0.125 * distortion
     xx, yy, zz = x, y, z
     a = turbulence((xx + p, yy + p, zz), 2, 0, noise_basis='VORONOI_F2F1')
@@ -269,16 +273,18 @@ def planet_noise(coords, oct=6, hard=0, noisebasis='PERLIN_ORIGINAL', nabla=0.00
     return (zdy - ydz), (zdx - xdz), (ydx - xdy)
 
 
-###----------------------------------------------------------------------
+# ----------------------------------------------------------------------
 # v.1.04 Effect functions:
 
 def maximum(a, b):
-    if (a > b): b = a
+    if (a > b):
+        b = a
     return b
 
 
 def minimum(a, b):
-    if (a < b): b = a
+    if (a < b):
+        b = a
     return b
 
 
@@ -286,38 +292,38 @@ def Mix_Modes(a, b, mixfactor, mode):
     mode = int(mode)
     a = a * (1.0 - mixfactor)
     b = b * (1.0 + mixfactor)
-    #1  mix
+    # 1  mix
     if mode == 0:
         return (a * (1.0 - 0.5) + b * 0.5)
-    #2  add
+    # 2  add
     elif mode == 1:
         return (a + b)
-    #3  sub.
+    # 3  sub.
     elif mode == 2:
         return (a - b)
-    #4  mult.
+    # 4  mult.
     elif mode == 3:
         return (a * b)
-    #5  abs diff.
+    # 5  abs diff.
     elif mode == 4:
         return (abs(a - b))
-    #6  screen
+    # 6  screen
     elif mode == 5:
         return 1.0 - ((1.0 - a) * (1.0 - b) / 1.0)
-    #7  addmodulo
+    # 7  addmodulo
     elif mode == 6:
         return (a + b) % 1.0
-    #8  min.
+    # 8  min.
     elif mode == 7:
         return minimum(a, b)
-    #9  max.
+    # 9  max.
     elif mode == 8:
         return maximum(a, b)
     else:
         return 0
 
 
-Bias_Types  = [sin_bias, cos_bias, tri_bias, saw_bias, no_bias]
+Bias_Types = [sin_bias, cos_bias, tri_bias, saw_bias, no_bias]
 Sharp_Types = [soft, sharp, sharper]
 
 
@@ -337,46 +343,47 @@ def Effect_Basis_Function(coords, type, bias):
     iscale = 1.0
     offset = 0.0
 
-    ## gradient:
+    # gradient:
     if type == 1:
         effect = offset + iscale * (Bias_Types[bias](x + y))
-    ## waves / bumps:
+    # waves / bumps:
     elif type == 2:
         effect = offset + iscale * 0.5 * (Bias_Types[bias](x * pi) + Bias_Types[bias](y * pi))
-    ## zigzag:
+    # zigzag:
     elif type == 3:
         effect = offset + iscale * Bias_Types[bias](offset + iscale * sin(x * pi + sin(y * pi)))
-    ## wavy:
+    # wavy:
     elif type == 4:
         effect = offset + iscale * (Bias_Types[bias](cos(x) + sin(y) + cos(x * 2 + y * 2) - sin(-x * 4 + y * 4)))
-    ## sine bump:
+    # sine bump:
     elif type == 5:
-        effect =   offset + iscale * 1 - Bias_Types[bias]((sin(x * pi) + sin(y * pi)))
-    ## dots:
+        effect = offset + iscale * 1 - Bias_Types[bias]((sin(x * pi) + sin(y * pi)))
+    # dots:
     elif type == 6:
         effect = offset + iscale * (Bias_Types[bias](x * pi * 2) * Bias_Types[bias](y * pi * 2)) - 0.5
-    ## rings:
+    # rings:
     elif type == 7:
-        effect = offset + iscale * (Bias_Types[bias ](1.0 - (x * x + y * y)))
-    ## spiral:
+        effect = offset + iscale * (Bias_Types[bias](1.0 - (x * x + y * y)))
+    # spiral:
     elif type == 8:
-        effect = offset + iscale * Bias_Types[bias]( (x * sin(x * x + y * y) + y * cos(x * x + y * y)) / (x**2 + y**2 + 0.5)) * 2
-    ## square / piramide:
+        effect = offset + iscale * \
+            Bias_Types[bias]((x * sin(x * x + y * y) + y * cos(x * x + y * y)) / (x**2 + y**2 + 0.5)) * 2
+    # square / piramide:
     elif type == 9:
         effect = offset + iscale * Bias_Types[bias](1.0 - sqrt((x * x)**10 + (y * y)**10)**0.1)
-    ## blocks:
+    # blocks:
     elif type == 10:
-        effect = (0.5 - max(Bias_Types[bias](x * pi) , Bias_Types[bias](y * pi)))
+        effect = (0.5 - max(Bias_Types[bias](x * pi), Bias_Types[bias](y * pi)))
         if effect > 0.0:
             effect = 1.0
         effect = offset + iscale * effect
-    ## grid:
+    # grid:
     elif type == 11:
         effect = (0.025 - min(Bias_Types[bias](x * pi), Bias_Types[bias](y * pi)))
         if effect > 0.0:
             effect = 1.0
         effect = offset + iscale * effect
-    ## tech:
+    # tech:
     elif type == 12:
         a = max(Bias_Types[bias](x * pi), Bias_Types[bias](y * pi))
         b = max(Bias_Types[bias](x * pi * 2 + 2), Bias_Types[bias](y * pi * 2 + 2))
@@ -384,51 +391,51 @@ def Effect_Basis_Function(coords, type, bias):
         if effect > 0.5:
             effect = 1.0
         effect = offset + iscale * effect
-    ## crackle:
+    # crackle:
     elif type == 13:
         t = turbulence((x, y, 0), 6, 0, noise_basis="BLENDER") * 0.25
         effect = variable_lacunarity((x, y, t), 0.25, noise_type2='VORONOI_CRACKLE')
         if effect > 0.5:
             effect = 0.5
         effect = offset + iscale * effect
-    ## sparse cracks noise:
+    # sparse cracks noise:
     elif type == 14:
         effect = 2.5 * abs(noise((x, y, 0), noise_basis="PERLIN_ORIGINAL")) - 0.1
         if effect > 0.25:
             effect = 0.25
         effect = offset + iscale * (effect * 2.5)
-    ## shattered rock noise:
+    # shattered rock noise:
     elif type == 15:
         effect = 0.5 + noise((x, y, 0), noise_basis="VORONOI_F2F1")
         if effect > 0.75:
             effect = 0.75
         effect = offset + iscale * effect
-    ## lunar noise:
+    # lunar noise:
     elif type == 16:
         effect = 0.25 + 1.5 * voronoi((x, y, 0), distance_metric='DISTANCE_SQUARED')[0][0]
         if effect > 0.5:
             effect = 0.5
         effect = offset + iscale * effect * 2
-    ## cosine noise:
+    # cosine noise:
     elif type == 17:
         effect = cos(5 * noise((x, y, 0), noise_basis="BLENDER"))
         effect = offset + iscale * (effect * 0.5)
-    ## spikey noise:
+    # spikey noise:
     elif type == 18:
         n = 0.5 + 0.5 * turbulence((x * 5, y * 5, 0), 8, 0, noise_basis="BLENDER")
         effect = ((n * n)**5)
         effect = offset + iscale * effect
-    ## stone noise:
+    # stone noise:
     elif type == 19:
         effect = offset + iscale * (noise((x * 2, y * 2, 0), noise_basis="BLENDER") * 1.5 - 0.75)
-    ## Flat Turb:
+    # Flat Turb:
     elif type == 20:
         t = turbulence((x, y, 0), 6, 0, noise_basis="BLENDER")
         effect = t * 2.0
         if effect > 0.25:
             effect = 0.25
         effect = offset + iscale * effect
-    ## Flat Voronoi:
+    # Flat Voronoi:
     elif type == 21:
         t = 1 - voronoi((x, y, 0), distance_metric='DISTANCE_SQUARED')[0][0]
         effect = t * 2 - 1.5
@@ -448,19 +455,19 @@ def Effect_Basis_Function(coords, type, bias):
 def Effect_Function(coords, type, bias, turb, depth, frequency, amplitude):
 
     x, y, z = coords
-    ## turbulence:
+    # turbulence:
     if turb > 0.0:
-        t = turb * ( 0.5 + 0.5 * turbulence(coords, 6, 0, noise_basis="BLENDER"))
+        t = turb * (0.5 + 0.5 * turbulence(coords, 6, 0, noise_basis="BLENDER"))
         x = x + t
         y = y + t
         z = z + t
 
     result = Effect_Basis_Function((x, y, z), type, bias) * amplitude
-    ## fractalize:
+    # fractalize:
     if depth != 0:
-        i=0
+        i = 0
         for i in range(depth):
-            i+=1
+            i += 1
             x *= frequency
             y *= frequency
             result += Effect_Basis_Function((x, y, z), type, bias) * amplitude / i
@@ -578,20 +585,21 @@ def noise_gen(coords, props):
         value = fractal(ncoords, dimension, lacunarity, depth, noise_basis=nbasis)
 
     elif ntype in [5, 'turbulence_vector']:
-        value = turbulence_vector(ncoords, depth, hardnoise, noise_basis=nbasis, amplitude_scale=amp, frequency_scale=freq)[0]
+        value = turbulence_vector(ncoords, depth, hardnoise, noise_basis=nbasis,
+                                  amplitude_scale=amp, frequency_scale=freq)[0]
 
     elif ntype in [6, 'variable_lacunarity']:
         value = variable_lacunarity(ncoords, distortion, noise_type1=nbasis, noise_type2=vlbasis)
 
     elif ntype in [7, 'marble_noise']:
         value = marble_noise(
-                        (ncoords[0] - origin_x + x_offset),
-                        (ncoords[1] - origin_y + y_offset),
-                        (ncoords[2] - origin_z + z_offset),
-                        (origin[0] + x_offset, origin[1] + y_offset, origin[2] + z_offset), nsize,
-                        marbleshape, marblebias, marblesharpnes,
-                        distortion, depth, hardnoise, nbasis, amp, freq
-                        )
+            (ncoords[0] - origin_x + x_offset),
+            (ncoords[1] - origin_y + y_offset),
+            (ncoords[2] - origin_z + z_offset),
+            (origin[0] + x_offset, origin[1] + y_offset, origin[2] + z_offset), nsize,
+            marbleshape, marblebias, marblesharpnes,
+            distortion, depth, hardnoise, nbasis, amp, freq
+        )
     elif ntype in [8, 'shattered_hterrain']:
         value = shattered_hterrain(ncoords, dimension, lacunarity, depth, offset, distortion, nbasis)
 
@@ -617,7 +625,7 @@ def noise_gen(coords, props):
         value = rocks_noise(ncoords, depth, hardnoise, nbasis, distortion)
 
     elif ntype in [16, 'slick_rock']:
-        value = slick_rock(ncoords,dimension, lacunarity, depth, offset, gain, distortion, nbasis, vlbasis)
+        value = slick_rock(ncoords, dimension, lacunarity, depth, offset, gain, distortion, nbasis, vlbasis)
 
     elif ntype in [17, 'planet_noise']:
         value = planet_noise(ncoords, depth, hardnoise, nbasis)[2] * 0.5 + 0.5
@@ -632,7 +640,7 @@ def noise_gen(coords, props):
 
     # Effect mix
     val = value
-    if fx_type in [0,"0"]:
+    if fx_type in [0, "0"]:
         fx_mixfactor = -1.0
         fxval = val
     else:
@@ -649,14 +657,15 @@ def noise_gen(coords, props):
     if not sphere:
         if falloff:
             ratio_x, ratio_y = abs(x) * 2 / meshsize_x, abs(y) * 2 / meshsize_y
-            fallofftypes = [0,
-                            sqrt(ratio_y**falloffsize_y),
-                            sqrt(ratio_x**falloffsize_x),
-                            sqrt(ratio_x**falloffsize_x + ratio_y**falloffsize_y)
-                           ]
+            fallofftypes = [
+                0,
+                sqrt(ratio_y**falloffsize_y),
+                sqrt(ratio_x**falloffsize_x),
+                sqrt(ratio_x**falloffsize_x + ratio_y**falloffsize_y)
+            ]
             dist = fallofftypes[falloff]
             value -= edge_level
-            if(dist < 1.0):
+            if dist < 1.0:
                 dist = (dist * dist * (3 - 2 * dist))
                 value = (value - value * dist) + edge_level
             else:
@@ -682,11 +691,11 @@ def noise_gen(coords, props):
 
         elif stratatype in [4, "4"]:
             strata = strata / height
-            value = int( value * strata ) * 1.0 / strata
+            value = int(value * strata) * 1.0 / strata
 
         elif stratatype in [5, "5"]:
             strata = strata / height
-            steps = (int( value * strata ) * 1.0 / strata)
+            steps = (int(value * strata) * 1.0 / strata)
             value = (value * (1.0 - 0.5) + steps * 0.5)
 
     # Clamp height min max
diff --git a/ant_landscape/eroder.py b/ant_landscape/eroder.py
index 558d2edb0435b23486174c2259b19d83adaf6cec..ae1326df45de576a3c54c005d125c39d64796b55 100644
--- a/ant_landscape/eroder.py
+++ b/ant_landscape/eroder.py
@@ -43,7 +43,6 @@ class Grid:
         self.sedmax = 1.0
         self.scourmin = 1.0
 
-
     def init_water_and_sediment(self):
         if self.water is None:
             self.water = np.zeros(self.center.shape, dtype=np.single)
@@ -60,39 +59,34 @@ class Grid:
         if self.avalanced is None:
             self.avalanced = np.zeros(self.center.shape, dtype=np.single)
 
-
     def __str__(self):
         return ''.join(self.__str_iter__(fmt="%.3f"))
 
-
     def __str_iter__(self, fmt):
         for row in self.center[::]:
-            values=[]
+            values = []
             for v in row:
-                values.append(fmt%v)
-            yield  ' '.join(values) + '\n'
-
+                values.append(fmt % v)
+            yield ' '.join(values) + '\n'
 
     @staticmethod
     def fromFile(filename):
         if filename == '-':
             filename = sys.stdin
-        g=Grid()
-        g.center=np.loadtxt(filename,np.single)
+        g = Grid()
+        g.center = np.loadtxt(filename, np.single)
         return g
 
-
     def toFile(self, filename, fmt="%.3f"):
-        if filename == '-' :
+        if filename == '-':
             filename = sys.stdout.fileno()
-        with open(filename,"w") as f:
+        with open(filename, "w") as f:
             for line in self.__str_iter__(fmt):
                 f.write(line)
 
-
-    def raw(self,format="%.3f"):
-        fstr=format+" "+ format+" "+ format+" "
-        a=self.center / self.zscale
+    def raw(self, format="%.3f"):
+        fstr = format + " " + format + " " + format + " "
+        a = self.center / self.zscale
         minx = 0.0 if self.minx is None else self.minx
         miny = 0.0 if self.miny is None else self.miny
         maxx = 1.0 if self.maxx is None else self.maxx
@@ -105,21 +99,19 @@ class Grid:
             for col in range(a.shape[1] - 1):
                 col0 = minx + col * dx
                 col1 = col0 + dx
-                yield (fstr%(row0 ,col0 ,a[row  ][col  ])+
-                       fstr%(row0 ,col1 ,a[row  ][col+1])+
-                       fstr%(row1 ,col0 ,a[row+1][col  ])+"\n")
-                yield (fstr%(row0 ,col1 ,a[row  ][col+1])+
-                       fstr%(row1 ,col0 ,a[row+1][col  ])+
-                       fstr%(row1 ,col1 ,a[row+1][col+1])+"\n")
-
+                yield (fstr % (row0, col0, a[row][col]) +
+                       fstr % (row0, col1, a[row][col + 1]) +
+                       fstr % (row1, col0, a[row + 1][col]) + "\n")
+                yield (fstr % (row0, col1, a[row][col + 1]) +
+                       fstr % (row1, col0, a[row + 1][col]) +
+                       fstr % (row1, col1, a[row + 1][col + 1]) + "\n")
 
     def toRaw(self, filename, infomap=None):
-        with open(filename if type(filename) == str else sys.stdout.fileno() , "w") as f:
+        with open(filename if type(filename) == str else sys.stdout.fileno(), "w") as f:
             f.writelines(self.raw())
         if infomap:
-            with open(os.path.splitext(filename)[0]+".inf" if type(filename) == str else sys.stdout.fileno() , "w") as f:
-                f.writelines("\n".join("%-15s: %s"%t for t in sorted(infomap.items())))
-
+            with open(os.path.splitext(filename)[0] + ".inf" if type(filename) == str else sys.stdout.fileno(), "w") as f:
+                f.writelines("\n".join("%-15s: %s" % t for t in sorted(infomap.items())))
 
     @staticmethod
     def fromRaw(filename):
@@ -128,15 +120,14 @@ class Grid:
         """
         g = Grid.fromFile(filename)
         # we assume tris and an axis aligned grid
-        g.center = np.reshape(g.center,(-1,3))
+        g.center = np.reshape(g.center, (-1, 3))
         g._sort()
         return g
 
-
     def _sort(self, expfact):
         # keep unique vertices only by creating a set and sort first on x then on y coordinate
         # using rather slow python sort but couldn't wrap my head around np.lexsort
-        verts = sorted(list({ tuple(t) for t in self.center[::] }))
+        verts = sorted(list({tuple(t) for t in self.center[::]}))
         x = set(c[0] for c in verts)
         y = set(c[1] for c in verts)
         nx = len(x)
@@ -145,41 +136,40 @@ class Grid:
         self.maxx = max(x)
         self.miny = min(y)
         self.maxy = max(y)
-        xscale = (self.maxx-self.minx)/(nx-1)
-        yscale = (self.maxy-self.miny)/(ny-1)
+        xscale = (self.maxx - self.minx) / (nx - 1)
+        yscale = (self.maxy - self.miny) / (ny - 1)
         # note: a purely flat plane cannot be scaled
-        if (yscale != 0.0) and (abs(xscale/yscale) - 1.0 > 1e-3):
-            raise ValueError("Mesh spacing not square %d x %d  %.4f x %4.f"%(nx,ny,xscale,yscale))
+        if (yscale != 0.0) and (abs(xscale / yscale) - 1.0 > 1e-3):
+            raise ValueError("Mesh spacing not square %d x %d  %.4f x %4.f" % (nx, ny, xscale, yscale))
         self.zscale = 1.0
-        if abs(yscale) > 1e-6 :
-            self.zscale = 1.0/yscale
+        if abs(yscale) > 1e-6:
+            self.zscale = 1.0 / yscale
 
         # keep just the z-values and null any offset
-        # we might catch a reshape error that will occur if nx*ny != # of vertices (if we are not dealing with a heightfield but with a mesh with duplicate x,y coords, like an axis aligned cube
-        self.center = np.array([c[2] for c in verts],dtype=np.single).reshape(nx,ny)
-        self.center = (self.center-np.amin(self.center))*self.zscale
+        # we might catch a reshape error that will occur if nx*ny != # of vertices
+        # (if we are not dealing with a heightfield but with a mesh with duplicate
+        # x,y coords, like an axis aligned cube
+        self.center = np.array([c[2] for c in verts], dtype=np.single).reshape(nx, ny)
+        self.center = (self.center - np.amin(self.center)) * self.zscale
         if self.rainmap is not None:
             rmscale = np.max(self.center)
-            self.rainmap = expfact + (1-expfact)*(self.center/rmscale)
-
+            self.rainmap = expfact + (1 - expfact) * (self.center / rmscale)
 
     @staticmethod
     def fromBlenderMesh(me, vg, expfact):
         g = Grid()
-        g.center = np.asarray(list(tuple(v.co) for v in me.vertices), dtype=np.single )
+        g.center = np.asarray(list(tuple(v.co) for v in me.vertices), dtype=np.single)
         g.rainmap = None
         if vg is not None:
             for v in me.vertices:
-                vg.add([v.index],0.0,'ADD')
-            g.rainmap=np.asarray(list( (v.co[0], v.co[1], vg.weight(v.index)) for v in me.vertices), dtype=np.single )
+                vg.add([v.index], 0.0, 'ADD')
+            g.rainmap = np.asarray(list((v.co[0], v.co[1], vg.weight(v.index)) for v in me.vertices), dtype=np.single)
         g._sort(expfact)
         return g
 
-
     def setrainmap(self, rainmap):
         self.rainmap = rainmap
 
-
     def _verts(self, surface):
         a = surface / self.zscale
         minx = 0.0 if self.minx is None else self.minx
@@ -192,90 +182,81 @@ class Grid:
             row0 = miny + row * dy
             for col in range(a.shape[1]):
                 col0 = minx + col * dx
-                yield (row0 ,col0 ,a[row  ][col  ])
-
+                yield (row0, col0, a[row][col])
 
     def _faces(self):
         nrow, ncol = self.center.shape
-        for row in range(nrow-1):
-            for col in range(ncol-1):
-              vi = row * ncol + col
-              yield (vi, vi+ncol, vi+1)
-              yield (vi+1, vi+ncol, vi+ncol+1)
-
+        for row in range(nrow - 1):
+            for col in range(ncol - 1):
+                vi = row * ncol + col
+                yield (vi, vi + ncol, vi + 1)
+                yield (vi + 1, vi + ncol, vi + ncol + 1)
 
     def toBlenderMesh(self, me):
         # pass me as argument so that we don't need to import bpy and create a dependency
-        # the docs state that from_pydata takes iterators as arguments but it will fail with generators because it does len(arg)
-        me.from_pydata(list(self._verts(self.center)),[],list(self._faces()))
-
+        # the docs state that from_pydata takes iterators as arguments but it will
+        # fail with generators because it does len(arg)
+        me.from_pydata(list(self._verts(self.center)), [], list(self._faces()))
 
     def toWaterMesh(self, me):
         # pass me as argument so that we don't need to import bpy and create a dependency
-        # the docs state that from_pydata takes iterators as arguments but it will fail with generators because it does len(arg)
-        me.from_pydata(list(self._verts(self.water)),[],list(self._faces()))
-
+        # the docs state that from_pydata takes iterators as arguments but it will
+        # fail with generators because it does len(arg)
+        me.from_pydata(list(self._verts(self.water)), [], list(self._faces()))
 
     def peak(self, value=1):
-        nx,ny = self.center.shape
-        self.center[int(nx/2),int(ny/2)] += value
-
+        nx, ny = self.center.shape
+        self.center[int(nx / 2), int(ny / 2)] += value
 
     def shelf(self, value=1):
-        nx,ny = self.center.shape
-        self.center[:nx/2] += value
-
+        nx, ny = self.center.shape
+        self.center[:nx / 2] += value
 
     def mesa(self, value=1):
-        nx,ny = self.center.shape
-        self.center[nx/4:3*nx/4,ny/4:3*ny/4] += value
-
+        nx, ny = self.center.shape
+        self.center[nx / 4:3 * nx / 4, ny / 4:3 * ny / 4] += value
 
     def random(self, value=1):
-        self.center += np.random.random_sample(self.center.shape)*value
-
+        self.center += np.random.random_sample(self.center.shape) * value
 
     def neighborgrid(self):
-        self.up = np.roll(self.center,-1,0)
-        self.down = np.roll(self.center,1,0)
-        self.left = np.roll(self.center,-1,1)
-        self.right = np.roll(self.center,1,1)
-
+        self.up = np.roll(self.center, -1, 0)
+        self.down = np.roll(self.center, 1, 0)
+        self.left = np.roll(self.center, -1, 1)
+        self.right = np.roll(self.center, 1, 1)
 
     def zeroedge(self, quantity=None):
         c = self.center if quantity is None else quantity
-        c[0,:] = 0
-        c[-1,:] = 0
-        c[:,0] = 0
-        c[:,-1] = 0
-
+        c[0, :] = 0
+        c[-1, :] = 0
+        c[:, 0] = 0
+        c[:, -1] = 0
 
     def diffuse(self, Kd, IterDiffuse, numexpr):
         self.zeroedge()
-        c = self.center[1:-1,1:-1]
-        up = self.center[ :-2,1:-1]
-        down = self.center[2:  ,1:-1]
+        c = self.center[1:-1, 1:-1]
+        up = self.center[:-2, 1:-1]
+        down = self.center[2:, 1:-1]
         left = self.center[1:-1, :-2]
-        right = self.center[1:-1,2:  ]
+        right = self.center[1:-1, 2:]
         if(numexpr and numexpr_available):
-            self.center[1:-1,1:-1] = ne.evaluate('c + Kd * (up + down + left + right - 4.0 * c)')
+            self.center[1:-1, 1:-1] = ne.evaluate('c + Kd * (up + down + left + right - 4.0 * c)')
         else:
-            self.center[1:-1,1:-1] = c + (Kd/IterDiffuse) * (up + down + left + right - 4.0 * c)
+            self.center[1:-1, 1:-1] = c + (Kd / IterDiffuse) * (up + down + left + right - 4.0 * c)
         self.maxrss = max(getmemsize(), self.maxrss)
         return self.center
 
-
     def avalanche(self, delta, iterava, prob, numexpr):
         self.zeroedge()
-        c     = self.center[1:-1,1:-1]
-        up    = self.center[ :-2,1:-1]
-        down  = self.center[2:  ,1:-1]
-        left  = self.center[1:-1, :-2]
-        right = self.center[1:-1,2:  ]
+        c = self.center[1:-1, 1:-1]
+        up = self.center[:-2, 1:-1]
+        down = self.center[2:, 1:-1]
+        left = self.center[1:-1, :-2]
+        right = self.center[1:-1, 2:]
         where = np.where
 
         if(numexpr and numexpr_available):
-            self.center[1:-1,1:-1] = ne.evaluate('c + where((up   -c) > delta ,(up   -c -delta)/2, 0) \
+            self.center[1:-1, 1:-1] = ne.evaluate('c + where((up   -c) > delta ,(up   -c -delta)/2, 0) \
                  + where((down -c) > delta ,(down -c -delta)/2, 0)  \
                  + where((left -c) > delta ,(left -c -delta)/2, 0)  \
                  + where((right-c) > delta ,(right-c -delta)/2, 0)  \
@@ -286,38 +267,36 @@ class Grid:
         else:
             sa = (
                 # incoming
-                   where((up   -c) > delta ,(up   -c -delta)/2, 0)
-                 + where((down -c) > delta ,(down -c -delta)/2, 0)
-                 + where((left -c) > delta ,(left -c -delta)/2, 0)
-                 + where((right-c) > delta ,(right-c -delta)/2, 0)
+                where((up - c) > delta, (up - c - delta) / 2, 0)
+                + where((down - c) > delta, (down - c - delta) / 2, 0)
+                + where((left - c) > delta, (left - c - delta) / 2, 0)
+                + where((right - c) > delta, (right - c - delta) / 2, 0)
                 # outgoing
-                 + where((up   -c) < -delta,(up   -c +delta)/2, 0)
-                 + where((down -c) < -delta,(down -c +delta)/2, 0)
-                 + where((left -c) < -delta,(left -c +delta)/2, 0)
-                 + where((right-c) < -delta,(right-c +delta)/2, 0)
-                 )
-            randarray = np.random.randint(0,100,sa.shape) *0.01
+                + where((up - c) < -delta, (up - c + delta) / 2, 0)
+                + where((down - c) < -delta, (down - c + delta) / 2, 0)
+                + where((left - c) < -delta, (left - c + delta) / 2, 0)
+                + where((right - c) < -delta, (right - c + delta) / 2, 0)
+            )
+            randarray = np.random.randint(0, 100, sa.shape) * 0.01
             sa = where(randarray < prob, sa, 0)
-            self.avalanced[1:-1,1:-1] = self.avalanced[1:-1,1:-1] + sa/iterava
-            self.center[1:-1,1:-1] = c + sa/iterava
+            self.avalanced[1:-1, 1:-1] = self.avalanced[1:-1, 1:-1] + sa / iterava
+            self.center[1:-1, 1:-1] = c + sa / iterava
 
         self.maxrss = max(getmemsize(), self.maxrss)
         return self.center
 
-
     def rain(self, amount=1, variance=0, userainmap=False):
-        self.water += (1.0 - np.random.random(self.water.shape) * variance) * (amount if ((self.rainmap is None) or (not userainmap)) else self.rainmap * amount)
-
+        self.water += (1.0 - np.random.random(self.water.shape) * variance) * \
+            (amount if ((self.rainmap is None) or (not userainmap)) else self.rainmap * amount)
 
     def spring(self, amount, px, py, radius):
         # px, py and radius are all fractions
         nx, ny = self.center.shape
-        rx = max(int(nx*radius),1)
-        ry = max(int(ny*radius),1)
-        px = int(nx*px)
-        py = int(ny*py)
-        self.water[px-rx:px+rx+1,py-ry:py+ry+1] += amount
-
+        rx = max(int(nx * radius), 1)
+        ry = max(int(ny * radius), 1)
+        px = int(nx * px)
+        py = int(ny * py)
+        self.water[px - rx:px + rx + 1, py - ry:py + ry + 1] += amount
 
     def river(self, Kc, Ks, Kdep, Ka, Kev, numexpr):
         zeros = np.zeros
@@ -328,11 +307,11 @@ class Grid:
         arctan = np.arctan
         sin = np.sin
 
-        center = (slice(   1,   -1,None),slice(   1,  -1,None))
-        up     = (slice(None,   -2,None),slice(   1,  -1,None))
-        down   = (slice(   2, None,None),slice(   1,  -1,None))
-        left   = (slice(   1,   -1,None),slice(None,  -2,None))
-        right  = (slice(   1,   -1,None),slice(   2,None,None))
+        center = (slice(1, -1, None), slice(1, -1, None))
+        up = (slice(None, -2, None), slice(1, -1, None))
+        down = (slice(2, None, None), slice(1, -1, None))
+        left = (slice(1, -1, None), slice(None, -2, None))
+        right = (slice(1, -1, None), slice(2, None, None))
 
         water = self.water
         rock = self.center
@@ -348,7 +327,7 @@ class Grid:
         svdw = zeros(water[center].shape)
         sds = zeros(water[center].shape)
         angle = zeros(water[center].shape)
-        for d in (up,down,left,right):
+        for d in (up, down, left, right):
             if(numexpr and numexpr_available):
                 hdd = height[d]
                 hcc = height[center]
@@ -356,25 +335,26 @@ class Grid:
                 inflow = ne.evaluate('dw > 0')
                 wdd = water[d]
                 wcc = water[center]
-                dw = ne.evaluate('where(inflow, where(wdd<dw, wdd, dw), where(-wcc>dw, -wcc, dw))/4.0') # nested where() represent min() and max()
-                sdw  = ne.evaluate('sdw + dw')
-                scd  = sc[d]
-                scc  = sc[center]
-                rockd= rock[d]
-                rockc= rock[center]
-                sds  = ne.evaluate('sds + dw * where(inflow, scd, scc)')
+                # nested where() represent min() and max()
+                dw = ne.evaluate('where(inflow, where(wdd<dw, wdd, dw), where(-wcc>dw, -wcc, dw))/4.0')
+                sdw = ne.evaluate('sdw + dw')
+                scd = sc[d]
+                scc = sc[center]
+                rockd = rock[d]
+                rockc = rock[center]
+                sds = ne.evaluate('sds + dw * where(inflow, scd, scc)')
                 svdw = ne.evaluate('svdw + abs(dw)')
-                angle= ne.evaluate('angle + arctan(abs(rockd-rockc))')
+                angle = ne.evaluate('angle + arctan(abs(rockd-rockc))')
             else:
-                dw = (height[d]-height[center])
+                dw = (height[d] - height[center])
                 inflow = dw > 0
-                dw = where(inflow, min(water[d], dw), max(-water[center], dw))/4.0
-                sdw  = sdw + dw
-                sds  = sds + dw * where(inflow, sc[d], sc[center])
+                dw = where(inflow, min(water[d], dw), max(-water[center], dw)) / 4.0
+                sdw = sdw + dw
+                sds = sds + dw * where(inflow, sc[d], sc[center])
                 svdw = svdw + abs(dw)
-                angle= angle + np.arctan(abs(rock[d]-rock[center]))
+                angle = angle + np.arctan(abs(rock[d] - rock[center]))
 
-        if(numexpr and numexpr_available):
+        if numexpr and numexpr_available:
             wcc = water[center]
             scc = sediment[center]
             rcc = rock[center]
@@ -391,10 +371,10 @@ class Grid:
             wcc = water[center]
             scc = sediment[center]
             rcc = rock[center]
-            water[center] = wcc * (1-Kev) + sdw
+            water[center] = wcc * (1 - Kev) + sdw
             sediment[center] = scc + sds
             sc = where(wcc > 0, scc / wcc, 2 * Kc)
-            fKc = Kc*svdw
+            fKc = Kc * svdw
             ds = where(fKc > sc, (fKc - sc) * Ks, (fKc - sc) * Kdep) * wcc
             self.flowrate[center] = svdw
             self.scour[center] = ds
@@ -402,7 +382,6 @@ class Grid:
             self.capacity[center] = fKc
             sediment[center] = scc + ds + sds
 
-
     def flow(self, Kc, Ks, Kz, Ka, numexpr):
         zeros = np.zeros
         where = np.where
@@ -412,16 +391,30 @@ class Grid:
         arctan = np.arctan
         sin = np.sin
 
-        center = (slice(   1,   -1,None),slice(   1,  -1,None))
+        center = (slice(1, -1, None), slice(1, -1, None))
         rock = self.center
         ds = self.scour[center]
         rcc = rock[center]
         rock[center] = rcc - ds * Kz
         # there isn't really a bottom to the rock but negative values look ugly
-        rock[center] = where(rcc<0,0,rcc)
-
-
-    def rivergeneration(self, rainamount, rainvariance, userainmap, Kc, Ks, Kdep, Ka, Kev, Kspring, Kspringx, Kspringy, Kspringr, numexpr):
+        rock[center] = where(rcc < 0, 0, rcc)
+
+    def rivergeneration(
+            self,
+            rainamount,
+            rainvariance,
+            userainmap,
+            Kc,
+            Ks,
+            Kdep,
+            Ka,
+            Kev,
+            Kspring,
+            Kspringx,
+            Kspringy,
+            Kspringr,
+            numexpr,
+    ):
         self.init_water_and_sediment()
         self.rain(rainamount, rainvariance, userainmap)
         self.zeroedge(self.water)
@@ -429,32 +422,43 @@ class Grid:
         self.river(Kc, Ks, Kdep, Ka, Kev, numexpr)
         self.watermax = np.max(self.water)
 
-
-    def fluvial_erosion(self, rainamount, rainvariance, userainmap, Kc, Ks, Kdep, Ka, Kspring, Kspringx, Kspringy, Kspringr, numexpr):
+    def fluvial_erosion(
+            self,
+            rainamount,
+            rainvariance,
+            userainmap,
+            Kc,
+            Ks,
+            Kdep,
+            Ka,
+            Kspring,
+            Kspringx,
+            Kspringy,
+            Kspringr,
+            numexpr,
+    ):
         self.flow(Kc, Ks, Kdep, Ka, numexpr)
         self.flowratemax = np.max(self.flowrate)
         self.scourmax = np.max(self.scour)
         self.scourmin = np.min(self.scour)
         self.sedmax = np.max(self.sediment)
 
-
     def analyze(self):
         self.neighborgrid()
         # just looking at up and left to avoid needless double calculations
-        slopes=np.concatenate((np.abs(self.left - self.center),np.abs(self.up - self.center)))
-        return '\n'.join(["%-15s: %.3f"%t for t in [
-                ('height average', np.average(self.center)),
-                ('height median', np.median(self.center)),
-                ('height max', np.max(self.center)),
-                ('height min', np.min(self.center)),
-                ('height std', np.std(self.center)),
-                ('slope average', np.average(slopes)),
-                ('slope median', np.median(slopes)),
-                ('slope max', np.max(slopes)),
-                ('slope min', np.min(slopes)),
-                ('slope std', np.std(slopes))
-                ]]
-            )
+        slopes = np.concatenate((np.abs(self.left - self.center), np.abs(self.up - self.center)))
+        return '\n'.join(["%-15s: %.3f" % t for t in [
+            ('height average', np.average(self.center)),
+            ('height median', np.median(self.center)),
+            ('height max', np.max(self.center)),
+            ('height min', np.min(self.center)),
+            ('height std', np.std(self.center)),
+            ('slope average', np.average(slopes)),
+            ('slope median', np.median(slopes)),
+            ('slope max', np.max(slopes)),
+            ('slope min', np.min(slopes)),
+            ('slope std', np.std(slopes))
+        ]])
 
 
 class TestGrid(unittest.TestCase):
@@ -462,12 +466,11 @@ class TestGrid(unittest.TestCase):
     def test_diffuse(self):
         g = Grid(5)
         g.peak(1)
-        self.assertEqual(g.center[2,2],1.0)
+        self.assertEqual(g.center[2, 2], 1.0)
         g.diffuse(0.1, numexpr=False)
-        for n in [(2,1),(2,3),(1,2),(3,2)]:
-            self.assertAlmostEqual(g.center[n],0.1)
-        self.assertAlmostEqual(g.center[2,2],0.6)
-
+        for n in [(2, 1), (2, 3), (1, 2), (3, 2)]:
+            self.assertAlmostEqual(g.center[n], 0.1)
+        self.assertAlmostEqual(g.center[2, 2], 0.6)
 
     def test_diffuse_numexpr(self):
         g = Grid(5)
@@ -476,8 +479,7 @@ class TestGrid(unittest.TestCase):
         h = Grid(5)
         h.peak(1)
         h.diffuse(0.1, numexpr=True)
-        self.assertEqual(list(g.center.flat),list(h.center.flat))
-
+        self.assertEqual(list(g.center.flat), list(h.center.flat))
 
     def test_avalanche_numexpr(self):
         g = Grid(5)
@@ -488,7 +490,7 @@ class TestGrid(unittest.TestCase):
         h.avalanche(0.1, numexpr=True)
         print(g)
         print(h)
-        np.testing.assert_almost_equal(g.center,h.center)
+        np.testing.assert_almost_equal(g.center, h.center)
 
 
 if __name__ == "__main__":
@@ -501,7 +503,8 @@ if __name__ == "__main__":
     parser.add_argument('-Kh', dest='Kh', type=float, default=6, help='Maximum stable cliff height')
     parser.add_argument('-Kp', dest='Kp', type=float, default=0.1, help='Avalanche probability for unstable cliffs')
     parser.add_argument('-Kr', dest='Kr', type=float, default=0.1, help='Average amount of rain per iteration')
-    parser.add_argument('-Kspring', dest='Kspring', type=float, default=0.0, help='Average amount of wellwater per iteration')
+    parser.add_argument('-Kspring', dest='Kspring', type=float, default=0.0,
+                        help='Average amount of wellwater per iteration')
     parser.add_argument('-Kspringx', dest='Kspringx', type=float, default=0.5, help='relative x position of spring')
     parser.add_argument('-Kspringy', dest='Kspringy', type=float, default=0.5, help='relative y position of spring')
     parser.add_argument('-Kspringr', dest='Kspringr', type=float, default=0.02, help='radius of spring')
@@ -509,26 +512,45 @@ if __name__ == "__main__":
     parser.add_argument('-Ks', dest='Ks', type=float, default=0.1, help='Soil softness constant')
     parser.add_argument('-Kc', dest='Kc', type=float, default=1.0, help='Sediment capacity')
     parser.add_argument('-Ka', dest='Ka', type=float, default=2.0, help='Slope dependency of erosion')
-    parser.add_argument('-ri', action='store_true', dest='rawin', default=False, help='use Blender raw format for input')
-    parser.add_argument('-ro', action='store_true', dest='rawout', default=False, help='use Blender raw format for output')
-    parser.add_argument('-i',  action='store_true', dest='useinputfile', default=False, help='use an inputfile (instead of just a synthesized grid)')
-    parser.add_argument('-t',  action='store_true', dest='timingonly', default=False, help='do not write anything to an output file')
+    parser.add_argument(
+        '-ri',
+        action='store_true',
+        dest='rawin',
+        default=False,
+        help='use Blender raw format for input')
+    parser.add_argument(
+        '-ro',
+        action='store_true',
+        dest='rawout',
+        default=False,
+        help='use Blender raw format for output')
+    parser.add_argument('-i', action='store_true', dest='useinputfile', default=False,
+                        help='use an inputfile (instead of just a synthesized grid)')
+    parser.add_argument(
+        '-t',
+        action='store_true',
+        dest='timingonly',
+        default=False,
+        help='do not write anything to an output file')
     parser.add_argument('-infile', type=str, default="-", help='input filename')
     parser.add_argument('-outfile', type=str, default="-", help='output filename')
     parser.add_argument('-Gn', dest='gridsize', type=int, default=20, help='Gridsize (always square)')
     parser.add_argument('-Gp', dest='gridpeak', type=float, default=0, help='Add peak with given height')
     parser.add_argument('-Gs', dest='gridshelf', type=float, default=0, help='Add shelve with given height')
     parser.add_argument('-Gm', dest='gridmesa', type=float, default=0, help='Add mesa with given height')
-    parser.add_argument('-Gr', dest='gridrandom', type=float, default=0, help='Add random values between 0 and given value')
+    parser.add_argument('-Gr', dest='gridrandom', type=float, default=0,
+                        help='Add random values between 0 and given value')
     parser.add_argument('-m', dest='threads', type=int, default=1, help='number of threads to use')
     parser.add_argument('-u', action='store_true', dest='unittest', default=False, help='perform unittests')
-    parser.add_argument('-a', action='store_true', dest='analyze', default=False, help='show some statistics of input and output meshes')
-    parser.add_argument('-d', action='store_true', dest='dump', default=False, help='show sediment and water meshes at end of run')
+    parser.add_argument('-a', action='store_true', dest='analyze', default=False,
+                        help='show some statistics of input and output meshes')
+    parser.add_argument('-d', action='store_true', dest='dump', default=False,
+                        help='show sediment and water meshes at end of run')
     parser.add_argument('-n', action='store_true', dest='usenumexpr', default=False, help='use numexpr optimizations')
 
     args = parser.parse_args()
     print("\nInput arguments:")
-    print("\n".join("%-15s: %s"%t for t in sorted(vars(args).items())), file=sys.stderr)
+    print("\n".join("%-15s: %s" % t for t in sorted(vars(args).items())), file=sys.stderr)
 
     if args.unittest:
         unittest.main(argv=[sys.argv[0]])
@@ -542,13 +564,17 @@ if __name__ == "__main__":
     else:
         grid = Grid(args.gridsize)
 
-    if args.gridpeak > 0 : grid.peak(args.gridpeak)
-    if args.gridmesa > 0 : grid.mesa(args.gridmesa)
-    if args.gridshelf > 0 : grid.shelf(args.gridshelf)
-    if args.gridrandom > 0 : grid.random(args.gridrandom)
+    if args.gridpeak > 0:
+        grid.peak(args.gridpeak)
+    if args.gridmesa > 0:
+        grid.mesa(args.gridmesa)
+    if args.gridshelf > 0:
+        grid.shelf(args.gridshelf)
+    if args.gridrandom > 0:
+        grid.random(args.gridrandom)
 
     if args.analyze:
-        print('\nstatistics of the input grid:\n\n', grid.analyze(), file=sys.stderr, sep='' )
+        print('\nstatistics of the input grid:\n\n', grid.analyze(), file=sys.stderr, sep='')
     t = getptime()
     for g in range(args.iterations):
         if args.Kd > 0:
@@ -556,9 +582,20 @@ if __name__ == "__main__":
         if args.Kh > 0 and args.Kp > rand():
             grid.avalanche(args.Kh, args.usenumexpr)
         if args.Kr > 0 or args.Kspring > 0:
-            grid.fluvial_erosion(args.Kr, args.Kc, args.Ks, args.Kdep, args.Ka, args.Kspring, args.Kspringx, args.Kspringy, args.Kspringr, args.usenumexpr)
+            grid.fluvial_erosion(
+                args.Kr,
+                args.Kc,
+                args.Ks,
+                args.Kdep,
+                args.Ka,
+                args.Kspring,
+                args.Kspringx,
+                args.Kspringy,
+                args.Kspringr,
+                args.usenumexpr,
+            )
     t = getptime() - t
-    print("\nElapsed time: %.1f seconds, max memory %.1f Mb.\n"%(t,grid.maxrss), file=sys.stderr)
+    print("\nElapsed time: %.1f seconds, max memory %.1f Mb.\n" % (t, grid.maxrss), file=sys.stderr)
     if args.analyze:
         print('\nstatistics of the output grid:\n\n', grid.analyze(), file=sys.stderr, sep='')
 
@@ -569,6 +606,6 @@ if __name__ == "__main__":
             grid.toFile(args.outfile)
 
     if args.dump:
-        print("sediment\n", np.array_str(grid.sediment,precision=3), file=sys.stderr)
-        print("water\n", np.array_str(grid.water,precision=3), file=sys.stderr)
-        print("sediment concentration\n", np.array_str(grid.sediment/grid.water,precision=3), file=sys.stderr)
+        print("sediment\n", np.array_str(grid.sediment, precision=3), file=sys.stderr)
+        print("water\n", np.array_str(grid.water, precision=3), file=sys.stderr)
+        print("sediment concentration\n", np.array_str(grid.sediment / grid.water, precision=3), file=sys.stderr)
diff --git a/ant_landscape/mesh_ant_displace.py b/ant_landscape/mesh_ant_displace.py
index 5638d96a7d4feee8203fd67c63da27f9644f4565..132ca6fe660f14c2c18d6320c67e1f1fb81cc746 100644
--- a/ant_landscape/mesh_ant_displace.py
+++ b/ant_landscape/mesh_ant_displace.py
@@ -7,24 +7,26 @@
 # import modules
 import bpy
 from bpy.props import (
-        BoolProperty,
-        EnumProperty,
-        FloatProperty,
-        IntProperty,
-        StringProperty,
-        FloatVectorProperty,
-        )
+    BoolProperty,
+    EnumProperty,
+    FloatProperty,
+    IntProperty,
+    StringProperty,
+    FloatVectorProperty,
+)
 from .ant_functions import (
-        draw_ant_refresh,
-        draw_ant_main,
-        draw_ant_noise,
-        draw_ant_displace,
-        )
+    draw_ant_refresh,
+    draw_ant_main,
+    draw_ant_noise,
+    draw_ant_displace,
+)
 from .ant_noise import noise_gen
 from ant_landscape import ant_noise
 
 # ------------------------------------------------------------
 # Do vert displacement
+
+
 class AntMeshDisplace(bpy.types.Operator):
     bl_idname = "mesh.ant_displace"
     bl_label = "Another Noise Tool - Displace"
@@ -32,131 +34,131 @@ class AntMeshDisplace(bpy.types.Operator):
     bl_options = {'REGISTER', 'UNDO', 'PRESET'}
 
     ant_terrain_name: StringProperty(
-            name="Name",
-            default="Landscape"
-            )
+        name="Name",
+        default="Landscape"
+    )
     land_material: StringProperty(
-            name='Material',
-            default="",
-            description="Terrain material"
-            )
+        name='Material',
+        default="",
+        description="Terrain material"
+    )
     water_material: StringProperty(
-            name='Material',
-            default="",
-            description="Water plane material"
-            )
+        name='Material',
+        default="",
+        description="Water plane material"
+    )
     texture_block: StringProperty(
-            name="Texture",
-            default=""
-            )
+        name="Texture",
+        default=""
+    )
     at_cursor: BoolProperty(
-            name="Cursor",
-            default=True,
-            description="Place at cursor location",
-            )
+        name="Cursor",
+        default=True,
+        description="Place at cursor location",
+    )
     smooth_mesh: BoolProperty(
-            name="Smooth",
-            default=True,
-            description="Shade smooth"
-            )
+        name="Smooth",
+        default=True,
+        description="Shade smooth"
+    )
     tri_face: BoolProperty(
-            name="Triangulate",
-            default=False,
-            description="Triangulate faces"
-            )
+        name="Triangulate",
+        default=False,
+        description="Triangulate faces"
+    )
     sphere_mesh: BoolProperty(
-            name="Sphere",
-            default=False,
-            description="Generate uv sphere - remove doubles when ready"
-            )
+        name="Sphere",
+        default=False,
+        description="Generate uv sphere - remove doubles when ready"
+    )
     subdivision_x: IntProperty(
-            name="Subdivisions X",
-            default=128,
-            min=4,
-            max=6400,
-            description="Mesh X subdivisions"
-            )
+        name="Subdivisions X",
+        default=128,
+        min=4,
+        max=6400,
+        description="Mesh X subdivisions"
+    )
     subdivision_y: IntProperty(
-            default=128,
-            name="Subdivisions Y",
-            min=4,
-            max=6400,
-            description="Mesh Y subdivisions"
-            )
+        default=128,
+        name="Subdivisions Y",
+        min=4,
+        max=6400,
+        description="Mesh Y subdivisions"
+    )
     mesh_size: FloatProperty(
-            default=2.0,
-            name="Mesh Size",
-            min=0.01,
-            max=100000.0,
-            description="Mesh size"
-            )
+        default=2.0,
+        name="Mesh Size",
+        min=0.01,
+        max=100000.0,
+        description="Mesh size"
+    )
     mesh_size_x: FloatProperty(
-            default=2.0,
-            name="Mesh Size X",
-            min=0.01,
-            description="Mesh x size"
-            )
+        default=2.0,
+        name="Mesh Size X",
+        min=0.01,
+        description="Mesh x size"
+    )
     mesh_size_y: FloatProperty(
-            name="Mesh Size Y",
-            default=2.0,
-            min=0.01,
-            description="Mesh y size"
-            )
+        name="Mesh Size Y",
+        default=2.0,
+        min=0.01,
+        description="Mesh y size"
+    )
 
     random_seed: IntProperty(
-            name="Random Seed",
-            default=0,
-            min=0,
-            description="Randomize noise origin"
-            )
+        name="Random Seed",
+        default=0,
+        min=0,
+        description="Randomize noise origin"
+    )
     noise_offset_x: FloatProperty(
-            name="Offset X",
-            default=0.0,
-            description="Noise X Offset"
-            )
+        name="Offset X",
+        default=0.0,
+        description="Noise X Offset"
+    )
     noise_offset_y: FloatProperty(
-            name="Offset Y",
-            default=0.0,
-            description="Noise Y Offset"
-            )
+        name="Offset Y",
+        default=0.0,
+        description="Noise Y Offset"
+    )
     noise_offset_z: FloatProperty(
-            name="Offset Z",
-            default=0.0,
-            description="Noise Z Offset"
-            )
+        name="Offset Z",
+        default=0.0,
+        description="Noise Z Offset"
+    )
     noise_size_x: FloatProperty(
-            default=1.0,
-            name="Size X",
-            min=0.01,
-            max=1000.0,
-            description="Noise x size"
-            )
+        default=1.0,
+        name="Size X",
+        min=0.01,
+        max=1000.0,
+        description="Noise x size"
+    )
     noise_size_y: FloatProperty(
-            name="Size Y",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Noise y size"
-            )
+        name="Size Y",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Noise y size"
+    )
     noise_size_z: FloatProperty(
-            name="Size Z",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Noise Z size"
-            )
+        name="Size Z",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Noise Z size"
+    )
     noise_size: FloatProperty(
-            name="Noise Size",
-            default=0.25,
-            min=0.01,
-            max=1000.0,
-            description="Noise size"
-            )
+        name="Noise Size",
+        default=0.25,
+        min=0.01,
+        max=1000.0,
+        description="Noise size"
+    )
     noise_type: EnumProperty(
-            name="Noise Type",
-            default='hetero_terrain',
-            description="Noise type",
-            items = [
+        name="Noise Type",
+        default='hetero_terrain',
+        description="Noise type",
+        items=[
                 ('multi_fractal', "Multi Fractal", "Blender: Multi Fractal algorithm", 0),
                 ('ridged_multi_fractal', "Ridged MFractal", "Blender: Ridged Multi Fractal", 1),
                 ('hybrid_multi_fractal', "Hybrid MFractal", "Blender: Hybrid Multi Fractal", 2),
@@ -176,110 +178,110 @@ class AntMeshDisplace(bpy.types.Operator):
                 ('slick_rock', "Slick Rock", "A.N.T: slick rock", 16),
                 ('planet_noise', "Planet Noise", "Planet Noise by: Farsthary", 17),
                 ('blender_texture', "Blender Texture - Texture Nodes", "Blender texture data block", 18)]
-            )
+    )
     basis_type: EnumProperty(
-            name="Noise Basis",
-            default=ant_noise.noise_basis_default,
-            description="Noise basis algorithms",
-            items = ant_noise.noise_basis
-            )
+        name="Noise Basis",
+        default=ant_noise.noise_basis_default,
+        description="Noise basis algorithms",
+        items=ant_noise.noise_basis
+    )
     vl_basis_type: EnumProperty(
-            name="vlNoise Basis",
-            default=ant_noise.noise_basis_default,
-            description="VLNoise basis algorithms",
-            items = ant_noise.noise_basis
-            )
+        name="vlNoise Basis",
+        default=ant_noise.noise_basis_default,
+        description="VLNoise basis algorithms",
+        items=ant_noise.noise_basis
+    )
     distortion: FloatProperty(
-            name="Distortion",
-            default=1.0,
-            min=0.01,
-            max=100.0,
-            description="Distortion amount"
-            )
+        name="Distortion",
+        default=1.0,
+        min=0.01,
+        max=100.0,
+        description="Distortion amount"
+    )
     hard_noise: EnumProperty(
-            name="Soft Hard",
-            default="0",
-            description="Soft Noise, Hard noise",
-            items = [
+        name="Soft Hard",
+        default="0",
+        description="Soft Noise, Hard noise",
+        items=[
                 ("0", "Soft", "Soft Noise", 0),
                 ("1", "Hard", "Hard noise", 1)]
-            )
+    )
     noise_depth: IntProperty(
-            name="Depth",
-            default=8,
-            min=0,
-            max=16,
-            description="Noise Depth - number of frequencies in the fBm"
-            )
+        name="Depth",
+        default=8,
+        min=0,
+        max=16,
+        description="Noise Depth - number of frequencies in the fBm"
+    )
     amplitude: FloatProperty(
-            name="Amp",
-            default=0.5,
-            min=0.01,
-            max=1.0,
-            description="Amplitude"
-            )
+        name="Amp",
+        default=0.5,
+        min=0.01,
+        max=1.0,
+        description="Amplitude"
+    )
     frequency: FloatProperty(
-            name="Freq",
-            default=2.0,
-            min=0.01,
-            max=5.0,
-            description="Frequency"
-            )
+        name="Freq",
+        default=2.0,
+        min=0.01,
+        max=5.0,
+        description="Frequency"
+    )
     dimension: FloatProperty(
-            name="Dimension",
-            default=1.0,
-            min=0.01,
-            max=2.0,
-            description="H - fractal dimension of the roughest areas"
-            )
+        name="Dimension",
+        default=1.0,
+        min=0.01,
+        max=2.0,
+        description="H - fractal dimension of the roughest areas"
+    )
     lacunarity: FloatProperty(
-            name="Lacunarity",
-            min=0.01,
-            max=6.0,
-            default=2.0,
-            description="Lacunarity - gap between successive frequencies"
-            )
+        name="Lacunarity",
+        min=0.01,
+        max=6.0,
+        default=2.0,
+        description="Lacunarity - gap between successive frequencies"
+    )
     offset: FloatProperty(
-            name="Offset",
-            default=1.0,
-            min=0.01,
-            max=6.0,
-            description="Offset - raises the terrain from sea level"
-            )
+        name="Offset",
+        default=1.0,
+        min=0.01,
+        max=6.0,
+        description="Offset - raises the terrain from sea level"
+    )
     gain: FloatProperty(
-            name="Gain",
-            default=1.0,
-            min=0.01,
-            max=6.0,
-            description="Gain - scale factor"
-            )
+        name="Gain",
+        default=1.0,
+        min=0.01,
+        max=6.0,
+        description="Gain - scale factor"
+    )
     marble_bias: EnumProperty(
-            name="Bias",
-            default="0",
-            description="Marble bias",
-            items = [
+        name="Bias",
+        default="0",
+        description="Marble bias",
+        items=[
                 ("0", "Sin", "Sin", 0),
                 ("1", "Cos", "Cos", 1),
                 ("2", "Tri", "Tri", 2),
                 ("3", "Saw", "Saw", 3)]
-            )
+    )
     marble_sharp: EnumProperty(
-            name="Sharp",
-            default="0",
-            description="Marble sharpness",
-            items = [
+        name="Sharp",
+        default="0",
+        description="Marble sharpness",
+        items=[
                 ("0", "Soft", "Soft", 0),
                 ("1", "Sharp", "Sharp", 1),
                 ("2", "Sharper", "Sharper", 2),
                 ("3", "Soft inv.", "Soft", 3),
                 ("4", "Sharp inv.", "Sharp", 4),
                 ("5", "Sharper inv.", "Sharper", 5)]
-            )
+    )
     marble_shape: EnumProperty(
-            name="Shape",
-            default="0",
-            description="Marble shape",
-            items= [
+        name="Shape",
+        default="0",
+        description="Marble shape",
+        items=[
                 ("0", "Default", "Default", 0),
                 ("1", "Ring", "Ring", 1),
                 ("2", "Swirl", "Swirl", 2),
@@ -288,39 +290,39 @@ class AntMeshDisplace(bpy.types.Operator):
                 ("5", "Z", "Z", 5),
                 ("6", "Y", "Y", 6),
                 ("7", "X", "X", 7)]
-        )
+    )
     height: FloatProperty(
-            name="Height",
-            default=0.25,
-            min=-10000.0,
-            max=10000.0,
-            description="Noise intensity scale"
-            )
+        name="Height",
+        default=0.25,
+        min=-10000.0,
+        max=10000.0,
+        description="Noise intensity scale"
+    )
     height_invert: BoolProperty(
-            name="Invert",
-            default=False,
-            description="Height invert",
-            )
+        name="Invert",
+        default=False,
+        description="Height invert",
+    )
     height_offset: FloatProperty(
-            name="Offset",
-            default=0.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Height offset"
-            )
+        name="Offset",
+        default=0.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Height offset"
+    )
 
     fx_mixfactor: FloatProperty(
-            name="Mix Factor",
-            default=0.0,
-            min=-1.0,
-            max=1.0,
-            description="Effect mix factor: -1.0 = Noise, +1.0 = Effect"
-            )
+        name="Mix Factor",
+        default=0.0,
+        min=-1.0,
+        max=1.0,
+        description="Effect mix factor: -1.0 = Noise, +1.0 = Effect"
+    )
     fx_mix_mode: EnumProperty(
-            name="Effect Mix",
-            default="0",
-            description="Effect mix mode",
-            items = [
+        name="Effect Mix",
+        default="0",
+        description="Effect mix mode",
+        items=[
                 ("0", "Mix", "Mix", 0),
                 ("1", "Add", "Add", 1),
                 ("2", "Sub", "Subtract", 2),
@@ -330,13 +332,13 @@ class AntMeshDisplace(bpy.types.Operator):
                 ("6", "Mod", "Modulo", 6),
                 ("7", "Min", "Minimum", 7),
                 ("8", "Max", "Maximum", 8)
-                ]
-            )
+        ]
+    )
     fx_type: EnumProperty(
-            name="Effect Type",
-            default="0",
-            description="Effect type",
-            items = [
+        name="Effect Type",
+        default="0",
+        description="Effect type",
+        items=[
                 ("0", "None", "No effect", 0),
                 ("1", "Gradient", "Gradient", 1),
                 ("2", "Waves", "Waves - Bumps", 2),
@@ -359,223 +361,220 @@ class AntMeshDisplace(bpy.types.Operator):
                 ("19", "Stone", "Stone", 19),
                 ("20", "Flat Turb", "Flat turbulence", 20),
                 ("21", "Flat Voronoi", "Flat voronoi", 21)
-                ]
-            )
+        ]
+    )
     fx_bias: EnumProperty(
-            name="Effect Bias",
-            default="0",
-            description="Effect bias type",
-            items = [
+        name="Effect Bias",
+        default="0",
+        description="Effect bias type",
+        items=[
                 ("0", "Sin", "Sin", 0),
                 ("1", "Cos", "Cos", 1),
                 ("2", "Tri", "Tri", 2),
                 ("3", "Saw", "Saw", 3),
                 ("4", "None", "None", 4)
-                ]
-            )
+        ]
+    )
     fx_turb: FloatProperty(
-            name="Distortion",
-            default=0.0,
-            min=0.0,
-            max=1000.0,
-            description="Effect turbulence distortion"
-            )
+        name="Distortion",
+        default=0.0,
+        min=0.0,
+        max=1000.0,
+        description="Effect turbulence distortion"
+    )
     fx_depth: IntProperty(
-            name="Depth",
-            default=0,
-            min=0,
-            max=16,
-            description="Effect depth - number of frequencies"
-            )
+        name="Depth",
+        default=0,
+        min=0,
+        max=16,
+        description="Effect depth - number of frequencies"
+    )
     fx_amplitude: FloatProperty(
-            name="Amp",
-            default=0.5,
-            min=0.01,
-            max=1.0,
-            description="Amplitude"
-            )
+        name="Amp",
+        default=0.5,
+        min=0.01,
+        max=1.0,
+        description="Amplitude"
+    )
     fx_frequency: FloatProperty(
-            name="Freq",
-            default=2.0,
-            min=0.01,
-            max=5.0,
-            description="Frequency"
-            )
+        name="Freq",
+        default=2.0,
+        min=0.01,
+        max=5.0,
+        description="Frequency"
+    )
     fx_size: FloatProperty(
-            name="Effect Size",
-            default=1.0,
-            min=0.01,
-            max=1000.0,
-            description="Effect size"
-            )
+        name="Effect Size",
+        default=1.0,
+        min=0.01,
+        max=1000.0,
+        description="Effect size"
+    )
     fx_loc_x: FloatProperty(
-            name="Offset X",
-            default=0.0,
-            description="Effect x offset"
-            )
+        name="Offset X",
+        default=0.0,
+        description="Effect x offset"
+    )
     fx_loc_y: FloatProperty(
-            name="Offset Y",
-            default=0.0,
-            description="Effect y offset"
-            )
+        name="Offset Y",
+        default=0.0,
+        description="Effect y offset"
+    )
     fx_height: FloatProperty(
-            name="Intensity",
-            default=1.0,
-            min=-1000.0,
-            max=1000.0,
-            description="Effect intensity scale"
-            )
+        name="Intensity",
+        default=1.0,
+        min=-1000.0,
+        max=1000.0,
+        description="Effect intensity scale"
+    )
     fx_invert: BoolProperty(
-            name="Invert",
-            default=False,
-            description="Effect invert"
-            )
+        name="Invert",
+        default=False,
+        description="Effect invert"
+    )
     fx_offset: FloatProperty(
-            name="Offset",
-            default=0.0,
-            min=-1000.0,
-            max=1000.0,
-            description="Effect height offset"
-            )
+        name="Offset",
+        default=0.0,
+        min=-1000.0,
+        max=1000.0,
+        description="Effect height offset"
+    )
 
     edge_falloff: EnumProperty(
-            name="Falloff",
-            default="0",
-            description="Flatten edges",
-            items = [
+        name="Falloff",
+        default="0",
+        description="Flatten edges",
+        items=[
                 ("0", "None", "None", 0),
                 ("1", "Y", "Y Falloff", 1),
                 ("2", "X", "X Falloff", 2),
                 ("3", "X Y", "X Y Falloff", 3)]
-            )
+    )
     falloff_x: FloatProperty(
-            name="Falloff X",
-            default=4.0,
-            min=0.1,
-            max=100.0,
-            description="Falloff x scale"
-            )
+        name="Falloff X",
+        default=4.0,
+        min=0.1,
+        max=100.0,
+        description="Falloff x scale"
+    )
     falloff_y: FloatProperty(
-            name="Falloff Y",
-            default=4.0,
-            min=0.1,
-            max=100.0,
-            description="Falloff y scale"
-            )
+        name="Falloff Y",
+        default=4.0,
+        min=0.1,
+        max=100.0,
+        description="Falloff y scale"
+    )
     edge_level: FloatProperty(
-            name="Edge Level",
-            default=0.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Edge level, sealevel offset"
-            )
+        name="Edge Level",
+        default=0.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Edge level, sealevel offset"
+    )
     maximum: FloatProperty(
-            name="Maximum",
-            default=1.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Maximum, flattens terrain at plateau level"
-            )
+        name="Maximum",
+        default=1.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Maximum, flattens terrain at plateau level"
+    )
     minimum: FloatProperty(
-            name="Minimum",
-            default=-1.0,
-            min=-10000.0,
-            max=10000.0,
-            description="Minimum, flattens terrain at seabed level"
-            )
+        name="Minimum",
+        default=-1.0,
+        min=-10000.0,
+        max=10000.0,
+        description="Minimum, flattens terrain at seabed level"
+    )
     vert_group: StringProperty(
-            name="Vertex Group",
-            default=""
-            )
+        name="Vertex Group",
+        default=""
+    )
     strata: FloatProperty(
-            name="Amount",
-            default=5.0,
-            min=0.01,
-            max=1000.0,
-            description="Strata layers / terraces"
-            )
+        name="Amount",
+        default=5.0,
+        min=0.01,
+        max=1000.0,
+        description="Strata layers / terraces"
+    )
     strata_type: EnumProperty(
-            name="Strata",
-            default="0",
-            description="Strata types",
-            items = [
+        name="Strata",
+        default="0",
+        description="Strata types",
+        items=[
                 ("0", "None", "No strata", 0),
                 ("1", "Smooth", "Smooth transitions", 1),
                 ("2", "Sharp Sub", "Sharp subtract transitions", 2),
                 ("3", "Sharp Add", "Sharp add transitions", 3),
                 ("4", "Quantize", "Quantize", 4),
                 ("5", "Quantize Mix", "Quantize mixed", 5)]
-            )
+    )
     water_plane: BoolProperty(
-            name="Water Plane",
-            default=False,
-            description="Add water plane"
-            )
+        name="Water Plane",
+        default=False,
+        description="Add water plane"
+    )
     water_level: FloatProperty(
-            name="Level",
-            default=0.01,
-            min=-10000.0,
-            max=10000.0,
-            description="Water level"
-            )
+        name="Level",
+        default=0.01,
+        min=-10000.0,
+        max=10000.0,
+        description="Water level"
+    )
     remove_double: BoolProperty(
-            name="Remove Doubles",
-            default=False,
-            description="Remove doubles"
-            )
+        name="Remove Doubles",
+        default=False,
+        description="Remove doubles"
+    )
     direction: EnumProperty(
-            name="Direction",
-            default="NORMAL",
-            description="Displacement direction",
-            items = [
+        name="Direction",
+        default="NORMAL",
+        description="Displacement direction",
+        items=[
                 ("NORMAL", "Normal", "Displace along vertex normal direction", 0),
                 ("Z", "Z", "Displace in the Z direction", 1),
                 ("Y", "Y", "Displace in the Y direction", 2),
                 ("X", "X", "Displace in the X direction", 3)]
-            )
+    )
     show_main_settings: BoolProperty(
-            name="Main Settings",
-            default=True,
-            description="Show settings"
-            )
+        name="Main Settings",
+        default=True,
+        description="Show settings"
+    )
     show_noise_settings: BoolProperty(
-            name="Noise Settings",
-            default=True,
-            description="Show noise settings"
-            )
+        name="Noise Settings",
+        default=True,
+        description="Show noise settings"
+    )
     show_displace_settings: BoolProperty(
-            name="Displace Settings",
-            default=True,
-            description="Show terrain settings"
-            )
+        name="Displace Settings",
+        default=True,
+        description="Show terrain settings"
+    )
     refresh: BoolProperty(
-            name="Refresh",
-            default=False,
-            description="Refresh"
-            )
+        name="Refresh",
+        default=False,
+        description="Refresh"
+    )
     auto_refresh: BoolProperty(
-            name="Auto",
-            default=False,
-            description="Automatic refresh"
-            )
+        name="Auto",
+        default=False,
+        description="Automatic refresh"
+    )
 
     def draw(self, context):
         draw_ant_refresh(self, context)
         draw_ant_noise(self, context, generate=False)
         draw_ant_displace(self, context, generate=False)
 
-
     @classmethod
     def poll(cls, context):
         ob = context.object
         return (ob and ob.type == 'MESH')
 
-
     def invoke(self, context, event):
         self.refresh = True
         return self.execute(context)
 
-
     def execute(self, context):
         if not self.refresh:
             return {'PASS_THROUGH'}
@@ -649,7 +648,7 @@ class AntMeshDisplace(bpy.types.Operator):
             self.fx_height,
             self.fx_offset,
             self.fx_invert
-            ]
+        ]
 
         # do displace
         mesh = ob.data
diff --git a/ant_landscape/stats.py b/ant_landscape/stats.py
index c8793dd99f88bd067274b3c5a8923c12a0a6a690..b217a7b077236c3c9fceb685f45c96b12edc3daa 100644
--- a/ant_landscape/stats.py
+++ b/ant_landscape/stats.py
@@ -5,15 +5,16 @@ from time import time
 try:
     import psutil
     # print('psutil available')
-    psutil_available=True
+    psutil_available = True
 except ImportError:
-    psutil_available=False
+    psutil_available = False
+
 
 class Stats:
     def __init__(self):
         self.memstats_available = False
         if psutil_available:
-            self.process=psutil.Process()
+            self.process = psutil.Process()
             self.memstats_available = True
         self.reset()
 
diff --git a/ant_landscape/test.py b/ant_landscape/test.py
index 39d060c45d01201567e78a748e5c21c5d90f91f3..65e641bd8e4c1dd4483c829f6d7400adffa18cc4 100644
--- a/ant_landscape/test.py
+++ b/ant_landscape/test.py
@@ -18,6 +18,6 @@ if __name__ == '__main__':
     a = cos(a)
     print(stats.time())
     print(stats.memory())
-    a = cos(a)**2+sin(a)**2
+    a = cos(a) ** 2 + sin(a) ** 2
     print(stats.time())
     print(stats.memory())
diff --git a/ant_landscape/utils.py b/ant_landscape/utils.py
index 7687bd54ea4e6ef08ccf393bafaad4692806f00b..9451f83af5837abf7058ea9f70c41f318760cf85 100644
--- a/ant_landscape/utils.py
+++ b/ant_landscape/utils.py
@@ -1,8 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
-numexpr_available=False
+numexpr_available = False
 try:
     import numexpr
-    numexpr_available=True
+    numexpr_available = True
 except ImportError:
     pass
diff --git a/archimesh/__init__.py b/archimesh/__init__.py
index 261397f12dbd16644095ee20e4af2d83869faf8d..d79e02f7bc78765734ea151bc2dc9816f6b137fd 100644
--- a/archimesh/__init__.py
+++ b/archimesh/__init__.py
@@ -11,8 +11,8 @@ bl_info = {
     "name": "Archimesh",
     "author": "Antonio Vazquez (antonioya)",
     "location": "View3D > Add Mesh / Sidebar > Create Tab",
-    "version": (1, 2, 2),
-    "blender": (2, 80, 0),
+    "version": (1, 2, 3),
+    "blender": (3, 0, 0),
     "description": "Generate rooms, doors, windows, and other architecture objects",
     "doc_url": "{BLENDER_MANUAL_URL}/addons/add_mesh/archimesh.html",
     "category": "Add Mesh"
diff --git a/archimesh/achm_gltools.py b/archimesh/achm_gltools.py
index de11226d8ea5ce2d6e4527f3143dd0c7f1f7a327..00160989823d41cef0ea67cd1506320b2d6076a6 100644
--- a/archimesh/achm_gltools.py
+++ b/archimesh/achm_gltools.py
@@ -16,7 +16,6 @@ from mathutils import Vector
 from bpy_extras import view3d_utils
 from .achm_room_maker import get_wall_points
 # GPU
-import bgl
 import gpu
 from gpu_extras.batch import batch_for_shader
 
@@ -39,7 +38,7 @@ def draw_main(context):
     measure = scene.archimesh_gl_measure
     dspname = scene.archimesh_gl_name
 
-    bgl.glEnable(bgl.GL_BLEND)
+    gpu.state.blend_set('ALPHA')
     # Display selected or all
     if scene.archimesh_gl_ghost is False:
         objlist = context.selected_objects
@@ -81,8 +80,8 @@ def draw_main(context):
     # -----------------------
     # restore opengl defaults
     # -----------------------
-    bgl.glLineWidth(1)
-    bgl.glDisable(bgl.GL_BLEND)
+    gpu.state.line_width_set(1.0)
+    gpu.state.blend_set('NONE')
 
 
 # -------------------------------------------------------------
@@ -175,8 +174,8 @@ def draw_room_data(myobj, op, region, rv3d, rgba, rgbaw, fsize, wfsize, space, m
         screen_point_b2 = view3d_utils.location_3d_to_region_2d(region, rv3d, b2_s2)
 
         # colour + line setup
-        bgl.glEnable(bgl.GL_BLEND)
-        bgl.glLineWidth(1)
+        gpu.state.blend_set('ALPHA')
+        gpu.state.line_width_set(1.0)
         # --------------------------------
         # Measures
         # --------------------------------
@@ -284,8 +283,8 @@ def draw_door_data(myobj, op, region, rv3d, rgba, fsize, space, measure):
     screen_point_ep3 = view3d_utils.location_3d_to_region_2d(region, rv3d, e_p3)
 
     # colour + line setup
-    bgl.glEnable(bgl.GL_BLEND)
-    bgl.glLineWidth(1)
+    gpu.state.blend_set('ALPHA')
+    gpu.state.line_width_set(1.0)
 
     # --------------------------------
     # Measures
@@ -368,8 +367,8 @@ def draw_window_rail_data(myobj, op, region, rv3d, rgba, fsize, space, measure):
     screen_point_tp3 = view3d_utils.location_3d_to_region_2d(region, rv3d, t_p3)
 
     # colour + line setup
-    bgl.glEnable(bgl.GL_BLEND)
-    bgl.glLineWidth(1)
+    gpu.state.blend_set('ALPHA')
+    gpu.state.line_width_set(1.0)
 
     # --------------------------------
     # Measures
@@ -473,9 +472,10 @@ def draw_window_panel_data(myobj, op, region, rv3d, rgba, fsize, space, measure)
     screen_point_gp3 = view3d_utils.location_3d_to_region_2d(region, rv3d, g_p3)
     screen_point_gp4 = view3d_utils.location_3d_to_region_2d(region, rv3d, g_p4)
     screen_point_gp5 = view3d_utils.location_3d_to_region_2d(region, rv3d, g_p5)
+
     # colour + line setup
-    bgl.glEnable(bgl.GL_BLEND)
-    bgl.glLineWidth(1)
+    gpu.state.blend_set('ALPHA')
+    gpu.state.line_width_set(1.0)
 
     # --------------------------------
     # Measures
diff --git a/archimesh/achm_main_panel.py b/archimesh/achm_main_panel.py
index 97c7d19df9649d00695cb11ca9a742fdbff1e79b..482e26fdef73136f4f852e51ab145fc87ec122b8 100644
--- a/archimesh/achm_main_panel.py
+++ b/archimesh/achm_main_panel.py
@@ -8,7 +8,6 @@
 # noinspection PyUnresolvedReferences
 import bpy
 # noinspection PyUnresolvedReferences
-import bgl
 from bpy.types import Operator, Panel, SpaceView3D
 from math import sqrt, fabs, pi, asin
 from .achm_tools import *
@@ -36,6 +35,7 @@ class ARCHIMESH_OT_Hole(Operator):
     bl_label = "Auto Holes"
     bl_description = "Enable windows and doors holes for any selected object (needs wall thickness)"
     bl_category = 'View'
+    bl_options = {'UNDO', 'REGISTER'}
 
     # ------------------------------
     # Execute
@@ -170,6 +170,7 @@ class ARCHIMESH_OT_Pencil(Operator):
     bl_label = "Room from Draw"
     bl_description = "Create a room base on grease pencil strokes (draw from top view (7 key))"
     bl_category = 'View'
+    bl_options = {'UNDO', 'REGISTER'}
 
     # ------------------------------
     # Execute
diff --git a/btrace/__init__.py b/btrace/__init__.py
index fd4bf57c92b176399ae15470a9b0a0df78797236..527ceb3f4ac5656f4acd1e2cc62c99b375371ea8 100644
--- a/btrace/__init__.py
+++ b/btrace/__init__.py
@@ -4,7 +4,7 @@
 bl_info = {
     "name": "BTracer",
     "author": "liero, crazycourier, Atom, Meta-Androcto, MacKracken",
-    "version": (1, 2, 3),
+    "version": (1, 2, 4),
     "blender": (2, 80, 0),
     "location": "View3D > Sidebar > Create Tab",
     "description": "Tools for converting/animating objects/particles into curves",
diff --git a/btrace/bTrace.py b/btrace/bTrace.py
index a17c3dd7d690c7dfc162459fd5e1203f97c05bd4..2ec92d72c1e9bbf64523cd062f1eee5ded7fa97c 100644
--- a/btrace/bTrace.py
+++ b/btrace/bTrace.py
@@ -303,9 +303,9 @@ class OBJECT_OT_particletrace(Operator):
                 spline = tracer[0].splines.new('BEZIER')
 
                 # add point to spline based on step size
-                spline.bezier_points.add((x.lifetime - 1) // particle_step)
+                spline.bezier_points.add(int((x.lifetime - 1) // particle_step))
                 for t in list(range(int(x.lifetime))):
-                    bpy.context.scene.frame_set(t + x.birth_time)
+                    bpy.context.scene.frame_set(int(t + x.birth_time))
 
                     if not t % particle_step:
                         p = spline.bezier_points[t // particle_step]
diff --git a/curve_assign_shapekey.py b/curve_assign_shapekey.py
index a87e89b07f0be08643982e10198a8d990bf35138..75e42b1ab4f90e0a8324860232580168a4940828 100644
--- a/curve_assign_shapekey.py
+++ b/curve_assign_shapekey.py
@@ -8,7 +8,7 @@
 #
 # https://github.com/Shriinivas/assignshapekey/blob/master/LICENSE
 
-import bpy, bmesh, bgl, gpu
+import bpy, bmesh, gpu
 from gpu_extras.batch import batch_for_shader
 from bpy.props import BoolProperty, EnumProperty, StringProperty
 from collections import OrderedDict
@@ -21,8 +21,8 @@ from bpy.types import Panel, Operator, AddonPreferences
 bl_info = {
     "name": "Assign Shape Keys",
     "author": "Shrinivas Kulkarni",
-    "version": (1, 0, 1),
-    "blender": (2, 80, 0),
+    "version": (1, 0, 2),
+    "blender": (3, 0, 0),
     "location": "View 3D > Sidebar > Edit Tab",
     "description": "Assigns one or more Bezier curves as shape keys to another Bezier curve",
     "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/assign_shape_keys.html",
@@ -835,7 +835,7 @@ class MarkerController:
             context.area.tag_redraw()
 
     def drawHandler(self):
-        bgl.glPointSize(MarkerController.defPointSize)
+        gpu.state.point_size_set(MarkerController.defPointSize)
         self.batch.draw(self.shader)
 
     def removeMarkers(self, context):
diff --git a/curve_tools/auto_loft.py b/curve_tools/auto_loft.py
index 3092e6b856c13f2ea24fbee8bdd3c2cd01b506cf..b14aaf3b84b69b88c23dd3326da4886728c2a471 100644
--- a/curve_tools/auto_loft.py
+++ b/curve_tools/auto_loft.py
@@ -12,6 +12,7 @@ class OperatorAutoLoftCurves(Operator):
     bl_idname = "curvetools.create_auto_loft"
     bl_label = "Loft"
     bl_description = "Lofts selected curves"
+    bl_options = {'UNDO'}
 
     @classmethod
     def poll(cls, context):
diff --git a/curve_tools/operators.py b/curve_tools/operators.py
index e4480f882f3b351bc7fdd0f7f0d1b3c2c6507d74..d4ada3187e32bd39feed3ce1d84e9c4de6ac6971 100644
--- a/curve_tools/operators.py
+++ b/curve_tools/operators.py
@@ -145,6 +145,7 @@ class OperatorOriginToSpline0Start(bpy.types.Operator):
     bl_idname = "curvetools.operatororigintospline0start"
     bl_label = "OriginToSpline0Start"
     bl_description = "Sets the origin of the active/selected curve to the starting point of the (first) spline. Nice for curve modifiers"
+    bl_options = {'UNDO'}
 
 
     @classmethod
@@ -182,6 +183,7 @@ class OperatorIntersectCurves(bpy.types.Operator):
     bl_idname = "curvetools.operatorintersectcurves"
     bl_label = "Intersect"
     bl_description = "Intersects selected curves"
+    bl_options = {'UNDO'}
 
 
     @classmethod
@@ -233,6 +235,7 @@ class OperatorLoftCurves(bpy.types.Operator):
     bl_idname = "curvetools.operatorloftcurves"
     bl_label = "Loft"
     bl_description = "Lofts selected curves"
+    bl_options = {'UNDO'}
 
 
     @classmethod
@@ -258,6 +261,7 @@ class OperatorSweepCurves(bpy.types.Operator):
     bl_idname = "curvetools.operatorsweepcurves"
     bl_label = "Sweep"
     bl_description = "Sweeps the active curve along to other curve (rail)"
+    bl_options = {'UNDO'}
 
 
     @classmethod
@@ -283,6 +287,7 @@ class OperatorBirail(bpy.types.Operator):
     bl_idname = "curvetools.operatorbirail"
     bl_label = "Birail"
     bl_description = "Generates a birailed surface from 3 selected curves -- in order: rail1, rail2 and profile"
+    bl_options = {'UNDO'}
 
 
     @classmethod
@@ -306,6 +311,7 @@ class OperatorSplinesSetResolution(bpy.types.Operator):
     bl_idname = "curvetools.operatorsplinessetresolution"
     bl_label = "SplinesSetResolution"
     bl_description = "Sets the resolution of all splines"
+    bl_options = {'UNDO'}
 
 
     @classmethod
@@ -330,6 +336,7 @@ class OperatorSplinesRemoveZeroSegment(bpy.types.Operator):
     bl_idname = "curvetools.operatorsplinesremovezerosegment"
     bl_label = "SplinesRemoveZeroSegment"
     bl_description = "Removes splines with no segments -- they seem to creep up, sometimes"
+    bl_options = {'UNDO'}
 
 
     @classmethod
@@ -364,6 +371,7 @@ class OperatorSplinesRemoveShort(bpy.types.Operator):
     bl_idname = "curvetools.operatorsplinesremoveshort"
     bl_label = "SplinesRemoveShort"
     bl_description = "Removes splines with a length smaller than the threshold"
+    bl_options = {'UNDO'}
 
 
     @classmethod
@@ -393,6 +401,7 @@ class OperatorSplinesJoinNeighbouring(bpy.types.Operator):
     bl_idname = "curvetools.operatorsplinesjoinneighbouring"
     bl_label = "SplinesJoinNeighbouring"
     bl_description = "Joins neighbouring splines within a distance smaller than the threshold"
+    bl_options = {'UNDO'}
 
 
     @classmethod
diff --git a/curve_tools/path_finder.py b/curve_tools/path_finder.py
index abf53a6cddf6570ccb3b6e589541b894740d01c5..242ed27cd78438e3fb5dc7ac6a0606ce24c09c33 100644
--- a/curve_tools/path_finder.py
+++ b/curve_tools/path_finder.py
@@ -3,8 +3,8 @@
 bl_info = {
     'name': 'PathFinder',
     'author': 'Spivak Vladimir (cwolf3d)',
-    'version': (0, 5, 0),
-    'blender': (2, 80, 0),
+    'version': (0, 5, 1),
+    'blender': (3, 0, 0),
     'location': 'Curve Tools addon. (N) Panel',
     'description': 'PathFinder - quick search, selection, removal of splines',
     'warning': '', # used for warning icon and text in addons panel
@@ -17,7 +17,6 @@ import time
 import threading
 
 import gpu
-import bgl
 from gpu_extras.batch import batch_for_shader
 
 import bpy
@@ -85,8 +84,8 @@ def draw_bezier_points(self, context, spline, matrix_world, path_color, path_thi
 
     shader.bind()
     shader.uniform_float("color", path_color)
-    bgl.glEnable(bgl.GL_BLEND)
-    bgl.glLineWidth(path_thickness)
+    gpu.state.blend_set('ALPHA')
+    gpu.state.line_width_set(path_thickness)
     batch.draw(shader)
 
 def draw_points(self, context, spline, matrix_world, path_color, path_thickness):
@@ -98,8 +97,8 @@ def draw_points(self, context, spline, matrix_world, path_color, path_thickness)
 
     shader.bind()
     shader.uniform_float("color", path_color)
-    bgl.glEnable(bgl.GL_BLEND)
-    bgl.glLineWidth(path_thickness)
+    gpu.state.blend_set('ALPHA')
+    gpu.state.line_width_set(path_thickness)
     batch.draw(shader)
 
 def near(location3D, point, radius):
diff --git a/curve_tools/show_resolution.py b/curve_tools/show_resolution.py
index b2dbda7fac0926d99632ce355664fd4f4cbb0282..07a06540ea83c6d1e8eb3d57c597f9015be76c96 100644
--- a/curve_tools/show_resolution.py
+++ b/curve_tools/show_resolution.py
@@ -6,7 +6,6 @@ import bpy
 from bpy import *
 from bpy.props import *
 
-import bgl
 import blf
 import gpu
 from gpu_extras.batch import batch_for_shader
diff --git a/curve_tools/splines_sequence.py b/curve_tools/splines_sequence.py
index 987660582a4d3a7747cfadc22b82c9f06b6a59fd..89b9624c9f705b02c2319f60ce28042e92cfb2a5 100644
--- a/curve_tools/splines_sequence.py
+++ b/curve_tools/splines_sequence.py
@@ -3,7 +3,6 @@
 
 import bpy
 
-import bgl
 import blf
 import gpu
 from gpu_extras.batch import batch_for_shader
@@ -66,7 +65,7 @@ def draw(self, context, splines, sequence_color, font_thickness, font_size, matr
             batch = batch_for_shader(shader, 'LINES', {"pos": points})
 
             shader.bind()
-            bgl.glLineWidth(font_thickness)
+            gpu.state.line_width_set(font_thickness)
             shader.uniform_float("color", sequence_color)
             batch.draw(shader)
             i += font_size + font_size * 0.5
@@ -215,6 +214,7 @@ class RearrangeSpline(bpy.types.Operator):
     bl_idname = "curvetools.rearrange_spline"
     bl_label = "Rearrange Spline"
     bl_description = "Rearrange Spline"
+    bl_options = {'UNDO'}
 
     Types = [('NEXT', "Next", "next"),
              ('PREV', "Prev", "prev")]
diff --git a/depsgraph_debug.py b/depsgraph_debug.py
index 4405eb8e3b67d08bc06db162744fcaf36edc7001..7c8784a290d26ce4e9ce54139e7a91d1bf39bdae 100644
--- a/depsgraph_debug.py
+++ b/depsgraph_debug.py
@@ -211,6 +211,35 @@ class SCENE_OT_depsgraph_stats_image(Operator,
         return True
 
 
+class SCENE_OT_depsgraph_relations_svg(Operator,
+                                       SCENE_OT_depsgraph_image_common):
+    bl_idname = "scene.depsgraph_relations_svg"
+    bl_label = "Depsgraph as SVG in Browser"
+    bl_description = "Create an SVG image from the dependency graph and open it in the web browser"
+
+    def performSave(self, context, depsgraph):
+        import os
+        import subprocess
+        import webbrowser
+        # Create temporary file.
+        dot_filepath = self._createTempFile(suffix=".dot")
+        # Save dependency graph to graphviz file.
+        depsgraph.debug_relations_graphviz(dot_filepath)
+        # Convert graphviz to SVG image.
+        svg_filepath = os.path.join(bpy.app.tempdir, "depsgraph.svg")
+        command = ("dot", "-Tsvg", dot_filepath, "-o", svg_filepath)
+        try:
+            subprocess.run(command)
+            webbrowser.open_new_tab("file://" + os.path.abspath(svg_filepath))
+        except:
+            self.report({'ERROR'}, "Error invoking dot command")
+            return False
+        finally:
+            # Remove graphviz file.
+            os.remove(dot_filepath)
+        return True
+
+
 ###############################################################################
 # Interface.
 
@@ -224,6 +253,7 @@ class SCENE_PT_depsgraph_common:
         row = col.row()
         row.operator("scene.depsgraph_relations_graphviz")
         row.operator("scene.depsgraph_relations_image")
+        col.operator("scene.depsgraph_relations_svg")
         # Everything related on evaluaiton statistics.
         col.label(text="Statistics:")
         row = col.row()
@@ -264,6 +294,7 @@ class RENDERLAYER_PT_depsgraph(bpy.types.Panel, SCENE_PT_depsgraph_common):
 def register():
     bpy.utils.register_class(SCENE_OT_depsgraph_relations_graphviz)
     bpy.utils.register_class(SCENE_OT_depsgraph_relations_image)
+    bpy.utils.register_class(SCENE_OT_depsgraph_relations_svg)
     bpy.utils.register_class(SCENE_OT_depsgraph_stats_gnuplot)
     bpy.utils.register_class(SCENE_OT_depsgraph_stats_image)
     bpy.utils.register_class(SCENE_PT_depsgraph)
@@ -273,6 +304,7 @@ def register():
 def unregister():
     bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_graphviz)
     bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_image)
+    bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_svg)
     bpy.utils.unregister_class(SCENE_OT_depsgraph_stats_gnuplot)
     bpy.utils.unregister_class(SCENE_OT_depsgraph_stats_image)
     bpy.utils.unregister_class(SCENE_PT_depsgraph)
diff --git a/greasepencil_tools/__init__.py b/greasepencil_tools/__init__.py
index 980493ed3d14a1e76509bdea6ee40ae77294839c..c75d0442f6b7064d73bb3a7c83e2fd9d2e472cf3 100644
--- a/greasepencil_tools/__init__.py
+++ b/greasepencil_tools/__init__.py
@@ -4,8 +4,8 @@ bl_info = {
 "name": "Grease Pencil Tools",
 "description": "Extra tools for Grease Pencil",
 "author": "Samuel Bernou, Antonio Vazquez, Daniel Martinez Lara, Matias Mendiola",
-"version": (1, 6, 0),
-"blender": (2, 91, 0),
+"version": (1, 6, 1),
+"blender": (3, 0, 0),
 "location": "Sidebar > Grease Pencil > Grease Pencil Tools",
 "warning": "",
 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/greasepencil_tools.html",
diff --git a/greasepencil_tools/rotate_canvas.py b/greasepencil_tools/rotate_canvas.py
index a733eb2d464a56808f3d986c427fd5e1b7d5ec47..c2482fbdb6f60cda5693eb414e05e9745f7b13ed 100644
--- a/greasepencil_tools/rotate_canvas.py
+++ b/greasepencil_tools/rotate_canvas.py
@@ -10,7 +10,6 @@ from bpy.props import BoolProperty, EnumProperty
 from time import time
 ## draw utils
 import gpu
-import bgl
 import blf
 from gpu_extras.batch import batch_for_shader
 from gpu_extras.presets import draw_circle_2d
@@ -31,8 +30,8 @@ def draw_callback_px(self, context):
     if context.area != self.current_area:
         return
     shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
-    bgl.glEnable(bgl.GL_BLEND)
-    bgl.glLineWidth(2)
+    gpu.state.blend_set('ALPHA')
+    gpu.state.line_width_set(2.0)
 
     # init
     batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": [self.center, self.initial_pos]})#self.vector_initial
@@ -55,8 +54,8 @@ def draw_callback_px(self, context):
     # batch.draw(shader)
 
     # restore opengl defaults
-    bgl.glLineWidth(1)
-    bgl.glDisable(bgl.GL_BLEND)
+    gpu.state.line_width_set(1.0)
+    gpu.state.blend_set('NONE')
 
     ## text
     font_id = 0
diff --git a/greasepencil_tools/timeline_scrub.py b/greasepencil_tools/timeline_scrub.py
index 6a1913ae4340372b0c9badc77febde334497e13d..1f6c2a41087ac2d9e4f52cb73f73aa276b559376 100644
--- a/greasepencil_tools/timeline_scrub.py
+++ b/greasepencil_tools/timeline_scrub.py
@@ -8,7 +8,6 @@ import numpy as np
 from time import time
 import bpy
 import gpu
-import bgl
 import blf
 from gpu_extras.batch import batch_for_shader
 
@@ -39,8 +38,8 @@ def draw_callback_px(self, context):
     font_id = 0
 
     shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')  # initiate shader
-    bgl.glEnable(bgl.GL_BLEND)
-    bgl.glLineWidth(1)
+    gpu.state.blend_set('ALPHA')
+    gpu.state.line_width_set(1.0)
 
     # Draw HUD
     if self.use_hud_time_line:
@@ -51,18 +50,18 @@ def draw_callback_px(self, context):
     # Display keyframes
     if self.use_hud_keyframes:
         if self.keyframe_aspect == 'LINE':
-            bgl.glLineWidth(3)
+            gpu.state.line_width_set(3.0)
             shader.bind()
             shader.uniform_float("color", self.color_timeline)
             self.batch_keyframes.draw(shader)
         else:
-            bgl.glLineWidth(1)
+            gpu.state.line_width_set(1.0)
             shader.bind()
             shader.uniform_float("color", self.color_timeline)
             self.batch_keyframes.draw(shader)
 
     # Show current frame line
-    bgl.glLineWidth(1)
+    gpu.state.line_width_set(1.0)
     if self.use_hud_playhead:
         playhead = [(self.cursor_x, self.my + self.playhead_size/2),
                     (self.cursor_x, self.my - self.playhead_size/2)]
@@ -72,7 +71,7 @@ def draw_callback_px(self, context):
         batch.draw(shader)
 
     # restore opengl defaults
-    bgl.glDisable(bgl.GL_BLEND)
+    gpu.state.blend_set('NONE')
 
     # Display current frame text
     blf.color(font_id, *self.color_text)
diff --git a/io_import_images_as_planes.py b/io_import_images_as_planes.py
index 50a562382cd80c744eb017b0ccc6772cd6c3ebb5..ef63ec5230fc9ffc66de11193bd10f7e3aaefeae 100644
--- a/io_import_images_as_planes.py
+++ b/io_import_images_as_planes.py
@@ -2,8 +2,8 @@
 
 bl_info = {
     "name": "Import Images as Planes",
-    "author": "Florian Meyer (tstscr), mont29, matali, Ted Schundler (SpkyElctrc)",
-    "version": (3, 4, 0),
+    "author": "Florian Meyer (tstscr), mont29, matali, Ted Schundler (SpkyElctrc), mrbimax",
+    "version": (3, 5, 0),
     "blender": (2, 91, 0),
     "location": "File > Import > Images as Planes or Add > Mesh > Images as Planes",
     "description": "Imports images and creates planes with the appropriate aspect ratio. "
@@ -718,6 +718,34 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
         name="Strength", min=0.0, default=1.0, soft_max=10.0,
         step=100, description="Brightness of Emission Texture")
 
+    use_transparency: BoolProperty(
+        name="Use Alpha", default=True,
+        description="Use alpha channel for transparency")
+
+    BLEND_METHODS = (
+        ('BLEND',"Blend","Render polygon transparent, depending on alpha channel of the texture"),
+        ('CLIP', "Clip","Use the alpha threshold to clip the visibility (binary visibility)"),
+        ('HASHED', "Hashed","Use noise to dither the binary visibility (works well with multi-samples)"),
+        ('OPAQUE', "Opaque","Render surface without transparency"),
+    )
+    blend_method: EnumProperty(name="Blend Mode", items=BLEND_METHODS, default='BLEND', description="Blend Mode for Transparent Faces")
+
+    SHADOW_METHODS = (
+        ('CLIP', "Clip","Use the alpha threshold to clip the visibility (binary visibility)"),
+        ('HASHED', "Hashed","Use noise to dither the binary visibility (works well with multi-samples)"),
+        ('OPAQUE',"Opaque","Material will cast shadows without transparency"),
+        ('NONE',"None","Material will cast no shadow"),
+    )
+    shadow_method: EnumProperty(name="Shadow Mode", items=SHADOW_METHODS, default='CLIP', description="Shadow mapping method")
+
+    use_backface_culling: BoolProperty(
+        name="Backface Culling", default=False,
+        description="Use back face culling to hide the back side of faces")
+
+    show_transparent_back: BoolProperty(
+        name="Show Backface", default=True,
+        description="Render multiple transparent layers (may introduce transparency sorting problems)")
+
     overwrite_material: BoolProperty(
         name="Overwrite Material", default=True,
         description="Overwrite existing Material (based on material name)")
@@ -729,9 +757,20 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
 
     # ------------------
     # Properties - Image
-    use_transparency: BoolProperty(
-        name="Use Alpha", default=True,
-        description="Use alpha channel for transparency")
+    INTERPOLATION_MODES = (
+        ('Linear', "Linear", "Linear interpolation"),
+        ('Closest', "Closest", "No interpolation (sample closest texel)"),
+        ('Cubic', "Cubic", "Cubic interpolation"),
+        ('Smart', "Smart", "Bicubic when magnifying, else bilinear (OSL only)"),
+    )
+    interpolation: EnumProperty(name="Interpolation", items=INTERPOLATION_MODES, default='Linear', description="Texture interpolation")
+
+    EXTENSION_MODES = (
+        ('CLIP', "Clip", "Clip to image size and set exterior pixels as transparent"),
+        ('EXTEND', "Extend", "Extend by repeating edge pixels of the image"),
+        ('REPEAT', "Repeat", "Cause the image to repeat horizontally and vertically"),
+    )
+    extension: EnumProperty(name="Extension", items=EXTENSION_MODES, default='CLIP', description="How the image is extrapolated past its original bounds")
 
     t = bpy.types.Image.bl_rna.properties["alpha_mode"]
     alpha_mode_items = tuple((e.identifier, e.name, e.description) for e in t.enum_items)
@@ -766,27 +805,53 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
 
         box.label(text="Compositing Nodes:", icon='RENDERLAYERS')
         box.prop(self, "compositing_nodes")
-
+        layout = self.layout
+        box = layout.box()
         box.label(text="Material Settings:", icon='MATERIAL')
 
+        box.label(text="Material Type")
         row = box.row()
         row.prop(self, 'shader', expand=True)
         if self.shader == 'EMISSION':
             box.prop(self, "emit_strength")
 
+        box.label(text="Blend Mode")
+        row = box.row()
+        row.prop(self, 'blend_method', expand=True)
+        if self.use_transparency and self.alpha_mode != "NONE" and self.blend_method == "OPAQUE":
+            box.label(text="'Opaque' does not support alpha", icon="ERROR")
+        if self.blend_method == 'BLEND':
+            row = box.row()
+            row.prop(self, "show_transparent_back")
+
+        box.label(text="Shadow Mode")
+        row = box.row()
+        row.prop(self, 'shadow_method', expand=True)
+
+        row = box.row()
+        row.prop(self, "use_backface_culling")
+
         engine = context.scene.render.engine
         if engine not in ('CYCLES', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'):
             box.label(text="%s is not supported" % engine, icon='ERROR')
 
         box.prop(self, "overwrite_material")
-
+        layout = self.layout
+        box = layout.box()
         box.label(text="Texture Settings:", icon='TEXTURE')
+        box.label(text="Interpolation")
+        row = box.row()
+        row.prop(self, 'interpolation', expand=True)
+        box.label(text="Extension")
+        row = box.row()
+        row.prop(self, 'extension', expand=True)
         row = box.row()
         row.prop(self, "use_transparency")
-        sub = row.row()
-        sub.active = self.use_transparency
-        sub.prop(self, "alpha_mode", text="")
-        box.prop(self, "use_auto_refresh")
+        if self.use_transparency:
+            sub = row.row()
+            sub.prop(self, "alpha_mode", text="")
+        row = box.row()
+        row.prop(self, "use_auto_refresh")
 
     def draw_spatial_config(self, context):
         # --- Spatial Properties: Position, Size and Orientation --- #
@@ -970,6 +1035,8 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
         tex_image = node_tree.nodes.new('ShaderNodeTexImage')
         tex_image.image = img_spec.image
         tex_image.show_texture = True
+        tex_image.interpolation = self.interpolation
+        tex_image.extension = self.extension
         self.apply_texture_options(tex_image, img_spec)
         return tex_image
 
@@ -985,8 +1052,13 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
             material = bpy.data.materials.new(name=name_compat)
 
         material.use_nodes = True
-        if self.use_transparency:
-            material.blend_method = 'BLEND'
+
+        material.blend_method = self.blend_method
+        material.shadow_method = self.shadow_method
+
+        material.use_backface_culling = self.use_backface_culling
+        material.show_transparent_back = self.show_transparent_back
+
         node_tree = material.node_tree
         out_node = clean_node_tree(node_tree)
 
diff --git a/io_mesh_stl/blender_utils.py b/io_mesh_stl/blender_utils.py
index fdf353df2a1d32a68bf3cfb5bc235dcd1428efea..5c84152e930a959ae6501740d497561455ba71ba 100644
--- a/io_mesh_stl/blender_utils.py
+++ b/io_mesh_stl/blender_utils.py
@@ -48,7 +48,7 @@ def faces_from_mesh(ob, global_matrix, use_mesh_modifiers=False):
     """
     From an object, return a generator over a list of faces.
 
-    Each faces is a list of his vertexes. Each vertex is a tuple of
+    Each faces is a list of his vertices. Each vertex is a tuple of
     his coordinate.
 
     use_mesh_modifiers
diff --git a/io_mesh_stl/stl_utils.py b/io_mesh_stl/stl_utils.py
index 465806abcfb1d18878991e2f2b22b3df66465d54..e041c5276aca83bb234c4ab4f2692a7f6306b2be 100644
--- a/io_mesh_stl/stl_utils.py
+++ b/io_mesh_stl/stl_utils.py
@@ -168,7 +168,7 @@ def _binary_write(filepath, faces):
 
         for face in faces:
             # calculate face normal
-            # write normal + vertexes + pad as attributes
+            # write normal + vertices + pad as attributes
             fw(struct.pack('<3f', *normal(*face)) + pack(*itertools.chain.from_iterable(face)))
             # attribute byte count (unused)
             fw(b'\0\0')
diff --git a/io_mesh_uv_layout/__init__.py b/io_mesh_uv_layout/__init__.py
index 324f2254e190db1b96a10652ad41f6c7723fc753..40d9b5016d0b4abae9f6b3bcbadc3dac7a30a7c0 100644
--- a/io_mesh_uv_layout/__init__.py
+++ b/io_mesh_uv_layout/__init__.py
@@ -3,8 +3,8 @@
 bl_info = {
     "name": "UV Layout",
     "author": "Campbell Barton, Matt Ebb",
-    "version": (1, 1, 1),
-    "blender": (2, 80, 0),
+    "version": (1, 1, 3),
+    "blender": (3, 0, 0),
     "location": "Image-Window > UVs > Export UV Layout",
     "description": "Export the UV layout as a 2D graphic",
     "warning": "",
@@ -128,10 +128,10 @@ class ExportUVLayout(bpy.types.Operator):
         polygon_data = list(self.iter_polygon_data_to_draw(context, meshes))
         different_colors = set(color for _, color in polygon_data)
         if self.modified:
-          depsgraph = context.evaluated_depsgraph_get()
-          for obj in self.iter_objects_to_export(context):
-              obj_eval = obj.evaluated_get(depsgraph)
-              obj_eval.to_mesh_clear()
+            depsgraph = context.evaluated_depsgraph_get()
+            for obj in self.iter_objects_to_export(context):
+                obj_eval = obj.evaluated_get(depsgraph)
+                obj_eval.to_mesh_clear()
 
         export = self.get_exporter()
         export(filepath, polygon_data, different_colors, self.size[0], self.size[1], self.opacity)
diff --git a/io_mesh_uv_layout/export_uv_eps.py b/io_mesh_uv_layout/export_uv_eps.py
index 04b8a38e53133869b28b7210d9213d726b8598e4..9e013e13ef99ffbddc7e515103bd513057d1b426 100644
--- a/io_mesh_uv_layout/export_uv_eps.py
+++ b/io_mesh_uv_layout/export_uv_eps.py
@@ -8,6 +8,7 @@ def export(filepath, face_data, colors, width, height, opacity):
         for text in get_file_parts(face_data, colors, width, height, opacity):
             file.write(text)
 
+
 def get_file_parts(face_data, colors, width, height, opacity):
     yield from header(width, height)
     if opacity > 0.0:
@@ -35,6 +36,7 @@ def header(width, height):
     yield "1 setlinejoin\n"
     yield "1 setlinecap\n"
 
+
 def prepare_colors(colors, out_name_by_color):
     for i, color in enumerate(colors):
         name = f"COLOR_{i}"
@@ -48,18 +50,21 @@ def prepare_colors(colors, out_name_by_color):
         yield "0 setgray\n"
         yield "} def\n"
 
+
 def draw_colored_polygons(face_data, name_by_color, width, height):
     for uvs, color in face_data:
         yield from draw_polygon_path(uvs, width, height)
         yield "closepath\n"
         yield "%s\n" % name_by_color[color]
 
+
 def draw_lines(face_data, width, height):
     for uvs, _ in face_data:
         yield from draw_polygon_path(uvs, width, height)
         yield "closepath\n"
         yield "stroke\n"
 
+
 def draw_polygon_path(uvs, width, height):
     yield "newpath\n"
     for j, uv in enumerate(uvs):
@@ -69,6 +74,7 @@ def draw_polygon_path(uvs, width, height):
         else:
             yield "%.5f %.5f lineto\n" % uv_scale
 
+
 def footer():
     yield "showpage\n"
     yield "%%EOF\n"
diff --git a/io_mesh_uv_layout/export_uv_png.py b/io_mesh_uv_layout/export_uv_png.py
index 958bac8ea50579f54b737f330343e7230ac45485..b051c980c4b651ecfeaecfa8f5e1c5e7929a324f 100644
--- a/io_mesh_uv_layout/export_uv_png.py
+++ b/io_mesh_uv_layout/export_uv_png.py
@@ -2,31 +2,30 @@
 
 import bpy
 import gpu
-import bgl
 from mathutils import Vector, Matrix
 from mathutils.geometry import tessellate_polygon
 from gpu_extras.batch import batch_for_shader
 
+
 def export(filepath, face_data, colors, width, height, opacity):
     offscreen = gpu.types.GPUOffScreen(width, height)
     offscreen.bind()
 
     try:
-        bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
-        bgl.glClear(bgl.GL_COLOR_BUFFER_BIT)
+        fb = gpu.state.active_framebuffer_get()
+        fb.clear(color=(0.0, 0.0, 0.0, 0.0))
         draw_image(face_data, opacity)
 
-        pixel_data = get_pixel_data_from_current_back_buffer(width, height)
+        pixel_data = fb.read_color(0, 0, width, height, 4, 0, 'UBYTE')
+        pixel_data.dimensions = width * height * 4
         save_pixels(filepath, pixel_data, width, height)
     finally:
         offscreen.unbind()
         offscreen.free()
 
+
 def draw_image(face_data, opacity):
-    bgl.glLineWidth(1)
-    bgl.glEnable(bgl.GL_BLEND)
-    bgl.glEnable(bgl.GL_LINE_SMOOTH)
-    bgl.glHint(bgl.GL_LINE_SMOOTH_HINT, bgl.GL_NICEST)
+    gpu.state.blend_set('ALPHA_PREMULT')
 
     with gpu.matrix.push_pop():
         gpu.matrix.load_matrix(get_normalize_uvs_matrix())
@@ -35,8 +34,8 @@ def draw_image(face_data, opacity):
         draw_background_colors(face_data, opacity)
         draw_lines(face_data)
 
-    bgl.glDisable(bgl.GL_BLEND)
-    bgl.glDisable(bgl.GL_LINE_SMOOTH)
+    gpu.state.blend_set('NONE')
+
 
 def get_normalize_uvs_matrix():
     '''matrix maps x and y coordinates from [0, 1] to [-1, 1]'''
@@ -47,6 +46,7 @@ def get_normalize_uvs_matrix():
     matrix[1][1] = 2
     return matrix
 
+
 def draw_background_colors(face_data, opacity):
     coords = [uv for uvs, _ in face_data for uv in uvs]
     colors = [(*color, opacity) for uvs, color in face_data for _ in range(len(uvs))]
@@ -59,35 +59,38 @@ def draw_background_colors(face_data, opacity):
         offset += len(uvs)
 
     shader = gpu.shader.from_builtin('2D_FLAT_COLOR')
-    batch = batch_for_shader(shader, 'TRIS',
-        {"pos" : coords,
-         "color" : colors},
-        indices=indices)
+    batch = batch_for_shader(
+        shader, 'TRIS',
+        {"pos": coords, "color": colors},
+        indices=indices,
+    )
     batch.draw(shader)
 
+
 def tessellate_uvs(uvs):
     return tessellate_polygon([uvs])
 
+
 def draw_lines(face_data):
     coords = []
     for uvs, _ in face_data:
         for i in range(len(uvs)):
             start = uvs[i]
-            end = uvs[(i+1) % len(uvs)]
+            end = uvs[(i + 1) % len(uvs)]
             coords.append((start[0], start[1]))
             coords.append((end[0], end[1]))
 
-    shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
-    batch = batch_for_shader(shader, 'LINES', {"pos" : coords})
+    # Use '2D_UNIFORM_COLOR' in the `batch_for_shader` so we don't need to
+    # convert the coordinates to 3D as in the case of
+    # '3D_POLYLINE_UNIFORM_COLOR'.
+    batch = batch_for_shader(gpu.shader.from_builtin('2D_UNIFORM_COLOR'), 'LINES', {"pos": coords})
+    shader = gpu.shader.from_builtin('3D_POLYLINE_UNIFORM_COLOR')
     shader.bind()
+    shader.uniform_float("viewportSize", gpu.state.viewport_get()[2:])
+    shader.uniform_float("lineWidth", 0.5)
     shader.uniform_float("color", (0, 0, 0, 1))
     batch.draw(shader)
 
-def get_pixel_data_from_current_back_buffer(width, height):
-    buffer = bgl.Buffer(bgl.GL_BYTE, width * height * 4)
-    bgl.glReadBuffer(bgl.GL_BACK)
-    bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)
-    return buffer
 
 def save_pixels(filepath, pixel_data, width, height):
     image = bpy.data.images.new("temp", width, height, alpha=True)
diff --git a/io_mesh_uv_layout/export_uv_svg.py b/io_mesh_uv_layout/export_uv_svg.py
index d8e2b5a433d6759e6d49e3e07af5d5919a11cba2..a4b6a3c30a645fc1cec1aade7bbc631399d054e4 100644
--- a/io_mesh_uv_layout/export_uv_svg.py
+++ b/io_mesh_uv_layout/export_uv_svg.py
@@ -4,16 +4,19 @@ import bpy
 from os.path import basename
 from xml.sax.saxutils import escape
 
+
 def export(filepath, face_data, colors, width, height, opacity):
     with open(filepath, 'w', encoding='utf-8') as file:
         for text in get_file_parts(face_data, colors, width, height, opacity):
             file.write(text)
 
+
 def get_file_parts(face_data, colors, width, height, opacity):
     yield from header(width, height)
     yield from draw_polygons(face_data, width, height, opacity)
     yield from footer()
 
+
 def header(width, height):
     yield '<?xml version="1.0" standalone="no"?>\n'
     yield '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \n'
@@ -23,6 +26,7 @@ def header(width, height):
     desc = f"{basename(bpy.data.filepath)}, (Blender {bpy.app.version_string})"
     yield f'<desc>{escape(desc)}</desc>\n'
 
+
 def draw_polygons(face_data, width, height, opacity):
     for uvs, color in face_data:
         fill = f'fill="{get_color_string(color)}"'
@@ -37,10 +41,12 @@ def draw_polygons(face_data, width, height, opacity):
             yield f'{x*width:.3f},{y*height:.3f} '
         yield '" />\n'
 
+
 def get_color_string(color):
     r, g, b = color
     return f"rgb({round(r*255)}, {round(g*255)}, {round(b*255)})"
 
+
 def footer():
     yield '\n'
     yield '</svg>\n'
diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py
index 1b7e646dba279e342ff38ea9eab361b5e4fee739..feea84369b476aa80e3a2fcc317940c04faf9119 100644
--- a/io_scene_fbx/__init__.py
+++ b/io_scene_fbx/__init__.py
@@ -3,7 +3,7 @@
 bl_info = {
     "name": "FBX format",
     "author": "Campbell Barton, Bastien Montagne, Jens Restemeier",
-    "version": (4, 36, 2),
+    "version": (4, 36, 3),
     "blender": (3, 2, 0),
     "location": "File > Import-Export",
     "description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions",
diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py
index ae7be61c2d8a5c0bd1a518ec97cbc4ddf99991a8..f560b19c98bd2aa58008d4b5bec71b3033df184c 100644
--- a/io_scene_fbx/export_fbx_bin.py
+++ b/io_scene_fbx/export_fbx_bin.py
@@ -865,9 +865,9 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
         if last_subsurf:
             elem_data_single_int32(geom, b"Smoothness", 2) # Display control mesh and smoothed
             if last_subsurf.boundary_smooth == "PRESERVE_CORNERS":
-                elem_data_single_int32(geom, b"BoundaryRule", 2) # CreaseAll
+                elem_data_single_int32(geom, b"BoundaryRule", 1) # CreaseAll
             else:
-                elem_data_single_int32(geom, b"BoundaryRule", 1) # CreaseEdge
+                elem_data_single_int32(geom, b"BoundaryRule", 2) # CreaseEdge
             elem_data_single_int32(geom, b"PreviewDivisionLevels", last_subsurf.levels)
             elem_data_single_int32(geom, b"RenderDivisionLevels", last_subsurf.render_levels)
 
@@ -1528,7 +1528,7 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
             elem_data_single_int32(fbx_skin, b"Version", FBX_DEFORMER_SKIN_VERSION)
             elem_data_single_float64(fbx_skin, b"Link_DeformAcuracy", 50.0)  # Only vague idea what it is...
 
-            # Pre-process vertex weights (also to check vertices assigned ot more than four bones).
+            # Pre-process vertex weights (also to check vertices assigned to more than four bones).
             ob = ob_obj.bdata
             bo_vg_idx = {bo_obj.bdata.name: ob.vertex_groups[bo_obj.bdata.name].index
                          for bo_obj in clusters.keys() if bo_obj.bdata.name in ob.vertex_groups}
diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py
index 90f0c016cecdcff3121fe38c13f8536cc0286869..72d666c1c4396762f1de576f9e9f399fbc240166 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -2929,7 +2929,7 @@ def load(operator, context, filepath="",
                             mod.levels = preview_levels
                             mod.render_levels = render_levels
                             boundary_rule = elem_prop_first(elem_find_first(fbx_sdata, b'BoundaryRule'), default=1)
-                            if boundary_rule == 2:
+                            if boundary_rule == 1:
                                 mod.boundary_smooth = "PRESERVE_CORNERS"
                             else:
                                 mod.boundary_smooth = "ALL"
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index ce48f29326ce5ca04f25fb401eb5e5e57d77c8df..459a87953e960cfea17d07addbe389e3ceb9423d 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -4,7 +4,7 @@
 bl_info = {
     'name': 'glTF 2.0 format',
     'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
-    "version": (3, 3, 5),
+    "version": (3, 4, 10),
     'blender': (3, 3, 0),
     'location': 'File > Import-Export',
     'description': 'Import-Export as glTF 2.0',
@@ -259,6 +259,14 @@ class ExportGLTF2_Base:
         default='EXPORT'
     )
 
+    export_original_specular: BoolProperty(
+        name='Export original PBR Specular',
+        description=(
+            'Export original glTF PBR Specular, instead of Blender Principled Shader Specular'
+        ),
+        default=False,
+    )
+
     export_colors: BoolProperty(
         name='Vertex Colors',
         description='Export vertex colors with meshes',
@@ -372,9 +380,17 @@ class ExportGLTF2_Base:
         default=True
     )
 
+    export_nla_strips_merged_animation_name: StringProperty(
+        name='Merged Animation Name',
+        description=(
+            "Name of single glTF animation to be exported"
+        ),
+        default='Animation'
+    )
+
     export_def_bones: BoolProperty(
         name='Export Deformation Bones Only',
-        description='Export Deformation bones only (and needed bones for hierarchy)',
+        description='Export Deformation bones only',
         default=False
     )
 
@@ -387,6 +403,15 @@ class ExportGLTF2_Base:
         default=False
     )
 
+    export_anim_single_armature: BoolProperty(
+        name='Export all Armature Actions',
+        description=(
+            "Export all actions, bound to a single armature. "
+            "WARNING: Option does not support exports including multiple armatures"
+        ),
+        default=True
+    )
+
     export_current_frame: BoolProperty(
         name='Use Current Frame',
         description='Export the scene in the current animation frame',
@@ -430,13 +455,6 @@ class ExportGLTF2_Base:
         default=False
     )
 
-    export_displacement: BoolProperty(
-        name='Displacement Textures (EXPERIMENTAL)',
-        description='EXPERIMENTAL: Export displacement textures. '
-                    'Uses incomplete "KHR_materials_displacement" glTF extension',
-        default=False
-    )
-
     will_save_settings: BoolProperty(
         name='Remember Export Settings',
         description='Store glTF export settings in the Blender project',
@@ -547,6 +565,7 @@ class ExportGLTF2_Base:
         export_settings['gltf_colors'] = self.export_colors
         export_settings['gltf_cameras'] = self.export_cameras
 
+        export_settings['gltf_original_specular'] = self.export_original_specular
 
         export_settings['gltf_visible'] = self.use_visible
         export_settings['gltf_renderable'] = self.use_renderable
@@ -560,26 +579,28 @@ class ExportGLTF2_Base:
         export_settings['gltf_apply'] = self.export_apply
         export_settings['gltf_current_frame'] = self.export_current_frame
         export_settings['gltf_animations'] = self.export_animations
+        export_settings['gltf_def_bones'] = self.export_def_bones
         if self.export_animations:
             export_settings['gltf_frame_range'] = self.export_frame_range
             export_settings['gltf_force_sampling'] = self.export_force_sampling
-            if self.export_force_sampling:
-                export_settings['gltf_def_bones'] = self.export_def_bones
-            else:
+            if not self.export_force_sampling:
                 export_settings['gltf_def_bones'] = False
             export_settings['gltf_nla_strips'] = self.export_nla_strips
+            export_settings['gltf_nla_strips_merged_animation_name'] = self.export_nla_strips_merged_animation_name
             export_settings['gltf_optimize_animation'] = self.optimize_animation_size
+            export_settings['gltf_export_anim_single_armature'] = self.export_anim_single_armature
         else:
             export_settings['gltf_frame_range'] = False
             export_settings['gltf_move_keyframes'] = False
             export_settings['gltf_force_sampling'] = False
-            export_settings['gltf_def_bones'] = False
             export_settings['gltf_optimize_animation'] = False
+            export_settings['gltf_export_anim_single_armature'] = False
         export_settings['gltf_skins'] = self.export_skins
         if self.export_skins:
             export_settings['gltf_all_vertex_influences'] = self.export_all_influences
         else:
             export_settings['gltf_all_vertex_influences'] = False
+            export_settings['gltf_def_bones'] = False
         export_settings['gltf_frame_step'] = self.export_frame_step
         export_settings['gltf_morph'] = self.export_morph
         if self.export_morph:
@@ -592,7 +613,6 @@ class ExportGLTF2_Base:
             export_settings['gltf_morph_tangent'] = False
 
         export_settings['gltf_lights'] = self.export_lights
-        export_settings['gltf_displacement'] = self.export_displacement
 
         export_settings['gltf_binary'] = bytearray()
         export_settings['gltf_binaryfilename'] = (
@@ -737,6 +757,22 @@ class GLTF_PT_export_geometry(bpy.types.Panel):
 
         return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
 
+    def draw(self, context):
+        pass
+
+class GLTF_PT_export_geometry_mesh(bpy.types.Panel):
+    bl_space_type = 'FILE_BROWSER'
+    bl_region_type = 'TOOL_PROPS'
+    bl_label = "Mesh"
+    bl_parent_id = "GLTF_PT_export_geometry"
+    bl_options = {'DEFAULT_CLOSED'}
+
+    @classmethod
+    def poll(cls, context):
+        sfile = context.space_data
+        operator = sfile.active_operator
+        return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
+
     def draw(self, context):
         layout = self.layout
         layout.use_property_split = True
@@ -757,11 +793,56 @@ class GLTF_PT_export_geometry(bpy.types.Panel):
         col.prop(operator, 'use_mesh_edges')
         col.prop(operator, 'use_mesh_vertices')
 
+
+class GLTF_PT_export_geometry_material(bpy.types.Panel):
+    bl_space_type = 'FILE_BROWSER'
+    bl_region_type = 'TOOL_PROPS'
+    bl_label = "Material"
+    bl_parent_id = "GLTF_PT_export_geometry"
+    bl_options = {'DEFAULT_CLOSED'}
+
+    @classmethod
+    def poll(cls, context):
+        sfile = context.space_data
+        operator = sfile.active_operator
+        return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.use_property_split = True
+        layout.use_property_decorate = False  # No animation.
+
+        sfile = context.space_data
+        operator = sfile.active_operator
+
         layout.prop(operator, 'export_materials')
         col = layout.column()
         col.active = operator.export_materials == "EXPORT"
         col.prop(operator, 'export_image_format')
 
+class GLTF_PT_export_geometry_original_pbr(bpy.types.Panel):
+    bl_space_type = 'FILE_BROWSER'
+    bl_region_type = 'TOOL_PROPS'
+    bl_label = "PBR Extensions"
+    bl_parent_id = "GLTF_PT_export_geometry_material"
+    bl_options = {'DEFAULT_CLOSED'}
+
+    @classmethod
+    def poll(cls, context):
+        sfile = context.space_data
+        operator = sfile.active_operator
+        return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.use_property_split = True
+        layout.use_property_decorate = False  # No animation.
+
+        sfile = context.space_data
+        operator = sfile.active_operator
+
+        layout.prop(operator, 'export_original_specular')
+
 
 class GLTF_PT_export_geometry_compression(bpy.types.Panel):
     bl_space_type = 'FILE_BROWSER'
@@ -863,13 +944,10 @@ class GLTF_PT_export_animation_export(bpy.types.Panel):
         layout.prop(operator, 'export_frame_step')
         layout.prop(operator, 'export_force_sampling')
         layout.prop(operator, 'export_nla_strips')
+        if operator.export_nla_strips is False:
+            layout.prop(operator, 'export_nla_strips_merged_animation_name')
         layout.prop(operator, 'optimize_animation_size')
-
-        row = layout.row()
-        row.active = operator.export_force_sampling
-        row.prop(operator, 'export_def_bones')
-        if operator.export_force_sampling is False and operator.export_def_bones is True:
-            layout.label(text="Export only deformation bones is not possible when not sampling animation")
+        layout.prop(operator, 'export_anim_single_armature')
 
 
 class GLTF_PT_export_animation_shapekeys(bpy.types.Panel):
@@ -937,6 +1015,12 @@ class GLTF_PT_export_animation_skinning(bpy.types.Panel):
         layout.active = operator.export_skins
         layout.prop(operator, 'export_all_influences')
 
+        row = layout.row()
+        row.active = operator.export_force_sampling
+        row.prop(operator, 'export_def_bones')
+        if operator.export_force_sampling is False and operator.export_def_bones is True:
+            layout.label(text="Export only deformation bones is not possible when not sampling animation")
+
 class GLTF_PT_export_user_extensions(bpy.types.Panel):
     bl_space_type = 'FILE_BROWSER'
     bl_region_type = 'TOOL_PROPS'
@@ -1160,19 +1244,34 @@ class ImportGLTF2(Operator, ImportHelper):
             self.loglevel = logging.NOTSET
 
 
+def gltf_variant_ui_update(self, context):
+    from .blender.com.gltf2_blender_ui import variant_register, variant_unregister
+    if self.KHR_materials_variants_ui is True:
+        # register all needed types
+        variant_register()
+    else:
+        variant_unregister()
+
 class GLTF_AddonPreferences(bpy.types.AddonPreferences):
     bl_idname = __package__
 
     settings_node_ui : bpy.props.BoolProperty(
             default= False,
-            description="Displays glTF Settings node in Shader Editor (Menu Add > Ouput)"
+            description="Displays glTF Material Output node in Shader Editor (Menu Add > Output)"
             )
 
+    KHR_materials_variants_ui : bpy.props.BoolProperty(
+        default= False,
+        description="Displays glTF UI to manage material variants",
+        update=gltf_variant_ui_update
+        )
+
 
     def draw(self, context):
         layout = self.layout
         row = layout.row()
         row.prop(self, "settings_node_ui", text="Shader Editor Add-ons")
+        row.prop(self, "KHR_materials_variants_ui", text="Material Variants")
 
 def menu_func_import(self, context):
     self.layout.operator(ImportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
@@ -1184,6 +1283,9 @@ classes = (
     GLTF_PT_export_include,
     GLTF_PT_export_transform,
     GLTF_PT_export_geometry,
+    GLTF_PT_export_geometry_mesh,
+    GLTF_PT_export_geometry_material,
+    GLTF_PT_export_geometry_original_pbr,
     GLTF_PT_export_geometry_compression,
     GLTF_PT_export_animation,
     GLTF_PT_export_animation_export,
@@ -1203,6 +1305,8 @@ def register():
     # bpy.utils.register_module(__name__)
 
     blender_ui.register()
+    if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is True:
+        blender_ui.variant_register()
 
     # add to the export / import menu
     bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
@@ -1211,6 +1315,10 @@ def register():
 
 def unregister():
     import io_scene_gltf2.blender.com.gltf2_blender_ui as blender_ui
+    blender_ui.unregister()
+    if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is True:
+        blender_ui.variant_unregister()
+
     for c in classes:
         bpy.utils.unregister_class(c)
     for f in exporter_extension_panel_unregister_functors:
@@ -1221,8 +1329,6 @@ def unregister():
         f()
     importer_extension_panel_unregister_functors.clear()
 
-    blender_ui.unregister()
-
     # bpy.utils.unregister_module(__name__)
 
     # remove from the export / import menu
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_default.py b/io_scene_gltf2/blender/com/gltf2_blender_default.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3951f4e0f4c23b219888e33235ff6682e67c107
--- /dev/null
+++ b/io_scene_gltf2/blender/com/gltf2_blender_default.py
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+BLENDER_IOR = 1.45
+BLENDER_SPECULAR = 0.5
+BLENDER_SPECULAR_TINT = 0.0
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py
index 7b90b0a38d27846f87d47ebdb41d95944468aa5d..4f8417e9c7a6ad1c9597b7d2cfd298e22de5f89c 100755
--- a/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py
+++ b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py
@@ -3,13 +3,23 @@
 
 import bpy
 
-def get_gltf_node_name():
+# Get compatibility at export with old files
+def get_gltf_node_old_name():
     return "glTF Settings"
 
+def get_gltf_node_name():
+    return "glTF Material Output"
+
 def create_settings_group(name):
     gltf_node_group = bpy.data.node_groups.new(name, 'ShaderNodeTree')
     gltf_node_group.inputs.new("NodeSocketFloat", "Occlusion")
+    thicknessFactor  = gltf_node_group.inputs.new("NodeSocketFloat", "Thickness")
+    thicknessFactor.default_value = 0.0
     gltf_node_group.nodes.new('NodeGroupOutput')
     gltf_node_group_input = gltf_node_group.nodes.new('NodeGroupInput')
+    specular = gltf_node_group.inputs.new("NodeSocketFloat", "Specular")
+    specular.default_value = 1.0
+    specularColor = gltf_node_group.inputs.new("NodeSocketColor", "Specular Color")
+    specularColor.default_value = [1.0,1.0,1.0,1.0]
     gltf_node_group_input.location = -200, 0
-    return gltf_node_group
\ No newline at end of file
+    return gltf_node_group
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_ui.py b/io_scene_gltf2/blender/com/gltf2_blender_ui.py
index 59c364fb55a1856ff41e66418525bf696dde1772..0788bbbcf8946c4de85ea90d9bca7f0d3caa7f10 100644
--- a/io_scene_gltf2/blender/com/gltf2_blender_ui.py
+++ b/io_scene_gltf2/blender/com/gltf2_blender_ui.py
@@ -4,6 +4,8 @@
 import bpy
 from ..com.gltf2_blender_material_helpers import get_gltf_node_name, create_settings_group
 
+################ glTF Material Output node ###########################################
+
 def create_gltf_ao_group(operator, group_name):
 
     # create a new group
@@ -13,8 +15,8 @@ def create_gltf_ao_group(operator, group_name):
 
 class NODE_OT_GLTF_SETTINGS(bpy.types.Operator):
     bl_idname = "node.gltf_settings_node_operator"
-    bl_label  = "glTF Settings"
-
+    bl_label  = "glTF Material Output"
+    bl_description = "Add a node to the active tree for glTF export"
 
     @classmethod
     def poll(cls, context):
@@ -40,10 +42,446 @@ def add_gltf_settings_to_menu(self, context) :
     if bpy.context.preferences.addons['io_scene_gltf2'].preferences.settings_node_ui is True:
         self.layout.operator("node.gltf_settings_node_operator")
 
+################################### KHR_materials_variants ####################
+
+# Global UI panel
+
+class gltf2_KHR_materials_variants_variant(bpy.types.PropertyGroup):
+    variant_idx : bpy.props.IntProperty()
+    name : bpy.props.StringProperty(name="Variant Name")
+
+class SCENE_UL_gltf2_variants(bpy.types.UIList):
+    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+
+        if self.layout_type in {'DEFAULT', 'COMPACT'}:
+            layout.prop(item, "name", text="", emboss=False)
+
+        elif self.layout_type in {'GRID'}:
+            layout.alignment = 'CENTER'
+
+class SCENE_PT_gltf2_variants(bpy.types.Panel):
+    bl_label = "glTF Material Variants"
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'UI'
+    bl_category = "glTF Variants"
+
+    @classmethod
+    def poll(self, context):
+        return bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is True
+
+    def draw(self, context):
+        layout = self.layout
+        row = layout.row()
+
+        if bpy.data.scenes[0].get('gltf2_KHR_materials_variants_variants') and len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0:
+
+            row.template_list("SCENE_UL_gltf2_variants", "", bpy.data.scenes[0], "gltf2_KHR_materials_variants_variants", bpy.data.scenes[0], "gltf2_active_variant")
+            col = row.column()
+            row = col.column(align=True)
+            row.operator("scene.gltf2_variant_add", icon="ADD", text="")
+            row.operator("scene.gltf2_variant_remove", icon="REMOVE", text="")
+
+            row = layout.row()
+            row.operator("scene.gltf2_display_variant", text="Display Variant")
+            row = layout.row()
+            row.operator("scene.gltf2_assign_to_variant", text="Assign To Variant")
+            row = layout.row()
+            row.operator("scene.gltf2_reset_to_original", text="Reset To Original")
+            row.operator("scene.gltf2_assign_as_original", text="Assign as Original")
+        else:
+            row.operator("scene.gltf2_variant_add", text="Add Material Variant")
+
+class SCENE_OT_gltf2_variant_add(bpy.types.Operator):
+    """Add a new Material Variant"""
+    bl_idname = "scene.gltf2_variant_add"
+    bl_label = "Add Material Variant"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return True
+
+    def execute(self, context):
+        var = bpy.data.scenes[0].gltf2_KHR_materials_variants_variants.add()
+        var.variant_idx = len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) - 1
+        var.name = "VariantName"
+        bpy.data.scenes[0].gltf2_active_variant = len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) - 1
+        return {'FINISHED'}
+
+class SCENE_OT_gltf2_variant_remove(bpy.types.Operator):
+    """Add a new Material Variant"""
+    bl_idname = "scene.gltf2_variant_remove"
+    bl_label = "Remove Variant"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0
+
+    def execute(self, context):
+        bpy.data.scenes[0].gltf2_KHR_materials_variants_variants.remove(bpy.data.scenes[0].gltf2_active_variant)
+
+        # loop on all mesh
+        for obj in [o for o in bpy.data.objects if o.type == "MESH"]:
+            mesh = obj.data
+            remove_idx_data = []
+            for idx, i in enumerate(mesh.gltf2_variant_mesh_data):
+                remove_idx_variants = []
+                for idx_var, v in enumerate(i.variants):
+                    if v.variant.variant_idx == bpy.data.scenes[0].gltf2_active_variant:
+                        remove_idx_variants.append(idx_var)
+                    elif v.variant.variant_idx > bpy.data.scenes[0].gltf2_active_variant:
+                        v.variant.variant_idx -= 1
+
+                if len(remove_idx_variants) > 0:
+                    for idx_var in remove_idx_variants:
+                        i.variants.remove(idx_var)
+
+                if len(i.variants) == 0:
+                    remove_idx_data.append(idx)
+
+            if len(remove_idx_data) > 0:
+                for idx_data in remove_idx_data:
+                    mesh.gltf2_variant_mesh_data.remove(idx_data)
+                
+        return {'FINISHED'}    
+
+
+# Operator to display a variant
+class SCENE_OT_gltf2_display_variant(bpy.types.Operator):
+    bl_idname = "scene.gltf2_display_variant"
+    bl_label = "Display Variant"
+    bl_options = {'REGISTER'}
+
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0
+
+    def execute(self, context):
+
+        gltf2_active_variant = bpy.data.scenes[0].gltf2_active_variant
+
+        # loop on all mesh
+        for obj in [o for o in bpy.data.objects if o.type == "MESH"]:
+            mesh = obj.data
+            for i in mesh.gltf2_variant_mesh_data:
+                if i.variants and gltf2_active_variant in [v.variant.variant_idx for v in i.variants]:
+                    mat = i.material
+                    slot = i.material_slot_index
+                    if slot < len(obj.material_slots): # Seems user remove some slots...
+                        obj.material_slots[slot].material = mat
+
+        return {'FINISHED'}
+
+# Operator to assign current mesh materials to a variant
+class SCENE_OT_gltf2_assign_to_variant(bpy.types.Operator):
+    bl_idname = "scene.gltf2_assign_to_variant"
+    bl_label = "Assign To Variant"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0 \
+            and bpy.context.object.type == "MESH"
+
+    def execute(self, context):
+        gltf2_active_variant = bpy.data.scenes[0].gltf2_active_variant
+        obj = bpy.context.object
+
+        # loop on material slots ( primitives )
+        for mat_slot_idx, s in enumerate(obj.material_slots):
+            # Check if there is already data for this slot
+            found = False
+            for i in obj.data.gltf2_variant_mesh_data:
+                if i.material_slot_index == mat_slot_idx and i.material == s.material:
+                    found = True
+                    variant_primitive = i
+
+            if found is False:
+                variant_primitive = obj.data.gltf2_variant_mesh_data.add()
+                variant_primitive.material_slot_index = mat_slot_idx
+                variant_primitive.material = s.material
+
+            vari = variant_primitive.variants.add()
+            vari.variant.variant_idx = bpy.data.scenes[0].gltf2_active_variant
+
+        return {'FINISHED'}
+
+# Operator to reset mesh to original (using default material when exists)
+class SCENE_OT_gltf2_reset_to_original(bpy.types.Operator):
+    bl_idname = "scene.gltf2_reset_to_original"
+    bl_label = "Reset to Original"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return bpy.context.object.type == "MESH" and len(context.object.data.gltf2_variant_default_materials) > 0
+
+    def execute(self, context):
+        obj = bpy.context.object
+
+        # loop on material slots ( primitives )
+        for mat_slot_idx, s in enumerate(obj.material_slots):
+            # Check if there is a default material for this slot
+            found = False
+            for i in obj.data.gltf2_variant_default_materials:
+                if i.material_slot_index == mat_slot_idx:
+                    s.material = i.default_material
+                    break
+
+        return {'FINISHED'}
+
+# Operator to assign current materials as default materials
+class SCENE_OT_gltf2_assign_as_original(bpy.types.Operator):
+    bl_idname = "scene.gltf2_assign_as_original"
+    bl_label = "Assign as Original"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return bpy.context.object.type == "MESH"
+
+    def execute(self, context):
+        obj = bpy.context.object
+
+        # loop on material slots ( primitives )
+        for mat_slot_idx, s in enumerate(obj.material_slots):
+            # Check if there is a default material for this slot
+            found = False
+            for i in obj.data.gltf2_variant_default_materials:
+                if i.material_slot_index == mat_slot_idx:
+                    found = True
+                    # Update if needed
+                    i.default_material = s.material
+                    break
+
+            if found is False:
+                default_mat = obj.data.gltf2_variant_default_materials.add()
+                default_mat.material_slot_index = mat_slot_idx
+                default_mat.default_material = s.material
+
+        return {'FINISHED'}
+
+# Mesh Panel
+
+class gltf2_KHR_materials_variant_pointer(bpy.types.PropertyGroup):
+    variant: bpy.props.PointerProperty(type=gltf2_KHR_materials_variants_variant)
+
+class gltf2_KHR_materials_variants_default_material(bpy.types.PropertyGroup):
+    material_slot_index: bpy.props.IntProperty(name="Material Slot Index")
+    default_material: bpy.props.PointerProperty(type=bpy.types.Material)
+
+class gltf2_KHR_materials_variants_primitive(bpy.types.PropertyGroup):
+    material_slot_index : bpy.props.IntProperty(name="Material Slot Index")
+    material: bpy.props.PointerProperty(type=bpy.types.Material)
+    variants: bpy.props.CollectionProperty(type=gltf2_KHR_materials_variant_pointer)
+    active_variant_idx: bpy.props.IntProperty()
+
+class MESH_UL_gltf2_mesh_variants(bpy.types.UIList):
+    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+
+        vari = item.variant
+        layout.context_pointer_set("id", vari)
+
+        if self.layout_type in {'DEFAULT', 'COMPACT'}:
+            layout.prop(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants[vari.variant_idx], "name", text="", emboss=False)
+        elif self.layout_type in {'GRID'}:
+            layout.alignment = 'CENTER'
+
+class MESH_PT_gltf2_mesh_variants(bpy.types.Panel):
+    bl_label = "glTF Material Variants"
+    bl_space_type = 'PROPERTIES'
+    bl_region_type = 'WINDOW'
+    bl_context = "material"
+
+    @classmethod
+    def poll(self, context):
+        return bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is True \
+            and len(bpy.context.object.material_slots) > 0
+
+    def draw(self, context):
+        layout = self.layout
+
+        active_material_slots = bpy.context.object.active_material_index
+
+        found = False
+        if 'gltf2_variant_mesh_data' in bpy.context.object.data.keys():
+            for idx, prim in enumerate(bpy.context.object.data.gltf2_variant_mesh_data):
+                if prim.material_slot_index == active_material_slots and id(prim.material) == id(bpy.context.object.material_slots[active_material_slots].material):
+                    found = True
+                    break
+
+        row = layout.row()
+        if found is True:
+            row.template_list("MESH_UL_gltf2_mesh_variants", "", prim, "variants", prim, "active_variant_idx")
+            col = row.column()
+            row = col.column(align=True)
+            row.operator("scene.gltf2_variants_slot_add", icon="ADD", text="")
+            row.operator("scene.gltf2_remove_material_variant", icon="REMOVE", text="")
+
+            row = layout.row()
+            if 'gltf2_KHR_materials_variants_variants' in bpy.data.scenes[0].keys() and len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0:
+                row.prop_search(context.object.data, "gltf2_variant_pointer", bpy.data.scenes[0], "gltf2_KHR_materials_variants_variants", text="Variant")
+                row = layout.row()
+                row.operator("scene.gltf2_material_to_variant", text="Assign To Variant")
+            else:
+                row.label(text="Please Create a Variant First")
+        else:
+            if 'gltf2_KHR_materials_variants_variants' in bpy.data.scenes[0].keys() and len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0:
+                row.operator("scene.gltf2_variants_slot_add", text="Add a new Variant Slot")
+            else:
+                row.label(text="Please Create a Variant First")
+
+
+class SCENE_OT_gltf2_variant_slot_add(bpy.types.Operator):
+    """Add a new Slot"""
+    bl_idname = "scene.gltf2_variants_slot_add"
+    bl_label = "Add new Slot"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.context.object.material_slots) > 0
+
+    def execute(self, context):
+        mesh = context.object.data
+        # Check if there is already a data for this slot_idx + material
+
+        found = False
+        for i in mesh.gltf2_variant_mesh_data:
+            if i.material_slot_index == context.object.active_material_index and i.material == context.object.material_slots[context.object.active_material_index].material:
+                found = True
+                variant_primitive = i
+
+        if found is False:
+            variant_primitive = mesh.gltf2_variant_mesh_data.add()
+            variant_primitive.material_slot_index = context.object.active_material_index
+            variant_primitive.material = context.object.material_slots[context.object.active_material_index].material
+
+        vari = variant_primitive.variants.add()
+        vari.variant.variant_idx = bpy.data.scenes[0].gltf2_active_variant
+
+        return {'FINISHED'}
+
+class SCENE_OT_gltf2_material_to_variant(bpy.types.Operator):
+    """Assign Variant to Slot"""
+    bl_idname = "scene.gltf2_material_to_variant"
+    bl_label = "Assign Material To Variant"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.context.object.material_slots) > 0 and context.object.data.gltf2_variant_pointer != ""
+
+    def execute(self, context):
+        mesh = context.object.data
+
+        found = False
+        for i in mesh.gltf2_variant_mesh_data:
+            if i.material_slot_index == context.object.active_material_index and i.material == context.object.material_slots[context.object.active_material_index].material:
+                found = True
+                variant_primitive = i
+
+        if found is False:
+            return {'CANCELLED'}
+
+        vari = variant_primitive.variants[variant_primitive.active_variant_idx]
+
+        # Retrieve variant idx
+        found = False
+        for v in bpy.data.scenes[0].gltf2_KHR_materials_variants_variants:
+            if v.name == context.object.data.gltf2_variant_pointer:
+                found = True
+                break
+
+        if found is False:
+            return {'CANCELLED'}
+
+        vari.variant.variant_idx = v.variant_idx
+
+        return {'FINISHED'}
+
+class SCENE_OT_gltf2_remove_material_variant(bpy.types.Operator):
+    """Remove a variant Slot"""
+    bl_idname = "scene.gltf2_remove_material_variant"
+    bl_label = "Remove a variant Slot"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.context.object.material_slots) > 0 and len(bpy.context.object.data.gltf2_variant_mesh_data) > 0
+
+    def execute(self, context):
+        mesh = context.object.data
+
+        found = False
+        found_idx = -1
+        for idx, i in enumerate(mesh.gltf2_variant_mesh_data):
+            if i.material_slot_index == context.object.active_material_index and i.material == context.object.material_slots[context.object.active_material_index].material:
+                found = True
+                variant_primitive = i
+                found_idx = idx
+
+        if found is False:
+            return {'CANCELLED'}
+
+        variant_primitive.variants.remove(variant_primitive.active_variant_idx)
+
+        if len(variant_primitive.variants) == 0:
+            mesh.gltf2_variant_mesh_data.remove(found_idx)
+
+        return {'FINISHED'}
+
+
+###############################################################################
 
 def register():
     bpy.utils.register_class(NODE_OT_GLTF_SETTINGS)
     bpy.types.NODE_MT_category_SH_NEW_OUTPUT.append(add_gltf_settings_to_menu)
 
+def variant_register():
+    bpy.utils.register_class(SCENE_OT_gltf2_display_variant)
+    bpy.utils.register_class(SCENE_OT_gltf2_assign_to_variant)
+    bpy.utils.register_class(SCENE_OT_gltf2_reset_to_original)
+    bpy.utils.register_class(SCENE_OT_gltf2_assign_as_original)
+    bpy.utils.register_class(SCENE_OT_gltf2_remove_material_variant)
+    bpy.utils.register_class(gltf2_KHR_materials_variants_variant)
+    bpy.utils.register_class(gltf2_KHR_materials_variant_pointer)
+    bpy.utils.register_class(gltf2_KHR_materials_variants_primitive)
+    bpy.utils.register_class(gltf2_KHR_materials_variants_default_material)
+    bpy.utils.register_class(SCENE_UL_gltf2_variants)
+    bpy.utils.register_class(SCENE_PT_gltf2_variants)
+    bpy.utils.register_class(MESH_UL_gltf2_mesh_variants)
+    bpy.utils.register_class(MESH_PT_gltf2_mesh_variants)
+    bpy.utils.register_class(SCENE_OT_gltf2_variant_add)
+    bpy.utils.register_class(SCENE_OT_gltf2_variant_remove)
+    bpy.utils.register_class(SCENE_OT_gltf2_material_to_variant)
+    bpy.utils.register_class(SCENE_OT_gltf2_variant_slot_add)
+    bpy.types.Mesh.gltf2_variant_mesh_data = bpy.props.CollectionProperty(type=gltf2_KHR_materials_variants_primitive)
+    bpy.types.Mesh.gltf2_variant_default_materials = bpy.props.CollectionProperty(type=gltf2_KHR_materials_variants_default_material)
+    bpy.types.Mesh.gltf2_variant_pointer = bpy.props.StringProperty()
+    bpy.types.Scene.gltf2_KHR_materials_variants_variants = bpy.props.CollectionProperty(type=gltf2_KHR_materials_variants_variant)
+    bpy.types.Scene.gltf2_active_variant = bpy.props.IntProperty()
+
 def unregister():
     bpy.utils.unregister_class(NODE_OT_GLTF_SETTINGS)
+
+def variant_unregister():
+    bpy.utils.unregister_class(SCENE_OT_gltf2_variant_add)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_variant_remove)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_material_to_variant)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_variant_slot_add)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_display_variant)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_assign_to_variant)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_reset_to_original)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_assign_as_original)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_remove_material_variant)
+    bpy.utils.unregister_class(SCENE_PT_gltf2_variants)
+    bpy.utils.unregister_class(SCENE_UL_gltf2_variants)
+    bpy.utils.unregister_class(MESH_PT_gltf2_mesh_variants)
+    bpy.utils.unregister_class(MESH_UL_gltf2_mesh_variants)
+    bpy.utils.unregister_class(gltf2_KHR_materials_variants_default_material)
+    bpy.utils.unregister_class(gltf2_KHR_materials_variants_primitive)
+    bpy.utils.unregister_class(gltf2_KHR_materials_variants_variant)
+    bpy.utils.unregister_class(gltf2_KHR_materials_variant_pointer)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
index 812db3f99d6c9ee84ac4a1b13e17dc72609b85ce..96f97af14d14cb598d0c657fb397d357a876b6ee 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
@@ -20,7 +20,6 @@ RENDERABLE = 'gltf_renderable'
 ACTIVE_COLLECTION = 'gltf_active_collection'
 SKINS = 'gltf_skins'
 DEF_BONES_ONLY = 'gltf_def_bones'
-DISPLACEMENT = 'gltf_displacement'
 FORCE_SAMPLING = 'gltf_force_sampling'
 FRAME_RANGE = 'gltf_frame_range'
 FRAME_STEP = 'gltf_frame_step'
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
index d4f341264e2cbd96f491c33eb0620b88ef7cf508..93947acb3d71939906e1c117c5af78d9401d401f 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
@@ -39,6 +39,18 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups
     if export_settings[gltf2_blender_export_keys.COLORS]:
         color_max = len(blender_mesh.vertex_colors)
 
+    colors_attributes = []
+    rendered_color_idx = blender_mesh.attributes.render_color_index
+
+    if color_max > 0:
+        colors_attributes.append(rendered_color_idx)
+        # Then find other ones
+        colors_attributes.extend([
+            i for i in range(len(blender_mesh.color_attributes)) if i != rendered_color_idx \
+                and blender_mesh.vertex_colors.find(blender_mesh.color_attributes[i].name) != -1
+        ])
+
+
     armature = None
     skin = None
     if blender_vertex_groups and export_settings[gltf2_blender_export_keys.SKINS]:
@@ -110,7 +122,7 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups
         dot_fields += [('tx', np.float32), ('ty', np.float32), ('tz', np.float32), ('tw', np.float32)]
     for uv_i in range(tex_coord_max):
         dot_fields += [('uv%dx' % uv_i, np.float32), ('uv%dy' % uv_i, np.float32)]
-    for col_i in range(color_max):
+    for col_i, _ in enumerate(colors_attributes):
         dot_fields += [
             ('color%dr' % col_i, np.float32),
             ('color%dg' % col_i, np.float32),
@@ -163,8 +175,12 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups
         dots['uv%dy' % uv_i] = uvs[:, 1]
         del uvs
 
-    for col_i in range(color_max):
-        colors = __get_colors(blender_mesh, col_i)
+    colors_types = []
+    for col_i, blender_col_i in enumerate(colors_attributes):
+        colors, colors_type, domain = __get_colors(blender_mesh, col_i, blender_col_i)
+        if domain == "POINT":
+            colors = colors[dots['vertex_index']]
+        colors_types.append(colors_type)
         dots['color%dr' % col_i] = colors[:, 0]
         dots['color%dg' % col_i] = colors[:, 1]
         dots['color%db' % col_i] = colors[:, 2]
@@ -251,13 +267,16 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups
             uvs[:, 1] = prim_dots['uv%dy' % tex_coord_i]
             attributes['TEXCOORD_%d' % tex_coord_i] = uvs
 
-        for color_i in range(color_max):
+        for color_i, _ in enumerate(colors_attributes):
             colors = np.empty((len(prim_dots), 4), dtype=np.float32)
             colors[:, 0] = prim_dots['color%dr' % color_i]
             colors[:, 1] = prim_dots['color%dg' % color_i]
             colors[:, 2] = prim_dots['color%db' % color_i]
             colors[:, 3] = prim_dots['color%da' % color_i]
-            attributes['COLOR_%d' % color_i] = colors
+            attributes['COLOR_%d' % color_i] = {}
+            attributes['COLOR_%d' % color_i]["data"] = colors
+
+            attributes['COLOR_%d' % color_i]["norm"] = colors_types[color_i] == "BYTE_COLOR"
 
         if skin:
             joints = [[] for _ in range(num_joint_sets)]
@@ -525,13 +544,15 @@ def __get_uvs(blender_mesh, uv_i):
     return uvs
 
 
-def __get_colors(blender_mesh, color_i):
-    colors = np.empty(len(blender_mesh.loops) * 4, dtype=np.float32)
-    layer = blender_mesh.vertex_colors[color_i]
-    blender_mesh.color_attributes[layer.name].data.foreach_get('color', colors)
-    colors = colors.reshape(len(blender_mesh.loops), 4)
+def __get_colors(blender_mesh, color_i, blender_color_i):
+    if blender_mesh.color_attributes[blender_color_i].domain == "POINT":
+        colors = np.empty(len(blender_mesh.vertices) * 4, dtype=np.float32) #POINT
+    else:
+        colors = np.empty(len(blender_mesh.loops) * 4, dtype=np.float32) #CORNER
+    blender_mesh.color_attributes[blender_color_i].data.foreach_get('color', colors)
+    colors = colors.reshape(-1, 4)
     # colors are already linear, no need to switch color space
-    return colors
+    return colors, blender_mesh.color_attributes[blender_color_i].data_type, blender_mesh.color_attributes[blender_color_i].domain
 
 
 def __get_bone_data(blender_mesh, skin, blender_vertex_groups):
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
index 6153bc3369672a0186e3b49d8298dac58770d47b..721ef1157a61388586073c22875ebe63695043e1 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
@@ -59,6 +59,8 @@ def __gather_scene(blender_scene, export_settings):
     # Now, we can filter tree if needed
     vtree.filter()
 
+    vtree.variants_reset_to_original()
+
     export_user_extensions('vtree_after_filter_hook', export_settings, vtree)
 
     export_settings['vtree'] = vtree
@@ -94,9 +96,12 @@ def __gather_animations(blender_scene, export_settings):
     if export_settings['gltf_nla_strips'] is False:
         # Fake an animation with all animations of the scene
         merged_tracks = {}
-        merged_tracks['Animation'] = []
+        merged_tracks_name = 'Animation'
+        if(len(export_settings['gltf_nla_strips_merged_animation_name']) > 0):
+            merged_tracks_name = export_settings['gltf_nla_strips_merged_animation_name']
+        merged_tracks[merged_tracks_name] = []
         for idx, animation in enumerate(animations):
-            merged_tracks['Animation'].append(idx)
+            merged_tracks[merged_tracks_name].append(idx)
 
 
     to_delete_idx = []
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
index 98ae8b8268ddab71637eacc46c73f15cb01f24c4..3e67f1f70be82d08c6141d0e4866d35aeddebb1b 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
@@ -37,7 +37,7 @@ def gather_animation_channels(obj_uuid: int,
     if blender_action.use_frame_range is True:
         bake_range_start = blender_action.frame_start
         bake_range_end = blender_action.frame_end
-        force_range = True # keyframe_points is read-only, we cant restrict here
+        force_range = True # keyframe_points is read-only, we can't restrict here
     else:
         groups = __get_channel_groups(blender_action, blender_object, export_settings)
         # Note: channels has some None items only for SK if some SK are not animated
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
index e1ed19ea18a81a57be7f4d59330693b5ded173cf..53d789451084cf1f2b75ae756fe026d31d96355c 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
@@ -398,11 +398,17 @@ def gather_keyframes(blender_obj_uuid: str,
                     key.set_first_tangent()
                 else:
                     # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
-                    # use a point at t-1 to define the tangent. This allows the tangent control point to be transformed
-                    # normally
+                    # use a point at t+1 to define the tangent. This allows the tangent control point to be transformed
+                    # normally, but only works for locally linear transformation. The more non-linear a transform, the
+                    # more imprecise this method is.
+                    # We could use any other (v1, t1) for which (v1 - v0) / (t1 - t0) equals the tangent. By using t+1
+                    # for both in and out tangents, we guarantee that (even if there are errors or numerical imprecisions)
+                    # symmetrical control points translate to symmetrical tangents.
+                    # Note: I am not sure that linearity is never broken with quaternions and their normalization.
+                    # Especially at sign swap it might occur that the value gets negated but the control point not.
+                    # I have however not once encountered an issue with this.
                     key.in_tangent = [
-                        c.keyframe_points[i].co[1] + ((c.keyframe_points[i].co[1] - c.keyframe_points[i].handle_left[1]
-                                                       ) / (frame - frames[i - 1]))
+                        c.keyframe_points[i].co[1] + (c.keyframe_points[i].handle_left[1] - c.keyframe_points[i].co[1]) / (c.keyframe_points[i].handle_left[0] - c.keyframe_points[i].co[0])
                         for c in channels if c is not None
                     ]
                 # Construct the out tangent
@@ -410,12 +416,10 @@ def gather_keyframes(blender_obj_uuid: str,
                     # end out-tangent should become all zero
                     key.set_last_tangent()
                 else:
-                    # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
-                    # use a point at t+1 to define the tangent. This allows the tangent control point to be transformed
-                    # normally
+                    # otherwise construct an in tangent coordinate from the keyframes control points.
+                    # This happens the same way how in tangents are handled above.
                     key.out_tangent = [
-                        c.keyframe_points[i].co[1] + ((c.keyframe_points[i].handle_right[1] - c.keyframe_points[i].co[1]
-                                                       ) / (frames[i + 1] - frame))
+                        c.keyframe_points[i].co[1] + (c.keyframe_points[i].handle_right[1] - c.keyframe_points[i].co[1]) / (c.keyframe_points[i].handle_right[0] - c.keyframe_points[i].co[0])
                         for c in channels if c is not None
                     ]
 
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
index 1ee98a29f91348568da0c681b21d7854e3149095..5c8011ed516174313efa036f01b7f2d8dfc59617 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
@@ -414,6 +414,7 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
         transform = parent_inverse
 
     values = []
+    fps = bpy.context.scene.render.fps
     for keyframe in keyframes:
         # Transform the data and build gltf control points
         value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform, need_rotation_correction)
@@ -426,11 +427,11 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
             in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform, need_rotation_correction)
             if is_yup and blender_object_if_armature is None:
                 in_tangent = gltf2_blender_math.swizzle_yup(in_tangent, target_datapath)
-            # the tangent in glTF is relative to the keyframe value
+            # the tangent in glTF is relative to the keyframe value and uses seconds
             if not isinstance(value, list):
-                in_tangent = value - in_tangent
+                in_tangent = fps * (in_tangent - value)
             else:
-                in_tangent = [value[i] - in_tangent[i] for i in range(len(value))]
+                in_tangent = [fps * (in_tangent[i] - value[i]) for i in range(len(value))]
             keyframe_value = gltf2_blender_math.mathutils_to_gltf(in_tangent) + keyframe_value  # append
 
         if keyframe.out_tangent is not None:
@@ -438,11 +439,11 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
             out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform, need_rotation_correction)
             if is_yup and blender_object_if_armature is None:
                 out_tangent = gltf2_blender_math.swizzle_yup(out_tangent, target_datapath)
-            # the tangent in glTF is relative to the keyframe value
+            # the tangent in glTF is relative to the keyframe value and uses seconds
             if not isinstance(value, list):
-                out_tangent = value - out_tangent
+                out_tangent = fps * (out_tangent - value)
             else:
-                out_tangent = [value[i] - out_tangent[i] for i in range(len(value))]
+                out_tangent = [fps * (out_tangent[i] - value[i]) for i in range(len(value))]
             keyframe_value = keyframe_value + gltf2_blender_math.mathutils_to_gltf(out_tangent)  # append
 
         values += keyframe_value
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
index 20a919dc82867af3cc7c3858dff790addd854e20..bdb2ee00fad833d6d8c59376cdd47c63b18e68c3 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
@@ -64,21 +64,24 @@ def gather_animations(  obj_uuid: int,
         # No TRS animation are found for this object.
         # But we need to bake, in case we export selection
         # (Only when force sampling is ON)
-        # If force sampling is OFF, can lead to inconsistant export anyway
+        # If force sampling is OFF, can lead to inconsistent export anyway
         if export_settings['gltf_selected'] is True and blender_object.type != "ARMATURE" and export_settings['gltf_force_sampling'] is True:
-            channels = __gather_channels_baked(obj_uuid, export_settings)
-            if channels is not None:
-                animation = gltf2_io.Animation(
-                        channels=channels,
-                        extensions=None, # as other animations
-                        extras=None, # Because there is no animation to get extras from
-                        name=blender_object.name, # Use object name as animation name
-                        samplers=[]
-                    )
-
-                __link_samplers(animation, export_settings)
-                if animation is not None:
-                    animations.append(animation)
+            # We also have to check if this is a skinned mesh, because we don't have to force animation baking on this case
+            # (skinned meshes TRS must be ignored, says glTF specification)
+            if export_settings['vtree'].nodes[obj_uuid].skin is None:
+                channels = __gather_channels_baked(obj_uuid, export_settings)
+                if channels is not None:
+                    animation = gltf2_io.Animation(
+                            channels=channels,
+                            extensions=None, # as other animations
+                            extras=None, # Because there is no animation to get extras from
+                            name=blender_object.name, # Use object name as animation name
+                            samplers=[]
+                        )
+
+                    __link_samplers(animation, export_settings)
+                    if animation is not None:
+                        animations.append(animation)
         elif export_settings['gltf_selected'] is True and blender_object.type == "ARMATURE":
             # We need to bake all bones. Because some bone can have some constraints linking to
             # some other armature bones, for example
@@ -319,20 +322,21 @@ def __get_blender_actions(blender_object: bpy.types.Object,
                         action_on_type[strip.action.name] = "SHAPEKEY"
 
     # If there are only 1 armature, include all animations, even if not in NLA
-    if blender_object.type == "ARMATURE":
-        if len(export_settings['vtree'].get_all_node_of_type(VExportNode.ARMATURE)) == 1:
-            # Keep all actions on objects (no Shapekey animation)
-            for act in [a for a in bpy.data.actions if a.id_root == "OBJECT"]:
-                # We need to check this is an armature action
-                # Checking that at least 1 bone is animated
-                if not __is_armature_action(act):
-                    continue
-                # Check if this action is already taken into account
-                if act.name in blender_tracks.keys():
-                    continue
-                blender_actions.append(act)
-                blender_tracks[act.name] = None
-                action_on_type[act.name] = "OBJECT"
+    if export_settings['gltf_export_anim_single_armature'] is True:
+        if blender_object.type == "ARMATURE":
+            if len(export_settings['vtree'].get_all_node_of_type(VExportNode.ARMATURE)) == 1:
+                # Keep all actions on objects (no Shapekey animation)
+                for act in [a for a in bpy.data.actions if a.id_root == "OBJECT"]:
+                    # We need to check this is an armature action
+                    # Checking that at least 1 bone is animated
+                    if not __is_armature_action(act):
+                        continue
+                    # Check if this action is already taken into account
+                    if act.name in blender_tracks.keys():
+                        continue
+                    blender_actions.append(act)
+                    blender_tracks[act.name] = None
+                    action_on_type[act.name] = "OBJECT"
 
     export_user_extensions('gather_actions_hook', export_settings, blender_object, blender_actions, blender_tracks, action_on_type)
 
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
index b0e538c87dc0f9bcdd155b3f22be2182241120dc..9da5cc65fe2dba9788a5ee6f68582a33068beb6b 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
@@ -13,7 +13,7 @@ def get_sk_drivers(blender_armature_uuid, export_settings):
     drivers = []
 
     # Take into account skinned mesh, and mesh parented to a bone of the armature
-    children_list = export_settings['vtree'].nodes[blender_armature_uuid].children
+    children_list = export_settings['vtree'].nodes[blender_armature_uuid].children.copy()
     for bone in export_settings['vtree'].get_all_bones(blender_armature_uuid):
         children_list.extend(export_settings['vtree'].nodes[bone].children)
 
@@ -74,7 +74,8 @@ def get_sk_drivers(blender_armature_uuid, export_settings):
             else:
                 all_sorted_channels.append(existing_idx[i])
 
-        if len(all_sorted_channels) > 0:
+        # Checks there are some driver on SK, and that there is not only invalid drivers
+        if len(all_sorted_channels) > 0 and not all([i is None for i in all_sorted_channels]):
             drivers.append((child_uuid, tuple(all_sorted_channels)))
 
     return tuple(drivers)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
index 0dfce9f9b956aaaa15b46d2a385718ca685327d4..81e79a5094b58d7340634729f959a7544a777701 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
@@ -11,7 +11,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
 from io_scene_gltf2.io.exp import gltf2_io_binary_data
 from io_scene_gltf2.io.exp import gltf2_io_image_data
 from io_scene_gltf2.io.com import gltf2_io_debug
-from io_scene_gltf2.blender.exp.gltf2_blender_image import Channel, ExportImage, FillImage
+from io_scene_gltf2.blender.exp.gltf2_blender_image import Channel, ExportImage, FillImage, StoreImage, StoreData
 from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
 
@@ -21,26 +21,31 @@ def gather_image(
         blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
         export_settings):
     if not __filter_image(blender_shader_sockets, export_settings):
-        return None
+        return None, None
 
     image_data = __get_image_data(blender_shader_sockets, export_settings)
     if image_data.empty():
         # The export image has no data
-        return None
+        return None, None
 
     mime_type = __gather_mime_type(blender_shader_sockets, image_data, export_settings)
     name = __gather_name(image_data, export_settings)
 
+    factor = None
+
     if image_data.original is None:
-        uri = __gather_uri(image_data, mime_type, name, export_settings)
+        uri, factor_uri = __gather_uri(image_data, mime_type, name, export_settings)
     else:
         # Retrieve URI relative to exported glTF files
         uri = __gather_original_uri(image_data.original.filepath, export_settings)
         # In case we can't retrieve image (for example packed images, with original moved)
         # We don't create invalid image without uri
+        factor_uri = None
         if uri is None: return None
 
-    buffer_view = __gather_buffer_view(image_data, mime_type, name, export_settings)
+    buffer_view, factor_buffer_view = __gather_buffer_view(image_data, mime_type, name, export_settings)
+
+    factor = factor_uri if uri is not None else factor_buffer_view
 
     image = __make_image(
         buffer_view,
@@ -54,7 +59,7 @@ def gather_image(
 
     export_user_extensions('gather_image_hook', export_settings, image, blender_shader_sockets)
 
-    return image
+    return image, factor
 
 def __gather_original_uri(original_uri, export_settings):
 
@@ -98,8 +103,9 @@ def __filter_image(sockets, export_settings):
 @cached
 def __gather_buffer_view(image_data, mime_type, name, export_settings):
     if export_settings[gltf2_blender_export_keys.FORMAT] != 'GLTF_SEPARATE':
-        return gltf2_io_binary_data.BinaryData(data=image_data.encode(mime_type))
-    return None
+        data, factor = image_data.encode(mime_type)
+        return gltf2_io_binary_data.BinaryData(data=data), factor
+    return None, None
 
 
 def __gather_extensions(sockets, export_settings):
@@ -165,13 +171,14 @@ def __gather_name(export_image, export_settings):
 def __gather_uri(image_data, mime_type, name, export_settings):
     if export_settings[gltf2_blender_export_keys.FORMAT] == 'GLTF_SEPARATE':
         # as usual we just store the data in place instead of already resolving the references
+        data, factor = image_data.encode(mime_type=mime_type)
         return gltf2_io_image_data.ImageData(
-            data=image_data.encode(mime_type=mime_type),
+            data=data,
             mime_type=mime_type,
             name=name
-        )
+        ), factor
 
-    return None
+    return None, None
 
 
 def __get_image_data(sockets, export_settings) -> ExportImage:
@@ -179,6 +186,18 @@ def __get_image_data(sockets, export_settings) -> ExportImage:
     # in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary
     # resources.
     results = [__get_tex_from_socket(socket, export_settings) for socket in sockets]
+
+    # Check if we need a simple mapping or more complex calculation
+    if any([socket.name == "Specular" and socket.node.type == "BSDF_PRINCIPLED" for socket in sockets]):
+        return __get_image_data_specular(sockets, results, export_settings)
+    else:
+        return __get_image_data_mapping(sockets, results, export_settings)
+    
+def __get_image_data_mapping(sockets, results, export_settings) -> ExportImage:
+    """
+    Simple mapping
+    Will fit for most of exported textures : RoughnessMetallic, Basecolor, normal, ...
+    """
     composed_image = ExportImage()
     for result, socket in zip(results, sockets):
         # Assume that user know what he does, and that channels/images are already combined correctly for pbr
@@ -217,6 +236,12 @@ def __get_image_data(sockets, export_settings) -> ExportImage:
                 dst_chan = Channel.R
             elif socket.name == 'Clearcoat Roughness':
                 dst_chan = Channel.G
+            elif socket.name == 'Thickness': # For KHR_materials_volume
+                dst_chan = Channel.G
+            elif socket.name == "Specular": # For original KHR_material_specular
+                dst_chan = Channel.A
+            elif socket.name == "Sigma": # For KHR_materials_sheen
+                dst_chan = Channel.A
 
             if dst_chan is not None:
                 composed_image.fill_image(result.shader_node.image, dst_chan, src_chan)
@@ -243,6 +268,54 @@ def __get_image_data(sockets, export_settings) -> ExportImage:
     return composed_image
 
 
+def __get_image_data_specular(sockets, results, export_settings) -> ExportImage:
+    """
+    calculating Specular Texture, settings needed data
+    """   
+    from io_scene_gltf2.blender.exp.gltf2_blender_texture_specular import specular_calculation
+    composed_image = ExportImage()
+    composed_image.set_calc(specular_calculation)
+
+    composed_image.store_data("ior", sockets[4].default_value, type="Data")
+
+    results = [__get_tex_from_socket(socket, export_settings) for socket in sockets[:-1]] #Do not retrieve IOR --> No texture allowed
+
+    mapping = {
+        0: "specular",
+        1: "specular_tint",
+        2: "base_color",
+        3: "transmission"
+    }
+
+    for idx, result in enumerate(results):
+        if __get_tex_from_socket(sockets[idx], export_settings):
+
+            composed_image.store_data(mapping[idx], result.shader_node.image, type="Image")
+
+            # rudimentarily try follow the node tree to find the correct image data.
+            src_chan = None if idx == 2 else Channel.R
+            for elem in result.path:
+                if isinstance(elem.from_node, bpy.types.ShaderNodeSeparateColor):
+                    src_chan = {
+                        'Red': Channel.R,
+                        'Green': Channel.G,
+                        'Blue': Channel.B,
+                    }[elem.from_socket.name]
+                if elem.from_socket.name == 'Alpha':
+                    src_chan = Channel.A
+            # For base_color, keep all channels, as this is a Vec, not scalar
+            if idx != 2:
+                composed_image.store_data(mapping[idx] + "_channel", src_chan, type="Data")
+            else:
+                if src_chan is not None:
+                    composed_image.store_data(mapping[idx] + "_channel", src_chan, type="Data")
+
+        else:
+            composed_image.store_data(mapping[idx], sockets[idx].default_value, type="Data")
+
+    return composed_image
+
+# TODOExt deduplicate
 @cached
 def __get_tex_from_socket(blender_shader_socket: bpy.types.NodeSocket, export_settings):
     result = gltf2_blender_search_node_tree.from_socket(
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
index 56c3acff392895d8e0a6fbf6620976750118e9a7..c0d17fd38228c0849bb0ce660e082565ee512b82 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
@@ -1,21 +1,27 @@
 # SPDX-License-Identifier: Apache-2.0
-# Copyright 2018-2021 The glTF-Blender-IO authors.
+# Copyright 2018-2022 The glTF-Blender-IO authors.
 
 from copy import deepcopy
 import bpy
 
 from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, cached_by_key
 from io_scene_gltf2.io.com import gltf2_io
-from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_unlit
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info, gltf2_blender_export_keys
-from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
-
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_pbr_metallic_roughness
-from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_unlit
 from ..com.gltf2_blender_extras import generate_extras
 from io_scene_gltf2.blender.exp import gltf2_blender_get
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
 from io_scene_gltf2.io.com.gltf2_io_debug import print_console
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_volume import export_volume
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_emission import export_emission_factor, \
+    export_emission_texture, export_emission_strength_extension
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_sheen import export_sheen
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_specular import export_specular
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_transmission import export_transmission
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_clearcoat import export_clearcoat
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_ior import export_ior
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
 
 @cached
 def get_material_cache_key(blender_material, active_uvmap_index, export_settings):
@@ -39,24 +45,31 @@ def gather_material(blender_material, active_uvmap_index, export_settings):
     if not __filter_material(blender_material, export_settings):
         return None
 
-    mat_unlit = __gather_material_unlit(blender_material, active_uvmap_index, export_settings)
+    mat_unlit = __export_unlit(blender_material, active_uvmap_index, export_settings)
     if mat_unlit is not None:
         export_user_extensions('gather_material_hook', export_settings, mat_unlit, blender_material)
         return mat_unlit
 
     orm_texture = __gather_orm_texture(blender_material, export_settings)
 
+    emissive_factor = __gather_emissive_factor(blender_material, export_settings)
     emissive_texture, uvmap_actives_emissive_texture = __gather_emissive_texture(blender_material, export_settings)
-    extensions, uvmap_actives_extensions = __gather_extensions(blender_material, export_settings)
+    extensions, uvmap_actives_extensions = __gather_extensions(blender_material, emissive_factor, export_settings)
     normal_texture, uvmap_actives_normal_texture = __gather_normal_texture(blender_material, export_settings)
     occlusion_texture, uvmap_actives_occlusion_texture = __gather_occlusion_texture(blender_material, orm_texture, export_settings)
     pbr_metallic_roughness, uvmap_actives_pbr_metallic_roughness = __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settings)
 
+    if any([i>1.0 for i in emissive_factor or []]) is True:
+        # Strength is set on extension
+        emission_strength = max(emissive_factor)
+        emissive_factor = [f / emission_strength for f in emissive_factor]
+
+
     base_material = gltf2_io.Material(
         alpha_cutoff=__gather_alpha_cutoff(blender_material, export_settings),
         alpha_mode=__gather_alpha_mode(blender_material, export_settings),
         double_sided=__gather_double_sided(blender_material, export_settings),
-        emissive_factor=__gather_emissive_factor(blender_material, export_settings),
+        emissive_factor=emissive_factor,
         emissive_texture=emissive_texture,
         extensions=extensions,
         extras=__gather_extras(blender_material, export_settings),
@@ -106,6 +119,14 @@ def gather_material(blender_material, active_uvmap_index, export_settings):
             material.extensions["KHR_materials_clearcoat"].extension['clearcoatNormalTexture'].tex_coord = active_uvmap_index
         elif tex == "transmissionTexture": #TODO not tested yet
             material.extensions["KHR_materials_transmission"].extension['transmissionTexture'].tex_coord = active_uvmap_index
+        elif tex == "specularTexture":
+            material.extensions["KHR_materials_specular"].extension['specularTexture'].tex_coord = active_uvmap_index
+        elif tex == "specularColorTexture":
+            material.extensions["KHR_materials_specular"].extension['specularColorTexture'].tex_coord = active_uvmap_index
+        elif tex == "sheenColorTexture":
+            material.extensions["KHR_materials_sheen"].extension['sheenColorTexture'].tex_coord = active_uvmap_index
+        elif tex == "sheenRoughnessTexture":
+            material.extensions["KHR_materials_sheen"].extension['sheenRoughnessTexture'].tex_coord = active_uvmap_index
 
     # If material is not using active UVMap, we need to return the same material,
     # Even if multiples meshes are using different active UVMap
@@ -188,72 +209,61 @@ def __gather_double_sided(blender_material, export_settings):
 
 
 def __gather_emissive_factor(blender_material, export_settings):
-    emissive_socket = gltf2_blender_get.get_socket(blender_material, "Emissive")
-    if emissive_socket is None:
-        emissive_socket = gltf2_blender_get.get_socket_old(blender_material, "EmissiveFactor")
-    if isinstance(emissive_socket, bpy.types.NodeSocket):
-        if export_settings['gltf_image_format'] != "NONE":
-            factor = gltf2_blender_get.get_factor_from_socket(emissive_socket, kind='RGB')
-        else:
-            factor = gltf2_blender_get.get_const_from_default_value_socket(emissive_socket, kind='RGB')
-
-        if factor is None and emissive_socket.is_linked:
-            # In glTF, the default emissiveFactor is all zeros, so if an emission texture is connected,
-            # we have to manually set it to all ones.
-            factor = [1.0, 1.0, 1.0]
-
-        if factor is None: factor = [0.0, 0.0, 0.0]
-
-        # Handle Emission Strength
-        strength_socket = None
-        if emissive_socket.node.type == 'EMISSION':
-            strength_socket = emissive_socket.node.inputs['Strength']
-        elif 'Emission Strength' in emissive_socket.node.inputs:
-            strength_socket = emissive_socket.node.inputs['Emission Strength']
-        strength = (
-            gltf2_blender_get.get_const_from_socket(strength_socket, kind='VALUE')
-            if strength_socket is not None
-            else None
-        )
-        if strength is not None:
-            factor = [f * strength for f in factor]
-
-        # Clamp to range [0,1]
-        factor = [min(1.0, f) for f in factor]
-
-        if factor == [0, 0, 0]: factor = None
-
-        return factor
-
-    return None
-
+    return export_emission_factor(blender_material, export_settings)
 
 def __gather_emissive_texture(blender_material, export_settings):
-    emissive = gltf2_blender_get.get_socket(blender_material, "Emissive")
-    if emissive is None:
-        emissive = gltf2_blender_get.get_socket_old(blender_material, "Emissive")
-    emissive_texture, use_actives_uvmap_emissive = gltf2_blender_gather_texture_info.gather_texture_info(emissive, (emissive,), export_settings)
-    return emissive_texture, ["emissiveTexture"] if use_actives_uvmap_emissive else None
+    return export_emission_texture(blender_material, export_settings)
 
 
-def __gather_extensions(blender_material, export_settings):
+def __gather_extensions(blender_material, emissive_factor, export_settings):
     extensions = {}
 
     # KHR_materials_clearcoat
     actives_uvmaps = []
 
-    clearcoat_extension, use_actives_uvmap_clearcoat = __gather_clearcoat_extension(blender_material, export_settings)
+    clearcoat_extension, use_actives_uvmap_clearcoat = export_clearcoat(blender_material, export_settings)
     if clearcoat_extension:
         extensions["KHR_materials_clearcoat"] = clearcoat_extension
         actives_uvmaps.extend(use_actives_uvmap_clearcoat)
 
     # KHR_materials_transmission
 
-    transmission_extension, use_actives_uvmap_transmission = __gather_transmission_extension(blender_material, export_settings)
+    transmission_extension, use_actives_uvmap_transmission = export_transmission(blender_material, export_settings)
     if transmission_extension:
         extensions["KHR_materials_transmission"] = transmission_extension
         actives_uvmaps.extend(use_actives_uvmap_transmission)
 
+    # KHR_materials_emissive_strength
+    if any([i>1.0 for i in emissive_factor or []]):
+        emissive_strength_extension = export_emission_strength_extension(emissive_factor, export_settings)
+        if emissive_strength_extension:
+            extensions["KHR_materials_emissive_strength"] = emissive_strength_extension
+
+    # KHR_materials_volume
+
+    volume_extension, use_actives_uvmap_volume_thickness  = export_volume(blender_material, export_settings)
+    if volume_extension:
+        extensions["KHR_materials_volume"] = volume_extension
+        actives_uvmaps.extend(use_actives_uvmap_volume_thickness)
+
+    # KHR_materials_specular
+    specular_extension, use_actives_uvmap_specular = export_specular(blender_material, export_settings)
+    if specular_extension:
+        extensions["KHR_materials_specular"] = specular_extension
+        actives_uvmaps.extend(use_actives_uvmap_specular)
+
+    # KHR_materials_sheen
+    sheen_extension, use_actives_uvmap_sheen = export_sheen(blender_material, export_settings)
+    if sheen_extension:
+        extensions["KHR_materials_sheen"] = sheen_extension
+        actives_uvmaps.extend(use_actives_uvmap_sheen)   
+
+    # KHR_materials_ior
+    # Keep this extension at the end, because we export it only if some others are exported
+    ior_extension = export_ior(blender_material, extensions, export_settings)
+    if ior_extension:
+        extensions["KHR_materials_ior"] = ior_extension
+
     return extensions, actives_uvmaps if extensions else None
 
 
@@ -271,7 +281,7 @@ def __gather_normal_texture(blender_material, export_settings):
     normal = gltf2_blender_get.get_socket(blender_material, "Normal")
     if normal is None:
         normal = gltf2_blender_get.get_socket_old(blender_material, "Normal")
-    normal_texture, use_active_uvmap_normal = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
+    normal_texture, use_active_uvmap_normal, _ = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
         normal,
         (normal,),
         export_settings)
@@ -283,20 +293,20 @@ def __gather_orm_texture(blender_material, export_settings):
     # If not fully shared, return None, so the images will be cached and processed separately.
 
     occlusion = gltf2_blender_get.get_socket(blender_material, "Occlusion")
-    if occlusion is None or not __has_image_node_from_socket(occlusion):
+    if occlusion is None or not gltf2_blender_get.has_image_node_from_socket(occlusion):
         occlusion = gltf2_blender_get.get_socket_old(blender_material, "Occlusion")
-        if occlusion is None or not __has_image_node_from_socket(occlusion):
+        if occlusion is None or not gltf2_blender_get.has_image_node_from_socket(occlusion):
             return None
 
     metallic_socket = gltf2_blender_get.get_socket(blender_material, "Metallic")
     roughness_socket = gltf2_blender_get.get_socket(blender_material, "Roughness")
 
-    hasMetal = metallic_socket is not None and __has_image_node_from_socket(metallic_socket)
-    hasRough = roughness_socket is not None and __has_image_node_from_socket(roughness_socket)
+    hasMetal = metallic_socket is not None and gltf2_blender_get.has_image_node_from_socket(metallic_socket)
+    hasRough = roughness_socket is not None and gltf2_blender_get.has_image_node_from_socket(roughness_socket)
 
     if not hasMetal and not hasRough:
         metallic_roughness = gltf2_blender_get.get_socket_old(blender_material, "MetallicRoughness")
-        if metallic_roughness is None or not __has_image_node_from_socket(metallic_roughness):
+        if metallic_roughness is None or not gltf2_blender_get.has_image_node_from_socket(metallic_roughness):
             return None
         result = (occlusion, metallic_roughness)
     elif not hasMetal:
@@ -313,7 +323,7 @@ def __gather_orm_texture(blender_material, export_settings):
         return None
 
     # Double-check this will past the filter in texture_info
-    info, info_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(result[0], result, export_settings)
+    info, info_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(result[0], result, export_settings)
     if info is None:
         return None
 
@@ -323,7 +333,7 @@ def __gather_occlusion_texture(blender_material, orm_texture, export_settings):
     occlusion = gltf2_blender_get.get_socket(blender_material, "Occlusion")
     if occlusion is None:
         occlusion = gltf2_blender_get.get_socket_old(blender_material, "Occlusion")
-    occlusion_texture, use_active_uvmap_occlusion = gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
+    occlusion_texture, use_active_uvmap_occlusion, _ = gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
         occlusion,
         orm_texture or (occlusion,),
         export_settings)
@@ -336,129 +346,7 @@ def __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settin
         orm_texture,
         export_settings)
 
-def __has_image_node_from_socket(socket):
-    result = gltf2_blender_search_node_tree.from_socket(
-        socket,
-        gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
-    if not result:
-        return False
-    return True
-
-def __gather_clearcoat_extension(blender_material, export_settings):
-    clearcoat_enabled = False
-    has_clearcoat_texture = False
-    has_clearcoat_roughness_texture = False
-
-    clearcoat_extension = {}
-    clearcoat_roughness_slots = ()
-
-    clearcoat_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat')
-    clearcoat_roughness_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Roughness')
-    clearcoat_normal_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Normal')
-
-    if isinstance(clearcoat_socket, bpy.types.NodeSocket) and not clearcoat_socket.is_linked:
-        clearcoat_extension['clearcoatFactor'] = clearcoat_socket.default_value
-        clearcoat_enabled = clearcoat_extension['clearcoatFactor'] > 0
-    elif __has_image_node_from_socket(clearcoat_socket):
-        fac = gltf2_blender_get.get_factor_from_socket(clearcoat_socket, kind='VALUE')
-        # default value in glTF is 0.0, but if there is a texture without factor, use 1
-        clearcoat_extension['clearcoatFactor'] = fac if fac != None else 1.0
-        has_clearcoat_texture = True
-        clearcoat_enabled = True
-
-    if not clearcoat_enabled:
-        return None, None
-
-    if isinstance(clearcoat_roughness_socket, bpy.types.NodeSocket) and not clearcoat_roughness_socket.is_linked:
-        clearcoat_extension['clearcoatRoughnessFactor'] = clearcoat_roughness_socket.default_value
-    elif __has_image_node_from_socket(clearcoat_roughness_socket):
-        fac = gltf2_blender_get.get_factor_from_socket(clearcoat_roughness_socket, kind='VALUE')
-        # default value in glTF is 0.0, but if there is a texture without factor, use 1
-        clearcoat_extension['clearcoatRoughnessFactor'] = fac if fac != None else 1.0
-        has_clearcoat_roughness_texture = True
-
-    # Pack clearcoat (R) and clearcoatRoughness (G) channels.
-    if has_clearcoat_texture and has_clearcoat_roughness_texture:
-        clearcoat_roughness_slots = (clearcoat_socket, clearcoat_roughness_socket,)
-    elif has_clearcoat_texture:
-        clearcoat_roughness_slots = (clearcoat_socket,)
-    elif has_clearcoat_roughness_texture:
-        clearcoat_roughness_slots = (clearcoat_roughness_socket,)
-
-    use_actives_uvmaps = []
-
-    if len(clearcoat_roughness_slots) > 0:
-        if has_clearcoat_texture:
-            clearcoat_texture, clearcoat_texture_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
-                clearcoat_socket,
-                clearcoat_roughness_slots,
-                export_settings,
-            )
-            clearcoat_extension['clearcoatTexture'] = clearcoat_texture
-            if clearcoat_texture_use_active_uvmap:
-                use_actives_uvmaps.append("clearcoatTexture")
-        if has_clearcoat_roughness_texture:
-            clearcoat_roughness_texture, clearcoat_roughness_texture_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
-                clearcoat_roughness_socket,
-                clearcoat_roughness_slots,
-                export_settings,
-            )
-            clearcoat_extension['clearcoatRoughnessTexture'] = clearcoat_roughness_texture
-            if clearcoat_roughness_texture_use_active_uvmap:
-                use_actives_uvmaps.append("clearcoatRoughnessTexture")
-    if __has_image_node_from_socket(clearcoat_normal_socket):
-        clearcoat_normal_texture, clearcoat_normal_texture_use_active_uvmap = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
-            clearcoat_normal_socket,
-            (clearcoat_normal_socket,),
-            export_settings
-        )
-        clearcoat_extension['clearcoatNormalTexture'] = clearcoat_normal_texture
-        if clearcoat_normal_texture_use_active_uvmap:
-            use_actives_uvmaps.append("clearcoatNormalTexture")
-
-    return Extension('KHR_materials_clearcoat', clearcoat_extension, False), use_actives_uvmaps
-
-def __gather_transmission_extension(blender_material, export_settings):
-    transmission_enabled = False
-    has_transmission_texture = False
-
-    transmission_extension = {}
-    transmission_slots = ()
-
-    transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
-
-    if isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked:
-        transmission_extension['transmissionFactor'] = transmission_socket.default_value
-        transmission_enabled = transmission_extension['transmissionFactor'] > 0
-    elif __has_image_node_from_socket(transmission_socket):
-        transmission_extension['transmissionFactor'] = 1.0
-        has_transmission_texture = True
-        transmission_enabled = True
-
-    if not transmission_enabled:
-        return None, None
-
-    # Pack transmission channel (R).
-    if has_transmission_texture:
-        transmission_slots = (transmission_socket,)
-
-    use_actives_uvmaps = []
-
-    if len(transmission_slots) > 0:
-        combined_texture, use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
-            transmission_socket,
-            transmission_slots,
-            export_settings,
-        )
-        if has_transmission_texture:
-            transmission_extension['transmissionTexture'] = combined_texture
-        if use_active_uvmap:
-            use_actives_uvmaps.append("transmissionTexture")
-
-    return Extension('KHR_materials_transmission', transmission_extension, False), use_actives_uvmaps
-
-
-def __gather_material_unlit(blender_material, active_uvmap_index, export_settings):
+def __export_unlit(blender_material, active_uvmap_index, export_settings):
     gltf2_unlit = gltf2_blender_gather_materials_unlit
 
     info = gltf2_unlit.detect_shadeless_material(blender_material, export_settings)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py
new file mode 100644
index 0000000000000000000000000000000000000000..65c164b4b0dab67e0f68c68151a158395ce83455
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py
@@ -0,0 +1,81 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+def export_clearcoat(blender_material, export_settings):
+    clearcoat_enabled = False
+    has_clearcoat_texture = False
+    has_clearcoat_roughness_texture = False
+
+    clearcoat_extension = {}
+    clearcoat_roughness_slots = ()
+
+    clearcoat_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat')
+    clearcoat_roughness_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Roughness')
+    clearcoat_normal_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Normal')
+
+    if isinstance(clearcoat_socket, bpy.types.NodeSocket) and not clearcoat_socket.is_linked:
+        clearcoat_extension['clearcoatFactor'] = clearcoat_socket.default_value
+        clearcoat_enabled = clearcoat_extension['clearcoatFactor'] > 0
+    elif gltf2_blender_get.has_image_node_from_socket(clearcoat_socket):
+        fac = gltf2_blender_get.get_factor_from_socket(clearcoat_socket, kind='VALUE')
+        # default value in glTF is 0.0, but if there is a texture without factor, use 1
+        clearcoat_extension['clearcoatFactor'] = fac if fac != None else 1.0
+        has_clearcoat_texture = True
+        clearcoat_enabled = True
+
+    if not clearcoat_enabled:
+        return None, None
+
+    if isinstance(clearcoat_roughness_socket, bpy.types.NodeSocket) and not clearcoat_roughness_socket.is_linked:
+        clearcoat_extension['clearcoatRoughnessFactor'] = clearcoat_roughness_socket.default_value
+    elif gltf2_blender_get.has_image_node_from_socket(clearcoat_roughness_socket):
+        fac = gltf2_blender_get.get_factor_from_socket(clearcoat_roughness_socket, kind='VALUE')
+        # default value in glTF is 0.0, but if there is a texture without factor, use 1
+        clearcoat_extension['clearcoatRoughnessFactor'] = fac if fac != None else 1.0
+        has_clearcoat_roughness_texture = True
+
+    # Pack clearcoat (R) and clearcoatRoughness (G) channels.
+    if has_clearcoat_texture and has_clearcoat_roughness_texture:
+        clearcoat_roughness_slots = (clearcoat_socket, clearcoat_roughness_socket,)
+    elif has_clearcoat_texture:
+        clearcoat_roughness_slots = (clearcoat_socket,)
+    elif has_clearcoat_roughness_texture:
+        clearcoat_roughness_slots = (clearcoat_roughness_socket,)
+
+    use_actives_uvmaps = []
+
+    if len(clearcoat_roughness_slots) > 0:
+        if has_clearcoat_texture:
+            clearcoat_texture, clearcoat_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                clearcoat_socket,
+                clearcoat_roughness_slots,
+                export_settings,
+            )
+            clearcoat_extension['clearcoatTexture'] = clearcoat_texture
+            if clearcoat_texture_use_active_uvmap:
+                use_actives_uvmaps.append("clearcoatTexture")
+        if has_clearcoat_roughness_texture:
+            clearcoat_roughness_texture, clearcoat_roughness_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                clearcoat_roughness_socket,
+                clearcoat_roughness_slots,
+                export_settings,
+            )
+            clearcoat_extension['clearcoatRoughnessTexture'] = clearcoat_roughness_texture
+            if clearcoat_roughness_texture_use_active_uvmap:
+                use_actives_uvmaps.append("clearcoatRoughnessTexture")
+    if gltf2_blender_get.has_image_node_from_socket(clearcoat_normal_socket):
+        clearcoat_normal_texture, clearcoat_normal_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
+            clearcoat_normal_socket,
+            (clearcoat_normal_socket,),
+            export_settings
+        )
+        clearcoat_extension['clearcoatNormalTexture'] = clearcoat_normal_texture
+        if clearcoat_normal_texture_use_active_uvmap:
+            use_actives_uvmaps.append("clearcoatNormalTexture")
+
+    return Extension('KHR_materials_clearcoat', clearcoat_extension, False), use_actives_uvmaps
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py
new file mode 100644
index 0000000000000000000000000000000000000000..562fc19d91c1abe295ff8681aa12c3dd3c62bfd8
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+def export_emission_factor(blender_material, export_settings):
+    emissive_socket = gltf2_blender_get.get_socket(blender_material, "Emissive")
+    if emissive_socket is None:
+        emissive_socket = gltf2_blender_get.get_socket_old(blender_material, "EmissiveFactor")
+    if isinstance(emissive_socket, bpy.types.NodeSocket):
+        if export_settings['gltf_image_format'] != "NONE":
+            factor = gltf2_blender_get.get_factor_from_socket(emissive_socket, kind='RGB')
+        else:
+            factor = gltf2_blender_get.get_const_from_default_value_socket(emissive_socket, kind='RGB')
+
+        if factor is None and emissive_socket.is_linked:
+            # In glTF, the default emissiveFactor is all zeros, so if an emission texture is connected,
+            # we have to manually set it to all ones.
+            factor = [1.0, 1.0, 1.0]
+
+        if factor is None: factor = [0.0, 0.0, 0.0]
+
+        # Handle Emission Strength
+        strength_socket = None
+        if emissive_socket.node.type == 'EMISSION':
+            strength_socket = emissive_socket.node.inputs['Strength']
+        elif 'Emission Strength' in emissive_socket.node.inputs:
+            strength_socket = emissive_socket.node.inputs['Emission Strength']
+        strength = (
+            gltf2_blender_get.get_const_from_socket(strength_socket, kind='VALUE')
+            if strength_socket is not None
+            else None
+        )
+        if strength is not None:
+            factor = [f * strength for f in factor]
+
+        # Clamp to range [0,1]
+        # Official glTF clamp to range [0,1]
+        # If we are outside, we need to use extension KHR_materials_emissive_strength
+
+        if factor == [0, 0, 0]: factor = None
+
+        return factor
+
+    return None
+
+def export_emission_texture(blender_material, export_settings):
+    emissive = gltf2_blender_get.get_socket(blender_material, "Emissive")
+    if emissive is None:
+        emissive = gltf2_blender_get.get_socket_old(blender_material, "Emissive")
+    emissive_texture, use_actives_uvmap_emissive, _ = gltf2_blender_gather_texture_info.gather_texture_info(emissive, (emissive,), export_settings)
+    return emissive_texture, ["emissiveTexture"] if use_actives_uvmap_emissive else None
+
+def export_emission_strength_extension(emissive_factor, export_settings):
+    emissive_strength_extension = {}
+    emissive_strength_extension['emissiveStrength'] = max(emissive_factor)
+
+    return Extension('KHR_materials_emissive_strength', emissive_strength_extension, False)
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc219c01074ef602b5121b678a939e1bc00f69b1
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.io.com.gltf2_io_constants import GLTF_IOR
+
+def export_ior(blender_material, extensions, export_settings):
+    ior_socket = gltf2_blender_get.get_socket(blender_material, 'IOR')
+
+    if not ior_socket:
+        return None
+
+    # We don't manage case where socket is linked, always check default value
+    if ior_socket.is_linked:
+        # TODOExt: add warning?
+        return None
+
+    if ior_socket.default_value == GLTF_IOR:
+        return None
+
+    # Export only if the following extensions are exported:
+    need_to_export_ior = [
+        'KHR_materials_transmission',
+        'KHR_materials_volume',
+        'KHR_materials_specular'
+    ]
+
+    if not any([e in extensions.keys() for e in need_to_export_ior]):
+        return None
+
+    ior_extension = {}
+    ior_extension['ior'] = ior_socket.default_value
+
+    return Extension('KHR_materials_ior', ior_extension, False)
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
index 0b40ffd6025f00318e59581aa35642b166b398a2..a5929c05bd22c2e5ff3ce79eedd003a2ee8c38be 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
@@ -15,8 +15,8 @@ def gather_material_pbr_metallic_roughness(blender_material, orm_texture, export
     if not __filter_pbr_material(blender_material, export_settings):
         return None, None
 
-    base_color_texture, use_active_uvmap_base_color_texture = __gather_base_color_texture(blender_material, export_settings)
-    metallic_roughness_texture, use_active_uvmap_metallic_roughness_texture = __gather_metallic_roughness_texture(blender_material, orm_texture, export_settings)
+    base_color_texture, use_active_uvmap_base_color_texture, _ = __gather_base_color_texture(blender_material, export_settings)
+    metallic_roughness_texture, use_active_uvmap_metallic_roughness_texture, _ = __gather_metallic_roughness_texture(blender_material, orm_texture, export_settings)
 
     material = gltf2_io.MaterialPBRMetallicRoughness(
         base_color_factor=__gather_base_color_factor(blender_material, export_settings),
@@ -92,7 +92,7 @@ def __gather_base_color_texture(blender_material, export_settings):
         if socket is not None and __has_image_node_from_socket(socket)
     )
     if not inputs:
-        return None, None
+        return None, None, None
 
     return gltf2_blender_gather_texture_info.gather_texture_info(inputs[0], inputs, export_settings)
 
@@ -128,7 +128,7 @@ def __gather_metallic_roughness_texture(blender_material, orm_texture, export_se
     if not hasMetal and not hasRough:
         metallic_roughness = gltf2_blender_get.get_socket_old(blender_material, "MetallicRoughness")
         if metallic_roughness is None or not __has_image_node_from_socket(metallic_roughness):
-            return None, None
+            return None, None, None
         texture_input = (metallic_roughness,)
     elif not hasMetal:
         texture_input = (roughness_socket,)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py
new file mode 100644
index 0000000000000000000000000000000000000000..03625ecbc80a5ab6dfccfe7b722b93df8ac56856
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+
+def export_sheen(blender_material, export_settings):
+    sheen_extension = {}
+
+    sheenColor_socket = gltf2_blender_get.get_socket(blender_material, "sheenColor")
+    sheenRoughness_socket = gltf2_blender_get.get_socket(blender_material, "sheenRoughness")
+
+    if sheenColor_socket is None or sheenRoughness_socket is None:
+        return None, None
+
+    sheenColor_non_linked = isinstance(sheenColor_socket, bpy.types.NodeSocket) and not sheenColor_socket.is_linked
+    sheenRoughness_non_linked = isinstance(sheenRoughness_socket, bpy.types.NodeSocket) and not sheenRoughness_socket.is_linked
+
+
+    use_actives_uvmaps = []
+
+    if sheenColor_non_linked is True:
+        color = sheenColor_socket.default_value[:3]
+        if color != (0.0, 0.0, 0.0):
+            sheen_extension['sheenColorFactor'] = color
+    else:
+        # Factor
+        fac = gltf2_blender_get.get_factor_from_socket(sheenColor_socket, kind='RGB')
+        if fac is not None and fac != [0.0, 0.0, 0.0]:
+            sheen_extension['sheenColorFactor'] = fac
+        
+        # Texture
+        if gltf2_blender_get.has_image_node_from_socket(sheenColor_socket):
+            original_sheenColor_texture, original_sheenColor_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                sheenColor_socket,
+                (sheenColor_socket,),
+                export_settings,
+            )
+            sheen_extension['sheenColorTexture'] = original_sheenColor_texture
+            if original_sheenColor_use_active_uvmap:
+                use_actives_uvmaps.append("sheenColorTexture")
+
+
+    if sheenRoughness_non_linked is True:
+        fac = sheenRoughness_socket.default_value
+        if fac != 0.0:
+            sheen_extension['sheenRoughnessFactor'] = fac
+    else:
+        # Factor
+        fac = gltf2_blender_get.get_factor_from_socket(sheenRoughness_socket, kind='VALUE')
+        if fac is not None and fac != 0.0:
+            sheen_extension['sheenRoughnessFactor'] = fac
+        
+        # Texture
+        if gltf2_blender_get.has_image_node_from_socket(sheenRoughness_socket):
+            original_sheenRoughness_texture, original_sheenRoughness_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                sheenRoughness_socket,
+                (sheenRoughness_socket,),
+                export_settings,
+            )
+            sheen_extension['sheenRoughnessTexture'] = original_sheenRoughness_texture
+            if original_sheenRoughness_use_active_uvmap:
+                use_actives_uvmaps.append("sheenRoughnessTexture")
+    
+    return Extension('KHR_materials_sheen', sheen_extension, False), use_actives_uvmaps
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py
new file mode 100644
index 0000000000000000000000000000000000000000..22414b13e333e6224b2719fe3c989b4ac18f30da
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py
@@ -0,0 +1,168 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.io.com.gltf2_io_constants import GLTF_IOR
+from io_scene_gltf2.blender.com.gltf2_blender_default import BLENDER_SPECULAR, BLENDER_SPECULAR_TINT
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+
+
+def export_original_specular(blender_material, export_settings):
+    specular_extension = {}
+
+    original_specular_socket = gltf2_blender_get.get_socket_old(blender_material, 'Specular')
+    original_specularcolor_socket = gltf2_blender_get.get_socket_old(blender_material, 'Specular Color')
+
+    if original_specular_socket is None or original_specularcolor_socket is None:
+        return None, None
+
+    specular_non_linked = isinstance(original_specular_socket, bpy.types.NodeSocket) and not original_specular_socket.is_linked
+    specularcolor_non_linked = isinstance(original_specularcolor_socket, bpy.types.NodeSocket) and not original_specularcolor_socket.is_linked
+
+
+    use_actives_uvmaps = []
+
+    if specular_non_linked is True:
+        fac = original_specular_socket.default_value
+        if fac != 1.0:
+            specular_extension['specularFactor'] = fac
+    else:
+        # Factor
+        fac = gltf2_blender_get.get_factor_from_socket(original_specular_socket, kind='VALUE')
+        if fac is not None and fac != 1.0:
+            specular_extension['specularFactor'] = fac
+        
+        # Texture
+        if gltf2_blender_get.has_image_node_from_socket(original_specular_socket):
+            original_specular_texture, original_specular_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                original_specular_socket,
+                (original_specular_socket,),
+                export_settings,
+            )
+            specular_extension['specularTexture'] = original_specular_texture
+            if original_specular_use_active_uvmap:
+                use_actives_uvmaps.append("specularTexture")
+
+
+    if specularcolor_non_linked is True:
+        color = original_specularcolor_socket.default_value[:3]
+        if color != [1.0, 1.0, 1.0]:
+            specular_extension['specularColorFactor'] = color
+    else:
+        # Factor
+        fac = gltf2_blender_get.get_factor_from_socket(original_specularcolor_socket, kind='RGB')
+        if fac is not None and fac != [1.0, 1.0, 1.0]:
+            specular_extension['specularColorFactor'] = fac
+
+        # Texture
+        if gltf2_blender_get.has_image_node_from_socket(original_specularcolor_socket):
+            original_specularcolor_texture, original_specularcolor_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                original_specularcolor_socket,
+                (original_specularcolor_socket,),
+                export_settings,
+            )
+            specular_extension['specularColorTexture'] = original_specularcolor_texture
+            if original_specularcolor_use_active_uvmap:
+                use_actives_uvmaps.append("specularColorTexture")
+    
+    return Extension('KHR_materials_specular', specular_extension, False), use_actives_uvmaps
+
+def export_specular(blender_material, export_settings):
+
+    if export_settings['gltf_original_specular'] is True:
+        return export_original_specular(blender_material, export_settings)
+
+    specular_extension = {}
+    specular_ext_enabled = False
+
+    specular_socket = gltf2_blender_get.get_socket(blender_material, 'Specular')
+    specular_tint_socket = gltf2_blender_get.get_socket(blender_material, 'Specular Tint')
+    base_color_socket = gltf2_blender_get.get_socket(blender_material, 'Base Color')
+    transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
+    ior_socket = gltf2_blender_get.get_socket(blender_material, 'IOR')
+
+    if base_color_socket is None:
+        return None, None
+
+    # TODOExt replace by __has_image_node_from_socket calls
+    specular_not_linked = isinstance(specular_socket, bpy.types.NodeSocket) and not specular_socket.is_linked
+    specular_tint_not_linked = isinstance(specular_tint_socket, bpy.types.NodeSocket) and not specular_tint_socket.is_linked
+    base_color_not_linked = isinstance(base_color_socket, bpy.types.NodeSocket) and not base_color_socket.is_linked
+    transmission_not_linked = isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked
+    ior_not_linked = isinstance(ior_socket, bpy.types.NodeSocket) and not ior_socket.is_linked
+
+    specular = specular_socket.default_value if specular_not_linked else None
+    specular_tint = specular_tint_socket.default_value if specular_tint_not_linked else None
+    transmission = transmission_socket.default_value if transmission_not_linked else None
+    ior = ior_socket.default_value if ior_not_linked else GLTF_IOR   # textures not supported #TODOExt add warning?
+    base_color = base_color_socket.default_value[0:3]
+
+    no_texture = (transmission_not_linked and specular_not_linked and specular_tint_not_linked and
+        (specular_tint == 0.0 or (specular_tint != 0.0 and base_color_not_linked)))
+
+    use_actives_uvmaps = []
+
+    if no_texture:
+        if specular != BLENDER_SPECULAR or specular_tint != BLENDER_SPECULAR_TINT:
+            import numpy as np
+            # See https://gist.github.com/proog128/d627c692a6bbe584d66789a5a6437a33
+            specular_ext_enabled = True
+
+            def normalize(c):
+                luminance = lambda c: 0.3 * c[0] + 0.6 * c[1] + 0.1 * c[2]
+                assert(len(c) == 3)
+                l = luminance(c)
+                if l == 0:
+                    return np.array(c)
+                return np.array([c[0] / l, c[1] / l, c[2] / l])            
+
+            f0_from_ior = ((ior - 1)/(ior + 1))**2
+            tint_strength = (1 - specular_tint) + normalize(base_color) * specular_tint
+            specular_color = (1 - transmission) * (1 / f0_from_ior) * 0.08 * specular * tint_strength + transmission * tint_strength
+            specular_extension['specularColorFactor'] = list(specular_color)
+    else:
+        if specular_not_linked and specular == BLENDER_SPECULAR and specular_tint_not_linked and specular_tint == BLENDER_SPECULAR_TINT:
+            return None, None
+
+        # Trying to identify cases where exporting a texture will not be needed
+        if specular_not_linked and transmission_not_linked and \
+            specular == 0.0 and transmission == 0.0:
+
+            specular_extension['specularColorFactor'] = [0.0, 0.0, 0.0]
+            return specular_extension, []
+
+
+        # There will be a texture, with a complex calculation (no direct channel mapping)
+        sockets = (specular_socket, specular_tint_socket, base_color_socket, transmission_socket, ior_socket)
+        # Set primary socket having a texture
+        primary_socket = specular_socket
+        if specular_not_linked:
+            primary_socket = specular_tint_socket
+            if specular_tint_not_linked:
+                primary_socket = base_color_socket
+                if base_color_not_linked:
+                    primary_socket = transmission_socket
+
+        specularColorTexture, use_active_uvmap, specularColorFactor = gltf2_blender_gather_texture_info.gather_texture_info(
+            primary_socket, 
+            sockets, 
+            export_settings,
+            filter_type='ANY')
+        if specularColorTexture is None:
+            return None, None
+        if use_active_uvmap:
+            use_actives_uvmaps.append("specularColorTexture")
+
+        specular_ext_enabled = True
+        specular_extension['specularColorTexture'] = specularColorTexture
+
+
+        if specularColorFactor is not None:
+            specular_extension['specularColorFactor'] = specularColorFactor
+            
+
+    specular_extension = Extension('KHR_materials_specular', specular_extension, False) if specular_ext_enabled else None
+    return specular_extension, use_actives_uvmaps
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py
new file mode 100644
index 0000000000000000000000000000000000000000..fdc5d8c751705717edfa806e22e28cb38307f4c4
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py
@@ -0,0 +1,47 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+def export_transmission(blender_material, export_settings):
+    transmission_enabled = False
+    has_transmission_texture = False
+
+    transmission_extension = {}
+    transmission_slots = ()
+
+    transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
+
+    if isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked:
+        transmission_extension['transmissionFactor'] = transmission_socket.default_value
+        transmission_enabled = transmission_extension['transmissionFactor'] > 0
+    elif gltf2_blender_get.has_image_node_from_socket(transmission_socket):
+        fac = gltf2_blender_get.get_factor_from_socket(transmission_socket, kind='VALUE')
+        transmission_extension['transmissionFactor'] = fac if fac is not None else 1.0
+        has_transmission_texture = True
+        transmission_enabled = True
+
+    if not transmission_enabled:
+        return None, None
+
+    # Pack transmission channel (R).
+    if has_transmission_texture:
+        transmission_slots = (transmission_socket,)
+
+    use_actives_uvmaps = []
+
+    if len(transmission_slots) > 0:
+        combined_texture, use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+            transmission_socket,
+            transmission_slots,
+            export_settings,
+        )
+        if has_transmission_texture:
+            transmission_extension['transmissionTexture'] = combined_texture
+        if use_active_uvmap:
+            use_actives_uvmaps.append("transmissionTexture")
+
+    return Extension('KHR_materials_transmission', transmission_extension, False), use_actives_uvmaps
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py
index e104b7f1f30316ffdae863a29aa1b6c598329f9d..b501f98f0d453b0c290ed5357b65415d3ba6b579 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py
@@ -1,9 +1,9 @@
 # SPDX-License-Identifier: Apache-2.0
-# Copyright 2018-2021 The glTF-Blender-IO authors.
+# Copyright 2018-2022 The glTF-Blender-IO authors.
 
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
 from io_scene_gltf2.blender.exp import gltf2_blender_get
-
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
 
 def detect_shadeless_material(blender_material, export_settings):
     """Detect if this material is "shadeless" ie. should be exported
@@ -127,7 +127,7 @@ def gather_base_color_texture(info, export_settings):
         # because gather_image determines how to pack images based on the
         # names of sockets, and the names are hard-coded to a Principled
         # style graph.
-        unlit_texture, unlit_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
+        unlit_texture, unlit_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
             sockets[0],
             sockets,
             export_settings,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c452e6a6fcbed45189d6944db3b2b628c9e66f8
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from typing import Dict, Any
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io_variants
+
+
+@cached
+def gather_variant(variant_idx, export_settings) -> Dict[str, Any]:
+
+    variant = gltf2_io_variants.Variant(
+        name=bpy.data.scenes[0].gltf2_KHR_materials_variants_variants[variant_idx].name,
+        extensions=None,
+        extras=None
+    )
+    return variant.to_dict()
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a69e3f68fa55da85dee6b6e156436ad7d9639c9
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+
+def export_volume(blender_material, export_settings):
+    # Implementation based on https://github.com/KhronosGroup/glTF-Blender-IO/issues/1454#issuecomment-928319444
+
+    # If no transmission --> No volume
+    transmission_enabled = False
+    transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
+    if isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked:
+        transmission_enabled = transmission_socket.default_value > 0
+    elif gltf2_blender_get.has_image_node_from_socket(transmission_socket):
+        transmission_enabled = True
+
+    if transmission_enabled is False:
+        return None, None
+
+    volume_extension = {}
+    has_thickness_texture = False
+    thickness_slots = ()
+
+    thicknesss_socket = gltf2_blender_get.get_socket_old(blender_material, 'Thickness')
+    if thicknesss_socket is None:
+        # If no thickness (here because there is no glTF Material Output node), no volume extension export
+            return None, None
+
+    density_socket = gltf2_blender_get.get_socket(blender_material, 'Density', volume=True)
+    attenuation_color_socket = gltf2_blender_get.get_socket(blender_material, 'Color', volume=True)
+    # Even if density or attenuation are not set, we export volume extension
+
+    if isinstance(attenuation_color_socket, bpy.types.NodeSocket):
+        rgb = gltf2_blender_get.get_const_from_default_value_socket(attenuation_color_socket, kind='RGB')
+        volume_extension['attenuationColor'] = rgb
+
+    if isinstance(density_socket, bpy.types.NodeSocket):
+        density = gltf2_blender_get.get_const_from_default_value_socket(density_socket, kind='VALUE')
+        volume_extension['attenuationDistance'] = 1.0 / density if density != 0 else None # infinity (Using None as glTF default)
+
+
+    if isinstance(thicknesss_socket, bpy.types.NodeSocket) and not thicknesss_socket.is_linked:
+        val = thicknesss_socket.default_value
+        if val == 0.0:
+            # If no thickness, no volume extension export 
+            return None, None
+        volume_extension['thicknessFactor'] = val
+    elif gltf2_blender_get.has_image_node_from_socket(thicknesss_socket):
+        fac = gltf2_blender_get.get_factor_from_socket(thicknesss_socket, kind='VALUE')
+        # default value in glTF is 0.0, but if there is a texture without factor, use 1
+        volume_extension['thicknessFactor'] = fac if fac != None else 1.0
+        has_thickness_texture = True
+
+       # Pack thickness channel (R).
+    if has_thickness_texture:
+        thickness_slots = (thicknesss_socket,)
+
+    use_actives_uvmaps = []
+
+    if len(thickness_slots) > 0:
+        combined_texture, use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+            thicknesss_socket,
+            thickness_slots,
+            export_settings,
+        )
+        if has_thickness_texture:
+            volume_extension['thicknessTexture'] = combined_texture
+        if use_active_uvmap:
+            use_actives_uvmaps.append("thicknessTexture")
+
+    return Extension('KHR_materials_volume', volume_extension, False), use_actives_uvmaps
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
index 72f0268c68b505307280f564a4a49b9eb0f39ab8..1af588b9a3de9098ca79fd8b79fabced361bb4cc 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
@@ -44,7 +44,7 @@ def array_to_accessor(array, component_type, data_type, include_max_and_min=Fals
         amin = np.amin(array, axis=0).tolist()
 
     return gltf2_io.Accessor(
-        buffer_view=gltf2_io_binary_data.BinaryData(array.tobytes()),
+        buffer_view=gltf2_io_binary_data.BinaryData(array.tobytes(), gltf2_io_constants.BufferViewTarget.ARRAY_BUFFER),
         byte_offset=None,
         component_type=component_type,
         count=len(array),
@@ -124,28 +124,34 @@ def __gather_colors(blender_primitive, export_settings):
         color_index = 0
         color_id = 'COLOR_' + str(color_index)
         while blender_primitive["attributes"].get(color_id) is not None:
-            colors = blender_primitive["attributes"][color_id]
+            colors = blender_primitive["attributes"][color_id]["data"]
 
             if type(colors) is not np.ndarray:
                 colors = np.array(colors, dtype=np.float32)
                 colors = colors.reshape(len(colors) // 4, 4)
 
-            # Convert to normalized ushorts
-            colors *= 65535
-            colors += 0.5  # bias for rounding
-            colors = colors.astype(np.uint16)
+            if blender_primitive["attributes"][color_id]["norm"] is True:
+                comp_type = gltf2_io_constants.ComponentType.UnsignedShort
+
+                # Convert to normalized ushorts
+                colors *= 65535
+                colors += 0.5  # bias for rounding
+                colors = colors.astype(np.uint16)
+
+            else:
+                comp_type = gltf2_io_constants.ComponentType.Float
 
             attributes[color_id] = gltf2_io.Accessor(
-                buffer_view=gltf2_io_binary_data.BinaryData(colors.tobytes()),
+                buffer_view=gltf2_io_binary_data.BinaryData(colors.tobytes(), gltf2_io_constants.BufferViewTarget.ARRAY_BUFFER),
                 byte_offset=None,
-                component_type=gltf2_io_constants.ComponentType.UnsignedShort,
+                component_type=comp_type,
                 count=len(colors),
                 extensions=None,
                 extras=None,
                 max=None,
                 min=None,
                 name=None,
-                normalized=True,
+                normalized=blender_primitive["attributes"][color_id]["norm"],
                 sparse=None,
                 type=gltf2_io_constants.DataType.Vec4,
             )
@@ -167,6 +173,13 @@ def __gather_skins(blender_primitive, export_settings):
         max_bone_set_index += 1
     max_bone_set_index -= 1
 
+    # Here, a set represents a group of 4 weights.
+    # So max_bone_set_index value:
+    # if -1 => No weights
+    # if 1 => Max 4 weights
+    # if 2 => Max 8 weights
+    # etc...
+
     # If no skinning
     if max_bone_set_index < 0:
         return attributes
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
index 367c30f57dc4f68a430c7324bd049ecbb0e92241..576a1418c82a307def53053dc07bbd26df26c0c4 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
@@ -12,10 +12,12 @@ from io_scene_gltf2.blender.exp import gltf2_blender_extract
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitive_attributes
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_variants
 
 from io_scene_gltf2.io.com import gltf2_io
 from io_scene_gltf2.io.exp import gltf2_io_binary_data
 from io_scene_gltf2.io.com import gltf2_io_constants
+from io_scene_gltf2.io.com import gltf2_io_extensions
 from io_scene_gltf2.io.com.gltf2_io_debug import print_console
 
 
@@ -86,7 +88,7 @@ def gather_primitives(
 
         primitive = gltf2_io.MeshPrimitive(
             attributes=internal_primitive['attributes'],
-            extensions=None,
+            extensions=__gather_extensions(blender_mesh, material_idx, active_uvmap_idx, export_settings),
             extras=None,
             indices=internal_primitive['indices'],
             material=material,
@@ -148,7 +150,7 @@ def __gather_indices(blender_primitive, blender_mesh, modifiers, export_settings
         return None
 
     element_type = gltf2_io_constants.DataType.Scalar
-    binary_data = gltf2_io_binary_data.BinaryData(indices.tobytes())
+    binary_data = gltf2_io_binary_data.BinaryData(indices.tobytes(), bufferViewTarget=gltf2_io_constants.BufferViewTarget.ELEMENT_ARRAY_BUFFER)
     return gltf2_blender_gather_accessors.gather_accessor(
         binary_data,
         component_type,
@@ -214,3 +216,55 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings
                     morph_index += 1
         return targets
     return None
+
+def __gather_extensions(blender_mesh,
+                        material_idx: int,
+                        active_uvmap_idx,
+                        export_settings):
+    extensions = {}
+
+    if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is False:
+        return None
+
+    if bpy.data.scenes[0].get('gltf2_KHR_materials_variants_variants') is None:
+        return None
+    if len(bpy.data.scenes[0]['gltf2_KHR_materials_variants_variants']) == 0:
+        return None
+
+    # Material idx is the slot idx. Retrieve associated variant, if any
+    mapping = []
+    for i in [v for v in blender_mesh.gltf2_variant_mesh_data if v.material_slot_index == material_idx]:
+        variants = []
+        for idx, v in enumerate(i.variants):
+            if v.variant.variant_idx in [o.variant.variant_idx for o in i.variants[:idx]]:
+                # Avoid duplicates
+                continue
+            vari = gltf2_blender_gather_materials_variants.gather_variant(v.variant.variant_idx, export_settings)
+            if vari is not None:
+                variant_extension = gltf2_io_extensions.ChildOfRootExtension(
+                name="KHR_materials_variants",
+                path=["variants"],
+                extension=vari
+            )
+            variants.append(variant_extension)
+        if len(variants) > 0:
+            if i.material:
+                mat = gltf2_blender_gather_materials.gather_material(
+                        i.material,
+                        active_uvmap_idx,
+                        export_settings
+                    )
+            else:
+                # empty slot
+                mat = None
+            mapping.append({'material': mat, 'variants': variants})
+
+    if len(mapping) > 0:
+        extensions["KHR_materials_variants"] = gltf2_io_extensions.Extension(
+            name="KHR_materials_variants",
+            extension={
+                "mappings": mapping
+            }
+        )
+
+    return extensions if extensions else None
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
index e8c6baf183ffcc4c0b30d3ae44d0621b77ff2b93..ccfd42e5219f2b54ecf0a3e95d7731324b2500ea 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
@@ -26,24 +26,26 @@ def gather_texture(
     """
 
     if not __filter_texture(blender_shader_sockets, export_settings):
-        return None
+        return None, None
+
+    source, factor = __gather_source(blender_shader_sockets, export_settings)
 
     texture = gltf2_io.Texture(
         extensions=__gather_extensions(blender_shader_sockets, export_settings),
         extras=__gather_extras(blender_shader_sockets, export_settings),
         name=__gather_name(blender_shader_sockets, export_settings),
         sampler=__gather_sampler(blender_shader_sockets, export_settings),
-        source=__gather_source(blender_shader_sockets, export_settings)
+        source= source
     )
 
     # although valid, most viewers can't handle missing source properties
     # This can have None source for "keep original", when original can't be found
     if texture.source is None:
-        return None
+        return None, None
 
     export_user_extensions('gather_texture_hook', export_settings, texture, blender_shader_sockets)
 
-    return texture
+    return texture, factor
 
 
 def __filter_texture(blender_shader_sockets, export_settings):
@@ -66,13 +68,14 @@ def __gather_name(blender_shader_sockets, export_settings):
 
 
 def __gather_sampler(blender_shader_sockets, export_settings):
-    shader_nodes = [__get_tex_from_socket(socket).shader_node for socket in blender_shader_sockets]
+    shader_nodes = [__get_tex_from_socket(socket) for socket in blender_shader_sockets]
     if len(shader_nodes) > 1:
         gltf2_io_debug.print_console("WARNING",
                                      "More than one shader node tex image used for a texture. "
                                      "The resulting glTF sampler will behave like the first shader node tex image.")
+    first_valid_shader_node = next(filter(lambda x: x is not None, shader_nodes)).shader_node
     return gltf2_blender_gather_sampler.gather_sampler(
-        shader_nodes[0],
+        first_valid_shader_node,
         export_settings)
 
 
@@ -81,7 +84,7 @@ def __gather_source(blender_shader_sockets, export_settings):
 
 # Helpers
 
-
+# TODOExt deduplicate
 def __get_tex_from_socket(socket):
     result = gltf2_blender_search_node_tree.from_socket(
         socket,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
index 15b101ade7c38d19377225c32bfd882ed6ad1eca..5fe2da329ac77b254f7950ef5f4d9c813926a893 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
@@ -19,14 +19,14 @@ from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extension
 # occlusion the primary_socket would be the occlusion socket, and
 # blender_shader_sockets would be the (O,R,M) sockets.
 
-def gather_texture_info(primary_socket, blender_shader_sockets, export_settings):
-    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'DEFAULT', export_settings)
+def gather_texture_info(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
+    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'DEFAULT', filter_type, export_settings)
 
-def gather_material_normal_texture_info_class(primary_socket, blender_shader_sockets, export_settings):
-    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'NORMAL', export_settings)
+def gather_material_normal_texture_info_class(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
+    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'NORMAL', filter_type, export_settings)
 
-def gather_material_occlusion_texture_info_class(primary_socket, blender_shader_sockets, export_settings):
-    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'OCCLUSION', export_settings)
+def gather_material_occlusion_texture_info_class(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
+    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'OCCLUSION', filter_type, export_settings)
 
 
 @cached
@@ -34,16 +34,19 @@ def __gather_texture_info_helper(
         primary_socket: bpy.types.NodeSocket,
         blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
         kind: str,
+        filter_type: str,
         export_settings):
-    if not __filter_texture_info(primary_socket, blender_shader_sockets, export_settings):
-        return None, None
+    if not __filter_texture_info(primary_socket, blender_shader_sockets, filter_type, export_settings):
+        return None, None, None
 
     tex_transform, tex_coord, use_active_uvmap = __gather_texture_transform_and_tex_coord(primary_socket, export_settings)
 
+    index, factor = __gather_index(blender_shader_sockets, export_settings)
+
     fields = {
         'extensions': __gather_extensions(tex_transform, export_settings),
         'extras': __gather_extras(blender_shader_sockets, export_settings),
-        'index': __gather_index(blender_shader_sockets, export_settings),
+        'index': index,
         'tex_coord': tex_coord
     }
 
@@ -59,14 +62,14 @@ def __gather_texture_info_helper(
         texture_info = gltf2_io.MaterialOcclusionTextureInfoClass(**fields)
 
     if texture_info.index is None:
-        return None, None
+        return None, None, None
 
     export_user_extensions('gather_texture_info_hook', export_settings, texture_info, blender_shader_sockets)
 
-    return texture_info, use_active_uvmap
+    return texture_info, use_active_uvmap, factor
 
 
-def __filter_texture_info(primary_socket, blender_shader_sockets, export_settings):
+def __filter_texture_info(primary_socket, blender_shader_sockets, filter_type, export_settings):
     if primary_socket is None:
         return False
     if __get_tex_from_socket(primary_socket) is None:
@@ -75,9 +78,18 @@ def __filter_texture_info(primary_socket, blender_shader_sockets, export_setting
         return False
     if not all([elem is not None for elem in blender_shader_sockets]):
         return False
-    if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets]):
-        # sockets do not lead to a texture --> discard
-        return False
+    if filter_type == "ALL":
+        # Check that all sockets link to texture
+        if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets]):
+            # sockets do not lead to a texture --> discard
+            return False
+    elif filter_type == "ANY":
+        # Check that at least one socket link to texture
+        if all([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets]):
+            return False
+    elif filter_type == "NONE":
+        # No check 
+        pass
 
     return True
 
@@ -163,7 +175,7 @@ def __gather_texture_transform_and_tex_coord(primary_socket, export_settings):
 
     return texture_transform, texcoord_idx or None, use_active_uvmap
 
-
+# TODOExt deduplicate
 def __get_tex_from_socket(socket):
     result = gltf2_blender_search_node_tree.from_socket(
         socket,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
index ba63e049211d29362c3cf80447765b855d598999..c654b445e006a1e43cfa798ca599ef8e249c0d32 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
@@ -95,10 +95,18 @@ class VExportTree:
         bpy.context.window.scene = blender_scene
         depsgraph = bpy.context.evaluated_depsgraph_get()
 
+        # Gather parent/children information once, as calling bobj.children is
+        #   very expensive operation : takes O(len(bpy.data.objects)) time.
+        blender_children = dict()
+        for bobj in bpy.data.objects:
+            bparent = bobj.parent
+            blender_children.setdefault(bobj, [])
+            blender_children.setdefault(bparent, []).append(bobj)
+
         for blender_object in [obj.original for obj in depsgraph.scene_eval.objects if obj.parent is None]:
-            self.recursive_node_traverse(blender_object, None, None, Matrix.Identity(4))
+            self.recursive_node_traverse(blender_object, None, None, Matrix.Identity(4), blender_children)
 
-    def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, parent_coll_matrix_world, armature_uuid=None, dupli_world_matrix=None):
+    def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, parent_coll_matrix_world, blender_children, armature_uuid=None, dupli_world_matrix=None):
         node = VExportNode()
         node.uuid = str(uuid.uuid4())
         node.parent_uuid = parent_uuid
@@ -163,10 +171,16 @@ class VExportTree:
             # So real world matrix is collection world_matrix @ "world_matrix" of object
             node.matrix_world = parent_coll_matrix_world @ blender_object.matrix_world.copy()
             if node.blender_type == VExportNode.CAMERA and self.export_settings[gltf2_blender_export_keys.CAMERAS]:
-                correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
+                if self.export_settings[gltf2_blender_export_keys.YUP]:
+                    correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
+                else:
+                    correction = Matrix.Identity(4).to_quaternion()
                 node.matrix_world @= correction.to_matrix().to_4x4()
             elif node.blender_type == VExportNode.LIGHT and self.export_settings[gltf2_blender_export_keys.LIGHTS]:
-                correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
+                if self.export_settings[gltf2_blender_export_keys.YUP]:
+                    correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
+                else:
+                    correction = Matrix.Identity(4).to_quaternion()
                 node.matrix_world @= correction.to_matrix().to_4x4()
         elif node.blender_type == VExportNode.BONE:
             if self.export_settings['gltf_current_frame'] is True:
@@ -193,42 +207,42 @@ class VExportTree:
 
         # standard children
         if blender_bone is None and blender_object.is_instancer is False:
-            for child_object in blender_object.children:
+            for child_object in blender_children[blender_object]:
                 if child_object.parent_bone:
                     # Object parented to bones
                     # Will be manage later
                     continue
                 else:
                     # Classic parenting
-                    self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world)
+                    self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world, blender_children)
 
         # Collections
         if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
             for dupli_object in blender_object.instance_collection.all_objects:
                 if dupli_object.parent is not None:
                     continue
-                self.recursive_node_traverse(dupli_object, None, node.uuid, node.matrix_world)
+                self.recursive_node_traverse(dupli_object, None, node.uuid, node.matrix_world, blender_children)
 
         # Armature : children are bones with no parent
         if blender_object.type == "ARMATURE" and blender_bone is None:
             for b in [b for b in blender_object.pose.bones if b.parent is None]:
-                self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, node.uuid)
+                self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, blender_children, node.uuid)
 
         # Bones
         if blender_object.type == "ARMATURE" and blender_bone is not None:
             for b in blender_bone.children:
-                self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, armature_uuid)
+                self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, blender_children, armature_uuid)
 
         # Object parented to bone
         if blender_bone is not None:
-            for child_object in [c for c in blender_object.children if c.parent_bone is not None and c.parent_bone == blender_bone.name]:
-                self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world)
+            for child_object in [c for c in blender_children[blender_object] if c.parent_bone is not None and c.parent_bone == blender_bone.name]:
+                self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world, blender_children)
 
         # Duplis
         if blender_object.is_instancer is True and blender_object.instance_type != 'COLLECTION':
             depsgraph = bpy.context.evaluated_depsgraph_get()
             for (dupl, mat) in [(dup.object.original, dup.matrix_world.copy()) for dup in depsgraph.object_instances if dup.parent and id(dup.parent.original) == id(blender_object)]:
-                self.recursive_node_traverse(dupl, None, node.uuid, parent_coll_matrix_world, dupli_world_matrix=mat)
+                self.recursive_node_traverse(dupl, None, node.uuid, parent_coll_matrix_world, blender_children, dupli_world_matrix=mat)
 
     def get_all_objects(self):
         return [n.uuid for n in self.nodes.values() if n.blender_type != VExportNode.BONE]
@@ -462,3 +476,20 @@ class VExportTree:
                 skin = gather_skin(n.uuid, self.export_settings)
                 skins.append(skin)
         return skins
+
+    def variants_reset_to_original(self):
+        # Only if Variants are displayed and exported
+        if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is False:
+            return
+        objects = [self.nodes[o].blender_object for o in self.get_all_node_of_type(VExportNode.OBJECT) if self.nodes[o].blender_object.type == "MESH" \
+            and self.nodes[o].blender_object.data.get('gltf2_variant_default_materials') is not None]
+        for obj in objects:
+            # loop on material slots ( primitives )
+            for mat_slot_idx, s in enumerate(obj.material_slots):
+                # Check if there is a default material for this slot
+                for i in obj.data.gltf2_variant_default_materials:
+                    if i.material_slot_index == mat_slot_idx:
+                        s.material = i.default_material
+                        break
+
+            # If not found, keep current material as default
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_get.py b/io_scene_gltf2/blender/exp/gltf2_blender_get.py
index e38906e6494958b278a2c6be7b47369b0b1079c1..9e468186a9338aec7be998c09f923f5ee5b5839b 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_get.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_get.py
@@ -4,9 +4,10 @@
 import bpy
 from mathutils import Vector, Matrix
 
-from ..com.gltf2_blender_material_helpers import get_gltf_node_name
+from ..com.gltf2_blender_material_helpers import get_gltf_node_name, get_gltf_node_old_name
 from ...blender.com.gltf2_blender_conversion import texture_transform_blender_to_gltf
 from io_scene_gltf2.io.com import gltf2_io_debug
+from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
 
 
 def get_animation_target(action_group: bpy.types.ActionGroup):
@@ -47,7 +48,7 @@ def get_node_socket(blender_material, type, name):
     return None
 
 
-def get_socket(blender_material: bpy.types.Material, name: str):
+def get_socket(blender_material: bpy.types.Material, name: str, volume=False):
     """
     For a given material input name, retrieve the corresponding node tree socket.
 
@@ -70,8 +71,15 @@ def get_socket(blender_material: bpy.types.Material, name: str):
         elif name == "Background":
             type = bpy.types.ShaderNodeBackground
             name = "Color"
+        elif name == "sheenColor":
+            return get_node_socket(blender_material, bpy.types.ShaderNodeBsdfVelvet, "Color")
+        elif name == "sheenRoughness":
+            return get_node_socket(blender_material, bpy.types.ShaderNodeBsdfVelvet, "Sigma")
         else:
-            type = bpy.types.ShaderNodeBsdfPrincipled
+            if volume is False:
+                type = bpy.types.ShaderNodeBsdfPrincipled
+            else:
+                type = bpy.types.ShaderNodeVolumeAbsorption
 
         return get_node_socket(blender_material, type, name)
 
@@ -86,11 +94,11 @@ def get_socket_old(blender_material: bpy.types.Material, name: str):
     :param name: the name of the socket
     :return: a blender NodeSocket
     """
-    gltf_node_group_name = get_gltf_node_name().lower()
+    gltf_node_group_names = [get_gltf_node_name().lower(), get_gltf_node_old_name().lower()]
     if blender_material.node_tree and blender_material.use_nodes:
         nodes = [n for n in blender_material.node_tree.nodes if \
             isinstance(n, bpy.types.ShaderNodeGroup) and \
-            (n.node_tree.name.startswith('glTF Metallic Roughness') or n.node_tree.name.lower() == gltf_node_group_name)]
+            (n.node_tree.name.startswith('glTF Metallic Roughness') or n.node_tree.name.lower() in gltf_node_group_names)]
         inputs = sum([[input for input in node.inputs if input.name == name] for node in nodes], [])
         if inputs:
             return inputs[0]
@@ -297,3 +305,12 @@ def previous_node(socket):
     if prev_socket is not None:
         return prev_socket.node
     return None
+
+#TODOExt is this the same as __get_tex_from_socket from gather_image ?
+def has_image_node_from_socket(socket):
+    result = gltf2_blender_search_node_tree.from_socket(
+        socket,
+        gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
+    if not result:
+        return False
+    return True
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
index 8b9db89a344a7991bfaffad57e0b110bc3822848..6730f479135ce0aa16d89abc4638c0cdabf80f55 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
@@ -27,6 +27,18 @@ class FillWhite:
     """Fills a channel with all ones (1.0)."""
     pass
 
+class StoreData:
+    def __init__(self, data):
+        """Store numeric data (not an image channel"""
+        self.data = data
+
+class StoreImage:
+    """
+    Store a channel with the channel src_chan from a Blender image.
+    This channel will be used for numpy calculation (no direct channel mapping)
+    """
+    def __init__(self, image: bpy.types.Image):
+        self.image = image
 
 class ExportImage:
     """Custom image class.
@@ -55,9 +67,13 @@ class ExportImage:
 
     def __init__(self, original=None):
         self.fills = {}
+        self.stored = {}
 
-        # In case of keeping original texture images
-        self.original = original
+        self.original = original # In case of keeping original texture images
+        self.numpy_calc = None
+
+    def set_calc(self, numpy_calc):
+        self.numpy_calc = numpy_calc # In case of numpy calculation (no direct channel mapping)
 
     @staticmethod
     def from_blender_image(image: bpy.types.Image):
@@ -73,6 +89,12 @@ class ExportImage:
     def fill_image(self, image: bpy.types.Image, dst_chan: Channel, src_chan: Channel):
         self.fills[dst_chan] = FillImage(image, src_chan)
 
+    def store_data(self, identifier, data, type='Image'):
+        if type == "Image": # This is an image
+            self.stored[identifier] = StoreImage(data)
+        else: # This is a numeric value
+            self.stored[identifier] = StoreData(data)
+
     def fill_white(self, dst_chan: Channel):
         self.fills[dst_chan] = FillWhite()
 
@@ -81,7 +103,7 @@ class ExportImage:
 
     def empty(self) -> bool:
         if self.original is None:
-            return not self.fills
+            return not (self.fills or self.stored)
         else:
             return False
 
@@ -103,7 +125,7 @@ class ExportImage:
             len(set(fill.image.name for fill in self.fills.values())) == 1
         )
 
-    def encode(self, mime_type: Optional[str]) -> bytes:
+    def encode(self, mime_type: Optional[str]) -> Tuple[bytes, bool]:
         self.file_format = {
             "image/jpeg": "JPEG",
             "image/png": "PNG"
@@ -111,10 +133,14 @@ class ExportImage:
 
         # Happy path = we can just use an existing Blender image
         if self.__on_happy_path():
-            return self.__encode_happy()
+            return self.__encode_happy(), None
 
-        # Unhappy path = we need to create the image self.fills describes.
-        return self.__encode_unhappy()
+        # Unhappy path = we need to create the image self.fills describes or self.stores describes
+        if self.numpy_calc is None:
+            return self.__encode_unhappy(), None
+        else:
+            pixels, width, height, factor = self.numpy_calc(self.stored)
+            return self.__encode_from_numpy_array(pixels, (width, height)), factor
 
     def __encode_happy(self) -> bytes:
         return self.__encode_from_image(self.blender_image())
@@ -147,7 +173,7 @@ class ExportImage:
             else:
                 # Image is the wrong size; make a temp copy and scale it.
                 with TmpImageGuard() as guard:
-                    _make_temp_image_copy(guard, src_image=image)
+                    make_temp_image_copy(guard, src_image=image)
                     tmp_image = guard.image
                     tmp_image.scale(width, height)
                     tmp_image.pixels.foreach_get(tmp_buf)
@@ -197,7 +223,7 @@ class ExportImage:
 
         # Copy to a temp image and save.
         with TmpImageGuard() as guard:
-            _make_temp_image_copy(guard, src_image=image)
+            make_temp_image_copy(guard, src_image=image)
             tmp_image = guard.image
             return _encode_temp_image(tmp_image, self.file_format)
 
@@ -228,7 +254,7 @@ class TmpImageGuard:
             bpy.data.images.remove(self.image, do_unlink=True)
 
 
-def _make_temp_image_copy(guard: TmpImageGuard, src_image: bpy.types.Image):
+def make_temp_image_copy(guard: TmpImageGuard, src_image: bpy.types.Image):
     """Makes a temporary copy of src_image. Will be cleaned up with guard."""
     guard.image = src_image.copy()
     tmp_image = guard.image
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py b/io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py
new file mode 100644
index 0000000000000000000000000000000000000000..6321f1285d644a13629ed01d19afc6e6adf034d4
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+import numpy as np
+from .gltf2_blender_gather_image import StoreImage, StoreData
+from .gltf2_blender_image import TmpImageGuard, make_temp_image_copy
+
+def specular_calculation(stored):
+
+    # See https://gist.github.com/proog128/d627c692a6bbe584d66789a5a6437a33
+    
+    # Find all Blender images used
+    images = []
+    for fill in stored.values():
+        if isinstance(fill, StoreImage):
+            if fill.image not in images:
+                images.append(fill.image)
+
+    if not images:
+        # No ImageFills; use a 1x1 white pixel
+        pixels = np.array([1.0, 1.0, 1.0, 1.0], np.float32)
+        return pixels, 1, 1
+
+    width = max(image.size[0] for image in images)
+    height = max(image.size[1] for image in images)
+
+    buffers = {}
+
+    for identifier, image in [(ident, store.image) for (ident, store) in stored.items() if isinstance(store, StoreImage)]:
+        tmp_buf = np.empty(width * height * 4, np.float32)
+        
+        if image.size[0] == width and image.size[1] == height:
+            image.pixels.foreach_get(tmp_buf)
+        else:
+            # Image is the wrong size; make a temp copy and scale it.
+            with TmpImageGuard() as guard:
+                make_temp_image_copy(guard, src_image=image)
+                tmp_image = guard.image
+                tmp_image.scale(width, height)
+                tmp_image.pixels.foreach_get(tmp_buf)
+
+        buffers[identifier] = np.reshape(tmp_buf, [width, height, 4])
+
+    # keep only needed channels
+    ## scalar
+    for i in ['specular', 'specular_tint', 'transmission']:
+        if i in buffers.keys():
+            buffers[i] = buffers[i][:,:,stored[i + "_channel"].data]
+        else:
+            buffers[i] = np.full((width, height, 1), stored[i].data)
+
+    # Vector 3
+    for i in ['base_color']:
+        if i in buffers.keys():
+            if i + "_channel" not in stored.keys():
+                buffers[i] = buffers[i][:,:,:3]
+            else:
+                # keep only needed channel
+                for c in range(3):
+                    if c != stored[i+"_channel"].data:
+                        buffers[i][:, :, c] = 0.0
+                buffers[i] = buffers[i][:,:,:3]
+        else:
+            buffers[i] = np.full((width, height, 3), stored[i].data[0:3])
+
+    ior = stored['ior'].data
+
+    # calculation
+    stack3 = lambda v: np.dstack([v]*3)
+
+    def normalize(c):
+        luminance = lambda c: 0.3 * c[:,:,0] + 0.6 * c[:,:,1] + 0.1 * c[:,:,2]
+        l = luminance(c)
+        # TODOExt Manage all 0
+        return c / stack3(l)
+
+    
+    f0_from_ior = ((ior - 1)/(ior + 1))**2
+    tint_strength = (1 - stack3(buffers['specular_tint'])) + normalize(buffers['base_color']) * stack3(buffers['specular_tint'])
+    out_buf = (1 - stack3(buffers['transmission'])) * (1 / f0_from_ior) * 0.08 * stack3(buffers['specular']) * tint_strength + stack3(buffers['transmission']) * tint_strength
+
+    # Manage values > 1.0 -> Need to apply factor
+    factor = None
+    factors = [np.amax(out_buf[:, :, i]) for i in range(3)]
+
+    if any([f > 1.0 for f in factors]):
+        factor = [1.0 if f < 1.0 else f for f in factors]
+        out_buf /= factor
+
+    out_buf = np.dstack((out_buf, np.ones((width, height)))) # Set alpha (glTF specular) to 1
+    out_buf = np.reshape(out_buf, (width * height * 4))
+    
+    return np.float32(out_buf), width, height, [float(f) for f in factor] if factor else None
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_ior.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_ior.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab5b2e7e36a95e2cf86d3aac348dc0bff46270ec
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_ior.py
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2021 The glTF-Blender-IO authors.
+
+from ...io.com.gltf2_io_constants import GLTF_IOR
+
+def ior(mh, ior_socket):
+    try:
+        ext = mh.pymat.extensions['KHR_materials_ior']
+    except Exception:
+        return
+    ior = ext.get('ior', GLTF_IOR)
+    ior_socket.default_value = ior
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py
index bed63f7f6529fac05ea86e47d1fd65a000d8bc6e..19a394b9975b274bd38176ec7deabc6f4c5ff2af 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py
@@ -22,19 +22,24 @@ def pbr_specular_glossiness(mh):
     mh.node_tree.links.new(add_node.inputs[0], glossy_node.outputs[0])
     mh.node_tree.links.new(add_node.inputs[1], diffuse_node.outputs[0])
 
-    emission_socket, alpha_socket = make_output_nodes(
+    emission_socket, alpha_socket, _, _ = make_output_nodes(
         mh,
         location=(370, 250),
+        additional_location=None, #No additional location needed for SpecGloss
         shader_socket=add_node.outputs[0],
         make_emission_socket=mh.needs_emissive(),
         make_alpha_socket=not mh.is_opaque(),
+        make_volume_socket=None, # No possible to have KHR_materials_volume with specular/glossiness
+        make_velvet_socket=None # No possible to have KHR_materials_volume with specular/glossiness
     )
 
-    emission(
-        mh,
-        location=(-200, 860),
-        color_socket=emission_socket,
-    )
+    if emission_socket:
+        emission(
+            mh,
+            location=(-200, 860),
+            color_socket=emission_socket,
+            strength_socket=emission_socket.node.inputs['Strength']
+        )
 
     base_color(
         mh,
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_sheen.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_sheen.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa5cef759094ec0478d9f7b1410d7f6145ea0497
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_sheen.py
@@ -0,0 +1,88 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+from ...io.com.gltf2_io import TextureInfo
+from .gltf2_blender_texture import texture
+from .gltf2_blender_image import BlenderImage
+from ..exp.gltf2_blender_image import TmpImageGuard
+import numpy as np
+import bpy
+
+def sheen(  mh,
+            location_sheenColor,
+            location_sheenRoughness,
+            sheenColor_socket,
+            sheenRoughness_socket
+            ):
+
+    x_sheenColor, y_sheenColor = location_sheenColor
+    x_sheenRoughness, y_sheenRoughness = location_sheenRoughness
+
+    try:
+        ext = mh.pymat.extensions['KHR_materials_sheen']
+    except Exception:
+        return
+
+    sheenColorFactor = ext.get('sheenColorFactor', [0.0, 0.0, 0.0])
+    tex_info_color = ext.get('sheenColorTexture')
+    if tex_info_color is not None:
+        tex_info_color = TextureInfo.from_dict(tex_info_color)
+
+    sheenRoughnessFactor = ext.get('sheenRoughnessFactor', 0.0)
+    tex_info_roughness = ext.get('sheenRoughnessTexture')
+    if tex_info_roughness is not None:
+        tex_info_roughness = TextureInfo.from_dict(tex_info_roughness)    
+
+    if tex_info_color is None:
+        sheenColorFactor.extend([1.0])
+        sheenColor_socket.default_value = sheenColorFactor
+    else:
+        # Mix sheenColor factor
+        sheenColorFactor = sheenColorFactor + [1.0]
+        if sheenColorFactor != [1.0, 1.0, 1.0, 1.0]:
+            node = mh.node_tree.nodes.new('ShaderNodeMixRGB')
+            node.label = 'sheenColor Factor'
+            node.location = x_sheenColor - 140, y_sheenColor
+            node.blend_type = 'MULTIPLY'
+            # Outputs
+            mh.node_tree.links.new(sheenColor_socket, node.outputs[0])
+            # Inputs
+            node.inputs['Fac'].default_value = 1.0
+            sheenColor_socket = node.inputs['Color1']
+            node.inputs['Color2'].default_value = sheenColorFactor
+            x_sheenColor -= 200
+
+        texture(
+            mh,
+            tex_info=tex_info_color,
+            label='SHEEN COLOR',
+            location=(x_sheenColor, y_sheenColor),
+            color_socket=sheenColor_socket
+            )
+
+    if tex_info_roughness is None:
+        sheenRoughness_socket.default_value = sheenRoughnessFactor
+    else:
+         # Mix sheenRoughness factor
+        if sheenRoughnessFactor != 1.0:
+            node = mh.node_tree.nodes.new('ShaderNodeMath')
+            node.label = 'shennRoughness Factor'
+            node.location = x_sheenRoughness - 140, y_sheenRoughness
+            node.operation = 'MULTIPLY'
+            # Outputs
+            mh.node_tree.links.new(sheenRoughness_socket, node.outputs[0])
+            # Inputs
+            sheenRoughness_socket = node.inputs[0]
+            node.inputs[1].default_value = sheenRoughnessFactor
+            x_sheenRoughness -= 200
+
+        texture(
+            mh,
+            tex_info=tex_info_roughness,
+            label='SHEEN ROUGHNESS',
+            location=(x_sheenRoughness, y_sheenRoughness),
+            is_data=True,
+            color_socket=None,
+            alpha_socket=sheenRoughness_socket
+            )
+    return
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_specular.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_specular.py
new file mode 100644
index 0000000000000000000000000000000000000000..3441b5ad945be0dd18be474563aa6f41abc545ea
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_specular.py
@@ -0,0 +1,356 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2021 The glTF-Blender-IO authors.
+
+import bpy
+from ...io.com.gltf2_io import TextureInfo
+from .gltf2_blender_texture import texture
+from io_scene_gltf2.io.com.gltf2_io_constants import GLTF_IOR
+from .gltf2_blender_image import BlenderImage
+from ..exp.gltf2_blender_image import TmpImageGuard, make_temp_image_copy
+
+
+def specular(mh, location_specular, 
+                 location_specular_tint, 
+                 specular_socket, 
+                 specular_tint_socket,
+                 original_specular_socket,
+                 original_specularcolor_socket,
+                 location_original_specular,
+                 location_original_specularcolor):
+    x_specular, y_specular = location_specular
+    x_tint, y_tint = location_specular_tint
+
+    if specular_socket is None:
+        return
+    if specular_tint_socket is None:
+        return
+
+    try:
+        ext = mh.pymat.extensions['KHR_materials_specular']
+    except Exception:
+        return
+
+    import numpy as np
+
+    # Retrieve image names
+    try:
+        tex_info = mh.pymat.pbr_metallic_roughness.base_color_texture
+        pytexture = mh.gltf.data.textures[tex_info.index]
+        pyimg = mh.gltf.data.images[pytexture.source]
+        base_color_image_name = pyimg.blender_image_name
+    except:
+        base_color_image_name =  None
+
+    # First check if we need a texture or not -> retrieve all info needed
+    specular_factor = ext.get('specularFactor', 1.0)
+    tex_specular_info = ext.get('specularTexture')
+    if tex_specular_info is not None:
+        tex_specular_info = TextureInfo.from_dict(tex_specular_info)
+
+    specular_color_factor = np.array(ext.get('specularColorFactor', [1.0, 1.0, 1.0])[:3])
+    tex_specular_color_info = ext.get('specularColorTexture')
+    if tex_specular_color_info is not None:
+        tex_specular_color_info = TextureInfo.from_dict(tex_specular_color_info)
+
+    base_color_not_linked = base_color_image_name is None
+    base_color = np.array(mh.pymat.pbr_metallic_roughness.base_color_factor or [1, 1, 1])
+    tex_base_color = mh.pymat.pbr_metallic_roughness.base_color_texture
+    base_color = base_color[:3]
+
+    try:
+        ext_transmission = mh.pymat.extensions['KHR_materials_transmission']
+        transmission_factor = ext_transmission.get('transmissionFactor', 0)
+        tex_transmission_info = ext_transmission.get('transmissionTexture')
+        if tex_transmission_info is not None:
+            tex_transmission_info = TextureInfo.from_dict(tex_transmission_info)
+            pytexture = mh.gltf.data.textures[tex_transmission_info.index]
+            pyimg = mh.gltf.data.images[pytexture.source]
+            transmission_image_name = pyimg.blender_image_name
+        else:
+            transmission_image_name = None
+    except Exception:
+        transmission_factor = 0
+        tex_transmission_info = None
+        transmission_image_name = None
+
+    transmission_not_linked = transmission_image_name is None
+
+    try:
+        ext_ior = mh.pymat.extensions['KHR_materials_ior']
+        ior = ext_ior.get('ior', GLTF_IOR)
+    except:
+        ior = GLTF_IOR
+
+    use_texture = tex_specular_info is not None or tex_specular_color_info is not None \
+        or transmission_not_linked is False or base_color_not_linked is False
+
+
+    # Before creating converted textures,
+    # Also plug non converted data into glTF PBR Non Converted Extensions node
+    original_specular(  mh,
+                        specular_factor, 
+                        tex_specular_info, 
+                        specular_color_factor, 
+                        tex_specular_color_info,
+                        original_specular_socket,
+                        original_specularcolor_socket,
+                        location_original_specular,
+                        location_original_specularcolor
+                        )
+
+    
+    if not use_texture:
+
+        def luminance(c):
+            return 0.3 * c[0] + 0.6 * c[1] + 0.1 * c[2]
+
+        def normalize(c):
+            assert(len(c) == 3)
+            l = luminance(c)
+            if l == 0:
+                return c
+            return np.array([c[0] / l, c[1] / l, c[2] / l])
+
+        f0_from_ior = ((ior - 1)/(ior + 1))**2
+        lum_specular_color = luminance(specular_color_factor)
+        blender_specular = ((lum_specular_color - transmission_factor) / (1 - transmission_factor)) * (1 / 0.08) * f0_from_ior
+        if not all([i == 0 for i in normalize(base_color) - 1]):
+            blender_specular_tint = luminance((normalize(specular_color_factor) - 1) / (normalize(base_color) - 1))
+            if blender_specular_tint < 0 or blender_specular_tint > 1:
+                # TODOExt Warning clamping
+                blender_specular_tint = np.maximum(np.minimum(blender_specular_tint, 1), 0)
+        else:
+            blender_specular_tint = 1.0
+
+        specular_socket.default_value = blender_specular
+        specular_tint_socket.default_value = blender_specular_tint
+        # Note: blender_specular can be greater 1. The Blender documentation permits this.
+
+        return
+    else:
+        # Need to create a texture
+        # First, retrieve and create all images needed
+
+        # Base Color is already created
+        # Transmission is already created
+        # specularTexture is just created by original specular function
+        specular_image_name = None
+        try:
+            pytexture = mh.gltf.data.textures[tex_specular_info.index]
+            pyimg = mh.gltf.data.images[pytexture.source]
+            specular_image_name = pyimg.blender_image_name
+        except:
+            specular_image_name =  None
+
+
+        # specularColorTexture is just created by original specular function
+        specularcolor_image_name = None
+        try:
+            pytexture = mh.gltf.data.textures[tex_specular_color_info.index]
+            pyimg = mh.gltf.data.images[pytexture.source]
+            specularcolor_image_name = pyimg.blender_image_name
+        except:
+            specularcolor_image_name =  None
+
+        stack3 = lambda v: np.dstack([v]*3)
+
+        texts = {
+            base_color_image_name : 'basecolor',
+            transmission_image_name : 'transmission',
+            specularcolor_image_name : 'speccolor',
+            specular_image_name: 'spec'
+        }
+        images = [(name, bpy.data.images[name]) for name in [base_color_image_name, transmission_image_name, specularcolor_image_name, specular_image_name] if name is not None]
+        
+        width = max(image[1].size[0] for image in images)
+        height = max(image[1].size[1] for image in images)
+
+        buffers = {}
+        for name, image in images:
+            tmp_buf = np.empty(width * height * 4, np.float32)
+            
+            if image.size[0] == width and image.size[1] == height:
+                image.pixels.foreach_get(tmp_buf)
+            else:
+                # Image is the wrong size; make a temp copy and scale it.
+                with TmpImageGuard() as guard:
+                    make_temp_image_copy(guard, src_image=image)
+                    tmp_image = guard.image
+                    tmp_image.scale(width, height)
+                    tmp_image.pixels.foreach_get(tmp_buf)
+
+            buffers[texts[name]] = np.reshape(tmp_buf, [width, height, 4])
+            buffers[texts[name]] = buffers[texts[name]][:,:,:3]
+
+            # Manage factors
+            if name == transmission_image_name:
+                buffers[texts[name]] = stack3(buffers[texts[name]][:,:,0])  # Transmission : keep only R channel
+
+                buffers[texts[name]] *= stack3(transmission_factor)
+
+            elif name == base_color_image_name:
+                buffers[texts[name]] *= base_color
+
+            elif name == specularcolor_image_name:
+                buffers[texts[name]] *= specular_color_factor
+
+        # Create buffer if there is no image
+        if 'basecolor' not in buffers.keys():
+            buffers['basecolor'] = np.full((width, height, 3), base_color)
+        if 'transmission' not in buffers.keys():
+            buffers['transmission'] = np.full((width, height, 3), transmission_factor)
+        if 'speccolor' not in buffers.keys():
+            buffers['speccolor'] = np.full((width, height, 3), specular_color_factor)
+
+        # Calculation
+
+        luminance = lambda c: 0.3 * c[:,:,0] + 0.6 * c[:,:,1] + 0.1 * c[:,:,2]
+        def normalize(c):
+            l = luminance(c)
+            if np.all(l == 0.0):
+                return np.array(c)
+            return c / stack3(l)
+
+        f0_from_ior = ((ior - 1)/(ior + 1))**2
+        lum_specular_color = stack3(luminance(buffers['speccolor']))
+        blender_specular = ((lum_specular_color - buffers['transmission']) / (1 - buffers['transmission'])) * (1 / 0.08) * f0_from_ior
+        if not np.all(normalize(buffers['basecolor']) - 1 == 0.0):
+            blender_specular_tint = luminance((normalize(buffers['speccolor']) - 1) / (normalize(buffers['basecolor']) - 1))
+            np.nan_to_num(blender_specular_tint, copy=False)
+            blender_specular_tint = np.clip(blender_specular_tint, 0.0, 1.0)
+            blender_specular_tint = stack3(blender_specular_tint)
+        else:
+            blender_specular_tint = stack3(np.ones((width, height)))
+
+        blender_specular = np.dstack((blender_specular, np.ones((width, height)))) # Set alpha to 1
+        blender_specular_tint = np.dstack((blender_specular_tint, np.ones((width, height)))) # Set alpha to 1
+
+        # Check if we really need to create a texture
+        blender_specular_tex_not_needed = np.all(np.isclose(blender_specular, blender_specular[0][0]))
+        blender_specular_tint_tex_not_needed = np.all(np.isclose(blender_specular_tint, blender_specular_tint[0][0]))
+
+        if blender_specular_tex_not_needed == True:
+            lum = lambda c: 0.3 * c[0] + 0.6 * c[1] + 0.1 * c[2]
+            specular_socket.default_value = lum(blender_specular[0][0][:3])
+        else:
+            blender_specular = np.reshape(blender_specular, width * height * 4)
+            # Create images in Blender, width and height are dummy values, then set packed file data
+            blender_image_spec = bpy.data.images.new('Specular', width, height)
+            blender_image_spec.pixels.foreach_set(np.float32(blender_specular))
+            blender_image_spec.pack()
+
+            # Create Textures in Blender
+            tex_info = tex_specular_info
+            if tex_info is None:
+                tex_info = tex_specular_color_info
+            if tex_info is None:
+                tex_info = tex_transmission_info
+            if tex_info is None:
+                tex_info = tex_base_color
+
+            texture(
+                mh,
+                tex_info=tex_info,
+                label='SPECULAR',
+                location=(x_specular, y_specular),
+                is_data=True,
+                color_socket=specular_socket,
+                forced_image=blender_image_spec
+            )
+    
+        if blender_specular_tint_tex_not_needed == True:
+            lum = lambda c: 0.3 * c[0] + 0.6 * c[1] + 0.1 * c[2]
+            specular_tint_socket.default_value = lum(blender_specular_tint[0][0])
+        else:
+            blender_specular_tint = np.reshape(blender_specular_tint, width * height * 4)
+            # Create images in Blender, width and height are dummy values, then set packed file data
+            blender_image_tint = bpy.data.images.new('Specular Tint', width, height)
+            blender_image_tint.pixels.foreach_set(np.float32(blender_specular_tint))
+            blender_image_tint.pack()
+    
+            # Create Textures in Blender
+            tex_info = tex_specular_color_info
+            if tex_info is None:
+                tex_info = tex_specular_info
+            if tex_info is None:
+                tex_info = tex_transmission_info
+            if tex_info is None:
+                tex_info = tex_base_color
+
+            texture(
+                mh,
+                tex_info=tex_info,
+                label='SPECULAR TINT',
+                location=(x_tint, y_tint),
+                is_data=True,
+                color_socket=specular_tint_socket,
+                forced_image=blender_image_tint
+            )
+
+def original_specular(  mh,
+                        specular_factor,
+                        tex_specular_info, 
+                        specular_color_factor, 
+                        tex_specular_color_info,
+                        original_specular_socket,
+                        original_specularcolor_socket,
+                        location_original_specular,
+                        location_original_specularcolor
+                        ):
+
+    x_specular, y_specular = location_original_specular
+    x_specularcolor, y_specularcolor = location_original_specularcolor
+
+    if tex_specular_info is None:
+        original_specular_socket.default_value = specular_factor
+    else:
+        # Mix specular factor
+        if specular_factor != 1.0:
+            node = mh.node_tree.nodes.new('ShaderNodeMath')
+            node.label = 'Specular Factor'
+            node.location = x_specular - 140, y_specular
+            node.operation = 'MULTIPLY'
+            # Outputs
+            mh.node_tree.links.new(original_specular_socket, node.outputs[0])
+            # Inputs
+            original_specular_socket = node.inputs[0]
+            node.inputs[1].default_value = specular_factor
+            x_specular -= 200
+
+        texture(
+            mh,
+            tex_info=tex_specular_info,
+            label='SPECULAR',
+            location=(x_specular, y_specular),
+            is_data=True,
+            color_socket=None,
+            alpha_socket=original_specular_socket
+            )
+
+    if tex_specular_color_info is None:
+        specular_color_factor = list(specular_color_factor)
+        specular_color_factor.extend([1.0])
+        original_specularcolor_socket.default_value = specular_color_factor
+    else:
+            specular_color_factor = list(specular_color_factor) + [1.0]
+            if specular_color_factor != [1.0, 1.0, 1.0, 1.0]:
+                # Mix specularColorFactor
+                node = mh.node_tree.nodes.new('ShaderNodeMixRGB')
+                node.label = 'SpecularColor Factor'
+                node.location = x_specularcolor - 140, y_specularcolor
+                node.blend_type = 'MULTIPLY'
+                # Outputs
+                mh.node_tree.links.new(original_specularcolor_socket, node.outputs[0])
+                # Inputs
+                node.inputs['Fac'].default_value = 1.0
+                original_specularcolor_socket = node.inputs['Color1']
+                node.inputs['Color2'].default_value = specular_color_factor
+                x_specularcolor -= 200
+            
+            texture(
+                mh,
+                tex_info=tex_specular_color_info,
+                label='SPECULAR COLOR',
+                location=(x_specularcolor, y_specularcolor),
+                color_socket=original_specularcolor_socket,
+                )
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_transmission.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_transmission.py
new file mode 100644
index 0000000000000000000000000000000000000000..dab25d1466203089cc21bc5ff696433f8d61a06e
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_transmission.py
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+
+from ...io.com.gltf2_io import TextureInfo, MaterialNormalTextureInfoClass
+from .gltf2_blender_texture import texture
+
+
+# [Texture] => [Separate R] => [Transmission Factor] =>
+def transmission(mh, location, transmission_socket):
+    x, y = location
+    try:
+        ext = mh.pymat.extensions['KHR_materials_transmission']
+    except Exception:
+        return
+    transmission_factor = ext.get('transmissionFactor', 0)
+
+    # Default value is 0, so no transmission
+    if transmission_factor == 0:
+        return
+
+    tex_info = ext.get('transmissionTexture')
+    if tex_info is not None:
+        tex_info = TextureInfo.from_dict(tex_info)
+
+    if transmission_socket is None:
+        return
+
+    if tex_info is None:
+        transmission_socket.default_value = transmission_factor
+        return
+
+    # Mix transmission factor
+    if transmission_factor != 1:
+        node = mh.node_tree.nodes.new('ShaderNodeMath')
+        node.label = 'Transmission Factor'
+        node.location = x - 140, y
+        node.operation = 'MULTIPLY'
+        # Outputs
+        mh.node_tree.links.new(transmission_socket, node.outputs[0])
+        # Inputs
+        transmission_socket = node.inputs[0]
+        node.inputs[1].default_value = transmission_factor
+
+        x -= 200
+
+    # Separate RGB
+    node = mh.node_tree.nodes.new('ShaderNodeSeparateColor')
+    node.location = x - 150, y - 75
+    # Outputs
+    mh.node_tree.links.new(transmission_socket, node.outputs['Red'])
+    # Inputs
+    transmission_socket = node.inputs[0]
+
+    x -= 200
+
+    texture(
+        mh,
+        tex_info=tex_info,
+        label='TRANSMISSION',
+        location=(x, y),
+        is_data=True,
+        color_socket=transmission_socket,
+    )
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_unlit.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_unlit.py
index 48ad46fddc64564911b92b7f160b6894dc0f42ef..1ffdc7e419775ccd903648c079f0b48607250229 100644
--- a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_unlit.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_unlit.py
@@ -24,12 +24,15 @@ def unlit(mh):
     mh.node_tree.links.new(mix_node.inputs[1], transparent_node.outputs[0])
     mh.node_tree.links.new(mix_node.inputs[2], emission_node.outputs[0])
 
-    _emission_socket, alpha_socket = make_output_nodes(
+    _emission_socket, alpha_socket, _, _ = make_output_nodes(
         mh,
         location=(420, 280) if mh.is_opaque() else (150, 130),
+        additional_location=None, #No additional location needed for Unlit
         shader_socket=mix_node.outputs[0],
         make_emission_socket=False,
         make_alpha_socket=not mh.is_opaque(),
+        make_volume_socket=None, # Not possible to have KHR_materials_volume with unlit
+        make_velvet_socket=None #Not possible to have KHR_materials_sheen with unlit
     )
 
     base_color(
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_volume.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_volume.py
new file mode 100644
index 0000000000000000000000000000000000000000..f909c7f63a345edd08a10dfeb6b0f667e1c52ff7
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_volume.py
@@ -0,0 +1,82 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2021 The glTF-Blender-IO authors.
+
+from ...io.com.gltf2_io import TextureInfo, MaterialNormalTextureInfoClass
+from .gltf2_blender_texture import texture
+
+def volume(mh, location, volume_socket, thickness_socket):
+    # implementation based on https://github.com/KhronosGroup/glTF-Blender-IO/issues/1454#issuecomment-928319444
+    try:
+        ext = mh.pymat.extensions['KHR_materials_volume']
+    except Exception:
+        return
+
+    # Attenuation Color
+    attenuationColor = \
+            mh.pymat.extensions['KHR_materials_volume'] \
+            .get('attenuationColor')
+    # glTF is color3, Blender adds alpha
+    if attenuationColor is None:
+        attenuationColor = [1.0, 1.0, 1.0, 1.0]
+    else:
+        attenuationColor.extend([1.0])
+    volume_socket.node.inputs[0].default_value = attenuationColor
+
+    # Attenuation Distance / Density
+    attenuationDistance = mh.pymat.extensions['KHR_materials_volume'].get('attenuationDistance')
+    if attenuationDistance is None:
+        density = 0
+    else:
+        density = 1.0 / attenuationDistance
+    volume_socket.node.inputs[1].default_value = density
+
+    # thicknessFactor / thicknessTexture
+    x, y = location
+    try:
+        ext = mh.pymat.extensions['KHR_materials_volume']
+    except Exception:
+        return
+    thickness_factor = ext.get('thicknessFactor', 0)
+    tex_info = ext.get('thicknessTexture')
+    if tex_info is not None:
+        tex_info = TextureInfo.from_dict(tex_info)
+
+    if thickness_socket is None:
+        return
+
+    if tex_info is None:
+        thickness_socket.default_value = thickness_factor
+        return
+
+    # Mix thickness factor
+    if thickness_factor != 1:
+        node = mh.node_tree.nodes.new('ShaderNodeMath')
+        node.label = 'Thickness Factor'
+        node.location = x - 140, y
+        node.operation = 'MULTIPLY'
+        # Outputs
+        mh.node_tree.links.new(thickness_socket, node.outputs[0])
+        # Inputs
+        thickness_socket = node.inputs[0]
+        node.inputs[1].default_value = thickness_factor
+
+        x -= 200
+
+    # Separate RGB
+    node = mh.node_tree.nodes.new('ShaderNodeSeparateColor')
+    node.location = x - 150, y - 75
+    # Outputs
+    mh.node_tree.links.new(thickness_socket, node.outputs['Green'])
+    # Inputs
+    thickness_socket = node.inputs[0]
+
+    x -= 200
+
+    texture(
+        mh,
+        tex_info=tex_info,
+        label='THICKNESS',
+        location=(x, y),
+        is_data=True,
+        color_socket=thickness_socket,
+    )
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py
index f25564653d7653b53d42bcab1fd42221252cb8da..33713b9748787b155b78277ef5e7eb89335b4255 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py
@@ -4,6 +4,8 @@
 import bpy
 from mathutils import Vector, Quaternion, Matrix
 from .gltf2_blender_scene import BlenderScene
+from ..com.gltf2_blender_ui import gltf2_KHR_materials_variants_variant, gltf2_KHR_materials_variants_primitive, gltf2_KHR_materials_variants_default_material
+from .gltf2_blender_material import BlenderMaterial
 
 
 class BlenderGlTF():
@@ -180,6 +182,10 @@ class BlenderGlTF():
 
                     mesh.shapekey_names.append(shapekey_name)
 
+        # Manage KHR_materials_variants
+        BlenderGlTF.manage_material_variants(gltf)
+
+
     @staticmethod
     def find_unused_name(haystack, desired_name):
         """Finds a name not in haystack and <= 63 UTF-8 bytes.
@@ -201,3 +207,25 @@ class BlenderGlTF():
 
             suffix = '.%03d' % cntr
             cntr += 1
+
+
+    @staticmethod
+    def manage_material_variants(gltf):
+        if not (gltf.data.extensions is not None and 'KHR_materials_variants' in gltf.data.extensions.keys()):
+            gltf.KHR_materials_variants = False
+            return
+
+        gltf.KHR_materials_variants = True
+        # If there is no KHR_materials_variants data in scene, create it
+        if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is False:
+            bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui = True
+            # Setting preferences as dirty, to be sure that option is saved
+            bpy.context.preferences.is_dirty = True
+
+        if len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0:
+            bpy.data.scenes[0].gltf2_KHR_materials_variants_variants.clear()
+
+        for idx_variant, variant in enumerate(gltf.data.extensions['KHR_materials_variants']['variants']):
+            var = bpy.data.scenes[0].gltf2_KHR_materials_variants_variants.add()
+            var.name = variant['name']
+            var.variant_idx = idx_variant
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_image.py b/io_scene_gltf2/blender/imp/gltf2_blender_image.py
index 4f9af79915cb435af70f00723e27ae988d0a47e2..abce03546148edcf6867ce3cef39934400b996c6 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_image.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_image.py
@@ -83,7 +83,7 @@ def create_from_data(gltf, img_idx):
     img_data = BinaryData.get_image_data(gltf, img_idx)
     if img_data is None:
         return
-    img_name = 'Image_%d' % img_idx
+    img_name = gltf.data.images[img_idx].name or 'Image_%d' % img_idx
 
     # Create image, width and height are dummy values
     blender_image = bpy.data.images.new(img_name, 8, 8)
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_material.py b/io_scene_gltf2/blender/imp/gltf2_blender_material.py
index 1d18c65d0f1031746ff9a6c30498fe00dc2a33c6..9a582f7e6e9d92c61198f86a683da15d7d508d8b 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_material.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_material.py
@@ -48,6 +48,11 @@ class BlenderMaterial():
         else:
             pbr_metallic_roughness(mh)
 
+        # Manage KHR_materials_variants
+        # We need to store link between material idx in glTF and Blender Material id
+        if gltf.KHR_materials_variants is True:
+            gltf.variant_mapping[str(material_idx) + str(vertex_color)] = mat
+
         import_user_extensions('gather_import_material_after_hook', gltf, pymaterial, vertex_color, mat)
 
     @staticmethod
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
index a3c1bd349b1e15b82c6d6fa65a51b9d306a43b54..b886dd2589695b8a6385ad6b952bf61d0f1e741d 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
@@ -11,6 +11,7 @@ from .gltf2_blender_material import BlenderMaterial
 from ...io.com.gltf2_io_debug import print_console
 from .gltf2_io_draco_compression_extension import decode_primitive
 from io_scene_gltf2.io.imp.gltf2_io_user_extensions import import_user_extensions
+from ..com.gltf2_blender_ui import gltf2_KHR_materials_variants_primitive, gltf2_KHR_materials_variants_variant, gltf2_KHR_materials_variants_default_material
 
 
 class BlenderMesh():
@@ -343,12 +344,19 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
     # ----
     # Assign materials to faces
     has_materials = any(prim.material is not None for prim in pymesh.primitives)
+    # Even if no primitive have material, we need to create slots if some primitives have some variant
+    if has_materials is False:
+        has_materials = any(prim.extensions is not None and 'KHR_materials_variants' in prim.extensions.keys() for prim in pymesh.primitives)
+
+    has_variant = prim.extensions is not None and 'KHR_materials_variants' in prim.extensions.keys() \
+                and 'mappings' in prim.extensions['KHR_materials_variants'].keys()
+
     if has_materials:
         material_indices = np.empty(num_faces, dtype=np.uint32)
         empty_material_slot_index = None
         f = 0
 
-        for prim in pymesh.primitives:
+        for idx_prim, prim in enumerate(pymesh.primitives):
             if prim.material is not None:
                 # Get the material
                 pymaterial = gltf.data.materials[prim.material]
@@ -358,19 +366,55 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
                 material_name = pymaterial.blender_material[vertex_color]
 
                 # Put material in slot (if not there)
-                if material_name not in mesh.materials:
+                if not has_variant:
+                    if material_name not in mesh.materials:
+                        mesh.materials.append(bpy.data.materials[material_name])
+                    material_index = mesh.materials.find(material_name)
+                else:
+                    # In case of variant, do not merge slots
                     mesh.materials.append(bpy.data.materials[material_name])
-                material_index = mesh.materials.find(material_name)
+                    material_index = len(mesh.materials) - 1
             else:
-                if empty_material_slot_index is None:
+                if not has_variant:
+                    if empty_material_slot_index is None:
+                        mesh.materials.append(None)
+                        empty_material_slot_index = len(mesh.materials) - 1
+                    material_index = empty_material_slot_index
+                else:
+                    # In case of variant, do not merge slots
                     mesh.materials.append(None)
-                    empty_material_slot_index = len(mesh.materials) - 1
-                material_index = empty_material_slot_index
+                    material_index = len(mesh.materials) - 1
 
             material_indices[f:f + prim.num_faces].fill(material_index)
 
             f += prim.num_faces
 
+            # Manage variants
+            if has_variant:
+
+                # Store default material
+                default_mat = mesh.gltf2_variant_default_materials.add()
+                default_mat.material_slot_index = material_index
+                default_mat.default_material = bpy.data.materials[material_name] if prim.material is not None else None
+
+                for mapping in prim.extensions['KHR_materials_variants']['mappings']:
+                    # Store, for each variant, the material link to this primitive
+                    
+                    variant_primitive = mesh.gltf2_variant_mesh_data.add()
+                    variant_primitive.material_slot_index = material_index
+                    if 'material' not in mapping.keys():
+                        # Default material
+                        variant_primitive.material = None
+                    else:
+                        vertex_color = 'COLOR_0' if 'COLOR_0' in prim.attributes else None
+                        if str(mapping['material']) + str(vertex_color) not in gltf.variant_mapping.keys():
+                            BlenderMaterial.create(gltf, mapping['material'], vertex_color)
+                        variant_primitive.material = gltf.variant_mapping[str(mapping['material']) + str(vertex_color)]
+
+                    for variant in mapping['variants']:
+                        vari = variant_primitive.variants.add()
+                        vari.variant.variant_idx = variant
+
         mesh.polygons.foreach_set('material_index', material_indices)
 
     # ----
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py b/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py
index 4c32c35d4679b92535a212ae068a8c6323e639cc..b6b8e19f268880aeadb88ab0f66931eb16e57432 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py
@@ -1,13 +1,19 @@
 # SPDX-License-Identifier: Apache-2.0
 # Copyright 2018-2021 The glTF-Blender-IO authors.
 
+from re import M
 import bpy
 from ...io.com.gltf2_io import TextureInfo, MaterialPBRMetallicRoughness
 from ..com.gltf2_blender_material_helpers import get_gltf_node_name, create_settings_group
 from .gltf2_blender_texture import texture
 from .gltf2_blender_KHR_materials_clearcoat import \
     clearcoat, clearcoat_roughness, clearcoat_normal
-
+from .gltf2_blender_KHR_materials_transmission import transmission
+from .gltf2_blender_KHR_materials_ior import ior
+from .gltf2_blender_KHR_materials_volume import volume
+from .gltf2_blender_KHR_materials_specular import specular
+from .gltf2_blender_KHR_materials_sheen import sheen
+from ...io.com.gltf2_io_constants import GLTF_IOR
 
 class MaterialHelper:
     """Helper class. Stores material stuff to be passed around everywhere."""
@@ -20,6 +26,7 @@ class MaterialHelper:
         if pymat.pbr_metallic_roughness is None:
             pymat.pbr_metallic_roughness = \
                 MaterialPBRMetallicRoughness.from_dict({})
+        self.settings_node = None
 
     def is_opaque(self):
         alpha_mode = self.pymat.alpha_mode
@@ -36,15 +43,52 @@ def pbr_metallic_roughness(mh: MaterialHelper):
     """Creates node tree for pbrMetallicRoughness materials."""
     pbr_node = mh.node_tree.nodes.new('ShaderNodeBsdfPrincipled')
     pbr_node.location = 10, 300
-
-    make_output_nodes(
+    additional_location = 40, -370 # For occlusion and/or volume / original PBR extensions
+
+    # Set IOR to 1.5, this is the default in glTF
+    # This value may be overridden later if IOR extension is set on file
+    pbr_node.inputs['IOR'].default_value = GLTF_IOR
+
+    if mh.pymat.occlusion_texture is not None or (mh.pymat.extensions and 'KHR_materials_specular' in mh.pymat.extensions):
+        if mh.settings_node is None:
+            mh.settings_node = make_settings_node(mh)
+            mh.settings_node.location = additional_location
+            mh.settings_node.width = 180
+            additional_location = additional_location[0], additional_location[1] - 150
+
+    need_volume_node = False
+    if mh.pymat.extensions and 'KHR_materials_volume' in mh.pymat.extensions:
+        if 'thicknessFactor' in mh.pymat.extensions['KHR_materials_volume'] \
+            and mh.pymat.extensions['KHR_materials_volume']['thicknessFactor'] != 0.0:
+
+            need_volume_node = True
+
+            # We also need glTF Material Output Node, to set thicknessFactor and thicknessTexture
+            mh.settings_node = make_settings_node(mh)
+            mh.settings_node.location = additional_location
+            mh.settings_node.width = 180
+            volume_location = additional_location
+            additional_location = additional_location[0], additional_location[1] - 150
+
+    need_velvet_node = False
+    if mh.pymat.extensions and 'KHR_materials_sheen' in mh.pymat.extensions:
+        need_velvet_node = True
+
+    _, _, volume_socket, velvet_node = make_output_nodes(
         mh,
         location=(250, 260),
+        additional_location=additional_location,
         shader_socket=pbr_node.outputs[0],
-        make_emission_socket=False,
-        make_alpha_socket=False,
+        make_emission_socket=False, # is managed by Principled shader node
+        make_alpha_socket=False, # is managed by Principled shader node
+        make_volume_socket=need_volume_node,
+        make_velvet_socket=need_velvet_node
     )
 
+
+    if mh.pymat.extensions and 'KHR_materials_sheen':
+        pass #TOTOEXT     
+
     locs = calc_locations(mh)
 
     emission(
@@ -75,13 +119,10 @@ def pbr_metallic_roughness(mh: MaterialHelper):
     )
 
     if mh.pymat.occlusion_texture is not None:
-        node = make_settings_node(mh)
-        node.location = 40, -370
-        node.width = 180
         occlusion(
             mh,
             location=locs['occlusion'],
-            occlusion_socket=node.inputs['Occlusion'],
+            occlusion_socket=mh.settings_node.inputs['Occlusion'],
         )
 
     clearcoat(
@@ -102,6 +143,46 @@ def pbr_metallic_roughness(mh: MaterialHelper):
         normal_socket=pbr_node.inputs['Clearcoat Normal'],
     )
 
+    transmission(
+        mh,
+        location=locs['transmission'],
+        transmission_socket=pbr_node.inputs['Transmission']
+    )
+
+    if need_volume_node:
+        volume(
+            mh,
+            location=locs['volume_thickness'],
+            volume_socket=volume_socket,
+            thickness_socket=mh.settings_node.inputs[1] if mh.settings_node else None
+        )
+
+    specular(
+        mh,
+        location_specular=locs['specularTexture'],
+        location_specular_tint=locs['specularColorTexture'],
+        specular_socket=pbr_node.inputs['Specular'],
+        specular_tint_socket=pbr_node.inputs['Specular Tint'],
+        original_specular_socket=mh.settings_node.inputs[2] if mh.settings_node else None,
+        original_specularcolor_socket=mh.settings_node.inputs[3] if mh.settings_node else None,
+        location_original_specular=locs['original_specularTexture'],
+        location_original_specularcolor=locs['original_specularColorTexture']
+    )
+
+    if need_velvet_node:
+        sheen(
+            mh,
+            location_sheenColor=locs['sheenColorTexture'],
+            location_sheenRoughness=locs['sheenRoughnessTexture'],
+            sheenColor_socket=velvet_node.inputs[0],
+            sheenRoughness_socket=velvet_node.inputs[1]
+        )
+
+    ior(
+        mh,
+        ior_socket=pbr_node.inputs['IOR']
+    )
+
 
 def calc_locations(mh):
     """Calculate locations to place each bit of the node graph at."""
@@ -116,18 +197,53 @@ def calc_locations(mh):
     except Exception:
         clearcoat_ext = {}
 
+    try:
+        transmission_ext = mh.pymat.exntesions['KHR_materials_transmission']
+    except:
+        transmission_ext = {}
+
+    try:
+        volume_ext = mh.pymat.extensions['KHR_materials_volume']
+    except Exception:
+        volume_ext = {}
+
+    try:
+        specular_ext = mh.pymat.extensions['KHR_materials_specular']
+    except:
+        specular_ext = {}
+
+    try:
+        sheen_ext = mh.pymat.extensions['KHR_materials_sheen']
+    except:
+        sheen_ext = {}
+
+    locs['sheenColorTexture'] = (x, y)
+    if 'sheenColorTexture' in sheen_ext:
+        y -= height
+    locs['sheenRoughnessTexture'] = (x, y)
+    if 'sheenRoughnessTexture' in sheen_ext:
+        y -= height
     locs['base_color'] = (x, y)
     if mh.pymat.pbr_metallic_roughness.base_color_texture is not None or mh.vertex_color:
         y -= height
     locs['metallic_roughness'] = (x, y)
     if mh.pymat.pbr_metallic_roughness.metallic_roughness_texture is not None:
         y -= height
+    locs['specularTexture'] = (x, y)
+    if 'specularTexture' in specular_ext:
+        y -= height
+    locs['specularColorTexture'] = (x, y)
+    if 'specularColorTexture' in specular_ext:
+        y -= height
     locs['clearcoat'] = (x, y)
     if 'clearcoatTexture' in clearcoat_ext:
         y -= height
     locs['clearcoat_roughness'] = (x, y)
     if 'clearcoatRoughnessTexture' in clearcoat_ext:
         y -= height
+    locs['transmission'] = (x, y)
+    if 'transmissionTexture' in transmission_ext:
+        y -= height
     locs['emission'] = (x, y)
     if mh.pymat.emissive_texture is not None:
         y -= height
@@ -140,6 +256,22 @@ def calc_locations(mh):
     locs['occlusion'] = (x, y)
     if mh.pymat.occlusion_texture is not None:
         y -= height
+    locs['volume_thickness'] = (x, y)
+    if 'thicknessTexture' in volume_ext:
+        y -= height
+    locs['original_specularTexture'] = (x, y)
+    if 'specularTexture' in specular_ext:
+        y -= height
+    locs['original_specularColorTexture'] = (x, y)
+    if 'specularColorTexture' in specular_ext:
+        y -= height
+    locs['original_sheenColorTexture'] = (x, y)
+    if 'sheenColorTexture' in sheen_ext:
+        y -= height
+    locs['original_sheenRoughnessTexture'] = (x, y)
+    if 'sheenRoughnessTexture' in sheen_ext:
+        y -= height
+
 
     # Center things
     total_height = -y
@@ -157,21 +289,29 @@ def calc_locations(mh):
 
 
 # [Texture] => [Emissive Factor] =>
-def emission(mh: MaterialHelper, location, color_socket, strength_socket=None):
+def emission(mh: MaterialHelper, location, color_socket, strength_socket):
     x, y = location
     emissive_factor = mh.pymat.emissive_factor or [0, 0, 0]
 
+    strength = 1
+    try:
+        # Get strength from KHR_materials_emissive_strength if exists
+        strength = mh.pymat.extensions['KHR_materials_emissive_strength']['emissiveStrength']
+    except Exception:
+        pass
+
     if color_socket is None:
         return
 
     if mh.pymat.emissive_texture is None:
         color_socket.default_value = emissive_factor + [1]
+        strength_socket.default_value = strength
         return
 
     # Put grayscale emissive factors into the Emission Strength
     e0, e1, e2 = emissive_factor
     if strength_socket and e0 == e1 == e2:
-        strength_socket.default_value = e0
+        strength_socket.default_value = e0 * strength
 
     # Otherwise, use a multiply node for it
     else:
@@ -189,6 +329,8 @@ def emission(mh: MaterialHelper, location, color_socket, strength_socket=None):
 
             x -= 200
 
+        strength_socket.default_value = strength
+
     texture(
         mh,
         tex_info=mh.pymat.emissive_texture,
@@ -466,17 +608,22 @@ def occlusion(mh: MaterialHelper, location, occlusion_socket):
     )
 
 
-# => [Add Emission] => [Mix Alpha] => [Material Output]
+# => [Add Emission] => [Mix Alpha] => [Material Output] if needed, only for SpecGlossiness
+# => [Volume] => [Add Shader] => [Material Output] if needed
+# => [Velvet] => [Add Shader] => [Material Output] if needed
 def make_output_nodes(
     mh: MaterialHelper,
     location,
+    additional_location,
     shader_socket,
     make_emission_socket,
     make_alpha_socket,
+    make_volume_socket,
+    make_velvet_socket, # For sheen
 ):
     """
     Creates the Material Output node and connects shader_socket to it.
-    If requested, it can also create places to hookup the emission/alpha
+    If requested, it can also create places to hookup the emission/alpha.sheen
     in between shader_socket and the Output node too.
 
     :return: a pair containing the sockets you should put emission and alpha
@@ -484,6 +631,7 @@ def make_output_nodes(
     """
     x, y = location
     emission_socket = None
+    velvet_node = None
     alpha_socket = None
 
     # Create an Emission node and add it to the shader.
@@ -512,6 +660,31 @@ def make_output_nodes(
             x += 380
             y += 125
 
+    # Create an Velvet node add add it to the shader
+    # Note that you can not have Emission & Velvet at the same time
+    if make_velvet_socket:
+        # Velvet
+        node = mh.node_tree.nodes.new("ShaderNodeBsdfVelvet")
+        node.location = x + 50, y + 250
+        # Node
+        velvet_node = node
+        # Outputs
+        velvet_output = node.outputs[0]
+
+        # Add
+        node = mh.node_tree.nodes.new('ShaderNodeAddShader')
+        node.location = x + 250, y + 160
+        # Inputs
+        mh.node_tree.links.new(node.inputs[0], velvet_output)
+        mh.node_tree.links.new(node.inputs[1], shader_socket)
+        # Outputs
+        shader_socket = node.outputs[0]
+
+
+        x += 380
+        y += 125
+
+
     # Mix with a Transparent BSDF. Mixing factor is the alpha value.
     if make_alpha_socket:
         # Transparent BSDF
@@ -535,12 +708,23 @@ def make_output_nodes(
         y -= 210
 
     # Material output
-    node = mh.node_tree.nodes.new('ShaderNodeOutputMaterial')
-    node.location = x + 70, y + 10
+    node_output = mh.node_tree.nodes.new('ShaderNodeOutputMaterial')
+    node_output.location = x + 70, y + 10
+
     # Outputs
-    mh.node_tree.links.new(node.inputs[0], shader_socket)
+    mh.node_tree.links.new(node_output.inputs[0], shader_socket)
+
+    # Volume Node
+    volume_socket = None
+    if make_volume_socket:
+        node = mh.node_tree.nodes.new('ShaderNodeVolumeAbsorption')
+        node.location = additional_location
+        # Outputs
+        mh.node_tree.links.new(node_output.inputs[1], node.outputs[0])
+        volume_socket = node.outputs[0]
+
 
-    return emission_socket, alpha_socket
+    return emission_socket, alpha_socket, volume_socket, velvet_node
 
 
 def make_settings_node(mh):
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_texture.py b/io_scene_gltf2/blender/imp/gltf2_blender_texture.py
index 24c9df7c55f92366917c4e5fbbe941995e8685f2..12e6d5949b927dcd2cebc5bf38fbfefba6ba7e05 100644
--- a/io_scene_gltf2/blender/imp/gltf2_blender_texture.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_texture.py
@@ -17,6 +17,7 @@ def texture(
     color_socket,
     alpha_socket=None,
     is_data=False,
+    forced_image=None
 ):
     """Creates nodes for a TextureInfo and hooks up the color/alpha outputs."""
     x, y = location
@@ -36,12 +37,15 @@ def texture(
     tex_img.location = x - 240, y
     tex_img.label = label
     # Get image
-    if pytexture.source is not None:
-        BlenderImage.create(mh.gltf, pytexture.source)
-        pyimg = mh.gltf.data.images[pytexture.source]
-        blender_image_name = pyimg.blender_image_name
-        if blender_image_name:
-            tex_img.image = bpy.data.images[blender_image_name]
+    if forced_image is None:
+        if pytexture.source is not None:
+            BlenderImage.create(mh.gltf, pytexture.source)
+            pyimg = mh.gltf.data.images[pytexture.source]
+            blender_image_name = pyimg.blender_image_name
+            if blender_image_name:
+                tex_img.image = bpy.data.images[blender_image_name]
+    else:
+        tex_img.image = forced_image
     # Set colorspace for data images
     if is_data:
         if tex_img.image:
@@ -49,7 +53,8 @@ def texture(
     # Set filtering
     set_filtering(tex_img, pysampler)
     # Outputs
-    mh.node_tree.links.new(color_socket, tex_img.outputs['Color'])
+    if color_socket is not None:
+        mh.node_tree.links.new(color_socket, tex_img.outputs['Color'])
     if alpha_socket is not None:
         mh.node_tree.links.new(alpha_socket, tex_img.outputs['Alpha'])
     # Inputs
diff --git a/io_scene_gltf2/io/com/gltf2_io_constants.py b/io_scene_gltf2/io/com/gltf2_io_constants.py
index 19ead516ea028f8b7c8475519b0a009b68ef7f2a..816220d953c02fe14d16685966a6e72eb34e62c6 100755
--- a/io_scene_gltf2/io/com/gltf2_io_constants.py
+++ b/io_scene_gltf2/io/com/gltf2_io_constants.py
@@ -118,6 +118,9 @@ class TextureWrap(IntEnum):
     MirroredRepeat = 33648
     Repeat = 10497
 
+class BufferViewTarget(IntEnum):
+    ARRAY_BUFFER = 34962
+    ELEMENT_ARRAY_BUFFER = 34963
 
 #################
 # LEGACY DEFINES
@@ -145,3 +148,5 @@ GLTF_DATA_TYPE_VEC4 = "VEC4"
 GLTF_DATA_TYPE_MAT2 = "MAT2"
 GLTF_DATA_TYPE_MAT3 = "MAT3"
 GLTF_DATA_TYPE_MAT4 = "MAT4"
+
+GLTF_IOR = 1.5
\ No newline at end of file
diff --git a/io_scene_gltf2/io/com/gltf2_io_draco_compression_extension.py b/io_scene_gltf2/io/com/gltf2_io_draco_compression_extension.py
index 28f06e5106e791329528dd006b330c63674e5a35..b9f9ccecf5808f1ddf9f9ea9bad98a252fdc44fb 100644
--- a/io_scene_gltf2/io/com/gltf2_io_draco_compression_extension.py
+++ b/io_scene_gltf2/io/com/gltf2_io_draco_compression_extension.py
@@ -27,7 +27,7 @@ def dll_path() -> Path:
             'darwin': blender_root.parent / 'Resources' / python_lib / python_version / 'site-packages'
         }.get(sys.platform)
     else:
-        path = Path(path)
+        return Path(path)
 
     library_name = {
         'win32': '{}.dll'.format(lib_name),
@@ -46,12 +46,11 @@ def dll_exists(quiet=False) -> bool:
     Checks whether the DLL path exists.
     :return: True if the DLL exists.
     """
-    exists = dll_path().exists()
+    path = dll_path()
+    exists = path.exists() and path.is_file()
     if quiet is False:
-        print("'{}' ".format(dll_path().absolute()) + ("exists, draco mesh compression is available" if exists else
-                                                       "{} {} {}".format(
-                                                           "does not exist, draco mesh compression not available,",
-                                                           "please add it or create environment variable BLENDER_EXTERN_DRACO_LIBRARY_PATH",
-                                                           "pointing to the folder"
-                                                      )))
+        if exists:
+            print_console('INFO', 'Draco mesh compression is available, use library at %s' % dll_path().absolute())
+        else:
+            print_console('ERROR', 'Draco mesh compression is not available because library could not be found at %s' % dll_path().absolute())
     return exists
diff --git a/io_scene_gltf2/io/com/gltf2_io_variants.py b/io_scene_gltf2/io/com/gltf2_io_variants.py
new file mode 100644
index 0000000000000000000000000000000000000000..3824fee4095f129dc699dafbbffcb1f786760c2d
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_variants.py
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+from io_scene_gltf2.io.com.gltf2_io import from_dict, from_union, from_none, from_float, from_str, from_list
+from io_scene_gltf2.io.com.gltf2_io import to_float, to_class
+
+class Variant:
+    """defines variant for use with glTF 2.0."""
+    def __init__(self, name, extensions, extras):
+        self.name = name
+        self.extensions = extensions
+        self.extras = extras
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        name = from_union([from_str, from_none], obj.get("name"))
+        extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+                                obj.get("extensions"))
+        extras = obj.get("extras")
+        return Variant(name, extensions, extras)
+
+    def to_dict(self):
+        result = {}
+        result["name"] = from_union([from_str, from_none], self.name)
+        result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+                                          self.extensions)
+        result["extras"] = self.extras
+        return result
diff --git a/io_scene_gltf2/io/exp/gltf2_io_binary_data.py b/io_scene_gltf2/io/exp/gltf2_io_binary_data.py
index 104055519d87f357adadb108e3d85db8aa43c4f0..6a617628475843af5ec41a458e109fa3bda11d49 100755
--- a/io_scene_gltf2/io/exp/gltf2_io_binary_data.py
+++ b/io_scene_gltf2/io/exp/gltf2_io_binary_data.py
@@ -9,10 +9,11 @@ from io_scene_gltf2.io.com import gltf2_io_constants
 class BinaryData:
     """Store for gltf binary data that can later be stored in a buffer."""
 
-    def __init__(self, data: bytes):
+    def __init__(self, data: bytes, bufferViewTarget=None):
         if not isinstance(data, bytes):
             raise TypeError("Data is not a bytes array")
         self.data = data
+        self.bufferViewTarget = bufferViewTarget
 
     def __eq__(self, other):
         return self.data == other.data
@@ -21,9 +22,9 @@ class BinaryData:
         return hash(self.data)
 
     @classmethod
-    def from_list(cls, lst: typing.List[typing.Any], gltf_component_type: gltf2_io_constants.ComponentType):
+    def from_list(cls, lst: typing.List[typing.Any], gltf_component_type: gltf2_io_constants.ComponentType, bufferViewTarget=None):
         format_char = gltf2_io_constants.ComponentType.to_type_code(gltf_component_type)
-        return BinaryData(array.array(format_char, lst).tobytes())
+        return BinaryData(array.array(format_char, lst).tobytes(), bufferViewTarget)
 
     @property
     def byte_length(self):
diff --git a/io_scene_gltf2/io/exp/gltf2_io_buffer.py b/io_scene_gltf2/io/exp/gltf2_io_buffer.py
index 5fae3834abf9232a20f87c7c21de7eb5afe8cdb5..4b70e789565e2245d8fbdf28b15f3f5fa01f9c08 100755
--- a/io_scene_gltf2/io/exp/gltf2_io_buffer.py
+++ b/io_scene_gltf2/io/exp/gltf2_io_buffer.py
@@ -35,7 +35,7 @@ class Buffer:
             extensions=None,
             extras=None,
             name=None,
-            target=None
+            target=binary_data.bufferViewTarget
         )
         return buffer_view
 
diff --git a/io_scene_gltf2/io/imp/gltf2_io_gltf.py b/io_scene_gltf2/io/imp/gltf2_io_gltf.py
index 9f096e691eb6ec555f7e2bd89a459c28fa14678d..75ce7265bb16e27cb8acae9fc07fbcfecb36f4eb 100755
--- a/io_scene_gltf2/io/imp/gltf2_io_gltf.py
+++ b/io_scene_gltf2/io/imp/gltf2_io_gltf.py
@@ -28,6 +28,7 @@ class glTFImporter():
         self.accessor_cache = {}
         self.decode_accessor_cache = {}
         self.import_user_extensions = import_settings['import_user_extensions']
+        self.variant_mapping = {} # Used to map between mgltf material idx and blender material, for Variants
 
         if 'loglevel' not in self.import_settings.keys():
             self.import_settings['loglevel'] = logging.ERROR
@@ -44,7 +45,13 @@ class glTFImporter():
             'KHR_texture_transform',
             'KHR_materials_clearcoat',
             'KHR_mesh_quantization',
-            'KHR_draco_mesh_compression'
+            'KHR_draco_mesh_compression',
+            'KHR_materials_variants',
+            'KHR_materials_emissive_strength',
+            'KHR_materials_transmission',
+            'KHR_materials_specular',
+            'KHR_materials_sheen',
+            'KHR_materials_ior'
         ]
 
         # Add extensions required supported by custom import extensions
diff --git a/io_scene_obj/__init__.py b/io_scene_obj/__init__.py
index 0ec62410dc8aed9c840685aa89736eb62f14a2ac..d49f83674fafa6e73c9af9ed4fb8bc16e6b9800e 100644
--- a/io_scene_obj/__init__.py
+++ b/io_scene_obj/__init__.py
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 bl_info = {
-    "name": "Wavefront OBJ format",
+    "name": "Wavefront OBJ format (legacy)",
     "author": "Campbell Barton, Bastien Montagne",
     "version": (3, 9, 0),
     "blender": (3, 0, 0),
@@ -459,11 +459,11 @@ class OBJ_PT_export_geometry(bpy.types.Panel):
 
 
 def menu_func_import(self, context):
-    self.layout.operator(ImportOBJ.bl_idname, text="Wavefront (.obj)")
+    self.layout.operator(ImportOBJ.bl_idname, text="Wavefront (.obj) (legacy)")
 
 
 def menu_func_export(self, context):
-    self.layout.operator(ExportOBJ.bl_idname, text="Wavefront (.obj)")
+    self.layout.operator(ExportOBJ.bl_idname, text="Wavefront (.obj) (legacy)")
 
 
 classes = (
diff --git a/io_scene_obj/import_obj.py b/io_scene_obj/import_obj.py
index d41e72f8767c5a3049fb5217eba86c9a20388c23..63c4d0e13100b47828f3a7a5a07cd63ed51b142d 100644
--- a/io_scene_obj/import_obj.py
+++ b/io_scene_obj/import_obj.py
@@ -584,7 +584,7 @@ def create_mesh(new_objects,
         len_face_vert_loc_indices = len(face_vert_loc_indices)
 
         if len_face_vert_loc_indices == 1:
-            faces.pop(f_idx)  # cant add single vert faces
+            faces.pop(f_idx)  # can't add single vert faces
 
         # Face with a single item in face_vert_nor_indices is actually a polyline!
         elif face_is_edge(face):
@@ -979,7 +979,7 @@ def load(context,
 
         # when there are faces that end with \
         # it means they are multiline-
-        # since we use xreadline we cant skip to the next line
+        # since we use xreadline we can't skip to the next line
         # so we need to know whether
         context_multi_line = b''
 
diff --git a/io_scene_x3d/import_x3d.py b/io_scene_x3d/import_x3d.py
index 6d13bb7928e512adac1a928409949042cbddd787..228e050705801ecf21a23de65d6b12738a137c19 100644
--- a/io_scene_x3d/import_x3d.py
+++ b/io_scene_x3d/import_x3d.py
@@ -1003,13 +1003,13 @@ class vrmlNode(object):
                     print('\tWarning: Inline URL could not be found:', url)
                 else:
                     if url == self.getFilename():
-                        print('\tWarning: cant Inline yourself recursively:', url)
+                        print('\tWarning: can\'t Inline yourself recursively:', url)
                     else:
 
                         try:
                             data = gzipOpen(url)
                         except:
-                            print('\tWarning: cant open the file:', url)
+                            print('\tWarning: can\'t open the file:', url)
                             data = None
 
                         if data:
diff --git a/magic_uv/common.py b/magic_uv/common.py
index fccce1c458b50103dd50e6d710dbb3da93cd1184..f76fcc677f4315d47d9343b72b8a7cd569ae070f 100644
--- a/magic_uv/common.py
+++ b/magic_uv/common.py
@@ -1242,11 +1242,14 @@ def __is_points_in_polygon(points, subject_points):
 
 def get_uv_editable_objects(context):
     if compat.check_version(2, 80, 0) < 0:
-        objs = [context.active_object]
+        objs = []
     else:
         objs = [o for o in bpy.data.objects
                 if compat.get_object_select(o) and o.type == 'MESH']
-        objs.append(context.active_object)
+
+    ob = context.active_object
+    if ob is not None:
+        objs.append(ob)
 
     objs = list(set(objs))
     return objs
diff --git a/magic_uv/op/copy_paste_uv_object.py b/magic_uv/op/copy_paste_uv_object.py
index 39795b52efe0eebee0575c0abfe20897474a8e76..897891e4bc0962569cdc16ccd7bf52f4bbf64388 100644
--- a/magic_uv/op/copy_paste_uv_object.py
+++ b/magic_uv/op/copy_paste_uv_object.py
@@ -30,15 +30,16 @@ def _is_valid_context(context):
     if not common.is_valid_space(context, ['VIEW_3D']):
         return False
 
+    # Only object mode is allowed to execute.
+    ob = context.object
+    if ob is not None and ob.mode != 'OBJECT':
+        return False
+
     # Multiple objects editing mode is not supported in this feature.
     objs = common.get_uv_editable_objects(context)
     if len(objs) != 1:
         return False
 
-    # only object mode is allowed to execute
-    if context.object.mode != 'OBJECT':
-        return False
-
     return True
 
 
diff --git a/magic_uv/utils/graph.py b/magic_uv/utils/graph.py
index bebabf636c256e2e8b6efa0fce5d30dc6e80a3af..277800bcee4699470c9c7bfdf1def10e210600c5 100644
--- a/magic_uv/utils/graph.py
+++ b/magic_uv/utils/graph.py
@@ -29,7 +29,7 @@ class Edge:
             raise RuntimeError("Loop edge in {} is not supported."
                                .format(node.key))
         if node not in (self.node_1, self.node_2):
-            raise RuntimeError("Node {} does not belog this edge."
+            raise RuntimeError("Node {} does not belong to this edge."
                                .format(node.key))
         if self.node_1 == node:
             return self.node_2
diff --git a/materials_utils/functions.py b/materials_utils/functions.py
index 9397257a2d6c1be8063223d3528431791ccc20cb..93d700d880db59ad161cd2e37d90e142b11d86ab 100644
--- a/materials_utils/functions.py
+++ b/materials_utils/functions.py
@@ -333,7 +333,7 @@ def mu_select_by_material_name(self, find_material_name, extend_selection = Fals
             elif not internal:
                 # Some object types are not supported
                 #  mostly because don't really support selecting by material (like Font/Text objects)
-                #  ore that they don't support multiple materials/are just "weird" (i.e. Meta balls)
+                #  or that they don't support multiple materials/are just "weird" (i.e. Meta balls)
                 self.report({'WARNING'}, "The type '" +
                                             obj.type +
                                             "' isn't supported in Edit mode by Material Utilities!")
diff --git a/measureit/__init__.py b/measureit/__init__.py
index 8d4b95769902c78d5fcbb042f49141c1eed65107..c7f4fc7e681155069485a1d100c4d96afa2819f6 100644
--- a/measureit/__init__.py
+++ b/measureit/__init__.py
@@ -11,8 +11,8 @@ bl_info = {
     "name": "MeasureIt",
     "author": "Antonio Vazquez (antonioya)",
     "location": "View3D > Sidebar > View Tab",
-    "version": (1, 8, 1),
-    "blender": (2, 80, 0),
+    "version": (1, 8, 2),
+    "blender": (3, 0, 0),
     "description": "Tools for measuring objects.",
     "doc_url": "{BLENDER_MANUAL_URL}/addons/3d_view/measureit.html",
     "category": "3D View"
diff --git a/measureit/measureit_geometry.py b/measureit/measureit_geometry.py
index a1e90b9ed8e747b1adf3c97d986256b051145a00..56efad0e31e1a77226309b5b8267c79eb53b4812 100644
--- a/measureit/measureit_geometry.py
+++ b/measureit/measureit_geometry.py
@@ -17,7 +17,6 @@ from bpy_extras import view3d_utils, mesh_utils
 import bpy_extras.object_utils as object_utils
 from sys import exc_info
 # GPU
-import bgl
 import gpu
 from gpu_extras.batch import batch_for_shader
 
@@ -477,7 +476,7 @@ def draw_segments(context, myobj, op, region, rv3d):
                     # ------------------------------------
                     # Draw lines
                     # ------------------------------------
-                    bgl.glEnable(bgl.GL_BLEND)
+                    gpu.state.blend_set('ALPHA')
 
                     if ms.gltype == 1:  # Segment
                         draw_line(screen_point_ap1, screen_point_v11, rgba)
@@ -1175,7 +1174,7 @@ def draw_faces(context, myobj, region, rv3d):
 
             a_p2 = (a_p1[0] + normal[0] * ln, a_p1[1] + normal[1] * ln, a_p1[2] + normal[2] * ln)
             # line setup
-            bgl.glEnable(bgl.GL_BLEND)
+            gpu.state.blend_set('ALPHA')
             imm_set_line_width(th)
             # converting to screen coordinates
             txtpoint2d = get_2d_point(region, rv3d, a_p1)
@@ -1185,7 +1184,7 @@ def draw_faces(context, myobj, region, rv3d):
                 draw_text(myobj, txtpoint2d, str(f.index), rgba, fsize)
             # Draw Normal
             if scene.measureit_debug_normals is True:
-                bgl.glEnable(bgl.GL_BLEND)
+                gpu.state.blend_set('ALPHA')
                 draw_arrow(txtpoint2d, point2, rgba, 10, "99", "1")
 
                 if len(obverts) > 2 and scene.measureit_debug_normal_details is True:
diff --git a/measureit/measureit_main.py b/measureit/measureit_main.py
index 23e9de461204f943e4f9e20e958c191ccd11052c..d69ba268ec34f35b4856538b6470540f146e0fba 100644
--- a/measureit/measureit_main.py
+++ b/measureit/measureit_main.py
@@ -11,7 +11,6 @@ import bpy
 import bmesh
 from bmesh import from_edit_mesh
 # noinspection PyUnresolvedReferences
-import bgl
 from bpy.types import PropertyGroup, Panel, Object, Operator, SpaceView3D
 from bpy.props import IntProperty, CollectionProperty, FloatVectorProperty, BoolProperty, StringProperty, \
                       FloatProperty, EnumProperty
@@ -1933,8 +1932,8 @@ def draw_main(context):
     else:
         objlist = context.view_layer.objects
 
-    # Enable GL drawing
-    bgl.glEnable(bgl.GL_BLEND)
+    # Enable drawing
+    gpu.state.blend_set('ALPHA')
     # ---------------------------------------
     # Generate all OpenGL calls for measures
     # ---------------------------------------
@@ -1964,9 +1963,9 @@ def draw_main(context):
                 draw_faces(context, myobj, region, rv3d)
 
     # -----------------------
-    # restore opengl defaults
+    # restore defaults
     # -----------------------
-    bgl.glDisable(bgl.GL_BLEND)
+    gpu.state.blend_set('NONE')
 
 
 # -------------------------------------------------------------
diff --git a/measureit/measureit_render.py b/measureit/measureit_render.py
index efc5c1b00b31c2a2ab6b32d9a8bec8a5c50600ea..093149d5cfc7774bebf14e5ad5a20e281f2dbdf7 100644
--- a/measureit/measureit_render.py
+++ b/measureit/measureit_render.py
@@ -8,7 +8,6 @@
 # noinspection PyUnresolvedReferences
 import bpy
 import gpu
-import bgl
 # noinspection PyUnresolvedReferences
 import blf
 from os import path, remove
@@ -54,8 +53,8 @@ def render_main(self, context, animation=False):
         [0, 0, 0, 1]])
 
     with offscreen.bind():
-        bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
-        bgl.glClear(bgl.GL_COLOR_BUFFER_BIT)
+        fb = gpu.state.active_framebuffer_get()
+        fb.clear(color=(0.0, 0.0, 0.0, 0.0))
         gpu.matrix.reset()
         gpu.matrix.load_matrix(view_matrix)
         gpu.matrix.load_projection_matrix(Matrix.Identity(4))
@@ -101,9 +100,8 @@ def render_main(self, context, animation=False):
             y2 = height - y1
             draw_rectangle((x1, y1), (x2, y2), rfcolor)
 
-        buffer = bgl.Buffer(bgl.GL_BYTE, width * height * 4)
-        bgl.glReadBuffer(bgl.GL_COLOR_ATTACHMENT0)
-        bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)
+        buffer = fb.read_color(0, 0, width, height, 4, 0, 'UBYTE')
+        buffer.dimensions = width * height * 4
 
     offscreen.free()
 
diff --git a/mesh_bsurfaces.py b/mesh_bsurfaces.py
index 58ddd7aaed4214692ee87fea6f15658c23a8da4d..c980ed047d0060865e912b557e25708d774818ff 100644
--- a/mesh_bsurfaces.py
+++ b/mesh_bsurfaces.py
@@ -4,7 +4,7 @@
 bl_info = {
     "name": "Bsurfaces GPL Edition",
     "author": "Eclectiel, Vladimir Spivak (cwolf3d)",
-    "version": (1, 8, 0),
+    "version": (1, 8, 1),
     "blender": (2, 80, 0),
     "location": "View3D EditMode > Sidebar > Edit Tab",
     "description": "Modeling and retopology tool",
@@ -2517,7 +2517,7 @@ class MESH_OT_SURFSK_add_surface(Operator):
                           self.average_gp_segment_length
                         )
                 for t in range(2):
-                    bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts=segments)
+                    bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts=int(segments))
 
                 # Delete the other vertices and make it non-cyclic to
                 # keep only the needed verts of the "closing segment"
diff --git a/mesh_snap_utilities_line/op_line.py b/mesh_snap_utilities_line/op_line.py
index 7dcb4a522dd462da16bd4b5a856d55fbe807c49b..eacc551be20a947c98660b1fadb975dfec77ad88 100644
--- a/mesh_snap_utilities_line/op_line.py
+++ b/mesh_snap_utilities_line/op_line.py
@@ -51,10 +51,10 @@ def get_closest_edge(bm, point, dist):
     return r_edge
 
 
-def get_loose_linked_edges(bmvert):
-    linked = [e for e in bmvert.link_edges if not e.link_faces]
+def get_loose_linked_edges(vert):
+    linked = [e for e in vert.link_edges if e.is_wire]
     for e in linked:
-        linked += [le for v in e.verts if not v.link_faces for le in v.link_edges if le not in linked]
+        linked += [le for v in e.verts if v.is_wire for le in v.link_edges if le not in linked]
     return linked
 
 
@@ -170,8 +170,28 @@ def make_line(self, bm_geom, location):
                     break
 
             ed_list.update(get_loose_linked_edges(v2))
+            ed_list = list(ed_list)
+
+            # WORKAROUND: `edgenet_fill` only works with loose edges or boundary
+            # edges, so remove the other edges and create temporary elements to
+            # replace them.
+            targetmap = {}
+            ed_new = []
+            for edge in ed_list:
+                if not edge.is_wire and not edge.is_boundary:
+                    v1, v2 = edge.verts
+                    tmp_vert = bm.verts.new(v2.co)
+                    e1 = bm.edges.new([v1, tmp_vert])
+                    e2 = bm.edges.new([tmp_vert, v2])
+                    ed_list.remove(edge)
+                    ed_new.append(e1)
+                    ed_new.append(e2)
+                    targetmap[tmp_vert] = v2
+
+            bmesh.ops.edgenet_fill(bm, edges=ed_list + ed_new)
+            if targetmap:
+                bmesh.ops.weld_verts(bm, targetmap=targetmap)
 
-            bmesh.ops.edgenet_fill(bm, edges=list(ed_list))
             update_edit_mesh = True
             # print('face created')
 
diff --git a/mesh_tissue/utils.py b/mesh_tissue/utils.py
index b617ac93acca4ba56fa72feceb7e10460d27ea18..b43310843e6b82f27a2d28321173c1519bd194f8 100644
--- a/mesh_tissue/utils.py
+++ b/mesh_tissue/utils.py
@@ -1251,7 +1251,7 @@ def get_weight(vertex_group, n_verts):
     :type vertex_group: :class:'bpy.types.VertexGroup'
     :arg n_verts: Number of Vertices (output list size).
     :type n_verts: int
-    :return: Readed weight values.
+    :return: Read weight values.
     :rtype: list
     """
     weight = [0]*n_verts
@@ -1267,7 +1267,7 @@ def get_weight_numpy(vertex_group, n_verts):
     :type vertex_group: :class:'bpy.types.VertexGroup'
     :arg n_verts: Number of Vertices (output list size).
     :type n_verts: int
-    :return: Readed weight values as numpy array.
+    :return: Read weight values as numpy array.
     :rtype: :class:'numpy.ndarray'
     """
     weight = [0]*n_verts
diff --git a/mesh_tissue/weight_tools.py b/mesh_tissue/weight_tools.py
index 2736945eb571fe22ed14f0821a772c2ba0bd1c7f..8576b88c389c56431520d1efcc2b0477ddd0e528 100644
--- a/mesh_tissue/weight_tools.py
+++ b/mesh_tissue/weight_tools.py
@@ -2513,7 +2513,7 @@ class vertex_group_to_vertex_colors(Operator):
 
         bpy.ops.object.mode_set(mode='OBJECT')
         group_name = obj.vertex_groups[group_id].name
-        bpy.ops.mesh.vertex_color_add()
+        me.vertex_colors.new()
         colors_id = obj.data.vertex_colors.active_index
 
         colors_name = group_name
@@ -2694,8 +2694,8 @@ class curvature_to_vertex_groups(Operator):
 
     def execute(self, context):
         bpy.ops.object.mode_set(mode='OBJECT')
-        bpy.ops.mesh.vertex_color_add()
         vertex_colors = context.active_object.data.vertex_colors
+        vertex_colors.new()
         vertex_colors[-1].active = True
         vertex_colors[-1].active_render = True
         vertex_colors[-1].name = "Curvature"
@@ -2706,7 +2706,7 @@ class curvature_to_vertex_groups(Operator):
             blur_iterations=self.blur_iterations, clean_angle=self.max_angle,
             dirt_angle=self.min_angle)
         bpy.ops.object.vertex_colors_to_vertex_groups(invert=self.invert)
-        bpy.ops.mesh.vertex_color_remove()
+        vertex_colors.remove(vertex_colors.active)
         return {'FINISHED'}
 
 class face_area_to_vertex_groups(Operator):
diff --git a/node_wrangler.py b/node_wrangler.py
index 1a815dc63218c119f88c50719b4558a446c60561..cc1b85febb5065d5859e9871c248b8a5f77a685e 100644
--- a/node_wrangler.py
+++ b/node_wrangler.py
@@ -3,7 +3,7 @@
 bl_info = {
     "name": "Node Wrangler",
     "author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer",
-    "version": (3, 40),
+    "version": (3, 41),
     "blender": (2, 93, 0),
     "location": "Node Editor Toolbar or Shift-W",
     "description": "Various tools to enhance and speed up node-based workflow",
@@ -72,406 +72,6 @@ rl_outputs = (
     RL_entry('use_pass_z', 'Z', 'Depth', True, True),
     )
 
-# shader nodes
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-shaders_input_nodes_props = (
-    ('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
-    ('ShaderNodeAttribute', 'ATTRIBUTE', 'Attribute'),
-    ('ShaderNodeBevel', 'BEVEL', 'Bevel'),
-    ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
-    ('ShaderNodeFresnel', 'FRESNEL', 'Fresnel'),
-    ('ShaderNodeNewGeometry', 'NEW_GEOMETRY', 'Geometry'),
-    ('ShaderNodeHairInfo', 'HAIR_INFO', 'Hair Info'),
-    ('ShaderNodeLayerWeight', 'LAYER_WEIGHT', 'Layer Weight'),
-    ('ShaderNodeLightPath', 'LIGHT_PATH', 'Light Path'),
-    ('ShaderNodeObjectInfo', 'OBJECT_INFO', 'Object Info'),
-    ('ShaderNodeParticleInfo', 'PARTICLE_INFO', 'Particle Info'),
-    ('ShaderNodeRGB', 'RGB', 'RGB'),
-    ('ShaderNodeTangent', 'TANGENT', 'Tangent'),
-    ('ShaderNodeTexCoord', 'TEX_COORD', 'Texture Coordinate'),
-    ('ShaderNodeUVMap', 'UVMAP', 'UV Map'),
-    ('ShaderNodeValue', 'VALUE', 'Value'),
-    ('ShaderNodeVertexColor', 'VERTEX_COLOR', 'Vertex Color'),
-    ('ShaderNodeVolumeInfo', 'VOLUME_INFO', 'Volume Info'),
-    ('ShaderNodeWireframe', 'WIREFRAME', 'Wireframe'),
-
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-shaders_output_nodes_props = (
-    ('ShaderNodeOutputAOV', 'OUTPUT_AOV', 'AOV Output'),
-    ('ShaderNodeOutputLight', 'OUTPUT_LIGHT', 'Light Output'),
-    ('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'),
-    ('ShaderNodeOutputWorld', 'OUTPUT_WORLD', 'World Output'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-shaders_shader_nodes_props = (
-    ('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
-    ('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
-    ('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
-    ('ShaderNodeEmission', 'EMISSION', 'Emission'),
-    ('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
-    ('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
-    ('ShaderNodeBsdfHair', 'BSDF_HAIR', 'Hair BSDF'),
-    ('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
-    ('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
-    ('ShaderNodeBsdfPrincipled', 'BSDF_PRINCIPLED', 'Principled BSDF'),
-    ('ShaderNodeBsdfHairPrincipled', 'BSDF_HAIR_PRINCIPLED', 'Principled Hair BSDF'),
-    ('ShaderNodeVolumePrincipled', 'PRINCIPLED_VOLUME', 'Principled Volume'),
-    ('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
-    ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
-    ('ShaderNodeBsdfToon', 'BSDF_TOON', 'Toon BSDF'),
-    ('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
-    ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
-    ('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
-    ('ShaderNodeBackground', 'BACKGROUND', 'Background'),
-    ('ShaderNodeVolumeAbsorption', 'VOLUME_ABSORPTION', 'Volume Absorption'),
-    ('ShaderNodeVolumeScatter', 'VOLUME_SCATTER', 'Volume Scatter'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping things in alphabetical order so we don't need to sort later.
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-shaders_texture_nodes_props = (
-    ('ShaderNodeTexBrick', 'TEX_BRICK', 'Brick Texture'),
-    ('ShaderNodeTexChecker', 'TEX_CHECKER', 'Checker Texture'),
-    ('ShaderNodeTexEnvironment', 'TEX_ENVIRONMENT', 'Environment Texture'),
-    ('ShaderNodeTexGradient', 'TEX_GRADIENT', 'Gradient Texture'),
-    ('ShaderNodeTexIES', 'TEX_IES', 'IES Texture'),
-    ('ShaderNodeTexImage', 'TEX_IMAGE', 'Image Texture'),
-    ('ShaderNodeTexMagic', 'TEX_MAGIC', 'Magic Texture'),
-    ('ShaderNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave Texture'),
-    ('ShaderNodeTexNoise', 'TEX_NOISE', 'Noise Texture'),
-    ('ShaderNodeTexPointDensity', 'TEX_POINTDENSITY', 'Point Density'),
-    ('ShaderNodeTexSky', 'TEX_SKY', 'Sky Texture'),
-    ('ShaderNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi Texture'),
-    ('ShaderNodeTexWave', 'TEX_WAVE', 'Wave Texture'),
-    ('ShaderNodeTexWhiteNoise', 'TEX_WHITE_NOISE', 'White Noise'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-shaders_color_nodes_props = (
-    ('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
-    ('ShaderNodeGamma', 'GAMMA', 'Gamma'),
-    ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue Saturation Value'),
-    ('ShaderNodeInvert', 'INVERT', 'Invert'),
-    ('ShaderNodeLightFalloff', 'LIGHT_FALLOFF', 'Light Falloff'),
-    ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
-    ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-shaders_vector_nodes_props = (
-    ('ShaderNodeBump', 'BUMP', 'Bump'),
-    ('ShaderNodeDisplacement', 'DISPLACEMENT', 'Displacement'),
-    ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
-    ('ShaderNodeNormal', 'NORMAL', 'Normal'),
-    ('ShaderNodeNormalMap', 'NORMAL_MAP', 'Normal Map'),
-    ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
-    ('ShaderNodeVectorDisplacement', 'VECTOR_DISPLACEMENT', 'Vector Displacement'),
-    ('ShaderNodeVectorTransform', 'VECT_TRANSFORM', 'Vector Transform'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-shaders_converter_nodes_props = (
-    ('ShaderNodeBlackbody', 'BLACKBODY', 'Blackbody'),
-    ('ShaderNodeClamp', 'CLAMP', 'Clamp'),
-    ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
-    ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
-    ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
-    ('ShaderNodeCombineXYZ', 'COMBXYZ', 'Combine XYZ'),
-    ('ShaderNodeMapRange', 'MAP_RANGE', 'Map Range'),
-    ('ShaderNodeMath', 'MATH', 'Math'),
-    ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
-    ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
-    ('ShaderNodeSeparateXYZ', 'SEPXYZ', 'Separate XYZ'),
-    ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
-    ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
-    ('ShaderNodeWavelength', 'WAVELENGTH', 'Wavelength'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-shaders_layout_nodes_props = (
-    ('NodeFrame', 'FRAME', 'Frame'),
-    ('NodeReroute', 'REROUTE', 'Reroute'),
-)
-
-# compositing nodes
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-compo_input_nodes_props = (
-    ('CompositorNodeBokehImage', 'BOKEHIMAGE', 'Bokeh Image'),
-    ('CompositorNodeImage', 'IMAGE', 'Image'),
-    ('CompositorNodeMask', 'MASK', 'Mask'),
-    ('CompositorNodeMovieClip', 'MOVIECLIP', 'Movie Clip'),
-    ('CompositorNodeRLayers', 'R_LAYERS', 'Render Layers'),
-    ('CompositorNodeRGB', 'RGB', 'RGB'),
-    ('CompositorNodeTexture', 'TEXTURE', 'Texture'),
-    ('CompositorNodeTime', 'TIME', 'Time'),
-    ('CompositorNodeTrackPos', 'TRACKPOS', 'Track Position'),
-    ('CompositorNodeValue', 'VALUE', 'Value'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-compo_output_nodes_props = (
-    ('CompositorNodeComposite', 'COMPOSITE', 'Composite'),
-    ('CompositorNodeOutputFile', 'OUTPUT_FILE', 'File Output'),
-    ('CompositorNodeLevels', 'LEVELS', 'Levels'),
-    ('CompositorNodeSplitViewer', 'SPLITVIEWER', 'Split Viewer'),
-    ('CompositorNodeViewer', 'VIEWER', 'Viewer'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-compo_color_nodes_props = (
-    ('CompositorNodeAlphaOver', 'ALPHAOVER', 'Alpha Over'),
-    ('CompositorNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright/Contrast'),
-    ('CompositorNodeColorBalance', 'COLORBALANCE', 'Color Balance'),
-    ('CompositorNodeColorCorrection', 'COLORCORRECTION', 'Color Correction'),
-    ('CompositorNodeGamma', 'GAMMA', 'Gamma'),
-    ('CompositorNodeHueCorrect', 'HUECORRECT', 'Hue Correct'),
-    ('CompositorNodeHueSat', 'HUE_SAT', 'Hue Saturation Value'),
-    ('CompositorNodeInvert', 'INVERT', 'Invert'),
-    ('CompositorNodeMixRGB', 'MIX_RGB', 'Mix'),
-    ('CompositorNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
-    ('CompositorNodeTonemap', 'TONEMAP', 'Tonemap'),
-    ('CompositorNodeZcombine', 'ZCOMBINE', 'Z Combine'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-compo_converter_nodes_props = (
-    ('CompositorNodePremulKey', 'PREMULKEY', 'Alpha Convert'),
-    ('CompositorNodeValToRGB', 'VALTORGB', 'ColorRamp'),
-    ('CompositorNodeCombHSVA', 'COMBHSVA', 'Combine HSVA'),
-    ('CompositorNodeCombRGBA', 'COMBRGBA', 'Combine RGBA'),
-    ('CompositorNodeCombYCCA', 'COMBYCCA', 'Combine YCbCrA'),
-    ('CompositorNodeCombYUVA', 'COMBYUVA', 'Combine YUVA'),
-    ('CompositorNodeIDMask', 'ID_MASK', 'ID Mask'),
-    ('CompositorNodeMath', 'MATH', 'Math'),
-    ('CompositorNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
-    ('CompositorNodeSepRGBA', 'SEPRGBA', 'Separate RGBA'),
-    ('CompositorNodeSepHSVA', 'SEPHSVA', 'Separate HSVA'),
-    ('CompositorNodeSepYUVA', 'SEPYUVA', 'Separate YUVA'),
-    ('CompositorNodeSepYCCA', 'SEPYCCA', 'Separate YCbCrA'),
-    ('CompositorNodeSetAlpha', 'SETALPHA', 'Set Alpha'),
-    ('CompositorNodeSwitchView', 'VIEWSWITCH', 'View Switch'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-compo_filter_nodes_props = (
-    ('CompositorNodeBilateralblur', 'BILATERALBLUR', 'Bilateral Blur'),
-    ('CompositorNodeBlur', 'BLUR', 'Blur'),
-    ('CompositorNodeBokehBlur', 'BOKEHBLUR', 'Bokeh Blur'),
-    ('CompositorNodeDefocus', 'DEFOCUS', 'Defocus'),
-    ('CompositorNodeDenoise', 'DENOISE', 'Denoise'),
-    ('CompositorNodeDespeckle', 'DESPECKLE', 'Despeckle'),
-    ('CompositorNodeDilateErode', 'DILATEERODE', 'Dilate/Erode'),
-    ('CompositorNodeDBlur', 'DBLUR', 'Directional Blur'),
-    ('CompositorNodeFilter', 'FILTER', 'Filter'),
-    ('CompositorNodeGlare', 'GLARE', 'Glare'),
-    ('CompositorNodeInpaint', 'INPAINT', 'Inpaint'),
-    ('CompositorNodePixelate', 'PIXELATE', 'Pixelate'),
-    ('CompositorNodeSunBeams', 'SUNBEAMS', 'Sun Beams'),
-    ('CompositorNodeVecBlur', 'VECBLUR', 'Vector Blur'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-compo_vector_nodes_props = (
-    ('CompositorNodeMapRange', 'MAP_RANGE', 'Map Range'),
-    ('CompositorNodeMapValue', 'MAP_VALUE', 'Map Value'),
-    ('CompositorNodeNormal', 'NORMAL', 'Normal'),
-    ('CompositorNodeNormalize', 'NORMALIZE', 'Normalize'),
-    ('CompositorNodeCurveVec', 'CURVE_VEC', 'Vector Curves'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-compo_matte_nodes_props = (
-    ('CompositorNodeBoxMask', 'BOXMASK', 'Box Mask'),
-    ('CompositorNodeChannelMatte', 'CHANNEL_MATTE', 'Channel Key'),
-    ('CompositorNodeChromaMatte', 'CHROMA_MATTE', 'Chroma Key'),
-    ('CompositorNodeColorMatte', 'COLOR_MATTE', 'Color Key'),
-    ('CompositorNodeColorSpill', 'COLOR_SPILL', 'Color Spill'),
-    ('CompositorNodeCryptomatte', 'CRYPTOMATTE', 'Cryptomatte'),
-    ('CompositorNodeDiffMatte', 'DIFF_MATTE', 'Difference Key'),
-    ('CompositorNodeDistanceMatte', 'DISTANCE_MATTE', 'Distance Key'),
-    ('CompositorNodeDoubleEdgeMask', 'DOUBLEEDGEMASK', 'Double Edge Mask'),
-    ('CompositorNodeEllipseMask', 'ELLIPSEMASK', 'Ellipse Mask'),
-    ('CompositorNodeKeying', 'KEYING', 'Keying'),
-    ('CompositorNodeKeyingScreen', 'KEYINGSCREEN', 'Keying Screen'),
-    ('CompositorNodeLumaMatte', 'LUMA_MATTE', 'Luminance Key'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-compo_distort_nodes_props = (
-    ('CompositorNodeCornerPin', 'CORNERPIN', 'Corner Pin'),
-    ('CompositorNodeCrop', 'CROP', 'Crop'),
-    ('CompositorNodeDisplace', 'DISPLACE', 'Displace'),
-    ('CompositorNodeFlip', 'FLIP', 'Flip'),
-    ('CompositorNodeLensdist', 'LENSDIST', 'Lens Distortion'),
-    ('CompositorNodeMapUV', 'MAP_UV', 'Map UV'),
-    ('CompositorNodeMovieDistortion', 'MOVIEDISTORTION', 'Movie Distortion'),
-    ('CompositorNodePlaneTrackDeform', 'PLANETRACKDEFORM', 'Plane Track Deform'),
-    ('CompositorNodeRotate', 'ROTATE', 'Rotate'),
-    ('CompositorNodeScale', 'SCALE', 'Scale'),
-    ('CompositorNodeStabilize', 'STABILIZE2D', 'Stabilize 2D'),
-    ('CompositorNodeTransform', 'TRANSFORM', 'Transform'),
-    ('CompositorNodeTranslate', 'TRANSLATE', 'Translate'),
-)
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical order so we don't need to sort later.
-compo_layout_nodes_props = (
-    ('NodeFrame', 'FRAME', 'Frame'),
-    ('NodeReroute', 'REROUTE', 'Reroute'),
-    ('CompositorNodeSwitch', 'SWITCH', 'Switch'),
-)
-# Blender Render material nodes
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-blender_mat_input_nodes_props = (
-    ('ShaderNodeMaterial', 'MATERIAL', 'Material'),
-    ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
-    ('ShaderNodeLightData', 'LIGHT', 'Light Data'),
-    ('ShaderNodeValue', 'VALUE', 'Value'),
-    ('ShaderNodeRGB', 'RGB', 'RGB'),
-    ('ShaderNodeTexture', 'TEXTURE', 'Texture'),
-    ('ShaderNodeGeometry', 'GEOMETRY', 'Geometry'),
-    ('ShaderNodeExtendedMaterial', 'MATERIAL_EXT', 'Extended Material'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-blender_mat_output_nodes_props = (
-    ('ShaderNodeOutput', 'OUTPUT', 'Output'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-blender_mat_color_nodes_props = (
-    ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
-    ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
-    ('ShaderNodeInvert', 'INVERT', 'Invert'),
-    ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue Saturation Value'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-blender_mat_vector_nodes_props = (
-    ('ShaderNodeNormal', 'NORMAL', 'Normal'),
-    ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
-    ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-blender_mat_converter_nodes_props = (
-    ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
-    ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
-    ('ShaderNodeMath', 'MATH', 'Math'),
-    ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
-    ('ShaderNodeSqueeze', 'SQUEEZE', 'Squeeze Value'),
-    ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
-    ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
-    ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
-    ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-blender_mat_layout_nodes_props = (
-    ('NodeReroute', 'REROUTE', 'Reroute'),
-)
-
-# Texture Nodes
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-texture_input_nodes_props = (
-    ('TextureNodeCurveTime', 'CURVE_TIME', 'Curve Time'),
-    ('TextureNodeCoordinates', 'COORD', 'Coordinates'),
-    ('TextureNodeTexture', 'TEXTURE', 'Texture'),
-    ('TextureNodeImage', 'IMAGE', 'Image'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-texture_output_nodes_props = (
-    ('TextureNodeOutput', 'OUTPUT', 'Output'),
-    ('TextureNodeViewer', 'VIEWER', 'Viewer'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-texture_color_nodes_props = (
-    ('TextureNodeMixRGB', 'MIX_RGB', 'Mix RGB'),
-    ('TextureNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
-    ('TextureNodeInvert', 'INVERT', 'Invert'),
-    ('TextureNodeHueSaturation', 'HUE_SAT', 'Hue Saturation Value'),
-    ('TextureNodeCompose', 'COMPOSE', 'Combine RGBA'),
-    ('TextureNodeDecompose', 'DECOMPOSE', 'Separate RGBA'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-texture_pattern_nodes_props = (
-    ('TextureNodeChecker', 'CHECKER', 'Checker'),
-    ('TextureNodeBricks', 'BRICKS', 'Bricks'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-texture_textures_nodes_props = (
-    ('TextureNodeTexNoise', 'TEX_NOISE', 'Noise'),
-    ('TextureNodeTexDistNoise', 'TEX_DISTNOISE', 'Distorted Noise'),
-    ('TextureNodeTexClouds', 'TEX_CLOUDS', 'Clouds'),
-    ('TextureNodeTexBlend', 'TEX_BLEND', 'Blend'),
-    ('TextureNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi'),
-    ('TextureNodeTexMagic', 'TEX_MAGIC', 'Magic'),
-    ('TextureNodeTexMarble', 'TEX_MARBLE', 'Marble'),
-    ('TextureNodeTexWood', 'TEX_WOOD', 'Wood'),
-    ('TextureNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave'),
-    ('TextureNodeTexStucci', 'TEX_STUCCI', 'Stucci'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-texture_converter_nodes_props = (
-    ('TextureNodeMath', 'MATH', 'Math'),
-    ('TextureNodeValToRGB', 'VALTORGB', 'ColorRamp'),
-    ('TextureNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
-    ('TextureNodeValToNor', 'VALTONOR', 'Value to Normal'),
-    ('TextureNodeDistance', 'DISTANCE', 'Distance'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-texture_distort_nodes_props = (
-    ('TextureNodeScale', 'SCALE', 'Scale'),
-    ('TextureNodeTranslate', 'TRANSLATE', 'Translate'),
-    ('TextureNodeRotate', 'ROTATE', 'Rotate'),
-    ('TextureNodeAt', 'AT', 'At'),
-)
-
-# (rna_type.identifier, type, rna_type.name)
-# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-texture_layout_nodes_props = (
-    ('NodeReroute', 'REROUTE', 'Reroute'),
-)
-
 # list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
 # used list, not tuple for easy merging with other lists.
 blend_types = [
@@ -596,6 +196,13 @@ def get_nodes_from_category(category_name, context):
         if category.name == category_name:
             return sorted(category.items(context), key=lambda node: node.label)
 
+def get_first_enabled_output(node):
+    for output in node.outputs:
+        if output.enabled:
+            return output
+    else:
+        return node.outputs[0]
+
 def is_visible_socket(socket):
     return not socket.hide and socket.enabled and socket.type != 'CUSTOM'
 
@@ -813,8 +420,7 @@ def draw_circle_2d_filled(shader, mx, my, radius, colour=(1.0, 1.0, 1.0, 0.7)):
 
 
 def draw_rounded_node_border(shader, node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)):
-    area_width = bpy.context.area.width - (16*dpifac()) - 1
-    bottom_bar = (16*dpifac()) + 1
+    area_width = bpy.context.area.width
     sides = 16
     radius = radius*dpifac()
 
@@ -840,7 +446,7 @@ def draw_rounded_node_border(shader, node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)
     vertices = [(mx,my)]
     for i in range(sides+1):
         if (4<=i<=8):
-            if my > bottom_bar and mx < area_width:
+            if mx < area_width:
                 cosine = radius * cos(i * 2 * pi / sides) + mx
                 sine = radius * sin(i * 2 * pi / sides) + my
                 vertices.append((cosine,sine))
@@ -854,7 +460,7 @@ def draw_rounded_node_border(shader, node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)
     vertices = [(mx,my)]
     for i in range(sides+1):
         if (0<=i<=4):
-            if my > bottom_bar and mx < area_width:
+            if mx < area_width:
                 cosine = radius * cos(i * 2 * pi / sides) + mx
                 sine = radius * sin(i * 2 * pi / sides) + my
                 vertices.append((cosine,sine))
@@ -868,7 +474,7 @@ def draw_rounded_node_border(shader, node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)
     vertices = [(mx,my)]
     for i in range(sides+1):
         if (8<=i<=12):
-            if my > bottom_bar and mx < area_width:
+            if mx < area_width:
                 cosine = radius * cos(i * 2 * pi / sides) + mx
                 sine = radius * sin(i * 2 * pi / sides) + my
                 vertices.append((cosine,sine))
@@ -882,7 +488,7 @@ def draw_rounded_node_border(shader, node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)
     vertices = [(mx,my)]
     for i in range(sides+1):
         if (12<=i<=16):
-            if my > bottom_bar and mx < area_width:
+            if mx < area_width:
                 cosine = radius * cos(i * 2 * pi / sides) + mx
                 sine = radius * sin(i * 2 * pi / sides) + my
                 vertices.append((cosine,sine))
@@ -911,18 +517,15 @@ def draw_rounded_node_border(shader, node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)
     m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
     m1x = min(m1x, area_width)
     m2x = min(m2x, area_width)
-    if m1y > bottom_bar and m2y > bottom_bar:
-        vertices.extend([(m1x,m1y), (m2x,m1y),
-                         (m2x,m1y+radius), (m1x,m1y+radius)])
-        indices.extend([(id_last, id_last+1, id_last+3),
-                        (id_last+3, id_last+1, id_last+2)])
-        id_last += 4
+    vertices.extend([(m1x,m1y), (m2x,m1y),
+                     (m2x,m1y+radius), (m1x,m1y+radius)])
+    indices.extend([(id_last, id_last+1, id_last+3),
+                    (id_last+3, id_last+1, id_last+2)])
+    id_last += 4
 
     # Right edge
     m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
     m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
-    m1y = max(m1y, bottom_bar)
-    m2y = max(m2y, bottom_bar)
     if m1x < area_width and m2x < area_width:
         vertices.extend([(m1x,m2y), (m1x+radius,m2y),
                          (m1x+radius,m1y), (m1x,m1y)])
@@ -935,11 +538,10 @@ def draw_rounded_node_border(shader, node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)
     m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy-ndimy, clip=False)
     m1x = min(m1x, area_width)
     m2x = min(m2x, area_width)
-    if m1y > bottom_bar and m2y > bottom_bar:
-        vertices.extend([(m1x,m2y), (m2x,m2y),
-                         (m2x,m1y-radius), (m1x,m1y-radius)])
-        indices.extend([(id_last, id_last+1, id_last+3),
-                        (id_last+3, id_last+1, id_last+2)])
+    vertices.extend([(m1x,m2y), (m2x,m2y),
+                     (m2x,m1y-radius), (m1x,m1y-radius)])
+    indices.extend([(id_last, id_last+1, id_last+3),
+                    (id_last+3, id_last+1, id_last+2)])
 
     # now draw all edges in one batch
     if len(vertices) != 0:
@@ -1051,7 +653,7 @@ def get_internal_socket(socket):
     return iterator[i]
 
 def is_viewer_link(link, output_node):
-    if "Emission Viewer" in link.to_node.name or link.to_node == output_node and link.to_socket == output_node.inputs[0]:
+    if link.to_node == output_node and link.to_socket == output_node.inputs[0]:
         return True
     if link.to_node.type == 'GROUP_OUTPUT':
         socket = get_internal_socket(link.to_socket)
@@ -1068,8 +670,6 @@ def get_output_location(tree):
     # get right-most location
     sorted_by_xloc = (sorted(tree.nodes, key=lambda x: x.location.x))
     max_xloc_node = sorted_by_xloc[-1]
-    if max_xloc_node.name == 'Emission Viewer':
-        max_xloc_node = sorted_by_xloc[-2]
 
     # get average y location
     sum_yloc = 0
@@ -1686,7 +1286,7 @@ class NWAddAttrNode(Operator, NWBase):
 class NWPreviewNode(Operator, NWBase):
     bl_idname = "node.nw_preview_node"
     bl_label = "Preview Node"
-    bl_description = "Connect active node to Emission Shader for shadeless previews, or to the geometry node tree's output"
+    bl_description = "Connect active node to the Node Group output or the Material Output"
     bl_options = {'REGISTER', 'UNDO'}
 
     # If false, the operator is not executed if the current node group happens to be a geometry nodes group.
@@ -1697,7 +1297,6 @@ class NWPreviewNode(Operator, NWBase):
     def __init__(self):
         self.shader_output_type = ""
         self.shader_output_ident = ""
-        self.shader_viewer_ident = ""
 
     @classmethod
     def poll(cls, context):
@@ -1748,16 +1347,13 @@ class NWPreviewNode(Operator, NWBase):
             if space.id not in [light for light in bpy.data.lights]:  # cannot use bpy.data.lights directly as iterable
                 self.shader_output_type = "OUTPUT_MATERIAL"
                 self.shader_output_ident = "ShaderNodeOutputMaterial"
-                self.shader_viewer_ident = "ShaderNodeEmission"
             else:
                 self.shader_output_type = "OUTPUT_LIGHT"
                 self.shader_output_ident = "ShaderNodeOutputLight"
-                self.shader_viewer_ident = "ShaderNodeEmission"
 
         elif shader_type == 'WORLD':
             self.shader_output_type = "OUTPUT_WORLD"
             self.shader_output_ident = "ShaderNodeOutputWorld"
-            self.shader_viewer_ident = "ShaderNodeBackground"
 
     def get_shader_output_node(self, tree):
         for node in tree.nodes:
@@ -1820,8 +1416,7 @@ class NWPreviewNode(Operator, NWBase):
             self.used_viewer_sockets_active_mat = []
             materialout = self.get_shader_output_node(bpy.context.space_data.node_tree)
             if materialout:
-                emission = self.get_viewer_node(materialout)
-                self.search_sockets((emission if emission else materialout), self.used_viewer_sockets_active_mat)
+                self.search_sockets(materialout, self.used_viewer_sockets_active_mat)
         return socket in self.used_viewer_sockets_active_mat
 
     def is_socket_used_other_mats(self, socket):
@@ -1834,18 +1429,9 @@ class NWPreviewNode(Operator, NWBase):
                 # get viewer node
                 materialout = self.get_shader_output_node(mat.node_tree)
                 if materialout:
-                    emission = self.get_viewer_node(materialout)
-                    self.search_sockets((emission if emission else materialout), self.used_viewer_sockets_other_mats)
+                    self.search_sockets(materialout, self.used_viewer_sockets_other_mats)
         return socket in self.used_viewer_sockets_other_mats
 
-    @staticmethod
-    def get_viewer_node(materialout):
-        input_socket = materialout.inputs[0]
-        if len(input_socket.links) > 0:
-            node = input_socket.links[0].from_node
-            if node.type == 'EMISSION' and node.name == "Emission Viewer":
-                return node
-
     def invoke(self, context, event):
         space = context.space_data
         # Ignore operator when running in wrong context.
@@ -1854,7 +1440,6 @@ class NWPreviewNode(Operator, NWBase):
 
         shader_type = space.shader_type
         self.init_shader_variables(space, shader_type)
-        shader_types = [x[1] for x in shaders_shader_nodes_props]
         mlocx = event.mouse_region_x
         mlocy = event.mouse_region_y
         select_node = bpy.ops.node.select(location=(mlocx, mlocy), extend=False)
@@ -1864,8 +1449,7 @@ class NWPreviewNode(Operator, NWBase):
             base_node_tree = space.node_tree
             active = nodes.active
 
-            # For geometry node trees we just connect to the group output,
-            # because there is no "viewer node" yet.
+            # For geometry node trees we just connect to the group output
             if space.tree_type == "GeometryNodeTree":
                 valid = False
                 if active:
@@ -1903,7 +1487,6 @@ class NWPreviewNode(Operator, NWBase):
                                     out_i = valid_outputs[0]
 
                 make_links = []  # store sockets for new links
-                delete_nodes = [] # store unused nodes to delete in the end
                 if active.outputs:
                     # If there is no 'GEOMETRY' output type - We can't preview the node
                     if out_i is None:
@@ -1944,10 +1527,6 @@ class NWPreviewNode(Operator, NWBase):
                     tree = socket.id_data
                     tree.outputs.remove(socket)
 
-                # Delete nodes
-                for tree, node in delete_nodes:
-                    tree.nodes.remove(node)
-
                 nodes.active = active
                 active.select = True
                 force_update(context)
@@ -1955,10 +1534,11 @@ class NWPreviewNode(Operator, NWBase):
 
 
             # What follows is code for the shader editor
-            output_types = [x[1] for x in shaders_output_nodes_props]
+            output_types = [x.nodetype for x in
+                            get_nodes_from_category('Output', context)]
             valid = False
             if active:
-                if (active.name != "Emission Viewer") and (active.type not in output_types):
+                if active.rna_type.identifier not in output_types:
                     for out in active.outputs:
                         if is_visible_socket(out):
                             valid = True
@@ -1976,7 +1556,7 @@ class NWPreviewNode(Operator, NWBase):
                     materialout = base_node_tree.nodes.new(self.shader_output_ident)
                     materialout.location = get_output_location(base_node_tree)
                     materialout.select = False
-                # Analyze outputs, add "Emission Viewer" if needed, make links
+                # Analyze outputs
                 out_i = None
                 valid_outputs = []
                 for i, out in enumerate(active.outputs):
@@ -1994,56 +1574,11 @@ class NWPreviewNode(Operator, NWBase):
                                     out_i = valid_outputs[0]
 
                 make_links = []  # store sockets for new links
-                delete_nodes = [] # store unused nodes to delete in the end
                 if active.outputs:
-                    # If output type not 'SHADER' - "Emission Viewer" needed
-                    if active.outputs[out_i].type != 'SHADER':
-                        socket_type = 'NodeSocketColor'
-                        # get Emission Viewer node
-                        emission_exists = False
-                        emission_placeholder = base_node_tree.nodes[0]
-                        for node in base_node_tree.nodes:
-                            if "Emission Viewer" in node.name:
-                                emission_exists = True
-                                emission_placeholder = node
-                        if not emission_exists:
-                            emission = base_node_tree.nodes.new(self.shader_viewer_ident)
-                            emission.hide = True
-                            emission.location = [materialout.location.x, (materialout.location.y + 40)]
-                            emission.label = "Viewer"
-                            emission.name = "Emission Viewer"
-                            emission.use_custom_color = True
-                            emission.color = (0.6, 0.5, 0.4)
-                            emission.select = False
-                        else:
-                            emission = emission_placeholder
-                        output_socket = emission.inputs[0]
-
-                        # If Viewer is connected to output by user, don't change those connections (patch by gandalf3)
-                        if emission.outputs[0].links.__len__() > 0:
-                            if not emission.outputs[0].links[0].to_node == materialout:
-                                make_links.append((emission.outputs[0], materialout.inputs[0]))
-                        else:
-                            make_links.append((emission.outputs[0], materialout.inputs[0]))
-
-                        # Set brightness of viewer to compensate for Film and CM exposure
-                        if context.scene.render.engine == 'CYCLES' and hasattr(context.scene, 'cycles'):
-                            intensity = 1/context.scene.cycles.film_exposure  # Film exposure is a multiplier
-                        else:
-                            intensity = 1
-
-                        intensity /= pow(2, (context.scene.view_settings.exposure))  # CM exposure is measured in stops/EVs (2^x)
-                        emission.inputs[1].default_value = intensity
-
-                    else:
-                        # Output type is 'SHADER', no Viewer needed. Delete Viewer if exists.
-                        socket_type = 'NodeSocketShader'
-                        materialout_index = 1 if active.outputs[out_i].name == "Volume" else 0
-                        make_links.append((active.outputs[out_i], materialout.inputs[materialout_index]))
-                        output_socket = materialout.inputs[materialout_index]
-                        for node in base_node_tree.nodes:
-                            if node.name == 'Emission Viewer':
-                                delete_nodes.append((base_node_tree, node))
+                    socket_type = 'NodeSocketShader'
+                    materialout_index = 1 if active.outputs[out_i].name == "Volume" else 0
+                    make_links.append((active.outputs[out_i], materialout.inputs[materialout_index]))
+                    output_socket = materialout.inputs[materialout_index]
                     for li_from, li_to in make_links:
                         base_node_tree.links.new(li_from, li_to)
 
@@ -2069,10 +1604,6 @@ class NWPreviewNode(Operator, NWBase):
                         tree = socket.id_data
                         tree.outputs.remove(socket)
 
-                # Delete nodes
-                for tree, node in delete_nodes:
-                    tree.nodes.remove(node)
-
                 nodes.active = active
                 active.select = True
 
@@ -2094,14 +1625,27 @@ class NWFrameSelected(Operator, NWBase):
         description='The visual name of the frame node',
         default=' '
     )
+    use_custom_color_prop: BoolProperty(
+        name="Custom Color",
+        description="Use custom color for the frame node",
+        default=False
+    )
     color_prop: FloatVectorProperty(
         name="Color",
         description="The color of the frame node",
-        default=(0.6, 0.6, 0.6),
+        default=(0.604, 0.604, 0.604),
         min=0, max=1, step=1, precision=3,
         subtype='COLOR_GAMMA', size=3
     )
 
+    def draw(self, context):
+        layout = self.layout
+        layout.prop(self, 'label_prop')
+        layout.prop(self, 'use_custom_color_prop')
+        col = layout.column()
+        col.active = self.use_custom_color_prop
+        col.prop(self, 'color_prop', text="")
+
     def execute(self, context):
         nodes, links = get_nodes_links(context)
         selected = []
@@ -2112,7 +1656,7 @@ class NWFrameSelected(Operator, NWBase):
         bpy.ops.node.add_node(type='NodeFrame')
         frm = nodes.active
         frm.label = self.label_prop
-        frm.use_custom_color = True
+        frm.use_custom_color = self.use_custom_color_prop
         frm.color = self.color_prop
 
         for node in selected:
@@ -2170,51 +1714,17 @@ class NWSwitchNodeType(Operator, NWBase):
     bl_label = "Switch Node Type"
     bl_options = {'REGISTER', 'UNDO'}
 
-    to_type: EnumProperty(
-        name="Switch to type",
-        items=list(shaders_input_nodes_props) +
-        list(shaders_output_nodes_props) +
-        list(shaders_shader_nodes_props) +
-        list(shaders_texture_nodes_props) +
-        list(shaders_color_nodes_props) +
-        list(shaders_vector_nodes_props) +
-        list(shaders_converter_nodes_props) +
-        list(shaders_layout_nodes_props) +
-        list(compo_input_nodes_props) +
-        list(compo_output_nodes_props) +
-        list(compo_color_nodes_props) +
-        list(compo_converter_nodes_props) +
-        list(compo_filter_nodes_props) +
-        list(compo_vector_nodes_props) +
-        list(compo_matte_nodes_props) +
-        list(compo_distort_nodes_props) +
-        list(compo_layout_nodes_props) +
-        list(blender_mat_input_nodes_props) +
-        list(blender_mat_output_nodes_props) +
-        list(blender_mat_color_nodes_props) +
-        list(blender_mat_vector_nodes_props) +
-        list(blender_mat_converter_nodes_props) +
-        list(blender_mat_layout_nodes_props) +
-        list(texture_input_nodes_props) +
-        list(texture_output_nodes_props) +
-        list(texture_color_nodes_props) +
-        list(texture_pattern_nodes_props) +
-        list(texture_textures_nodes_props) +
-        list(texture_converter_nodes_props) +
-        list(texture_distort_nodes_props) +
-        list(texture_layout_nodes_props)
-    )
-
-    geo_to_type: StringProperty(
+    to_type: StringProperty(
         name="Switch to type",
         default = '',
     )
 
     def execute(self, context):
-        nodes, links = get_nodes_links(context)
         to_type = self.to_type
-        if self.geo_to_type != '':
-            to_type = self.geo_to_type
+        if len(to_type) == 0:
+            return {'CANCELLED'}
+
+        nodes, links = get_nodes_links(context)
         # Those types of nodes will not swap.
         src_excludes = ('NodeFrame')
         # Those attributes of nodes will be copied if possible
@@ -2511,8 +2021,8 @@ class NWMergeNodes(Operator, NWBase):
             mode = 'MIX'
         if (merge_type != 'MATH' and merge_type != 'GEOMETRY') and tree_type == 'GEOMETRY':
             merge_type = 'AUTO'
-        # The math nodes used for geometry nodes are of type 'ShaderNode'
-        if merge_type == 'MATH' and tree_type == 'GEOMETRY':
+        # The MixRGB node and math nodes used for geometry nodes are of type 'ShaderNode'
+        if (merge_type == 'MATH' or merge_type == 'MIX') and tree_type == 'GEOMETRY':
             node_type = 'ShaderNode'
         selected_mix = []  # entry = [index, loc]
         selected_shader = []  # entry = [index, loc]
@@ -2532,7 +2042,8 @@ class NWMergeNodes(Operator, NWBase):
                             ('VALUE', [t[0] for t in operations], selected_math),
                             ('VECTOR', [], selected_vector),
                     ):
-                        output_type = node.outputs[0].type
+                        output = get_first_enabled_output(node)
+                        output_type = output.type
                         valid_mode = mode in types_list
                         # When mode is 'MIX' we have to cheat since the mix node is not used in
                         # geometry nodes.
@@ -2583,7 +2094,7 @@ class NWMergeNodes(Operator, NWBase):
 
             # Change the node type for math nodes in a geometry node tree.
             if tree_type == 'GEOMETRY':
-                if nodes_list is selected_math or nodes_list is selected_vector:
+                if nodes_list is selected_math or nodes_list is selected_vector or nodes_list is selected_mix:
                     node_type = 'ShaderNode'
                     if mode == 'MIX':
                         mode = 'ADD'
@@ -2708,8 +2219,9 @@ class NWMergeNodes(Operator, NWBase):
             # Special case:
             # Two nodes were selected and first selected has no output links, second selected has output links.
             # Then add links from last add to all links 'to_socket' of out links of second selected.
+            first_selected_output = get_first_enabled_output(first_selected)
             if len(nodes_list) == 2:
-                if not first_selected.outputs[0].links:
+                if not first_selected_output.links:
                     second_selected = nodes[nodes_list[1][0]]
                     for ss_link in second_selected.outputs[0].links:
                         # Prevent cyclic dependencies when nodes to be merged are linked to one another.
@@ -2717,16 +2229,16 @@ class NWMergeNodes(Operator, NWBase):
                         if not self.link_creates_cycle(ss_link, invalid_nodes):
                             links.new(last_add.outputs[0], ss_link.to_socket)
             # add links from last_add to all links 'to_socket' of out links of first selected.
-            for fs_link in first_selected.outputs[0].links:
+            for fs_link in first_selected_output.links:
                 # Link only if "to_node" index not in invalid indexes list.
                 if not self.link_creates_cycle(fs_link, invalid_nodes):
                     links.new(last_add.outputs[0], fs_link.to_socket)
             # add link from "first" selected and "first" add node
             node_to = nodes[count_after - 1]
-            links.new(first_selected.outputs[0], node_to.inputs[first])
+            links.new(first_selected_output, node_to.inputs[first])
             if node_to.type == 'ZCOMBINE':
                 for fs_out in first_selected.outputs:
-                    if fs_out != first_selected.outputs[0] and fs_out.name in ('Z', 'Depth'):
+                    if fs_out != first_selected_output and fs_out.name in ('Z', 'Depth'):
                         links.new(fs_out, node_to.inputs[1])
                         break
             # add links between added ADD nodes and between selected and ADD nodes
@@ -2736,20 +2248,20 @@ class NWMergeNodes(Operator, NWBase):
                     node_to = nodes[index - 1]
                     node_to_input_i = first
                     node_to_z_i = 1  # if z combine - link z to first z input
-                    links.new(node_from.outputs[0], node_to.inputs[node_to_input_i])
+                    links.new(get_first_enabled_output(node_from), node_to.inputs[node_to_input_i])
                     if node_to.type == 'ZCOMBINE':
                         for from_out in node_from.outputs:
-                            if from_out != node_from.outputs[0] and from_out.name in ('Z', 'Depth'):
+                            if from_out != get_first_enabled_output(node_from) and from_out.name in ('Z', 'Depth'):
                                 links.new(from_out, node_to.inputs[node_to_z_i])
                 if len(nodes_list) > 1:
                     node_from = nodes[nodes_list[i + 1][0]]
                     node_to = nodes[index]
                     node_to_input_i = second
                     node_to_z_i = 3  # if z combine - link z to second z input
-                    links.new(node_from.outputs[0], node_to.inputs[node_to_input_i])
+                    links.new(get_first_enabled_output(node_from), node_to.inputs[node_to_input_i])
                     if node_to.type == 'ZCOMBINE':
                         for from_out in node_from.outputs:
-                            if from_out != node_from.outputs[0] and from_out.name in ('Z', 'Depth'):
+                            if from_out != get_first_enabled_output(node_from) and from_out.name in ('Z', 'Depth'):
                                 links.new(from_out, node_to.inputs[node_to_z_i])
                 index -= 1
             # set "last" of added nodes as active
@@ -3072,71 +2584,74 @@ class NWAddTextureSetup(Operator, NWBase):
 
     @classmethod
     def poll(cls, context):
-        valid = False
         if nw_check(context):
             space = context.space_data
             if space.tree_type == 'ShaderNodeTree':
-                valid = True
-        return valid
+                return True
+        return False
 
     def execute(self, context):
         nodes, links = get_nodes_links(context)
-        shader_types = [x[1] for x in shaders_shader_nodes_props if x[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
-        texture_types = [x[1] for x in shaders_texture_nodes_props]
+
+        texture_types = [x.nodetype for x in
+                         get_nodes_from_category('Texture', context)]
         selected_nodes = [n for n in nodes if n.select]
-        for t_node in selected_nodes:
-            valid = False
+
+        for node in selected_nodes:
+            if not node.inputs:
+                continue
+
             input_index = 0
-            if t_node.inputs:
-                for index, i in enumerate(t_node.inputs):
-                    if not i.is_linked:
-                        valid = True
-                        input_index = index
+            target_input = node.inputs[0]
+            for input in node.inputs:
+                if input.enabled:
+                    input_index += 1
+                    if not input.is_linked:
+                        target_input = input
                         break
-            if valid:
-                locx = t_node.location.x
-                locy = t_node.location.y - t_node.dimensions.y/2
-
-                xoffset = [500, 700]
-                is_texture = False
-                if t_node.type in texture_types + ['MAPPING']:
-                    xoffset = [290, 500]
-                    is_texture = True
-
-                coordout = 2
-                image_type = 'ShaderNodeTexImage'
-
-                if (t_node.type in texture_types and t_node.type != 'TEX_IMAGE') or (t_node.type == 'BACKGROUND'):
-                    coordout = 0  # image texture uses UVs, procedural textures and Background shader use Generated
-                    if t_node.type == 'BACKGROUND':
-                        image_type = 'ShaderNodeTexEnvironment'
-
-                if not is_texture:
-                    tex = nodes.new(image_type)
-                    tex.location = [locx - 200, locy + 112]
-                    nodes.active = tex
-                    links.new(tex.outputs[0], t_node.inputs[input_index])
-
-                t_node.select = False
-                if self.add_mapping or is_texture:
-                    if t_node.type != 'MAPPING':
-                        m = nodes.new('ShaderNodeMapping')
-                        m.location = [locx - xoffset[0], locy + 141]
-                        m.width = 240
-                    else:
-                        m = t_node
-                    coord = nodes.new('ShaderNodeTexCoord')
-                    coord.location = [locx - (200 if t_node.type == 'MAPPING' else xoffset[1]), locy + 124]
-
-                    if not is_texture:
-                        links.new(m.outputs[0], tex.inputs[0])
-                        links.new(coord.outputs[coordout], m.inputs[0])
-                    else:
-                        nodes.active = m
-                        links.new(m.outputs[0], t_node.inputs[input_index])
-                        links.new(coord.outputs[coordout], m.inputs[0])
             else:
-                self.report({'WARNING'}, "No free inputs for node: "+t_node.name)
+                self.report({'WARNING'}, "No free inputs for node: " + node.name)
+                continue
+
+            x_offset = 0
+            padding = 40.0
+            locx = node.location.x
+            locy = node.location.y - (input_index * padding)
+
+            is_texture_node = node.rna_type.identifier in texture_types
+            use_environment_texture = node.type == 'BACKGROUND'
+
+            # Add an image texture before normal shader nodes.
+            if not is_texture_node:
+                image_texture_type = 'ShaderNodeTexEnvironment' if use_environment_texture else 'ShaderNodeTexImage'
+                image_texture_node = nodes.new(image_texture_type)
+                x_offset = x_offset + image_texture_node.width + padding
+                image_texture_node.location = [locx - x_offset, locy]
+                nodes.active = image_texture_node
+                links.new(image_texture_node.outputs[0], target_input)
+
+                # The mapping setup following this will connect to the firrst input of this image texture.
+                target_input = image_texture_node.inputs[0]
+
+            node.select = False
+
+            if is_texture_node or self.add_mapping:
+                # Add Mapping node.
+                mapping_node = nodes.new('ShaderNodeMapping')
+                x_offset = x_offset + mapping_node.width + padding
+                mapping_node.location = [locx - x_offset, locy]
+                links.new(mapping_node.outputs[0], target_input)
+
+                # Add Texture Coordinates node.
+                tex_coord_node = nodes.new('ShaderNodeTexCoord')
+                x_offset = x_offset + tex_coord_node.width + padding
+                tex_coord_node.location = [locx - x_offset, locy]
+
+                is_procedural_texture = is_texture_node and node.type != 'TEX_IMAGE'
+                use_generated_coordinates = is_procedural_texture or use_environment_texture
+                tex_coord_output = tex_coord_node.outputs[0 if use_generated_coordinates else 2]
+                links.new(tex_coord_output, mapping_node.inputs[0])
+
         return {'FINISHED'}
 
 
@@ -3759,37 +3274,32 @@ class NWLinkToOutputNode(Operator):
     def execute(self, context):
         nodes, links = get_nodes_links(context)
         active = nodes.active
-        output_node = None
         output_index = None
         tree_type = context.space_data.tree_type
-        if tree_type == 'ShaderNodeTree':
-            output_types = [x[1] for x in shaders_output_nodes_props] + ['OUTPUT']
-        elif tree_type == 'CompositorNodeTree':
-            output_types = ['COMPOSITE']
-        elif tree_type == 'TextureNodeTree':
-            output_types = ['OUTPUT']
-        elif tree_type == 'GeometryNodeTree':
-            output_types = ['GROUP_OUTPUT']
+        shader_outputs = {'OBJECT':    'ShaderNodeOutputMaterial',
+                          'WORLD':     'ShaderNodeOutputWorld',
+                          'LINESTYLE': 'ShaderNodeOutputLineStyle'}
+        output_type = {
+            'ShaderNodeTree': shader_outputs[context.space_data.shader_type],
+            'CompositorNodeTree': 'CompositorNodeComposite',
+            'TextureNodeTree': 'TextureNodeOutput',
+            'GeometryNodeTree': 'NodeGroupOutput',
+        }[tree_type]
         for node in nodes:
-            if node.type in output_types:
+            # check whether the node is an output node and,
+            # if supported, whether it's the active one
+            if node.rna_type.identifier == output_type \
+               and (node.is_active_output if hasattr(node, 'is_active_output')
+                    else True):
                 output_node = node
                 break
-        if not output_node:
+        else:  # No output node exists
             bpy.ops.node.select_all(action="DESELECT")
-            if tree_type == 'ShaderNodeTree':
-                if context.space_data.shader_type == 'OBJECT':
-                    output_node = nodes.new('ShaderNodeOutputMaterial')
-                elif context.space_data.shader_type == 'WORLD':
-                    output_node = nodes.new('ShaderNodeOutputWorld')
-            elif tree_type == 'CompositorNodeTree':
-                output_node = nodes.new('CompositorNodeComposite')
-            elif tree_type == 'TextureNodeTree':
-                output_node = nodes.new('TextureNodeOutput')
-            elif tree_type == 'GeometryNodeTree':
-                output_node = nodes.new('NodeGroupOutput')
+            output_node = nodes.new(output_type)
             output_node.location.x = active.location.x + active.dimensions.x + 80
             output_node.location.y = active.location.y
-        if (output_node and active.outputs):
+
+        if active.outputs:
             for i, output in enumerate(active.outputs):
                 if is_visible_socket(output):
                     output_index = i
@@ -3803,7 +3313,7 @@ class NWLinkToOutputNode(Operator):
             if tree_type == 'ShaderNodeTree':
                 if active.outputs[output_index].name == 'Volume':
                     out_input_index = 1
-                elif active.outputs[output_index].type != 'SHADER':  # connect to displacement if not a shader
+                elif active.outputs[output_index].name == 'Displacement':
                     out_input_index = 2
             elif tree_type == 'GeometryNodeTree':
                 if active.outputs[output_index].type != 'GEOMETRY':
@@ -4417,12 +3927,10 @@ class NWConnectionListOutputs(Menu, NWBase):
         nodes, links = get_nodes_links(context)
 
         n1 = nodes[context.scene.NWLazySource]
-        index=0
-        for o in n1.outputs:
+        for index, output in enumerate(n1.outputs):
             # Only show sockets that are exposed.
-            if o.enabled:
-                layout.operator(NWCallInputsMenu.bl_idname, text=o.name, icon="RADIOBUT_OFF").from_socket=index
-            index+=1
+            if output.enabled:
+                layout.operator(NWCallInputsMenu.bl_idname, text=output.name, icon="RADIOBUT_OFF").from_socket=index
 
 
 class NWConnectionListInputs(Menu, NWBase):
@@ -4435,17 +3943,15 @@ class NWConnectionListInputs(Menu, NWBase):
 
         n2 = nodes[context.scene.NWLazyTarget]
 
-        index = 0
-        for i in n2.inputs:
+        for index, input in enumerate(n2.inputs):
             # Only show sockets that are exposed.
             # This prevents, for example, the scale value socket
             # of the vector math node being added to the list when
             # the mode is not 'SCALE'.
-            if i.enabled:
-                op = layout.operator(NWMakeLink.bl_idname, text=i.name, icon="FORWARD")
+            if input.enabled:
+                op = layout.operator(NWMakeLink.bl_idname, text=input.name, icon="FORWARD")
                 op.from_socket = context.scene.NWSourceSocket
                 op.to_socket = index
-                index+=1
 
 
 class NWMergeMathMenu(Menu, NWBase):
@@ -4628,391 +4134,17 @@ class NWSwitchNodeTypeMenu(Menu, NWBase):
 
     def draw(self, context):
         layout = self.layout
-        tree = context.space_data.node_tree
-        if tree.type == 'SHADER':
-            layout.menu(NWSwitchShadersInputSubmenu.bl_idname)
-            layout.menu(NWSwitchShadersOutputSubmenu.bl_idname)
-            layout.menu(NWSwitchShadersShaderSubmenu.bl_idname)
-            layout.menu(NWSwitchShadersTextureSubmenu.bl_idname)
-            layout.menu(NWSwitchShadersColorSubmenu.bl_idname)
-            layout.menu(NWSwitchShadersVectorSubmenu.bl_idname)
-            layout.menu(NWSwitchShadersConverterSubmenu.bl_idname)
-            layout.menu(NWSwitchShadersLayoutSubmenu.bl_idname)
-        if tree.type == 'COMPOSITING':
-            layout.menu(NWSwitchCompoInputSubmenu.bl_idname)
-            layout.menu(NWSwitchCompoOutputSubmenu.bl_idname)
-            layout.menu(NWSwitchCompoColorSubmenu.bl_idname)
-            layout.menu(NWSwitchCompoConverterSubmenu.bl_idname)
-            layout.menu(NWSwitchCompoFilterSubmenu.bl_idname)
-            layout.menu(NWSwitchCompoVectorSubmenu.bl_idname)
-            layout.menu(NWSwitchCompoMatteSubmenu.bl_idname)
-            layout.menu(NWSwitchCompoDistortSubmenu.bl_idname)
-            layout.menu(NWSwitchCompoLayoutSubmenu.bl_idname)
-        if tree.type == 'TEXTURE':
-            layout.menu(NWSwitchTexInputSubmenu.bl_idname)
-            layout.menu(NWSwitchTexOutputSubmenu.bl_idname)
-            layout.menu(NWSwitchTexColorSubmenu.bl_idname)
-            layout.menu(NWSwitchTexPatternSubmenu.bl_idname)
-            layout.menu(NWSwitchTexTexturesSubmenu.bl_idname)
-            layout.menu(NWSwitchTexConverterSubmenu.bl_idname)
-            layout.menu(NWSwitchTexDistortSubmenu.bl_idname)
-            layout.menu(NWSwitchTexLayoutSubmenu.bl_idname)
-        if tree.type == 'GEOMETRY':
-            categories = [c for c in node_categories_iter(context)
+        categories = [c for c in node_categories_iter(context)
                       if c.name not in ['Group', 'Script']]
-            for cat in categories:
-                idname = f"NODE_MT_nw_switch_{cat.identifier}_submenu"
-                if hasattr(bpy.types, idname):
-                    layout.menu(idname)
-                else:
-                    layout.label(text="Unable to load altered node lists.")
-                    layout.label(text="Please re-enable Node Wrangler.")
-                    break
-
-
-class NWSwitchShadersInputSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_shaders_input_submenu"
-    bl_label = "Input"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in shaders_input_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchShadersOutputSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_shaders_output_submenu"
-    bl_label = "Output"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in shaders_output_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchShadersShaderSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_shaders_shader_submenu"
-    bl_label = "Shader"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in shaders_shader_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchShadersTextureSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_shaders_texture_submenu"
-    bl_label = "Texture"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in shaders_texture_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchShadersColorSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_shaders_color_submenu"
-    bl_label = "Color"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in shaders_color_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchShadersVectorSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_shaders_vector_submenu"
-    bl_label = "Vector"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in shaders_vector_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchShadersConverterSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_shaders_converter_submenu"
-    bl_label = "Converter"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in shaders_converter_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchShadersLayoutSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_shaders_layout_submenu"
-    bl_label = "Layout"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in shaders_layout_nodes_props:
-            if node_type != 'FRAME':
-                props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-                props.to_type = ident
-
-
-class NWSwitchCompoInputSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_compo_input_submenu"
-    bl_label = "Input"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in compo_input_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchCompoOutputSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_compo_output_submenu"
-    bl_label = "Output"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in compo_output_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchCompoColorSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_compo_color_submenu"
-    bl_label = "Color"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in compo_color_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchCompoConverterSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_compo_converter_submenu"
-    bl_label = "Converter"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in compo_converter_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchCompoFilterSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_compo_filter_submenu"
-    bl_label = "Filter"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in compo_filter_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchCompoVectorSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_compo_vector_submenu"
-    bl_label = "Vector"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in compo_vector_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchCompoMatteSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_compo_matte_submenu"
-    bl_label = "Matte"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in compo_matte_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchCompoDistortSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_compo_distort_submenu"
-    bl_label = "Distort"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in compo_distort_nodes_props:
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchCompoLayoutSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_compo_layout_submenu"
-    bl_label = "Layout"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in compo_layout_nodes_props:
-            if node_type != 'FRAME':
-                props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-                props.to_type = ident
-
-
-class NWSwitchMatInputSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_mat_input_submenu"
-    bl_label = "Input"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(blender_mat_input_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchMatOutputSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_mat_output_submenu"
-    bl_label = "Output"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(blender_mat_output_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchMatColorSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_mat_color_submenu"
-    bl_label = "Color"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(blender_mat_color_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchMatVectorSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_mat_vector_submenu"
-    bl_label = "Vector"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(blender_mat_vector_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchMatConverterSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_mat_converter_submenu"
-    bl_label = "Converter"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(blender_mat_converter_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchMatLayoutSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_mat_layout_submenu"
-    bl_label = "Layout"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(blender_mat_layout_nodes_props, key=lambda k: k[2]):
-            if node_type != 'FRAME':
-                props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-                props.to_type = ident
-
-
-class NWSwitchTexInputSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_tex_input_submenu"
-    bl_label = "Input"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(texture_input_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchTexOutputSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_tex_output_submenu"
-    bl_label = "Output"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(texture_output_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchTexColorSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_tex_color_submenu"
-    bl_label = "Color"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(texture_color_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchTexPatternSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_tex_pattern_submenu"
-    bl_label = "Pattern"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(texture_pattern_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchTexTexturesSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_tex_textures_submenu"
-    bl_label = "Textures"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(texture_textures_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchTexConverterSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_tex_converter_submenu"
-    bl_label = "Converter"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(texture_converter_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchTexDistortSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_tex_distort_submenu"
-    bl_label = "Distort"
-
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(texture_distort_nodes_props, key=lambda k: k[2]):
-            props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-            props.to_type = ident
-
-
-class NWSwitchTexLayoutSubmenu(Menu, NWBase):
-    bl_idname = "NODE_MT_nw_switch_tex_layout_submenu"
-    bl_label = "Layout"
+        for cat in categories:
+            idname = f"NODE_MT_nw_switch_{cat.identifier}_submenu"
+            if hasattr(bpy.types, idname):
+                layout.menu(idname)
+            else:
+                layout.label(text="Unable to load altered node lists.")
+                layout.label(text="Please re-enable Node Wrangler.")
+                break
 
-    def draw(self, context):
-        layout = self.layout
-        for ident, node_type, rna_name in sorted(texture_layout_nodes_props, key=lambda k: k[2]):
-            if node_type != 'FRAME':
-                props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
-                props.to_type = ident
 
 def draw_switch_category_submenu(self, context):
     layout = self.layout
@@ -5027,7 +4159,7 @@ def draw_switch_category_submenu(self, context):
                 node.draw(self, layout, context)
                 continue
             props = layout.operator(NWSwitchNodeType.bl_idname, text=node.label)
-            props.geo_to_type = node.nodetype
+            props.to_type = node.nodetype
 
 #
 #  APPENDAGES TO EXISTING UI
@@ -5071,12 +4203,12 @@ def reset_nodes_button(self, context):
     node_ignore = ["FRAME","REROUTE", "GROUP"]
 
     # Check if active node is in the selection and respective type
-    if (len(node_selected) == 1) and node_active.select and node_active.type not in node_ignore:
+    if (len(node_selected) == 1) and node_active and node_active.select and node_active.type not in node_ignore:
         row = self.layout.row()
         row.operator("node.nw_reset_nodes", text="Reset Node", icon="FILE_REFRESH")
         self.layout.separator()
 
-    elif (len(node_selected) == 1) and node_active.select and node_active.type == "FRAME":
+    elif (len(node_selected) == 1) and node_active and node_active.select and node_active.type == "FRAME":
         row = self.layout.row()
         row.operator("node.nw_reset_nodes", text="Reset Nodes in Frame", icon="FILE_REFRESH")
         self.layout.separator()
@@ -5245,8 +4377,8 @@ kmi_defs = (
     (NWDeleteUnused.bl_idname, 'X', 'PRESS', False, False, True, None, "Delete unused nodes"),
     # Frame Selected
     (NWFrameSelected.bl_idname, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
-    # Swap Outputs
-    (NWSwapLinks.bl_idname, 'S', 'PRESS', False, False, True, None, "Swap Outputs"),
+    # Swap Links
+    (NWSwapLinks.bl_idname, 'S', 'PRESS', False, False, True, None, "Swap Links"),
     # Preview Node
     (NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', True, True, False, (('run_in_geometry_nodes', False),), "Preview node output"),
     (NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', False, True, True, (('run_in_geometry_nodes', True),), "Preview node output"),
@@ -5330,37 +4462,6 @@ classes = (
     NWLinkUseOutputsNamesMenu,
     NWAttributeMenu,
     NWSwitchNodeTypeMenu,
-    NWSwitchShadersInputSubmenu,
-    NWSwitchShadersOutputSubmenu,
-    NWSwitchShadersShaderSubmenu,
-    NWSwitchShadersTextureSubmenu,
-    NWSwitchShadersColorSubmenu,
-    NWSwitchShadersVectorSubmenu,
-    NWSwitchShadersConverterSubmenu,
-    NWSwitchShadersLayoutSubmenu,
-    NWSwitchCompoInputSubmenu,
-    NWSwitchCompoOutputSubmenu,
-    NWSwitchCompoColorSubmenu,
-    NWSwitchCompoConverterSubmenu,
-    NWSwitchCompoFilterSubmenu,
-    NWSwitchCompoVectorSubmenu,
-    NWSwitchCompoMatteSubmenu,
-    NWSwitchCompoDistortSubmenu,
-    NWSwitchCompoLayoutSubmenu,
-    NWSwitchMatInputSubmenu,
-    NWSwitchMatOutputSubmenu,
-    NWSwitchMatColorSubmenu,
-    NWSwitchMatVectorSubmenu,
-    NWSwitchMatConverterSubmenu,
-    NWSwitchMatLayoutSubmenu,
-    NWSwitchTexInputSubmenu,
-    NWSwitchTexOutputSubmenu,
-    NWSwitchTexColorSubmenu,
-    NWSwitchTexPatternSubmenu,
-    NWSwitchTexTexturesSubmenu,
-    NWSwitchTexConverterSubmenu,
-    NWSwitchTexDistortSubmenu,
-    NWSwitchTexLayoutSubmenu,
 )
 
 def register():
@@ -5417,7 +4518,7 @@ def register():
     # switch submenus
     switch_category_menus.clear()
     for cat in node_categories_iter(None):
-        if cat.name not in ['Group', 'Script'] and cat.identifier.startswith('GEO'):
+        if cat.name not in ['Group', 'Script']:
             idname = f"NODE_MT_nw_switch_{cat.identifier}_submenu"
             switch_category_type = type(idname, (bpy.types.Menu,), {
                 "bl_space_type": 'NODE_EDITOR',
diff --git a/object_boolean_tools.py b/object_boolean_tools.py
index f95f19ccc641f26cd091291543128fb0bc0ad332..149433cadc8d156b2b208f53698516dabb57ea5a 100644
--- a/object_boolean_tools.py
+++ b/object_boolean_tools.py
@@ -1090,7 +1090,7 @@ class PREFS_BoolTool_Props(AddonPreferences):
         "for a custom version that can optimize the visualization of Brushes",
     )
     use_wire: BoolProperty(
-        name="Display As Wirewrame",
+        name="Display As Wireframe",
         description="Display brush as wireframe instead of bounding box",
     )
     category: StringProperty(
diff --git a/object_collection_manager/__init__.py b/object_collection_manager/__init__.py
index fd53b291fb7b4939bdeb861bba465018e0b718b2..7467fcbd001b9a71ac902392e637017f4a489e52 100644
--- a/object_collection_manager/__init__.py
+++ b/object_collection_manager/__init__.py
@@ -6,7 +6,7 @@ bl_info = {
     "name": "Collection Manager",
     "description": "Manage collections and their objects",
     "author": "Ryan Inch",
-    "version": (2, 24, 1),
+    "version": (2, 24, 4),
     "blender": (2, 80, 0),
     "location": "View3D - Object Mode (Shortcut - M)",
     "warning": '',  # used for warning icon and text in addons panel
diff --git a/object_collection_manager/cm_init.py b/object_collection_manager/cm_init.py
index 38080cb883d606562c11414c18f72cbc52167b3a..679d8d3e2e58c8ec651c9af74e088b283ddbe1f0 100644
--- a/object_collection_manager/cm_init.py
+++ b/object_collection_manager/cm_init.py
@@ -144,6 +144,10 @@ def disable_objects_menu_addition(self, context):
 
 
 def register_disable_objects_hotkeys():
+    if addon_disable_objects_hotkey_keymaps:
+        # guard to handle default value updates (mouse hover + backspace)
+        return
+
     wm = bpy.context.window_manager
     if wm.keyconfigs.addon: # not present when started with --background
         km = wm.keyconfigs.addon.keymaps.new(name='Object Mode')
diff --git a/object_collection_manager/operator_utils.py b/object_collection_manager/operator_utils.py
index 51b4385d551b24f368ceb0003f71a2f39c8cefbf..4394cf3a18cf33603d21fc8884a839e12ddf008f 100644
--- a/object_collection_manager/operator_utils.py
+++ b/object_collection_manager/operator_utils.py
@@ -604,10 +604,10 @@ def select_collection_objects(is_master_collection, collection_name, replace, ne
     if replace:
         bpy.ops.object.select_all(action='DESELECT')
 
-    def select_objects(collection, selection_state):
-        if selection_state == None:
-            selection_state = get_move_selection().isdisjoint(collection.objects)
+    if selection_state == None:
+        selection_state = get_move_selection().isdisjoint(target_collection.objects)
 
+    def select_objects(collection, selection_state):
         for obj in collection.objects:
             try:
                 obj.select_set(selection_state)
diff --git a/object_collection_manager/qcd_init.py b/object_collection_manager/qcd_init.py
index 1273e6e03c0173aaa1cd450420c5e346b2d0bf38..6d4ac7e7cff2d83b893b7f4e2a69c56497f77340 100644
--- a/object_collection_manager/qcd_init.py
+++ b/object_collection_manager/qcd_init.py
@@ -27,6 +27,7 @@ from bpy.app.handlers import persistent
 addon_qcd_keymaps = []
 addon_qcd_view_hotkey_keymaps = []
 addon_qcd_view_edit_mode_hotkey_keymaps = []
+qcd_registered = False
 
 
 qcd_classes = (
@@ -76,6 +77,11 @@ def load_pre_handler(dummy):
 
 
 def register_qcd():
+    global qcd_registered
+    if qcd_registered:
+        # guard to handle default value updates (mouse hover + backspace)
+        return
+
     for cls in qcd_classes:
         bpy.utils.register_class(cls)
 
@@ -107,8 +113,14 @@ def register_qcd():
     if prefs.enable_qcd_3dview_header_widget:
         register_qcd_3dview_header_widget()
 
+    qcd_registered = True
+
 
 def register_qcd_view_hotkeys():
+    if addon_qcd_view_hotkey_keymaps:
+        # guard to handle default value updates (mouse hover + backspace)
+        return
+
     wm = bpy.context.window_manager
     if wm.keyconfigs.addon: # not present when started with --background
         # create qcd hotkeys
@@ -135,8 +147,8 @@ def register_qcd_view_hotkeys():
             ["ZERO", True, "20"],
         ]
 
-        for key in qcd_hotkeys:
-            for mode in ['Object Mode', 'Pose', 'Weight Paint']:
+        for mode in ['Object Mode', 'Pose', 'Weight Paint']:
+            for key in qcd_hotkeys:
                 km = wm.keyconfigs.addon.keymaps.new(name=mode)
                 kmi = km.keymap_items.new('view3d.view_qcd_slot', key[0], 'PRESS', alt=key[1])
                 kmi.properties.slot = key[2]
@@ -149,37 +161,41 @@ def register_qcd_view_hotkeys():
                 kmi.properties.toggle = True
                 addon_qcd_view_hotkey_keymaps.append((km, kmi))
 
-                km = wm.keyconfigs.addon.keymaps.new(name=mode)
-                kmi = km.keymap_items.new('view3d.enable_all_qcd_slots', 'PLUS', 'PRESS', shift=True)
-                addon_qcd_view_hotkey_keymaps.append((km, kmi))
+            km = wm.keyconfigs.addon.keymaps.new(name=mode)
+            kmi = km.keymap_items.new('view3d.enable_all_qcd_slots', 'PLUS', 'PRESS', shift=True)
+            addon_qcd_view_hotkey_keymaps.append((km, kmi))
 
-                km = wm.keyconfigs.addon.keymaps.new(name=mode)
-                kmi = km.keymap_items.new('view3d.isolate_selected_objects_collections', 'EQUAL', 'PRESS')
-                addon_qcd_view_hotkey_keymaps.append((km, kmi))
+            km = wm.keyconfigs.addon.keymaps.new(name=mode)
+            kmi = km.keymap_items.new('view3d.isolate_selected_objects_collections', 'EQUAL', 'PRESS')
+            addon_qcd_view_hotkey_keymaps.append((km, kmi))
 
-                km = wm.keyconfigs.addon.keymaps.new(name=mode)
-                kmi = km.keymap_items.new('view3d.disable_selected_objects_collections', 'MINUS', 'PRESS')
-                addon_qcd_view_hotkey_keymaps.append((km, kmi))
+            km = wm.keyconfigs.addon.keymaps.new(name=mode)
+            kmi = km.keymap_items.new('view3d.disable_selected_objects_collections', 'MINUS', 'PRESS')
+            addon_qcd_view_hotkey_keymaps.append((km, kmi))
 
-                km = wm.keyconfigs.addon.keymaps.new(name=mode)
-                kmi = km.keymap_items.new('view3d.disable_all_non_qcd_slots', 'PLUS', 'PRESS', shift=True, alt=True)
-                addon_qcd_view_hotkey_keymaps.append((km, kmi))
+            km = wm.keyconfigs.addon.keymaps.new(name=mode)
+            kmi = km.keymap_items.new('view3d.disable_all_non_qcd_slots', 'PLUS', 'PRESS', shift=True, alt=True)
+            addon_qcd_view_hotkey_keymaps.append((km, kmi))
 
-                km = wm.keyconfigs.addon.keymaps.new(name=mode)
-                kmi = km.keymap_items.new('view3d.disable_all_collections', 'EQUAL', 'PRESS', alt=True, ctrl=True)
-                addon_qcd_view_hotkey_keymaps.append((km, kmi))
+            km = wm.keyconfigs.addon.keymaps.new(name=mode)
+            kmi = km.keymap_items.new('view3d.disable_all_collections', 'EQUAL', 'PRESS', alt=True, ctrl=True)
+            addon_qcd_view_hotkey_keymaps.append((km, kmi))
 
-                km = wm.keyconfigs.addon.keymaps.new(name=mode)
-                kmi = km.keymap_items.new('view3d.select_all_qcd_objects', 'PLUS', 'PRESS', shift=True, ctrl=True)
-                addon_qcd_view_hotkey_keymaps.append((km, kmi))
+            km = wm.keyconfigs.addon.keymaps.new(name=mode)
+            kmi = km.keymap_items.new('view3d.select_all_qcd_objects', 'PLUS', 'PRESS', shift=True, ctrl=True)
+            addon_qcd_view_hotkey_keymaps.append((km, kmi))
 
 
-                km = wm.keyconfigs.addon.keymaps.new(name=mode)
-                kmi = km.keymap_items.new('view3d.discard_qcd_history', 'EQUAL', 'PRESS', alt=True)
-                addon_qcd_view_hotkey_keymaps.append((km, kmi))
+            km = wm.keyconfigs.addon.keymaps.new(name=mode)
+            kmi = km.keymap_items.new('view3d.discard_qcd_history', 'EQUAL', 'PRESS', alt=True)
+            addon_qcd_view_hotkey_keymaps.append((km, kmi))
 
 
 def register_qcd_view_edit_mode_hotkeys():
+    if addon_qcd_view_edit_mode_hotkey_keymaps:
+        # guard to handle default value updates (mouse hover + backspace)
+        return
+
     wm = bpy.context.window_manager
     if wm.keyconfigs.addon: # not present when started with --background
         # create qcd hotkeys
@@ -248,12 +264,22 @@ def register_qcd_view_edit_mode_hotkeys():
 
 
 def register_qcd_3dview_header_widget():
+    # unregister first to guard against default value updates (mouse hover + backspace)
+    # if the widget isn't registered it will just do nothing
+    unregister_qcd_3dview_header_widget()
+
+    # add the widget to the header, and an update function to the top bar to get view layer changes
     bpy.types.VIEW3D_HT_header.append(ui.view3d_header_qcd_slots)
     bpy.types.TOPBAR_HT_upper_bar.append(ui.view_layer_update)
 
 
 
 def unregister_qcd():
+    global qcd_registered
+    if not qcd_registered:
+        # guard to handle default value updates (mouse hover + backspace)
+        return
+
     unregister_qcd_3dview_header_widget()
 
     for cls in qcd_classes:
@@ -279,6 +305,8 @@ def unregister_qcd():
 
     unregister_qcd_view_edit_mode_hotkeys()
 
+    qcd_registered = False
+
 
 def unregister_qcd_view_hotkeys():
     # remove keymaps when qcd view hotkeys are deactivated
diff --git a/object_fracture_cell/__init__.py b/object_fracture_cell/__init__.py
index e9f70a446bc3853fff36c3265a69ae4e935bb436..543f86f132a04d776b703975ae7972e1b1245fab 100644
--- a/object_fracture_cell/__init__.py
+++ b/object_fracture_cell/__init__.py
@@ -482,7 +482,7 @@ class FractureCell(Operator):
         rowsub.prop(self, "use_data_match")
         rowsub = col.row()
 
-        # on same row for even layout but infact are not all that related
+        # on same row for even layout but in fact are not all that related
         rowsub.prop(self, "material_index")
         rowsub.prop(self, "use_interior_vgroup")
 
diff --git a/object_fracture_cell/fracture_cell_setup.py b/object_fracture_cell/fracture_cell_setup.py
index 8171e44e462ff0b7b5422588d6dc5cfe97e7d5a9..2e735495f4e4df3cc07784c9ed2481c9e624dfcf 100644
--- a/object_fracture_cell/fracture_cell_setup.py
+++ b/object_fracture_cell/fracture_cell_setup.py
@@ -145,7 +145,7 @@ def cell_fracture_objects(
         random.shuffle(points)
         points[source_limit:] = []
 
-    # saddly we cant be sure there are no doubles
+    # sadly we can't be sure there are no doubles
     from mathutils import Vector
     to_tuple = Vector.to_tuple
     points = list({to_tuple(p, 4): p for p in points}.values())
diff --git a/object_print3d_utils/export.py b/object_print3d_utils/export.py
index aec1973274aab10bf1e88470d228126fc41c8137..11ce5e00c350135d796eeabce70eed8668031b5e 100644
--- a/object_print3d_utils/export.py
+++ b/object_print3d_utils/export.py
@@ -5,6 +5,8 @@
 
 import bpy
 
+from bpy.app.translations import pgettext_tip as tip_
+
 
 def image_get(mat):
     from bpy_extras import node_shader_utils
@@ -79,7 +81,7 @@ def write_mesh(context, report_cb):
     # first ensure the path is created
     if export_path:
         # this can fail with strange errors,
-        # if the dir cant be made then we get an error later.
+        # if the dir can't be made then we get an error later.
         try:
             os.makedirs(export_path, exist_ok=True)
         except:
@@ -132,17 +134,17 @@ def write_mesh(context, report_cb):
             use_normals=export_data_layers,
         )
     elif export_format == 'OBJ':
-        addon_ensure("io_scene_obj")
         filepath = bpy.path.ensure_ext(filepath, ".obj")
-        ret = bpy.ops.export_scene.obj(
+        ret = bpy.ops.wm.obj_export(
             filepath=filepath,
-            use_mesh_modifiers=True,
-            use_selection=True,
-            global_scale=global_scale,
+            apply_modifiers=True,
+            export_selected_objects=True,
+            scaling_factor=global_scale,
             path_mode=path_mode,
-            use_normals=export_data_layers,
-            use_uvs=export_data_layers,
-            use_materials=export_data_layers,
+            export_normals=export_data_layers,
+            export_uv=export_data_layers,
+            export_materials=export_data_layers,
+            export_colors=export_data_layers,
         )
     else:
         assert 0
@@ -153,7 +155,7 @@ def write_mesh(context, report_cb):
 
     if 'FINISHED' in ret:
         if report_cb is not None:
-            report_cb({'INFO'}, f"Exported: {filepath!r}")
+            report_cb({'INFO'}, tip_("Exported: {!r}").format(filepath))
 
         return True
 
diff --git a/object_print3d_utils/mesh_helpers.py b/object_print3d_utils/mesh_helpers.py
index 7d23a0786a48f2749b27ee1c5f906baa7ed7787c..444df1f11069e1dbf93950355d8a0f4cc78fa0f0 100644
--- a/object_print3d_utils/mesh_helpers.py
+++ b/object_print3d_utils/mesh_helpers.py
@@ -32,7 +32,13 @@ def bmesh_copy_from_object(obj, transform=True, triangulate=True, apply_modifier
     # would save ram
 
     if transform:
-        bm.transform(obj.matrix_world)
+        matrix = obj.matrix_world.copy()
+        if not matrix.is_identity:
+            bm.transform(matrix)
+            # Update normals if the matrix has no rotation.
+            matrix.translation.zero()
+            if not matrix.is_identity:
+                bm.normal_update()
 
     if triangulate:
         bmesh.ops.triangulate(bm, faces=bm.faces)
diff --git a/object_print3d_utils/operators.py b/object_print3d_utils/operators.py
index 85f268e7db5c1708f949cb1da6867945f9674e45..d47aa84afd7fd92e9a2e9041ac7376edca227391 100644
--- a/object_print3d_utils/operators.py
+++ b/object_print3d_utils/operators.py
@@ -13,6 +13,8 @@ from bpy.props import (
 )
 import bmesh
 
+from bpy.app.translations import pgettext_tip as tip_
+
 from . import report
 
 
@@ -87,7 +89,7 @@ class MESH_OT_print3d_info_volume(Operator):
             volume_str = clean_float(volume_unit, 4)
             volume_fmt = f"{volume_str} {symbol}"
 
-        report.update((f"Volume: {volume_fmt}³", None))
+        report.update((tip_("Volume: {}³").format(volume_fmt), None))
 
         return {'FINISHED'}
 
@@ -118,7 +120,7 @@ class MESH_OT_print3d_info_area(Operator):
             area_str = clean_float(area_unit, 4)
             area_fmt = f"{area_str} {symbol}"
 
-        report.update((f"Area: {area_fmt}²", None))
+        report.update((tip_("Area: {}²").format(area_fmt), None))
 
         return {'FINISHED'}
 
@@ -161,8 +163,12 @@ class MESH_OT_print3d_check_solid(Operator):
             (i for i, ele in enumerate(bm.edges) if ele.is_manifold and (not ele.is_contiguous)),
         )
 
-        info.append((f"Non Manifold Edge: {len(edges_non_manifold)}", (bmesh.types.BMEdge, edges_non_manifold)))
-        info.append((f"Bad Contig. Edges: {len(edges_non_contig)}", (bmesh.types.BMEdge, edges_non_contig)))
+        info.append(
+            (tip_("Non Manifold Edge: {}").format(
+                len(edges_non_manifold)),
+                (bmesh.types.BMEdge,
+                 edges_non_manifold)))
+        info.append((tip_("Bad Contig. Edges: {}").format(len(edges_non_contig)), (bmesh.types.BMEdge, edges_non_contig)))
 
         bm.free()
 
@@ -180,7 +186,7 @@ class MESH_OT_print3d_check_intersections(Operator):
         from . import mesh_helpers
 
         faces_intersect = mesh_helpers.bmesh_check_self_intersect_object(obj)
-        info.append((f"Intersect Face: {len(faces_intersect)}", (bmesh.types.BMFace, faces_intersect)))
+        info.append((tip_("Intersect Face: {}").format(len(faces_intersect)), (bmesh.types.BMFace, faces_intersect)))
 
     def execute(self, context):
         return execute_check(self, context)
@@ -208,8 +214,8 @@ class MESH_OT_print3d_check_degenerate(Operator):
         faces_zero = array.array('i', (i for i, ele in enumerate(bm.faces) if ele.calc_area() <= threshold))
         edges_zero = array.array('i', (i for i, ele in enumerate(bm.edges) if ele.calc_length() <= threshold))
 
-        info.append((f"Zero Faces: {len(faces_zero)}", (bmesh.types.BMFace, faces_zero)))
-        info.append((f"Zero Edges: {len(edges_zero)}", (bmesh.types.BMEdge, edges_zero)))
+        info.append((tip_("Zero Faces: {}").format(len(faces_zero)), (bmesh.types.BMFace, faces_zero)))
+        info.append((tip_("Zero Edges: {}").format(len(edges_zero)), (bmesh.types.BMEdge, edges_zero)))
 
         bm.free()
 
@@ -239,7 +245,7 @@ class MESH_OT_print3d_check_distorted(Operator):
             (i for i, ele in enumerate(bm.faces) if mesh_helpers.face_is_distorted(ele, angle_distort))
         )
 
-        info.append((f"Non-Flat Faces: {len(faces_distort)}", (bmesh.types.BMFace, faces_distort)))
+        info.append((tip_("Non-Flat Faces: {}").format(len(faces_distort)), (bmesh.types.BMFace, faces_distort)))
 
         bm.free()
 
@@ -263,7 +269,7 @@ class MESH_OT_print3d_check_thick(Operator):
         print_3d = scene.print_3d
 
         faces_error = mesh_helpers.bmesh_check_thick_object(obj, print_3d.thickness_min)
-        info.append((f"Thin Faces: {len(faces_error)}", (bmesh.types.BMFace, faces_error)))
+        info.append((tip_("Thin Faces: {}").format(len(faces_error)), (bmesh.types.BMFace, faces_error)))
 
     def execute(self, context):
         return execute_check(self, context)
@@ -290,7 +296,7 @@ class MESH_OT_print3d_check_sharp(Operator):
             if ele.is_manifold and ele.calc_face_angle_signed() > angle_sharp
         ]
 
-        info.append((f"Sharp Edge: {len(edges_sharp)}", (bmesh.types.BMEdge, edges_sharp)))
+        info.append((tip_("Sharp Edge: {}").format(len(edges_sharp)), (bmesh.types.BMEdge, edges_sharp)))
         bm.free()
 
     def execute(self, context):
@@ -327,7 +333,7 @@ class MESH_OT_print3d_check_overhang(Operator):
             if z_down_angle(ele.normal, 4.0) < angle_overhang
         ]
 
-        info.append((f"Overhang Face: {len(faces_overhang)}", (bmesh.types.BMFace, faces_overhang)))
+        info.append((tip_("Overhang Face: {}").format(len(faces_overhang)), (bmesh.types.BMFace, faces_overhang)))
         bm.free()
 
     def execute(self, context):
@@ -390,7 +396,7 @@ class MESH_OT_print3d_clean_distorted(Operator):
             bmesh.ops.triangulate(bm, faces=elems_triangulate)
             mesh_helpers.bmesh_to_object(obj, bm)
 
-        self.report({'INFO'}, f"Triangulated {len(elems_triangulate)} faces")
+        self.report({'INFO'}, tip_("Triangulated {} faces").format(len(elems_triangulate)))
 
         return {'FINISHED'}
 
@@ -441,7 +447,7 @@ class MESH_OT_print3d_clean_non_manifold(Operator):
         edges = bm_key[1] - bm_key_orig[1]
         faces = bm_key[2] - bm_key_orig[2]
 
-        self.report({'INFO'}, f"Modified: {verts:+} vertices, {edges:+} edges, {faces:+} faces")
+        self.report({'INFO'}, tip_("Modified: {:+} vertices, {:+} edges, {:+} faces").format(verts, edges, faces))
 
         return {'FINISHED'}
 
@@ -616,7 +622,7 @@ def _scale(scale, report=None, report_suffix=""):
         bpy.ops.transform.resize(value=(scale,) * 3)
     if report is not None:
         scale_fmt = clean_float(scale, 6)
-        report({'INFO'}, f"Scaled by {scale_fmt}{report_suffix}")
+        report({'INFO'}, tip_("Scaled by {}{}").format(scale_fmt, report_suffix))
 
 
 class MESH_OT_print3d_scale_to_volume(Operator):
@@ -638,7 +644,7 @@ class MESH_OT_print3d_scale_to_volume(Operator):
     def execute(self, context):
         scale = math.pow(self.volume, 1 / 3) / math.pow(self.volume_init, 1 / 3)
         scale_fmt = clean_float(scale, 6)
-        self.report({'INFO'}, f"Scaled by {scale_fmt}")
+        self.report({'INFO'}, tip_("Scaled by {}").format(scale_fmt))
         _scale(scale, self.report)
         return {'FINISHED'}
 
@@ -689,7 +695,7 @@ class MESH_OT_print3d_scale_to_bounds(Operator):
     def execute(self, context):
         scale = self.length / self.length_init
         axis = "XYZ"[self.axis_init]
-        _scale(scale, report=self.report, report_suffix=f", Clamping {axis}-Axis")
+        _scale(scale, report=self.report, report_suffix=tip_(", Clamping {}-Axis").format(axis))
         return {'FINISHED'}
 
     def invoke(self, context, event):
@@ -763,7 +769,10 @@ class MESH_OT_print3d_align_to_xy(Operator):
             normal = Vector((0.0, 0.0, 0.0))
             if face_areas:
                 for face in faces:
-                    normal += (face.normal * face.calc_area())
+                    if mode_orig == 'EDIT_MESH':
+                        normal += (face.normal * face.calc_area())
+                    else:
+                        normal += (face.normal * face.area)
             else:
                 for face in faces:
                     normal += face.normal
@@ -777,9 +786,9 @@ class MESH_OT_print3d_align_to_xy(Operator):
 
         if len(skip_invalid) > 0:
             for name in skip_invalid:
-                print(f"Align to XY: Skipping object {name}. No faces selected.")
+                print(tip_("Align to XY: Skipping object {}. No faces selected.").format(name))
             if len(skip_invalid) == 1:
-                self.report({'WARNING'}, "Skipping object. No faces selected" % skip_invalid[0])
+                self.report({'WARNING'}, tip_("Skipping object {}. No faces selected").format(skip_invalid[0]))
             else:
                 self.report({'WARNING'}, "Skipping some objects. No faces selected. See terminal")
         return {'FINISHED'}
diff --git a/object_scatter/__init__.py b/object_scatter/__init__.py
index 12bc8ca020bccebc51dfcfcf4a5bd798a4b626f6..8edeac719f2117d63300dd81c7c5ce6625c4f85c 100644
--- a/object_scatter/__init__.py
+++ b/object_scatter/__init__.py
@@ -3,8 +3,8 @@
 bl_info = {
     "name": "Scatter Objects",
     "author": "Jacques Lucke",
-    "version": (0, 1),
-    "blender": (2, 80, 0),
+    "version": (0, 2),
+    "blender": (3, 0, 0),
     "location": "3D View",
     "description": "Distribute object instances on another object.",
     "warning": "",
diff --git a/object_scatter/operator.py b/object_scatter/operator.py
index 07bf3884de1e05f21366e9c4706525c5b70bec8e..8be78672b3bb860661adefb0ac2c512083cd4dc7 100644
--- a/object_scatter/operator.py
+++ b/object_scatter/operator.py
@@ -2,7 +2,6 @@
 
 import bpy
 import gpu
-import bgl
 import blf
 import math
 import enum
@@ -340,14 +339,14 @@ def draw_matrices_batches(batches):
     shader.bind()
     shader.uniform_float("color", (0.4, 0.4, 1.0, 0.3))
 
-    bgl.glEnable(bgl.GL_BLEND)
-    bgl.glDepthMask(bgl.GL_FALSE)
+    gpu.state.blend_set('ALPHA')
+    gpu.state.depth_mask_set(False)
 
     for batch in batches:
         batch.draw(shader)
 
-    bgl.glDisable(bgl.GL_BLEND)
-    bgl.glDepthMask(bgl.GL_TRUE)
+    gpu.state.blend_set('NONE')
+    gpu.state.depth_mask_set(True)
 
 def create_batch_for_matrices(matrices, base_scale):
     coords = []
@@ -367,7 +366,7 @@ def create_batch_for_matrices(matrices, base_scale):
 
 def draw_line_strip_batch(batch, color, thickness=1):
     shader = get_uniform_color_shader()
-    bgl.glLineWidth(thickness)
+    gpu.state.line_width_set(thickness)
     shader.bind()
     shader.uniform_float("color", color)
     batch.draw(shader)
diff --git a/pose_library/__init__.py b/pose_library/__init__.py
index 33ca777f841699e5270e731f0a01fe87d3266253..b13d1bf0ddcaea198b59c9e2218d842c1593146e 100644
--- a/pose_library/__init__.py
+++ b/pose_library/__init__.py
@@ -10,9 +10,8 @@ bl_info = {
     "author": "Sybren A. Stüvel",
     "version": (2, 0),
     "blender": (3, 0, 0),
-    "warning": "In heavily development, things may change",
     "location": "Asset Browser -> Animations, and 3D Viewport -> Animation panel",
-    # "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/pose_library.html",
+    "doc_url": "{BLENDER_MANUAL_URL}/animation/armatures/posing/editing/pose_library.html",
     "support": "OFFICIAL",
     "category": "Animation",
 }
diff --git a/power_sequencer/__init__.py b/power_sequencer/__init__.py
index c646da7c1434d23a417c5b9af5a0dd32f60fdb48..877de3028c5dcb11b4ebef72dd926ccb89eff33e 100755
--- a/power_sequencer/__init__.py
+++ b/power_sequencer/__init__.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 from typing import List, Tuple, Type
 
 import bpy
@@ -27,8 +24,8 @@ bl_info = {
     "name": "Power Sequencer",
     "description": "Video editing tools for content creators",
     "author": "Nathan Lovato",
-    "version": (1, 5, 0),
-    "blender": (2, 81, 0),
+    "version": (2, 0, 2),
+    "blender": (2, 93, 3),
     "location": "Sequencer",
     "tracker_url": "https://github.com/GDquest/Blender-power-sequencer/issues",
     "wiki_url": "https://www.gdquest.com/docs/documentation/power-sequencer/",
diff --git a/power_sequencer/addon_preferences.py b/power_sequencer/addon_preferences.py
index 4220e89c58f5a33018ff48b75aa1e1dbff51ab81..cfa213341212b20d0e1f5e091ef8f89a6f446679 100644
--- a/power_sequencer/addon_preferences.py
+++ b/power_sequencer/addon_preferences.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 """
 Add-on preferences and interface in the Blender preferences window.
 """
diff --git a/power_sequencer/addon_properties.py b/power_sequencer/addon_properties.py
index 575a3954c67e2b65c031e2f9f21db0b7e20bae76..3e7a4df37b92b64f2992906491cd7d59f1a57f8d 100644
--- a/power_sequencer/addon_properties.py
+++ b/power_sequencer/addon_properties.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 
@@ -10,8 +7,6 @@ class PowerSequencerProperties(bpy.types.PropertyGroup):
     playback_speed: bpy.props.EnumProperty(
         items=[
             ("NORMAL", "Normal (1x)", ""),
-            ("FAST", "Fast (1.33x)", ""),
-            ("FASTER", "Faster (1.66x)", ""),
             ("DOUBLE", "Double (2x)", ""),
             ("TRIPLE", "Triple (3x)", ""),
         ],
@@ -19,8 +14,6 @@ class PowerSequencerProperties(bpy.types.PropertyGroup):
         default="NORMAL",
     )
 
-    frame_pre: bpy.props.IntProperty(name="Frame before frame_change", default=0, min=0)
-
     active_tab: bpy.props.StringProperty(
         name="Active Tab", description="The name of the active tab in the UI", default="Sequencer"
     )
diff --git a/power_sequencer/handlers.py b/power_sequencer/handlers.py
index d9c107dab75b8e50fdcdf2f74e3cd28a746d2e94..c743d18b88c8f1ab6e5c65d587abd651c4c215a7 100644
--- a/power_sequencer/handlers.py
+++ b/power_sequencer/handlers.py
@@ -1,21 +1,9 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from bpy.app.handlers import persistent
 
 
-@persistent
-def power_sequencer_load_file_post(arg):
-    """
-    Called after loading the blend file
-    """
-    for scene in bpy.data.scenes:
-        scene.power_sequencer.frame_pre = bpy.context.scene.frame_current
-
-
 @persistent
 def power_sequencer_playback_speed_post(scene):
     """
@@ -24,27 +12,23 @@ def power_sequencer_playback_speed_post(scene):
     It steps over frame rather than increase the playback speed smoothly,
     but it's still useful for faster editing
     """
+
+    # Calling this function triggers a callback to this function via the frame
+    # changed handler, causing a stack overflow. We use a property to prevent
+    # errors.
     if bpy.context.screen and not bpy.context.screen.is_animation_playing:
         return
 
     playback_speed = scene.power_sequencer.playback_speed
 
-    frame_start = scene.frame_current
-    frame_post = scene.frame_current
-
-    if playback_speed == "FAST" and frame_start % 3 == 0:
-        frame_post += 1
-    elif playback_speed == "FASTER" and frame_start % 2 == 0:
-        frame_post += 1
-    elif playback_speed == "DOUBLE":
-        # 2.5x -> skip 5 frames for 2. 2 then 3 then 2 etc.
-        frame_post += 1
+    target_frame = scene.frame_current
+    if playback_speed == "DOUBLE":
+        target_frame += 1
     elif playback_speed == "TRIPLE":
-        frame_post += 2
+        target_frame += 2
 
-    if frame_start != frame_post:
-        bpy.ops.screen.frame_offset(delta=frame_post - frame_start)
-    scene.power_sequencer.frame_pre = scene.frame_current
+    if target_frame != scene.frame_current:
+        bpy.ops.screen.frame_offset(delta=target_frame - scene.frame_current)
 
 
 def draw_playback_speed(self, context):
@@ -64,7 +48,6 @@ def register_handlers():
     bpy.types.SEQUENCER_HT_header.append(draw_playback_speed)
 
     # Handlers
-    bpy.app.handlers.load_post.append(power_sequencer_load_file_post)
     bpy.app.handlers.frame_change_post.append(power_sequencer_playback_speed_post)
 
 
@@ -74,5 +57,4 @@ def unregister_handlers():
     bpy.types.SEQUENCER_HT_header.remove(draw_playback_speed)
 
     # Handlers
-    bpy.app.handlers.load_post.remove(power_sequencer_load_file_post)
     bpy.app.handlers.frame_change_post.remove(power_sequencer_playback_speed_post)
diff --git a/power_sequencer/operators/__init__.py b/power_sequencer/operators/__init__.py
index b1222a18554c2a5e84365a0252fb7abb66ea2793..57dbe2bbfa041cc5427fb6065b41b27d3230c4df 100755
--- a/power_sequencer/operators/__init__.py
+++ b/power_sequencer/operators/__init__.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import importlib
 import os
 
@@ -16,7 +13,7 @@ def get_operator_classes():
     module_paths = ["." + os.path.splitext(f)[0] for f in module_files]
     classes = []
     for path in module_paths:
-        module = importlib.import_module(path, package="power_sequencer.operators")
+        module = importlib.import_module(path, package=__package__)
         operator_names = [entry for entry in dir(module) if entry.startswith("POWER_SEQUENCER_OT")]
         classes.extend([getattr(module, name) for name in operator_names])
     return classes
diff --git a/power_sequencer/operators/channel_offset.py b/power_sequencer/operators/channel_offset.py
index b7569afaf0ee756fa9871a2462e04c613ed00c50..1e9c97f771e30b942659bad1f3f7b00ac0639506 100644
--- a/power_sequencer/operators/channel_offset.py
+++ b/power_sequencer/operators/channel_offset.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 from operator import attrgetter
 
 import bpy
@@ -117,12 +114,11 @@ class POWER_SEQUENCER_OT_channel_offset(bpy.types.Operator):
                             context, s.frame_final_start, s.frame_final_end, to_trim, to_delete
                         )
 
-                if not self.keep_selection_offset:
-                    s.channel = comparison_function(limit_channel, s.channel + channel_offset)
-                    if s.channel == limit_channel:
-                        move_selection(context, [s], 0, 0)
+                s.channel = comparison_function(limit_channel, s.channel + channel_offset)
+                if s.channel == limit_channel:
+                    move_selection(context, [s], 0, 0)
 
-            if self.keep_selection_offset:
+            if self.keep_selection_offset and not self.trim_target_channel:
                 start_frame = head.frame_final_start
                 x_difference = 0
                 while not head.channel == limit_channel:
diff --git a/power_sequencer/operators/concatenate_strips.py b/power_sequencer/operators/concatenate_strips.py
index 32f5f59b0cb58b6d3056ca4a2275296692a1b2f3..17ff4ee4a5125fdff1ca476371a66ea8fcf74103 100644
--- a/power_sequencer/operators/concatenate_strips.py
+++ b/power_sequencer/operators/concatenate_strips.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from operator import attrgetter
 
diff --git a/power_sequencer/operators/copy_selected_sequences.py b/power_sequencer/operators/copy_selected_sequences.py
index 643ecb339c4409e57ce27ffaca1ef34b3e8dd782..338f1cff61a3cb0edfbe6b030fea66e81c37930b 100644
--- a/power_sequencer/operators/copy_selected_sequences.py
+++ b/power_sequencer/operators/copy_selected_sequences.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from operator import attrgetter
 
diff --git a/power_sequencer/operators/crossfade_add.py b/power_sequencer/operators/crossfade_add.py
index 73644c809c2e2f09fc32b8693885e4a77df2cc19..25ce7ae7699c978e5f9fd0d6979528f1644c0857 100644
--- a/power_sequencer/operators/crossfade_add.py
+++ b/power_sequencer/operators/crossfade_add.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import find_sequences_after
@@ -94,7 +91,7 @@ class POWER_SEQUENCER_OT_crossfade_add(bpy.types.Operator):
         Moves the handles of the two sequences before adding the crossfade
         """
         fade_duration = convert_duration_to_frames(context, self.crossfade_duration)
-        fade_offset = fade_duration / 2
+        fade_offset = int(fade_duration / 2)
 
         if hasattr(sequence_1, "input_1"):
             sequence_1.input_1.frame_final_end -= fade_offset
diff --git a/power_sequencer/operators/crossfade_edit.py b/power_sequencer/operators/crossfade_edit.py
index 0e330e10a63158452d3d4a38c221da5846e9c864..6d3be976812e442db9483804cffd8b67c104c1cc 100644
--- a/power_sequencer/operators/crossfade_edit.py
+++ b/power_sequencer/operators/crossfade_edit.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.global_settings import SequenceTypes
diff --git a/power_sequencer/operators/cut_strips_under_cursor.py b/power_sequencer/operators/cut_strips_under_cursor.py
index 4a66a78200e14d2ee6a2a510c13818253e530f7a..51bf07890ca2cc5799cca09b0bd1372b3d38eac6 100644
--- a/power_sequencer/operators/cut_strips_under_cursor.py
+++ b/power_sequencer/operators/cut_strips_under_cursor.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/delete_direct.py b/power_sequencer/operators/delete_direct.py
index bda5face60d38fc669e7432c197858e08353c7c2..1fc8bf60dcaeac84c0c887a253f7a9efdcf91399 100644
--- a/power_sequencer/operators/delete_direct.py
+++ b/power_sequencer/operators/delete_direct.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import get_mouse_frame_and_channel
diff --git a/power_sequencer/operators/deselect_all_left_or_right.py b/power_sequencer/operators/deselect_all_left_or_right.py
index 6769d1737c56bcabd7812fc9952a066b6fda1e6d..3ba794832c9bde5a1e791844d396c4af6fde9553 100644
--- a/power_sequencer/operators/deselect_all_left_or_right.py
+++ b/power_sequencer/operators/deselect_all_left_or_right.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/deselect_handles_and_grab.py b/power_sequencer/operators/deselect_handles_and_grab.py
index 468baf8b10c5584055f5939175c39efdcb9318da..36f547df62940f1e82b0c7501648fbee8281d748 100644
--- a/power_sequencer/operators/deselect_handles_and_grab.py
+++ b/power_sequencer/operators/deselect_handles_and_grab.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/duplicate_move.py b/power_sequencer/operators/duplicate_move.py
index 32c62a56b537990e490f2573ef93af788f7218e0..cce7a6ed4dacc985108db5fc5a186e8bf78af790 100644
--- a/power_sequencer/operators/duplicate_move.py
+++ b/power_sequencer/operators/duplicate_move.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import get_mouse_frame_and_channel
diff --git a/power_sequencer/operators/expand_to_surrounding_cuts.py b/power_sequencer/operators/expand_to_surrounding_cuts.py
index 7129e42d981d1d72855a2af2971f7da7d8bfb926..4b8a6f93ba1140b5f75a0b6b1899defe04f2751b 100644
--- a/power_sequencer/operators/expand_to_surrounding_cuts.py
+++ b/power_sequencer/operators/expand_to_surrounding_cuts.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import slice_selection
@@ -22,7 +19,11 @@ class POWER_SEQUENCER_OT_expand_to_surrounding_cuts(bpy.types.Operator):
         "demo": "",
         "description": doc_description(__doc__),
         "shortcuts": [
-            ({"type": "E", "value": "PRESS", "ctrl": True}, {}, "Expand to Surrounding Cuts",)
+            (
+                {"type": "E", "value": "PRESS", "ctrl": True},
+                {},
+                "Expand to Surrounding Cuts",
+            )
         ],
         "keymap": "Sequencer",
     }
diff --git a/power_sequencer/operators/fade_add.py b/power_sequencer/operators/fade_add.py
index 39fe18a92f5af669c11314b7baea049c6197daae..9a3d19731f6685e75f4a3fa395ec0e2d521cdbd2 100644
--- a/power_sequencer/operators/fade_add.py
+++ b/power_sequencer/operators/fade_add.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from mathutils import Vector
 from math import floor
@@ -40,7 +37,10 @@ class POWER_SEQUENCER_OT_fade_add(bpy.types.Operator):
     bl_options = {"REGISTER", "UNDO"}
 
     duration_seconds: bpy.props.FloatProperty(
-        name="Fade Duration", description="Duration of the fade in seconds", default=1.0, min=0.01,
+        name="Fade Duration",
+        description="Duration of the fade in seconds",
+        default=1.0,
+        min=0.01,
     )
     type: bpy.props.EnumProperty(
         items=[
diff --git a/power_sequencer/operators/fade_clear.py b/power_sequencer/operators/fade_clear.py
index c74b27cfc7cef3fda3d77b69dfa74350b90cfb60..d73c73472ed82989dacbcd9eff61d6c4263d49ca 100644
--- a/power_sequencer/operators/fade_clear.py
+++ b/power_sequencer/operators/fade_clear.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -21,7 +18,11 @@ class POWER_SEQUENCER_OT_fade_clear(bpy.types.Operator):
         "demo": "",
         "description": doc_description(__doc__),
         "shortcuts": [
-            ({"type": "F", "value": "PRESS", "alt": True, "ctrl": True}, {}, "Clear Fades",)
+            (
+                {"type": "F", "value": "PRESS", "alt": True, "ctrl": True},
+                {},
+                "Clear Fades",
+            )
         ],
         "keymap": "Sequencer",
     }
diff --git a/power_sequencer/operators/gap_remove.py b/power_sequencer/operators/gap_remove.py
index 7f2ec5ae17b131d866b815da2e59f10b218d21f0..64070667789c34231837f6bb34db886b10d51b19 100644
--- a/power_sequencer/operators/gap_remove.py
+++ b/power_sequencer/operators/gap_remove.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from operator import attrgetter
 
diff --git a/power_sequencer/operators/grab.py b/power_sequencer/operators/grab.py
index de53d2a476731d2b7f6255f5682836cfa1a0c5a4..57299d726b824cf3ee7d8796d6d0456e45d35158 100644
--- a/power_sequencer/operators/grab.py
+++ b/power_sequencer/operators/grab.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import get_mouse_frame_and_channel
diff --git a/power_sequencer/operators/grab_closest_handle_or_cut.py b/power_sequencer/operators/grab_closest_handle_or_cut.py
index 55440b9514056a0aef2ecfee089a94e9b464d79e..7a7a056d02a71fe2687909b234b69d31fe228311 100644
--- a/power_sequencer/operators/grab_closest_handle_or_cut.py
+++ b/power_sequencer/operators/grab_closest_handle_or_cut.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 """
 Selects and grabs the strip handle or cut closest to the mouse cursor.
 Hover near a cut and use this operator to slide it.
diff --git a/power_sequencer/operators/grab_sequence_handles.py b/power_sequencer/operators/grab_sequence_handles.py
index ecbe8c4b41c9cd6969c9633376c6b77b428899df..d8137b0f06801f9d0b7ecf7ad61225c13a884003 100644
--- a/power_sequencer/operators/grab_sequence_handles.py
+++ b/power_sequencer/operators/grab_sequence_handles.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.global_settings import SequenceTypes
@@ -34,8 +31,8 @@ class POWER_SEQUENCER_OT_grab_sequence_handles(bpy.types.Operator):
     bl_options = {"REGISTER", "UNDO"}
 
     always_find_closest: bpy.props.BoolProperty(name="Always find closest", default=False)
-    frame: bpy.props.IntProperty(name="Frame", default=-1, options={"HIDDEN"})
-    channel: bpy.props.IntProperty(name="Channel", default=-1, options={"HIDDEN"})
+    frame: bpy.props.FloatProperty(name="Frame", default=-1.0, options={"HIDDEN"})
+    channel: bpy.props.FloatProperty(name="Channel", default=-1.0, options={"HIDDEN"})
 
     @classmethod
     def poll(cls, context):
diff --git a/power_sequencer/operators/import_local_footage.py b/power_sequencer/operators/import_local_footage.py
index 8612210ae4d301e89fe948a81f7387268514d742..7eb7120b0ba457df64d031011232e8c951836ce7 100644
--- a/power_sequencer/operators/import_local_footage.py
+++ b/power_sequencer/operators/import_local_footage.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import json
 import os
 import re
diff --git a/power_sequencer/operators/jump_time_offset.py b/power_sequencer/operators/jump_time_offset.py
index ee9b1f9b5c65f8b60415366540c8dbf7f1db2ceb..799418275dd95afae4ab1543c5d82be6816114ac 100644
--- a/power_sequencer/operators/jump_time_offset.py
+++ b/power_sequencer/operators/jump_time_offset.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import convert_duration_to_frames
diff --git a/power_sequencer/operators/jump_to_cut.py b/power_sequencer/operators/jump_to_cut.py
index add0dab9b188fa166829c5c7b1a5a94bce249300..02a0b9f99954be770087e069e3536620e95ab0be 100644
--- a/power_sequencer/operators/jump_to_cut.py
+++ b/power_sequencer/operators/jump_to_cut.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from operator import attrgetter
 
@@ -45,8 +42,8 @@ class POWER_SEQUENCER_OT_jump_to_cut(bpy.types.Operator):
         name="Direction",
         description="Jump direction, either forward or backward",
         items=[
-            ("RIGHT", "Forward", "Jump forward in time"),
-            ("LEFT", "Backward", "Jump backward in time"),
+            ("RIGHT", "Right", "Jump forward in time"),
+            ("LEFT", "Left", "Jump backward in time"),
         ],
     )
 
@@ -56,9 +53,6 @@ class POWER_SEQUENCER_OT_jump_to_cut(bpy.types.Operator):
 
     def execute(self, context):
         frame_current = context.scene.frame_current
-        sorted_sequences = sorted(
-            context.sequences, key=attrgetter("frame_final_start", "frame_final_end")
-        )
 
         fcurves = []
         animation_data = context.scene.animation_data
@@ -66,43 +60,53 @@ class POWER_SEQUENCER_OT_jump_to_cut(bpy.types.Operator):
             fcurves = animation_data.action.fcurves
 
         frame_target = -1
+
+        # First find the closest cut, then if that sequence has an associated
+        # fcurve, loop through the curve's keyframes.
         if self.direction == "RIGHT":
-            sequences = [s for s in sorted_sequences if s.frame_final_end > frame_current]
-            for s in sequences:
+            frame_target = 100_000_000
+            for s in context.sequences:
+                if s.frame_final_end <= frame_current:
+                    continue
+
+                candidates = [frame_target, s.frame_final_end]
+                if s.frame_final_start > frame_current:
+                    candidates.append(s.frame_final_start)
 
-                frame_target = (
-                    s.frame_final_end
-                    if s.frame_final_start <= frame_current
-                    else s.frame_final_start
-                )
+                frame_target = min(candidates)
 
                 for f in fcurves:
+                    if s.name not in f.data_path:
+                        continue
+
                     for k in f.keyframe_points:
                         frame = k.co[0]
-                        if frame <= context.scene.frame_current:
+                        if frame <= frame_current:
                             continue
                         frame_target = min(frame_target, frame)
-                break
 
         elif self.direction == "LEFT":
-            sequences = [
-                s for s in reversed(sorted_sequences) if s.frame_final_start < frame_current
-            ]
-            for s in sequences:
+            for s in context.sequences:
+                if s.frame_final_start >= frame_current:
+                    continue
+
+                candidates = [frame_target, s.frame_final_start]
+                if s.frame_final_end < frame_current:
+                    candidates.append(s.frame_final_end)
 
-                frame_target = (
-                    s.frame_final_start if s.frame_final_end >= frame_current else s.frame_final_end
-                )
+                frame_target = max(candidates)
 
                 for f in fcurves:
+                    if s.name not in f.data_path:
+                        continue
+
                     for k in f.keyframe_points:
                         frame = k.co[0]
-                        if frame >= context.scene.frame_current:
+                        if frame >= frame_current:
                             continue
                         frame_target = max(frame_target, frame)
-                break
 
-        if frame_target != -1:
-            context.scene.frame_current = max(1, frame_target)
+        if frame_target > 0 and frame_target != 100_000_000:
+            context.scene.frame_current = int(frame_target)
 
         return {"FINISHED"}
diff --git a/power_sequencer/operators/make_hold_frame.py b/power_sequencer/operators/make_hold_frame.py
index 4c1f110de6e7d4ad1e565a79ab6e472481168643..3650a4cbfd9d380e5466ed83b31680c6f0d04db3 100644
--- a/power_sequencer/operators/make_hold_frame.py
+++ b/power_sequencer/operators/make_hold_frame.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 import operator
 
diff --git a/power_sequencer/operators/marker_delete_closest.py b/power_sequencer/operators/marker_delete_closest.py
index a47569589acdff9c8f102e99d5ba2f1658eb1164..68659b51e0f9a7a08054449880f455c09b59d0cc 100644
--- a/power_sequencer/operators/marker_delete_closest.py
+++ b/power_sequencer/operators/marker_delete_closest.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/marker_delete_direct.py b/power_sequencer/operators/marker_delete_direct.py
index efc8ba6663a8a601a6063943f27c73b8925bcbff..3b6f9297bfdeb31dfb85b8565d899fcd8338e043 100644
--- a/power_sequencer/operators/marker_delete_direct.py
+++ b/power_sequencer/operators/marker_delete_direct.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/marker_snap_to_cursor.py b/power_sequencer/operators/marker_snap_to_cursor.py
index dbb3a6c75e7bc2b4c72ee9d2ac6435d1c92aec68..8fe8065326546fcf8be9f46f7f3f6580cb458d3e 100644
--- a/power_sequencer/operators/marker_snap_to_cursor.py
+++ b/power_sequencer/operators/marker_snap_to_cursor.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/markers_as_timecodes.py b/power_sequencer/operators/markers_as_timecodes.py
index 526588388cf8fddf687c539381ae79333622291b..74b427941b2f097a02bf270d04cf7a203ae80b4b 100644
--- a/power_sequencer/operators/markers_as_timecodes.py
+++ b/power_sequencer/operators/markers_as_timecodes.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 import datetime as dt
 
diff --git a/power_sequencer/operators/markers_create_from_selected.py b/power_sequencer/operators/markers_create_from_selected.py
index bd346d84150d883f1f0edab26798d488ebac8426..620995deac904ef98a1f55189e86b3dc2a243cbe 100644
--- a/power_sequencer/operators/markers_create_from_selected.py
+++ b/power_sequencer/operators/markers_create_from_selected.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/markers_set_preview_in_between.py b/power_sequencer/operators/markers_set_preview_in_between.py
index 607243123e172d32119cb293ddb9b8b081d6c142..8ac0e9f61709f0c99a29541c2d79a58208dfa323 100644
--- a/power_sequencer/operators/markers_set_preview_in_between.py
+++ b/power_sequencer/operators/markers_set_preview_in_between.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import find_neighboring_markers
diff --git a/power_sequencer/operators/markers_snap_matching_strips.py b/power_sequencer/operators/markers_snap_matching_strips.py
index da475eeb8af7fa9b62df85b38044f84fddf151fe..b9eab7b4374f5c21cc5cbbf071ad53639137ec94 100644
--- a/power_sequencer/operators/markers_snap_matching_strips.py
+++ b/power_sequencer/operators/markers_snap_matching_strips.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/meta_resize_to_content.py b/power_sequencer/operators/meta_resize_to_content.py
index ed54ebcae3a6f22e0cea2a143230c5d19d74c4d8..b5d0b29fda3e865caa826639c62351f433e109a6 100644
--- a/power_sequencer/operators/meta_resize_to_content.py
+++ b/power_sequencer/operators/meta_resize_to_content.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from .utils.functions import get_frame_range
 
diff --git a/power_sequencer/operators/meta_trim_content_to_bounds.py b/power_sequencer/operators/meta_trim_content_to_bounds.py
index 11b483697fb97be4ee158d150f79f58299e87585..e309f8e163f1740b504c261cd5a59134919affae 100644
--- a/power_sequencer/operators/meta_trim_content_to_bounds.py
+++ b/power_sequencer/operators/meta_trim_content_to_bounds.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from .utils.global_settings import SequenceTypes
 
diff --git a/power_sequencer/operators/meta_ungroup_and_trim.py b/power_sequencer/operators/meta_ungroup_and_trim.py
index 00c79cf496b8d99cd73715c4ca1e2f24f0c977e0..e467492d6f484494f5abe07b9d672a3c52046dee 100644
--- a/power_sequencer/operators/meta_ungroup_and_trim.py
+++ b/power_sequencer/operators/meta_ungroup_and_trim.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/mouse_toggle_mute.py b/power_sequencer/operators/mouse_toggle_mute.py
index 362b2e9d5c2e50d26e3e6310d65cc08869e692a7..cd135829121a2af41fb4ad592fa1c5c348df62db 100644
--- a/power_sequencer/operators/mouse_toggle_mute.py
+++ b/power_sequencer/operators/mouse_toggle_mute.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 """Toggle mute a sequence as you click on it"""
 import bpy
 from math import floor
diff --git a/power_sequencer/operators/mouse_trim_instantly.py b/power_sequencer/operators/mouse_trim_instantly.py
index 332053fc59c26fba9cff2039ae0ea282fb8f464f..3a79a5a7a330928fb80ba36f417970d7465c7e23 100644
--- a/power_sequencer/operators/mouse_trim_instantly.py
+++ b/power_sequencer/operators/mouse_trim_instantly.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from math import floor
 
diff --git a/power_sequencer/operators/mouse_trim_modal.py b/power_sequencer/operators/mouse_trim_modal.py
index a138933b00de2988e4176f6cae6e7b8515c18e18..8136adae22db73115c737d51662c51044e8533f9 100644
--- a/power_sequencer/operators/mouse_trim_modal.py
+++ b/power_sequencer/operators/mouse_trim_modal.py
@@ -1,10 +1,6 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
-import bgl
 import gpu
 import math
 from mathutils import Vector
@@ -361,8 +357,8 @@ def draw(self, context, frame_start=-1, frame_end=-1, target_strips=[], draw_arr
     rect_origin = Vector((start_x, start_y))
     rect_size = Vector((end_x - start_x, abs(start_y - end_y)))
 
-    bgl.glEnable(bgl.GL_BLEND)
-    bgl.glLineWidth(3)
+    gpu.state.blend_set('ALPHA')
+    gpu.state.line_width_set(3.0)
     draw_rectangle(SHADER, rect_origin, rect_size, color_fill)
     # Vertical lines
     draw_line(SHADER, Vector((start_x, start_y)), Vector((start_x, end_y)), color_line)
@@ -377,8 +373,8 @@ def draw(self, context, frame_start=-1, frame_end=-1, target_strips=[], draw_arr
         draw_triangle_equilateral(SHADER, center_1, radius, color=color_line)
         draw_triangle_equilateral(SHADER, center_2, radius, math.pi, color=color_line)
 
-    bgl.glLineWidth(1)
-    bgl.glDisable(bgl.GL_BLEND)
+    gpu.state.line_width_set(1)
+    gpu.state.blend_set('NONE')
 
 
 def get_frame_and_channel(event):
diff --git a/power_sequencer/operators/open_project_directory.py b/power_sequencer/operators/open_project_directory.py
index f32f2ac2f985223fdbf0148ce15db2cebb487cf2..02c72de119bf2853295bc5b11cc353f72f1662fe 100644
--- a/power_sequencer/operators/open_project_directory.py
+++ b/power_sequencer/operators/open_project_directory.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 import os
 from platform import system
diff --git a/power_sequencer/operators/playback_speed_decrease.py b/power_sequencer/operators/playback_speed_decrease.py
deleted file mode 100644
index a856ac4beb587f8e40ba5c08ca553b675386b955..0000000000000000000000000000000000000000
--- a/power_sequencer/operators/playback_speed_decrease.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
-import bpy
-
-from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
-
-
-class POWER_SEQUENCER_OT_playback_speed_decrease(bpy.types.Operator):
-    """
-    *brief* Decrease playback speed incrementally down to normal
-
-
-    Playback speed may be set to any of the following speeds:
-
-    * Normal (1x)
-    * Fast (1.33x)
-    * Faster (1.66x)
-    * Double (2x)
-    * Triple (3x)
-
-    Activating this operator will decrease playback speed through each
-    of these steps until minimum speed is reached.
-    """
-
-    doc = {
-        "name": doc_name(__qualname__),
-        "demo": "",
-        "description": doc_description(__doc__),
-        "shortcuts": [({"type": "COMMA", "value": "PRESS"}, {}, "Decrease Playback Speed")],
-        "keymap": "Sequencer",
-    }
-    bl_idname = doc_idname(__qualname__)
-    bl_label = doc["name"]
-    bl_description = doc_brief(doc["description"])
-
-    @classmethod
-    def poll(cls, context):
-        return context.sequences
-
-    def execute(self, context):
-        scene = context.scene
-
-        speeds = ["NORMAL", "FAST", "FASTER", "DOUBLE", "TRIPLE"]
-        playback_speed = scene.power_sequencer.playback_speed
-
-        index = max(0, speeds.index(playback_speed) - 1)
-        scene.power_sequencer.playback_speed = speeds[index]
-
-        return {"FINISHED"}
diff --git a/power_sequencer/operators/playback_speed_increase.py b/power_sequencer/operators/playback_speed_increase.py
deleted file mode 100644
index 7c2f2ad2978211757edb11412175ae2f4a6fde7b..0000000000000000000000000000000000000000
--- a/power_sequencer/operators/playback_speed_increase.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
-import bpy
-
-from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
-
-
-class POWER_SEQUENCER_OT_playback_speed_increase(bpy.types.Operator):
-    """
-    *brief* Increase playback speed up to triple
-
-
-    Playback speed may be set to any of the following speeds:
-
-    * Normal (1x)
-    * Fast (1.33x)
-    * Faster (1.66x)
-    * Double (2x)
-    * Triple (3x)
-
-    Activating this operator will increase playback speed through each
-    of these steps until maximum speed is reached.
-    """
-
-    doc = {
-        "name": doc_name(__qualname__),
-        "demo": "",
-        "description": doc_description(__doc__),
-        "shortcuts": [({"type": "PERIOD", "value": "PRESS"}, {}, "Increase playback speed")],
-        "keymap": "Sequencer",
-    }
-    bl_idname = doc_idname(__qualname__)
-    bl_label = doc["name"]
-    bl_description = doc_brief(doc["description"])
-
-    @classmethod
-    def poll(cls, context):
-        return context.sequences
-
-    def execute(self, context):
-        scene = context.scene
-
-        speeds = ["NORMAL", "FAST", "FASTER", "DOUBLE", "TRIPLE"]
-        playback_speed = scene.power_sequencer.playback_speed
-
-        index = min(speeds.index(playback_speed) + 1, len(speeds) - 1)
-        scene.power_sequencer.playback_speed = speeds[index]
-
-        return {"FINISHED"}
diff --git a/power_sequencer/operators/playback_speed_set.py b/power_sequencer/operators/playback_speed_set.py
index 9f576247eba597c9029beed97f82e0522dbecef2..82c45b7aafa1618dc3aebd508ac6a4d7e07f0165 100644
--- a/power_sequencer/operators/playback_speed_set.py
+++ b/power_sequencer/operators/playback_speed_set.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -19,14 +16,8 @@ class POWER_SEQUENCER_OT_playback_speed_set(bpy.types.Operator):
         "description": doc_description(__doc__),
         "shortcuts": [
             ({"type": "ONE", "ctrl": True, "value": "PRESS"}, {"speed": "NORMAL"}, "Speed to 1x"),
-            ({"type": "TWO", "ctrl": True, "value": "PRESS"}, {"speed": "FAST"}, "Speed to 1.33x"),
-            (
-                {"type": "THREE", "ctrl": True, "value": "PRESS"},
-                {"speed": "FASTER"},
-                "Speed to 1.66x",
-            ),
-            ({"type": "FOUR", "ctrl": True, "value": "PRESS"}, {"speed": "DOUBLE"}, "Speed to 2x"),
-            ({"type": "FIVE", "ctrl": True, "value": "PRESS"}, {"speed": "TRIPLE"}, "Speed to 3x"),
+            ({"type": "TWO", "ctrl": True, "value": "PRESS"}, {"speed": "DOUBLE"}, "Speed to 2x"),
+            ({"type": "THREE", "ctrl": True, "value": "PRESS"}, {"speed": "TRIPLE"}, "Speed to 3x"),
         ],
         "keymap": "Sequencer",
     }
@@ -38,8 +29,6 @@ class POWER_SEQUENCER_OT_playback_speed_set(bpy.types.Operator):
     speed: bpy.props.EnumProperty(
         items=[
             ("NORMAL", "Normal (1x)", ""),
-            ("FAST", "Fast (1.33x)", ""),
-            ("FASTER", "Faster (1.66x)", ""),
             ("DOUBLE", "Double (2x)", ""),
             ("TRIPLE", "Triple (3x)", ""),
         ],
diff --git a/power_sequencer/operators/preview_closest_cut.py b/power_sequencer/operators/preview_closest_cut.py
index 312be788319528c154bdb655ce80eace8bb9fde5..2375c21a9be058499304372dcaa2c5a30f586370 100644
--- a/power_sequencer/operators/preview_closest_cut.py
+++ b/power_sequencer/operators/preview_closest_cut.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import get_frame_range
diff --git a/power_sequencer/operators/preview_to_selection.py b/power_sequencer/operators/preview_to_selection.py
index ea6c0ed7d41bc93e2dda93f66a77b16cc564e714..7ad8e2c78ec8d2752b13f5e3bbce0309b4aadfe1 100644
--- a/power_sequencer/operators/preview_to_selection.py
+++ b/power_sequencer/operators/preview_to_selection.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import get_frame_range
diff --git a/power_sequencer/operators/render_apply_preset.py b/power_sequencer/operators/render_apply_preset.py
index dc3b0ed100e1584b1f95b0895670ace44c6db780..d7ad5b38fe3b392ee8705ed025f6844f0139b5c2 100644
--- a/power_sequencer/operators/render_apply_preset.py
+++ b/power_sequencer/operators/render_apply_preset.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 import os
 
diff --git a/power_sequencer/operators/render_presets/twitter_720p.py b/power_sequencer/operators/render_presets/twitter_720p.py
index 109090cc79439bfe0e20a1866f5f74e541e8f3b3..eadb42661a7ee702876ced48537ced19a080de24 100644
--- a/power_sequencer/operators/render_presets/twitter_720p.py
+++ b/power_sequencer/operators/render_presets/twitter_720p.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 if __name__ == "__main__":
     import bpy
 
diff --git a/power_sequencer/operators/render_presets/youtube_1080.py b/power_sequencer/operators/render_presets/youtube_1080.py
index 5bf6e8268a43916bb27a1d4337c47943a91be23e..61bab65dad91709e1d517d1ae9d1ec9dbb7ba4af 100644
--- a/power_sequencer/operators/render_presets/youtube_1080.py
+++ b/power_sequencer/operators/render_presets/youtube_1080.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 if __name__ == "__main__":
     import bpy
 
diff --git a/power_sequencer/operators/ripple_delete.py b/power_sequencer/operators/ripple_delete.py
index 939da4e00ee95e13db33feb295b20ba5d9bca331..515f318ffcf28caf48881694161d2f09eee757d6 100644
--- a/power_sequencer/operators/ripple_delete.py
+++ b/power_sequencer/operators/ripple_delete.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_brief, doc_description, doc_idname, doc_name
diff --git a/power_sequencer/operators/save_direct.py b/power_sequencer/operators/save_direct.py
index f41c3d4f92695c2fce858cf2c76b009a1c8c0a66..09b130a345a2c38c0733be89ad4ff0a3640e6425 100644
--- a/power_sequencer/operators/save_direct.py
+++ b/power_sequencer/operators/save_direct.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/scene_create_from_selection.py b/power_sequencer/operators/scene_create_from_selection.py
index 206e385acd8481f272c51282e43b4496b3e5ec04..15ed023755f98a2d4a2afd554a529b6c0e5c0326 100644
--- a/power_sequencer/operators/scene_create_from_selection.py
+++ b/power_sequencer/operators/scene_create_from_selection.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from operator import attrgetter
 
diff --git a/power_sequencer/operators/scene_cycle.py b/power_sequencer/operators/scene_cycle.py
index 1be39ab910ee7455858a6d1191b1d96c552b79c2..05b861e7055b7eb2066f0caef7234e47ed628f93 100644
--- a/power_sequencer/operators/scene_cycle.py
+++ b/power_sequencer/operators/scene_cycle.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/scene_merge_from.py b/power_sequencer/operators/scene_merge_from.py
index b411e039edc840417253746034500f4a58b32e43..9d790bafd5e19c164fefc43469181cf85012c0ca 100644
--- a/power_sequencer/operators/scene_merge_from.py
+++ b/power_sequencer/operators/scene_merge_from.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/scene_open_from_strip.py b/power_sequencer/operators/scene_open_from_strip.py
index 0b992356615130e87c6aded6449a50837ba00730..8c868bc32d8ddadcf6ef2e59ca86f30099fd7f89 100644
--- a/power_sequencer/operators/scene_open_from_strip.py
+++ b/power_sequencer/operators/scene_open_from_strip.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/scene_rename_with_strip.py b/power_sequencer/operators/scene_rename_with_strip.py
index 8bc6432f496345a773e58ce3f35b55c12f6c96ae..b5327439a961ef901bcd6140601cdd4136d18f68 100644
--- a/power_sequencer/operators/scene_rename_with_strip.py
+++ b/power_sequencer/operators/scene_rename_with_strip.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/select_all_left_or_right.py b/power_sequencer/operators/select_all_left_or_right.py
index 7d9087ee2902e9d0fae176f8821d99a3fba57e70..794a9378728ca74e7a4b8adba415593c06fae3e9 100644
--- a/power_sequencer/operators/select_all_left_or_right.py
+++ b/power_sequencer/operators/select_all_left_or_right.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -51,4 +48,10 @@ class POWER_SEQUENCER_OT_select_all_left_or_right(bpy.types.Operator):
         return context.sequences
 
     def execute(self, context):
-        return bpy.ops.sequencer.select("INVOKE_DEFAULT", left_right=self.side)
+        if self.side == "LEFT":
+            for s in context.sequences:
+                s.select = s.frame_final_end < context.scene.frame_current
+        else:
+            for s in context.sequences:
+                s.select = s.frame_final_start > context.scene.frame_current
+        return {"FINISHED"}
diff --git a/power_sequencer/operators/select_closest_to_mouse.py b/power_sequencer/operators/select_closest_to_mouse.py
index f14bc3095ba068e3abec92916aeb6963fbe8b426..dee7e967a3d6fc255247dbc6197b8c0652ede276 100644
--- a/power_sequencer/operators/select_closest_to_mouse.py
+++ b/power_sequencer/operators/select_closest_to_mouse.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import find_strips_mouse
diff --git a/power_sequencer/operators/select_linked_effect.py b/power_sequencer/operators/select_linked_effect.py
index a63096f13778875b0fb1b16c29a758735880ee9d..d00a7a4af6c0978c037d95da43d8f542f3f99984 100644
--- a/power_sequencer/operators/select_linked_effect.py
+++ b/power_sequencer/operators/select_linked_effect.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import find_linked
diff --git a/power_sequencer/operators/select_linked_strips.py b/power_sequencer/operators/select_linked_strips.py
index 5716d13d5f5eaa61a51ddc9785329a7b2c65e2a0..290222570852a2cc024fb64b148755668c1c5e7a 100644
--- a/power_sequencer/operators/select_linked_strips.py
+++ b/power_sequencer/operators/select_linked_strips.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/select_related_strips.py b/power_sequencer/operators/select_related_strips.py
index 0e442ebdb0ab03ef8fdd8021fce890dc1de06099..e7101fee3052a9b8eeaf042de852e70df351a252 100644
--- a/power_sequencer/operators/select_related_strips.py
+++ b/power_sequencer/operators/select_related_strips.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.global_settings import SequenceTypes
diff --git a/power_sequencer/operators/select_strips_under_cursor.py b/power_sequencer/operators/select_strips_under_cursor.py
index a47d6e8011f67739cc9a1ef462f4757aaabe17bf..0c4712b55178d0295892ce84e4fb0284b77b2361 100644
--- a/power_sequencer/operators/select_strips_under_cursor.py
+++ b/power_sequencer/operators/select_strips_under_cursor.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import get_sequences_under_cursor
diff --git a/power_sequencer/operators/set_timeline_range.py b/power_sequencer/operators/set_timeline_range.py
index 65a90f4d87ef4d7abf1264912245d5705b42fcb6..a9bbd163c3ac5f909344decf704eca06b09fa0f5 100644
--- a/power_sequencer/operators/set_timeline_range.py
+++ b/power_sequencer/operators/set_timeline_range.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/snap.py b/power_sequencer/operators/snap.py
index 793f5bd9fe70b11e88c3bb4736a2e1fd95a4455b..24a4f52aed113c1c1280f3d4d1e7e0aaf0524845 100644
--- a/power_sequencer/operators/snap.py
+++ b/power_sequencer/operators/snap.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import get_sequences_under_cursor
@@ -21,7 +18,11 @@ class POWER_SEQUENCER_OT_snap(bpy.types.Operator):
         "demo": "",
         "description": doc_description(__doc__),
         "shortcuts": [
-            ({"type": "S", "value": "PRESS", "shift": True}, {}, "Snap sequences to cursor",)
+            (
+                {"type": "S", "value": "PRESS", "shift": True},
+                {},
+                "Snap sequences to cursor",
+            )
         ],
         "keymap": "Sequencer",
     }
diff --git a/power_sequencer/operators/snap_selection.py b/power_sequencer/operators/snap_selection.py
index 38da4c17d926e2fc2b658c279a6132fe1755f134..b73c5561728ae9fc445b48ebdc63153a16d4fb6b 100644
--- a/power_sequencer/operators/snap_selection.py
+++ b/power_sequencer/operators/snap_selection.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from .utils.functions import get_sequences_under_cursor, move_selection
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -21,7 +18,11 @@ class POWER_SEQUENCER_OT_snap_selection(bpy.types.Operator):
         "demo": "",
         "description": doc_description(__doc__),
         "shortcuts": [
-            ({"type": "S", "value": "PRESS", "alt": True}, {}, "Snap selection to cursor",)
+            (
+                {"type": "S", "value": "PRESS", "alt": True},
+                {},
+                "Snap selection to cursor",
+            )
         ],
         "keymap": "Sequencer",
     }
diff --git a/power_sequencer/operators/space_sequences.py b/power_sequencer/operators/space_sequences.py
index ddb28dfed598c7194b4bb48afe58145f9c405bcb..da3a0ac5a8a9fd09192e2e3103298fa58afa6f27 100644
--- a/power_sequencer/operators/space_sequences.py
+++ b/power_sequencer/operators/space_sequences.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import convert_duration_to_frames
diff --git a/power_sequencer/operators/speed_remove_effect.py b/power_sequencer/operators/speed_remove_effect.py
index ae2a4d3550f0ceeb30f719ed2641ec7bddf2b9a7..69348461494d192641437462b11e184fa97fe331 100644
--- a/power_sequencer/operators/speed_remove_effect.py
+++ b/power_sequencer/operators/speed_remove_effect.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/speed_up_movie_strip.py b/power_sequencer/operators/speed_up_movie_strip.py
index c004e2fcde574e5a4422536ee48705c8f8dcec21..9e863f364366e4c91ce1dece2d241a16e1907a32 100644
--- a/power_sequencer/operators/speed_up_movie_strip.py
+++ b/power_sequencer/operators/speed_up_movie_strip.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from math import ceil
 
@@ -24,9 +21,21 @@ class POWER_SEQUENCER_OT_speed_up_movie_strip(bpy.types.Operator):
         "demo": "https://i.imgur.com/ZyEd0jD.gif",
         "description": doc_description(__doc__),
         "shortcuts": [
-            ({"type": "TWO", "value": "PRESS", "alt": True}, {"speed_factor": 2.0}, "Speed x2",),
-            ({"type": "THREE", "value": "PRESS", "alt": True}, {"speed_factor": 3.0}, "Speed x3",),
-            ({"type": "FOUR", "value": "PRESS", "alt": True}, {"speed_factor": 4.0}, "Speed x4",),
+            (
+                {"type": "TWO", "value": "PRESS", "alt": True},
+                {"speed_factor": 2.0},
+                "Speed x2",
+            ),
+            (
+                {"type": "THREE", "value": "PRESS", "alt": True},
+                {"speed_factor": 3.0},
+                "Speed x3",
+            ),
+            (
+                {"type": "FOUR", "value": "PRESS", "alt": True},
+                {"speed_factor": 4.0},
+                "Speed x4",
+            ),
         ],
         "keymap": "Sequencer",
     }
diff --git a/power_sequencer/operators/swap_strips.py b/power_sequencer/operators/swap_strips.py
index 44537c43d4ff490309ef4fda75f82621a037f28b..6b0460fad1b9bfe4d7b0ee7292a181c0e5a9480d 100644
--- a/power_sequencer/operators/swap_strips.py
+++ b/power_sequencer/operators/swap_strips.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from operator import attrgetter
 
diff --git a/power_sequencer/operators/toggle_selected_mute.py b/power_sequencer/operators/toggle_selected_mute.py
index 3ee1a022391cce7c63e28c81bdf4ce572a639961..1a79237f535e2f2adaf33bf474722142b9445ec4 100644
--- a/power_sequencer/operators/toggle_selected_mute.py
+++ b/power_sequencer/operators/toggle_selected_mute.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
diff --git a/power_sequencer/operators/toggle_waveforms.py b/power_sequencer/operators/toggle_waveforms.py
index 3e0641827d2bb0e949a663bf5971f917c2abbacc..12104493dd25715768037084bc84bfc680381db7 100644
--- a/power_sequencer/operators/toggle_waveforms.py
+++ b/power_sequencer/operators/toggle_waveforms.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.global_settings import SequenceTypes
diff --git a/power_sequencer/operators/transitions_remove.py b/power_sequencer/operators/transitions_remove.py
index d1db0d7458d334c5ea27f5746612b47368e98fa7..1fb1b0d021db94bad5f514f06540f43d70ac295a 100644
--- a/power_sequencer/operators/transitions_remove.py
+++ b/power_sequencer/operators/transitions_remove.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from operator import attrgetter
 
diff --git a/power_sequencer/operators/trim_left_or_right_handles.py b/power_sequencer/operators/trim_left_or_right_handles.py
index aea81610e524b12e946159d39e4406d797e31581..c02b8650120176b7b3921ef2bbb62bc082c0de82 100644
--- a/power_sequencer/operators/trim_left_or_right_handles.py
+++ b/power_sequencer/operators/trim_left_or_right_handles.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 from operator import attrgetter
 
 import bpy
diff --git a/power_sequencer/operators/trim_three_point_edit.py b/power_sequencer/operators/trim_three_point_edit.py
index f174589e2e92699a0f0d30b05fa12dd81ad4d004..6d173258e9c2b44bb84184c19a2fb221be1b2a6b 100644
--- a/power_sequencer/operators/trim_three_point_edit.py
+++ b/power_sequencer/operators/trim_three_point_edit.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.functions import get_mouse_frame_and_channel
diff --git a/power_sequencer/operators/trim_to_surrounding_cuts.py b/power_sequencer/operators/trim_to_surrounding_cuts.py
index ab5df939228ee7adba38aa5bcda23560a89158e0..3057dfe517d840f6832b03279195014e33d685b4 100644
--- a/power_sequencer/operators/trim_to_surrounding_cuts.py
+++ b/power_sequencer/operators/trim_to_surrounding_cuts.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 """
 Find the two closest cuts, trims and deletes all strips above in the range but leaves some
 margin. Removes the newly formed gap.
diff --git a/power_sequencer/operators/utils/__init__.py b/power_sequencer/operators/utils/__init__.py
index 71fca8c4f86128b3429e159f46dab1e88e154eaa..be8d8b53ed0aed85ff2ce8274ab1e1c976349d8c 100644
--- a/power_sequencer/operators/utils/__init__.py
+++ b/power_sequencer/operators/utils/__init__.py
@@ -1,4 +1,2 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
diff --git a/power_sequencer/operators/utils/doc.py b/power_sequencer/operators/utils/doc.py
index 8bdae4107a2c16a644c6d0ee878617887f816a95..be52c7c2ed78451054a48d9bc2eb998c45bcd69e 100644
--- a/power_sequencer/operators/utils/doc.py
+++ b/power_sequencer/operators/utils/doc.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 """
 Utilities to convert operator names and docstrings to human-readable text.
 Used to generate names for Blender's operator search, and to generate Power Sequencer's documentation.
diff --git a/power_sequencer/operators/utils/draw.py b/power_sequencer/operators/utils/draw.py
index 5a156bea63f1c8ccc52bc67ade4808130de9cab5..3cede7783c994e37140e94c43f4537b33e245b7f 100644
--- a/power_sequencer/operators/utils/draw.py
+++ b/power_sequencer/operators/utils/draw.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 """Drawing utilities. A list of functions to draw common elements"""
 # import bgl
 import blf
diff --git a/power_sequencer/operators/utils/functions.py b/power_sequencer/operators/utils/functions.py
index 0d5c9fcf9fb841b6d23c1b61fc818837a4df8361..c4552c2dd8b7e1822ebd4e4c3ac93e4f13683ed5 100644
--- a/power_sequencer/operators/utils/functions.py
+++ b/power_sequencer/operators/utils/functions.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 from math import floor, sqrt
 from operator import attrgetter
 
@@ -279,9 +276,7 @@ def trim_strips(context, frame_start, frame_end, to_trim, to_delete=[]):
         elif s.frame_final_end > trim_start and s.frame_final_start < trim_start:
             s.frame_final_end = trim_start
 
-    for s in to_delete:
-        bpy.context.sequences.remove(s)
-
+    delete_strips(to_delete)
     for s in initial_selection:
         s.select = True
     return {"FINISHED"}
@@ -331,7 +326,8 @@ def get_sequences_under_cursor(context):
 
 
 def ripple_move(context, sequences, duration_frames, delete=False):
-    """Moves sequences in the list and ripples the change to all sequences after them, in the corresponding channels
+    """
+    Moves sequences in the list and ripples the change to all sequences after them, in the corresponding channels
     The `duration_frames` can be positive or negative.
     If `delete` is True, deletes every sequence in `sequences`.
     """
@@ -344,10 +340,7 @@ def ripple_move(context, sequences, duration_frames, delete=False):
     ]
 
     if delete:
-        bpy.ops.sequencer.select_all(action="DESELECT")
-        for s in sequences:
-            s.select = True
-        bpy.ops.sequencer.delete()
+        delete_strips(sequences)
     else:
         to_ripple = set(to_ripple + sequences)
 
@@ -390,8 +383,20 @@ def find_strips_in_range(frame_start, frame_end, sequences, find_overlapping=Tru
     return strips_inside_range, strips_overlapping_range
 
 
+def delete_strips(to_delete):
+    """
+    Deletes the list of sequences `to_delete`
+    """
+    # Effect strips get deleted with their source so we skip them to avoid errors.
+    to_delete = [s for s in to_delete if s.type in SequenceTypes.CUTABLE]
+    sequences = bpy.context.scene.sequence_editor.sequences
+    for s in to_delete:
+        sequences.remove(s)
+
+
 def move_selection(context, sequences, frame_offset, channel_offset=0):
-    """Offsets the selected `sequences` horizontally and vertically and preserves
+    """
+    Offsets the selected `sequences` horizontally and vertically and preserves
     the current selected sequences.
     """
     if not sequences:
diff --git a/power_sequencer/operators/utils/global_settings.py b/power_sequencer/operators/utils/global_settings.py
index a5c9756b7db8a56326f78c24970a07d8384ae56f..6c5ed55a7ae4832170aeaa4fbe1723b09f6a7f7e 100644
--- a/power_sequencer/operators/utils/global_settings.py
+++ b/power_sequencer/operators/utils/global_settings.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 class ProjectSettings:
     RESOLUTION_X = 1920
     RESOLUTION_Y = 1080
diff --git a/power_sequencer/operators/utils/info_progress_bar.py b/power_sequencer/operators/utils/info_progress_bar.py
index e645c762ab8ee37ce70d2d09f8b8b38b30170cc8..e479aecf62eb0488c36dc39b280830aaafc1aaf3 100644
--- a/power_sequencer/operators/utils/info_progress_bar.py
+++ b/power_sequencer/operators/utils/info_progress_bar.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 
diff --git a/power_sequencer/operators/value_offset.py b/power_sequencer/operators/value_offset.py
index 919e8a43410e5e2729b0097ec8e1ec076dc7c9be..f7ea129fee7ec32d37218ed9c542462b808dd0ef 100644
--- a/power_sequencer/operators/value_offset.py
+++ b/power_sequencer/operators/value_offset.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 from .utils.doc import doc_brief, doc_description, doc_idname, doc_name
@@ -10,8 +7,9 @@ from .utils.functions import convert_duration_to_frames
 
 
 class POWER_SEQUENCER_OT_value_offset(bpy.types.Operator):
-    """Instantly offset selected strips, either using frames or seconds. """ \
-    """Allows to nudge the selection quickly, using keyboard shortcuts"""
+    """Instantly offset selected strips, either using frames or seconds. Allows to
+    nudge the selection quickly, using keyboard shortcuts.
+    """
 
     doc = {
         "name": doc_name(__qualname__),
diff --git a/power_sequencer/tools/__init__.py b/power_sequencer/tools/__init__.py
index e7ac0ddc56093b9c7a82df0b8e7a70da3a792f5d..6c24a950ae12916b55e8bfa53794619326f4c9c7 100644
--- a/power_sequencer/tools/__init__.py
+++ b/power_sequencer/tools/__init__.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import importlib
 import os
 
@@ -16,7 +13,7 @@ def get_tool_classes():
     module_paths = ["." + os.path.splitext(f)[0] for f in module_files]
     classes = []
     for path in module_paths:
-        module = importlib.import_module(path, package="power_sequencer.tools")
+        module = importlib.import_module(path, package=__package__)
         tool_names = [entry for entry in dir(module) if entry.startswith("POWER_SEQUENCER_TOOL")]
         classes.extend([getattr(module, name) for name in tool_names])
     return classes
diff --git a/power_sequencer/tools/trim.py b/power_sequencer/tools/trim.py
index 9c60b2d0c875ef751e9d333b9f7cc2db6d5d9c2c..7b740828a8ded32da9a86bdef9ff70e48a4305a3 100644
--- a/power_sequencer/tools/trim.py
+++ b/power_sequencer/tools/trim.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from bpy.types import WorkSpaceTool
 
diff --git a/power_sequencer/ui/__init__.py b/power_sequencer/ui/__init__.py
index 9b5b5c6bfa8c6ca47aeead8fd8837908d975ce08..b81bdea274b7cfb7f60f7380291ef0763b0b31de 100644
--- a/power_sequencer/ui/__init__.py
+++ b/power_sequencer/ui/__init__.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from .menu_contextual import POWER_SEQUENCER_MT_contextual
 from .menu_toolbar import (
diff --git a/power_sequencer/ui/menu_contextual.py b/power_sequencer/ui/menu_contextual.py
index 416284482b9e0399ad8d88263cae279325e059b2..56b1b33a3e18fb5a2af04d6f0b2ae2724ddd1414 100644
--- a/power_sequencer/ui/menu_contextual.py
+++ b/power_sequencer/ui/menu_contextual.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 from ..operators.utils.global_settings import SequenceTypes
 
diff --git a/power_sequencer/ui/menu_toolbar.py b/power_sequencer/ui/menu_toolbar.py
index 4feb1ea7d17026dd9846c52790ca020f6c8b9c73..6372f1398653a3bf27b4dd11d1aad776bbbf6770 100755
--- a/power_sequencer/ui/menu_toolbar.py
+++ b/power_sequencer/ui/menu_toolbar.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 
 
diff --git a/power_sequencer/utils/addon_auto_imports.py b/power_sequencer/utils/addon_auto_imports.py
index 47f430b90fce7c19a99f2a2a864d480a44932a71..21a45615741d3f48891b939c9e24fb4051fa837a 100644
--- a/power_sequencer/utils/addon_auto_imports.py
+++ b/power_sequencer/utils/addon_auto_imports.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import pkgutil
 import importlib
 
diff --git a/power_sequencer/utils/register_shortcuts.py b/power_sequencer/utils/register_shortcuts.py
index f928f7ffc0dd2fb07f61dedc54101cfe6370119a..29694ceec54ee4b2cf98f87fec9222bf16592fdd 100644
--- a/power_sequencer/utils/register_shortcuts.py
+++ b/power_sequencer/utils/register_shortcuts.py
@@ -1,8 +1,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
-
-# This file is part of Power Sequencer.
-
+# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
 import bpy
 import operator as op
 from .. import operators
diff --git a/precision_drawing_tools/__init__.py b/precision_drawing_tools/__init__.py
index 6cb9ab52f04e256af63c8b77b8695573aafddfb5..6fdb72f206f1003054e674b6d597352336e12d38 100644
--- a/precision_drawing_tools/__init__.py
+++ b/precision_drawing_tools/__init__.py
@@ -12,7 +12,7 @@
 bl_info = {
     "name": "Precision Drawing Tools (PDT)",
     "author": "Alan Odom (Clockmender), Rune Morling (ermo)",
-    "version": (1, 5, 2),
+    "version": (1, 5, 3),
     "blender": (3, 0, 0),
     "location": "View3D > UI > PDT",
     "description": "Precision Drawing Tools for Accurate Modelling",
diff --git a/precision_drawing_tools/pdt_functions.py b/precision_drawing_tools/pdt_functions.py
index 66bf1de2f1e33684967f551320321e989b9ec739..9352b66f7e6f42d205d4850acec7eb2bc88fd01e 100644
--- a/precision_drawing_tools/pdt_functions.py
+++ b/precision_drawing_tools/pdt_functions.py
@@ -8,7 +8,6 @@
 
 import bpy
 import bmesh
-import bgl
 import gpu
 import numpy as np
 from mathutils import Vector, Quaternion
@@ -584,7 +583,7 @@ def draw_3d(coords, gtype, rgba, context):
 
     try:
         if coords is not None:
-            bgl.glEnable(bgl.GL_BLEND)
+            gpu.state.blend_set('ALPHA')
             SHADER.bind()
             SHADER.uniform_float("color", rgba)
             batch.draw(SHADER)
diff --git a/precision_drawing_tools/pdt_tangent.py b/precision_drawing_tools/pdt_tangent.py
index 88abd2d0ee0baafb790c30f38f81e97015e16188..f9ccda51bc7b716e114fc67334911552299b5a95 100644
--- a/precision_drawing_tools/pdt_tangent.py
+++ b/precision_drawing_tools/pdt_tangent.py
@@ -199,8 +199,8 @@ def tangent_setup(context, pg, plane, obj_data, centre_0, centre_1, centre_2, ra
         obj_data: All the data of the chosen object
         centre_0: Centre coordinates of the first arc
         centre_1: Centre coordinates of the second arc
-        centre_2: Coordinates fo the point
-        radius_0: Radius if the first Arc
+        centre_2: Coordinates of the point
+        radius_0: Radius of the first Arc
         radius_1: Radius of the second Arc
 
     Returns:
diff --git a/render_povray/model_meta_topology.py b/render_povray/model_meta_topology.py
index 33a335fa8b237d0f2363ce346d9b83e06dcee250..47e874c52131b2e36ff0141e28ee0ad165892f5b 100644
--- a/render_povray/model_meta_topology.py
+++ b/render_povray/model_meta_topology.py
@@ -180,7 +180,7 @@ def export_meta(file, metas, tab_write, DEF_MAT_NAME):
                 try:
                     one_material = elems[1].data.materials[
                         0
-                    ]  # lame! - blender cant do enything else.
+                    ]  # lame! - blender can't do enything else.
                 except BaseException as e:
                     print(e.__doc__)
                     print('An exception occurred: {}'.format(e))
@@ -236,7 +236,7 @@ def export_meta(file, metas, tab_write, DEF_MAT_NAME):
             importance = ob.pov.importance_value
 
             try:
-                material = meta.materials[0]  # lame! - blender cant do enything else.
+                material = meta.materials[0]  # lame! - blender can't do anything else.
             except:
                 material = None
 
diff --git a/render_povray/model_poly_topology.py b/render_povray/model_poly_topology.py
index c8a990ea3dfe5e68a400084d705bbf69245c5702..b4d200ecfe0d65f2ec28b327681b50358352cd39 100644
--- a/render_povray/model_poly_topology.py
+++ b/render_povray/model_poly_topology.py
@@ -52,7 +52,7 @@ def export_mesh(file,
     except BaseException as e:
         print(e.__doc__)
         print("An exception occurred: {}".format(e))
-        # also happens when curves cant be made into meshes because of no-data
+        # also happens when curves can't be made into meshes because of no-data
         return False  # To continue object loop
     if me:
         me.calc_loop_triangles()
diff --git a/render_povray/particles.py b/render_povray/particles.py
index 18cc0ac708cba2660144585e91d5bfd474487c0c..fdbd46477d2e0655bcf11558eaf8d3627ded077d 100644
--- a/render_povray/particles.py
+++ b/render_povray/particles.py
@@ -75,7 +75,7 @@ def export_hair(file, ob, mod, p_sys, global_matrix):
     # When you render, the entire dependency graph will be
     # evaluated at render resolution, including the particles.
     # In the viewport it will be at viewport resolution.
-    # So there is no need fo render engines to use this function anymore,
+    # So there is no need for render engines to use this function anymore,
     # it's automatic now.
     steps = p_sys_settings.display_step
     steps = 2**steps  # or + 1 # Formerly : len(particle.hair_keys)
diff --git a/render_povray/render_gui.py b/render_povray/render_gui.py
index 7baa7affd8a255fc9d2850e5044f2c5bcace8e96..e00e37054c06c5033d0cedb0abdbf44bf8148e0a 100755
--- a/render_povray/render_gui.py
+++ b/render_povray/render_gui.py
@@ -242,7 +242,7 @@ class RENDER_PT_POV_hues(RenderButtonsPanel, Panel):
 
 
 class RENDER_PT_POV_pattern_rules(RenderButtonsPanel, Panel):
-    """Use this class to change pov sets of texture generating algorythms."""
+    """Use this class to change pov sets of texture generating algorithms."""
 
     bl_label = "Pattern Rules"
     bl_parent_id = "RENDER_PT_POV_render_settings"
diff --git a/rigify/__init__.py b/rigify/__init__.py
index 10e90b4c423711d1b64692c6aacf6b9422cdb370..386c8055dbf10c0b53aae8b656ebf69b75e142a0 100644
--- a/rigify/__init__.py
+++ b/rigify/__init__.py
@@ -539,8 +539,8 @@ def register():
     IDStore.rigify_types = CollectionProperty(type=RigifyName)
     IDStore.rigify_active_type = IntProperty(name="Rigify Active Type", description="The selected rig type")
 
-    bpy.types.Armature.rigify_force_widget_update = BoolProperty(name="Force Widget Update",
-        description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created",
+    bpy.types.Armature.rigify_force_widget_update = BoolProperty(name="Overwrite Widget Meshes",
+        description="Forces Rigify to delete and rebuild all of the rig widget objects. By default, already existing widgets are reused as-is to facilitate manual editing",
         default=False)
 
     bpy.types.Armature.rigify_mirror_widgets = BoolProperty(name="Mirror Widgets",
@@ -550,14 +550,18 @@ def register():
         name="Widgets Collection",
         description="Defines which collection to place widget objects in. If unset, a new one will be created based on the name of the rig")
 
+    bpy.types.Armature.rigify_rig_basename = StringProperty(name="Rigify Rig Name",
+        description="Optional. If specified, this name will be used for the newly generated rig, widget collection and script. Otherwise, a name is generated based on the name of the metarig object by replacing 'metarig' with 'rig', 'META' with 'RIG', or prefixing with 'RIG-'. When updating an already generated rig its name is never changed",
+        default="")
+
     bpy.types.Armature.rigify_target_rig = PointerProperty(type=bpy.types.Object,
         name="Rigify Target Rig",
-        description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created",
+        description="Defines which rig to overwrite. If unset, a new one will be created with name based on the Rig Name option or the name of the metarig",
         poll=lambda self, obj: obj.type == 'ARMATURE' and obj.data is not self)
 
     bpy.types.Armature.rigify_rig_ui = PointerProperty(type=bpy.types.Text,
         name="Rigify Target Rig UI",
-        description="Defines the UI to overwrite. If unset, 'rig_ui.py' will be used")
+        description="Defines the UI to overwrite. If unset, a new one will be created and named based on the name of the rig")
 
     bpy.types.Armature.rigify_finalize_script = PointerProperty(type=bpy.types.Text,
         name="Finalize Script",
diff --git a/rigify/generate.py b/rigify/generate.py
index 3acc2e40bfbcb82439ed36973f5ba26f201bbb54..8a2aa942bacc8ff8c1d8f6bfb4dfa717682302b9 100644
--- a/rigify/generate.py
+++ b/rigify/generate.py
@@ -65,7 +65,9 @@ class Generator(base_generate.BaseGenerator):
 
         target_rig = meta_data.rigify_target_rig
         if not target_rig:
-            if "metarig" in self.metarig.name:
+            if meta_data.rigify_rig_basename:
+                rig_new_name = meta_data.rigify_rig_basename
+            elif "metarig" in self.metarig.name:
                 rig_new_name = self.metarig.name.replace("metarig", "rig")
             elif "META" in self.metarig.name:
                 rig_new_name = self.metarig.name.replace("META", "RIG")
@@ -76,7 +78,7 @@ class Generator(base_generate.BaseGenerator):
             target_rig.display_type = 'WIRE'
 
         # If the object is already added to the scene, switch to its collection
-        if target_rig.name in self.context.scene.collection.all_objects:
+        if target_rig in list(self.context.scene.collection.all_objects):
             self.__switch_to_usable_collection(target_rig)
         else:
             # Otherwise, add to the selected collection or the metarig collection if unusable
@@ -117,11 +119,14 @@ class Generator(base_generate.BaseGenerator):
         wgts_group_name = "WGTS_" + self.obj.name
         old_collection = bpy.data.collections.get(wgts_group_name)
 
+        if old_collection and old_collection.library:
+            old_collection = None
+
         if not old_collection:
             # Update the old 'Widgets' collection
             legacy_collection = bpy.data.collections.get('Widgets')
 
-            if legacy_collection and wgts_group_name in legacy_collection.objects:
+            if legacy_collection and wgts_group_name in legacy_collection.objects and not legacy_collection.library:
                 legacy_collection.name = wgts_group_name
                 old_collection = legacy_collection
 
diff --git a/rigify/metarigs/Animals/horse.py b/rigify/metarigs/Animals/horse.py
index cd393c5c8382b16b5967e222d9f8a74a8959b055..c9be9d540491139b7a2c2ea4c05338d59ff62737 100644
--- a/rigify/metarigs/Animals/horse.py
+++ b/rigify/metarigs/Animals/horse.py
@@ -618,28 +618,28 @@ def create(obj):
     bone.tail = 0.1990, -1.4668, 1.7420
     bone.roll = 0.0000
     bone.use_connect = False
-    bone.parent = arm.edit_bones[bones['ear.L']]
+    bone.parent = arm.edit_bones[bones['head']]
     bones['eye.L'] = bone.name
     bone = arm.edit_bones.new('nose.L')
     bone.head = 0.0450, -1.6240, 1.4228
     bone.tail = 0.1039, -1.6613, 1.4269
     bone.roll = 0.0000
     bone.use_connect = False
-    bone.parent = arm.edit_bones[bones['ear.L']]
+    bone.parent = arm.edit_bones[bones['head']]
     bones['nose.L'] = bone.name
     bone = arm.edit_bones.new('eye.R')
     bone.head = -0.0988, -1.4596, 1.7351
     bone.tail = -0.1990, -1.4668, 1.7420
     bone.roll = -0.0000
     bone.use_connect = False
-    bone.parent = arm.edit_bones[bones['ear.L']]
+    bone.parent = arm.edit_bones[bones['head']]
     bones['eye.R'] = bone.name
     bone = arm.edit_bones.new('nose.R')
     bone.head = -0.0450, -1.6240, 1.4228
     bone.tail = -0.1039, -1.6613, 1.4269
     bone.roll = -0.0000
     bone.use_connect = False
-    bone.parent = arm.edit_bones[bones['ear.L']]
+    bone.parent = arm.edit_bones[bones['head']]
     bones['nose.R'] = bone.name
     bone = arm.edit_bones.new('ear.R.001')
     bone.head = -0.1056, -1.4118, 1.9537
diff --git a/rigify/metarigs/Animals/wolf.py b/rigify/metarigs/Animals/wolf.py
index a2ade91049030c1968907094edde3b06b4d6462f..87d74c87b5e22a50ad7d8b41fe678b076ef4e51f 100644
--- a/rigify/metarigs/Animals/wolf.py
+++ b/rigify/metarigs/Animals/wolf.py
@@ -169,7 +169,7 @@ def create(obj):
 
     bone = arm.edit_bones.new('spine.004')
     bone.head = 0.0000, 0.4085, 0.7928
-    bone.tail = 0.0000, 0.2416, 0.7928
+    bone.tail = 0.0000, 0.2416, 0.7927
     bone.roll = 0.0000
     bone.use_connect = False
     bones['spine.004'] = bone.name
@@ -181,7 +181,7 @@ def create(obj):
     bone.parent = arm.edit_bones[bones['spine.004']]
     bones['spine.003'] = bone.name
     bone = arm.edit_bones.new('spine.005')
-    bone.head = 0.0000, 0.2416, 0.7928
+    bone.head = 0.0000, 0.2416, 0.7927
     bone.tail = 0.0000, 0.1202, 0.7773
     bone.roll = 0.0000
     bone.use_connect = True
@@ -189,14 +189,14 @@ def create(obj):
     bones['spine.005'] = bone.name
     bone = arm.edit_bones.new('spine.002')
     bone.head = 0.0000, 0.5555, 0.7567
-    bone.tail = 0.0000, 0.7816, 0.7412
+    bone.tail = 0.0000, 0.7816, 0.7411
     bone.roll = 0.0000
     bone.use_connect = True
     bone.parent = arm.edit_bones[bones['spine.003']]
     bones['spine.002'] = bone.name
     bone = arm.edit_bones.new('spine.006')
     bone.head = 0.0000, 0.1202, 0.7773
-    bone.tail = 0.0000, 0.0096, 0.7773
+    bone.tail = 0.0000, 0.0096, 0.7772
     bone.roll = 0.0000
     bone.use_connect = True
     bone.parent = arm.edit_bones[bones['spine.005']]
@@ -230,14 +230,14 @@ def create(obj):
     bone.parent = arm.edit_bones[bones['spine.005']]
     bones['thigh.R'] = bone.name
     bone = arm.edit_bones.new('spine.001')
-    bone.head = 0.0000, 0.7816, 0.7412
+    bone.head = 0.0000, 0.7816, 0.7411
     bone.tail = 0.0000, 0.9624, 0.7412
     bone.roll = 0.0000
     bone.use_connect = True
     bone.parent = arm.edit_bones[bones['spine.002']]
     bones['spine.001'] = bone.name
     bone = arm.edit_bones.new('spine.007')
-    bone.head = 0.0000, 0.0096, 0.7773
+    bone.head = 0.0000, 0.0096, 0.7772
     bone.tail = 0.0000, -0.0980, 0.7945
     bone.roll = 0.0000
     bone.use_connect = True
diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py
index b98907ee1d08f1f55d5cb332d8b325ba619b8690..d581805fae4327b0b65590b063d95c72e360e5a5 100644
--- a/rigify/rig_ui_template.py
+++ b/rigify/rig_ui_template.py
@@ -1159,7 +1159,8 @@ class ScriptGenerator(base_generate.GeneratorPlugin):
         if script:
             script.clear()
         else:
-            script = bpy.data.texts.new("rig_ui.py")
+            script_name = self.generator.obj.name + "_ui.py"
+            script = bpy.data.texts.new(script_name)
             metarig.data.rigify_rig_ui = script
 
         for s in OrderedDict.fromkeys(self.ui_imports):
diff --git a/rigify/rigs/limbs/limb_rigs.py b/rigify/rigs/limbs/limb_rigs.py
index fa20dd313934098b4f0b3b1d5397043dfa7cc90e..a094e17675dc28d23b45bfe5b61191d6e8b82362 100644
--- a/rigify/rigs/limbs/limb_rigs.py
+++ b/rigify/rigs/limbs/limb_rigs.py
@@ -52,6 +52,7 @@ class BaseLimbRig(BaseRig):
         self.segments = self.params.segments
         self.bbone_segments = self.params.bbones
         self.use_ik_pivot = self.params.make_custom_pivot
+        self.use_uniform_scale = self.params.limb_uniform_scale
 
         rot_axis = self.params.rotation_axis
 
@@ -144,6 +145,8 @@ class BaseLimbRig(BaseRig):
     #     FK chain parents (or None)
     #   ik_pivot
     #     Custom IK pivot result (optional).
+    #   ik_scale
+    #     Helper bone that implements uniform scaling.
     #   ik_swing
     #     Bone that tracks ik_target to manually handle limb swing.
     #   ik_target
@@ -178,14 +181,15 @@ class BaseLimbRig(BaseRig):
         bone = self.get_bone(self.bones.ctrl.master)
         bone.lock_location = (True, True, True)
         bone.lock_rotation = (True, True, True)
-        bone.lock_scale = (False, False, False)
+        bone.lock_scale = (not self.use_uniform_scale,) * 3
         bone.lock_rotation_w = True
 
     @stage.rig_bones
     def rig_master_control(self):
         mch = self.bones.mch
         self.make_constraint(mch.master, 'COPY_SCALE', 'root', use_make_uniform=True)
-        self.make_constraint(mch.master, 'COPY_SCALE', self.bones.ctrl.master, use_offset=True, space='LOCAL')
+        if self.use_uniform_scale:
+            self.make_constraint(mch.master, 'COPY_SCALE', self.bones.ctrl.master, use_offset=True, space='LOCAL')
 
     @stage.generate_widgets
     def make_master_control_widget(self):
@@ -221,10 +225,12 @@ class BaseLimbRig(BaseRig):
         mch = self.bones.mch.follow
 
         self.make_constraint(mch, 'COPY_SCALE', 'root', use_make_uniform=True)
-        self.make_constraint(
-            mch, 'COPY_SCALE', self.bones.ctrl.master,
-            use_make_uniform=True, use_offset=True, space='LOCAL'
-        )
+
+        if self.use_uniform_scale:
+            self.make_constraint(
+                mch, 'COPY_SCALE', self.bones.ctrl.master,
+                use_make_uniform=True, use_offset=True, space='LOCAL'
+            )
 
         con = self.make_constraint(mch, 'COPY_ROTATION', 'root')
 
@@ -361,9 +367,12 @@ class BaseLimbRig(BaseRig):
 
         self.bones.ctrl.ik_base = self.make_ik_base_bone(orgs)
         self.bones.ctrl.ik_pole = self.make_ik_pole_bone(orgs)
-        self.bones.ctrl.ik = ik_name = self.make_ik_control_bone(orgs)
+        self.bones.ctrl.ik = ik_name = parent = self.make_ik_control_bone(orgs)
+
+        if self.use_uniform_scale:
+            self.bones.mch.ik_scale = parent = self.make_ik_scale_bone(ik_name, orgs)
 
-        self.component_ik_pivot = self.build_ik_pivot(ik_name)
+        self.component_ik_pivot = self.build_ik_pivot(ik_name, parent=parent)
         self.build_ik_parent_switch(SwitchParentBuilder(self.generator))
 
     def make_ik_base_bone(self, orgs):
@@ -382,13 +391,18 @@ class BaseLimbRig(BaseRig):
     def make_ik_control_bone(self, orgs):
         return self.copy_bone(orgs[2], make_derived_name(orgs[2], 'ctrl', '_ik'))
 
+    def make_ik_scale_bone(self, ctrl, orgs):
+        return self.copy_bone(ctrl, make_derived_name(orgs[2], 'mch', '_ik_scale'), scale=1/2)
+
     def build_ik_pivot(self, ik_name, **args):
         if self.use_ik_pivot:
-            return CustomPivotControl(self, 'ik_pivot', ik_name, parent=ik_name, **args)
+            return CustomPivotControl(self, 'ik_pivot', ik_name, **args)
 
     def get_ik_control_output(self):
         if self.component_ik_pivot:
             return self.component_ik_pivot.output
+        elif self.use_uniform_scale:
+            return self.bones.mch.ik_scale
         else:
             return self.bones.ctrl.ik
 
@@ -430,6 +444,9 @@ class BaseLimbRig(BaseRig):
         else:
             self.set_bone_parent(self.bones.ctrl.ik_base, self.bones.mch.ik_swing)
 
+        if self.use_uniform_scale:
+            self.set_bone_parent(self.bones.mch.ik_scale, self.bones.ctrl.ik)
+
         self.set_ik_local_location(self.bones.ctrl.ik)
         self.set_ik_local_location(self.bones.ctrl.ik_pole)
 
@@ -445,11 +462,13 @@ class BaseLimbRig(BaseRig):
     @stage.rig_bones
     def rig_ik_controls(self):
         self.rig_hide_pole_control(self.bones.ctrl.ik_pole)
-        self.rig_ik_control_scale(self.bones.ctrl.ik)
 
-    def rig_ik_control_scale(self, ctrl):
+        if self.use_uniform_scale:
+            self.rig_ik_control_scale(self.bones.mch.ik_scale)
+
+    def rig_ik_control_scale(self, mch):
         self.make_constraint(
-            ctrl, 'COPY_SCALE', self.bones.ctrl.master,
+            mch, 'COPY_SCALE', self.bones.ctrl.master,
             use_make_uniform=True, use_offset=True, space='LOCAL',
         )
 
@@ -937,6 +956,12 @@ class BaseLimbRig(BaseRig):
             description = "Specifies the value of the Local Location option for IK controls, which decides if the location channels are aligned to the local control orientation or world",
         )
 
+        params.limb_uniform_scale = bpy.props.BoolProperty(
+            name        = "Support Uniform Scaling",
+            default     = False,
+            description = "Support uniformly scaling the limb via the gear control at the base"
+        )
+
         # Setting up extra layers for the FK and tweak
         ControlLayersOption.FK.add_parameters(params)
         ControlLayersOption.TWEAK.add_parameters(params)
@@ -958,6 +983,7 @@ class BaseLimbRig(BaseRig):
         r = layout.row()
         r.prop(params, "bbones")
 
+        layout.prop(params, 'limb_uniform_scale')
         layout.prop(params, 'make_custom_pivot', text="Custom IK Pivot")
         layout.prop(params, 'ik_local_location')
 
diff --git a/rigify/rigs/spines/super_head.py b/rigify/rigs/spines/super_head.py
index e2932ad2838c4cd1df4e673b8d9418169b01111c..49f1ce3e5694d7b17d95315092142d0cb8412466 100644
--- a/rigify/rigs/spines/super_head.py
+++ b/rigify/rigs/spines/super_head.py
@@ -292,6 +292,8 @@ class Rig(BaseHeadTailRig):
             influence=nfactor, space='LOCAL'
         )
 
+        self.make_constraint(mch, 'COPY_SCALE', ctrl.neck)
+
     ####################################################
     # Tweak bones
 
diff --git a/rigify/ui.py b/rigify/ui.py
index 68cfd330ff4561887eee9b4183fd6027779ef347..8b719a3f128df405807670538d2e91cccd20b18f 100644
--- a/rigify/ui.py
+++ b/rigify/ui.py
@@ -137,10 +137,20 @@ class DATA_PT_rigify_advanced(bpy.types.Panel):
         armature_id_store = context.object.data
 
         col = layout.column()
-        col.row().prop(armature_id_store, "rigify_target_rig", text="Target Rig")
-        col.row().prop(armature_id_store, "rigify_rig_ui", text="Rig UI Script")
+
+        row = col.row()
+        row.active = not armature_id_store.rigify_target_rig
+        row.prop(armature_id_store, "rigify_rig_basename", text="Rig Name")
+
+        col.separator()
+
+        col2 = col.box().column()
+        col2.label(text="Overwrite Existing:")
+        col2.row().prop(armature_id_store, "rigify_target_rig", text="Target Rig")
+        col2.row().prop(armature_id_store, "rigify_rig_ui", text="Rig UI Script")
+        col2.row().prop(armature_id_store, "rigify_widgets_collection")
+
         col.separator()
-        col.row().prop(armature_id_store, "rigify_widgets_collection")
         col.row().prop(armature_id_store, "rigify_force_widget_update")
         col.row().prop(armature_id_store, "rigify_mirror_widgets")
         col.separator()
diff --git a/rigify/utils/collections.py b/rigify/utils/collections.py
index 8a299a9b5b34350228889182a3179c7b3fba49b0..9eeaac51ba2dacf406538341a30aa4fa1b17fb31 100644
--- a/rigify/utils/collections.py
+++ b/rigify/utils/collections.py
@@ -53,7 +53,7 @@ def ensure_collection(context, collection_name, hidden=False) -> bpy.types.Colle
     active_collection = active_layer_coll.collection
 
     collection = bpy.data.collections.get(collection_name)
-    if not collection:
+    if not collection or collection.library:
         # Create the collection
         collection = bpy.data.collections.new(collection_name)
         collection.hide_viewport = hidden
diff --git a/rigify/utils/node_merger.py b/rigify/utils/node_merger.py
index 617b99df8d287c24d44e8a1c8982fa826873963e..2da48adaf482c056b4a39d81439a340726b90aba 100644
--- a/rigify/utils/node_merger.py
+++ b/rigify/utils/node_merger.py
@@ -73,7 +73,9 @@ class NodeMerger(GeneratorPlugin):
             while pending:
                 added = set()
                 for j in pending:
-                    for co, idx, dist in tree.find_range(nodes[j].point, self.epsilon):
+                    point = nodes[j].point
+                    eps = max(1, point.length) * self.epsilon
+                    for co, idx, dist in tree.find_range(point, eps):
                         added.add(idx)
                 pending = added.difference(merge_set)
                 merge_set.update(added)
diff --git a/rigify/utils/widgets.py b/rigify/utils/widgets.py
index e02f3387e83cf1c0f2b6acb13a1205d7e6fba116..5a16065b6770a6dd4f868c514ceb7b50e7d78caf 100644
--- a/rigify/utils/widgets.py
+++ b/rigify/utils/widgets.py
@@ -86,6 +86,9 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
         if not obj:
             # Search the scene by name
             obj = scene.objects.get(obj_name)
+            if obj and obj.library:
+                local_objs = [obj for obj in scene.objects if obj.name == obj_name and not obj.library]
+                obj = local_objs[0] if local_objs else None
 
         if obj:
             # Record the generated widget
diff --git a/space_view3d_math_vis/__init__.py b/space_view3d_math_vis/__init__.py
index b6aac3cd1c7ecb77f9589646b86ae31c3090690e..ac05ffc602890ffa451c1866292590d854b8d826 100644
--- a/space_view3d_math_vis/__init__.py
+++ b/space_view3d_math_vis/__init__.py
@@ -3,8 +3,8 @@
 bl_info = {
     "name": "Math Vis (Console)",
     "author": "Campbell Barton",
-    "version": (0, 2, 1),
-    "blender": (2, 80, 0),
+    "version": (0, 2, 2),
+    "blender": (3, 0, 0),
     "location": "Properties: Scene > Math Vis Console and Python Console: Menu",
     "description": "Display console defined mathutils variables in the 3D view",
     "doc_url": "{BLENDER_MANUAL_URL}/addons/3d_view/math_vis_console.html",
diff --git a/space_view3d_math_vis/draw.py b/space_view3d_math_vis/draw.py
index 28722a9926153a781ac6ab0a2d3725b4452c6fe6..0b7084ebc965c1641db234575b8f330c63eb97bb 100644
--- a/space_view3d_math_vis/draw.py
+++ b/space_view3d_math_vis/draw.py
@@ -3,7 +3,6 @@
 import bpy
 import blf
 import gpu
-import bgl
 from gpu_extras.batch import batch_for_shader
 
 from . import utils
@@ -29,7 +28,7 @@ COLOR_BOUNDING_BOX_ACTIVE = (1.0, 0.5, 0.0, 1.0)
 def tag_redraw_areas():
     context = bpy.context
 
-    # Py cant access notifers
+    # Py can't access notifers
     for window in context.window_manager.windows:
         for area in window.screen.areas:
             if area.type in ['VIEW_3D', 'PROPERTIES']:
@@ -126,9 +125,9 @@ def draw_callback_view():
     with_bounding_box = not settings.bbox_hide
 
     if settings.in_front:
-        bgl.glDepthFunc(bgl.GL_ALWAYS)
+        gpu.state.depth_test_set('ALWAYS')
     else:
-        bgl.glDepthFunc(bgl.GL_LESS)
+        gpu.state.depth_test_set('LESS')
 
     data_matrix, data_quat, data_euler, data_vector, data_vector_array = utils.console_math_data()
     if settings.index in range(0,len(prop_states)):
diff --git a/sun_position/__init__.py b/sun_position/__init__.py
index 01e22df024802b3d83f04830896684bb27d4a342..5b2a89cec5495eebac209a8d1984c9eb41a8f000 100644
--- a/sun_position/__init__.py
+++ b/sun_position/__init__.py
@@ -29,39 +29,32 @@ if "bpy" in locals():
     importlib.reload(properties)
     importlib.reload(ui_sun)
     importlib.reload(hdr)
+    importlib.reload(translations)
 
 else:
-    from . import properties, ui_sun, hdr
+    from . import properties, ui_sun, hdr, translations
 
 import bpy
 
 
+register_classes, unregister_classes = bpy.utils.register_classes_factory(
+    (properties.SunPosProperties,
+     properties.SunPosAddonPreferences, ui_sun.SUNPOS_OT_AddPreset,
+     ui_sun.SUNPOS_MT_Presets, ui_sun.SUNPOS_PT_Panel,
+     ui_sun.SUNPOS_PT_Location, ui_sun.SUNPOS_PT_Time, hdr.SUNPOS_OT_ShowHdr))
+
+
 def register():
-    bpy.utils.register_class(properties.SunPosProperties)
+    register_classes()
     bpy.types.Scene.sun_pos_properties = (
         bpy.props.PointerProperty(type=properties.SunPosProperties,
-                        name="Sun Position",
-                        description="Sun Position Settings"))
-    bpy.utils.register_class(properties.SunPosAddonPreferences)
-    bpy.utils.register_class(ui_sun.SUNPOS_OT_AddPreset)
-    bpy.utils.register_class(ui_sun.SUNPOS_MT_Presets)
-    bpy.utils.register_class(ui_sun.SUNPOS_PT_Panel)
-    bpy.utils.register_class(ui_sun.SUNPOS_PT_Location)
-    bpy.utils.register_class(ui_sun.SUNPOS_PT_Time)
-    bpy.utils.register_class(hdr.SUNPOS_OT_ShowHdr)
-
+                                  name="Sun Position",
+                                  description="Sun Position Settings"))
     bpy.app.handlers.frame_change_post.append(sun_calc.sun_handler)
-
+    bpy.app.translations.register(__name__, translations.translations_dict)
 
 def unregister():
-    bpy.utils.unregister_class(hdr.SUNPOS_OT_ShowHdr)
-    bpy.utils.unregister_class(ui_sun.SUNPOS_PT_Panel)
-    bpy.utils.unregister_class(ui_sun.SUNPOS_PT_Location)
-    bpy.utils.unregister_class(ui_sun.SUNPOS_PT_Time)
-    bpy.utils.unregister_class(ui_sun.SUNPOS_MT_Presets)
-    bpy.utils.unregister_class(ui_sun.SUNPOS_OT_AddPreset)
-    bpy.utils.unregister_class(properties.SunPosAddonPreferences)
-    del bpy.types.Scene.sun_pos_properties
-    bpy.utils.unregister_class(properties.SunPosProperties)
-
+    bpy.app.translations.unregister(__name__)
     bpy.app.handlers.frame_change_post.remove(sun_calc.sun_handler)
+    del bpy.types.Scene.sun_pos_properties
+    unregister_classes()
diff --git a/sun_position/properties.py b/sun_position/properties.py
index ef3a21e3a8f95ed66f21a819f97d8edfdf51139c..af2734a4c0aa0ea6dbfcf017083bd3d2f8b4b724 100644
--- a/sun_position/properties.py
+++ b/sun_position/properties.py
@@ -136,7 +136,7 @@ class SunPosProperties(PropertyGroup):
 
     object_collection_type: EnumProperty(
         name="Display type",
-        description="Show object group as sun motion",
+        description="Show object collection as sun motion",
         items=(
             ('ANALEMMA', "Analemma", ""),
             ('DIURNAL', "Diurnal", ""),
@@ -174,6 +174,7 @@ class SunPosProperties(PropertyGroup):
         update=sun_update)
 
     bind_to_sun: BoolProperty(
+        name="Bind Texture to Sun",
         description="If true, Environment texture moves with sun",
         default=False,
         update=sun_update)
diff --git a/sun_position/translations.py b/sun_position/translations.py
new file mode 100644
index 0000000000000000000000000000000000000000..c50b797cd2eac735e7209f6458f30de003a4cd04
--- /dev/null
+++ b/sun_position/translations.py
@@ -0,0 +1,493 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# ##### BEGIN AUTOGENERATED I18N SECTION #####
+# NOTE: You can safely move around this auto-generated block (with the begin/end markers!),
+#       and edit the translations by hand.
+#       Just carefully respect the format of the tuple!
+
+# Tuple of tuples:
+# ((msgctxt, msgid), (sources, gen_comments), (lang, translation, (is_fuzzy, comments)), ...)
+translations_tuple = (
+    (("*", ""),
+     ((), ()),
+     ("fr_FR", "Project-Id-Version: Sun Position 3.1.2 (0)\n",
+               (False,
+                ("Blender's translation file (po format).",
+                 "Copyright (C) 2022 The Blender Foundation.",
+                 "This file is distributed under the same license as the Blender package.",
+                 "Damien Picard <dam.pic@free.fr>, 2022."))),
+    ),
+    (("*", "Azimuth and elevation info"),
+     (("bpy.types.SunPosAddonPreferences.show_az_el",),
+      ()),
+     ("fr_FR", "Infos d’azimut et de hauteur",
+               (False, ())),
+    ),
+    (("*", "Show azimuth and solar elevation info"),
+     (("bpy.types.SunPosAddonPreferences.show_az_el",),
+      ()),
+     ("fr_FR", "Afficher les infos d’azimut et de hauteur du soleil",
+               (False, ())),
+    ),
+    (("*", "Daylight savings"),
+     (("bpy.types.SunPosAddonPreferences.show_daylight_savings",
+       "bpy.types.SunPosProperties.use_daylight_savings"),
+      ()),
+     ("fr_FR", "Heure d’été",
+               (False, ())),
+    ),
+    (("*", "Show daylight savings time choice"),
+     (("bpy.types.SunPosAddonPreferences.show_daylight_savings",),
+      ()),
+     ("fr_FR", "Afficher l’option de changement d’heure",
+               (False, ())),
+    ),
+    (("*", "D° M' S\""),
+     (("bpy.types.SunPosAddonPreferences.show_dms",),
+      ()),
+     ("fr_FR", "",
+               (False, ())),
+    ),
+    (("*", "Show lat/long degrees, minutes, seconds labels"),
+     (("bpy.types.SunPosAddonPreferences.show_dms",),
+      ()),
+     ("fr_FR", "Afficher les étiquettes de latitude et longitude en degrés, minutes, secondes",
+               (False, ())),
+    ),
+    (("*", "Show North"),
+     (("bpy.types.SunPosAddonPreferences.show_north",
+       "bpy.types.SunPosProperties.show_north"),
+      ()),
+     ("fr_FR", "Afficher le nord",
+               (False, ())),
+    ),
+    (("*", "Show north offset choice and slider"),
+     (("bpy.types.SunPosAddonPreferences.show_north",),
+      ()),
+     ("fr_FR", "Afficher l’option et le curseur de décalage du nord",
+               (False, ())),
+    ),
+    (("*", "Refraction"),
+     (("bpy.types.SunPosAddonPreferences.show_refraction",),
+      ()),
+     ("fr_FR", "Réfraction",
+               (False, ())),
+    ),
+    (("*", "Show sun refraction choice"),
+     (("bpy.types.SunPosAddonPreferences.show_refraction",),
+      ()),
+     ("fr_FR", "Afficher l’option de réfraction du soleil",
+               (False, ())),
+    ),
+    (("*", "Sunrise and sunset info"),
+     (("bpy.types.SunPosAddonPreferences.show_rise_set",),
+      ()),
+     ("fr_FR", "Infos de lever et coucher",
+               (False, ())),
+    ),
+    (("*", "Show sunrise and sunset labels"),
+     (("bpy.types.SunPosAddonPreferences.show_rise_set",),
+      ()),
+     ("fr_FR", "Afficher les informations de lever et coucher du soleil",
+               (False, ())),
+    ),
+    (("*", "Time and place presets"),
+     (("bpy.types.SunPosAddonPreferences.show_time_place",),
+      ()),
+     ("fr_FR", "Préréglages d’heure et de lieu",
+               (False, ())),
+    ),
+    (("*", "Show time/place presets"),
+     (("bpy.types.SunPosAddonPreferences.show_time_place",),
+      ()),
+     ("fr_FR", "Afficher les préréglages d’heure et de lieu",
+               (False, ())),
+    ),
+    (("*", "Sun Position"),
+     (("bpy.types.Scene.sun_pos_properties",
+       "bpy.types.SUNPOS_PT_Panel",
+       "Add-on Sun Position info: name"),
+      ()),
+     ("fr_FR", "Position du Soleil",
+               (False, ())),
+    ),
+    (("*", "Sun Position Settings"),
+     (("bpy.types.Scene.sun_pos_properties",),
+      ()),
+     ("fr_FR", "Options de Position du Soleil",
+               (False, ())),
+    ),
+    (("*", "Sun Position Presets"),
+     (("bpy.types.SUNPOS_MT_Presets",),
+      ()),
+     ("fr_FR", "Préréglages de position du Soleil",
+               (False, ())),
+    ),
+    (("Operator", "Sync Sun to Texture"),
+     (("bpy.types.WORLD_OT_sunpos_show_hdr",),
+      ()),
+     ("fr_FR", "Synchroniser Soleil et texture",
+               (False, ())),
+    ),
+    (("*", "UTC zone"),
+     (("bpy.types.SunPosProperties.UTC_zone",),
+      ()),
+     ("fr_FR", "Fuseau horaire",
+               (False, ())),
+    ),
+    (("*", "Time zone: Difference from Greenwich, England in hours"),
+     (("bpy.types.SunPosProperties.UTC_zone",),
+      ()),
+     ("fr_FR", "Fuseau horaire : différence avec Greenwich, Angleterre, en heures",
+               (False, ())),
+    ),
+    (("*", "Bind Texture to Sun"),
+     (("bpy.types.SunPosProperties.bind_to_sun",
+       "scripts/addons/sun_position/ui_sun.py:119"),
+      ()),
+     ("fr_FR", "Lier la texture au Soleil",
+               (False, ())),
+    ),
+    (("*", "If true, Environment texture moves with sun"),
+     (("bpy.types.SunPosProperties.bind_to_sun",),
+      ()),
+     ("fr_FR", "Si actif, la texture d’environnement tourne avec le Soleil",
+               (False, ())),
+    ),
+    (("*", "Enter coordinates"),
+     (("bpy.types.SunPosProperties.co_parser",),
+      ()),
+     ("fr_FR", "Saisir coordonnées",
+               (False, ())),
+    ),
+    (("*", "Enter coordinates from an online map"),
+     (("bpy.types.SunPosProperties.co_parser",),
+      ()),
+     ("fr_FR", "Saisir des coordonnées depuis une carte",
+               (False, ())),
+    ),
+    (("*", "Day"),
+     (("bpy.types.SunPosProperties.day",),
+      ()),
+     ("fr_FR", "Jour",
+               (False, ())),
+    ),
+    (("*", "Day of year"),
+     (("bpy.types.SunPosProperties.day_of_year",),
+      ()),
+     ("fr_FR", "Jour de l’année",
+               (False, ())),
+    ),
+    (("*", "Rotation angle of sun and environment texture"),
+     (("bpy.types.SunPosProperties.hdr_azimuth",),
+      ()),
+     ("fr_FR", "Angle de rotation du Soleil et de la texture d’environnement",
+               (False, ())),
+    ),
+    (("*", "Elevation"),
+     (("bpy.types.SunPosProperties.hdr_elevation",),
+      ()),
+     ("fr_FR", "Hauteur",
+               (False, ())),
+    ),
+    (("*", "Elevation angle of sun"),
+     (("bpy.types.SunPosProperties.hdr_elevation",),
+      ()),
+     ("fr_FR", "Angle de hauteur du Soleil",
+               (False, ())),
+    ),
+    (("*", "Name of texture to use. World nodes must be enabled and color set to Environment Texture"),
+     (("bpy.types.SunPosProperties.hdr_texture",),
+      ()),
+     ("fr_FR", "Nom de la texture à utiliser. Les nœuds de shader du monde doivent être activés, et la couleur utiliser une texture d’environnement",
+               (False, ())),
+    ),
+    (("*", "Latitude"),
+     (("bpy.types.SunPosProperties.latitude",),
+      ()),
+     ("fr_FR", "Latitude",
+               (False, ())),
+    ),
+    (("*", "Latitude: (+) Northern (-) Southern"),
+     (("bpy.types.SunPosProperties.latitude",),
+      ()),
+     ("fr_FR", "Latitude : (+) nord (-) sud",
+               (False, ())),
+    ),
+    (("*", "Longitude"),
+     (("bpy.types.SunPosProperties.longitude",),
+      ()),
+     ("fr_FR", "Longitude",
+               (False, ())),
+    ),
+    (("*", "Longitude: (-) West of Greenwich (+) East of Greenwich"),
+     (("bpy.types.SunPosProperties.longitude",),
+      ()),
+     ("fr_FR", "Longitude : (-) ouest depuis Greenwich (+) est depuis Greenwich",
+               (False, ())),
+    ),
+    (("*", "Month"),
+     (("bpy.types.SunPosProperties.month",),
+      ()),
+     ("fr_FR", "Mois",
+               (False, ())),
+    ),
+    (("*", "North Offset"),
+     (("bpy.types.SunPosProperties.north_offset",),
+      ()),
+     ("fr_FR", "Décalage du nord",
+               (False, ())),
+    ),
+    (("*", "Rotate the scene to choose North direction"),
+     (("bpy.types.SunPosProperties.north_offset",),
+      ()),
+     ("fr_FR", "Tourner la scène pour choisir la direction du nord",
+               (False, ())),
+    ),
+    (("*", "Collection of objects used to visualize sun motion"),
+     (("bpy.types.SunPosProperties.object_collection",),
+      ()),
+     ("fr_FR", "Collection d’objets utilisée pour visualiser la trajectoire du Soleil",
+               (False, ())),
+    ),
+    (("*", "Show object collection as sun motion"),
+     (("bpy.types.SunPosProperties.object_collection_type",),
+      ()),
+     ("fr_FR", "Afficher la collection en tant que",
+               (False, ())),
+    ),
+    (("*", "Analemma"),
+     (("bpy.types.SunPosProperties.object_collection_type:'ANALEMMA'",),
+      ()),
+     ("fr_FR", "Analemme",
+               (False, ())),
+    ),
+    (("*", "Diurnal"),
+     (("bpy.types.SunPosProperties.object_collection_type:'DIURNAL'",),
+      ()),
+     ("fr_FR", "Diurne",
+               (False, ())),
+    ),
+    (("*", "Draw line pointing north"),
+     (("bpy.types.SunPosProperties.show_north",),
+      ()),
+     ("fr_FR", "Afficher une ligne pointant le nord",
+               (False, ())),
+    ),
+    (("*", "Name of sky texture to be used"),
+     (("bpy.types.SunPosProperties.sky_texture",),
+      ()),
+     ("fr_FR", "Nom de la texture à utiliser",
+               (False, ())),
+    ),
+    (("*", "Distance to sun from origin"),
+     (("bpy.types.SunPosProperties.sun_distance",),
+      ()),
+     ("fr_FR", "Distance entre l’origine et le Soleil",
+               (False, ())),
+    ),
+    (("*", "Sun Object"),
+     (("bpy.types.SunPosProperties.sun_object",
+       "scripts/addons/sun_position/ui_sun.py:101"),
+      ()),
+     ("fr_FR", "Objet soleil",
+               (False, ())),
+    ),
+    (("*", "Sun object to set in the scene"),
+     (("bpy.types.SunPosProperties.sun_object",),
+      ()),
+     ("fr_FR", "Objet soleil à utiliser dans la scène",
+               (False, ())),
+    ),
+    (("*", "Time of the day"),
+     (("bpy.types.SunPosProperties.time",),
+      ()),
+     ("fr_FR", "Heure du jour",
+               (False, ())),
+    ),
+    (("*", "Time Spread"),
+     (("bpy.types.SunPosProperties.time_spread",),
+      ()),
+     ("fr_FR", "Plage horaire",
+               (False, ())),
+    ),
+    (("*", "Time period in which to spread object collection"),
+     (("bpy.types.SunPosProperties.time_spread",),
+      ()),
+     ("fr_FR", "Plage horaire à visualiser par les objets de la collection",
+               (False, ())),
+    ),
+    (("*", "Usage mode"),
+     (("bpy.types.SunPosProperties.usage_mode",),
+      ()),
+     ("fr_FR", "Mode",
+               (False, ())),
+    ),
+    (("*", "Operate in normal mode or environment texture mode"),
+     (("bpy.types.SunPosProperties.usage_mode",),
+      ()),
+     ("fr_FR", "Passer en mode normal ou texture d’environnement",
+               (False, ())),
+    ),
+    (("*", "Sun + HDR texture"),
+     (("bpy.types.SunPosProperties.usage_mode:'HDR'",),
+      ()),
+     ("fr_FR", "Soleil + texture HDRI",
+               (False, ())),
+    ),
+    (("*", "Use day of year"),
+     (("bpy.types.SunPosProperties.use_day_of_year",),
+      ()),
+     ("fr_FR", "Utiliser le jour de l’année",
+               (False, ())),
+    ),
+    (("*", "Use a single value for day of year"),
+     (("bpy.types.SunPosProperties.use_day_of_year",),
+      ()),
+     ("fr_FR", "Utiliser une seule valeur pour le jour de l’année",
+               (False, ())),
+    ),
+    (("*", "Daylight savings time adds 1 hour to standard time"),
+     (("bpy.types.SunPosProperties.use_daylight_savings",),
+      ()),
+     ("fr_FR", "L’heure d’été ajoute une heure à l’heure standard",
+               (False, ())),
+    ),
+    (("*", "Use refraction"),
+     (("bpy.types.SunPosProperties.use_refraction",),
+      ()),
+     ("fr_FR", "Utiliser la réfraction",
+               (False, ())),
+    ),
+    (("*", "Show apparent sun position due to refraction"),
+     (("bpy.types.SunPosProperties.use_refraction",),
+      ()),
+     ("fr_FR", "Afficher la position apparente du Soleil due à la réfraction",
+               (False, ())),
+    ),
+    (("*", "Year"),
+     (("bpy.types.SunPosProperties.year",),
+      ()),
+     ("fr_FR", "Année",
+               (False, ())),
+    ),
+    (("*", "Could not find 3D View"),
+     (("scripts/addons/sun_position/hdr.py:262",),
+      ()),
+     ("fr_FR", "Impossible de trouver la vue 3D",
+               (False, ())),
+    ),
+    (("*", "Please select an Environment Texture node"),
+     (("scripts/addons/sun_position/hdr.py:268",),
+      ()),
+     ("fr_FR", "Veuillez utiliser un nœud de texture d’environnement",
+               (False, ())),
+    ),
+    (("*", "Unknown projection"),
+     (("scripts/addons/sun_position/hdr.py:180",),
+      ()),
+     ("fr_FR", "Projection inconnue",
+               (False, ())),
+    ),
+    (("*", "Show options or labels:"),
+     (("scripts/addons/sun_position/properties.py:242",),
+      ()),
+     ("fr_FR", "Afficher les options et étiquettes :",
+               (False, ())),
+    ),
+    (("*", "Usage Mode"),
+     (("scripts/addons/sun_position/ui_sun.py:71",),
+      ()),
+     ("fr_FR", "Mode",
+               (False, ())),
+    ),
+    (("*", "Environment Texture"),
+     (("scripts/addons/sun_position/ui_sun.py:85",),
+      ()),
+     ("fr_FR", "Texture d’environnement",
+               (False, ())),
+    ),
+    (("*", "Enter Coordinates"),
+     (("scripts/addons/sun_position/ui_sun.py:174",),
+      ()),
+     ("fr_FR", "Saisir coordonnées",
+               (False, ())),
+    ),
+    (("*", "Local:"),
+     (("scripts/addons/sun_position/ui_sun.py:269",),
+      ()),
+     ("fr_FR", "Locale :",
+               (False, ())),
+    ),
+    (("*", "UTC:"),
+     (("scripts/addons/sun_position/ui_sun.py:272",),
+      ()),
+     ("fr_FR", "UTC : ",
+               (False, ())),
+    ),
+    (("*", "Please select World in the World panel."),
+     (("scripts/addons/sun_position/ui_sun.py:95",
+       "scripts/addons/sun_position/ui_sun.py:153"),
+      ()),
+     ("fr_FR", "Veuillez sélectionner le monde dans le panneau Monde",
+               (False, ())),
+    ),
+    (("*", "Release binding"),
+     (("scripts/addons/sun_position/ui_sun.py:116",),
+      ()),
+     ("fr_FR", "Annuler le lien",
+               (False, ())),
+    ),
+    (("*", "Azimuth:"),
+     (("scripts/addons/sun_position/ui_sun.py:205",),
+      ()),
+     ("fr_FR", "Azimut :",
+               (False, ())),
+    ),
+    (("*", "Elevation:"),
+     (("scripts/addons/sun_position/ui_sun.py:208",),
+      ()),
+     ("fr_FR", "Hauteur :",
+               (False, ())),
+    ),
+    (("*", "Sunrise:"),
+     (("scripts/addons/sun_position/ui_sun.py:284",),
+      ()),
+     ("fr_FR", "Lever : ",
+               (False, ())),
+    ),
+    (("*", "Sunset:"),
+     (("scripts/addons/sun_position/ui_sun.py:287",),
+      ()),
+     ("fr_FR", "Coucher : ",
+               (False, ())),
+    ),
+    (("*", "Please activate Use Nodes in the World panel."),
+     (("scripts/addons/sun_position/ui_sun.py:92",
+       "scripts/addons/sun_position/ui_sun.py:150"),
+      ()),
+     ("fr_FR", "Veuillez activer Utiliser nœuds dans le panneau Monde",
+               (False, ())),
+    ),
+    (("*", "World > Sun Position"),
+     (("Add-on Sun Position info: location",),
+      ()),
+     ("fr_FR", "Monde > Position du Soleil",
+               (False, ())),
+    ),
+    (("*", "Show sun position with objects and/or sky texture"),
+     (("Add-on Sun Position info: description",),
+      ()),
+     ("fr_FR", "Afficher la position du Soleil avec des objets ou une texture de ciel",
+               (False, ())),
+    ),
+)
+
+translations_dict = {}
+for msg in translations_tuple:
+    key = msg[0]
+    for lang, trans, (is_fuzzy, comments) in msg[2:]:
+        if trans and not is_fuzzy:
+            translations_dict.setdefault(lang, {})[key] = trans
+
+# ##### END AUTOGENERATED I18N SECTION #####
diff --git a/sun_position/ui_sun.py b/sun_position/ui_sun.py
index 1f4f8f3405a41ae6bc7fbb0375802a63948fb9da..c6eebc338e3aa4ae48a66f0916c5ff1e5229ecf2 100644
--- a/sun_position/ui_sun.py
+++ b/sun_position/ui_sun.py
@@ -112,11 +112,11 @@ class SUNPOS_PT_Panel(bpy.types.Panel):
 
         col = flow.column(align=True)
         if sp.bind_to_sun:
-            prop_text="Release binding"
+            col.prop(sp, "bind_to_sun", toggle=True, icon="CONSTRAINT",
+                     text="Release binding")
         else:
-            prop_text="Bind Texture to Sun "
-        col.prop(sp, "bind_to_sun", toggle=True, icon="CONSTRAINT",
-                 text=prop_text)
+            col.prop(sp, "bind_to_sun", toggle=True, icon="CONSTRAINT",
+                     text="Bind Texture to Sun")
 
         row = col.row(align=True)
         row.enabled = not sp.bind_to_sun
@@ -201,14 +201,12 @@ class SUNPOS_PT_Location(bpy.types.Panel):
 
         if p.show_az_el:
             col = flow.column(align=True)
-            row = col.row()
-            row.alignment = 'RIGHT'
-            row.label(text="Azimuth: " +
-                     str(round(sun.azimuth, 3)) + "°")
-            row = col.row()
-            row.alignment = 'RIGHT'
-            row.label(text="Elevation: " +
-                     str(round(sun.elevation, 3)) + "°")
+            split = col.split(factor=0.4, align=True)
+            split.label(text="Azimuth:")
+            split.label(text=str(round(sun.azimuth, 3)) + "°")
+            split = col.split(factor=0.4, align=True)
+            split.label(text="Elevation:")
+            split.label(text=str(round(sun.elevation, 3)) + "°")
             col.separator()
 
         if p.show_refraction:
@@ -266,16 +264,27 @@ class SUNPOS_PT_Time(bpy.types.Panel):
                          sp.longitude,
                          sp.UTC_zone)
         col.alignment = 'CENTER'
-        col.label(text="Local: " + lt, icon='TIME')
-        col.label(text="  UTC: " + ut, icon='PREVIEW_RANGE')
+
+        split = col.split(factor=0.5, align=True)
+        split.label(text="Local:", icon='TIME')
+        split.label(text=lt)
+        split = col.split(factor=0.5, align=True)
+        split.label(text="UTC:", icon='PREVIEW_RANGE')
+        split.label(text=ut)
         col.separator()
 
+
         col = flow.column(align=True)
         col.alignment = 'CENTER'
         if p.show_rise_set:
             sr = format_hms(sun.sunrise.time)
             ss = format_hms(sun.sunset.time)
-            tsr = "Sunrise: " + sr
-            tss = " Sunset: " + ss
-            col.label(text=tsr, icon='LIGHT_SUN')
-            col.label(text=tss, icon='SOLO_ON')
+
+            split = col.split(factor=0.5, align=True)
+            split.label(text="Sunrise:", icon='LIGHT_SUN')
+            split.label(text=sr)
+            split = col.split(factor=0.5, align=True)
+            split.label(text="Sunset:", icon='SOLO_ON')
+            split.label(text=ss)
+
+        col.separator()
diff --git a/system_demo_mode/__init__.py b/system_demo_mode/__init__.py
index 9af8b2177f89924948709828f10ca4b26348598f..7187053271a7fdb2279c09c4ed7a0b3704ab6b45 100644
--- a/system_demo_mode/__init__.py
+++ b/system_demo_mode/__init__.py
@@ -190,7 +190,7 @@ class DemoModeRun(bpy.types.Operator):
         if extern_demo_mode_run():
             return {'FINISHED'}
         else:
-            self.report({'ERROR'}, "Cant load demo.py config, run: File -> Demo Mode (Setup)")
+            self.report({'ERROR'}, "Can't load demo.py config, run: File -> Demo Mode (Setup)")
             return {'CANCELLED'}
 
 
diff --git a/system_demo_mode/config.py b/system_demo_mode/config.py
index 3c0550ee0fb7f036b6a9ec5d292ac34ef4bc3542..5d9399590af7d511a3bd1d4018bb5e5ff03b8745 100644
--- a/system_demo_mode/config.py
+++ b/system_demo_mode/config.py
@@ -40,7 +40,7 @@ def as_string(dirpath, random_order, exit, **kwargs):
         "# generated file\n",
         "\n",
         "# edit the search path so other systems may find the files below\n",
-        "# based on name only if the absolute paths cant be found\n",
+        "# based on name only if the absolute paths cannot be found\n",
         "# Use '//' for current blend file path.\n",
         "\n",
         "search_path = %r\n" % dirpath,
diff --git a/system_demo_mode/demo_mode.py b/system_demo_mode/demo_mode.py
index 4def3031b20c0b42131d4733e75703030ea8864f..f412869b903722c778df003eb76d2eb12a10d365 100644
--- a/system_demo_mode/demo_mode.py
+++ b/system_demo_mode/demo_mode.py
@@ -523,7 +523,7 @@ def load_config(cfg_name=DEMO_CFG):
         if not os.path.exists(filepath_test):
             filepath_test = lookup_file(filepath_test)  # attempt to get from searchpath
         if not os.path.exists(filepath_test):
-            print("Cant find %r or %r, skipping!")
+            print("Can't find %r or %r, skipping!")
             continue
 
         filecfg["file"] = os.path.normpath(filepath_test)
diff --git a/system_property_chart.py b/system_property_chart.py
index b1fa54c208846a107ef9522171c344211e0ac1f7..bb6d4682151bcd7fd56e3b6b1d78f913fe0c8603 100644
--- a/system_property_chart.py
+++ b/system_property_chart.py
@@ -219,16 +219,26 @@ def _property_chart_copy(self, context):
 
     data_path = self.data_path
 
-    # quick & nasty method!
+    data_path_with_dot = data_path
+    if not data_path_with_dot.startswith("["):
+        data_path_with_dot = "." + data_path_with_dot
+
+    code = compile(
+        "obj_iter%s = obj%s" % (data_path_with_dot, data_path_with_dot),
+        "<property_chart>",
+        'exec',
+    )
+
     for obj_iter in selected_objects:
         if obj != obj_iter:
             try:
-                exec("obj_iter.%s = obj.%s" % (data_path, data_path))
+                exec(code, {}, {"obj": obj, "obj_iter": obj_iter})
             except:
                 # just in case we need to know what went wrong!
                 import traceback
                 traceback.print_exc()
 
+
 from bpy.props import StringProperty
 
 
@@ -252,6 +262,7 @@ class CopyPropertyChart(Operator):
 
 # List The Classes #
 
+
 classes = (
     AddPresetProperties,
     SCENE_MT_properties_presets,
@@ -260,6 +271,7 @@ classes = (
     CopyPropertyChart
 )
 
+
 def register():
     for cls in classes:
         bpy.utils.register_class(cls)
diff --git a/viewport_vr_preview/__init__.py b/viewport_vr_preview/__init__.py
index eb7dca4dc4dd91f3dc9899fac6f4a1666450cff3..cc4580ae07389194dc65b7a59990dd67c9dcbe2f 100644
--- a/viewport_vr_preview/__init__.py
+++ b/viewport_vr_preview/__init__.py
@@ -3,7 +3,7 @@
 bl_info = {
     "name": "VR Scene Inspection",
     "author": "Julian Eisel (Severin), Sebastian Koenig, Peter Kim (muxed-reality)",
-    "version": (0, 11, 0),
+    "version": (0, 11, 1),
     "blender": (3, 2, 0),
     "location": "3D View > Sidebar > VR",
     "description": ("View the viewport with virtual reality glasses "
diff --git a/viewport_vr_preview/operators.py b/viewport_vr_preview/operators.py
index 735d2bd960c2c5f16f55c5f1366fd49c43ee602e..8a0e1a2a2433bdf34d1386fbc0b836cadf12387b 100644
--- a/viewport_vr_preview/operators.py
+++ b/viewport_vr_preview/operators.py
@@ -15,7 +15,6 @@ from bpy.types import (
     Operator,
 )
 from bpy_extras.io_utils import ExportHelper, ImportHelper
-import bgl
 import math
 from math import radians
 from mathutils import Euler, Matrix, Quaternion, Vector
@@ -962,8 +961,8 @@ class VIEW3D_GT_vr_camera_cone(Gizmo):
                 'LINES', lines_shape_verts)
 
         # Ensure correct GL state (otherwise other gizmos might mess that up)
-        bgl.glLineWidth(1)
-        bgl.glEnable(bgl.GL_BLEND)
+        gpu.state.line_width_set(1.0)
+        gpu.state.blend_set('ALPHA')
 
         self.draw_custom_shape(self.frame_shape)
         self.draw_custom_shape(self.lines_shape)
@@ -973,8 +972,8 @@ class VIEW3D_GT_vr_controller_grip(Gizmo):
     bl_idname = "VIEW_3D_GT_vr_controller_grip"
 
     def draw(self, context):
-        bgl.glLineWidth(1)
-        bgl.glEnable(bgl.GL_BLEND)
+        gpu.state.line_width_set(1.0)
+        gpu.state.blend_set('ALPHA')
 
         self.color = 0.422, 0.438, 0.446
         self.draw_preset_circle(self.matrix_basis, axis='POS_X')
@@ -986,8 +985,8 @@ class VIEW3D_GT_vr_controller_aim(Gizmo):
     bl_idname = "VIEW_3D_GT_vr_controller_aim"
 
     def draw(self, context):
-        bgl.glLineWidth(1)
-        bgl.glEnable(bgl.GL_BLEND)
+        gpu.state.line_width_set(1.0)
+        gpu.state.blend_set('ALPHA')
 
         self.color = 1.0, 0.2, 0.322
         self.draw_preset_arrow(self.matrix_basis, axis='POS_X')