Repository: khronosgroup/gltf-blender-io
Issue: Shape Keys applied to wrong target in Blender 2.8

Exporting a GLB out of Blender applies the blend shape attributes to the wrong morph target. 

In my example, the "Glee Eyes" target is being applied to the "Super Open" target. We have tried removing the "Super Open" target completely and re exporting and it just gets applied elsewhere.

Blend and GLB file: https://drive.google.com/drive/folders/14YZx1q0INs-SvKSFIdRv8dy_2QTbapA0?usp=sharing


Changed files:
addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py, addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py, addons/io_scene_gltf2/blender/exp/gltf2_blender_extract.py, addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py

diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
index cce6470d..cf9686a7 100644
--- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
+++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
@@ -499,14 +499,14 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp
     blender_shape_keys = []
 
     if blender_mesh.shape_keys is not None:
-        morph_max = len(blender_mesh.shape_keys.key_blocks) - 1
-
         for blender_shape_key in blender_mesh.shape_keys.key_blocks:
             if blender_shape_key != blender_shape_key.relative_key:
-                blender_shape_keys.append(ShapeKey(
-                    blender_shape_key,
-                    blender_shape_key.normals_vertex_get(),  # calculate vertex normals for this shape key
-                    blender_shape_key.normals_polygon_get()))  # calculate polygon normals for this shape key
+                if blender_shape_key.mute is False:
+                    morph_max += 1
+                    blender_shape_keys.append(ShapeKey(
+                        blender_shape_key,
+                        blender_shape_key.normals_vertex_get(),  # calculate vertex normals for this shape key
+                        blender_shape_key.normals_polygon_get()))  # calculate polygon normals for this shape key
 
     #
     # Convert polygon to primitive indices and eliminate invalid ones. Assign to material.
diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
index c8285806..2477ba22 100644
--- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
+++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
@@ -64,12 +64,35 @@ def gather_animation_channels(blender_action: bpy.types.Action,
                 channels.append(channel)
     else:
         for channel_group in __get_channel_groups(blender_action, blender_object, export_settings):
-            channel = __gather_animation_channel(channel_group, blender_object, export_settings, None, None, None, None, blender_action.name)
+            channel_group_sorted = __get_channel_group_sorted(channel_group, blender_object)
+            channel = __gather_animation_channel(channel_group_sorted, blender_object, export_settings, None, None, None, None, blender_action.name)
             if channel is not None:
                 channels.append(channel)
 
     return channels
 
+def __get_channel_group_sorted(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object):
+    # if this is shapekey animation, we need to sort in same order than shapekeys
+    # else, no need to sort
+    if blender_object.type == "MESH":
+        first_channel = channels[0]
+        object_path = get_target_object_path(first_channel.data_path)
+        if object_path:
+            # This is shapekeys, we need to sort channels
+            shapekeys_idx = {}
+            cpt_sk = 0
+            for sk in blender_object.data.shape_keys.key_blocks:
+                if sk == sk.relative_key:
+                    continue
+                if sk.mute is True:
+                    continue
+                shapekeys_idx[sk.name] = cpt_sk
+                cpt_sk += 1
+
+            return tuple(sorted(channels, key=lambda x: shapekeys_idx[blender_object.data.shape_keys.path_resolve(get_target_object_path(x.data_path)).name]))
+
+    # 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,
@@ -164,12 +187,17 @@ def __get_channel_groups(blender_action: bpy.types.Action, blender_object: bpy.t
         else:
             try:
                 target = gltf2_blender_get.get_object_from_datapath(blender_object, object_path)
+                if blender_object.type == "MESH":
+                    shape_key = blender_object.data.shape_keys.path_resolve(object_path)
+                    if shape_key.mute is True:
+                        continue
             except ValueError as e:
                 # if the object is a mesh and the action target path can not be resolved, we know that this is a morph
                 # animation.
                 if blender_object.type == "MESH":
-                    # if you need the specific shape key for some reason, this is it:
-                    # shape_key = blender_object.data.shape_keys.path_resolve(object_path)
+                    shape_key = blender_object.data.shape_keys.path_resolve(object_path)
+                    if shape_key.mute is True:
+                        continue
                     target = blender_object.data.shape_keys
                 else:
                     gltf2_io_debug.print_console("WARNING", "Animation target {} not found".format(object_path))
diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
index 8ca627d8..3dd32965 100644
--- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
+++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
@@ -83,7 +83,8 @@ def __gather_extras(blender_mesh: bpy.types.Mesh,
             target_names = []
             for blender_shape_key in blender_mesh.shape_keys.key_blocks:
                 if blender_shape_key != blender_shape_key.relative_key:
-                    target_names.append(blender_shape_key.name)
+                    if blender_shape_key.mute is False:
+                        target_names.append(blender_shape_key.name)
             extras['targetNames'] = target_names
 
     if extras:
@@ -130,6 +131,7 @@ def __gather_weights(blender_mesh: bpy.types.Mesh,
 
     for blender_shape_key in blender_mesh.shape_keys.key_blocks:
         if blender_shape_key != blender_shape_key.relative_key:
-            weights.append(blender_shape_key.value)
+            if blender_shape_key.mute is False:
+                weights.append(blender_shape_key.value)
 
     return weights
diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
index 4d6e8297..e8c13c4b 100644
--- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
+++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
@@ -146,86 +146,90 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings
         if blender_mesh.shape_keys is not None:
             morph_index = 0
             for blender_shape_key in blender_mesh.shape_keys.key_blocks:
-                if blender_shape_key != blender_shape_key.relative_key:
-
-                    target_position_id = 'MORPH_POSITION_' + str(morph_index)
-                    target_normal_id = 'MORPH_NORMAL_' + str(morph_index)
-                    target_tangent_id = 'MORPH_TANGENT_' + str(morph_index)
-
-                    if blender_primitive["attributes"].get(target_position_id):
-                        target = {}
-                        internal_target_position = blender_primitive["attributes"][target_position_id]
+                if blender_shape_key == blender_shape_key.relative_key:
+                    continue
+
+                if blender_shape_key.mute is True:
+                    continue
+
+                target_position_id = 'MORPH_POSITION_' + str(morph_index)
+                target_normal_id = 'MORPH_NORMAL_' + str(morph_index)
+                target_tangent_id = 'MORPH_TANGENT_' + str(morph_index)
+
+                if blender_primitive["attributes"].get(target_position_id):
+                    target = {}
+                    internal_target_position = blender_primitive["attributes"][target_position_id]
+                    binary_data = gltf2_io_binary_data.BinaryData.from_list(
+                        internal_target_position,
+                        gltf2_io_constants.ComponentType.Float
+                    )
+                    target["POSITION"] = gltf2_io.Accessor(
+                        buffer_view=binary_data,
+                        byte_offset=None,
+                        component_type=gltf2_io_constants.ComponentType.Float,
+                        count=len(internal_target_position) // gltf2_io_constants.DataType.num_elements(
+                            gltf2_io_constants.DataType.Vec3),
+                        extensions=None,
+                        extras=None,
+                        max=gltf2_blender_utils.max_components(
+                            internal_target_position, gltf2_io_constants.DataType.Vec3),
+                        min=gltf2_blender_utils.min_components(
+                            internal_target_position, gltf2_io_constants.DataType.Vec3),
+                        name=None,
+                        normalized=None,
+                        sparse=None,
+                        type=gltf2_io_constants.DataType.Vec3
+                    )
+
+                    if export_settings[NORMALS] \\
+                            and export_settings[MORPH_NORMAL] \\
+                            and blender_primitive["attributes"].get(target_normal_id):
+
+                        internal_target_normal = blender_primitive["attributes"][target_normal_id]
                         binary_data = gltf2_io_binary_data.BinaryData.from_list(
-                            internal_target_position,
-                            gltf2_io_constants.ComponentType.Float
+                            internal_target_normal,
+                            gltf2_io_constants.ComponentType.Float,
                         )
-                        target["POSITION"] = gltf2_io.Accessor(
+                        target['NORMAL'] = gltf2_io.Accessor(
                             buffer_view=binary_data,
                             byte_offset=None,
                             component_type=gltf2_io_constants.ComponentType.Float,
-                            count=len(internal_target_position) // gltf2_io_constants.DataType.num_elements(
+                            count=len(internal_target_normal) // gltf2_io_constants.DataType.num_elements(
                                 gltf2_io_constants.DataType.Vec3),
                             extensions=None,
                             extras=None,
-                            max=gltf2_blender_utils.max_components(
-                                internal_target_position, gltf2_io_constants.DataType.Vec3),
-                            min=gltf2_blender_utils.min_components(
-                                internal_target_position, gltf2_io_constants.DataType.Vec3),
+                            max=None,
+                            min=None,
                             name=None,
                             normalized=None,
                             sparse=None,
                             type=gltf2_io_constants.DataType.Vec3
                         )
 
-                        if export_settings[NORMALS] \\
-                                and export_settings[MORPH_NORMAL] \\
-                                and blender_primitive["attributes"].get(target_normal_id):
-
-                            internal_target_normal = blender_primitive["attributes"][target_normal_id]
-                            binary_data = gltf2_io_binary_data.BinaryData.from_list(
-                                internal_target_normal,
-                                gltf2_io_constants.ComponentType.Float,
-                            )
-                            target['NORMAL'] = gltf2_io.Accessor(
-                                buffer_view=binary_data,
-                                byte_offset=None,
-                                component_type=gltf2_io_constants.ComponentType.Float,
-                                count=len(internal_target_normal) // gltf2_io_constants.DataType.num_elements(
-                                    gltf2_io_constants.DataType.Vec3),
-                                extensions=None,
-                                extras=None,
-                                max=None,
-                                min=None,
-                                name=None,
-                                normalized=None,
-                                sparse=None,
-                                type=gltf2_io_constants.DataType.Vec3
-                            )
-
-                        if export_settings[TANGENTS] \\
-                                and export_settings[MORPH_TANGENT] \\
-                                and blender_primitive["attributes"].get(target_tangent_id):
-                            internal_target_tangent = blender_primitive["attributes"][target_tangent_id]
-                            binary_data = gltf2_io_binary_data.BinaryData.from_list(
-                                internal_target_tangent,
-                                gltf2_io_constants.ComponentType.Float,
-                            )
-                            target['TANGENT'] = gltf2_io.Accessor(
-                                buffer_view=binary_data,
-                                byte_offset=None,
-                                component_type=gltf2_io_constants.ComponentType.Float,
-                                count=len(internal_target_tangent) // gltf2_io_constants.DataType.num_elements(
-                                    gltf2_io_constants.DataType.Vec3),
-                                extensions=None,
-                                extras=None,
-                                max=None,
-                                min=None,
-                                name=None,
-                                normalized=None,
-                                sparse=None,
-                                type=gltf2_io_constants.DataType.Vec3
-                            )
-                        targets.append(target)
-                        morph_index += 1
+                    if export_settings[TANGENTS] \\
+                            and export_settings[MORPH_TANGENT] \\
+                            and blender_primitive["attributes"].get(target_tangent_id):
+                        internal_target_tangent = blender_primitive["attributes"][target_tangent_id]
+                        binary_data = gltf2_io_binary_data.BinaryData.from_list(
+                            internal_target_tangent,
+                            gltf2_io_constants.ComponentType.Float,
+                        )
+                        target['TANGENT'] = gltf2_io.Accessor(
+                            buffer_view=binary_data,
+                            byte_offset=None,
+                            component_type=gltf2_io_constants.ComponentType.Float,
+                            count=len(internal_target_tangent) // gltf2_io_constants.DataType.num_elements(
+                                gltf2_io_constants.DataType.Vec3),
+                            extensions=None,
+                            extras=None,
+                            max=None,
+                            min=None,
+                            name=None,
+                            normalized=None,
+                            sparse=None,
+                            type=gltf2_io_constants.DataType.Vec3
+                        )
+                    targets.append(target)
+                    morph_index += 1
         return targets
     return None