# This file is part of Korman. # # Korman is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Korman is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Korman. If not, see . import bpy import math import mathutils from PyHSPlasma import * import weakref from . import utils class AnimationConverter: def __init__(self, exporter): self._exporter = weakref.ref(exporter) self._bl_fps = bpy.context.scene.render.fps def convert_action2tm(self, action, default_xform): """Converts a Blender Action to a plCompoundController.""" fcurves = action.fcurves if not fcurves: return None # NOTE: plCompoundController is from Myst 5 and was backported to MOUL. # Worry not however... libHSPlasma will do the conversion for us. tm = plCompoundController() tm.X = self.make_pos_controller(fcurves, default_xform) tm.Y = self.make_rot_controller(fcurves, default_xform) tm.Z = self.make_scale_controller(fcurves, default_xform) return tm def make_matrix44_controller(self, pos_fcurves, scale_fcurves, default_pos, default_scale): pos_keyframes, pos_bez = self._process_keyframes(pos_fcurves) scale_keyframes, scale_bez = self._process_keyframes(scale_fcurves) if not pos_keyframes and not scale_keyframes: return None # Matrix keyframes cannot do bezier schtuff if pos_bez or scale_bez: self._exporter().report.warn("This animation cannot use bezier keyframes--forcing linear", indent=3) # Let's pair up the pos and scale schtuff based on frame numbers. I realize that we're creating # a lot of temporary objects, but until I see profiling results that this is terrible, I prefer # to have code that makes sense. keyframes = [] for pos, scale in zip(pos_keyframes, scale_keyframes): if pos.frame_num == scale.frame_num: keyframes.append((pos, scale)) elif pos.frame_num < scale.frame_num: keyframes.append((pos, None)) keyframes.append((None, scale)) elif pos.frame_num > scale.frame_num: keyframes.append((None, scale)) keyframes.append((pos, None)) # Now we make the controller ctrl = self._make_matrix44_controller(pos_fcurves, scale_fcurves, keyframes, default_pos, default_scale) return ctrl def make_pos_controller(self, fcurves, default_xform): pos_curves = [i for i in fcurves if i.data_path == "location" and i.keyframe_points] keyframes, bez_chans = self._process_keyframes(pos_curves) if not keyframes: return None # At one point, I had some... insanity here to try to crush bezier channels and hand off to # blah blah blah... As it turns out, point3 keyframe's tangents are vector3s :) ctrl = self._make_point3_controller(pos_curves, keyframes, bez_chans, default_xform.to_translation()) return ctrl def make_rot_controller(self, fcurves, default_xform): # TODO: support rotation_quaternion rot_curves = [i for i in fcurves if i.data_path == "rotation_euler" and i.keyframe_points] keyframes, bez_chans = self._process_keyframes(rot_curves) if not keyframes: return None # Ugh. Unfortunately, it appears Blender's default interpolation is bezier. So who knows if # many users will actually see the benefit here? Makes me sad. if bez_chans: ctrl = self._make_scalar_compound_controller(rot_curves, keyframes, bez_chans, default_xform.to_euler()) else: ctrl = self._make_quat_controller(rot_curves, keyframes, default_xform.to_euler()) return ctrl def make_scale_controller(self, fcurves, default_xform): scale_curves = [i for i in fcurves if i.data_path == "scale" and i.keyframe_points] keyframes, bez_chans = self._process_keyframes(scale_curves) if not keyframes: return None # There is no such thing as a compound scale controller... in Plasma, anyway. ctrl = self._make_scale_value_controller(scale_curves, keyframes, bez_chans, default_xform) return ctrl def make_scalar_leaf_controller(self, fcurve): keyframes, bezier = self._process_fcurve(fcurve) if not keyframes: return None ctrl = self._make_scalar_leaf_controller(keyframes, bezier) return ctrl def _make_matrix44_controller(self, pos_fcurves, scale_fcurves, keyframes, default_pos, default_scale): ctrl = plLeafController() keyframe_type = hsKeyFrame.kMatrix44KeyFrame exported_frames = [] pcurves = { i.array_index: i for i in pos_fcurves } scurves = { i.array_index: i for i in scale_fcurves } def eval_fcurve(fcurves, keyframe, i, default_xform): try: return fcurves[i].evaluate(keyframe.frame_num_blender) except KeyError: return default_xform[i] for pos_key, scale_key in keyframes: valid_key = pos_key if pos_key is not None else scale_key exported = hsMatrix44Key() exported.frame = valid_key.frame_num exported.frameTime = valid_key.frame_time exported.type = keyframe_type if pos_key is not None: pos_value = [pos_key.values[i] if i in pos_key.values else eval_fcurve(pcurves, pos_key, i, default_pos) for i in range(3)] else: pos_value = [eval_fcurve(pcurves, valid_key, i, default_pos) for i in range(3)] if scale_key is not None: scale_value = [scale_key.values[i] if i in scale_key.values else eval_fcurve(scurves, scale_key, i, default_scale) for i in range(3)] else: scale_value = [eval_fcurve(scurves, valid_key, i, default_scale) for i in range(3)] pos_value = hsVector3(*pos_value) scale_value = hsVector3(*scale_value) value = hsMatrix44() value.setTranslate(pos_value) value.setScale(scale_value) exported.value = value exported_frames.append(exported) ctrl.keys = (exported_frames, keyframe_type) return ctrl def _make_point3_controller(self, fcurves, keyframes, bezier, default_xform): ctrl = plLeafController() subctrls = ("X", "Y", "Z") keyframe_type = hsKeyFrame.kBezPoint3KeyFrame if bezier else hsKeyFrame.kPoint3KeyFrame exported_frames = [] ctrl_fcurves = { i.array_index: i for i in fcurves } for keyframe in keyframes: exported = hsPoint3Key() exported.frame = keyframe.frame_num exported.frameTime = keyframe.frame_time exported.type = keyframe_type in_tan = hsVector3() out_tan = hsVector3() value = hsVector3() for i, subctrl in enumerate(subctrls): fval = keyframe.values.get(i, None) if fval is not None: setattr(value, subctrl, fval) setattr(in_tan, subctrl, keyframe.in_tans[i]) setattr(out_tan, subctrl, keyframe.out_tans[i]) else: try: setattr(value, subctrl, ctrl_fcurves[i].evaluate(keyframe.frame_num_blender)) except KeyError: setattr(value, subctrl, default_xform[i]) setattr(in_tan, subctrl, 0.0) setattr(out_tan, subctrl, 0.0) exported.inTan = in_tan exported.outTan = out_tan exported.value = value exported_frames.append(exported) ctrl.keys = (exported_frames, keyframe_type) return ctrl def _make_quat_controller(self, fcurves, keyframes, default_xform): ctrl = plLeafController() keyframe_type = hsKeyFrame.kQuatKeyFrame exported_frames = [] ctrl_fcurves = { i.array_index: i for i in fcurves } for keyframe in keyframes: exported = hsQuatKey() exported.frame = keyframe.frame_num exported.frameTime = keyframe.frame_time exported.type = keyframe_type # NOTE: quat keyframes don't do bezier nonsense value = mathutils.Euler() for i in range(3): fval = keyframe.values.get(i, None) if fval is not None: value[i] = fval else: try: value[i] = ctrl_fcurves[i].evaluate(keyframe.frame_num_blender) except KeyError: value[i] = default_xform[i] quat = value.to_quaternion() exported.value = utils.quaternion(quat) exported_frames.append(exported) ctrl.keys = (exported_frames, keyframe_type) return ctrl def _make_scalar_compound_controller(self, fcurves, keyframes, bez_chans, default_xform): ctrl = plCompoundController() subctrls = ("X", "Y", "Z") for i in subctrls: setattr(ctrl, i, plLeafController()) exported_frames = ([], [], []) ctrl_fcurves = { i.array_index: i for i in fcurves } for keyframe in keyframes: for i, subctrl in enumerate(subctrls): fval = keyframe.values.get(i, None) if fval is not None: keyframe_type = hsKeyFrame.kBezScalarKeyFrame if i in bez_chans else hsKeyFrame.kScalarKeyFrame exported = hsScalarKey() exported.frame = keyframe.frame_num exported.frameTime = keyframe.frame_time exported.inTan = keyframe.in_tans[i] exported.outTan = keyframe.out_tans[i] exported.type = keyframe_type exported.value = fval exported_frames[i].append(exported) for i, subctrl in enumerate(subctrls): my_keyframes = exported_frames[i] # ensure this controller has at least ONE keyframe if not my_keyframes: hack_frame = hsScalarKey() hack_frame.frame = 0 hack_frame.frameTime = 0.0 hack_frame.type = hsKeyFrame.kScalarKeyFrame hack_frame.value = default_xform[i] my_keyframes.append(hack_frame) getattr(ctrl, subctrl).keys = (my_keyframes, my_keyframes[0].type) return ctrl def _make_scalar_leaf_controller(self, keyframes, bezier): ctrl = plLeafController() keyframe_type = hsKeyFrame.kBezScalarKeyFrame if bezier else hsKeyFrame.kScalarKeyFrame exported_frames = [] for keyframe in keyframes: exported = hsScalarKey() exported.frame = keyframe.frame_num exported.frameTime = keyframe.frame_time exported.inTan = keyframe.in_tan exported.outTan = keyframe.out_tan exported.type = keyframe_type exported.value = keyframe.value exported_frames.append(exported) ctrl.keys = (exported_frames, keyframe_type) return ctrl def _make_scale_value_controller(self, fcurves, keyframes, bez_chans, default_xform): subctrls = ("X", "Y", "Z") keyframe_type = hsKeyFrame.kBezScaleKeyFrame if bez_chans else hsKeyFrame.kScaleKeyFrame exported_frames = [] ctrl_fcurves = { i.array_index: i for i in fcurves } default_scale = default_xform.to_scale() unit_quat = default_xform.to_quaternion() unit_quat.normalize() unit_quat = utils.quaternion(unit_quat) for keyframe in keyframes: exported = hsScaleKey() exported.frame = keyframe.frame_num exported.frameTime = keyframe.frame_time exported.type = keyframe_type in_tan = hsVector3() out_tan = hsVector3() value = hsVector3() for i, subctrl in enumerate(subctrls): fval = keyframe.values.get(i, None) if fval is not None: setattr(value, subctrl, fval) setattr(in_tan, subctrl, keyframe.in_tans[i]) setattr(out_tan, subctrl, keyframe.out_tans[i]) else: try: setattr(value, subctrl, ctrl_fcurves[i].evaluate(keyframe.frame_num_blender)) except KeyError: setattr(value, subctrl, default_scale[i]) setattr(in_tan, subctrl, 0.0) setattr(out_tan, subctrl, 0.0) exported.inTan = in_tan exported.outTan = out_tan exported.value = (value, unit_quat) exported_frames.append(exported) ctrl = plLeafController() ctrl.keys = (exported_frames, keyframe_type) return ctrl def _process_fcurve(self, fcurve): """Like _process_keyframes, but for one fcurve""" keyframe_data = type("KeyFrameData", (), {}) fps = self._bl_fps pi = math.pi keyframes = {} bezier = False fcurve.update() for fkey in fcurve.keyframe_points: keyframe = keyframe_data() frame_num, value = fkey.co if fps == 30.0: keyframe.frame_num = int(frame_num) else: keyframe.frame_num = int(frame_num * (30.0 / fps)) keyframe.frame_time = frame_num / fps if fkey.interpolation == "BEZIER": keyframe.in_tan = -(value - fkey.handle_left[1]) / (frame_num - fkey.handle_left[0]) / fps / (2 * pi) keyframe.out_tan = (value - fkey.handle_right[1]) / (frame_num - fkey.handle_right[0]) / fps / (2 * pi) bezier = True else: keyframe.in_tan = 0.0 keyframe.out_tan = 0.0 keyframe.value = value keyframes[frame_num] = keyframe final_keyframes = [keyframes[i] for i in sorted(keyframes)] return (final_keyframes, bezier) def _process_keyframes(self, fcurves): """Groups all FCurves for the same frame together""" keyframe_data = type("KeyFrameData", (), {}) fps = self._bl_fps pi = math.pi keyframes = {} bez_chans = set() for fcurve in fcurves: fcurve.update() for fkey in fcurve.keyframe_points: frame_num, value = fkey.co keyframe = keyframes.get(frame_num, None) if keyframe is None: keyframe = keyframe_data() if fps == 30.0: # hope you don't have a frame 29.9 and frame 30.0... keyframe.frame_num = int(frame_num) else: keyframe.frame_num = int(frame_num * (30.0 / fps)) keyframe.frame_num_blender = frame_num keyframe.frame_time = frame_num / fps keyframe.in_tans = {} keyframe.out_tans = {} keyframe.values = {} keyframes[frame_num] = keyframe idx = fcurve.array_index keyframe.values[idx] = value # Calculate the bezier interpolation nonsense if fkey.interpolation == "BEZIER": keyframe.in_tans[idx] = -(value - fkey.handle_left[1]) / (frame_num - fkey.handle_left[0]) / fps / (2 * pi) keyframe.out_tans[idx] = (value - fkey.handle_right[1]) / (frame_num - fkey.handle_right[0]) / fps / (2 * pi) bez_chans.add(idx) else: keyframe.in_tans[idx] = 0.0 keyframe.out_tans[idx] = 0.0 # Return the keyframes in a sequence sorted by frame number final_keyframes = [keyframes[i] for i in sorted(keyframes)] return (final_keyframes, bez_chans) @property def _mgr(self): return self._exporter().mgr