diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index a672be22300ccbfdb5dcfb44765c70eddf42344a..1b3c0cc2e04770cbadac9471faa0ffb10389b779 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, 2, 7),
+    "version": (3, 2, 8),
     'blender': (3, 1, 0),
     'location': 'File > Import-Export',
     'description': 'Import-Export as glTF 2.0',
@@ -879,6 +879,8 @@ class GLTF_PT_export_animation_export(bpy.types.Panel):
         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_animation_shapekeys(bpy.types.Panel):
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_math.py b/io_scene_gltf2/blender/com/gltf2_blender_math.py
index 0498e8f894b5a629fc3e4896aa1c1e7de8aa704f..d2a018a830c2e305adcc852b9c1c471c5f6450da 100755
--- a/io_scene_gltf2/blender/com/gltf2_blender_math.py
+++ b/io_scene_gltf2/blender/com/gltf2_blender_math.py
@@ -98,7 +98,7 @@ def swizzle_yup_value(value: typing.Any) -> typing.Any:
     return value
 
 
-def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Matrix = Matrix.Identity(4)) -> typing \
+def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Matrix = Matrix.Identity(4), need_rotation_correction: bool = False) -> typing \
         .Union[Vector, Quaternion]:
     """Manage transformations."""
     target = get_target_property_name(data_path)
@@ -116,25 +116,31 @@ def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Ma
     if transform_func is None:
         raise RuntimeError("Cannot transform values at {}".format(data_path))
 
-    return transform_func(v, transform)
+    return transform_func(v, transform, need_rotation_correction)
 
 
-def transform_location(location: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector:
+def transform_location(location: Vector, transform: Matrix = Matrix.Identity(4), need_rotation_correction:bool = False) -> Vector:
     """Transform location."""
+    correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
     m = Matrix.Translation(location)
+    if need_rotation_correction:
+        m @= correction.to_matrix().to_4x4()
     m = transform @ m
     return m.to_translation()
 
 
-def transform_rotation(rotation: Quaternion, transform: Matrix = Matrix.Identity(4)) -> Quaternion:
+def transform_rotation(rotation: Quaternion, transform: Matrix = Matrix.Identity(4), need_rotation_correction: bool = False) -> Quaternion:
     """Transform rotation."""
     rotation.normalize()
+    correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
     m = rotation.to_matrix().to_4x4()
+    if need_rotation_correction:
+        m @= correction.to_matrix().to_4x4()
     m = transform @ m
     return m.to_quaternion()
 
 
-def transform_scale(scale: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector:
+def transform_scale(scale: Vector, transform: Matrix = Matrix.Identity(4), need_rotation_correction: bool  = False) -> Vector:
     """Transform scale."""
     m = Matrix.Identity(4)
     m[0][0] = scale.x
@@ -145,7 +151,7 @@ def transform_scale(scale: Vector, transform: Matrix = Matrix.Identity(4)) -> Ve
     return m.to_scale()
 
 
-def transform_value(value: Vector, _: Matrix = Matrix.Identity(4)) -> Vector:
+def transform_value(value: Vector, _: Matrix = Matrix.Identity(4), need_rotation_correction: bool = False) -> Vector:
     """Transform value."""
     return value
 
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 61a9f5bf6a471b7e28368d0f4d6272c5d93256d2..812db3f99d6c9ee84ac4a1b13e17dc72609b85ce 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
@@ -19,6 +19,7 @@ VISIBLE = 'gltf_visible'
 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'
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
index f5b69f13142512e177e63414697ad08f78e98f7d..d81bd706930eceb2fd10034d426346aa36ed92e5 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
@@ -9,10 +9,14 @@ from ...io.com.gltf2_io_debug import print_console
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
 
 
-def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vertex_groups, modifiers, export_settings):
+def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings):
     """Extract primitives from a mesh."""
     print_console('INFO', 'Extracting primitive: ' + blender_mesh.name)
 
+    blender_object = None
+    if uuid_for_skined_data:
+        blender_object = export_settings['vtree'].nodes[uuid_for_skined_data].blender_object
+
     use_normals = export_settings[gltf2_blender_export_keys.NORMALS]
     if use_normals:
         blender_mesh.calc_normals_split()
@@ -57,7 +61,7 @@ def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vert
             armature = None
 
         if armature:
-            skin = gltf2_blender_gather_skins.gather_skin(armature, export_settings)
+            skin = gltf2_blender_gather_skins.gather_skin(export_settings['vtree'].nodes[uuid_for_skined_data].armature, export_settings)
             if not skin:
                 armature = None
 
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
index 31c0fa62505ed2647ec573f11243fdc54dce9f66..f515da8cdf2ede497266098f94dacec3eaf46e07 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
@@ -7,10 +7,12 @@ from io_scene_gltf2.io.com import gltf2_io
 from io_scene_gltf2.io.com.gltf2_io_debug import print_console
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_nodes
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_animations
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_sampler_keyframes
 from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
 from ..com.gltf2_blender_extras import generate_extras
 from io_scene_gltf2.blender.exp import gltf2_blender_export_keys
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_tree
 
 
 def gather_gltf2(export_settings):
@@ -22,12 +24,18 @@ def gather_gltf2(export_settings):
     scenes = []
     animations = []  # unfortunately animations in gltf2 are just as 'root' as scenes.
     active_scene = None
+    store_user_scene = bpy.context.scene
     for blender_scene in bpy.data.scenes:
         scenes.append(__gather_scene(blender_scene, export_settings))
         if export_settings[gltf2_blender_export_keys.ANIMATIONS]:
+            # resetting object cache
+            gltf2_blender_gather_animation_sampler_keyframes.get_object_matrix.reset_cache()
             animations += __gather_animations(blender_scene, export_settings)
         if bpy.context.scene.name == blender_scene.name:
             active_scene = len(scenes) -1
+
+    # restore user scene
+    bpy.context.window.scene = store_user_scene
     return active_scene, scenes, animations
 
 
@@ -40,14 +48,25 @@ def __gather_scene(blender_scene, export_settings):
         nodes=[]
     )
 
-    for blender_object in blender_scene.objects:
-        if blender_object.parent is None:
-            node = gltf2_blender_gather_nodes.gather_node(
-                blender_object,
-                blender_object.library.name if blender_object.library else None,
-                blender_scene, None, export_settings)
-            if node is not None:
-                scene.nodes.append(node)
+
+    vtree = gltf2_blender_gather_tree.VExportTree(export_settings)
+    vtree.construct(blender_scene)
+    vtree.search_missing_armature() # In case armature are no parented correctly
+
+    export_user_extensions('vtree_before_filter_hook', export_settings, vtree)
+
+    # Now, we can filter tree if needed
+    vtree.filter()
+
+    export_user_extensions('vtree_after_filter_hook', export_settings, vtree)
+
+    export_settings['vtree'] = vtree
+
+    for r in [vtree.nodes[r] for r in vtree.roots]:
+        node = gltf2_blender_gather_nodes.gather_node(
+            r, export_settings)
+        if node is not None:
+            scene.nodes.append(node)
 
     export_user_extensions('gather_scene_hook', export_settings, scene, blender_scene)
 
@@ -58,15 +77,16 @@ def __gather_animations(blender_scene, export_settings):
     animations = []
     merged_tracks = {}
 
-    for blender_object in blender_scene.objects:
+    vtree = export_settings['vtree']
+    for obj_uuid in vtree.get_all_objects():
+        blender_object = vtree.nodes[obj_uuid].blender_object
+
+        # Do not manage not exported objects
+        if vtree.nodes[obj_uuid].node is None:
+            continue
 
-        # First check if this object is exported or not. Do not export animation of not exported object
-        obj_node = gltf2_blender_gather_nodes.gather_node(blender_object,
-            blender_object.library.name if blender_object.library else None,
-            blender_scene, None, export_settings)
-        if obj_node is not None:
-            animations_, merged_tracks = gltf2_blender_gather_animations.gather_animations(blender_object, merged_tracks, len(animations), export_settings)
-            animations += animations_
+        animations_, merged_tracks = gltf2_blender_gather_animations.gather_animations(obj_uuid, merged_tracks, len(animations), export_settings)
+        animations += animations_
 
     if export_settings['gltf_nla_strips'] is False:
         # Fake an animation with all animations of the scene
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py
index 928fa14ad8b15f58eaed9c0d4ce3f8ef1a04cd1a..0e542de8925490657f766b4e05b2400b4d9a57b9 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py
@@ -12,18 +12,20 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
 
 @cached
