123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- from .logger import *
- from .package_level import *
- import bpy
- FRAME_BASED_ANIMATION = True # turn off for diagnostics; only actual keyframes will be written for skeleton animation
- # passed to Animation constructor from animatable objects, defined in BABYLON.Animation
- #ANIMATIONTYPE_FLOAT = 0
- ANIMATIONTYPE_VECTOR3 = 1
- ANIMATIONTYPE_QUATERNION = 2
- ANIMATIONTYPE_MATRIX = 3
- #ANIMATIONTYPE_COLOR3 = 4
- # passed to Animation constructor from animatable objects, defined in BABYLON.Animation
- #ANIMATIONLOOPMODE_RELATIVE = 0
- ANIMATIONLOOPMODE_CYCLE = 1
- #ANIMATIONLOOPMODE_CONSTANT = 2
- #===============================================================================
- class AnimationRange:
- # constructor called by the static actionPrep method
- def __init__(self, name, frames, frameOffset):
- # process input args to members
- self.name = name
- self.frames_in = frames
- self.frame_start = AnimationRange.nextStartingFrame(frameOffset)
- self.frames_out = []
- for frame in self.frames_in:
- self.frames_out.append(self.frame_start + frame)
- highest_idx = len(self.frames_in) - 1
- self.highest_frame_in = self.frames_in [highest_idx]
- self.frame_end = self.frames_out[highest_idx]
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def to_string(self):
- 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) + ']'
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def to_scene_file(self, file_handler):
- file_handler.write('{')
- write_string(file_handler, 'name', self.name, True)
- write_int(file_handler, 'from', self.frame_start)
- write_int(file_handler, 'to', self.frame_end)
- file_handler.write('}')
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- @staticmethod
- def actionPrep(object, action, includeAllFrames, frameOffset):
- # when name in format of object-action, verify object's name matches
- if action.name.find('-') > 0:
- split = action.name.partition('-')
- if split[0] != object.name: return None
- actionName = split[2]
- else:
- actionName = action.name
- # assign the action to the object
- object.animation_data.action = action
- if includeAllFrames:
- frame_start = int(action.frame_range[0])
- frame_end = int(action.frame_range[1])
- frames = range(frame_start, frame_end + 1) # range is not inclusive with 2nd arg
- else:
- # capture built up from fcurves
- frames = dict()
- for fcurve in object.animation_data.action.fcurves:
- for key in fcurve.keyframe_points:
- frame = key.co.x
- frames[frame] = True
- frames = sorted(frames)
- return AnimationRange(actionName, frames, frameOffset)
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- @staticmethod
- def nextStartingFrame(frameOffset):
- if frameOffset == 0: return 0
- # ensure a gap of at least 5 frames, starting on an even multiple of 10
- frameOffset += 4
- remainder = frameOffset % 10
- return frameOffset + 10 - remainder
- #===============================================================================
- class Animation:
- def __init__(self, dataType, loopBehavior, name, propertyInBabylon, attrInBlender = None, mult = 1, xOffset = 0):
- self.dataType = dataType
- self.framePerSecond = bpy.context.scene.render.fps
- self.loopBehavior = loopBehavior
- self.name = name
- self.propertyInBabylon = propertyInBabylon
- # these never get used by Bones, so optional in contructor args
- self.attrInBlender = attrInBlender
- self.mult = mult
- self.xOffset = xOffset
- #keys
- self.frames = []
- self.values = [] # vector3 for ANIMATIONTYPE_VECTOR3 & matrices for ANIMATIONTYPE_MATRIX
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- # a separate method outside of constructor, so can be called once for each Blender Action object participates in
- def append_range(self, object, animationRange):
- # action already assigned, always using poses, not every frame, build up again filtering by attrInBlender
- for idx in range(len(animationRange.frames_in)):
- bpy.context.scene.frame_set(animationRange.frames_in[idx])
- self.frames.append(animationRange.frames_out[idx])
- self.values.append(self.get_attr(object))
- return len(animationRange.frames_in) > 0
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- # for auto animate
- def get_first_frame(self):
- return self.frames[0] if len(self.frames) > 0 else -1
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- # for auto animate
- def get_last_frame(self):
- return self.frames[len(self.frames) - 1] if len(self.frames) > 0 else -1
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def to_scene_file(self, file_handler):
- file_handler.write('{')
- write_int(file_handler, 'dataType', self.dataType, True)
- write_int(file_handler, 'framePerSecond', self.framePerSecond)
- file_handler.write(',"keys":[')
- first = True
- for frame_idx in range(len(self.frames)):
- if first != True:
- file_handler.write(',')
- first = False
- file_handler.write('\n{')
- write_int(file_handler, 'frame', self.frames[frame_idx], True)
- value_idx = self.values[frame_idx]
- if self.dataType == ANIMATIONTYPE_MATRIX:
- write_matrix4(file_handler, 'values', value_idx)
- elif self.dataType == ANIMATIONTYPE_QUATERNION:
- write_quaternion(file_handler, 'values', value_idx)
- else:
- write_vector(file_handler, 'values', value_idx)
- file_handler.write('}')
- file_handler.write(']') # close keys
- # put this at the end to make less crazy looking ]}]]]}}}}}}}]]]],
- # since animation is also at the end of the bone, mesh, camera, or light
- write_int(file_handler, 'loopBehavior', self.loopBehavior)
- write_string(file_handler, 'name', self.name)
- write_string(file_handler, 'property', self.propertyInBabylon)
- file_handler.write('}')
- #===============================================================================
- class VectorAnimation(Animation):
- def __init__(self, object, propertyInBabylon, attrInBlender, mult = 1, xOffset = 0):
- super().__init__(ANIMATIONTYPE_VECTOR3, ANIMATIONLOOPMODE_CYCLE, propertyInBabylon + ' animation', propertyInBabylon, attrInBlender, mult, xOffset)
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def get_attr(self, object):
- return scale_vector(getattr(object, self.attrInBlender), self.mult, self.xOffset)
- #===============================================================================
- class QuaternionAnimation(Animation):
- def __init__(self, object, propertyInBabylon, attrInBlender, mult = 1, xOffset = 0):
- super().__init__(ANIMATIONTYPE_QUATERNION, ANIMATIONLOOPMODE_CYCLE, propertyInBabylon + ' animation', propertyInBabylon, attrInBlender, mult, xOffset)
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def get_attr(self, object):
- return post_rotate_quaternion(getattr(object, self.attrInBlender), self.xOffset)
- #===============================================================================
- class QuaternionToEulerAnimation(Animation):
- def __init__(self, propertyInBabylon, attrInBlender, mult = 1, xOffset = 0):
- super().__init__(ANIMATIONTYPE_VECTOR3, ANIMATIONLOOPMODE_CYCLE, propertyInBabylon + ' animation', propertyInBabylon, attrInBlender, mult, Offset)
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def get_attr(self, object):
- quat = getattr(object, self.attrInBlender)
- eul = quat.to_euler("XYZ")
- return scale_vector(eul, self.mult, self.xOffset)
|