def __gather_animation(blender_action: bpy.types.Action, blender_object: bpy.types.Object, export_settings) -> typing.Optional[gltf2_io.Animation]: if not __filter_animation(blender_action, blender_object, export_settings): return None name = __gather_name(blender_action, blender_object, export_settings) try: animation = gltf2_io.Animation( channels=__gather_channels(blender_action, blender_object, export_settings), extensions=__gather_extensions(blender_action, blender_object, export_settings), extras=__gather_extras(blender_action, blender_object, export_settings), name=name, samplers=__gather_samplers(blender_action, blender_object, export_settings)) except RuntimeError as error: print_console( "WARNING", "Animation '{}' could not be exported. Cause: {}".format( name, error)) return None # To allow reuse of samplers in one animation, __link_samplers(animation, export_settings) if not animation.channels: return None export_user_extensions('gather_animation_hook', export_settings, animation, blender_action, blender_object) return animation
def __merge_animations(blender_scene, animations, export_settings): scene_animation = gltf2_io.Animation( channels=__merge_channels(animations), extensions=__merge_extensions(animations), extras=__merge_extras(animations), name=blender_scene.name, samplers=__merge_samplers(animations)) return scene_animation
def gather_animations(blender_object, export_settings): """ Gather all animations which contribute to the objects property :param blender_object: The blender object which is animated :param export_settings: :return: A list of glTF2 animations """ if not __filter_animation(blender_object, export_settings): return None return gltf2_io.Animation( channels=__gather_channels(blender_object, export_settings), extensions=__gather_extensions(blender_object, export_settings), extras=__gather_extras(blender_object, export_settings), name=__gather_name(blender_object, export_settings), samplers=__gather_samples(blender_object, export_settings))
def __gather_animation(blender_action: bpy.types.Action, blender_object: bpy.types.Object, export_settings) -> typing.Optional[gltf2_io.Animation]: if not __filter_animation(blender_action, blender_object, export_settings): return None animation = gltf2_io.Animation( channels=__gather_channels(blender_action, blender_object, export_settings), extensions=__gather_extensions(blender_action, blender_object, export_settings), extras=__gather_extras(blender_action, blender_object, export_settings), name=__gather_name(blender_action, blender_object, export_settings), samplers=__gather_samplers(blender_action, blender_object, export_settings)) # To allow reuse of samplers in one animation, __link_samplers(animation, export_settings) return animation
def __gather_animation(pre_anim, nodes, export_settings): anim_name, (frame_start, frame_end) = pre_anim print(f'exporting animation track {anim_name}') # Star all the tracks named anim_name. Objects without an anim_name # track are considered unanimated. Star the empty temp track for those. anim_nodes = [] for node in nodes: if node.__blender_data[0] != 'OBJECT' and node.__blender_data[ 0] != 'BONE': continue ob = node.__blender_data[1] if not ob.animation_data: continue for track in ob.animation_data.nla_tracks: if track.name == anim_name: track.is_solo = True if ob.type == 'ARMATURE' and node.__blender_data[0] == 'BONE': # only append bones that are animated in current anim actions = __get_blender_actions(ob) for action in actions: if action[1] == anim_name: for fcurve in action[0].fcurves: bone_path = fcurve.data_path.rpartition('.')[0] bone = ob.path_resolve(bone_path) if node.name == bone.name: if node not in anim_nodes: anim_nodes.append(node) else: anim_nodes.append(node) break else: if node.__blender_data[0] == 'OBJECT': node.__temp_nla_track.is_solo = True f_start = math.floor(frame_start) f_end = math.ceil(frame_end) + 1 f_step = export_settings['gltf_frame_step'] # Stores TRS values for each node at each frame data = {} data['translation'] = [[] for _node in nodes] data['rotation'] = [[] for _node in nodes] data['scale'] = [[] for _node in nodes] for f in range(f_start, f_end, f_step): bpy.context.scene.frame_set(f) for i, node in enumerate(anim_nodes): if node.__blender_data[0] == 'OBJECT': t, r, s = __get_gltf_trs_from_object(node.__blender_data[1], export_settings) elif node.__blender_data[0] == 'BONE': arma_ob = node.__blender_data[1] pbone = arma_ob.pose.bones[node.__blender_data[2]] t, r, s = __get_gltf_trs_from_bone(pbone, export_settings) else: assert False data['translation'][i].append(t) data['rotation'][i].append(r) data['scale'][i].append(s) # Put it all together to get the glTF animation channels = [] samplers = [] for i, node in enumerate(anim_nodes): # Get paths used in the NLA track actions = __get_blender_actions(node.__blender_data[1]) paths = [] pathTypes = { 'delta_location': 'translation', 'delta_rotation_euler': 'rotation', 'location': 'translation', 'rotation_axis_angle': 'rotation', 'rotation_euler': 'rotation', 'rotation_quaternion': 'rotation', 'scale': 'scale' } for action in actions: if action[1] == anim_name: for fcurve in action[0].fcurves: data_path = fcurve.data_path if node.__blender_data[0] == 'OBJECT': paths.append(pathTypes.get(data_path)) else: # for armatures paths.append( pathTypes.get(data_path.rpartition('.')[2])) for path in ['translation', 'rotation', 'scale']: if path in paths: sampler = gltf2_io.AnimationSampler( input=__get_keyframe_accessor(f_start, f_end, f_step), output=__encode_output_accessor(data[path][i], path), interpolation='LINEAR', extensions=None, extras=None, ) samplers.append(sampler) channel = gltf2_io.AnimationChannel( sampler=len(samplers) - 1, target=gltf2_io.AnimationChannelTarget( node=node, path=path, extensions=None, extras=None, ), extensions=None, extras=None, ) channels.append(channel) animation = gltf2_io.Animation( name=anim_name, channels=channels, samplers=samplers, extensions=None, extras=None, ) return animation
def gather_animations( obj_uuid: int, tracks: typing.Dict[str, typing.List[int]], offset: int, export_settings ) -> typing.Tuple[typing.List[gltf2_io.Animation], typing.Dict[ str, typing.List[int]]]: """ Gather all animations which contribute to the objects property, and corresponding track names :param blender_object: The blender object which is animated :param export_settings: :return: A list of glTF2 animations and tracks """ animations = [] blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object # Collect all 'actions' affecting this object. There is a direct mapping between blender actions and glTF animations blender_actions = __get_blender_actions(blender_object, export_settings) if len([a for a in blender_actions if a[2] == "OBJECT"]) == 0: # No TRS animation are found for this object. # But we need to bake, in case we export selection if export_settings[ 'gltf_selected'] is True and blender_object.type != "ARMATURE": channels = __gather_channels_baked(obj_uuid, export_settings) if channels is not None: animation = gltf2_io.Animation( channels=channels, extensions=None, # as other animations extras= None, # Because there is no animation to get extras from name=blender_object. name, # Use object name as animation name samplers=[]) __link_samplers(animation, export_settings) if animation is not None: animations.append(animation) elif export_settings[ 'gltf_selected'] is True and blender_object.type == "ARMATURE": # We need to bake all bones. Because some bone can have some constraints linking to # some other armature bones, for example #TODO pass current_action = None if blender_object.animation_data and blender_object.animation_data.action: current_action = blender_object.animation_data.action # Remove any solo (starred) NLA track. Restored after export solo_track = None if blender_object.animation_data: for track in blender_object.animation_data.nla_tracks: if track.is_solo: solo_track = track track.is_solo = False break # Remove any tweak mode. Restore after export if blender_object.animation_data: restore_tweak_mode = blender_object.animation_data.use_tweak_mode # Export all collected actions. for blender_action, track_name, on_type in blender_actions: # Set action as active, to be able to bake if needed if on_type == "OBJECT": # Not for shapekeys! if blender_object.animation_data.action is None \ or (blender_object.animation_data.action.name != blender_action.name): if blender_object.animation_data.is_property_readonly( 'action'): blender_object.animation_data.use_tweak_mode = False try: blender_object.animation_data.action = blender_action except: error = "Action is readonly. Please check NLA editor" print_console( "WARNING", "Animation '{}' could not be exported. Cause: {}". format(blender_action.name, error)) continue # No need to set active shapekeys animations, this is needed for bone baking animation = __gather_animation(obj_uuid, blender_action, export_settings) if animation is not None: animations.append(animation) # Store data for merging animation later if track_name is not None: # Do not take into account animation not in NLA # Do not take into account default NLA track names if not (track_name.startswith("NlaTrack") or track_name.startswith("[Action Stash]")): if track_name not in tracks.keys(): tracks[track_name] = [] tracks[track_name].append( offset + len(animations) - 1) # Store index of animation in animations # Restore action status # TODO: do this in a finally if blender_object.animation_data: if blender_object.animation_data.action is not None: if current_action is None: # remove last exported action blender_object.animation_data.action = None elif blender_object.animation_data.action.name != current_action.name: # Restore action that was active at start of exporting blender_object.animation_data.action = current_action if solo_track is not None: solo_track.is_solo = True blender_object.animation_data.use_tweak_mode = restore_tweak_mode return animations, tracks