def get_bone_matrix(blender_obj_uuid_if_armature: typing.Optional[str], channels: typing.Tuple[bpy.types.FCurve], bake_bone: typing.Union[str, None], bake_channel: typing.Union[str, None], bake_range_start, bake_range_end, action_name: str, current_frame: int, step: int, export_settings ): blender_object_if_armature = export_settings['vtree'].nodes[blender_obj_uuid_if_armature].blender_object if blender_obj_uuid_if_armature is not None else None data = {} # Always using bake_range, because some bones may need to be baked, # even if user didn't request it start_frame = bake_range_start end_frame = bake_range_end frame = start_frame while frame <= end_frame: data[frame] = {} bpy.context.scene.frame_set(int(frame)) bones = export_settings['vtree'].get_all_bones(blender_obj_uuid_if_armature) for bone_uuid in bones: blender_bone = export_settings['vtree'].nodes[bone_uuid].blender_bone if export_settings['vtree'].nodes[bone_uuid].parent_uuid is not None and export_settings['vtree'].nodes[export_settings['vtree'].nodes[bone_uuid].parent_uuid].blender_type == VExportNode.BONE: blender_bone_parent = export_settings['vtree'].nodes[export_settings['vtree'].nodes[bone_uuid].parent_uuid].blender_bone rest_mat = blender_bone_parent.bone.matrix_local.inverted_safe() @ blender_bone.bone.matrix_local matrix = rest_mat.inverted_safe() @ blender_bone_parent.matrix.inverted_safe() @ blender_bone.matrix else: if blender_bone.parent is None: matrix = blender_bone.bone.matrix_local.inverted_safe() @ blender_bone.matrix else: # Bone has a parent, but in export, after filter, is at root of armature matrix = blender_bone.matrix.copy() data[frame][blender_bone.name] = matrix # If some drivers must be evaluated, do it here, to avoid to have to change frame by frame later drivers_to_manage = get_sk_drivers(blender_obj_uuid_if_armature, export_settings) for dr_obj_uuid, dr_fcurves in drivers_to_manage: vals = get_sk_driver_values(dr_obj_uuid, frame, dr_fcurves, export_settings) frame += step return data
def get_bone_matrix( blender_object_if_armature: typing.Optional[bpy.types.Object], channels: typing.Tuple[bpy.types.FCurve], bake_bone: typing.Union[str, None], bake_channel: typing.Union[str, None], bake_range_start, bake_range_end, action_name: str, current_frame: int, step: int): data = {} # Always using bake_range, because some bones may need to be baked, # even if user didn't request it start_frame = bake_range_start end_frame = bake_range_end frame = start_frame while frame <= end_frame: data[frame] = {} # we need to bake in the constraints bpy.context.scene.frame_set(frame) for pbone in blender_object_if_armature.pose.bones: if bake_bone is None: matrix = pbone.matrix_basis.copy() else: if (pbone.bone.use_inherit_rotation == False or pbone.bone.inherit_scale != "FULL" ) and pbone.parent != None: rest_mat = (pbone.parent.bone.matrix_local.inverted_safe() @ pbone.bone.matrix_local) matrix = ( rest_mat.inverted_safe() @ pbone.parent.matrix.inverted_safe() @ pbone.matrix) else: matrix = pbone.matrix matrix = blender_object_if_armature.convert_space( pose_bone=pbone, matrix=matrix, from_space='POSE', to_space='LOCAL') data[frame][pbone.name] = matrix # If some drivers must be evaluated, do it here, to avoid to have to change frame by frame later obj_driver = blender_object_if_armature.proxy if blender_object_if_armature.proxy else blender_object_if_armature drivers_to_manage = get_sk_drivers(obj_driver) for dr_obj, dr_fcurves in drivers_to_manage: vals = get_sk_driver_values(dr_obj, frame, dr_fcurves) frame += step return data
def get_bone_matrix( blender_object_if_armature: typing.Optional[bpy.types.Object], channels: typing.Tuple[bpy.types.FCurve], bake_bone: typing.Union[str, None], bake_channel: typing.Union[str, None], bake_range_start, bake_range_end, action_name: str, current_frame: int, step: int): data = {} # Always using bake_range, because some bones may need to be baked, # even if user didn't request it start_frame = bake_range_start end_frame = bake_range_end frame = start_frame while frame <= end_frame: data[frame] = {} # we need to bake in the constraints bpy.context.scene.frame_set(frame) for pbone in blender_object_if_armature.pose.bones: if bake_bone is None: matrix = pbone.matrix_basis else: matrix = pbone.matrix if bpy.app.version < (2, 80, 0): matrix = blender_object_if_armature.convert_space( pbone, matrix, 'POSE', 'LOCAL') else: matrix = blender_object_if_armature.convert_space( pose_bone=pbone, matrix=matrix, from_space='POSE', to_space='LOCAL') data[frame][pbone.name] = matrix # If some drivers must be evaluated, do it here, to avoid to have to change frame by frame later drivers_to_manage = get_sk_drivers(blender_object_if_armature) for dr_obj, dr_fcurves in drivers_to_manage: vals = get_sk_driver_values(dr_obj, frame, dr_fcurves) frame += step return data
def gather_animation_channels(blender_action: bpy.types.Action, blender_object: bpy.types.Object, export_settings ) -> typing.List[gltf2_io.AnimationChannel]: channels = [] # First calculate range of animation for baking # This is need if user set 'Force sampling' and in case we need to bake bake_range_start = None bake_range_end = None groups = __get_channel_groups(blender_action, blender_object, export_settings) # Note: channels has some None items only for SK if some SK are not animated for chans in groups: ranges = [channel.range() for channel in chans if channel is not None] if bake_range_start is None: bake_range_start = min([channel.range()[0] for channel in chans if channel is not None]) else: bake_range_start = min(bake_range_start, min([channel.range()[0] for channel in chans if channel is not None])) if bake_range_end is None: bake_range_end = max([channel.range()[1] for channel in chans if channel is not None]) else: bake_range_end = max(bake_range_end, max([channel.range()[1] for channel in chans if channel is not None])) if blender_object.type == "ARMATURE" and export_settings['gltf_force_sampling'] is True: # We have to store sampled animation data for every deformation bones # Check that there are some anim in this action if bake_range_start is None: return [] # Then bake all bones bones_to_be_animated = [] if export_settings["gltf_def_bones"] is False: bones_to_be_animated = blender_object.data.bones else: bones_to_be_animated, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object) bones_to_be_animated = [blender_object.pose.bones[b.name] for b in bones_to_be_animated] for bone in bones_to_be_animated: for p in ["location", "rotation_quaternion", "scale"]: channel = __gather_animation_channel( (), blender_object, export_settings, bone.name, p, bake_range_start, bake_range_end, blender_action.name, None) channels.append(channel) # Retrieve animation on armature object itself, if any fcurves_armature = __gather_armature_object_channel_groups(blender_action, blender_object, export_settings) for channel_group in fcurves_armature: # No need to sort on armature, that can't have SK if len(channel_group) == 0: # Only errors on channels, ignoring continue channel = __gather_animation_channel(channel_group, blender_object, export_settings, None, None, bake_range_start, bake_range_end, blender_action.name, None) if channel is not None: channels.append(channel) # Retrieve channels for drivers, if needed drivers_to_manage = gltf2_blender_gather_drivers.get_sk_drivers(blender_object) for obj, fcurves in drivers_to_manage: channel = __gather_animation_channel( fcurves, blender_object, export_settings, None, None, bake_range_start, bake_range_end, blender_action.name, obj) channels.append(channel) else: for channel_group in __get_channel_groups(blender_action, blender_object, export_settings): channel_group_sorted = __get_channel_group_sorted(channel_group, blender_object) if len(channel_group_sorted) == 0: # Only errors on channels, ignoring continue channel = __gather_animation_channel(channel_group_sorted, blender_object, export_settings, None, None, bake_range_start, bake_range_end, blender_action.name, None) if channel is not None: channels.append(channel) # resetting driver caches gltf2_blender_gather_drivers.get_sk_driver_values.reset_cache() gltf2_blender_gather_drivers.get_sk_drivers.reset_cache() return channels
def gather_animation_channels( obj_uuid: int, blender_action: bpy.types.Action, export_settings) -> typing.List[gltf2_io.AnimationChannel]: channels = [] blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object # First calculate range of animation for baking # This is need if user set 'Force sampling' and in case we need to bake bake_range_start = None bake_range_end = None force_range = False # If range is manually set, use it. Else, calculate it if blender_action.use_frame_range is True: bake_range_start = blender_action.frame_start bake_range_end = blender_action.frame_end force_range = True # keyframe_points is read-only, we cant restrict here else: groups = __get_channel_groups(blender_action, blender_object, export_settings) # Note: channels has some None items only for SK if some SK are not animated for chans in groups: ranges = [ channel.range() for channel in chans if channel is not None ] if bake_range_start is None: bake_range_start = min([ channel.range()[0] for channel in chans if channel is not None ]) else: bake_range_start = min( bake_range_start, min([ channel.range()[0] for channel in chans if channel is not None ])) if bake_range_end is None: bake_range_end = max([ channel.range()[1] for channel in chans if channel is not None ]) else: bake_range_end = max( bake_range_end, max([ channel.range()[1] for channel in chans if channel is not None ])) if blender_object.type == "ARMATURE" and export_settings[ 'gltf_force_sampling'] is True: # We have to store sampled animation data for every deformation bones # Check that there are some anim in this action if bake_range_start is None: return [] # Then bake all bones bones_to_be_animated = [] bones_uuid = export_settings["vtree"].get_all_bones(obj_uuid) bones_to_be_animated = [ blender_object.pose.bones[ export_settings["vtree"].nodes[b].blender_bone.name] for b in bones_uuid ] list_of_animated_bone_channels = [] for channel_group in __get_channel_groups(blender_action, blender_object, export_settings): channel_group_sorted = __get_channel_group_sorted( channel_group, blender_object) list_of_animated_bone_channels.extend([ (gltf2_blender_get.get_object_from_datapath( blender_object, get_target_object_path(i.data_path)).name, get_target_property_name(i.data_path)) for i in channel_group ]) for bone in bones_to_be_animated: for p in ["location", "rotation_quaternion", "scale"]: channel = gather_animation_channel( obj_uuid, (), export_settings, bone.name, p, bake_range_start, bake_range_end, force_range, blender_action.name, None, (bone.name, p) in list_of_animated_bone_channels) if channel is not None: channels.append(channel) # Retrieve animation on armature object itself, if any fcurves_armature = __gather_armature_object_channel_groups( blender_action, blender_object, export_settings) for channel_group in fcurves_armature: # No need to sort on armature, that can't have SK if len(channel_group) == 0: # Only errors on channels, ignoring continue channel = gather_animation_channel(obj_uuid, channel_group, export_settings, None, None, bake_range_start, bake_range_end, force_range, blender_action.name, None, True) if channel is not None: channels.append(channel) # Retrieve channels for drivers, if needed drivers_to_manage = gltf2_blender_gather_drivers.get_sk_drivers( obj_uuid, export_settings) for obj_driver_uuid, fcurves in drivers_to_manage: channel = gather_animation_channel(obj_uuid, fcurves, export_settings, None, None, bake_range_start, bake_range_end, force_range, blender_action.name, obj_driver_uuid, True) if channel is not None: channels.append(channel) else: done_paths = [] for channel_group in __get_channel_groups(blender_action, blender_object, export_settings): channel_group_sorted = __get_channel_group_sorted( channel_group, blender_object) if len(channel_group_sorted) == 0: # Only errors on channels, ignoring continue channel = gather_animation_channel(obj_uuid, channel_group_sorted, export_settings, None, None, bake_range_start, bake_range_end, force_range, blender_action.name, None, True) if channel is not None: channels.append(channel) # Store already done channel path target = [c for c in channel_group_sorted if c is not None][0].data_path.split('.')[-1] path = { "delta_location": "location", "delta_rotation_euler": "rotation_quaternion", "location": "location", "rotation_axis_angle": "rotation_quaternion", "rotation_euler": "rotation_quaternion", "rotation_quaternion": "rotation_quaternion", "scale": "scale", "value": "weights" }.get(target) if path is not None: done_paths.append(path) done_paths = list(set(done_paths)) if export_settings['gltf_selected'] is True and export_settings[ 'vtree'].tree_troncated is True: start_frame = min( [v[0] for v in [a.frame_range for a in bpy.data.actions]]) end_frame = max( [v[1] for v in [a.frame_range for a in bpy.data.actions]]) to_be_done = ['location', 'rotation_quaternion', 'scale'] to_be_done = [c for c in to_be_done if c not in done_paths] # In case of weight action, do nothing. # If there is only weight --> TRS is already managed at first if not (len(done_paths) == 1 and 'weights' in done_paths): for p in to_be_done: channel = gather_animation_channel( obj_uuid, (), export_settings, None, p, start_frame, end_frame, force_range, blender_action.name, None, False #If Object is not animated, don't keep animation for this channel ) if channel is not None: channels.append(channel) # resetting driver caches gltf2_blender_gather_drivers.get_sk_driver_values.reset_cache() gltf2_blender_gather_drivers.get_sk_drivers.reset_cache() # resetting bone caches gltf2_blender_gather_animation_sampler_keyframes.get_bone_matrix.reset_cache( ) return channels