Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove armature at export #2009

Merged
merged 17 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions addons/io_scene_gltf2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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_
Expand Down Expand Up @@ -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] = {}
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
43 changes: 38 additions & 5 deletions addons/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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()

Expand Down
10 changes: 7 additions & 3 deletions addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
132 changes: 79 additions & 53 deletions addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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),
Expand Down Expand Up @@ -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 = {}
Expand Down
Loading