animation_data.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. """Parsing Blender animation_data to create appropriate Godot
  2. AnimationPlayer as well as distribute Blender action into various
  3. action exporting functions"""
  4. import bpy
  5. from .action import (
  6. export_camera_action,
  7. export_shapekey_action,
  8. export_light_action,
  9. export_transform_action
  10. )
  11. from .constraint_baking import (
  12. bake_constraint_to_action,
  13. check_object_constraint,
  14. check_pose_constraint,
  15. action_baking_finalize,
  16. action_baking_initialize
  17. )
  18. from .serializer import get_animation_player
  19. from .action import ActionStrip
  20. ACTION_EXPORTER_MAP = {
  21. 'transform': export_transform_action,
  22. 'shapekey': export_shapekey_action,
  23. 'light': export_light_action,
  24. 'camera': export_camera_action,
  25. }
  26. class ObjectAnimationExporter:
  27. """A helper class holding states while exporting
  28. animation data from a blender object"""
  29. def __init__(self, godot_node, blender_object, action_type):
  30. self.godot_node = godot_node
  31. self.blender_object = blender_object
  32. self.action_exporter_func = ACTION_EXPORTER_MAP[action_type]
  33. self.animation_player = None
  34. self.need_baking = False
  35. self.unmute_nla_tracks = []
  36. self.mute_nla_tracks = []
  37. self.check_baking_condition(action_type)
  38. self.preprocess_nla_tracks(blender_object)
  39. def check_baking_condition(self, action_type):
  40. """Check whether the animated object has any constraint and
  41. thus need to do baking, if needs, some states would be set"""
  42. has_obj_cst = check_object_constraint(self.blender_object)
  43. has_pose_cst = check_pose_constraint(self.blender_object)
  44. self.need_baking = (
  45. action_type == 'transform' and (has_obj_cst or has_pose_cst)
  46. )
  47. def preprocess_nla_tracks(self, blender_object):
  48. """Iterative through nla tracks, separately store mute and unmuted
  49. tracks"""
  50. if blender_object.animation_data:
  51. for nla_track in blender_object.animation_data.nla_tracks:
  52. if not nla_track.strips:
  53. # skip empty tracks
  54. continue
  55. if not nla_track.mute:
  56. self.unmute_nla_tracks.append(nla_track)
  57. else:
  58. self.mute_nla_tracks.append(nla_track)
  59. def bake_to_new_action(self, action_to_bake):
  60. """Baking object and pose constraint altogether.
  61. Note that it accept a action to bake (which would not be modified)
  62. and always return a new created baked actiony"""
  63. return bake_constraint_to_action(
  64. self.blender_object, action_to_bake, False
  65. )
  66. def export_active_action(self, escn_file, active_action):
  67. """Export the active action, if needed, would call bake.
  68. Note that active_action maybe None, which would happen when object has
  69. some constraint (so even no action it is still animated)"""
  70. if self.need_baking:
  71. action_baking_initialize(active_action)
  72. action_active_to_export = self.bake_to_new_action(active_action)
  73. else:
  74. action_active_to_export = active_action
  75. if self.animation_player.active_animation is None:
  76. self.animation_player.add_active_animation_resource(
  77. escn_file, action_active_to_export.name
  78. )
  79. self.action_exporter_func(
  80. self.godot_node,
  81. self.animation_player,
  82. self.blender_object,
  83. ActionStrip(action_active_to_export),
  84. self.animation_player.active_animation
  85. )
  86. if self.need_baking:
  87. # remove new created action
  88. bpy.data.actions.remove(action_active_to_export)
  89. action_baking_finalize(active_action)
  90. else:
  91. # here export unmuted nla_tracks into animation resource,
  92. # this is not needed for baking, as baking has applied to
  93. # active action
  94. for track in self.unmute_nla_tracks:
  95. for strip in track.strips:
  96. if strip.action:
  97. self.action_exporter_func(
  98. self.godot_node,
  99. self.animation_player,
  100. self.blender_object,
  101. ActionStrip(strip),
  102. self.animation_player.active_animation
  103. )
  104. def export_active_action_from_nla(self, escn_file):
  105. """Export all unmute nla_tracks into an active action.
  106. Note that it would not do baking for constraint"""
  107. if self.animation_player.active_animation is None:
  108. self.animation_player.add_active_animation_resource(
  109. escn_file, self.blender_object.name + 'Action'
  110. )
  111. for track in self.unmute_nla_tracks:
  112. for strip in track.strips:
  113. if strip.action:
  114. self.action_exporter_func(
  115. self.godot_node,
  116. self.animation_player,
  117. self.blender_object,
  118. ActionStrip(strip),
  119. self.animation_player.active_animation
  120. )
  121. def export_stashed_track(self, escn_file, stashed_track):
  122. """Export a muted nla_track, track with all its contained action
  123. is exported to a single animation_resource.
  124. It works as an action lib"""
  125. if not stashed_track.strips:
  126. return
  127. # if only one action in nla_track, user may not editted
  128. # nla_track name, thus this would make exported name nicer
  129. if len(stashed_track.strips) > 1:
  130. anim_name = stashed_track.name
  131. else:
  132. anim_name = stashed_track.strips[0].name
  133. anim_resource = self.animation_player.create_animation_resource(
  134. escn_file, anim_name
  135. )
  136. for strip in stashed_track.strips:
  137. if strip.action:
  138. if self.need_baking:
  139. action_baking_initialize(strip.action)
  140. action_to_export = self.bake_to_new_action(strip.action)
  141. else:
  142. action_to_export = strip.action
  143. self.action_exporter_func(
  144. self.godot_node,
  145. self.animation_player,
  146. self.blender_object,
  147. ActionStrip(strip, action_to_export),
  148. anim_resource
  149. )
  150. if self.need_baking:
  151. # remove baked action
  152. bpy.data.actions.remove(action_to_export)
  153. action_baking_finalize(strip.action)
  154. if not self.need_baking:
  155. # if baking, nla_tracks are already baked into strips
  156. for nla_track in self.unmute_nla_tracks:
  157. for strip in nla_track.strips:
  158. if strip.action:
  159. self.action_exporter_func(
  160. self.godot_node,
  161. self.animation_player,
  162. self.blender_object,
  163. ActionStrip(strip),
  164. anim_resource
  165. )
  166. def export_animation_data(escn_file, export_settings, godot_node,
  167. blender_object, action_type):
  168. """Export the action and nla_tracks in blender_object.animation_data,
  169. it will further call the action exporting function in AnimationDataExporter
  170. given by `func_name`"""
  171. if not export_settings['use_export_animation']:
  172. return
  173. anim_exporter = ObjectAnimationExporter(
  174. godot_node, blender_object, action_type
  175. )
  176. if (blender_object.animation_data is None and
  177. not anim_exporter.need_baking):
  178. return
  179. anim_exporter.animation_player = get_animation_player(
  180. escn_file, export_settings, godot_node
  181. )
  182. # back up active action to reset back after finish exporting
  183. if blender_object.animation_data:
  184. active_action = blender_object.animation_data.action
  185. else:
  186. active_action = None
  187. if (active_action is not None or anim_exporter.need_baking):
  188. anim_exporter.export_active_action(escn_file, active_action)
  189. elif anim_exporter.unmute_nla_tracks:
  190. # if has effective nla_tracks but no active action, fake one
  191. anim_exporter.export_active_action_from_nla(escn_file)
  192. # export actions in nla_tracks, each exported to seperate
  193. # animation resources
  194. if export_settings['use_stashed_action']:
  195. for stashed_track in anim_exporter.mute_nla_tracks:
  196. anim_exporter.export_stashed_track(escn_file, stashed_track)
  197. if active_action is not None:
  198. blender_object.animation_data.action = active_action