-def gather_animation_channel_target(channels: typing.Tuple[bpy.types.FCurve],
-                                    blender_object: bpy.types.Object,
+def gather_animation_channel_target(obj_uuid: int,
+                                    channels: typing.Tuple[bpy.types.FCurve],
                                     bake_bone: typing.Union[str, None],
                                     bake_channel: typing.Union[str, None],
-                                    driver_obj,
+                                    driver_obj_uuid,
                                     export_settings
                                     ) -> gltf2_io.AnimationChannelTarget:
 
+        blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+
         animation_channel_target = gltf2_io.AnimationChannelTarget(
             extensions=__gather_extensions(channels, blender_object, export_settings, bake_bone),
             extras=__gather_extras(channels, blender_object, export_settings, bake_bone),
-            node=__gather_node(channels, blender_object, export_settings, bake_bone, driver_obj),
+            node=__gather_node(channels, obj_uuid, export_settings, bake_bone, driver_obj_uuid),
             path=__gather_path(channels, blender_object, export_settings, bake_bone, bake_channel)
         )
 
@@ -54,16 +56,16 @@ def __gather_extras(channels: typing.Tuple[bpy.types.FCurve],
 
 
 def __gather_node(channels: typing.Tuple[bpy.types.FCurve],
-                  blender_object: bpy.types.Object,
+                  obj_uuid: str,
                   export_settings,
                   bake_bone: typing.Union[str, None],
-                  driver_obj
+                  driver_obj_uuid
                   ) -> gltf2_io.Node:
 
-    if driver_obj is not None:
-        return gltf2_blender_gather_nodes.gather_node(driver_obj,
-            driver_obj.library.name if driver_obj.library else None,
-            None, None, export_settings)
+    blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+
+    if driver_obj_uuid is not None:
+        return export_settings['vtree'].nodes[driver_obj_uuid].node
 
     if blender_object.type == "ARMATURE":
         # TODO: get joint from fcurve data_path and gather_joint
@@ -74,16 +76,9 @@ def __gather_node(channels: typing.Tuple[bpy.types.FCurve],
             blender_bone = blender_object.path_resolve(channels[0].data_path.rsplit('.', 1)[0])
 
         if isinstance(blender_bone, bpy.types.PoseBone):
-            if export_settings["gltf_def_bones"] is False:
-                return gltf2_blender_gather_joints.gather_joint(blender_object, blender_bone, export_settings)
-            else:
-                bones, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object)
-                if blender_bone.name in [b.name for b in bones]:
-                    return gltf2_blender_gather_joints.gather_joint(blender_object, blender_bone, export_settings)
-
-    return gltf2_blender_gather_nodes.gather_node(blender_object,
-        blender_object.library.name if blender_object.library else None,
-        None, None, export_settings)
+            return gltf2_blender_gather_joints.gather_joint_vnode(export_settings['vtree'].nodes[obj_uuid].bones[blender_bone.name], export_settings)
+
+    return export_settings['vtree'].nodes[obj_uuid].node
 
 
 def __gather_path(channels: typing.Tuple[bpy.types.FCurve],
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 4c79092c3e50510132697a58e4d80de130d6956d..87ef7c13df97f0105ec1360eefac96c7dc0dd270 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
@@ -15,15 +15,18 @@ from io_scene_gltf2.blender.exp import gltf2_blender_get
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_drivers
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode
+from . import gltf2_blender_export_keys
 
 
 @cached
-def gather_animation_channels(blender_action: bpy.types.Action,
-                              blender_object: bpy.types.Object,
+def gather_animation_channels(obj_uuid: int,
+                              blender_action: bpy.types.Action,
                               export_settings
                               ) -> typing.List[gltf2_io.AnimationChannel]:
     channels = []
 
+    blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
 
     # First calculate range of animation for baking
     # This is need if user set 'Force sampling' and in case we need to bake
@@ -59,11 +62,8 @@ def gather_animation_channels(blender_action: bpy.types.Action,
 
         # Then bake all bones
         bones_to_be_animated = []
-        if export_settings["gltf_def_bones"] is False:
-            bones_to_be_animated = blender_object.data.bones
-        else:
-            bones_to_be_animated, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object)
-            bones_to_be_animated = [blender_object.pose.bones[b.name] for b in bones_to_be_animated]
+        bones_uuid = export_settings["vtree"].get_all_bones(obj_uuid)
+        bones_to_be_animated = [blender_object.pose.bones[export_settings["vtree"].nodes[b].blender_bone.name] for b in bones_uuid]
 
         list_of_animated_bone_channels = []
         for channel_group in __get_channel_groups(blender_action, blender_object, export_settings):
@@ -72,9 +72,9 @@ def gather_animation_channels(blender_action: bpy.types.Action,
 
         for bone in bones_to_be_animated:
             for p in ["location", "rotation_quaternion", "scale"]:
-                channel = __gather_animation_channel(
+                channel = gather_animation_channel(
+                    obj_uuid,
                     (),
-                    blender_object,
                     export_settings,
                     bone.name,
                     p,
@@ -95,17 +95,17 @@ def gather_animation_channels(blender_action: bpy.types.Action,
             if len(channel_group) == 0:
                 # Only errors on channels, ignoring
                 continue
-            channel = __gather_animation_channel(channel_group, blender_object, export_settings, None, None, bake_range_start, bake_range_end, force_range, blender_action.name, None, True)
+            channel = gather_animation_channel(obj_uuid, channel_group, export_settings, None, None, bake_range_start, bake_range_end, force_range, blender_action.name, None, True)
             if channel is not None:
                 channels.append(channel)
 
 
         # Retrieve channels for drivers, if needed
-        drivers_to_manage = gltf2_blender_gather_drivers.get_sk_drivers(blender_object)
-        for obj, fcurves in drivers_to_manage:
-            channel = __gather_animation_channel(
+        drivers_to_manage = gltf2_blender_gather_drivers.get_sk_drivers(obj_uuid, export_settings)
+        for obj_driver_uuid, fcurves in drivers_to_manage:
+            channel = gather_animation_channel(
+                obj_uuid,
                 fcurves,
-                blender_object,
                 export_settings,
                 None,
                 None,
@@ -113,31 +113,77 @@ def gather_animation_channels(blender_action: bpy.types.Action,
                 bake_range_end,
                 force_range,
                 blender_action.name,
-                obj,
-                False)
+                obj_driver_uuid,
+                True)
             if channel is not None:
                 channels.append(channel)
 
     else:
+        done_paths = []
         for channel_group in __get_channel_groups(blender_action, blender_object, export_settings):
             channel_group_sorted = __get_channel_group_sorted(channel_group, blender_object)
             if len(channel_group_sorted) == 0:
                 # Only errors on channels, ignoring
                 continue
-            channel = __gather_animation_channel(
-                        channel_group_sorted,
-                        blender_object,
+            channel = gather_animation_channel(
+                obj_uuid,
+                channel_group_sorted,
+                export_settings,
+                None,
+                None,
+                bake_range_start,
+                bake_range_end,
+                force_range,
+                blender_action.name,
+                None,
+                True
+                )
+            if channel is not None:
+                channels.append(channel)
+
+            # Store already done channel path
+            target = [c for c in channel_group_sorted if c is not None][0].data_path.split('.')[-1]
+            path = {
+                "delta_location": "location",
+                "delta_rotation_euler": "rotation_quaternion",
+                "location": "location",
+                "rotation_axis_angle": "rotation_quaternion",
+                "rotation_euler": "rotation_quaternion",
+                "rotation_quaternion": "rotation_quaternion",
+                "scale": "scale",
+                "value": "weights"
+            }.get(target)
+            if path is not None:
+                done_paths.append(path)
+        done_paths = list(set(done_paths))
+
+        if export_settings['gltf_selected'] is True and export_settings['vtree'].tree_troncated is True:
+            start_frame = min([v[0] for v in [a.frame_range for a in bpy.data.actions]])
+            end_frame = max([v[1] for v in [a.frame_range for a in bpy.data.actions]])
+            to_be_done = ['location', 'rotation_quaternion', 'scale']
+            to_be_done = [c for c in to_be_done if c not in done_paths]
+
+            # In case of weight action, do nothing.
+            # If there is only weight --> TRS is already managed at first
+            if not (len(done_paths) == 1 and 'weights' in done_paths):
+                for p in to_be_done:
+                    channel = gather_animation_channel(
+                        obj_uuid,
+                        (),
                         export_settings,
                         None,
-                        None,
-                        bake_range_start,
-                        bake_range_end,
+                        p,
+                        start_frame,
+                        end_frame,
                         force_range,
                         blender_action.name,
                         None,
-                        False)
-            if channel is not None:
-                channels.append(channel)
+                        False #If Object is not animated, don't keep animation for this channel
+                        ) 
+
+                    if channel is not None:
+                        channels.append(channel)
+
 
 
     # resetting driver caches
@@ -198,8 +244,9 @@ def __get_channel_group_sorted(channels: typing.Tuple[bpy.types.FCurve], blender
     # if not shapekeys, stay in same order, because order doesn't matter
     return channels
 
-def __gather_animation_channel(channels: typing.Tuple[bpy.types.FCurve],
-                               blender_object: bpy.types.Object,
+# This function can be called directly from gather_animation in case of bake animation (non animated selected object)
+def gather_animation_channel(obj_uuid: str,
+                               channels: typing.Tuple[bpy.types.FCurve],
                                export_settings,
                                bake_bone: typing.Union[str, None],
                                bake_channel: typing.Union[str, None],
@@ -207,15 +254,18 @@ def __gather_animation_channel(channels: typing.Tuple[bpy.types.FCurve],
                                bake_range_end,
                                force_range: bool,
                                action_name: str,
-                               driver_obj,
+                               driver_obj_uuid,
                                node_channel_is_animated: bool
                                ) -> typing.Union[gltf2_io.AnimationChannel, None]:
+
+    blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+
     if not __filter_animation_channel(channels, blender_object, export_settings):
         return None
 
-    __target= __gather_target(channels, blender_object, export_settings, bake_bone, bake_channel, driver_obj)
+    __target= __gather_target(obj_uuid, channels, export_settings, bake_bone, bake_channel, driver_obj_uuid)
     if __target.path is not None:
-        sampler = __gather_sampler(channels, blender_object, export_settings, bake_bone, bake_channel, bake_range_start, bake_range_end, force_range, action_name, driver_obj, node_channel_is_animated)
+        sampler = __gather_sampler(channels, obj_uuid, export_settings, bake_bone, bake_channel, bake_range_start, bake_range_end, force_range, action_name, driver_obj_uuid, node_channel_is_animated)
 
         if sampler is None:
             # After check, no need to animate this node for this channel
@@ -268,7 +318,7 @@ def __gather_extras(channels: typing.Tuple[bpy.types.FCurve],
 
 
 def __gather_sampler(channels: typing.Tuple[bpy.types.FCurve],
-                     blender_object: bpy.types.Object,
+                     obj_uuid: str,
                      export_settings,
                      bake_bone: typing.Union[str, None],
                      bake_channel: typing.Union[str, None],
@@ -276,33 +326,38 @@ def __gather_sampler(channels: typing.Tuple[bpy.types.FCurve],
                      bake_range_end,
                      force_range: bool,
                      action_name,
-                     driver_obj,
+                     driver_obj_uuid,
                      node_channel_is_animated: bool
                      ) -> gltf2_io.AnimationSampler:
+
+    need_rotation_correction = (export_settings[gltf2_blender_export_keys.CAMERAS] and export_settings['vtree'].nodes[obj_uuid].blender_type == VExportNode.CAMERA) or \
+        (export_settings[gltf2_blender_export_keys.LIGHTS] and export_settings['vtree'].nodes[obj_uuid].blender_type == VExportNode.LIGHT)
+
     return gltf2_blender_gather_animation_samplers.gather_animation_sampler(
         channels,
-        blender_object,
+        obj_uuid,
         bake_bone,
         bake_channel,
         bake_range_start,
         bake_range_end,
         force_range,
         action_name,
-        driver_obj,
+        driver_obj_uuid,
         node_channel_is_animated,
+        need_rotation_correction,
         export_settings
     )
 
 
-def __gather_target(channels: typing.Tuple[bpy.types.FCurve],
-                    blender_object: bpy.types.Object,
+def __gather_target(obj_uuid: str,
+                    channels: typing.Tuple[bpy.types.FCurve],
                     export_settings,
                     bake_bone: typing.Union[str, None],
                     bake_channel: typing.Union[str, None],
-                    driver_obj
+                    driver_obj_uuid
                     ) -> gltf2_io.AnimationChannelTarget:
     return gltf2_blender_gather_animation_channel_target.gather_animation_channel_target(
-        channels, blender_object, bake_bone, bake_channel, driver_obj, export_settings)
+        obj_uuid, channels, bake_bone, bake_channel, driver_obj_uuid, export_settings)
 
 
 def __get_channel_groups(blender_action: bpy.types.Action, blender_object: bpy.types.Object, export_settings):
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 d24db395b6d413a66e1d1524bd14f3a78cd68976..cd836682e1b353607ad537729bed7744c270cff4 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
@@ -5,12 +5,13 @@ import bpy
 import mathutils
 import typing
 
-from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, bonecache
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, bonecache, objectcache
 from io_scene_gltf2.blender.com import gltf2_blender_math
 from io_scene_gltf2.blender.exp import gltf2_blender_get
 from io_scene_gltf2.blender.exp.gltf2_blender_gather_drivers import get_sk_drivers, get_sk_driver_values
 from . import gltf2_blender_export_keys
 from io_scene_gltf2.io.com import gltf2_io_debug
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode
 import numpy as np
 
 
@@ -95,6 +96,10 @@ class Keyframe:
     def value(self, value: typing.List[float]):
         self.__value = self.__set_indexed(value)
 
+    @value.setter
+    def value_total(self, value: typing.List[float]):
+        self.__value = value
+
     @property
     def in_tangent(self) -> typing.Union[mathutils.Vector, mathutils.Euler, mathutils.Quaternion, typing.List[float]]:
         if self.__in_tangent is None:
@@ -120,9 +125,75 @@ class Keyframe:
         self.__out_tangent = self.__set_indexed(value)
 
 
+@objectcache
+def get_object_matrix(blender_obj_uuid: str,
+                      action_name: str,
+                      bake_range_start: int,
+                      bake_range_end: int,
+                      current_frame: int,
+                      step: int,
+                      export_settings
+                    ):
+
+    data = {}
+
+    # TODO : bake_range_start & bake_range_end are no more needed here
+    # Because we bake, we don't know exactly the frame range, 
+    # So using min / max of all actions
+
+    start_frame = min([v[0] for v in [a.frame_range for a in bpy.data.actions]])
+    end_frame = max([v[1] for v in [a.frame_range for a in bpy.data.actions]])
+
+    frame = start_frame
+    while frame <= end_frame:
+        bpy.context.scene.frame_set(int(frame))
+
+        for obj_uuid in [uid for (uid, n) in export_settings['vtree'].nodes.items() if n.blender_type not in [VExportNode.BONE]]:
+            blender_obj = export_settings['vtree'].nodes[obj_uuid].blender_object
+
+            # if this object is not animated, do not skip :
+            # We need this object too in case of bake
+
+            # calculate local matrix
+            if export_settings['vtree'].nodes[obj_uuid].parent_uuid is None:
+                parent_mat = mathutils.Matrix.Identity(4).freeze()
+            else:
+                if export_settings['vtree'].nodes[export_settings['vtree'].nodes[obj_uuid].parent_uuid].blender_type not in [VExportNode.BONE]:
+                    parent_mat = export_settings['vtree'].nodes[export_settings['vtree'].nodes[obj_uuid].parent_uuid].blender_object.matrix_world
+                else:
+                    # Object animated is parented to a bone
+                    blender_bone = export_settings['vtree'].nodes[export_settings['vtree'].nodes[obj_uuid].parent_bone_uuid].blender_bone
+                    armature_object = export_settings['vtree'].nodes[export_settings['vtree'].nodes[export_settings['vtree'].nodes[obj_uuid].parent_bone_uuid].armature].blender_object
+                    axis_basis_change = mathutils.Matrix(
+                        ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
+
+                    parent_mat = armature_object.matrix_world @ blender_bone.matrix @ axis_basis_change
+              
+            #For object inside collection (at root), matrix world is already expressed regarding collection parent
+            if export_settings['vtree'].nodes[obj_uuid].parent_uuid is not None and export_settings['vtree'].nodes[export_settings['vtree'].nodes[obj_uuid].parent_uuid].blender_type == VExportNode.COLLECTION:
+                parent_mat = mathutils.Matrix.Identity(4).freeze()
+
+            mat = parent_mat.inverted_safe() @ blender_obj.matrix_world
+
+            if obj_uuid not in data.keys():
+                data[obj_uuid] = {}
+            
+            if blender_obj.animation_data and blender_obj.animation_data.action:
+                if blender_obj.animation_data.action.name not in data[obj_uuid].keys():
+                    data[obj_uuid][blender_obj.animation_data.action.name] = {}
+                data[obj_uuid][blender_obj.animation_data.action.name][frame] = mat
+            else:
+                # case of baking selected object.
+                # There is no animation, so use uuid of object as key
+                if obj_uuid not in data[obj_uuid].keys():
+                    data[obj_uuid][obj_uuid] = {}
+                data[obj_uuid][obj_uuid][frame] = mat
+
+        frame += step
+    return data
 
 @bonecache
-def get_bone_matrix(blender_object_if_armature: typing.Optional[bpy.types.Object],
+def get_bone_matrix(blender_obj_uuid_if_armature: typing.Optional[str],
                      channels: typing.Tuple[bpy.types.FCurve],
                      bake_bone: typing.Union[str, None],
                      bake_channel: typing.Union[str, None],
@@ -130,9 +201,11 @@ def get_bone_matrix(blender_object_if_armature: typing.Optional[bpy.types.Object
                      bake_range_end,
                      action_name: str,
                      current_frame: int,
-                     step: int
+                     step: int,
+                     export_settings
                      ):
 
+    blender_object_if_armature = export_settings['vtree'].nodes[blender_obj_uuid_if_armature].blender_object if blender_obj_uuid_if_armature is not None else None
     data = {}
 
     # Always using bake_range, because some bones may need to be baked,
@@ -145,35 +218,40 @@ def get_bone_matrix(blender_object_if_armature: typing.Optional[bpy.types.Object
     frame = start_frame
     while frame <= end_frame:
         data[frame] = {}
-        # we need to bake in the constraints
         bpy.context.scene.frame_set(int(frame))
-        for pbone in blender_object_if_armature.pose.bones:
-            if bake_bone is None:
-                matrix = pbone.matrix_basis.copy()
+        bones = export_settings['vtree'].get_all_bones(blender_obj_uuid_if_armature)
+
+        for bone_uuid in bones:
+            blender_bone = export_settings['vtree'].nodes[bone_uuid].blender_bone
+
+            if export_settings['vtree'].nodes[bone_uuid].parent_uuid is not None and export_settings['vtree'].nodes[export_settings['vtree'].nodes[bone_uuid].parent_uuid].blender_type == VExportNode.BONE:
+                blender_bone_parent = export_settings['vtree'].nodes[export_settings['vtree'].nodes[bone_uuid].parent_uuid].blender_bone
+                rest_mat = blender_bone_parent.bone.matrix_local.inverted_safe() @ blender_bone.bone.matrix_local
+                matrix = rest_mat.inverted_safe() @ blender_bone_parent.matrix.inverted_safe() @ blender_bone.matrix
             else:
-                if (pbone.bone.use_inherit_rotation == False or pbone.bone.inherit_scale != "FULL") and pbone.parent != None:
-                    rest_mat = (pbone.parent.bone.matrix_local.inverted_safe() @ pbone.bone.matrix_local)
-                    matrix = (rest_mat.inverted_safe() @ pbone.parent.matrix.inverted_safe() @ pbone.matrix)
+                if blender_bone.parent is None:
+                    matrix = blender_bone.bone.matrix_local.inverted_safe() @ blender_bone.matrix
                 else:
-                    matrix = pbone.matrix
-                    matrix = blender_object_if_armature.convert_space(pose_bone=pbone, matrix=matrix, from_space='POSE', to_space='LOCAL')
-
+                    # Bone has a parent, but in export, after filter, is at root of armature
+                    matrix = blender_bone.matrix.copy()
 
-            data[frame][pbone.name] = matrix
+            data[frame][blender_bone.name] = matrix
 
 
         # If some drivers must be evaluated, do it here, to avoid to have to change frame by frame later
-        drivers_to_manage = get_sk_drivers(blender_object_if_armature)
-        for dr_obj, dr_fcurves in drivers_to_manage:
-            vals = get_sk_driver_values(dr_obj, frame, dr_fcurves)
+        drivers_to_manage = get_sk_drivers(blender_obj_uuid_if_armature, export_settings)
+        for dr_obj_uuid, dr_fcurves in drivers_to_manage:
+            vals = get_sk_driver_values(dr_obj_uuid, frame, dr_fcurves, export_settings)
 
         frame += step
 
     return data
 
 # cache for performance reasons
+# This function is called 2 times, for input (timing) and output (key values)
 @cached
-def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Object],
+def gather_keyframes(blender_obj_uuid: str,
+                     is_armature: bool,
                      channels: typing.Tuple[bpy.types.FCurve],
                      non_keyed_values: typing.Tuple[typing.Optional[float]],
                      bake_bone: typing.Union[str, None],
@@ -182,32 +260,40 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
                      bake_range_end,
                      force_range: bool,
                      action_name: str,
-                     driver_obj,
+                     driver_obj_uuid,
                      node_channel_is_animated: bool,
                      export_settings
-                     ) -> typing.List[Keyframe]:
+                     ) -> typing.Tuple[typing.List[Keyframe], bool]:
     """Convert the blender action groups' fcurves to keyframes for use in glTF."""
+
+    blender_object_if_armature = export_settings['vtree'].nodes[blender_obj_uuid].blender_object if is_armature is True is not None else None
+    blender_obj_uuid_if_armature = blender_obj_uuid if is_armature is True else None
+
     if force_range is True:
         start_frame = bake_range_start
         end_frame = bake_range_end
     else:
-        if bake_bone is None and driver_obj is None:
+        if bake_bone is None and driver_obj_uuid is None:
             # Find the start and end of the whole action group
             # Note: channels has some None items only for SK if some SK are not animated
             ranges = [channel.range() for channel in channels if channel is not None]
 
-            start_frame = min([channel.range()[0] for channel in channels  if channel is not None])
-            end_frame = max([channel.range()[1] for channel in channels  if channel is not None])
+            if len(channels) != 0:
+                start_frame = min([channel.range()[0] for channel in channels  if channel is not None])
+                end_frame = max([channel.range()[1] for channel in channels  if channel is not None])
+            else:
+                start_frame = bake_range_start
+                end_frame = bake_range_end
         else:
             start_frame = bake_range_start
             end_frame = bake_range_end
 
     keyframes = []
-    if needs_baking(blender_object_if_armature, channels, export_settings):
+    baking_is_needed = needs_baking(blender_object_if_armature, channels, export_settings)
+    if baking_is_needed:
         # Bake the animation, by evaluating the animation for all frames
-        # TODO: maybe baking can also be done with FCurve.convert_to_samples
 
-        if blender_object_if_armature is not None and driver_obj is None:
+        if blender_object_if_armature is not None and driver_obj_uuid is None:
             if bake_bone is None:
                 pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature,
                                                                                channels[0].data_path)
@@ -224,7 +310,7 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
             if isinstance(pose_bone_if_armature, bpy.types.PoseBone):
 
                 mat = get_bone_matrix(
-                    blender_object_if_armature,
+                    blender_obj_uuid_if_armature,
                     channels,
                     bake_bone,
                     bake_channel,
@@ -232,7 +318,8 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
                     bake_range_end,
                     action_name,
                     frame,
-                    step
+                    step,
+                    export_settings
                 )
                 trans, rot, scale = mat.decompose()
 
@@ -248,12 +335,36 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
                     "scale": scale
                 }[target_property]
             else:
-                if driver_obj is None:
-                    # Note: channels has some None items only for SK if some SK are not animated
-                    key.value = [c.evaluate(frame) for c in channels if c is not None]
-                    complete_key(key, non_keyed_values)
+                if driver_obj_uuid is None:
+                    # If channel is TRS, we bake from world matrix, else this is SK
+                    if len(channels) != 0:
+                        target = [c for c in channels if c is not None][0].data_path.split('.')[-1]
+                    else:
+                        target = bake_channel
+                    if target == "value": #SK
+                        # Note: channels has some None items only for SK if some SK are not animated
+                        key.value = [c.evaluate(frame) for c in channels if c is not None]
+                        complete_key(key, non_keyed_values)
+                    else:
+
+                        mat = get_object_matrix(blender_obj_uuid,
+                                action_name,
+                                bake_range_start,
+                                bake_range_end,
+                                frame,
+                                step,
+                                export_settings)
+
+                        trans, rot, sca = mat.decompose()
+                        key.value_total = {
+                            "location": trans,
+                            "rotation_axis_angle": [rot.to_axis_angle()[1], rot.to_axis_angle()[0][0], rot.to_axis_angle()[0][1], rot.to_axis_angle()[0][2]],
+                            "rotation_euler": rot.to_euler(),
+                            "rotation_quaternion": rot,
+                            "scale": sca
+                        }[target]
                 else:
-                    key.value = get_sk_driver_values(driver_obj, frame, channels)
+                    key.value = get_sk_driver_values(driver_obj_uuid, frame, channels, export_settings)
                     complete_key(key, non_keyed_values)
             keyframes.append(key)
             frame += step
@@ -307,7 +418,7 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
             keyframes.append(key)
 
     if not export_settings[gltf2_blender_export_keys.OPTIMIZE_ANIMS]:
-        return keyframes
+        return (keyframes, baking_is_needed)
 
     # For armature only
     # Check if all values are the same
@@ -319,17 +430,20 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
 
         if node_channel_is_animated is True: # fcurve on this bone for this property
              # Keep animation, but keep only 2 keyframes if data are not changing
-             return [keyframes[0], keyframes[-1]] if cst is True and len(keyframes) >= 2 else keyframes
+             return ([keyframes[0], keyframes[-1]], baking_is_needed) if cst is True and len(keyframes) >= 2 else (keyframes, baking_is_needed)
         else: # bone is not animated (no fcurve)
             # Not keeping if not changing property
-            return None if cst is True else keyframes
+            return (None, baking_is_needed) if cst is True else (keyframes, baking_is_needed)
     else:
         # For objects, if all values are the same, we keep only first and last
         cst = fcurve_is_constant(keyframes)
-        return [keyframes[0], keyframes[-1]] if cst is True and len(keyframes) >= 2 else keyframes
-
+        if node_channel_is_animated is True:
+            return ([keyframes[0], keyframes[-1]], baking_is_needed) if cst is True and len(keyframes) >= 2 else (keyframes, baking_is_needed)
+        else:
+            # baked object (selected but not animated)
+            return (None, baking_is_needed) if cst is True else (keyframes, baking_is_needed)
 
-    return keyframes
+    return (keyframes, baking_is_needed)
 
 
 def fcurve_is_constant(keyframes):
@@ -374,6 +488,10 @@ def needs_baking(blender_object_if_armature: typing.Optional[bpy.types.Object],
     if export_settings[gltf2_blender_export_keys.FORCE_SAMPLING]:
         return True
 
+    # If tree is troncated, sampling is forced
+    if export_settings['vtree'].tree_troncated is True:
+        return True
+
     # Sampling due to unsupported interpolation
     interpolation = [c for c in channels if c is not None][0].keyframe_points[0].interpolation
     if interpolation not in ["BEZIER", "LINEAR", "CONSTANT"]:
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 b3cc9d305eccafae0a7ba9bb81ddde3ef6a69efd..143fcceac4fc083a8a7eba8bd1fa82e825294ca3 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
@@ -3,6 +3,7 @@
 
 
 import typing
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode
 
 import bpy
 import mathutils
@@ -21,20 +22,23 @@ from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extension
 
 @cached
 def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
-                             blender_object: bpy.types.Object,
+                             obj_uuid: str,
                              bake_bone: typing.Union[str, None],
                              bake_channel: typing.Union[str, None],
                              bake_range_start,
                              bake_range_end,
                              force_range: bool,
                              action_name: str,
-                             driver_obj,
+                             driver_obj_uuid,
                              node_channel_is_animated: bool,
+                             need_rotation_correction,
                              export_settings
                              ) -> gltf2_io.AnimationSampler:
 
-    blender_object_if_armature = blender_object if blender_object.type == "ARMATURE" else None
-    if blender_object_if_armature is not None and driver_obj is None:
+    blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+    is_armature = True if blender_object.type == "ARMATURE" else False
+    blender_object_if_armature = blender_object if is_armature is True else None
+    if is_armature is True and driver_obj_uuid is None:
         if bake_bone is None:
             pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature,
                                                                                channels[0].data_path)
@@ -45,15 +49,15 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
     non_keyed_values = __gather_non_keyed_values(channels, blender_object,
                                                  blender_object_if_armature, pose_bone_if_armature,
                                                  bake_channel,
-                                                 driver_obj,
+                                                 driver_obj_uuid,
                                                  export_settings)
     if blender_object.parent is not None:
         matrix_parent_inverse = blender_object.matrix_parent_inverse.copy().freeze()
     else:
         matrix_parent_inverse = mathutils.Matrix.Identity(4).freeze()
 
-    input = __gather_input(channels, blender_object_if_armature, non_keyed_values,
-                         bake_bone, bake_channel, bake_range_start, bake_range_end, force_range, action_name, driver_obj, node_channel_is_animated, export_settings)
+    input = __gather_input(channels, obj_uuid, is_armature, non_keyed_values,
+                         bake_bone, bake_channel, bake_range_start, bake_range_end, force_range, action_name, driver_obj_uuid, node_channel_is_animated, export_settings)
 
     if input is None:
         # After check, no need to animate this node for this channel
@@ -66,7 +70,8 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
         interpolation=__gather_interpolation(channels, blender_object_if_armature, export_settings, bake_bone, bake_channel),
         output=__gather_output(channels,
                                matrix_parent_inverse,
-                               blender_object_if_armature,
+                               obj_uuid,
+                               is_armature,
                                non_keyed_values,
                                bake_bone,
                                bake_channel,
@@ -74,8 +79,9 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
                                bake_range_end,
                                force_range,
                                action_name,
-                               driver_obj,
+                               driver_obj_uuid,
                                node_channel_is_animated,
+                               need_rotation_correction,
                                export_settings)
     )
 
@@ -97,12 +103,13 @@ def __gather_non_keyed_values(channels: typing.Tuple[bpy.types.FCurve],
                               blender_object_if_armature: typing.Optional[bpy.types.Object],
                               pose_bone_if_armature: typing.Optional[bpy.types.PoseBone],
                               bake_channel: typing.Union[str, None],
-                              driver_obj,
+                              driver_obj_uuid,
                               export_settings
                               ) ->  typing.Tuple[typing.Optional[float]]:
 
     non_keyed_values = []
 
+    driver_obj = export_settings['vtree'].nodes[driver_obj_uuid].blender_object if driver_obj_uuid is not None else None
     obj = blender_object if driver_obj is None else driver_obj
 
     # Note: channels has some None items only for SK if some SK are not animated
@@ -217,10 +224,10 @@ def __gather_extras(channels: typing.Tuple[bpy.types.FCurve],
                     ) -> typing.Any:
     return None
 
-
 @cached
 def __gather_input(channels: typing.Tuple[bpy.types.FCurve],
-                   blender_object_if_armature: typing.Optional[bpy.types.Object],
+                   blender_obj_uuid: str,
+                   is_armature: bool,
                    non_keyed_values: typing.Tuple[typing.Optional[float]],
                    bake_bone: typing.Union[str, None],
                    bake_channel: typing.Union[str, None],
@@ -228,12 +235,13 @@ def __gather_input(channels: typing.Tuple[bpy.types.FCurve],
                    bake_range_end,
                    force_range: bool,
                    action_name,
-                   driver_obj,
+                   driver_obj_uuid,
                    node_channel_is_animated: bool,
                    export_settings
                    ) -> gltf2_io.Accessor:
     """Gather the key time codes."""
-    keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_object_if_armature,
+    keyframes, is_baked = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_obj_uuid,
+                                                                                  is_armature,
                                                                                   channels,
                                                                                   non_keyed_values,
                                                                                   bake_bone,
@@ -242,7 +250,7 @@ def __gather_input(channels: typing.Tuple[bpy.types.FCurve],
                                                                                   bake_range_end,
                                                                                   force_range,
                                                                                   action_name,
-                                                                                  driver_obj,
+                                                                                  driver_obj_uuid,
                                                                                   node_channel_is_animated,
                                                                                   export_settings)
     if keyframes is None:
@@ -277,14 +285,15 @@ def __gather_interpolation(channels: typing.Tuple[bpy.types.FCurve],
             # TODO: check if the bone was animated with CONSTANT
             return 'LINEAR'
         else:
-            max_keyframes = max([len(ch.keyframe_points) for ch in channels if ch is not None])
-            # If only single keyframe revert to STEP
-            if max_keyframes < 2:
-                return 'STEP'
+            if len(channels) != 0: # channels can be empty when baking object (non animated selected object)
+                max_keyframes = max([len(ch.keyframe_points) for ch in channels if ch is not None])
+                # If only single keyframe revert to STEP
+                if max_keyframes < 2:
+                    return 'STEP'
 
-            # If all keyframes are CONSTANT, we can use STEP.
-            if all(all(k.interpolation == 'CONSTANT' for k in c.keyframe_points) for c in channels if c is not None):
-                return 'STEP'
+                # If all keyframes are CONSTANT, we can use STEP.
+                if all(all(k.interpolation == 'CONSTANT' for k in c.keyframe_points) for c in channels if c is not None):
+                    return 'STEP'
 
             # Otherwise, sampled keyframes use LINEAR interpolation.
             return 'LINEAR'
@@ -304,7 +313,8 @@ def __gather_interpolation(channels: typing.Tuple[bpy.types.FCurve],
 @cached
 def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
                     parent_inverse,
-                    blender_object_if_armature: typing.Optional[bpy.types.Object],
+                    blender_obj_uuid: str,
+                    is_armature: bool,
                     non_keyed_values: typing.Tuple[typing.Optional[float]],
                     bake_bone: typing.Union[str, None],
                     bake_channel: typing.Union[str, None],
@@ -314,10 +324,12 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
                     action_name,
                     driver_obj,
                     node_channel_is_animated: bool,
+                    need_rotation_correction: bool,
                     export_settings
                     ) -> gltf2_io.Accessor:
     """Gather the data of the keyframes."""
-    keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_object_if_armature,
+    keyframes, is_baked = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_obj_uuid,
+                                                                                  is_armature,
                                                                                   channels,
                                                                                   non_keyed_values,
                                                                                   bake_bone,
@@ -329,10 +341,19 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
                                                                                   driver_obj,
                                                                                   node_channel_is_animated,
                                                                                   export_settings)
+
+    if is_baked is True:
+        parent_inverse = mathutils.Matrix.Identity(4).freeze()
+
+    blender_object_if_armature = export_settings['vtree'].nodes[blender_obj_uuid].blender_object if is_armature is True else None
+
     if bake_bone is not None:
         target_datapath = "pose.bones['" + bake_bone + "']." + bake_channel
     else:
-        target_datapath = [c for c in channels if c is not None][0].data_path
+        if len(channels) != 0: # channels can be empty when baking object (non animated selected object)
+            target_datapath = [c for c in channels if c is not None][0].data_path
+        else:
+            target_datapath = bake_channel
 
     is_yup = export_settings[gltf2_blender_export_keys.YUP]
 
@@ -355,6 +376,7 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
             bone = blender_object_if_armature.pose.bones[bake_bone]
         if isinstance(bone, bpy.types.PoseBone):
             if bone.parent is None:
+                # bone at root of armature
                 axis_basis_change = mathutils.Matrix.Identity(4)
                 if export_settings[gltf2_blender_export_keys.YUP]:
                     axis_basis_change = mathutils.Matrix(
@@ -364,10 +386,25 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
                          (0.0, 0.0, 0.0, 1.0)))
                 correction_matrix_local = axis_basis_change @ bone.bone.matrix_local
             else:
-                correction_matrix_local = (
-                    bone.parent.bone.matrix_local.inverted_safe() @
-                    bone.bone.matrix_local
-                )
+                # Bone is not at root of armature
+                # There are 2 cases :
+                parent_uuid = export_settings['vtree'].nodes[export_settings['vtree'].nodes[blender_obj_uuid].bones[bone.name]].parent_uuid
+                if parent_uuid is not None and export_settings['vtree'].nodes[parent_uuid].blender_type == VExportNode.BONE:
+                    # export bone is not at root of armature neither
+                    correction_matrix_local = (
+                        bone.parent.bone.matrix_local.inverted_safe() @
+                        bone.bone.matrix_local
+                    )
+                else:
+                    # exported bone (after filter) is at root of armature
+                    axis_basis_change = mathutils.Matrix.Identity(4)
+                    if export_settings[gltf2_blender_export_keys.YUP]:
+                        axis_basis_change = mathutils.Matrix(
+                            ((1.0, 0.0, 0.0, 0.0),
+                            (0.0, 0.0, 1.0, 0.0),
+                            (0.0, -1.0, 0.0, 0.0),
+                            (0.0, 0.0, 0.0, 1.0)))
+                    correction_matrix_local = axis_basis_change
 
             transform = correction_matrix_local
         else:
@@ -378,14 +415,14 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
     values = []
     for keyframe in keyframes:
         # Transform the data and build gltf control points
-        value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform)
+        value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform, need_rotation_correction)
         if is_yup and not is_armature_animation:
             value = gltf2_blender_math.swizzle_yup(value, target_datapath)
         keyframe_value = gltf2_blender_math.mathutils_to_gltf(value)
 
         if keyframe.in_tangent is not None:
             # we can directly transform the tangent as it currently is represented by a control point
-            in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform)
+            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
@@ -397,7 +434,7 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
 
         if keyframe.out_tangent is not None:
             # we can directly transform the tangent as it currently is represented by a control point
-            out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform)
+            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
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 39f09d521f59f0dc548a1cdce6ca69e48ed0faef..828d1955d543a0a04d3d93102f6854d4017fc355 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
@@ -11,7 +11,36 @@ from ..com.gltf2_blender_extras import generate_extras
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
 
 
-def gather_animations(blender_object: bpy.types.Object,
+def __gather_channels_baked(obj_uuid, export_settings):
+    channels = []
+
+    # If no animation in file, no need to bake
+    if len(bpy.data.actions) == 0:
+        return None
+
+    start_frame = min([v[0] for v in [a.frame_range for a in bpy.data.actions]])
+    end_frame = max([v[1] for v in [a.frame_range for a in bpy.data.actions]])
+
+    for p in ["location", "rotation_quaternion", "scale"]:
+        channel = gltf2_blender_gather_animation_channels.gather_animation_channel(
+            obj_uuid,
+            (),
+            export_settings,
+            None,
+            p,
+            start_frame,
+            end_frame,
+            False,
+            obj_uuid, # Use obj uuid as action name for caching
+            None,
+            False #If Object is not animated, don't keep animation for this channel
+            ) 
+        if channel is not None:
+            channels.append(channel)
+
+    return channels if len(channels) > 0 else None  
+
+def gather_animations(  obj_uuid: int,
                         tracks: typing.Dict[str, typing.List[int]],
                         offset: int,
                         export_settings) -> typing.Tuple[typing.List[gltf2_io.Animation], typing.Dict[str, typing.List[int]]]:
@@ -24,11 +53,29 @@ def gather_animations(blender_object: bpy.types.Object,
     """
     animations = []
 
+    blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+
     # Collect all 'actions' affecting this object. There is a direct mapping between blender actions and glTF animations
     blender_actions = __get_blender_actions(blender_object, export_settings)
 
-    # save the current active action of the object, if any
-    # We will restore it after export
+    if len([a for a in blender_actions if a[2] == "OBJECT"]) == 0:
+        # No TRS animation are found for this object.
+        # But we need to bake, in case we export selection
+        if export_settings['gltf_selected'] is True and blender_object.type != "ARMATURE": 
+            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)
+
     current_action = None
     if blender_object.animation_data and blender_object.animation_data.action:
         current_action = blender_object.animation_data.action
@@ -63,7 +110,7 @@ def gather_animations(blender_object: bpy.types.Object,
 
         # No need to set active shapekeys animations, this is needed for bone baking
 
-        animation = __gather_animation(blender_action, blender_object, export_settings)
+        animation = __gather_animation(obj_uuid, blender_action, export_settings)
         if animation is not None:
             animations.append(animation)
 
@@ -91,21 +138,24 @@ def gather_animations(blender_object: bpy.types.Object,
     return animations, tracks
 
 
-def __gather_animation(blender_action: bpy.types.Action,
-                       blender_object: bpy.types.Object,
-                       export_settings
+def __gather_animation( obj_uuid: int,
+                        blender_action: bpy.types.Action,
+                        export_settings
                        ) -> typing.Optional[gltf2_io.Animation]:
+
+    blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+
     if not __filter_animation(blender_action, blender_object, export_settings):
         return None
 
     name = __gather_name(blender_action, blender_object, export_settings)
     try:
         animation = gltf2_io.Animation(
-            channels=__gather_channels(blender_action, blender_object, export_settings),
+            channels=__gather_channels(obj_uuid, blender_action, export_settings),
             extensions=__gather_extensions(blender_action, blender_object, export_settings),
             extras=__gather_extras(blender_action, blender_object, export_settings),
             name=name,
-            samplers=__gather_samplers(blender_action, blender_object, export_settings)
+            samplers=__gather_samplers(obj_uuid, blender_action, export_settings)
         )
     except RuntimeError as error:
         print_console("WARNING", "Animation '{}' could not be exported. Cause: {}".format(name, error))
@@ -134,12 +184,12 @@ def __filter_animation(blender_action: bpy.types.Action,
     return True
 
 
-def __gather_channels(blender_action: bpy.types.Action,
-                      blender_object: bpy.types.Object,
+def __gather_channels(obj_uuid: int,
+                      blender_action: bpy.types.Action,
                       export_settings
                       ) -> typing.List[gltf2_io.AnimationChannel]:
     return gltf2_blender_gather_animation_channels.gather_animation_channels(
-        blender_action, blender_object, export_settings)
+        obj_uuid, blender_action, export_settings)
 
 
 def __gather_extensions(blender_action: bpy.types.Action,
@@ -166,8 +216,8 @@ def __gather_name(blender_action: bpy.types.Action,
     return blender_action.name
 
 
-def __gather_samplers(blender_action: bpy.types.Action,
-                      blender_object: bpy.types.Object,
+def __gather_samplers(obj_uuid: str,
+                      blender_action: bpy.types.Action,
                       export_settings
                       ) -> typing.List[gltf2_io.AnimationSampler]:
     # We need to gather the samplers after gathering all channels --> populate this list in __link_samplers
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py
index 7e49ac02f61bfc9d8a55811190861da39f7f2496..4f95431ca94edc900ba24dc033a2acdc4932b096 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py
@@ -6,83 +6,134 @@ import bpy
 from io_scene_gltf2.blender.exp import gltf2_blender_get
 
 
-def cached(func):
+def cached_by_key(key):
+    """
+    Decorates functions whose result should be cached. Use it like:
+        @cached_by_key(key=...)
+        def func(..., export_settings):
+            ...
+    The decorated function, func, must always take an "export_settings" arg
+    (the cache is stored here).
+    The key argument to the decorator is a function that computes the key to
+    cache on. It is passed all the arguments to func.
     """
-    Decorate the cache gather functions results.
+    def inner(func):
+        @functools.wraps(func)
+        def wrapper_cached(*args, **kwargs):
+            if kwargs.get("export_settings"):
+                export_settings = kwargs["export_settings"]
+            else:
+                export_settings = args[-1]
+
+            cache_key = key(*args, **kwargs)
+
+            # invalidate cache if export settings have changed
+            if not hasattr(func, "__export_settings") or export_settings != func.__export_settings:
+                func.__cache = {}
+                func.__export_settings = export_settings
+            # use or fill cache
+            if cache_key in func.__cache:
+                return func.__cache[cache_key]
+            else:
+                result = func(*args, **kwargs)
+                func.__cache[cache_key] = result
+                return result
+
+        return wrapper_cached
+
+    return inner
 
-    The gather function is only executed if its result isn't in the cache yet
-    :param func: the function to be decorated. It will have a static __cache member afterwards
-    :return:
+
+def default_key(*args, **kwargs):
+    """
+    Default cache key for @cached functions.
+    Cache on all arguments (except export_settings).
     """
+    assert len(args) >= 2 and 0 <= len(kwargs) <= 1, "Wrong signature for cached function"
+    cache_key_args = args
+    # make a shallow copy of the keyword arguments so that 'export_settings' can be removed
+    cache_key_kwargs = dict(kwargs)
+    if kwargs.get("export_settings"):
+        del cache_key_kwargs["export_settings"]
+    else:
+        cache_key_args = args[:-1]
+
+    cache_key = ()
+    for i in cache_key_args:
+        cache_key += (i,)
+    for i in cache_key_kwargs.values():
+        cache_key += (i,)
+
+    return cache_key
+
+
+def cached(func):
+    return cached_by_key(key=default_key)(func)
+
+def objectcache(func):
+
+    def reset_cache_objectcache():
+        func.__objectcache = {}
+
+    func.reset_cache = reset_cache_objectcache
+
     @functools.wraps(func)
-    def wrapper_cached(*args, **kwargs):
-        assert len(args) >= 2 and 0 <= len(kwargs) <= 1, "Wrong signature for cached function"
+    def wrapper_objectcache(*args, **kwargs):
         cache_key_args = args
-        # make a shallow copy of the keyword arguments so that 'export_settings' can be removed
-        cache_key_kwargs = dict(kwargs)
-        if kwargs.get("export_settings"):
-            export_settings = kwargs["export_settings"]
-            # 'export_settings' should not be cached
-            del cache_key_kwargs["export_settings"]
-        else:
-            export_settings = args[-1]
-            cache_key_args = args[:-1]
+        cache_key_args = args[:-1]
 
-        __by_name = [bpy.types.Object, bpy.types.Scene, bpy.types.Material, bpy.types.Action, bpy.types.Mesh, bpy.types.PoseBone]
+        if not hasattr(func, "__objectcache"):
+            func.reset_cache()
 
-        # we make a tuple from the function arguments so that they can be used as a key to the cache
-        cache_key = ()
-        for i in cache_key_args:
-            if type(i) in __by_name:
-                cache_key += (i.name,)
-            else:
-                cache_key += (i,)
-        for i in cache_key_kwargs.values():
-            if type(i) in __by_name:
-                cache_key += (i.name,)
-            else:
-                cache_key += (i,)
-
-        # invalidate cache if export settings have changed
-        if not hasattr(func, "__export_settings") or export_settings != func.__export_settings:
-            func.__cache = {}
-            func.__export_settings = export_settings
-        # use or fill cache
-        if cache_key in func.__cache:
-            return func.__cache[cache_key]
-        else:
+        # object is not cached yet
+        if cache_key_args[0] not in func.__objectcache.keys():
             result = func(*args)
-            func.__cache[cache_key] = result
-            return result
-    return wrapper_cached
+            func.__objectcache = result
+            return result[cache_key_args[0]][cache_key_args[1]][cache_key_args[4]]
+        # object is in cache, but not this action
+        # We need to keep other actions
+        elif cache_key_args[1] not in func.__objectcache[cache_key_args[0]].keys():
+            result = func(*args)
+            func.__objectcache[cache_key_args[0]][cache_key_args[1]] = result[cache_key_args[0]][cache_key_args[1]]
+            return result[cache_key_args[0]][cache_key_args[1]][cache_key_args[4]]
+        # all is already cached
+        else:
+            return func.__objectcache[cache_key_args[0]][cache_key_args[1]][cache_key_args[4]]
+    return wrapper_objectcache
 
 def bonecache(func):
 
     def reset_cache_bonecache():
         func.__current_action_name = None
-        func.__current_armature_name = None
+        func.__current_armature_uuid = None
         func.__bonecache = {}
 
     func.reset_cache = reset_cache_bonecache
 
     @functools.wraps(func)
     def wrapper_bonecache(*args, **kwargs):
-        if args[2] is None:
-            pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(args[0],
-                                                                args[1][0].data_path)
+
+        armature = args[-1]['vtree'].nodes[args[0]].blender_object
+
+        cache_key_args = args
+        cache_key_args = args[:-1]
+
+        if cache_key_args[2] is None:
+            pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(armature,
+                                                                cache_key_args[1][0].data_path)
         else:
-            pose_bone_if_armature = args[0].pose.bones[args[2]]
+            pose_bone_if_armature = armature.pose.bones[cache_key_args[2]]
 
         if not hasattr(func, "__current_action_name"):
             func.reset_cache()
-        if args[6] != func.__current_action_name or args[0] != func.__current_armature_name:
+        if cache_key_args[6] != func.__current_action_name or cache_key_args[0] != func.__current_armature_uuid:
             result = func(*args)
             func.__bonecache = result
-            func.__current_action_name = args[6]
-            func.__current_armature_name = args[0]
-            return result[args[7]][pose_bone_if_armature.name]
+            func.__current_action_name = cache_key_args[6]
+            func.__current_armature_uuid = cache_key_args[0]
+            return result[cache_key_args[7]][pose_bone_if_armature.name]
         else:
-            return func.__bonecache[args[7]][pose_bone_if_armature.name]
+            return func.__bonecache[cache_key_args[7]][pose_bone_if_armature.name]
     return wrapper_bonecache
 
 # TODO: replace "cached" with "unique" in all cases where the caching is functional and not only for performance reasons
@@ -92,23 +143,27 @@ unique = cached
 def skdriverdiscovercache(func):
 
     def reset_cache_skdriverdiscovercache():
-        func.__current_armature_name = None
+        func.__current_armature_uuid = None
         func.__skdriverdiscover = {}
 
     func.reset_cache = reset_cache_skdriverdiscovercache
 
     @functools.wraps(func)
     def wrapper_skdriverdiscover(*args, **kwargs):
-        if not hasattr(func, "__current_armature_name") or func.__current_armature_name is None:
+
+        cache_key_args = args
+        cache_key_args = args[:-1]
+
+        if not hasattr(func, "__current_armature_uuid") or func.__current_armature_uuid is None:
             func.reset_cache()
 
-        if args[0] != func.__current_armature_name:
+        if cache_key_args[0] != func.__current_armature_uuid:
             result = func(*args)
-            func.__skdriverdiscover[args[0]] = result
-            func.__current_armature_name = args[0]
+            func.__skdriverdiscover[cache_key_args[0]] = result
+            func.__current_armature_uuid = cache_key_args[0]
             return result
         else:
-            return func.__skdriverdiscover[args[0]]
+            return func.__skdriverdiscover[cache_key_args[0]]
     return wrapper_skdriverdiscover
 
 def skdrivervalues(func):
@@ -123,12 +178,17 @@ def skdrivervalues(func):
         if not hasattr(func, "__skdrivervalues") or func.__skdrivervalues is None:
             func.reset_cache()
 
-        if args[0].name not in func.__skdrivervalues.keys():
-            func.__skdrivervalues[args[0].name] = {}
-        if args[1] not in func.__skdrivervalues[args[0].name]:
+        armature = args[-1]['vtree'].nodes[args[0]].blender_object
+
+        cache_key_args = args
+        cache_key_args = args[:-1]
+
+        if armature.name not in func.__skdrivervalues.keys():
+            func.__skdrivervalues[armature.name] = {}
+        if cache_key_args[1] not in func.__skdrivervalues[armature.name]:
             vals = func(*args)
-            func.__skdrivervalues[args[0].name][args[1]] = vals
+            func.__skdrivervalues[armature.name][cache_key_args[1]] = vals
             return vals
         else:
-            return func.__skdrivervalues[args[0].name][args[1]]
+            return func.__skdrivervalues[armature.name][cache_key_args[1]]
     return wrapper_skdrivervalues
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 1f82c2b3872bd79bf5684aed097423ac9e4ab6d5..4e77f60e89ac36e681e01372c701ea972a68145e 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
@@ -5,13 +5,20 @@
 from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import skdriverdiscovercache, skdrivervalues
 from io_scene_gltf2.blender.com.gltf2_blender_data_path import get_target_object_path
 
-
 @skdriverdiscovercache
-def get_sk_drivers(blender_armature):
+def get_sk_drivers(blender_armature_uuid, export_settings):
+
+    blender_armature = export_settings['vtree'].nodes[blender_armature_uuid].blender_object
 
     drivers = []
 
-    for child in blender_armature.children:
+    for child_uuid in export_settings['vtree'].nodes[blender_armature_uuid].children:
+
+        if export_settings['vtree'].nodes[child_uuid].blender_type == "BONE":
+            continue
+
+        child = export_settings['vtree'].nodes[child_uuid].blender_object
+
         if not child.data:
             continue
         # child.data can be an armature - which has no shapekeys
@@ -63,13 +70,14 @@ def get_sk_drivers(blender_armature):
                 all_sorted_channels.append(existing_idx[i])
 
         if len(all_sorted_channels) > 0:
-            drivers.append((child, tuple(all_sorted_channels)))
+            drivers.append((child_uuid, tuple(all_sorted_channels)))
 
     return tuple(drivers)
 
 @skdrivervalues
-def get_sk_driver_values(blender_object, frame, fcurves):
+def get_sk_driver_values(blender_object_uuid, frame, fcurves, export_settings):
     sk_values = []
+    blender_object = export_settings['vtree'].nodes[blender_object_uuid].blender_object
     for f in [f for f in fcurves if f is not None]:
         sk_values.append(blender_object.data.shape_keys.path_resolve(get_target_object_path(f.data_path)).value)
 
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py
index ffc1231b13983b17f54900713b96d1ca34b390ad..f4fd6c5127092ed7c47a5820a2ec3c6e5669c04a 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: Apache-2.0
 # Copyright 2018-2021 The glTF-Blender-IO authors.
 
-import mathutils
+from mathutils import Matrix, Quaternion, Vector
 
 from . import gltf2_blender_export_keys
 from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
@@ -9,9 +9,40 @@ from io_scene_gltf2.io.com import gltf2_io
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
 from ..com.gltf2_blender_extras import generate_extras
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_tree
+
+
+
+# TODO these 3 functions move to shared file
+def __convert_swizzle_location(loc, export_settings):
+    """Convert a location from Blender coordinate system to glTF coordinate system."""
+    if export_settings[gltf2_blender_export_keys.YUP]:
+        return Vector((loc[0], loc[2], -loc[1]))
+    else:
+        return Vector((loc[0], loc[1], loc[2]))
+
+
+def __convert_swizzle_rotation(rot, export_settings):
+    """
+    Convert a quaternion rotation from Blender coordinate system to glTF coordinate system.
+
+    'w' is still at first position.
+    """
+    if export_settings[gltf2_blender_export_keys.YUP]:
+        return Quaternion((rot[0], rot[1], rot[3], -rot[2]))
+    else:
+        return Quaternion((rot[0], rot[1], rot[2], rot[3]))
+
+
+def __convert_swizzle_scale(scale, export_settings):
+    """Convert a scale from Blender coordinate system to glTF coordinate system."""
+    if export_settings[gltf2_blender_export_keys.YUP]:
+        return Vector((scale[0], scale[2], scale[1]))
+    else:
+        return Vector((scale[0], scale[1], scale[2]))
 
 @cached
-def gather_joint(blender_object, blender_bone, export_settings):
+def gather_joint_vnode(vnode, export_settings):
     """
     Generate a glTF2 node from a blender bone, as joints in glTF2 are simply nodes.
 
@@ -19,28 +50,19 @@ def gather_joint(blender_object, blender_bone, export_settings):
     :param export_settings: the settings for this export
     :return: a glTF2 node (acting as a joint)
     """
-    axis_basis_change = mathutils.Matrix.Identity(4)
-    if export_settings[gltf2_blender_export_keys.YUP]:
-        axis_basis_change = mathutils.Matrix(
-            ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
+    vtree = export_settings['vtree']
+    blender_object = vtree.nodes[vnode].blender_object
+    blender_bone = vtree.nodes[vnode].blender_bone
 
-    # extract bone transform
-    if blender_bone.parent is None:
-        correction_matrix_local = axis_basis_change @ blender_bone.bone.matrix_local
-    else:
-        correction_matrix_local = (
-            blender_bone.parent.bone.matrix_local.inverted_safe() @
-            blender_bone.bone.matrix_local
-        )
-
-    if (blender_bone.bone.use_inherit_rotation == False or blender_bone.bone.inherit_scale != "FULL") and blender_bone.parent != None:
-        rest_mat = (blender_bone.parent.bone.matrix_local.inverted_safe() @ blender_bone.bone.matrix_local)
-        matrix_basis = (rest_mat.inverted_safe() @ blender_bone.parent.matrix.inverted_safe() @ blender_bone.matrix)
-    else:
-        matrix_basis = blender_bone.matrix
-        matrix_basis = blender_object.convert_space(pose_bone=blender_bone, matrix=matrix_basis, from_space='POSE', to_space='LOCAL')
 
-    trans, rot, sca = (correction_matrix_local @ matrix_basis).decompose()
+    mat = vtree.nodes[vtree.nodes[vnode].parent_uuid].matrix_world.inverted_safe() @ vtree.nodes[vnode].matrix_world
+
+    trans, rot, sca = mat.decompose()
+
+    trans = __convert_swizzle_location(trans, export_settings)
+    rot = __convert_swizzle_rotation(rot, export_settings)
+    sca = __convert_swizzle_scale(sca, export_settings)
+
     translation, rotation, scale = (None, None, None)
     if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
         translation = [trans[0], trans[1], trans[2]]
@@ -52,14 +74,8 @@ def gather_joint(blender_object, blender_bone, export_settings):
     # traverse into children
     children = []
 
-    if export_settings["gltf_def_bones"] is False:
-        for bone in blender_bone.children:
-            children.append(gather_joint(blender_object, bone, export_settings))
-    else:
-        _, children_, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_bone.id_data)
-        if blender_bone.name in children_.keys():
-            for bone in children_[blender_bone.name]:
-                children.append(gather_joint(blender_object, blender_bone.id_data.pose.bones[bone], export_settings))
+    for bone_uuid in [c for c in vtree.nodes[vnode].children if vtree.nodes[c].blender_type == gltf2_blender_gather_tree.VExportNode.BONE]:
+        children.append(gather_joint_vnode(bone_uuid, export_settings))
 
     # finally add to the joints array containing all the joints in the hierarchy
     node = gltf2_io.Node(
@@ -79,6 +95,8 @@ def gather_joint(blender_object, blender_bone, export_settings):
 
     export_user_extensions('gather_joint_hook', export_settings, node, blender_bone)
 
+    vtree.nodes[vnode].node = node
+
     return node
 
 def __gather_extras(blender_bone, export_settings):
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 2d83b0dd86980f9cdc0b34f34782959e0090369f..845024e576884157202f546481f6a42764efbd2d 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
@@ -3,7 +3,7 @@
 
 import bpy
 
-from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+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_texture_info, gltf2_blender_export_keys
@@ -16,8 +16,14 @@ 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
 
-
 @cached
+def get_material_cache_key(blender_material, export_settings):
+    # Use id of material
+    # Do not use bpy.types that can be unhashable
+    # Do not use material name, that can be not unique (when linked)
+    return ((id(blender_material),))
+
+@cached_by_key(key=get_material_cache_key)
 def gather_material(blender_material, export_settings):
     """
     Gather the material used by the blender primitive.
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
index ac90e4cd10727881605ea9441c65bbcd75bcd5fe..c898712748cc6e67dd41bd0f3a957c51b2467f80 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
@@ -4,7 +4,7 @@
 import bpy
 from typing import Optional, Dict, List, Any, Tuple
 from .gltf2_blender_export_keys import MORPH
-from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+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.blender.exp import gltf2_blender_gather_primitives
 from ..com.gltf2_blender_extras import generate_extras
@@ -13,30 +13,64 @@ from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extension
 
 
 @cached
+def get_mesh_cache_key(blender_mesh,
+                blender_object,
+                vertex_groups,
+                modifiers,
+                skip_filter,
+                materials,
+                original_mesh,
+                export_settings):
+    # Use id of original mesh
+    # Do not use bpy.types that can be unhashable
+    # Do not use mesh name, that can be not unique (when linked)
+
+    # If materials are not exported, no need to cache by material
+    if export_settings['gltf_materials'] is None:
+        mats = None
+    else:
+        mats = tuple(id(m) if m is not None else None for m in materials)
+
+    # TODO check what is really needed for modifiers
+
+    mesh_to_id_cache = blender_mesh if original_mesh is None else original_mesh
+    return (
+        (id(mesh_to_id_cache),),
+        (modifiers,),
+        (skip_filter,),             #TODO to check if still needed
+        mats
+    )
+
+@cached_by_key(key=get_mesh_cache_key)
 def gather_mesh(blender_mesh: bpy.types.Mesh,
-                library: Optional[str],
-                blender_object: Optional[bpy.types.Object],
+                uuid_for_skined_data,
                 vertex_groups: Optional[bpy.types.VertexGroups],
                 modifiers: Optional[bpy.types.ObjectModifiers],
                 skip_filter: bool,
-                material_names: Tuple[str],
+                materials: Tuple[bpy.types.Material],
+                original_mesh: bpy.types.Mesh,
                 export_settings
                 ) -> Optional[gltf2_io.Mesh]:
-    if not skip_filter and not __filter_mesh(blender_mesh, library, vertex_groups, modifiers, export_settings):
+    if not skip_filter and not __filter_mesh(blender_mesh, vertex_groups, modifiers, export_settings):
         return None
 
     mesh = gltf2_io.Mesh(
-        extensions=__gather_extensions(blender_mesh, library, vertex_groups, modifiers, export_settings),
-        extras=__gather_extras(blender_mesh, library, vertex_groups, modifiers, export_settings),
-        name=__gather_name(blender_mesh, library, vertex_groups, modifiers, export_settings),
-        weights=__gather_weights(blender_mesh, library, vertex_groups, modifiers, export_settings),
-        primitives=__gather_primitives(blender_mesh, library, blender_object, vertex_groups, modifiers, material_names, export_settings),
+        extensions=__gather_extensions(blender_mesh, vertex_groups, modifiers, export_settings),
+        extras=__gather_extras(blender_mesh, vertex_groups, modifiers, export_settings),
+        name=__gather_name(blender_mesh, vertex_groups, modifiers, export_settings),
+        weights=__gather_weights(blender_mesh, vertex_groups, modifiers, export_settings),
+        primitives=__gather_primitives(blender_mesh, uuid_for_skined_data, vertex_groups, modifiers, materials, export_settings),
     )
 
     if len(mesh.primitives) == 0:
         print_console("WARNING", "Mesh '{}' has no primitives and will be omitted.".format(mesh.name))
         return None
 
+    blender_object = None
+    if uuid_for_skined_data:
+        blender_object = export_settings['vtree'].nodes[uuid_for_skined_data].blender_object
+
+
     export_user_extensions('gather_mesh_hook',
                            export_settings,
                            mesh,
@@ -45,13 +79,12 @@ def gather_mesh(blender_mesh: bpy.types.Mesh,
                            vertex_groups,
                            modifiers,
                            skip_filter,
-                           material_names)
+                           materials)
 
     return mesh
 
 
 def __filter_mesh(blender_mesh: bpy.types.Mesh,
-                  library: Optional[str],
                   vertex_groups: Optional[bpy.types.VertexGroups],
                   modifiers: Optional[bpy.types.ObjectModifiers],
                   export_settings
@@ -63,7 +96,6 @@ def __filter_mesh(blender_mesh: bpy.types.Mesh,
 
 
 def __gather_extensions(blender_mesh: bpy.types.Mesh,
-                        library: Optional[str],
                         vertex_groups: Optional[bpy.types.VertexGroups],
                         modifiers: Optional[bpy.types.ObjectModifiers],
                         export_settings
@@ -72,7 +104,6 @@ def __gather_extensions(blender_mesh: bpy.types.Mesh,
 
 
 def __gather_extras(blender_mesh: bpy.types.Mesh,
-                    library: Optional[str],
                     vertex_groups: Optional[bpy.types.VertexGroups],
                     modifiers: Optional[bpy.types.ObjectModifiers],
                     export_settings
@@ -100,7 +131,6 @@ def __gather_extras(blender_mesh: bpy.types.Mesh,
 
 
 def __gather_name(blender_mesh: bpy.types.Mesh,
-                  library: Optional[str],
                   vertex_groups: Optional[bpy.types.VertexGroups],
                   modifiers: Optional[bpy.types.ObjectModifiers],
                   export_settings
@@ -109,24 +139,21 @@ def __gather_name(blender_mesh: bpy.types.Mesh,
 
 
 def __gather_primitives(blender_mesh: bpy.types.Mesh,
-                        library: Optional[str],
-                        blender_object: Optional[bpy.types.Object],
+                        uuid_for_skined_data,
                         vertex_groups: Optional[bpy.types.VertexGroups],
                         modifiers: Optional[bpy.types.ObjectModifiers],
-                        material_names: Tuple[str],
+                        materials: Tuple[bpy.types.Material],
                         export_settings
                         ) -> List[gltf2_io.MeshPrimitive]:
     return gltf2_blender_gather_primitives.gather_primitives(blender_mesh,
-                                                             library,
-                                                             blender_object,
+                                                             uuid_for_skined_data,
                                                              vertex_groups,
                                                              modifiers,
-                                                             material_names,
+                                                             materials,
                                                              export_settings)
 
 
 def __gather_weights(blender_mesh: bpy.types.Mesh,
-                     library: Optional[str],
                      vertex_groups: Optional[bpy.types.VertexGroups],
                      modifiers: Optional[bpy.types.ObjectModifiers],
                      export_settings
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 a69f5d5805e71ab2cb6bfe6539aca9932a29739a..2578496062789c1b15f44fa559cfd3c70b61ff82 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
@@ -13,135 +13,47 @@ 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_gather_lights
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode
 from ..com.gltf2_blender_extras import generate_extras
 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.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 import gltf2_blender_gather_tree
 
 
-def gather_node(blender_object, library, blender_scene, dupli_object_parent, export_settings):
-    # custom cache to avoid cache miss when called from animation
-    # with blender_scene=None
-
-    # invalidate cache if export settings have changed
-    if not hasattr(gather_node, "__export_settings") or export_settings != gather_node.__export_settings:
-        gather_node.__cache = {}
-        gather_node.__export_settings = export_settings
-
-    if blender_scene is None and (blender_object.name, library) in gather_node.__cache:
-        return gather_node.__cache[(blender_object.name, library)]
-
-    node = __gather_node(blender_object, library, blender_scene, dupli_object_parent, export_settings)
-    gather_node.__cache[(blender_object.name, library)] = node
-    return node
-
-@cached
-def __gather_node(blender_object, library, blender_scene, dupli_object_parent, export_settings):
-    children, only_bone_children = __gather_children(blender_object, blender_scene, export_settings)
-
-    camera = None
-    mesh = None
-    skin = None
-    weights = None
-
-    # If blender_scene is None, we are coming from animation export
-    # Check to know if object is exported is already done, so we don't check
-    # again if object is instanced in scene : this check was already done when exporting object itself
-    if not __filter_node(blender_object, blender_scene, export_settings):
-        if children:
-            # This node should be filtered out, but has un-filtered children present.
-            # So, export this node, excluding its camera, mesh, skin, and weights.
-            # The transformations and animations on this node will have visible effects on children.
-
-            # Armature always have children node(s) (that are bone(s))
-            # We have to check if children are only bones or not for armatures
-            if blender_object.type == "ARMATURE" and only_bone_children is True:
-                return None
-
-            pass
-        else:
-            # This node is filtered out, and has no un-filtered children or descendants.
-            return None
-    else:
-        # This node is being fully exported.
-        camera = __gather_camera(blender_object, export_settings)
-        mesh = __gather_mesh(blender_object, library, export_settings)
-        skin = __gather_skin(blender_object, export_settings)
-        weights = __gather_weights(blender_object, export_settings)
+def gather_node(vnode, export_settings):
+    blender_object = vnode.blender_object
 
+    skin = __gather_skin(vnode, blender_object, export_settings)
     node = gltf2_io.Node(
-        camera=camera,
-        children=children,
+        camera=__gather_camera(blender_object, export_settings),
+        children=__gather_children(vnode, blender_object, export_settings),
         extensions=__gather_extensions(blender_object, export_settings),
         extras=__gather_extras(blender_object, export_settings),
         matrix=__gather_matrix(blender_object, export_settings),
-        mesh=mesh,
+        mesh=__gather_mesh(vnode, blender_object, export_settings),
         name=__gather_name(blender_object, export_settings),
         rotation=None,
         scale=None,
         skin=skin,
         translation=None,
-        weights=weights
+        weights=__gather_weights(blender_object, export_settings)
     )
 
     # If node mesh is skined, transforms should be ignored at import, so no need to set them here
     if node.skin is None:
-        node.translation, node.rotation, node.scale = __gather_trans_rot_scale(blender_object, export_settings)
+        node.translation, node.rotation, node.scale = __gather_trans_rot_scale(vnode, export_settings)
 
-    if export_settings[gltf2_blender_export_keys.YUP]:
-        # Checking node.extensions is making sure that the type of lamp is managed, and will be exported
-        if blender_object.type == 'LIGHT' and export_settings[gltf2_blender_export_keys.LIGHTS] and node.extensions:
-            correction_node = __get_correction_node(blender_object, export_settings)
-            correction_node.extensions = {"KHR_lights_punctual": node.extensions["KHR_lights_punctual"]}
-            del node.extensions["KHR_lights_punctual"]
-            node.children.append(correction_node)
-        if blender_object.type == 'CAMERA' and export_settings[gltf2_blender_export_keys.CAMERAS]:
-            correction_node = __get_correction_node(blender_object, export_settings)
-            correction_node.camera = node.camera
-            node.children.append(correction_node)
-        node.camera = None
 
     export_user_extensions('gather_node_hook', export_settings, node, blender_object)
 
-    return node
-
+    vnode.node = node
 
-def __filter_node(blender_object, blender_scene, export_settings):
-    if blender_object.users == 0:
-        return False
-    if blender_scene is not None:
-        instanced =  any([blender_object.name in layer.objects for layer in blender_scene.view_layers])
-        if instanced is False:
-            # Check if object is from a linked collection
-            if any([blender_object.name in coll.objects for coll in bpy.data.collections if coll.library is not None]):
-                pass
-            else:
-                # Not instanced, not linked -> We don't keep this object
-                return False
-    if export_settings[gltf2_blender_export_keys.SELECTED] and blender_object.select_get() is False:
-        return False
+    if node.skin is not None:
+        vnode.skin = skin
 
-    if export_settings[gltf2_blender_export_keys.VISIBLE] and blender_object.visible_get() is False:
-        return False
-
-    # render_get() doesn't exist, so unfortunately this won't take into account the Collection settings
-    if export_settings[gltf2_blender_export_keys.RENDERABLE] and blender_object.hide_render is True:
-        return False
-
-    if export_settings[gltf2_blender_export_keys.ACTIVE_COLLECTION]:
-        found = any(x == blender_object for x in bpy.context.collection.all_objects)
-
-        if not found:
-            return False
-
-    if blender_object.type == 'LIGHT':
-        return export_settings[gltf2_blender_export_keys.LIGHTS]
-
-    if blender_object.type == 'CAMERA':
-        return export_settings[gltf2_blender_export_keys.CAMERAS]
-
-    return True
+    return node
 
 
 def __gather_camera(blender_object, export_settings):
@@ -151,54 +63,35 @@ def __gather_camera(blender_object, export_settings):
     return gltf2_blender_gather_cameras.gather_camera(blender_object.data, export_settings)
 
 
-def __gather_children(blender_object, blender_scene, export_settings):
+def __gather_children(vnode, blender_object, export_settings):
     children = []
-    only_bone_children = True # True by default, will be set to False if needed
-    # standard children
-    for child_object in blender_object.children:
-        if child_object.parent_bone:
-            # this is handled further down,
-            # as the object should be a child of the specific bone,
-            # not the Armature object
-            continue
-
-        node = gather_node(child_object,
-            child_object.library.name if child_object.library else None,
-            blender_scene, None, export_settings)
+
+    vtree = export_settings['vtree']
+
+    # Standard Children / Collection
+    for c in [vtree.nodes[c] for c in vnode.children if vtree.nodes[c].blender_type != gltf2_blender_gather_tree.VExportNode.BONE]:
+        node = gather_node(c, export_settings)
         if node is not None:
             children.append(node)
-            only_bone_children = False
-    # blender dupli objects
-    if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
-        for dupli_object in blender_object.instance_collection.objects:
-            if dupli_object.parent is not None:
-                continue
-            if dupli_object.type == "ARMATURE":
-                continue # There is probably a proxy (no more existing)
-            node = gather_node(dupli_object,
-                dupli_object.library.name if dupli_object.library else None,
-                blender_scene, blender_object.name, export_settings)
-            if node is not None:
-                children.append(node)
-                only_bone_children = False
-
-    # blender bones
-    if blender_object.type == "ARMATURE":
+
+
+    # Armature --> Retrieve Blender bones
+    if vnode.blender_type == gltf2_blender_gather_tree.VExportNode.ARMATURE:
         root_joints = []
-        if export_settings["gltf_def_bones"] is False:
-            bones = blender_object.pose.bones
-        else:
-            bones, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object)
-            bones = [blender_object.pose.bones[b.name] for b in bones]
-        for blender_bone in bones:
-            if not blender_bone.parent:
-                joint = gltf2_blender_gather_joints.gather_joint(blender_object, blender_bone, export_settings)
-                children.append(joint)
-                root_joints.append(joint)
-        # handle objects directly parented to bones
-        direct_bone_children = [child for child in blender_object.children if child.parent_bone]
-        if len(direct_bone_children) != 0:
-            only_bone_children = False
+
+        all_armature_children = vnode.children
+        root_bones_uuid = [c for c in all_armature_children if export_settings['vtree'].nodes[c].blender_type == VExportNode.BONE]
+        for bone_uuid in root_bones_uuid:
+            joint = gltf2_blender_gather_joints.gather_joint_vnode(bone_uuid, export_settings)
+            children.append(joint)
+            root_joints.append(joint)
+
+        # Object parented to bones
+        direct_bone_children = []
+        for n in [vtree.nodes[i] for i in vtree.get_all_bones(vnode.uuid)]:
+            direct_bone_children.extend([c for c in n.children if vtree.nodes[c].blender_type != gltf2_blender_gather_tree.VExportNode.BONE])
+
+
         def find_parent_joint(joints, name):
             for joint in joints:
                 if joint.name == name:
@@ -207,44 +100,40 @@ def __gather_children(blender_object, blender_scene, export_settings):
                 if parent_joint:
                     return parent_joint
             return None
-        for child in direct_bone_children:
+
+        for child in direct_bone_children: # List of object that are parented to bones
             # find parent joint
-            parent_joint = find_parent_joint(root_joints, child.parent_bone)
+            parent_joint = find_parent_joint(root_joints, vtree.nodes[child].blender_object.parent_bone)
             if not parent_joint:
                 continue
-            child_node = gather_node(child, None, blender_scene, None, export_settings)
+            child_node = gather_node(vtree.nodes[child], export_settings)
             if child_node is None:
                 continue
             blender_bone = blender_object.pose.bones[parent_joint.name]
-            # fix rotation
-            if export_settings[gltf2_blender_export_keys.YUP]:
-                rot = child_node.rotation
-                if rot is None:
-                    rot = [0, 0, 0, 1]
-
-                rot_quat = Quaternion(rot)
-                axis_basis_change = Matrix(
-                    ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, -1.0, 0.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
-                mat = child.matrix_parent_inverse @ child.matrix_basis
-                mat = mat @ axis_basis_change
-
-                _, rot_quat, _ = mat.decompose()
-                child_node.rotation = [rot_quat[1], rot_quat[2], rot_quat[3], rot_quat[0]]
-
-            # fix translation (in blender bone's tail is the origin for children)
-            trans, _, _ = child.matrix_local.decompose()
-            if trans is None:
-                trans = [0, 0, 0]
-            # bones go down their local y axis
-            if blender_bone.matrix.to_scale()[1] >= 1e-6:
-                bone_tail = [0, blender_bone.length / blender_bone.matrix.to_scale()[1], 0]
-            else:
-                bone_tail = [0,0,0] # If scale is 0, tail == head
-            child_node.translation = [trans[idx] + bone_tail[idx] for idx in range(3)]
+
+            mat = vtree.nodes[vtree.nodes[child].parent_bone_uuid].matrix_world.inverted_safe() @ vtree.nodes[child].matrix_world
+            loc, rot_quat, scale = mat.decompose()
+
+            trans = __convert_swizzle_location(loc, export_settings)
+            rot = __convert_swizzle_rotation(rot_quat, export_settings)
+            sca = __convert_swizzle_scale(scale, export_settings)
+
+
+            translation, rotation, scale = (None, None, None)
+            if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
+                translation = [trans[0], trans[1], trans[2]]
+            if rot[0] != 1.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 0.0:
+                rotation = [rot[1], rot[2], rot[3], rot[0]]
+            if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0:
+                scale = [sca[0], sca[1], sca[2]]
+
+            child_node.translation = translation
+            child_node.rotation = rotation
+            child_node.scale = scale
 
             parent_joint.children.append(child_node)
 
-    return children, only_bone_children
+    return children
 
 
 def __gather_extensions(blender_object, export_settings):
@@ -283,13 +172,17 @@ def __gather_matrix(blender_object, export_settings):
     return []
 
 
-def __gather_mesh(blender_object, library, export_settings):
+def __gather_mesh(vnode, blender_object, export_settings):
     if blender_object.type in ['CURVE', 'SURFACE', 'FONT']:
-        return __gather_mesh_from_nonmesh(blender_object, library, export_settings)
+        return __gather_mesh_from_nonmesh(blender_object, export_settings)
 
     if blender_object.type != "MESH":
         return None
 
+    # For duplis instancer, when show is off -> export as empty
+    if vnode.force_as_empty is True:
+        return None
+
     # Be sure that object is valid (no NaN for example)
     blender_object.data.validate()
 
@@ -301,6 +194,8 @@ def __gather_mesh(blender_object, library, export_settings):
     if len(modifiers) == 0:
         modifiers = None
 
+    # TODO for objects without any modifiers, we can keep original mesh_data
+    # It will instance mesh in glTF
     if export_settings[gltf2_blender_export_keys.APPLY]:
         armature_modifiers = {}
         if export_settings[gltf2_blender_export_keys.SKINS]:
@@ -335,24 +230,23 @@ def __gather_mesh(blender_object, library, export_settings):
                 modifiers = None
 
     materials = tuple(ms.material for ms in blender_object.material_slots)
-    material_names = tuple(None if mat is None else mat.name for mat in materials)
 
     # retrieve armature
     # Because mesh data will be transforms to skeleton space,
     # we can't instantiate multiple object at different location, skined by same armature
-    blender_object_for_skined_data = None
+    uuid_for_skined_data = None
     if export_settings[gltf2_blender_export_keys.SKINS]:
         for idx, modifier in enumerate(blender_object.modifiers):
             if modifier.type == 'ARMATURE':
-                blender_object_for_skined_data = blender_object
+                uuid_for_skined_data = vnode.uuid
 
     result = gltf2_blender_gather_mesh.gather_mesh(blender_mesh,
-                                                   library,
-                                                   blender_object_for_skined_data,
+                                                   uuid_for_skined_data,
                                                    vertex_groups,
                                                    modifiers,
                                                    skip_filter,
-                                                   material_names,
+                                                   materials,
+                                                   None,
                                                    export_settings)
 
     if export_settings[gltf2_blender_export_keys.APPLY]:
@@ -361,7 +255,7 @@ def __gather_mesh(blender_object, library, export_settings):
     return result
 
 
-def __gather_mesh_from_nonmesh(blender_object, library, export_settings):
+def __gather_mesh_from_nonmesh(blender_object, export_settings):
     """Handles curves, surfaces, text, etc."""
     needs_to_mesh_clear = False
     try:
@@ -387,18 +281,18 @@ def __gather_mesh_from_nonmesh(blender_object, library, export_settings):
         needs_to_mesh_clear = True
 
         skip_filter = True
-        material_names = tuple([ms.material.name for ms in blender_object.material_slots if ms.material is not None])
+        materials = tuple([ms.material for ms in blender_object.material_slots if ms.material is not None])
         vertex_groups = None
         modifiers = None
         blender_object_for_skined_data = None
 
         result = gltf2_blender_gather_mesh.gather_mesh(blender_mesh,
-                                                       library,
                                                        blender_object_for_skined_data,
                                                        vertex_groups,
                                                        modifiers,
                                                        skip_filter,
-                                                       material_names,
+                                                       materials,
+                                                       blender_object.data,
                                                        export_settings)
 
     finally:
@@ -411,33 +305,15 @@ def __gather_mesh_from_nonmesh(blender_object, library, export_settings):
 def __gather_name(blender_object, export_settings):
     return blender_object.name
 
-
-def __gather_trans_rot_scale(blender_object, export_settings):
-    if blender_object.matrix_parent_inverse == Matrix.Identity(4):
-        trans = blender_object.location
-
-        if blender_object.rotation_mode in ['QUATERNION', 'AXIS_ANGLE']:
-            rot = blender_object.rotation_quaternion
-        else:
-            rot = blender_object.rotation_euler.to_quaternion()
-
-        sca = blender_object.scale
+def __gather_trans_rot_scale(vnode, export_settings):
+    if vnode.parent_uuid is None:
+        # No parent, so matrix is world matrix
+        trans, rot, sca = vnode.matrix_world.decompose()
     else:
-        # matrix_local = matrix_parent_inverse*location*rotation*scale
-        # Decomposing matrix_local gives less accuracy, but is needed if matrix_parent_inverse is not the identity.
+        # calculate local matrix
+        trans, rot, sca = (export_settings['vtree'].nodes[vnode.parent_uuid].matrix_world.inverted_safe() @ vnode.matrix_world).decompose()
 
 
-        if blender_object.matrix_local[3][3] != 0.0:
-            trans, rot, sca = blender_object.matrix_local.decompose()
-        else:
-            # Some really weird cases, scale is null (if parent is null when evaluation is done)
-            print_console('WARNING', 'Some nodes are 0 scaled during evaluation. Result can be wrong')
-            trans = blender_object.location
-            if blender_object.rotation_mode in ['QUATERNION', 'AXIS_ANGLE']:
-                rot = blender_object.rotation_quaternion
-            else:
-                rot = blender_object.rotation_euler.to_quaternion()
-            sca = blender_object.scale
 
     # make sure the rotation is normalized
     rot.normalize()
@@ -446,9 +322,9 @@ def __gather_trans_rot_scale(blender_object, export_settings):
     rot = __convert_swizzle_rotation(rot, export_settings)
     sca = __convert_swizzle_scale(sca, export_settings)
 
-    if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
+    if vnode.blender_object.instance_type == 'COLLECTION' and vnode.blender_object.instance_collection:
         offset = -__convert_swizzle_location(
-            blender_object.instance_collection.instance_offset, export_settings)
+            vnode.blender_object.instance_collection.instance_offset, export_settings)
 
         s = Matrix.Diagonal(sca).to_4x4()
         r = rot.to_matrix().to_4x4()
@@ -473,8 +349,7 @@ def __gather_trans_rot_scale(blender_object, export_settings):
         scale = [sca[0], sca[1], sca[2]]
     return translation, rotation, scale
 
-
-def __gather_skin(blender_object, export_settings):
+def __gather_skin(vnode, blender_object, export_settings):
     modifiers = {m.type: m for m in blender_object.modifiers}
     if "ARMATURE" not in modifiers or modifiers["ARMATURE"].object is None:
         return None
@@ -501,34 +376,12 @@ def __gather_skin(blender_object, export_settings):
         return None
 
     # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures
-    return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, export_settings)
+    return gltf2_blender_gather_skins.gather_skin(vnode.armature, export_settings)
 
 
 def __gather_weights(blender_object, export_settings):
     return None
 
-
-def __get_correction_node(blender_object, export_settings):
-    correction_quaternion = __convert_swizzle_rotation(
-        Quaternion((1.0, 0.0, 0.0), math.radians(-90.0)), export_settings)
-    correction_quaternion = [correction_quaternion[1], correction_quaternion[2],
-                             correction_quaternion[3], correction_quaternion[0]]
-    return gltf2_io.Node(
-        camera=None,
-        children=[],
-        extensions=None,
-        extras=None,
-        matrix=None,
-        mesh=None,
-        name=blender_object.name + '_Orientation',
-        rotation=correction_quaternion,
-        scale=None,
-        skin=None,
-        translation=None,
-        weights=None
-    )
-
-
 def __convert_swizzle_location(loc, export_settings):
     """Convert a location from Blender coordinate system to glTF coordinate system."""
     if export_settings[gltf2_blender_export_keys.YUP]:
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 82ff7f6614c4c137c780064b51c2422b5a7bd3a9..9e5ce648d399c22858f267eefca432560bb6fd07 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
@@ -7,7 +7,7 @@ import numpy as np
 
 from .gltf2_blender_export_keys import NORMALS, MORPH_NORMAL, TANGENTS, MORPH_TANGENT, MORPH
 
-from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, cached_by_key
 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
@@ -20,13 +20,34 @@ from io_scene_gltf2.io.com.gltf2_io_debug import print_console
 
 
 @cached
+def get_primitive_cache_key(
+        blender_mesh,
+        blender_object,
+        vertex_groups,
+        modifiers,
+        materials,
+        export_settings):
+
+    # Use id of mesh
+    # Do not use bpy.types that can be unhashable
+    # Do not use mesh name, that can be not unique (when linked)
+
+    # TODO check what is really needed for modifiers
+
+    return (
+        (id(blender_mesh),),
+        (modifiers,),
+        tuple(id(m) if m is not None else None for m in materials)
+    )
+
+
+@cached_by_key(key=get_primitive_cache_key)
 def gather_primitives(
         blender_mesh: bpy.types.Mesh,
-        library: Optional[str],
-        blender_object: Optional[bpy.types.Object],
+        uuid_for_skined_data,
         vertex_groups: Optional[bpy.types.VertexGroups],
         modifiers: Optional[bpy.types.ObjectModifiers],
-        material_names: Tuple[str],
+        materials: Tuple[bpy.types.Material],
         export_settings
         ) -> List[gltf2_io.MeshPrimitive]:
     """
@@ -36,7 +57,7 @@ def gather_primitives(
     """
     primitives = []
 
-    blender_primitives = __gather_cache_primitives(blender_mesh, library, blender_object,
+    blender_primitives = __gather_cache_primitives(blender_mesh, uuid_for_skined_data,
         vertex_groups, modifiers, export_settings)
 
     for internal_primitive in blender_primitives:
@@ -45,14 +66,13 @@ def gather_primitives(
 
         if export_settings['gltf_materials'] == "EXPORT" and material_idx is not None:
             blender_material = None
-            if material_names:
-                i = material_idx if material_idx < len(material_names) else -1
-                material_name = material_names[i]
-                if material_name is not None:
-                    blender_material = bpy.data.materials[material_name]
-            if blender_material is not None:
+            mat = None
+            if materials:
+                i = material_idx if material_idx < len(materials) else -1
+                mat = materials[i]
+            if mat is not None:
                 material = gltf2_blender_gather_materials.gather_material(
-                    blender_material,
+                    mat,
                     export_settings,
                 )
 
@@ -72,8 +92,7 @@ def gather_primitives(
 @cached
 def __gather_cache_primitives(
         blender_mesh: bpy.types.Mesh,
-        library: Optional[str],
-        blender_object: Optional[bpy.types.Object],
+        uuid_for_skined_data,
         vertex_groups: Optional[bpy.types.VertexGroups],
         modifiers: Optional[bpy.types.ObjectModifiers],
         export_settings
@@ -84,7 +103,7 @@ def __gather_cache_primitives(
     primitives = []
 
     blender_primitives = gltf2_blender_extract.extract_primitives(
-        None, blender_mesh, library, blender_object, vertex_groups, modifiers, export_settings)
+        blender_mesh, uuid_for_skined_data, vertex_groups, modifiers, export_settings)
 
     for internal_primitive in blender_primitives:
         primitive = {
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
index e5534c5a4ade11e84b61378bc3bad452ccf836f7..136d654d0084b0cc2d9d35b017b0c579370fd741 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
@@ -10,10 +10,12 @@ from io_scene_gltf2.io.com import gltf2_io_constants
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_tree
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode
 
 
 @cached
-def gather_skin(blender_object, export_settings):
+def gather_skin(armature_uuid, export_settings):
     """
     Gather armatures, bones etc into a glTF2 skin object.
 
@@ -21,78 +23,70 @@ def gather_skin(blender_object, export_settings):
     :param export_settings:
     :return: a glTF2 skin object
     """
-    if not __filter_skin(blender_object, export_settings):
+
+    blender_armature_object = export_settings['vtree'].nodes[armature_uuid].blender_object
+
+    if not __filter_skin(blender_armature_object, export_settings):
         return None
 
     skin = gltf2_io.Skin(
-        extensions=__gather_extensions(blender_object, export_settings),
-        extras=__gather_extras(blender_object, export_settings),
-        inverse_bind_matrices=__gather_inverse_bind_matrices(blender_object, export_settings),
-        joints=__gather_joints(blender_object, export_settings),
-        name=__gather_name(blender_object, export_settings),
-        skeleton=__gather_skeleton(blender_object, export_settings)
+        extensions=__gather_extensions(blender_armature_object, export_settings),
+        extras=__gather_extras(blender_armature_object, export_settings),
+        inverse_bind_matrices=__gather_inverse_bind_matrices(armature_uuid, export_settings),
+        joints=__gather_joints(armature_uuid, export_settings),
+        name=__gather_name(blender_armature_object, export_settings),
+        skeleton=__gather_skeleton(blender_armature_object, export_settings)
     )
 
-    export_user_extensions('gather_skin_hook', export_settings, skin, blender_object)
+    # If armature is not exported, joints will be empty.
+    # Do not construct skin in that case
+    if len(skin.joints) == 0:
+        return None
+
+    export_user_extensions('gather_skin_hook', export_settings, skin, blender_armature_object)
 
     return skin
 
 
-def __filter_skin(blender_object, export_settings):
+def __filter_skin(blender_armature_object, export_settings):
     if not export_settings[gltf2_blender_export_keys.SKINS]:
         return False
-    if blender_object.type != 'ARMATURE' or len(blender_object.pose.bones) == 0:
+    if blender_armature_object.type != 'ARMATURE' or len(blender_armature_object.pose.bones) == 0:
         return False
 
     return True
 
 
-def __gather_extensions(blender_object, export_settings):
+def __gather_extensions(blender_armature_object, export_settings):
     return None
 
 
-def __gather_extras(blender_object, export_settings):
+def __gather_extras(blender_armature_object, export_settings):
     return None
 
-def __gather_inverse_bind_matrices(blender_object, export_settings):
+def __gather_inverse_bind_matrices(armature_uuid, export_settings):
+
+    blender_armature_object = export_settings['vtree'].nodes[armature_uuid].blender_object
+
     axis_basis_change = mathutils.Matrix.Identity(4)
     if export_settings[gltf2_blender_export_keys.YUP]:
         axis_basis_change = mathutils.Matrix(
             ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
 
-    if export_settings['gltf_def_bones'] is False:
-        # build the hierarchy of nodes out of the bones
-        root_bones = []
-        for blender_bone in blender_object.pose.bones:
-            if not blender_bone.parent:
-                root_bones.append(blender_bone)
-    else:
-        _, children_, root_bones = get_bone_tree(None, blender_object)
-
-    matrices = []
-
-    # traverse the matrices in the same order as the joints and compute the inverse bind matrix
+    bones_uuid = export_settings['vtree'].get_all_bones(armature_uuid)
     def __collect_matrices(bone):
         inverse_bind_matrix = (
             axis_basis_change @
             (
-                blender_object.matrix_world @
+                blender_armature_object.matrix_world @
                 bone.bone.matrix_local
             )
         ).inverted_safe()
         matrices.append(inverse_bind_matrix)
 
-        if export_settings['gltf_def_bones'] is False:
-            for child in bone.children:
-                __collect_matrices(child)
-        else:
-            if bone.name in children_.keys():
-                for child in children_[bone.name]:
-                    __collect_matrices(blender_object.pose.bones[child])
-
-    # start with the "root" bones and recurse into children, in the same ordering as the how joints are gathered
-    for root_bone in root_bones:
-        __collect_matrices(root_bone)
+    matrices = []
+    for b in bones_uuid:
+        __collect_matrices(blender_armature_object.pose.bones[export_settings['vtree'].nodes[b].blender_bone.name])
 
     # flatten the matrices
     inverse_matrices = []
@@ -113,67 +107,26 @@ def __gather_inverse_bind_matrices(blender_object, export_settings):
     )
 
 
-def __gather_joints(blender_object, export_settings):
-    root_joints = []
-    if export_settings['gltf_def_bones'] is False:
-        # build the hierarchy of nodes out of the bones
-        for blender_bone in blender_object.pose.bones:
-            if not blender_bone.parent:
-                root_joints.append(gltf2_blender_gather_joints.gather_joint(blender_object, blender_bone, export_settings))
-    else:
-        _, children_, root_joints = get_bone_tree(None, blender_object)
-        root_joints = [gltf2_blender_gather_joints.gather_joint(blender_object, i, export_settings) for i in root_joints]
-
-    # joints is a flat list containing all nodes belonging to the skin
-    joints = []
-
-    def __collect_joints(node):
-        joints.append(node)
-        if export_settings['gltf_def_bones'] is False:
-            for child in node.children:
-                __collect_joints(child)
-        else:
-            if node.name in children_.keys():
-                for child in children_[node.name]:
-                    __collect_joints(gltf2_blender_gather_joints.gather_joint(blender_object, blender_object.pose.bones[child], export_settings))
-
-    for joint in root_joints:
-        __collect_joints(joint)
+def __gather_joints(armature_uuid, export_settings):
+
+    blender_armature_object = export_settings['vtree'].nodes[armature_uuid].blender_object
+
+    all_armature_children = export_settings['vtree'].nodes[armature_uuid].children
+    root_bones_uuid = [c for c in all_armature_children if export_settings['vtree'].nodes[c].blender_type == VExportNode.BONE]
 
+    # Create bone nodes
+    for root_bone_uuid in root_bones_uuid:
+        gltf2_blender_gather_joints.gather_joint_vnode(root_bone_uuid, export_settings)
+
+    bones_uuid = export_settings['vtree'].get_all_bones(armature_uuid)
+    joints = [export_settings['vtree'].nodes[b].node for b in bones_uuid]
     return joints
 
 
-def __gather_name(blender_object, export_settings):
-    return blender_object.name
+def __gather_name(blender_armature_object, export_settings):
+    return blender_armature_object.name
 
 
-def __gather_skeleton(blender_object, export_settings):
+def __gather_skeleton(blender_armature_object, export_settings):
     # In the future support the result of https://github.com/KhronosGroup/glTF/pull/1195
-    return None  # gltf2_blender_gather_nodes.gather_node(blender_object, blender_scene, export_settings)
-
-@cached
-def get_bone_tree(blender_dummy, blender_object):
-
-    bones = []
-    children = {}
-    root_bones = []
-
-    def get_parent(bone):
-        bones.append(bone.name)
-        if bone.parent is not None:
-            if bone.parent.name not in children.keys():
-                children[bone.parent.name] = []
-            children[bone.parent.name].append(bone.name)
-            get_parent(bone.parent)
-        else:
-            root_bones.append(bone.name)
-
-    for bone in [b for b in blender_object.data.bones if b.use_deform is True]:
-        get_parent(bone)
-
-    # remove duplicates
-    for k, v in children.items():
-        children[k] = list(set(v))
-    list_ = list(set(bones))
-    root_ = list(set(root_bones))
-    return [blender_object.data.bones[b] for b in list_], children, [blender_object.pose.bones[b] for b in root_]
+    return None
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a5ce2be32743027b956acae7177639d1af07bf0
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
@@ -0,0 +1,375 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 The glTF-Blender-IO authors.
+
+import bpy
+import uuid
+
+from . import gltf2_blender_export_keys
+from mathutils import Quaternion, Matrix
+
+class VExportNode:
+
+    OBJECT = 1
+    ARMATURE = 2
+    BONE = 3
+    LIGHT = 4
+    CAMERA = 5
+    COLLECTION = 6
+
+    # Parent type, to be set on child regarding its parent
+    NO_PARENT = 54
+    PARENT_OBJECT = 50
+    PARENT_BONE = 51
+    PARENT_BONE_RELATIVE = 52
+    PARENT_ROOT_BONE = 53
+    PARENT_BONE_BONE = 55
+
+
+    def __init__(self):
+        self.children = []
+        self.blender_type = None
+        self.world_matrix = None
+        self.parent_type = None
+
+        self.blender_object = None
+        self.blender_bone = None
+
+        self.force_as_empty = False # Used for instancer display
+
+        # Only for bone/bone and object parented to bone
+        self.parent_bone_uuid = None
+
+        # Only for bones
+        self.use_deform = None
+
+        # Only for armature
+        self.bones = {}
+
+        # For deformed object
+        self.armature = None # for deformed object and for bone
+        self.skin = None
+
+        # glTF
+        self.node = None
+
+    def add_child(self, uuid):
+        self.children.append(uuid)
+
+    def set_world_matrix(self, matrix):
+        self.world_matrix = matrix
+
+    def set_blender_data(self, blender_object, blender_bone):
+        self.blender_object = blender_object
+        self.blender_bone = blender_bone
+
+    def recursive_display(self, tree, mode):
+        if mode == "simple":
+            for c in self.children:
+                print(self.blender_object.name, "/", self.blender_bone.name if self.blender_bone else "", "-->", tree.nodes[c].blender_object.name, "/", tree.nodes[c].blender_bone.name if tree.nodes[c].blender_bone else "" )
+                tree.nodes[c].recursive_display(tree, mode)
+
+class VExportTree:
+    def __init__(self, export_settings):
+        self.nodes = {}
+        self.roots = []
+
+        self.export_settings = export_settings
+
+        self.tree_troncated = False
+
+    def add_node(self, node):
+        self.nodes[node.uuid] = node
+
+    def add_children(self, uuid_parent, uuid_child):
+        self.nodes[uuid_parent].add_child(uuid_child)
+
+    def construct(self, blender_scene):
+        bpy.context.window.scene = blender_scene
+        depsgraph = bpy.context.evaluated_depsgraph_get()
+
+        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))
+
+    def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, parent_coll_matrix_world, armature_uuid=None, dupli_world_matrix=None):
+        node = VExportNode()
+        node.uuid = str(uuid.uuid4())
+        node.parent_uuid = parent_uuid
+        node.set_blender_data(blender_object, blender_bone)
+
+        # add to parent if needed
+        if parent_uuid is not None:
+            self.add_children(parent_uuid, node.uuid)
+        else:
+            self.roots.append(node.uuid)
+
+        # Set blender type
+        if blender_bone is not None:
+            node.blender_type = VExportNode.BONE
+            self.nodes[armature_uuid].bones[blender_bone.name] = node.uuid
+            node.use_deform = blender_bone.id_data.data.bones[blender_bone.name].use_deform
+        elif blender_object.type == "ARMATURE":
+            node.blender_type = VExportNode.ARMATURE
+        elif blender_object.type == "CAMERA":
+            node.blender_type = VExportNode.CAMERA
+        elif blender_object.type == "LIGHT":
+            node.blender_type = VExportNode.LIGHT
+        elif blender_object.instance_type == "COLLECTION":
+            node.blender_type = VExportNode.COLLECTION
+        else:
+            node.blender_type = VExportNode.OBJECT
+
+        # For meshes with armature modifier (parent is armature), keep armature uuid
+        if node.blender_type == VExportNode.OBJECT:
+            modifiers = {m.type: m for m in blender_object.modifiers}
+            if "ARMATURE" in modifiers and modifiers["ARMATURE"].object is not None:
+                if parent_uuid is None or not self.nodes[parent_uuid].blender_type == VExportNode.ARMATURE:
+                    # correct workflow is to parent skinned mesh to armature, but ...
+                    # all users don't use correct workflow
+                    print("WARNING: Armature must be the parent of skinned mesh")
+                    print("Armature is selected by its name, but may be false in case of instances")
+                    # Search an armature by name, and use the first found
+                    # This will be done after all objects are setup
+                    node.armature_needed = modifiers["ARMATURE"].object.name
+                else:
+                    node.armature = parent_uuid
+
+        # For bones, store uuid of armature
+        if blender_bone is not None:
+            node.armature = armature_uuid
+
+        # for bone/bone parenting, store parent, this will help armature tree management
+        if parent_uuid is not None and self.nodes[parent_uuid].blender_type == VExportNode.BONE and node.blender_type == VExportNode.BONE:
+            node.parent_bone_uuid = parent_uuid
+
+
+        # Objects parented to bone
+        if parent_uuid is not None and self.nodes[parent_uuid].blender_type == VExportNode.BONE and node.blender_type != VExportNode.BONE:
+            node.parent_bone_uuid = parent_uuid
+
+        # World Matrix
+        # Store World Matrix for objects
+        if dupli_world_matrix is not None:
+            node.matrix_world = dupli_world_matrix
+        elif node.blender_type in [VExportNode.OBJECT, VExportNode.COLLECTION, VExportNode.ARMATURE, VExportNode.CAMERA, VExportNode.LIGHT]:
+            # Matrix World of object is expressed based on collection instance objects are
+            # 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))
+                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))
+                node.matrix_world @= correction.to_matrix().to_4x4()
+        elif node.blender_type == VExportNode.BONE:
+            node.matrix_world = self.nodes[node.armature].matrix_world @ blender_bone.matrix
+            axis_basis_change = Matrix(
+                ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
+            node.matrix_world = node.matrix_world @ axis_basis_change
+
+        # Force empty ?
+        # For duplis, if instancer is not display, we should create an empty
+        if blender_object.is_instancer is True and blender_object.show_instancer_for_render is False:
+            node.force_as_empty = True
+
+        # Storing this node
+        self.add_node(node)
+
+        ###### Manage children ######
+
+        # standard children
+        if blender_bone is None and blender_object.is_instancer is False:
+            for child_object in blender_object.children:
+                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)
+
+        # Collections
+        if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
+            for dupli_object in blender_object.instance_collection.objects:
+                if dupli_object.parent is not None:
+                    continue
+                self.recursive_node_traverse(dupli_object, None, node.uuid, node.matrix_world)
+
+        # 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)
+
+        # 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)
+
+        # 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)
+
+        # 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)
+
+    def get_all_objects(self):
+        return [n.uuid for n in self.nodes.values() if n.blender_type != VExportNode.BONE]
+
+    def get_all_bones(self, uuid): #For armatue Only
+        if self.nodes[uuid].blender_type == VExportNode.ARMATURE:
+            def recursive_get_all_bones(uuid):
+                total = []
+                if self.nodes[uuid].blender_type == VExportNode.BONE:
+                    total.append(uuid)
+                    for child_uuid in self.nodes[uuid].children:
+                        total.extend(recursive_get_all_bones(child_uuid))
+
+                return total
+
+            tot = []
+            for c_uuid in self.nodes[uuid].children:
+                tot.extend(recursive_get_all_bones(c_uuid))
+            return tot
+        else:
+            return []
+
+    def display(self, mode):
+        if mode == "simple":
+            for n in self.roots:
+                print("Root", self.nodes[n].blender_object.name, "/", self.nodes[n].blender_bone.name if self.nodes[n].blender_bone else "" )
+                self.nodes[n].recursive_display(self, mode)
+
+
+    def filter_tag(self):
+        roots = self.roots.copy()
+        for r in roots:
+            self.recursive_filter_tag(r, None)
+
+    def filter_perform(self):
+        roots = self.roots.copy()
+        for r in roots:
+            self.recursive_filter(r, None) # Root, so no parent
+
+    def filter(self):
+        self.filter_tag()
+        self.filter_perform()
+
+
+    def recursive_filter_tag(self, uuid, parent_keep_tag):
+        # parent_keep_tag is for collection instance
+        # some properties (selection, visibility, renderability)
+        # are defined at collection level, and we need to use these values
+        # for all objects of the collection instance.
+        # But some properties (camera, lamp ...) are not defined at collection level
+        if parent_keep_tag is None:
+            self.nodes[uuid].keep_tag = self.node_filter_not_inheritable_is_kept(uuid) and self.node_filter_inheritable_is_kept(uuid)
+        elif parent_keep_tag is True:
+            self.nodes[uuid].keep_tag = self.node_filter_not_inheritable_is_kept(uuid)
+        elif parent_keep_tag is False:
+            self.nodes[uuid].keep_tag = False
+        else:
+            print("This should not happen!")
+
+        for child in self.nodes[uuid].children:
+            if self.nodes[uuid].blender_type == VExportNode.COLLECTION:
+                self.recursive_filter_tag(child, self.nodes[uuid].keep_tag)
+            else:
+                self.recursive_filter_tag(child, parent_keep_tag)
+
+    def recursive_filter(self, uuid, parent_kept_uuid):
+        children = self.nodes[uuid].children.copy()
+
+        new_parent_kept_uuid = None
+        if self.nodes[uuid].keep_tag is False:
+            new_parent_kept_uuid = parent_kept_uuid
+            # Need to modify tree
+            if self.nodes[uuid].parent_uuid is not None:
+                self.nodes[self.nodes[uuid].parent_uuid].children.remove(uuid)
+            else:
+                # Remove from root
+                self.roots.remove(uuid)
+        else:
+            new_parent_kept_uuid = uuid
+
+            # If parent_uuid is not parent_kept_uuid, we need to modify children list of parent_kept_uuid
+            if parent_kept_uuid != self.nodes[uuid].parent_uuid and parent_kept_uuid is not None:
+                self.tree_troncated = True
+                self.nodes[parent_kept_uuid].children.append(uuid)
+
+            # If parent_kept_uuid is None, and parent_uuid was not, add to root list
+            if self.nodes[uuid].parent_uuid is not None and parent_kept_uuid is None:
+                self.tree_troncated = True
+                self.roots.append(uuid)
+
+            # Modify parent uuid
+            self.nodes[uuid].parent_uuid = parent_kept_uuid
+
+        for child in children:
+            self.recursive_filter(child, new_parent_kept_uuid)
+
+
+    def node_filter_not_inheritable_is_kept(self, uuid):
+        # Export Camera or not
+        if self.nodes[uuid].blender_type == VExportNode.CAMERA:
+            if self.export_settings[gltf2_blender_export_keys.CAMERAS] is False:
+                return False
+
+        # Export Lamp or not
+        if self.nodes[uuid].blender_type == VExportNode.LIGHT:
+            if self.export_settings[gltf2_blender_export_keys.LIGHTS] is False:
+                return False
+
+        # Export deform bones only
+        if self.nodes[uuid].blender_type == VExportNode.BONE:
+            if self.export_settings['gltf_def_bones'] is True and self.nodes[uuid].use_deform is False:
+                # Check if bone has some objected parented to bone. We need to keep it in that case, even if this is not a def bone
+                if len([c for c in self.nodes[uuid].children if self.nodes[c].blender_type != VExportNode.BONE]) != 0:
+                    return True
+                return False
+
+        return True
+
+    def node_filter_inheritable_is_kept(self, uuid):
+
+        if self.export_settings[gltf2_blender_export_keys.SELECTED] and self.nodes[uuid].blender_object.select_get() is False:
+            return False
+
+        if self.export_settings[gltf2_blender_export_keys.VISIBLE]:
+            # The eye in outliner (object)
+            if self.nodes[uuid].blender_object.visible_get() is False:
+                return False
+
+            # The screen in outliner (object)
+            if self.nodes[uuid].blender_object.hide_viewport is True:
+                return False
+
+            # The screen in outliner (collections)
+            if all([c.hide_viewport for c in self.nodes[uuid].blender_object.users_collection]):
+                return False
+
+        # The camera in outliner (object)
+        if self.export_settings[gltf2_blender_export_keys.RENDERABLE]:
+            if self.nodes[uuid].blender_object.hide_render is True:
+                return False
+
+            # The camera in outliner (collections)
+            if all([c.hide_render for c in self.nodes[uuid].blender_object.users_collection]):
+                return False
+
+        if self.export_settings[gltf2_blender_export_keys.ACTIVE_COLLECTION]:
+            found = any(x == self.nodes[uuid].blender_object for x in bpy.context.collection.all_objects)
+            if not found:
+                return False
+
+        return True
+    
+    def search_missing_armature(self):
+        for n in [n for n in self.nodes.values() if hasattr(n, "armature_needed") is True]:
+            candidates = [i for i in self.nodes.values() if i.blender_type == VExportNode.ARMATURE and i.blender_object.name == n.armature_needed]
+            if len(candidates) > 0:
+                n.armature = candidates[0].uuid
+            del n.armature_needed
+            
\ No newline at end of file