Skip to content

Commit

Permalink
Merge pull request #2257 from KhronosGroup/disable_viewport_driver_ma…
Browse files Browse the repository at this point in the history
…nagement

Manage driver on SK for armature when disabling viewport perf optim
  • Loading branch information
julienduroure authored May 30, 2024
2 parents 0a63041 + be122e2 commit 4081e54
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 107 deletions.
14 changes: 7 additions & 7 deletions addons/io_scene_gltf2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,11 +737,11 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
default=False
)

export_optimize_armature_disable_viewport: BoolProperty(
name='Disable viewport if possible',
export_optimize_disable_viewport: BoolProperty(
name='Disable viewport for other objects',
description=(
"When exporting armature, disable viewport for other objects, "
"for performance. Drivers on shape keys for skined meshes prevent this optimization for now"
"When exporting animations, disable viewport for other objects, "
"for performance"
),
default=False
)
Expand Down Expand Up @@ -1145,7 +1145,7 @@ def execute(self, context):
export_settings['gltf_optimize_animation'] = self.export_optimize_animation_size
export_settings['gltf_optimize_animation_keep_armature'] = self.export_optimize_animation_keep_anim_armature
export_settings['gltf_optimize_animation_keep_object'] = self.export_optimize_animation_keep_anim_object
export_settings['gltf_optimize_armature_disable_viewport'] = self.export_optimize_armature_disable_viewport
export_settings['gltf_optimize_disable_viewport'] = self.export_optimize_disable_viewport
export_settings['gltf_export_anim_single_armature'] = self.export_anim_single_armature
export_settings['gltf_export_reset_pose_bones'] = self.export_reset_pose_bones
export_settings['gltf_export_reset_sk_data'] = self.export_morph_reset_sk_data
Expand All @@ -1161,7 +1161,7 @@ def execute(self, context):
export_settings['gltf_optimize_animation'] = False
export_settings['gltf_optimize_animation_keep_armature'] = False
export_settings['gltf_optimize_animation_keep_object'] = False
export_settings['gltf_optimize_armature_disable_viewport'] = False
export_settings['gltf_optimize_disable_viewport'] = False
export_settings['gltf_export_anim_single_armature'] = False
export_settings['gltf_export_reset_pose_bones'] = False
export_settings['gltf_export_reset_sk_data'] = False
Expand Down Expand Up @@ -1638,7 +1638,7 @@ def export_panel_animation_optimize(layout, operator):
row.prop(operator, 'export_optimize_animation_keep_anim_object')

row = body.row()
row.prop(operator, 'export_optimize_armature_disable_viewport')
row.prop(operator, 'export_optimize_disable_viewport')


def export_panel_animation_extra(layout, operator):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from .sampled.shapekeys.gltf2_blender_gather_sk_action_sampled import gather_action_sk_sampled
from .sampled.object.gltf2_blender_gather_object_channels import gather_object_sampled_channels, gather_sampled_object_channel
from .sampled.shapekeys.gltf2_blender_gather_sk_channels import gather_sampled_sk_channel
from .gltf2_blender_gather_drivers import get_sk_drivers
from .gltf2_blender_gather_drivers import get_sk_drivers, get_driver_on_shapekey
from .gltf2_blender_gather_animation_utils import reset_bone_matrix, reset_sk_data, link_samplers, add_slide_data, merge_tracks_perform, bake_animation

def gather_actions_animations(export_settings):
Expand Down Expand Up @@ -270,24 +270,36 @@ def gather_action_animations( obj_uuid: int,
current_use_nla = blender_object.animation_data.use_nla
blender_object.animation_data.use_nla = False

# Try to disable all except armature in viewport, for performance
if export_settings['gltf_optimize_armature_disable_viewport'] \
# Disable all except armature in viewport, for performance
if export_settings['gltf_optimize_disable_viewport'] \
and export_settings['vtree'].nodes[obj_uuid].blender_object.type == "ARMATURE":

# If the skinned mesh has driver(s), we can't disable it to bake armature.
need_to_enable_again = False
sk_drivers = get_sk_drivers(obj_uuid, export_settings)
if len(sk_drivers) == 0:
need_to_enable_again = True
# Before baking, disabling from viewport all meshes
for obj in [n.blender_object for n in export_settings['vtree'].nodes.values() if n.blender_type in
[VExportNode.OBJECT, VExportNode.ARMATURE, VExportNode.COLLECTION]]:
obj.hide_viewport = True
export_settings['vtree'].nodes[obj_uuid].blender_object.hide_viewport = False
else:
export_settings['log'].warning("Can't disable viewport because of drivers")
export_settings['gltf_optimize_armature_disable_viewport'] = False # We changed the option here, so we don't need to re-check it later, during

# Before baking, disabling from viewport all meshes
for obj in [n.blender_object for n in export_settings['vtree'].nodes.values() if n.blender_type in
[VExportNode.OBJECT, VExportNode.ARMATURE, VExportNode.COLLECTION]]:
obj.hide_viewport = True
export_settings['vtree'].nodes[obj_uuid].blender_object.hide_viewport = False

# We need to create custom properties on armature to store shape keys drivers on disabled meshes
# This way, we can evaluate drivers on shape keys, and bake them
drivers = get_sk_drivers(obj_uuid, export_settings)
if drivers:
# So ... Let's create some costum properties and the armature
# First, retrieve the armature object
for mesh_uuid in drivers:
_, channels = get_driver_on_shapekey(mesh_uuid, export_settings)
blender_object["gltf_" + mesh_uuid] = [0.0] * len(channels)
for idx, channel in enumerate(channels):
if channel is None:
continue
if blender_object.animation_data is None or blender_object.animation_data.drivers is None:
# There is no animation on the armature, so no need to crate driver
# But, we need to copy the current value of the shape key to the custom property
blender_object["gltf_" + mesh_uuid][idx] = blender_object.data.shape_keys.key_blocks[channel.data_path.split('"')[1]].value
else:
dr = blender_object.animation_data.drivers.from_existing(src_driver=channel)
dr.data_path = "[\"gltf_" + mesh_uuid + "\"]"
dr.array_index = idx

export_user_extensions('animation_switch_loop_hook', export_settings, blender_object, False)

Expand Down Expand Up @@ -464,14 +476,21 @@ def gather_action_animations( obj_uuid: int,
if blender_object and current_world_matrix is not None:
blender_object.matrix_world = current_world_matrix

if export_settings['gltf_optimize_armature_disable_viewport'] \
if export_settings['gltf_optimize_disable_viewport'] \
and export_settings['vtree'].nodes[obj_uuid].blender_object.type == "ARMATURE":
if need_to_enable_again is True:
# And now, restoring meshes in viewport
for node, obj in [(n, n.blender_object) for n in export_settings['vtree'].nodes.values() if n.blender_type in
[VExportNode.OBJECT, VExportNode.ARMATURE, VExportNode.COLLECTION]]:
obj.hide_viewport = node.default_hide_viewport
export_settings['vtree'].nodes[obj_uuid].blender_object.hide_viewport = export_settings['vtree'].nodes[obj_uuid].default_hide_viewport
# And now, restoring meshes in viewport
for node, obj in [(n, n.blender_object) for n in export_settings['vtree'].nodes.values() if n.blender_type in
[VExportNode.OBJECT, VExportNode.ARMATURE, VExportNode.COLLECTION]]:
obj.hide_viewport = node.default_hide_viewport
export_settings['vtree'].nodes[obj_uuid].blender_object.hide_viewport = export_settings['vtree'].nodes[obj_uuid].default_hide_viewport
# Let's remove the custom properties, and first, remove drivers
drivers = get_sk_drivers(obj_uuid, export_settings)
if drivers:
for mesh_uuid in drivers:
for armature_driver in blender_object.animation_data.drivers:
if "gltf_" + mesh_uuid in armature_driver.data_path:
blender_object.animation_data.drivers.remove(armature_driver)
del blender_object["gltf_" + mesh_uuid]

export_user_extensions('animation_switch_loop_hook', export_settings, blender_object, True)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,58 +31,62 @@ def get_sk_drivers(blender_armature_uuid, export_settings):
children_list.extend(export_settings['vtree'].nodes[bone].children)

for child_uuid in children_list:
uuid, channels = get_driver_on_shapekey(child_uuid, export_settings)
if uuid is not None:
drivers.append(child_uuid)

if export_settings['vtree'].nodes[child_uuid].blender_type == "BONE":
continue
return drivers

child = export_settings['vtree'].nodes[child_uuid].blender_object
def get_driver_on_shapekey(blender_object_uuid, export_settings):
if export_settings['vtree'].nodes[blender_object_uuid].blender_type == "BONE":
return None, None

if not child.data:
continue
# child.data can be an armature - which has no shapekeys
if not hasattr(child.data, 'shape_keys'):
continue
if not child.data.shape_keys:
continue
if not child.data.shape_keys.animation_data:
continue
if not child.data.shape_keys.animation_data.drivers:
continue
if len(child.data.shape_keys.animation_data.drivers) <= 0:
continue
child = export_settings['vtree'].nodes[blender_object_uuid].blender_object

shapekeys_idx = {}
cpt_sk = 0
for sk in get_sk_exported(child.data.shape_keys.key_blocks):
shapekeys_idx[sk.name] = cpt_sk
cpt_sk += 1
if not child.data:
return None, None
# child.data can be an armature - which has no shapekeys
if not hasattr(child.data, 'shape_keys'):
return None, None
if not child.data.shape_keys:
return None, None
if not child.data.shape_keys.animation_data:
return None, None
if not child.data.shape_keys.animation_data.drivers:
return None, None
if len(child.data.shape_keys.animation_data.drivers) <= 0:
return None, None

# Note: channels will have some None items only for SK if some SK are not animated
idx_channel_mapping = []
all_sorted_channels = []
for sk_c in child.data.shape_keys.animation_data.drivers:
# Check if driver is valid. If not, ignore this driver channel
try:
# Check if driver is valid.
# Try/Except is no more a suffisant check, starting with version Blender 3.0,
# Blender crashes when trying to resolve path on invalid driver
if not sk_c.is_valid:
continue
sk_name = child.data.shape_keys.path_resolve(get_target_object_path(sk_c.data_path)).name
except:
continue
if skip_sk(child.data.shape_keys.key_blocks, child.data.shape_keys.key_blocks[sk_name]):
continue
idx_channel_mapping.append((shapekeys_idx[sk_name], sk_c))
existing_idx = dict(idx_channel_mapping)
for i in range(0, cpt_sk):
if i not in existing_idx.keys():
all_sorted_channels.append(None)
else:
all_sorted_channels.append(existing_idx[i])
shapekeys_idx = {}
cpt_sk = 0
for sk in get_sk_exported(child.data.shape_keys.key_blocks):
shapekeys_idx[sk.name] = cpt_sk
cpt_sk += 1

# Checks there are some driver on SK, and that there is not only invalid drivers
if len(all_sorted_channels) > 0 and not all([i is None for i in all_sorted_channels]):
drivers.append(child_uuid)
# Note: channels will have some None items only for SK if some SK are not animated
idx_channel_mapping = []
all_sorted_channels = []
for sk_c in child.data.shape_keys.animation_data.drivers:
# Check if driver is valid. If not, ignore this driver channel
try:
# Check if driver is valid.
# Try/Except is no more a suffisant check, starting with version Blender 3.0,
# Blender crashes when trying to resolve path on invalid driver
if not sk_c.is_valid:
return None, None
sk_name = child.data.shape_keys.path_resolve(get_target_object_path(sk_c.data_path)).name
except:
return None, None
if skip_sk(child.data.shape_keys.key_blocks, child.data.shape_keys.key_blocks[sk_name]):
return None, None
idx_channel_mapping.append((shapekeys_idx[sk_name], sk_c))
existing_idx = dict(idx_channel_mapping)
for i in range(0, cpt_sk):
if i not in existing_idx.keys():
all_sorted_channels.append(None)
else:
all_sorted_channels.append(existing_idx[i])

return drivers
# Checks there are some driver on SK, and that there is not only invalid drivers
if len(all_sorted_channels) > 0 and not all([i is None for i in all_sorted_channels]):
return blender_object_uuid, all_sorted_channels
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ def get_cache_data(path: str,
# - Action mode, where some object have multiple actions
# - For this case, on first call, we will cache active action for all objects
# - On next calls, we will cache only the action of current object, so we can disable viewport for others
# For armature : We already checked that we can disable viewport (in case of drivers, this is currently not possible)

need_to_enable_again = False
if export_settings['gltf_optimize_armature_disable_viewport'] is True and len(obj_uuids) == 1:
if export_settings['gltf_optimize_disable_viewport'] is True and len(obj_uuids) == 1:
need_to_enable_again = True
# Before baking, disabling from viewport all meshes
for obj in [n.blender_object for n in export_settings['vtree'].nodes.values() if n.blender_type in
Expand All @@ -65,7 +64,7 @@ def get_cache_data(path: str,
continue
obj.hide_viewport = True
export_settings['vtree'].nodes[obj_uuids[0]].blender_object.hide_viewport = False

# Work for drivers on shapekeys is already done at start of animation export

depsgraph = bpy.context.evaluated_depsgraph_get()

Expand Down Expand Up @@ -494,7 +493,11 @@ def object_caching(data, obj_uuids, current_instance, action_name, frame, depsgr

if cache_sk:
initialize_data_dict(data, key1, key2, key3, key4)
data[key1][key2][key3][key4][frame] = [k.value for k in get_sk_exported(driver_object.data.shape_keys.key_blocks)]
if export_settings['gltf_optimize_disable_viewport'] is True:
# Retrieve data from custom properties instead of shape keys
data[key1][key2][key3][key4][frame] = list(blender_obj['gltf_' + dr_obj]) # This include only exported SK
else:
data[key1][key2][key3][key4][frame] = [k.value for k in get_sk_exported(driver_object.data.shape_keys.key_blocks)]
cache_sk = False

def light_nodetree_caching(data, action_name, frame, export_settings):
Expand Down
Loading

0 comments on commit 4081e54

Please sign in to comment.