mirror of
https://projects.blender.org/blender/blender.git
synced 2025-01-22 07:22:12 -05:00
glTF exporter: add fallback interpolation option
Before, any sampled properties that are not keyed was exported with LINEAR interpolation Now, user can choose the interpolation for these not keyed values (LINEAR or CONSTANT/STEP) Example of not keyed properties: Deformation bones when animators animated other bones
This commit is contained in:
parent
a9d72b1a14
commit
748c91ce27
11 changed files with 38 additions and 27 deletions
|
@ -5,7 +5,7 @@
|
||||||
bl_info = {
|
bl_info = {
|
||||||
'name': 'glTF 2.0 format',
|
'name': 'glTF 2.0 format',
|
||||||
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
||||||
"version": (4, 4, 33),
|
"version": (4, 4, 34),
|
||||||
'blender': (4, 4, 0),
|
'blender': (4, 4, 0),
|
||||||
'location': 'File > Import-Export',
|
'location': 'File > Import-Export',
|
||||||
'description': 'Import-Export as glTF 2.0',
|
'description': 'Import-Export as glTF 2.0',
|
||||||
|
@ -671,6 +671,15 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
|
||||||
default=True
|
default=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export_sampling_interpolation_fallback: EnumProperty(
|
||||||
|
name='Sampling Interpolation Fallback',
|
||||||
|
items=(('LINEAR', 'Linear', 'Linear interpolation between keyframes'),
|
||||||
|
('STEP', 'Step', 'No interpolation between keyframes'),
|
||||||
|
),
|
||||||
|
description='Interpolation fallback for sampled animations, when the property is not keyed',
|
||||||
|
default='LINEAR'
|
||||||
|
)
|
||||||
|
|
||||||
export_pointer_animation: BoolProperty(
|
export_pointer_animation: BoolProperty(
|
||||||
name='Export Animation Pointer (Experimental)',
|
name='Export Animation Pointer (Experimental)',
|
||||||
description='Export material, Light & Camera animation as Animation Pointer. '
|
description='Export material, Light & Camera animation as Animation Pointer. '
|
||||||
|
@ -1179,6 +1188,7 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
|
||||||
if self.export_animations:
|
if self.export_animations:
|
||||||
export_settings['gltf_frame_range'] = self.export_frame_range
|
export_settings['gltf_frame_range'] = self.export_frame_range
|
||||||
export_settings['gltf_force_sampling'] = self.export_force_sampling
|
export_settings['gltf_force_sampling'] = self.export_force_sampling
|
||||||
|
export_settings['gltf_sampling_interpolation_fallback'] = self.export_sampling_interpolation_fallback
|
||||||
if not self.export_force_sampling:
|
if not self.export_force_sampling:
|
||||||
export_settings['gltf_def_bones'] = False
|
export_settings['gltf_def_bones'] = False
|
||||||
export_settings['gltf_bake_animation'] = False
|
export_settings['gltf_bake_animation'] = False
|
||||||
|
@ -1708,6 +1718,7 @@ def export_panel_animation_sampling(layout, operator):
|
||||||
body.active = operator.export_animations and operator.export_force_sampling
|
body.active = operator.export_animations and operator.export_force_sampling
|
||||||
|
|
||||||
body.prop(operator, 'export_frame_step')
|
body.prop(operator, 'export_frame_step')
|
||||||
|
body.prop(operator, 'export_sampling_interpolation_fallback')
|
||||||
|
|
||||||
|
|
||||||
def export_panel_animation_pointer(layout, operator):
|
def export_panel_animation_pointer(layout, operator):
|
||||||
|
|
|
@ -195,12 +195,12 @@ def get_attribute(attributes, name, data_type, domain):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_gltf_interpolation(interpolation):
|
def get_gltf_interpolation(interpolation, export_settings):
|
||||||
return {
|
return {
|
||||||
"BEZIER": "CUBICSPLINE",
|
"BEZIER": "CUBICSPLINE",
|
||||||
"LINEAR": "LINEAR",
|
"LINEAR": "LINEAR",
|
||||||
"CONSTANT": "STEP"
|
"CONSTANT": "STEP"
|
||||||
}.get(interpolation, "LINEAR")
|
}.get(interpolation, export_settings['gltf_sampling_interpolation_fallback']) # If unknown, default to the mode choosen by the user
|
||||||
|
|
||||||
|
|
||||||
def get_anisotropy_rotation_gltf_to_blender(rotation):
|
def get_anisotropy_rotation_gltf_to_blender(rotation):
|
||||||
|
|
|
@ -577,11 +577,11 @@ def gather_action_animations(obj_uuid: int,
|
||||||
blender_action.name,
|
blender_action.name,
|
||||||
slot.slot.handle,
|
slot.slot.handle,
|
||||||
True,
|
True,
|
||||||
get_gltf_interpolation("LINEAR"),
|
get_gltf_interpolation(export_settings['gltf_sampling_interpolation_fallback'], export_settings),
|
||||||
export_settings)
|
export_settings)
|
||||||
elif type_ == "OBJECT":
|
elif type_ == "OBJECT":
|
||||||
channel = gather_sampled_object_channel(
|
channel = gather_sampled_object_channel(
|
||||||
obj_uuid, prop, blender_action.name, slot.slot.handle, True, get_gltf_interpolation("LINEAR"), export_settings)
|
obj_uuid, prop, blender_action.name, slot.slot.handle, True, get_gltf_interpolation(export_settings['gltf_sampling_interpolation_fallback'], export_settings), export_settings)
|
||||||
elif type_ == "SK":
|
elif type_ == "SK":
|
||||||
channel = gather_sampled_sk_channel(obj_uuid, blender_action.name, slot.slot.handle, export_settings)
|
channel = gather_sampled_sk_channel(obj_uuid, blender_action.name, slot.slot.handle, export_settings)
|
||||||
elif type_ == "EXTRA": # TODOSLOT slot-3
|
elif type_ == "EXTRA": # TODOSLOT slot-3
|
||||||
|
|
|
@ -228,4 +228,4 @@ def __gather_interpolation(
|
||||||
blender_keyframe = [c for c in channel_group if c is not None][0].keyframe_points[0]
|
blender_keyframe = [c for c in channel_group if c is not None][0].keyframe_points[0]
|
||||||
|
|
||||||
# Select the interpolation method.
|
# Select the interpolation method.
|
||||||
return get_gltf_interpolation(blender_keyframe.interpolation)
|
return get_gltf_interpolation(blender_keyframe.interpolation, export_settings)
|
||||||
|
|
|
@ -38,7 +38,7 @@ def gather_armature_sampled_channels(armature_uuid, blender_action_name, slot_ha
|
||||||
for chan in [chan for chan in channels_animated.values() if chan['bone'] is not None]:
|
for chan in [chan for chan in channels_animated.values() if chan['bone'] is not None]:
|
||||||
for prop in chan['properties'].keys():
|
for prop in chan['properties'].keys():
|
||||||
list_of_animated_bone_channels[(chan['bone'], get_channel_from_target(get_target(prop)))] = get_gltf_interpolation(
|
list_of_animated_bone_channels[(chan['bone'], get_channel_from_target(get_target(prop)))] = get_gltf_interpolation(
|
||||||
chan['properties'][prop][0].keyframe_points[0].interpolation) # Could be exported without sampling : keep interpolation
|
chan['properties'][prop][0].keyframe_points[0].interpolation, export_settings) # Could be exported without sampling : keep interpolation
|
||||||
|
|
||||||
for _, _, chan_prop, chan_bone in [chan for chan in to_be_sampled if chan[1] == "BONE"]:
|
for _, _, chan_prop, chan_bone in [chan for chan in to_be_sampled if chan[1] == "BONE"]:
|
||||||
list_of_animated_bone_channels[
|
list_of_animated_bone_channels[
|
||||||
|
@ -46,7 +46,7 @@ def gather_armature_sampled_channels(armature_uuid, blender_action_name, slot_ha
|
||||||
chan_bone,
|
chan_bone,
|
||||||
chan_prop,
|
chan_prop,
|
||||||
)
|
)
|
||||||
] = get_gltf_interpolation("LINEAR") # if forced to be sampled, keep LINEAR interpolation
|
] = get_gltf_interpolation(export_settings['gltf_sampling_interpolation_fallback'], export_settings) # if forced to be sampled, keep the interpolation chosen by the user
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
# There is no animated channels (because if it was, we would have a slot_handle)
|
# There is no animated channels (because if it was, we would have a slot_handle)
|
||||||
|
@ -61,12 +61,12 @@ def gather_armature_sampled_channels(armature_uuid, blender_action_name, slot_ha
|
||||||
blender_action_name,
|
blender_action_name,
|
||||||
slot_handle,
|
slot_handle,
|
||||||
(bone, p) in list_of_animated_bone_channels.keys(),
|
(bone, p) in list_of_animated_bone_channels.keys(),
|
||||||
list_of_animated_bone_channels[(bone, p)] if (bone, p) in list_of_animated_bone_channels.keys() else get_gltf_interpolation("LINEAR"),
|
list_of_animated_bone_channels[(bone, p)] if (bone, p) in list_of_animated_bone_channels.keys() else get_gltf_interpolation(export_settings['gltf_sampling_interpolation_fallback'], export_settings),
|
||||||
export_settings)
|
export_settings)
|
||||||
if channel is not None:
|
if channel is not None:
|
||||||
channels.append(channel)
|
channels.append(channel)
|
||||||
|
|
||||||
bake_interpolation = get_gltf_interpolation("LINEAR")
|
bake_interpolation = get_gltf_interpolation(export_settings['gltf_sampling_interpolation_fallback'], export_settings)
|
||||||
# Retrieve animation on armature object itself, if any
|
# Retrieve animation on armature object itself, if any
|
||||||
if blender_action_name == armature_uuid or export_settings['gltf_animation_mode'] in ["SCENE", "NLA_TRACKS"]:
|
if blender_action_name == armature_uuid or export_settings['gltf_animation_mode'] in ["SCENE", "NLA_TRACKS"]:
|
||||||
# If armature is baked (no animation of armature), need to use all channels
|
# If armature is baked (no animation of armature), need to use all channels
|
||||||
|
@ -211,7 +211,7 @@ def __gather_armature_object_channel(obj_uuid: str, blender_action, slot_handle,
|
||||||
"delta_rotation_euler": "rotation_quaternion",
|
"delta_rotation_euler": "rotation_quaternion",
|
||||||
"delta_rotation_quaternion": "rotation_quaternion"
|
"delta_rotation_quaternion": "rotation_quaternion"
|
||||||
}.get(c),
|
}.get(c),
|
||||||
get_gltf_interpolation(inter)
|
get_gltf_interpolation(inter, export_settings)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ def __gather_armature_object_channel(obj_uuid: str, blender_action, slot_handle,
|
||||||
"delta_rotation_euler": "rotation_quaternion",
|
"delta_rotation_euler": "rotation_quaternion",
|
||||||
"delta_rotation_quaternion": "rotation_quaternion"
|
"delta_rotation_quaternion": "rotation_quaternion"
|
||||||
}.get(c[2]),
|
}.get(c[2]),
|
||||||
get_gltf_interpolation("LINEAR") # Forced to be sampled, so use LINEAR
|
get_gltf_interpolation(export_settings['gltf_sampling_interpolation_fallback'], export_settings) # Forced to be sampled, so use the interpolation chosen by the user
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -212,15 +212,15 @@ def __convert_keyframes(armature_uuid, bone_name, channel, keyframes, action_nam
|
||||||
def __gather_interpolation(node_channel_is_animated, node_channel_interpolation, keyframes, export_settings):
|
def __gather_interpolation(node_channel_is_animated, node_channel_interpolation, keyframes, export_settings):
|
||||||
|
|
||||||
if len(keyframes) > 2:
|
if len(keyframes) > 2:
|
||||||
# keep STEP as STEP, other become LINEAR
|
# keep STEP as STEP, other become the interpolation choosen by the user
|
||||||
return {
|
return {
|
||||||
"STEP": "STEP"
|
"STEP": "STEP"
|
||||||
}.get(node_channel_interpolation, "LINEAR")
|
}.get(node_channel_interpolation, export_settings['gltf_sampling_interpolation_fallback'])
|
||||||
elif len(keyframes) == 1:
|
elif len(keyframes) == 1:
|
||||||
if node_channel_is_animated is False:
|
if node_channel_is_animated is False:
|
||||||
return "STEP"
|
return "STEP"
|
||||||
elif node_channel_interpolation == "CUBICSPLINE":
|
elif node_channel_interpolation == "CUBICSPLINE":
|
||||||
return "LINEAR" # We can't have a single keyframe with CUBICSPLINE
|
return export_settings['gltf_sampling_interpolation_fallback'] # We can't have a single keyframe with CUBICSPLINE
|
||||||
else:
|
else:
|
||||||
return node_channel_interpolation
|
return node_channel_interpolation
|
||||||
else:
|
else:
|
||||||
|
@ -232,4 +232,4 @@ def __gather_interpolation(node_channel_is_animated, node_channel_interpolation,
|
||||||
if keyframes[0].value == keyframes[1].value:
|
if keyframes[0].value == keyframes[1].value:
|
||||||
return "STEP"
|
return "STEP"
|
||||||
else:
|
else:
|
||||||
return "LINEAR"
|
return export_settings['gltf_sampling_interpolation_fallback']
|
||||||
|
|
|
@ -31,7 +31,7 @@ def gather_data_sampled_channels(blender_type_data, blender_id, blender_action_n
|
||||||
blender_action_name,
|
blender_action_name,
|
||||||
slot_handle,
|
slot_handle,
|
||||||
path in list_of_animated_data_channels.keys(),
|
path in list_of_animated_data_channels.keys(),
|
||||||
list_of_animated_data_channels[path] if path in list_of_animated_data_channels.keys() else get_gltf_interpolation("LINEAR"),
|
list_of_animated_data_channels[path] if path in list_of_animated_data_channels.keys() else get_gltf_interpolation(export_settings['gltf_sampling_interpolation_fallback'], export_settings),
|
||||||
additional_key,
|
additional_key,
|
||||||
export_settings)
|
export_settings)
|
||||||
if channel is not None:
|
if channel is not None:
|
||||||
|
|
|
@ -133,7 +133,7 @@ def __gather_interpolation(
|
||||||
keyframes,
|
keyframes,
|
||||||
export_settings):
|
export_settings):
|
||||||
# TODOPointer
|
# TODOPointer
|
||||||
return 'LINEAR'
|
return export_settings['gltf_sampling_interpolation_fallback']
|
||||||
|
|
||||||
|
|
||||||
def __convert_to_gltf(value):
|
def __convert_to_gltf(value):
|
||||||
|
|
|
@ -30,11 +30,11 @@ def gather_object_sampled_channels(object_uuid: str, blender_action_name: str, s
|
||||||
for chan in [chan for chan in channels_animated.values() if chan['bone'] is None]:
|
for chan in [chan for chan in channels_animated.values() if chan['bone'] is None]:
|
||||||
for prop in chan['properties'].keys():
|
for prop in chan['properties'].keys():
|
||||||
list_of_animated_channels[get_channel_from_target(get_target(prop))] = get_gltf_interpolation(
|
list_of_animated_channels[get_channel_from_target(get_target(prop))] = get_gltf_interpolation(
|
||||||
chan['properties'][prop][0].keyframe_points[0].interpolation) # Could be exported without sampling : keep interpolation
|
chan['properties'][prop][0].keyframe_points[0].interpolation, export_settings) # Could be exported without sampling : keep interpolation
|
||||||
|
|
||||||
for _, _, chan_prop, _ in [chan for chan in to_be_sampled if chan[1] == "OBJECT"]:
|
for _, _, chan_prop, _ in [chan for chan in to_be_sampled if chan[1] == "OBJECT"]:
|
||||||
list_of_animated_channels[chan_prop] = get_gltf_interpolation(
|
list_of_animated_channels[chan_prop] = get_gltf_interpolation(
|
||||||
"LINEAR") # if forced to be sampled, keep LINEAR interpolation
|
export_settings['gltf_sampling_interpolation_fallback'], export_settings) # if forced to be sampled, keep the interpolation choosen by the user
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
# There is no animated channels (because if it was, we would have a slot_handle)
|
# There is no animated channels (because if it was, we would have a slot_handle)
|
||||||
|
@ -47,7 +47,7 @@ def gather_object_sampled_channels(object_uuid: str, blender_action_name: str, s
|
||||||
blender_action_name,
|
blender_action_name,
|
||||||
slot_handle,
|
slot_handle,
|
||||||
p in list_of_animated_channels.keys(),
|
p in list_of_animated_channels.keys(),
|
||||||
list_of_animated_channels[p] if p in list_of_animated_channels.keys() else get_gltf_interpolation("LINEAR"),
|
list_of_animated_channels[p] if p in list_of_animated_channels.keys() else get_gltf_interpolation(export_settings['gltf_sampling_interpolation_fallback'], export_settings),
|
||||||
export_settings
|
export_settings
|
||||||
)
|
)
|
||||||
if channel is not None:
|
if channel is not None:
|
||||||
|
|
|
@ -119,7 +119,7 @@ def __convert_keyframes(obj_uuid: str, channel: str, keyframes, action_name: str
|
||||||
value = gltf2_blender_math.swizzle_yup(value, channel)
|
value = gltf2_blender_math.swizzle_yup(value, channel)
|
||||||
keyframe_value = gltf2_blender_math.mathutils_to_gltf(value)
|
keyframe_value = gltf2_blender_math.mathutils_to_gltf(value)
|
||||||
|
|
||||||
# No tangents when baking, we are using LINEAR interpolation
|
# No tangents when baking, we are using LINEAR or STEP interpolation
|
||||||
|
|
||||||
values += keyframe_value
|
values += keyframe_value
|
||||||
|
|
||||||
|
@ -152,15 +152,15 @@ def __gather_interpolation(
|
||||||
export_settings):
|
export_settings):
|
||||||
|
|
||||||
if len(keyframes) > 2:
|
if len(keyframes) > 2:
|
||||||
# keep STEP as STEP, other become LINEAR
|
# keep STEP as STEP, other become the interpolation choosen by the user
|
||||||
return {
|
return {
|
||||||
"STEP": "STEP"
|
"STEP": "STEP"
|
||||||
}.get(node_channel_interpolation, "LINEAR")
|
}.get(node_channel_interpolation, export_settings['gltf_sampling_interpolation_fallback'])
|
||||||
elif len(keyframes) == 1:
|
elif len(keyframes) == 1:
|
||||||
if node_channel_is_animated is False:
|
if node_channel_is_animated is False:
|
||||||
return "STEP"
|
return "STEP"
|
||||||
elif node_channel_interpolation == "CUBICSPLINE":
|
elif node_channel_interpolation == "CUBICSPLINE":
|
||||||
return "LINEAR" # We can't have a single keyframe with CUBICSPLINE
|
return export_settings['gltf_sampling_interpolation_fallback'] # We can't have a single keyframe with CUBICSPLINE
|
||||||
else:
|
else:
|
||||||
return node_channel_interpolation
|
return node_channel_interpolation
|
||||||
else:
|
else:
|
||||||
|
@ -172,4 +172,4 @@ def __gather_interpolation(
|
||||||
if keyframes[0].value == keyframes[1].value:
|
if keyframes[0].value == keyframes[1].value:
|
||||||
return "STEP"
|
return "STEP"
|
||||||
else:
|
else:
|
||||||
return "LINEAR"
|
return export_settings['gltf_sampling_interpolation_fallback']
|
||||||
|
|
|
@ -113,4 +113,4 @@ def __convert_keyframes(obj_uuid, keyframes, action_name: str, export_settings):
|
||||||
|
|
||||||
def __gather_interpolation(export_settings):
|
def __gather_interpolation(export_settings):
|
||||||
# TODO: check if the SK was animated with CONSTANT
|
# TODO: check if the SK was animated with CONSTANT
|
||||||
return 'LINEAR'
|
return export_settings['gltf_sampling_interpolation_fallback']
|
||||||
|
|
Loading…
Reference in a new issue