From c2873b2abfdb7fa135df9f6d37919dcfc5dcd3fc Mon Sep 17 00:00:00 2001 From: David Date: Wed, 6 Dec 2023 22:45:59 -0800 Subject: [PATCH] node based incremental cond scheduling --- nodes/__init__.py | 4 + nodes/schedule.py | 182 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 nodes/schedule.py diff --git a/nodes/__init__.py b/nodes/__init__.py index 4095bbf..6f0169a 100644 --- a/nodes/__init__.py +++ b/nodes/__init__.py @@ -2,13 +2,17 @@ from .core import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS from .debug import NODE_CLASS_MAPPINGS as ncm0, NODE_DISPLAY_NAME_MAPPINGS as ndnm0 from .entangled import NODE_CLASS_MAPPINGS as ncm1, NODE_DISPLAY_NAME_MAPPINGS as ndnm1 +from .schedule import NODE_CLASS_MAPPINGS as ncm2, NODE_DISPLAY_NAME_MAPPINGS as ndnm2 + # there's probably a cleaner, more-dummy-proof way to do this. # feels like an accident waiting to happen. low risk though. NODE_CLASS_MAPPINGS.update(ncm0) NODE_CLASS_MAPPINGS.update(ncm1) +NODE_CLASS_MAPPINGS.update(ncm2) NODE_DISPLAY_NAME_MAPPINGS.update(ndnm0) NODE_DISPLAY_NAME_MAPPINGS.update(ndnm1) +NODE_DISPLAY_NAME_MAPPINGS.update(ndnm2) __all__ =["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] \ No newline at end of file diff --git a/nodes/schedule.py b/nodes/schedule.py new file mode 100644 index 0000000..64a95f3 --- /dev/null +++ b/nodes/schedule.py @@ -0,0 +1,182 @@ +import keyframed as kf +from functools import total_ordering +from sortedcontainers import SortedDict, SortedList +from .core import CATEGORY as RootCategory + + +CATEGORY=RootCategory + "/schedule" + + +@total_ordering +class ScheduleKeyframe(kf.Keyframe): + def __lt__(self, other): + return self.t < other + + +def update_schedule(schedule, keyframe): + bl_idx = schedule.bisect_left(keyframe.t) + try: + if schedule[bl_idx].t == keyframe.t: + #del schedule[bl_idx] + schedule.pop(bl_idx) + except IndexError: + pass + schedule.add(keyframe) + return schedule + + +# schedule = SortedList() + +# x0 = ScheduleKeyframe(t=0, value="a") +# x1 = ScheduleKeyframe(t=5, value="b") +# x2 = ScheduleKeyframe(t=5, value="c") +# x3 = ScheduleKeyframe(t=6, value="d") + + + +# schedule = update_schedule(schedule, x0) +# schedule = update_schedule(schedule, x3) # +# schedule = update_schedule(schedule, x2) +# schedule = update_schedule(schedule, x1) +# schedule + + + +################################################################################### + + + +class KfKeyframedCondition: + """ + Attaches a condition to a keyframe + """ + CATEGORY=CATEGORY + FUNCTION = 'main' + RETURN_TYPES = ("KEYFRAMED_CONDITION",) + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "conditioning": ("CONDITIONING", {}), + "time": ("FLOAT", {"default": 0}), + "weight": ("FLOAT", {"default": 1}), # maybe i should hide this attribute + "interpolation_method": (list(kf.interpolation.INTERPOLATORS.keys()),), + }, + } + + def main(self, conditioning, time, weight, interpolation_method): + keyframe = kf.Keyframe(t=time, value=weight, interpolation_method=interpolation_method) + return (keyframe, conditioning) + + +class KfSetKeyframe: + CATEGORY=CATEGORY + FUNCTION = 'main' + RETURN_TYPES = ("SCHEDULE",) + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "keyframed_condition": ("KEYFRAMED_CONDITION", {}), + }, + "optional": { + "schedule": ("SCHEDULE", {}), + } + } + def main(keyframed_condition, schedule=None): + #keyframe, kf_condition = keyframed_condition + if schedule is None: + schedule = SortedDict + schedule = update_schedule(schedule, keyframed_condition) + return (schedule,) + + +class KfGetScheduleConditionAtTime: + CATEGORY=CATEGORY + FUNCTION = 'main' + RETURN_TYPES = ("CONDITIONING",) + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "schedule": ("SCHEDULE",{}), + "time": ("FLOAT",{}), + } + } + + def main(self, schedule, time): + bl_idx = schedule.bisect_left(time) + left_kf, left_cond = schedule[bl_idx] + if left_kf.t == time: # hit time exactly, return + return (left_kf, left_cond) + if bl_idx == len(schedule): # there's nothing to our right, return + return (left_kf, left_cond) + right_kf, right_cond = schedule[bl_idx+1] + start, end = left_kf.t, right_kf.t + interval_length = end - start + elapsed = time-start + perc_complete = elapsed / interval_length + # TODO: use interpolation method on keyframe to compute transition weight + # For now, simple lerp + lerped_cond = perc_complete * right_cond + (1-perc_complete)*left_cond + # TODO: we could also interpolate and return an associated weight + return (lerped_cond,) + + +NODE_CLASS_MAPPINGS = { + "KfKeyframedCondition": KfKeyframedCondition, + "KfSetKeyframe": KfSetKeyframe, + "KfGetScheduleConditionAtTime": KfGetScheduleConditionAtTime, +} + +NODE_DISPLAY_NAME_MAPPINGS = {} + + +################################################################################### + +# class KfSetKeyframe: +# CATEGORY=CATEGORY +# FUNCTION = 'main' +# RETURN_TYPES = ("SCHEDULE",) + +# @classmethod +# def INPUT_TYPES(cls): +# return { +# "required": { +# "keyframed_condition": ("KEYFRAMED_CONDITION", {}), +# }, +# "optional": { +# "schedule": ("SCHEDULE", {}), +# } +# } +# def main(keyframed_condition, schedule=None): +# keyframe, kf_condition = keyframed_condition +# if schedule is None: +# schedule = SortedDict +# schedule[keyframe.t] = keyframed_condition +# return (schedule,) + +# class KfGetScheduleConditionAtTime: +# CATEGORY=CATEGORY +# FUNCTION = 'main' +# RETURN_TYPES = ("KEYFRAME",) + +# @classmethod +# def INPUT_TYPES(cls): +# return { +# "required": { +# "schedule": ("SCHEDULE",{}), +# "time": ("FLOAT",{}), +# } +# } + +# def main(self, schedule, time): + +# # right_index = self._data.bisect_right(k) +# # left_index = right_index - 1 +# # if right_index > 0: +# # _, left_value = self._data.peekitem(left_index) +# # else: \ No newline at end of file