animation.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. from .logger import *
  2. from .package_level import *
  3. import bpy
  4. FRAME_BASED_ANIMATION = True # turn off for diagnostics; only actual keyframes will be written for skeleton animation
  5. # passed to Animation constructor from animatable objects, defined in BABYLON.Animation
  6. #ANIMATIONTYPE_FLOAT = 0
  7. ANIMATIONTYPE_VECTOR3 = 1
  8. ANIMATIONTYPE_QUATERNION = 2
  9. ANIMATIONTYPE_MATRIX = 3
  10. #ANIMATIONTYPE_COLOR3 = 4
  11. # passed to Animation constructor from animatable objects, defined in BABYLON.Animation
  12. #ANIMATIONLOOPMODE_RELATIVE = 0
  13. ANIMATIONLOOPMODE_CYCLE = 1
  14. #ANIMATIONLOOPMODE_CONSTANT = 2
  15. #===============================================================================
  16. class AnimationRange:
  17. # constructor called by the static actionPrep method
  18. def __init__(self, name, frames, frameOffset):
  19. # process input args to members
  20. self.name = name
  21. self.frames_in = frames
  22. self.frame_start = AnimationRange.nextStartingFrame(frameOffset)
  23. self.frames_out = []
  24. for frame in self.frames_in:
  25. self.frames_out.append(self.frame_start + frame)
  26. highest_idx = len(self.frames_in) - 1
  27. self.highest_frame_in = self.frames_in [highest_idx]
  28. self.frame_end = self.frames_out[highest_idx]
  29. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  30. def to_string(self):
  31. return self.name + ': ' + ' in[' + format_int(self.frames_in[0]) + ' - ' + format_int(self.highest_frame_in) + '], out[' + format_int(self.frame_start) + ' - ' + format_int(self.frame_end) + ']'
  32. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  33. def to_scene_file(self, file_handler):
  34. file_handler.write('{')
  35. write_string(file_handler, 'name', self.name, True)
  36. write_int(file_handler, 'from', self.frame_start)
  37. write_int(file_handler, 'to', self.frame_end)
  38. file_handler.write('}')
  39. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  40. @staticmethod
  41. def actionPrep(object, action, includeAllFrames, frameOffset):
  42. # when name in format of object-action, verify object's name matches
  43. if action.name.find('-') > 0:
  44. split = action.name.partition('-')
  45. if split[0] != object.name: return None
  46. actionName = split[2]
  47. else:
  48. actionName = action.name
  49. # assign the action to the object
  50. object.animation_data.action = action
  51. if includeAllFrames:
  52. frame_start = int(action.frame_range[0])
  53. frame_end = int(action.frame_range[1])
  54. frames = range(frame_start, frame_end + 1) # range is not inclusive with 2nd arg
  55. else:
  56. # capture built up from fcurves
  57. frames = dict()
  58. for fcurve in object.animation_data.action.fcurves:
  59. for key in fcurve.keyframe_points:
  60. frame = key.co.x
  61. frames[frame] = True
  62. frames = sorted(frames)
  63. return AnimationRange(actionName, frames, frameOffset)
  64. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  65. @staticmethod
  66. def nextStartingFrame(frameOffset):
  67. if frameOffset == 0: return 0
  68. # ensure a gap of at least 5 frames, starting on an even multiple of 10
  69. frameOffset += 4
  70. remainder = frameOffset % 10
  71. return frameOffset + 10 - remainder
  72. #===============================================================================
  73. class Animation:
  74. def __init__(self, dataType, loopBehavior, name, propertyInBabylon, attrInBlender = None, mult = 1, xOffset = 0):
  75. self.dataType = dataType
  76. self.framePerSecond = bpy.context.scene.render.fps
  77. self.loopBehavior = loopBehavior
  78. self.name = name
  79. self.propertyInBabylon = propertyInBabylon
  80. # these never get used by Bones, so optional in contructor args
  81. self.attrInBlender = attrInBlender
  82. self.mult = mult
  83. self.xOffset = xOffset
  84. #keys
  85. self.frames = []
  86. self.values = [] # vector3 for ANIMATIONTYPE_VECTOR3 & matrices for ANIMATIONTYPE_MATRIX
  87. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  88. # a separate method outside of constructor, so can be called once for each Blender Action object participates in
  89. def append_range(self, object, animationRange):
  90. # action already assigned, always using poses, not every frame, build up again filtering by attrInBlender
  91. for idx in range(len(animationRange.frames_in)):
  92. bpy.context.scene.frame_set(animationRange.frames_in[idx])
  93. self.frames.append(animationRange.frames_out[idx])
  94. self.values.append(self.get_attr(object))
  95. return len(animationRange.frames_in) > 0
  96. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  97. # for auto animate
  98. def get_first_frame(self):
  99. return self.frames[0] if len(self.frames) > 0 else -1
  100. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  101. # for auto animate
  102. def get_last_frame(self):
  103. return self.frames[len(self.frames) - 1] if len(self.frames) > 0 else -1
  104. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  105. def to_scene_file(self, file_handler):
  106. file_handler.write('{')
  107. write_int(file_handler, 'dataType', self.dataType, True)
  108. write_int(file_handler, 'framePerSecond', self.framePerSecond)
  109. file_handler.write(',"keys":[')
  110. first = True
  111. for frame_idx in range(len(self.frames)):
  112. if first != True:
  113. file_handler.write(',')
  114. first = False
  115. file_handler.write('\n{')
  116. write_int(file_handler, 'frame', self.frames[frame_idx], True)
  117. value_idx = self.values[frame_idx]
  118. if self.dataType == ANIMATIONTYPE_MATRIX:
  119. write_matrix4(file_handler, 'values', value_idx)
  120. elif self.dataType == ANIMATIONTYPE_QUATERNION:
  121. write_quaternion(file_handler, 'values', value_idx)
  122. else:
  123. write_vector(file_handler, 'values', value_idx)
  124. file_handler.write('}')
  125. file_handler.write(']') # close keys
  126. # put this at the end to make less crazy looking ]}]]]}}}}}}}]]]],
  127. # since animation is also at the end of the bone, mesh, camera, or light
  128. write_int(file_handler, 'loopBehavior', self.loopBehavior)
  129. write_string(file_handler, 'name', self.name)
  130. write_string(file_handler, 'property', self.propertyInBabylon)
  131. file_handler.write('}')
  132. #===============================================================================
  133. class VectorAnimation(Animation):
  134. def __init__(self, object, propertyInBabylon, attrInBlender, mult = 1, xOffset = 0):
  135. super().__init__(ANIMATIONTYPE_VECTOR3, ANIMATIONLOOPMODE_CYCLE, propertyInBabylon + ' animation', propertyInBabylon, attrInBlender, mult, xOffset)
  136. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  137. def get_attr(self, object):
  138. return scale_vector(getattr(object, self.attrInBlender), self.mult, self.xOffset)
  139. #===============================================================================
  140. class QuaternionAnimation(Animation):
  141. def __init__(self, object, propertyInBabylon, attrInBlender, mult = 1, xOffset = 0):
  142. super().__init__(ANIMATIONTYPE_QUATERNION, ANIMATIONLOOPMODE_CYCLE, propertyInBabylon + ' animation', propertyInBabylon, attrInBlender, mult, xOffset)
  143. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  144. def get_attr(self, object):
  145. return post_rotate_quaternion(getattr(object, self.attrInBlender), self.xOffset)
  146. #===============================================================================
  147. class QuaternionToEulerAnimation(Animation):
  148. def __init__(self, propertyInBabylon, attrInBlender, mult = 1, xOffset = 0):
  149. super().__init__(ANIMATIONTYPE_VECTOR3, ANIMATIONLOOPMODE_CYCLE, propertyInBabylon + ' animation', propertyInBabylon, attrInBlender, mult, Offset)
  150. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  151. def get_attr(self, object):
  152. quat = getattr(object, self.attrInBlender)
  153. eul = quat.to_euler("XYZ")
  154. return scale_vector(eul, self.mult, self.xOffset)