diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index b57c3bf46f82bbb0c174fe5e1115b89ec4821519..091b82332fa2f4ca11eab30590a59c7726e2bf7c 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -53,21 +53,6 @@ bl_info = {
 #
 
 
-class GLTF2ExportSettings(bpy.types.Operator):
-    """Save the export settings on export (saved in .blend). """
-    """Toggle off to clear settings"""
-    bl_label = "Save Settings"
-    bl_idname = "scene.gltf2_export_settings_set"
-
-    def execute(self, context):
-        operator = context.active_operator
-        operator.will_save_settings = not operator.will_save_settings
-        if not operator.will_save_settings:
-            # clear settings
-            context.scene.pop(operator.scene_key)
-        return {"FINISHED"}
-
-
 class ExportGLTF2_Base:
 
     # TODO: refactor to avoid boilerplate
@@ -90,6 +75,16 @@ class ExportGLTF2_Base:
         default='GLB'
     )
 
+    ui_tab: EnumProperty(
+        items=(('GENERAL', "General", "General settings"),
+               ('MESHES', "Meshes", "Mesh settings"),
+               ('OBJECTS', "Objects", "Object settings"),
+               ('MATERIALS', "Materials", "Material settings"),
+               ('ANIMATION', "Animation", "Animation settings")),
+        name="ui_tab",
+        description="Export setting categories",
+    )
+
     export_copyright: StringProperty(
         name='Copyright',
         description='Legal rights and conditions for the model',
@@ -214,8 +209,7 @@ class ExportGLTF2_Base:
 
     export_all_influences: BoolProperty(
         name='Include All Bone Influences',
-        description='Allow >4 joint vertex influences. Models may appear' \
-            ' incorrectly in many viewers',
+        description='Allow >4 joint vertex influences. Models may appear incorrectly in many viewers',
         default=False
     )
 
@@ -239,22 +233,22 @@ class ExportGLTF2_Base:
 
     export_lights: BoolProperty(
         name='Punctual Lights',
-        description='Export directional, point, and spot lights. Uses ' \
-            ' "KHR_lights_punctual" glTF extension',
+        description='Export directional, point, and spot lights. '
+                    'Uses "KHR_lights_punctual" glTF extension',
         default=False
     )
 
     export_texture_transform: BoolProperty(
         name='Texture Transforms',
-        description='Export texture or UV position, rotation, and scale.' \
-            ' Uses "KHR_texture_transform" glTF extension',
+        description='Export texture or UV position, rotation, and scale. '
+                    'Uses "KHR_texture_transform" glTF extension',
         default=False
     )
 
     export_displacement: BoolProperty(
         name='Displacement Textures (EXPERIMENTAL)',
-        description='EXPERIMENTAL: Export displacement textures. Uses' \
-            ' incomplete "KHR_materials_displacement" glTF extension',
+        description='EXPERIMENTAL: Export displacement textures. '
+                    'Uses incomplete "KHR_materials_displacement" glTF extension',
         default=False
     )
 
@@ -360,40 +354,47 @@ class ExportGLTF2_Base:
         return gltf2_blender_export.save(context, export_settings)
 
     def draw(self, context):
-        layout = self.layout
-
-        #
-
-        col = layout.box().column()
-        col.label(text='General:', icon='PREFERENCES')
+        self.layout.prop(self, 'ui_tab', expand=True)
+        if self.ui_tab == 'GENERAL':
+            self.draw_general_settings()
+        elif self.ui_tab == 'MESHES':
+            self.draw_mesh_settings()
+        elif self.ui_tab == 'OBJECTS':
+            self.draw_object_settings()
+        elif self.ui_tab == 'MATERIALS':
+            self.draw_material_settings()
+        elif self.ui_tab == 'ANIMATION':
+            self.draw_animation_settings()
+
+    def draw_general_settings(self):
+        col = self.layout.box().column()
         col.prop(self, 'export_format')
         col.prop(self, 'export_selected')
-        #col.prop(self, 'export_layers')
         col.prop(self, 'export_apply')
         col.prop(self, 'export_yup')
         col.prop(self, 'export_extras')
         col.prop(self, 'export_copyright')
 
-        col = layout.box().column()
-        col.label(text='Meshes:', icon='MESH_DATA')
+    def draw_mesh_settings(self):
+        col = self.layout.box().column()
         col.prop(self, 'export_texcoords')
         col.prop(self, 'export_normals')
         if self.export_normals:
             col.prop(self, 'export_tangents')
         col.prop(self, 'export_colors')
 
-        col = layout.box().column()
-        col.label(text='Objects:', icon='OBJECT_DATA')
+    def draw_object_settings(self):
+        col = self.layout.box().column()
         col.prop(self, 'export_cameras')
         col.prop(self, 'export_lights')
 
-        col = layout.box().column()
-        col.label(text='Materials:', icon='MATERIAL_DATA')
+    def draw_material_settings(self):
+        col = self.layout.box().column()
         col.prop(self, 'export_materials')
         col.prop(self, 'export_texture_transform')
 
-        col = layout.box().column()
-        col.label(text='Animation:', icon='ARMATURE_DATA')
+    def draw_animation_settings(self):
+        col = self.layout.box().column()
         col.prop(self, 'export_animations')
         if self.export_animations:
             col.prop(self, 'export_frame_range')
@@ -412,12 +413,6 @@ class ExportGLTF2_Base:
             if self.export_morph_normal:
                 col.prop(self, 'export_morph_tangent')
 
-        row = layout.row()
-        row.operator(
-            GLTF2ExportSettings.bl_idname,
-            text=GLTF2ExportSettings.bl_label,
-            icon="%s" % "PINNED" if self.will_save_settings else "UNPINNED")
-
 
 class ExportGLTF2(bpy.types.Operator, ExportGLTF2_Base, ExportHelper):
     """Export scene as glTF 2.0 file"""
@@ -497,7 +492,6 @@ def menu_func_import(self, context):
 
 
 classes = (
-    GLTF2ExportSettings,
     ExportGLTF2,
     ImportGLTF2
 )
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export.py b/io_scene_gltf2/blender/exp/gltf2_blender_export.py
index f2cc9fa528d5c1550e0867cedc28c74a699cbe45..1ddeb6a5020890d384533f8e56db0819a3b7ccb6 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_export.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_export.py
@@ -33,6 +33,7 @@ def save(context, export_settings):
 
 
 def __export(export_settings):
+    export_settings['gltf_channelcache'] = dict()
     exporter = GlTF2Exporter(__get_copyright(export_settings))
     __add_root_objects(exporter, export_settings)
     buffer = __create_buffer(exporter, export_settings)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
index c26429d362959002c029bfd27483b33c8075d3b5..8762a90fc7f58c98dd39e7e126d37d0a79717676 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
@@ -975,10 +975,10 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp
 
         if max_index >= range_indices:
             #
-            # Spliting result_primitives.
+            # Splitting result_primitives.
             #
 
-            # At start, all indicees are pending.
+            # At start, all indices are pending.
             pending_attributes = {
                 POSITION_ATTRIBUTE: [],
                 NORMAL_ATTRIBUTE: []
@@ -1038,6 +1038,7 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp
             while len(pending_indices) > 0:
 
                 process_indices = pending_primitive[INDICES_ID]
+                max_index = max(process_indices)
 
                 pending_indices = []
 
@@ -1046,7 +1047,7 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp
 
                 all_local_indices = []
 
-                for i in range(0, (max(process_indices) // range_indices) + 1):
+                for i in range(0, (max_index // range_indices) + 1):
                     all_local_indices.append([])
 
                 #
@@ -1063,7 +1064,7 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp
                                          process_indices[face_index + 2])
 
                     # ... check if it can be but in a range of maximum indices.
-                    for i in range(0, (max(process_indices) // range_indices) + 1):
+                    for i in range(0, (max_index // range_indices) + 1):
                         offset = i * range_indices
 
                         # Yes, so store the primitive with its indices.
@@ -1075,7 +1076,7 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp
                             written = True
                             break
 
-                    # If not written, the triangel face has indices from different ranges.
+                    # If not written, the triangle face has indices from different ranges.
                     if not written:
                         pending_indices.extend([process_indices[face_index + 0], process_indices[face_index + 1],
                                                 process_indices[face_index + 2]])
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 d9583d080697aae15734d6b5a86270fe7817b7b7..b6131a594f7dde7e8d0cdb3a30cd17ac1f2f6c8f 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
@@ -49,7 +49,7 @@ def __filter_image(sockets_or_slots, export_settings):
 
 def __gather_buffer_view(sockets_or_slots, export_settings):
     if export_settings[gltf2_blender_export_keys.FORMAT] != 'GLTF_SEPARATE':
-        image = __get_image_data(sockets_or_slots)
+        image = __get_image_data(sockets_or_slots, export_settings)
         return gltf2_io_binary_data.BinaryData(
             data=image.to_image_data(__gather_mime_type(sockets_or_slots, export_settings)))
     return None
@@ -81,7 +81,7 @@ def __gather_name(sockets_or_slots, export_settings):
 def __gather_uri(sockets_or_slots, 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
-        return __get_image_data(sockets_or_slots)
+        return __get_image_data(sockets_or_slots, export_settings)
     return None
 
 
@@ -93,14 +93,21 @@ def __is_slot(sockets_or_slots):
     return isinstance(sockets_or_slots[0], bpy.types.MaterialTextureSlot)
 
 
-def __get_image_data(sockets_or_slots):
+def __get_image_data(sockets_or_slots, export_settings):
     # 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.List[typing.List[float]]:
+    def split_pixels_by_channels(image: bpy.types.Image, export_settings) -> typing.List[typing.List[float]]:
+        channelcache = export_settings['gltf_channelcache']
+        if image.name in channelcache:
+            return channelcache[image.name]
+
         pixels = np.array(image.pixels)
         pixels = pixels.reshape((pixels.shape[0] // image.channels, image.channels))
         channels = np.split(pixels, pixels.shape[1], axis=1)
+
+        channelcache[image.name] = channels
+
         return channels
 
     if __is_socket(sockets_or_slots):
@@ -118,15 +125,16 @@ def __get_image_data(sockets_or_slots):
                     }[elem.from_socket.name]
 
             if channel is not None:
-                pixels = [split_pixels_by_channels(result.shader_node.image)[channel]]
+                pixels = [split_pixels_by_channels(result.shader_node.image, export_settings)[channel]]
             else:
-                pixels = split_pixels_by_channels(result.shader_node.image)
+                pixels = split_pixels_by_channels(result.shader_node.image, export_settings)
                 channel = 0
 
             file_name = os.path.splitext(result.shader_node.image.name)[0]
 
             image_data = gltf2_io_image_data.ImageData(
                 file_name,
+                result.shader_node.image.filepath,
                 result.shader_node.image.size[0],
                 result.shader_node.image.size[1],
                 channel,
@@ -140,10 +148,11 @@ 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 = split_pixels_by_channels(texture.image)
+        pixels = split_pixels_by_channels(texture.image, export_settings)
 
         image_data = gltf2_io_image_data.ImageData(
             texture.name,
+            texture.image.filepath,
             texture.image.size[0],
             texture.image.size[1],
             0,
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 b1408b62b0d9ed70664a4facbc7c919fe3f28a09..6561567e284efb506ba53972ab07fcae63cba247 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
@@ -20,6 +20,9 @@ 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
 
+import bpy
+import os
+from shutil import copyfile
 
 class GlTF2Exporter:
     """
@@ -141,9 +144,18 @@ class GlTF2Exporter:
         :return:
         """
         for image in self.__images:
-            uri = output_path + image.name + ".png"
-            with open(uri, 'wb') as f:
-                f.write(image.to_png_data())
+            dst_path = output_path + image.name + ".png"
+
+            src_path = bpy.path.abspath(image.filepath)
+            if os.path.isfile(src_path):
+                # Source file exists.
+                if os.path.abspath(dst_path) != os.path.abspath(src_path):
+                    # Only copy, if source and destination are not the same.
+                    copyfile(src_path, dst_path)
+            else:
+                # Source file does not exist e.g. it is packed or has been generated.
+                with open(dst_path, 'wb') as f:
+                    f.write(image.to_png_data())
 
     def add_scene(self, scene: gltf2_io.Scene, active: bool = True):
         """
diff --git a/io_scene_gltf2/io/com/gltf2_io_debug.py b/io_scene_gltf2/io/com/gltf2_io_debug.py
index a7df8fed6d615147537b410927e847fb869950d0..b9098ebadf7580fc0b057e78b66dd016ab6ed369 100755
--- a/io_scene_gltf2/io/com/gltf2_io_debug.py
+++ b/io_scene_gltf2/io/com/gltf2_io_debug.py
@@ -54,7 +54,7 @@ def print_console(level, output):
     if OUTPUT_LEVELS.index(level) > OUTPUT_LEVELS.index(g_current_output_level):
         return
 
-    print(level + ': ' + output)
+    print(get_timestamp() + " | " + level + ': ' + output)
 
 
 def print_newline():
@@ -62,9 +62,14 @@ def print_newline():
     print()
 
 
+def get_timestamp():
+    current_time = time.gmtime()
+    return time.strftime("%H:%M:%S", current_time)
+
+
 def print_timestamp(label=None):
     """Print a timestamp to Blender console."""
-    output = 'Timestamp: ' + str(time.time())
+    output = 'Timestamp: ' + get_timestamp()
 
     if label is not None:
         output = output + ' (' + label + ')'
diff --git a/io_scene_gltf2/io/exp/gltf2_io_image_data.py b/io_scene_gltf2/io/exp/gltf2_io_image_data.py
index c69dacb290dd6088b470c559fa54efe87ed8778f..5f547f7567127797d06ac93c2287cb29a86ae24b 100755
--- a/io_scene_gltf2/io/exp/gltf2_io_image_data.py
+++ b/io_scene_gltf2/io/exp/gltf2_io_image_data.py
@@ -23,23 +23,17 @@ class ImageData:
     # FUTURE_WORK: as a method to allow the node graph to be better supported, we could model some of
     # the node graph elements with numpy functions
 
-    def __init__(self, name: str, width: int, height: int, offset: int, channels: typing.Optional[typing.List[np.ndarray]] = []):
+    def __init__(self, name: str, filepath: str, width: int, height: int, offset: int, channels: typing.Optional[typing.List[np.ndarray]] = []):
         if width <= 0 or height <= 0:
             raise ValueError("Image data can not have zero width or height")
         if offset + len(channels) > 4:
             raise ValueError("Image data can not have more than 4 channels")
-
-        self.channels = []
-        for fill in range(offset): 
-            # Fill before.
-            self.channels.append(np.ones_like(channels[0]))
-        self.channels += channels
-        total_channels = len(self.channels)
-        for fill in range(total_channels, 4):
-            # Fill after.
-            self.channels.append(np.ones_like(channels[0]))
-        
+        self.channels = [None, None, None, None]
+        channels_length = len(channels)
+        for index in range(offset, offset + channels_length):
+            self.channels[index] = channels[index - offset]
         self.name = name
+        self.filepath = filepath
         self.width = width
         self.height = height
 
@@ -51,7 +45,9 @@ class ImageData:
         if len(image_data.channels) != 4:
             raise ValueError("Can't append image: incomplete image")
 
-        self.name += image_data.name
+        if self.name != image_data.name:
+            self.name += image_data.name
+            self.filepath = ""
 
         # Replace channel.
         self.channels[channel] = image_data.channels[channel]
@@ -91,10 +87,18 @@ class ImageData:
         # if there is no data, create a single pixel image
         if not channels:
             channels = np.ones((1, 1))
-
             # fill all channels of the png
             for _ in range(4 - len(channels)):
                 channels.append(np.ones_like(channels[0]))
+        else:
+            template_index = None
+            for index in range(0, 4):
+                if channels[index] is not None:
+                    template_index = index
+                    break
+            for index in range(0, 4):
+                if channels[index] is None:
+                    channels[index] = np.ones_like(channels[template_index])
 
         image = np.concatenate(channels, axis=1)
         image = image.flatten()