diff --git a/korman/exporter/animation.py b/korman/exporter/animation.py index f87f214..925dc4a 100644 --- a/korman/exporter/animation.py +++ b/korman/exporter/animation.py @@ -455,6 +455,115 @@ class AnimationConverter: final_keyframes = [keyframes[i] for i in sorted(keyframes)] return (final_keyframes, bezier) + def _process_fcurves(self, fcurves, convert, defaults=None): + """Processes FCurves of different data sets and converts them into a single list of keyframes. + This should be used when multiple Blender fields map to a single Plasma option.""" + class KeyFrameData: + def __init__(self): + self.values = {} + fps = self._bl_fps + pi = math.pi + + # It is assumed therefore that any multichannel FCurves will have all channels represented. + # This seems fairly safe with my experiments with Lamp colors... + grouped_fcurves = {} + for fcurve in fcurves: + if fcurve is None: + continue + fcurve.update() + if fcurve.data_path in grouped_fcurves: + grouped_fcurves[fcurve.data_path][fcurve.array_index] = fcurve + else: + grouped_fcurves[fcurve.data_path] = { fcurve.array_index: fcurve } + + # Default values for channels that are not animated + for key, value in defaults.items(): + if key not in grouped_fcurves: + if hasattr(value, "__len__"): + grouped_fcurves[key] = value + else: + grouped_fcurves[key] = [value,] + + # Assemble a dict { PlasmaFrameNum: { FCurveDataPath: KeyFrame } } + keyframe_points = {} + for fcurve in fcurves: + if fcurve is None: + continue + for keyframe in fcurve.keyframe_points: + frame_num_blender, value = keyframe.co + frame_num = int(frame_num_blender * (30.0 / fps)) + + # This is a temporary keyframe, so we're not going to worry about converting everything + # Only the frame number to Plasma so we can go ahead and merge any rounded dupes + entry, data = keyframe_points.get(frame_num), None + if entry is None: + entry = {} + keyframe_points[frame_num] = entry + else: + data = entry.get(fcurve.data_path) + if data is None: + data = KeyFrameData() + data.frame_num = frame_num + data.frame_num_blender = frame_num_blender + entry[fcurve.data_path] = data + data.values[fcurve.array_index] = value + + # Now, we loop through our assembled keyframes and interpolate any missing data using the FCurves + fcurve_chans = { key: len(value) for key, value in grouped_fcurves.items() } + expected_values = sum(fcurve_chans.values()) + all_chans = frozenset(grouped_fcurves.keys()) + + # We will also do the final convert here as well... + final_keyframes = [] + + for frame_num in sorted(keyframe_points.copy().keys()): + keyframes = keyframe_points[frame_num] + frame_num_blender = next(iter(keyframes.values())).frame_num_blender + + # If any data_paths are missing, init a dummy + missing_channels = all_chans - frozenset(keyframes.keys()) + for chan in missing_channels: + dummy = KeyFrameData() + dummy.frame_num = frame_num + dummy.frame_num_blender = frame_num_blender + keyframes[chan] = dummy + + # Ensure all values are filled out. + num_values = sum(map(len, (i.values for i in keyframes.values()))) + if num_values != expected_values: + for chan, sorted_fcurves in grouped_fcurves.items(): + chan_keyframes = keyframes[chan] + chan_values = fcurve_chans[chan] + if len(chan_keyframes.values) == chan_values: + continue + for i in range(chan_values): + if i not in chan_keyframes.values: + fcurve = grouped_fcurves[chan][i] + if isinstance(fcurve, bpy.types.FCurve): + chan_keyframes.values[i] = fcurve.evaluate(chan_keyframes.frame_num_blender) + else: + # it's actually a default value! + chan_keyframes.values[i] = fcurve + + # All values are calculated! Now we convert the disparate key data into a single keyframe. + kwargs = { data_path: keyframe.values for data_path, keyframe in keyframes.items() } + final_keyframe = KeyFrameData() + final_keyframe.frame_num = frame_num + final_keyframe.frame_num_blender = frame_num_blender + final_keyframe.frame_time = frame_num / fps + value = convert(**kwargs) + if hasattr(value, "__len__"): + final_keyframe.in_tans = [0.0] * len(value) + final_keyframe.out_tans = [0.0] * len(value) + final_keyframe.values = value + else: + final_keyframe.in_tan = 0.0 + final_keyframe.out_tan = 0.0 + final_keyframe.value = value + final_keyframes.append(final_keyframe) + return final_keyframes + + def _process_keyframes(self, fcurves, convert=None): """Groups all FCurves for the same frame together""" keyframe_data = type("KeyFrameData", (), {}) @@ -482,7 +591,7 @@ class AnimationConverter: keyframe.values = {} keyframes[frame_num] = keyframe idx = fcurve.array_index - keyframe.values[idx] = value if convert is None else convert(value) + keyframe.values[idx] = value if convert is None else convert(value) # Calculate the bezier interpolation nonsense if fkey.interpolation == "BEZIER":