diff --git a/addons/io_scene_gltf2/__init__.py b/addons/io_scene_gltf2/__init__.py index 2e06f6f1d..c70569ccc 100644 --- a/addons/io_scene_gltf2/__init__.py +++ b/addons/io_scene_gltf2/__init__.py @@ -486,6 +486,15 @@ def __init__(self): default=False ) + export_armature_object_remove: BoolProperty( + name='Remove Armature Object', + description=( + 'Remove Armature object if possible.' + 'If Armature has multiple root bones, object will not be removed' + ), + default=False + ) + export_optimize_animation_size: BoolProperty( name='Optimize Animation Size', description=( @@ -831,6 +840,7 @@ def execute(self, context): export_settings['gltf_animations'] = self.export_animations export_settings['gltf_def_bones'] = self.export_def_bones export_settings['gltf_flatten_bones_hierarchy'] = self.export_hierarchy_flatten_bones + export_settings['gltf_armature_object_remove'] = self.export_armature_object_remove if self.export_animations: export_settings['gltf_frame_range'] = self.export_frame_range export_settings['gltf_force_sampling'] = self.export_force_sampling @@ -1289,6 +1299,8 @@ def draw(self, context): 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") row = layout.row() + row.prop(operator, 'export_armature_object_remove') + row = layout.row() row.prop(operator, 'export_hierarchy_flatten_bones') class GLTF_PT_export_data_compression(bpy.types.Panel): diff --git a/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_action.py b/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_action.py index 77d181fcb..434ffaee3 100644 --- a/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_action.py +++ b/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_action.py @@ -44,7 +44,12 @@ def gather_actions_animations(export_settings): # Do not manage not exported objects if vtree.nodes[obj_uuid].node is None: - continue + if export_settings["gltf_armature_object_remove"] is True: + # Manage armature object, as this is the object that has the animation + if not vtree.nodes[obj_uuid].blender_object: + continue + else: + continue animations_, merged_tracks = gather_action_animations(obj_uuid, merged_tracks, len(animations), export_settings) animations += animations_ @@ -73,7 +78,12 @@ def prepare_actions_range(export_settings): # Do not manage not exported objects if vtree.nodes[obj_uuid].node is None: - continue + if export_settings["gltf_armature_object_remove"] is True: + # Manage armature object, as this is the object that has the animation + if not vtree.nodes[obj_uuid].blender_object: + continue + else: + continue if obj_uuid not in export_settings['ranges']: export_settings['ranges'][obj_uuid] = {} @@ -178,7 +188,12 @@ def prepare_actions_range(export_settings): # Do not manage not exported objects if vtree.nodes[obj_uuid].node is None: - continue + if export_settings['gltf_armature_object_remove'] is True: + # Manage armature object, as this is the object that has the animation + if not vtree.nodes[obj_uuid].blender_object: + continue + else: + continue blender_actions = __get_blender_actions(obj_uuid, export_settings) for blender_action, track, type_ in blender_actions: diff --git a/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_scene_animation.py b/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_scene_animation.py index 51c628f41..e8abcab8c 100644 --- a/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_scene_animation.py +++ b/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_scene_animation.py @@ -45,7 +45,12 @@ def gather_scene_animations(export_settings): # Do not manage not exported objects if vtree.nodes[obj_uuid].node is None: - continue + if export_settings['gltf_armature_object_remove'] is True: + # Manage armature object, as this is the object that has the animation + if not vtree.nodes[obj_uuid].blender_object: + continue + else: + continue blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object diff --git a/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_tracks.py b/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_tracks.py index 5b9698a8e..3632c7adc 100644 --- a/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_tracks.py +++ b/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_tracks.py @@ -32,7 +32,12 @@ def gather_tracks_animations(export_settings): # Do not manage not exported objects if vtree.nodes[obj_uuid].node is None: - continue + if export_settings['gltf_armature_object_remove'] is True: + # Manage armature object, as this is the object that has the animation + if not vtree.nodes[obj_uuid].blender_object: + continue + else: + continue animations_, merged_tracks = gather_track_animations(obj_uuid, merged_tracks, len(animations), export_settings) animations += animations_ diff --git a/addons/io_scene_gltf2/blender/exp/animation/sampled/gltf2_blender_gather_animation_sampling_cache.py b/addons/io_scene_gltf2/blender/exp/animation/sampled/gltf2_blender_gather_animation_sampling_cache.py index f40e344c6..e9ecd73f2 100644 --- a/addons/io_scene_gltf2/blender/exp/animation/sampled/gltf2_blender_gather_animation_sampling_cache.py +++ b/addons/io_scene_gltf2/blender/exp/animation/sampled/gltf2_blender_gather_animation_sampling_cache.py @@ -129,6 +129,10 @@ def get_cache_data(path: str, # Bone has a parent, but in export, after filter, is at root of armature matrix = blender_bone.matrix.copy() + # Because there is no armature object, we need to apply the TRS of armature to the root bone + if export_settings['gltf_armature_object_remove'] is True: + matrix = matrix @ blender_obj.matrix_world + if blender_obj.animation_data and blender_obj.animation_data.action \ and export_settings['gltf_animation_mode'] in ["ACTIVE_ACTIONS", "ACTIONS"]: if blender_bone.name not in data[obj_uuid][blender_obj.animation_data.action.name]['bone'].keys(): diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather.py index 4a84a1ba9..dd1bc02a3 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather.py @@ -19,6 +19,7 @@ from ..com.gltf2_blender_extras import generate_extras from .gltf2_blender_gather_cache import cached from . import gltf2_blender_gather_nodes +from . import gltf2_blender_gather_joints from . import gltf2_blender_gather_tree from .animation.sampled.object.gltf2_blender_gather_object_keyframes import get_cache_data from .animation.gltf2_blender_gather_animations import gather_animations @@ -62,6 +63,8 @@ def __gather_scene(blender_scene, export_settings): vtree = gltf2_blender_gather_tree.VExportTree(export_settings) vtree.construct(blender_scene) vtree.search_missing_armature() # In case armature are no parented correctly + vtree.bake_armature_bone_list() # Used in case we remove the armature + vtree.check_if_we_can_remove_armature() # Check if we can remove the armatures objects export_user_extensions('vtree_before_filter_hook', export_settings, vtree) @@ -76,11 +79,41 @@ def __gather_scene(blender_scene, export_settings): 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) + + + + # If we don't remove armature object, we can't have bones directly at root of scene + # So looping only on root nodes, as they are all nodes, not bones + if export_settings['gltf_armature_object_remove'] is False: + 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) + else: + # If we remove armature objects, we can have bone at root of scene + armature_root_joints = {} + for r in [vtree.nodes[r] for r in vtree.roots]: + # Classic Object/node case + if r.blender_type != gltf2_blender_gather_tree.VExportNode.BONE: + node = gltf2_blender_gather_nodes.gather_node( + r, export_settings) + if node is not None: + scene.nodes.append(node) + else: + # We can have bone are root of scene because we remove the armature object + # and the armature was at root of scene + node = gltf2_blender_gather_joints.gather_joint_vnode( + r.uuid, export_settings) + if node is not None: + scene.nodes.append(node) + if r.armature not in armature_root_joints.keys(): + armature_root_joints[r.armature] = [] + armature_root_joints[r.armature].append(node) + + # Manage objects parented to bones, now we go through all root objects + for k, v in armature_root_joints.items(): + gltf2_blender_gather_nodes.get_objects_parented_to_bones(k, v, export_settings) vtree.add_neutral_bones() diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py index d21699472..f3c54fbc7 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py @@ -58,11 +58,15 @@ def gather_joint_vnode(vnode, export_settings): :return: a glTF2 node (acting as a joint) """ vtree = export_settings['vtree'] - blender_object = vtree.nodes[vnode].blender_object blender_bone = vtree.nodes[vnode].blender_bone - - mat = vtree.nodes[vtree.nodes[vnode].parent_uuid].matrix_world.inverted_safe() @ vtree.nodes[vnode].matrix_world + if export_settings['gltf_armature_object_remove'] is True: + if vtree.nodes[vnode].parent_uuid is not None: + mat = vtree.nodes[vtree.nodes[vnode].parent_uuid].matrix_world.inverted_safe() @ vtree.nodes[vnode].matrix_world + else: + mat = vtree.nodes[vnode].matrix_world + else: + mat = vtree.nodes[vtree.nodes[vnode].parent_uuid].matrix_world.inverted_safe() @ vtree.nodes[vnode].matrix_world trans, rot, sca = mat.decompose() diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py index a213bda70..92c7f4bc5 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py @@ -31,6 +31,7 @@ from .gltf2_blender_gather_tree import VExportNode def gather_node(vnode, export_settings): + blender_object = vnode.blender_object skin = gather_skin(vnode.uuid, export_settings) @@ -39,7 +40,7 @@ def gather_node(vnode, export_settings): node = gltf2_io.Node( camera=__gather_camera(blender_object, export_settings), - children=__gather_children(vnode, blender_object, export_settings), + children=__gather_children(vnode, export_settings), extensions=__gather_extensions(blender_object, export_settings), extras=__gather_extras(blender_object, export_settings), matrix=__gather_matrix(blender_object, export_settings), @@ -71,78 +72,103 @@ def __gather_camera(blender_object, export_settings): return gltf2_blender_gather_cameras.gather_camera(blender_object.data, export_settings) -def __gather_children(vnode, blender_object, export_settings): +def __gather_children(vnode, export_settings): children = [] vtree = export_settings['vtree'] + + armature_object_uuid = None + # 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) + if export_settings['gltf_armature_object_remove'] is False: + 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) + else: + root_joints = [] + for c in [vtree.nodes[c] for c in vnode.children]: + if c.blender_type != gltf2_blender_gather_tree.VExportNode.BONE: + node = gather_node(c, export_settings) + if node is not None: + children.append(node) + else: + # We come here because armature was remove, and bone can be a child of any object + joint = gltf2_blender_gather_joints.gather_joint_vnode(c.uuid, export_settings) + children.append(joint) + armature_object_uuid = c.armature + root_joints.append(joint) + # Now got all bone children (that are root joints), we can get object parented to bones # Armature --> Retrieve Blender bones + # This can't happen if we remove the Armature Object if vnode.blender_type == gltf2_blender_gather_tree.VExportNode.ARMATURE: + armature_object_uuid = vnode.uuid root_joints = [] - - 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] + root_bones_uuid = export_settings['vtree'].get_root_bones_uuid(vnode.uuid) 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: - return joint - parent_joint = find_parent_joint(joint.children, name) - if parent_joint: - return parent_joint - return None - - for child in direct_bone_children: # List of object that are parented to bones - # find parent joint - parent_joint = find_parent_joint(root_joints, vtree.nodes[child].blender_object.parent_bone) - if not parent_joint: - continue - child_node = gather_node(vtree.nodes[child], export_settings) - if child_node is None: - continue - blender_bone = blender_object.pose.bones[parent_joint.name] - - 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 + if vnode.blender_type == gltf2_blender_gather_tree.VExportNode.ARMATURE \ + or armature_object_uuid is not None: - parent_joint.children.append(child_node) + # Object parented to bones + get_objects_parented_to_bones(armature_object_uuid, root_joints, export_settings) return children +def get_objects_parented_to_bones(armature_object_uuid, root_joints, export_settings): + vtree = export_settings['vtree'] + direct_bone_children = [] + for n in [vtree.nodes[i] for i in vtree.get_all_bones(armature_object_uuid)]: + direct_bone_children.extend([c for c in n.children if vtree.nodes[c].blender_type != gltf2_blender_gather_tree.VExportNode.BONE]) + + for child in direct_bone_children: # List of object that are parented to bones + # find parent joint + parent_joint = __find_parent_joint(root_joints, vtree.nodes[child].blender_object.parent_bone) + if not parent_joint: + continue + child_node = gather_node(vtree.nodes[child], export_settings) + if child_node is None: + continue + + 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) + + +def __find_parent_joint(joints, name): + for joint in joints: + if joint.name == name: + return joint + parent_joint = __find_parent_joint(joint.children, name) + if parent_joint: + return parent_joint + return None + def __gather_extensions(blender_object, export_settings): extensions = {} diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py index 19722830c..3085e91bd 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py @@ -23,6 +23,7 @@ from ...io.exp import gltf2_io_binary_data from ..com.gltf2_blender_default import BLENDER_GLTF_SPECIAL_COLLECTION from . import gltf2_blender_gather_accessors +from .gltf2_blender_gather_joints import gather_joint_vnode class VExportNode: @@ -86,7 +87,7 @@ def set_blender_data(self, blender_object, 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 "" ) + print(tree.nodes[c].uuid, 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: @@ -288,23 +289,40 @@ def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, par 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 + def get_all_bones(self, uuid): #For armature only + if not hasattr(self.nodes[uuid], "all_bones"): + 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)) + self.nodes[uuid].all_bones = tot + return tot # Not really needed to return, we are just baking it before export really starts + else: + self.nodes[uuid].all_bones = [] + return [] + else: + return self.nodes[uuid].all_bones + + def get_root_bones_uuid(self, uuid): #For armature only + if not hasattr(self.nodes[uuid], "root_bones_uuid"): + if self.nodes[uuid].blender_type == VExportNode.ARMATURE: + all_armature_children = self.nodes[uuid].children + self.nodes[uuid].root_bones_uuid = [c for c in all_armature_children if self.nodes[c].blender_type == VExportNode.BONE] + return self.nodes[uuid].root_bones_uuid # Not really needed to return, we are just baking it before export really starts + else: + self.nodes[uuid].root_bones_uuid = [] + return [] else: - return [] + return self.nodes[uuid].root_bones_uuid def get_all_node_of_type(self, node_type): return [n.uuid for n in self.nodes.values() if n.blender_type == node_type] @@ -312,10 +330,9 @@ def get_all_node_of_type(self, node_type): 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 "" ) + print(self.nodes[n].uuid, "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: @@ -332,7 +349,6 @@ def filter(self): self.filter_perform() self.remove_filtered_nodes() - def recursive_filter_tag(self, uuid, parent_keep_tag): # parent_keep_tag is for collection instance # some properties (selection, visibility, renderability) @@ -452,10 +468,20 @@ def node_filter_inheritable_is_kept(self, uuid): bpy.data.collections[BLENDER_GLTF_SPECIAL_COLLECTION].objects: return False + if self.export_settings['gltf_armature_object_remove'] is True: + # If we remove the Armature object + if self.nodes[uuid].blender_type == VExportNode.ARMATURE: + self.nodes[uuid].arma_exported = True + return False + return True def remove_filtered_nodes(self): - self.nodes = {k:n for (k, n) in self.nodes.items() if n.keep_tag is True} + if self.export_settings['gltf_armature_object_remove'] is True: + # If we remove the Armature object + self.nodes = {k:n for (k, n) in self.nodes.items() if n.keep_tag is True or (n.keep_tag is False and n.blender_type == VExportNode.ARMATURE)} + else: + self.nodes = {k:n for (k, n) in self.nodes.items() if n.keep_tag is True} def search_missing_armature(self): for n in [n for n in self.nodes.values() if hasattr(n, "armature_needed") is True]: @@ -464,6 +490,14 @@ def search_missing_armature(self): n.armature = candidates[0].uuid del n.armature_needed + def bake_armature_bone_list(self): + # Used to store data in armature vnode + # If armature is removed from export + # Data are still available, even if armature is not exported (so bones are re-parented) + for n in [n for n in self.nodes.values() if n.blender_type == VExportNode.ARMATURE]: + self.get_all_bones(n.uuid) + self.get_root_bones_uuid(n.uuid) + def add_neutral_bones(self): added_armatures = [] for n in [n for n in self.nodes.values() if \ @@ -531,6 +565,9 @@ def get_unused_skins(self): from .gltf2_blender_gather_skins import gather_skin skins = [] for n in [n for n in self.nodes.values() if n.blender_type == VExportNode.ARMATURE]: + if self.export_settings['gltf_armature_object_remove'] is True: + if hasattr(n, "arma_exported") is False: + continue if len([m for m in self.nodes.values() if m.keep_tag is True and m.blender_type == VExportNode.OBJECT and m.armature == n.uuid]) == 0: skin = gather_skin(n.uuid, self.export_settings) skins.append(skin) @@ -562,3 +599,14 @@ def break_bone_hierarchy(self): self.nodes[self.nodes[bone].parent_uuid].children.remove(bone) self.nodes[bone].parent_uuid = arma self.nodes[arma].children.append(bone) + + def check_if_we_can_remove_armature(self): + # If user requested to remove armature, we need to check if it is possible + # If is impossible to remove it if armature has multiple root bones. (glTF validator error) + # Currently, we manage it at export level, not at each armature level + for arma_uuid in [n for n in self.nodes.keys() if self.nodes[n].blender_type == VExportNode.ARMATURE]: + if len(self.get_root_bones_uuid(arma_uuid)) > 1: + # We can't remove armature + self.export_settings['gltf_armature_object_remove'] = False + print("WARNING: We can't remove armature object because some armatures have multiple root bones.") + break diff --git a/docs/blender_docs/scene_gltf2.rst b/docs/blender_docs/scene_gltf2.rst index f60810be1..0269f907b 100644 --- a/docs/blender_docs/scene_gltf2.rst +++ b/docs/blender_docs/scene_gltf2.rst @@ -942,6 +942,8 @@ Use Rest Position Armature Export Deformation Bones only Export Deformation bones only, not other bones. Animation for deformation bones are baked. +Remove Armature Object + Remove Armature Objects if possible. If some armature(s) have multiple root bones, we can't remove them. Flatten Bone Hierarchy Useful in case of non-decomposable TRS matrix.