diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index 804d0159b70d31f7095f1e8fcee8bc24d8639d6f..4a5344d84608316d1fc999b6624cabe1b9ac54f1 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -18,6 +18,7 @@
 
 import os
 import bpy
+import datetime
 from bpy_extras.io_utils import ImportHelper, ExportHelper
 from bpy.types import Operator, AddonPreferences
 
@@ -158,11 +159,11 @@ class ExportGLTF2_Base:
         default=False
     )
 
-    export_layers: BoolProperty(
-        name='Export all layers',
-        description='',
-        default=True
-    )
+    # export_layers: BoolProperty(
+    #     name='Export all layers',
+    #     description='',
+    #     default=True
+    # )
 
     export_extras: BoolProperty(
         name='Export extras',
@@ -232,6 +233,12 @@ class ExportGLTF2_Base:
         default=False
     )
 
+    export_all_influences: BoolProperty(
+        name='Export all bone influences',
+        description='Export more than four joint vertex influences',
+        default=False
+    )
+
     export_morph: BoolProperty(
         name='Export morphing',
         description='',
@@ -307,6 +314,8 @@ class ExportGLTF2_Base:
         # All custom export settings are stored in this container.
         export_settings = {}
 
+        export_settings['timestamp'] = datetime.datetime.now()
+
         export_settings['gltf_filepath'] = bpy.path.ensure_ext(self.filepath, self.filename_ext)
         export_settings['gltf_filedirectory'] = os.path.dirname(export_settings['gltf_filepath']) + '/'
 
@@ -328,7 +337,7 @@ class ExportGLTF2_Base:
         else:
             export_settings['gltf_camera_infinite'] = False
         export_settings['gltf_selected'] = self.export_selected
-        export_settings['gltf_layers'] = self.export_layers
+        export_settings['gltf_layers'] = True #self.export_layers
         export_settings['gltf_extras'] = self.export_extras
         export_settings['gltf_yup'] = self.export_yup
         export_settings['gltf_apply'] = self.export_apply
@@ -346,8 +355,10 @@ class ExportGLTF2_Base:
         export_settings['gltf_skins'] = self.export_skins
         if self.export_skins:
             export_settings['gltf_bake_skins'] = self.export_bake_skins
+            export_settings['gltf_all_vertex_influences'] = self.export_all_influences
         else:
             export_settings['gltf_bake_skins'] = False
+            export_settings['gltf_all_vertex_influences'] = False
         export_settings['gltf_frame_step'] = self.export_frame_step
         export_settings['gltf_morph'] = self.export_morph
         if self.export_morph:
@@ -384,7 +395,7 @@ class ExportGLTF2_Base:
         col = layout.box().column()
         col.label(text='Nodes:')  # , icon='OOPS')
         col.prop(self, 'export_selected')
-        col.prop(self, 'export_layers')
+        #col.prop(self, 'export_layers')
         col.prop(self, 'export_extras')
         col.prop(self, 'export_yup')
 
@@ -413,6 +424,10 @@ class ExportGLTF2_Base:
         col.prop(self, 'export_materials')
         col.prop(self, 'export_texture_transform')
 
+        col = layout.box().column()
+        col.label(text='Lights:')  # , icon='LIGHT_DATA')
+        col.prop(self, 'export_lights')
+
         col = layout.box().column()
         col.label(text='Animation:')  # , icon='OUTLINER_DATA_POSE')
         col.prop(self, 'export_animations')
@@ -426,19 +441,13 @@ class ExportGLTF2_Base:
         col.prop(self, 'export_skins')
         if self.export_skins:
             col.prop(self, 'export_bake_skins')
+            col.prop(self, 'export_all_influences')
         col.prop(self, 'export_morph')
         if self.export_morph:
             col.prop(self, 'export_morph_normal')
             if self.export_morph_normal:
                 col.prop(self, 'export_morph_tangent')
 
-        addon_prefs = context.user_preferences.addons[__name__].preferences
-        if addon_prefs.experimental:
-            col = layout.box().column()
-            col.label(text='Experimental:')  # , icon='RADIO')
-            col.prop(self, 'export_lights')
-            col.prop(self, 'export_displacement')
-
         row = layout.row()
         row.operator(
             GLTF2ExportSettings.bl_idname,
@@ -474,16 +483,6 @@ def menu_func_export(self, context):
     self.layout.operator(ExportGLTF2_GLB.bl_idname, text='Binary glTF 2.0 (.glb)')
 
 
-class ExportGLTF2_AddonPreferences(AddonPreferences):
-    bl_idname = __name__
-
-    experimental: BoolProperty(name='Enable experimental glTF export settings', default=False)
-
-    def draw(self, context):
-        layout = self.layout
-        layout.prop(self, "experimental")
-
-
 class ImportglTF2(Operator, ImportHelper):
     bl_idname = 'import_scene.gltf'
     bl_label = "glTF 2.0 (.gltf/.glb)"
@@ -549,7 +548,6 @@ classes = (
     GLTF2ExportSettings,
     ExportGLTF2_GLTF,
     ExportGLTF2_GLB,
-    ExportGLTF2_AddonPreferences,
     ImportglTF2
 )
 
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 4941ffe2912829167beb15a18d322603044cf3c6..b89f51d700c4b1ca85a86ac0cb1a8a73267cea16 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
@@ -98,7 +98,7 @@ def __get_image_data(sockets_or_slots):
     # For shared ressources, such as images, we just store the portion of data that is needed in the glTF property
     # in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary
     # ressources.
-    def split_pixels_by_channels(image: bpy.types.Image) -> typing.Iterable[typing.Iterable[float]]:
+    def split_pixels_by_channels(image: bpy.types.Image) -> typing.List[typing.List[float]]:
         pixels = np.array(image.pixels)
         pixels = pixels.reshape((pixels.shape[0] // image.channels, image.channels))
         channels = np.split(pixels, pixels.shape[1], axis=1)
@@ -139,7 +139,7 @@ def __get_image_data(sockets_or_slots):
         return image
     elif __is_slot(sockets_or_slots):
         texture = __get_tex_from_slot(sockets_or_slots[0])
-        pixels = texture.image.pixels
+        pixels = split_pixels_by_channels(texture.image)
 
         image_data = gltf2_io_image_data.ImageData(
             texture.name,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_light_spots.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_light_spots.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b1f95e2c0e906adc247123b8e567b08975368bc
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_light_spots.py
@@ -0,0 +1,50 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from typing import Optional, List, Any
+
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+
+from io_scene_gltf2.io.com import gltf2_io_lights_punctual
+from io_scene_gltf2.io.com import gltf2_io_debug
+
+
+def gather_light_spot(blender_lamp, export_settings) -> Optional[gltf2_io_lights_punctual.LightSpot]:
+
+    if not __filter_light_spot(blender_lamp, export_settings):
+        return None
+
+    spot = gltf2_io_lights_punctual.LightSpot(
+        inner_cone_angle=__gather_inner_cone_angle(blender_lamp, export_settings),
+        outer_cone_angle=__gather_outer_cone_angle(blender_lamp, export_settings)
+    )
+    return spot
+
+
+def __filter_light_spot(blender_lamp, _) -> bool:
+    if blender_lamp.type != "SPOT":
+        return False
+
+    return True
+
+
+def __gather_inner_cone_angle(blender_lamp, _) -> Optional[float]:
+    angle = blender_lamp.spot_size * 0.5
+    return angle - angle * blender_lamp.spot_blend
+
+
+def __gather_outer_cone_angle(blender_lamp, _) -> Optional[float]:
+    return blender_lamp.spot_size * 0.5
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py
new file mode 100644
index 0000000000000000000000000000000000000000..5cc3dc62ba9c887970e8369e3d158844af750473
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py
@@ -0,0 +1,131 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import math
+from typing import Optional, List, Dict, Any
+
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+
+from io_scene_gltf2.io.com import gltf2_io_lights_punctual
+from io_scene_gltf2.io.com import gltf2_io_debug
+
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_light_spots
+from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
+
+@cached
+def gather_lights_punctual(blender_lamp, export_settings) -> Optional[Dict[str, Any]]:
+    if not __filter_lights_punctual(blender_lamp, export_settings):
+        return None
+
+    light = gltf2_io_lights_punctual.Light(
+        color=__gather_color(blender_lamp, export_settings),
+        intensity=__gather_intensity(blender_lamp, export_settings),
+        spot=__gather_spot(blender_lamp, export_settings),
+        type=__gather_type(blender_lamp, export_settings),
+        range=__gather_range(blender_lamp, export_settings),
+        name=__gather_name(blender_lamp, export_settings),
+        extensions=__gather_extensions(blender_lamp, export_settings),
+        extras=__gather_extras(blender_lamp, export_settings)
+    )
+
+    return light.to_dict()
+
+
+def __filter_lights_punctual(blender_lamp, export_settings) -> bool:
+    if blender_lamp.type in ["HEMI", "AREA"]:
+        gltf2_io_debug.print_console("WARNING", "Unsupported light source {}".format(blender_lamp.type))
+        return False
+
+    return True
+
+
+def __gather_color(blender_lamp, export_settings) -> Optional[List[float]]:
+    emission_node = __get_cycles_emission_node(blender_lamp)
+    if emission_node is not None:
+        return emission_node.inputs["Color"].default_value
+
+    return list(blender_lamp.color)
+
+
+def __gather_intensity(blender_lamp, _) -> Optional[float]:
+    emission_node = __get_cycles_emission_node(blender_lamp)
+    if emission_node is not None:
+        if blender_lamp.type != 'SUN':
+            # When using cycles, the strength should be influenced by a LightFalloff node
+            result = gltf2_blender_search_node_tree.from_socket(
+                emission_node.get("Strength"),
+                gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeLightFalloff)
+            )
+            if result:
+                quadratic_falloff_node = result[0].shader_node
+                emission_strength = quadratic_falloff_node.inputs["Strength"].default_value / (math.pi * 4.0)
+            else:
+                gltf2_io_debug.print_console('WARNING',
+                                             'No quadratic light falloff node attached to emission strength property')
+                emission_strength = blender_lamp.energy
+        else:
+            emission_strength = emission_node.inputs["Strength"].default_value
+        return emission_strength
+
+    return blender_lamp.energy
+
+
+def __gather_spot(blender_lamp, export_settings) -> Optional[gltf2_io_lights_punctual.LightSpot]:
+    if blender_lamp.type == "SPOT":
+        return gltf2_blender_gather_light_spots.gather_light_spot(blender_lamp, export_settings)
+    return None
+
+
+def __gather_type(blender_lamp, _) -> str:
+    return {
+        "POINT": "point",
+        "SUN": "directional",
+        "SPOT": "spot"
+    }[blender_lamp.type]
+
+
+def __gather_range(blender_lamp, export_settings) -> Optional[float]:
+    # TODO: calculate range from
+    # https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual#range-property
+    return None
+
+
+def __gather_name(blender_lamp, export_settings) -> Optional[str]:
+    return blender_lamp.name
+
+
+def __gather_extensions(blender_lamp, export_settings) -> Optional[dict]:
+    return None
+
+
+def __gather_extras(blender_lamp, export_settings) -> Optional[Any]:
+    return None
+
+
+def __get_cycles_emission_node(blender_lamp) -> Optional[bpy.types.ShaderNodeEmission]:
+    if blender_lamp.use_nodes and blender_lamp.node_tree:
+        for currentNode in blender_lamp.node_tree.nodes:
+            if isinstance(currentNode, bpy.types.ShaderNodeOutputLamp):
+                if not currentNode.is_active_output:
+                    continue
+                result = gltf2_blender_search_node_tree.from_socket(
+                    currentNode.inputs.get("Surface"),
+                    gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeEmission)
+                )
+                if not result:
+                    continue
+                return result[0].shader_node
+    return None
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
index 56a0a707aedaab8d8e0e744949e79d42b4751c46..e8da100d335e9c50a6c8e29732993c14917e0424 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import bpy
+
 from . import gltf2_blender_export_keys
 from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
@@ -19,7 +21,9 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather_cameras
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_mesh
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints
 from io_scene_gltf2.blender.exp import gltf2_blender_extract
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_lights
 from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.com import gltf2_io_extensions
 
 
 @cached
@@ -87,7 +91,28 @@ def __gather_children(blender_object, export_settings):
 
 
 def __gather_extensions(blender_object, export_settings):
-    return None
+    extensions = {}
+
+    if export_settings["gltf_lights"] and (blender_object.type == "LAMP" or blender_object.type == "LIGHT"):
+        blender_lamp = blender_object.data
+        light = gltf2_blender_gather_lights.gather_lights_punctual(
+            blender_lamp,
+            export_settings
+        )
+        if light is not None:
+            light_extension = gltf2_io_extensions.ChildOfRootExtension(
+                name="KHR_lights_punctual",
+                path=["lights"],
+                extension=light
+            )
+            extensions["KHR_lights_punctual"] = gltf2_io_extensions.Extension(
+                name="KHR_lights_punctual",
+                extension={
+                    "light": light_extension
+                }
+            )
+
+    return extensions if extensions else None
 
 
 def __gather_extras(blender_object, export_settings):
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 bd1294cd7407356db5a334f186f82b11bf8cb685..76a1f0e4a24fc71ae7cae73d8d47f7e6023da800 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
@@ -168,8 +168,8 @@ def __gather_skins(blender_primitive, export_settings):
             if bone_index >= 4:
                 gltf2_io_debug.print_console("WARNING", "There are more than 4 joint vertex influences."
                                                         "Consider to apply blenders Limit Total function.")
-                # TODO: add option to stop after 4
-                # break
+                if not export_settings['gltf_all_vertex_influences']:
+                    break
 
             # joints
             internal_joint = blender_primitive["attributes"][joint_id]
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
index 14b62c230f89efa70b05a71ba3fd89366edbf4a4..919e3d3ae310184a689284c273cec33c366a7c49 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
@@ -12,7 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from typing import Optional, List, Dict
+
 from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.com import gltf2_io_extensions
 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.exp import gltf2_io_buffer
@@ -208,6 +211,20 @@ class GlTF2Exporter:
         # TODO: allow embedding of images (base64)
         return image.name + ".png"
 
+    @classmethod
+    def __get_key_path(cls, d: dict, keypath: List[str], default=[]):
+        """Create if necessary and get the element at key path from a dict"""
+        key = keypath.pop(0)
+
+        if len(keypath) == 0:
+            v = d.get(key, default)
+            d[key] = v
+            return v
+
+        d_key = d.get(key, {})
+        d[key] = d_key
+        return cls.__get_key_path(d[key], keypath, default)
+
     def __traverse(self, node):
         """
         Recursively traverse a scene graph consisting of gltf compatible elements.
@@ -215,21 +232,21 @@ class GlTF2Exporter:
         The tree is traversed downwards until a primitive is reached. Then any ChildOfRoot property
         is stored in the according list in the glTF and replaced with a index reference in the upper level.
         """
-        def traverse_all_members(node):
+        def __traverse_property(node):
             for member_name in [a for a in dir(node) if not a.startswith('__') and not callable(getattr(node, a))]:
                 new_value = self.__traverse(getattr(node, member_name))
                 setattr(node, member_name, new_value)  # usually this is the same as before
 
-                # TODO: maybe with extensions hooks we can find a more elegant solution
-                if member_name == "extensions" and new_value is not None:
-                    for extension_name in new_value.keys():
-                        self.__append_unique_and_get_index(self.__gltf.extensions_used, extension_name)
-                        self.__append_unique_and_get_index(self.__gltf.extensions_required, extension_name)
+                # # TODO: maybe with extensions hooks we can find a more elegant solution
+                # if member_name == "extensions" and new_value is not None:
+                #     for extension_name in new_value.keys():
+                #         self.__append_unique_and_get_index(self.__gltf.extensions_used, extension_name)
+                #         self.__append_unique_and_get_index(self.__gltf.extensions_required, extension_name)
             return node
 
         # traverse nodes of a child of root property type and add them to the glTF root
         if type(node) in self.__childOfRootPropertyTypeLookup:
-            node = traverse_all_members(node)
+            node = __traverse_property(node)
             idx = self.__to_reference(node)
             # child of root properties are only present at root level --> replace with index in upper level
             return idx
@@ -247,7 +264,7 @@ class GlTF2Exporter:
 
         # traverse into any other property
         if type(node) in self.__propertyTypeLookup:
-            return traverse_all_members(node)
+            return __traverse_property(node)
 
         # binary data needs to be moved to a buffer and referenced with a buffer view
         if isinstance(node, gltf2_io_binary_data.BinaryData):
@@ -258,6 +275,21 @@ class GlTF2Exporter:
         if isinstance(node, gltf2_io_image_data.ImageData):
             return self.__add_image(node)
 
+        # extensions
+        if isinstance(node, gltf2_io_extensions.Extension):
+            extension = self.__traverse(node.extension)
+            self.__append_unique_and_get_index(self.__gltf.extensions_used, node.name)
+            self.__append_unique_and_get_index(self.__gltf.extensions_required, node.name)
+
+            # extensions that lie in the root of the glTF.
+            # They need to be converted to a reference at place of occurrence
+            if isinstance(node, gltf2_io_extensions.ChildOfRootExtension):
+                root_extension_list = self.__get_key_path(self.__gltf.extensions, [node.name] + node.path)
+                idx = self.__append_unique_and_get_index(root_extension_list, extension)
+                return idx
+
+            return extension
+
         # do nothing for any type that does not match a glTF schema (primitives)
         return node
 
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py
index a9dc86cfe20b5df2c8bf368109ca073e9a7481da..92b63c7d9859b24484d4beaa4370b7436285ca1b 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py
@@ -95,3 +95,4 @@ def from_socket(start_socket: bpy.types.NodeSocket,
 
     return __search_from_socket(start_socket, shader_node_filter, [])
 
+
diff --git a/io_scene_gltf2/io/com/gltf2_io_extensions.py b/io_scene_gltf2/io/com/gltf2_io_extensions.py
new file mode 100644
index 0000000000000000000000000000000000000000..2422d20584d0153ad62e631862c90cc00660d2aa
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_extensions.py
@@ -0,0 +1,38 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import List, Dict, Any
+
+
+class Extension:
+    """Container for extensions. Allows to specify requiredness"""
+    def __init__(self, name: str, extension: Dict[str, Any], required: bool = True):
+        self.name = name
+        self.extension = extension
+        self.required = required
+
+
+class ChildOfRootExtension(Extension):
+    """Container object for extensions that should be appended to the root extensions"""
+    def __init__(self, path: List[str], name: str, extension: Dict[str, Any], required: bool = True):
+        """
+        Wrap a local extension entity into an object that will later be inserted into a root extension and converted
+        to a reference.
+        :param path: The path of the extension object in the root extension. E.g. ['lights'] for
+        KHR_lights_punctual. Must be a path to a list in the extensions dict.
+        :param extension: The data that should be placed into the extension list
+        """
+        self.path = path
+        super().__init__(name, extension, required)
+
diff --git a/io_scene_gltf2/io/com/gltf2_io_lights_punctual.py b/io_scene_gltf2/io/com/gltf2_io_lights_punctual.py
new file mode 100644
index 0000000000000000000000000000000000000000..3dc8704cceae18505be494cff72e6521a4739e51
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_lights_punctual.py
@@ -0,0 +1,76 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from io_scene_gltf2.io.com.gltf2_io import *
+
+
+class LightSpot:
+    """light/spot"""
+    def __init__(self, inner_cone_angle, outer_cone_angle):
+        self.inner_cone_angle = inner_cone_angle
+        self.outer_cone_angle = outer_cone_angle
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        inner_cone_angle = from_union([from_float, from_none], obj.get("innerConeAngle"))
+        outer_cone_angle = from_union([from_float, from_none], obj.get("outerConeAngle"))
+        return LightSpot(inner_cone_angle, outer_cone_angle)
+
+    def to_dict(self):
+        result = {}
+        result["innerConeAngle"] = from_union([from_float, from_none], self.inner_cone_angle)
+        result["outerConeAngle"] = from_union([from_float, from_none], self.outer_cone_angle)
+        return result
+
+
+class Light:
+    """defines a set of lights for use with glTF 2.0. Lights define light sources within a scene"""
+    def __init__(self, color, intensity, spot, type, range, name, extensions, extras):
+        self.color = color
+        self.intensity = intensity
+        self.spot = spot
+        self.type = type
+        self.range = range
+        self.name = name
+        self.extensions = extensions
+        self.extras = extras
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        color = from_union([lambda x: from_list(from_float, x), from_none], obj.get("color"))
+        intensity = from_union([from_float, from_none], obj.get("intensity"))
+        spot = LightSpot.from_dict(obj.get("spot"))
+        type = from_str(obj.get("type"))
+        range = from_union([from_float, from_none], obj.get("range"))
+        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 Light(color, intensity, spot, type, range, name, extensions, extras)
+
+    def to_dict(self):
+        result = {}
+        result["color"] = from_union([lambda x: from_list(to_float, x), from_none], self.color)
+        result["intensity"] = from_union([from_float, from_none], self.intensity)
+        result["spot"] = from_union([lambda x: to_class(LightSpot, x), from_none], self.spot)
+        result["type"] = from_str(self.type)
+        result["range"] = from_union([from_float, from_none], self.range)
+        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
+