Merge pull request #16 from dmarx/dev

Parameter Groups, Drawing, Simplified Scheduling
This commit is contained in:
David Marx
2023-12-13 18:55:44 -08:00
committed by GitHub
7 changed files with 426 additions and 118 deletions
+12 -1
View File
@@ -49,11 +49,22 @@ Related project: https://github.com/FizzleDorf/ComfyUI_FizzNodes
## Prompt Scheduling
see also: [`Nodes > Scheduling`](https://github.com/dmarx/ComfyUI-Keyframed/blob/dev/README.md#scheduling)
<!--
![Prompt Scheduling](examples/prompt-scheduling.png)
-->
![Prompt Scheduling](examples/prompt-scheduling_ezmode.png)
This one is probably why you are here. This workflow demonstrates how to use the `keyframed/schedule` nodes to achieve similar behavior as [FizzNodes'](https://github.com/FizzleDorf/ComfyUI_FizzNodes) **PromptSchedule** node, but implemented differently.
This schedule is essentailly a normal AnimateDiff workflow where several nodes have replaced the normal conditioning setup. Rather than a single `CLIP Text Encode` node, we can have multiple prompts which transition sequentially over time. For documentation detailing how this workflow works, see the [`Nodes > Scheduling`](https://github.com/dmarx/ComfyUI-Keyframed/blob/dev/README.md#scheduling) section below.
This schedule is essentailly a normal AnimateDiff workflow where several nodes have replaced the normal conditioning setup. Rather than a single `CLIP Text Encode` node, we can have multiple prompts which transition sequentially over time.
The workflow above illustrations the most user-friendly interface, but there are nodes for lower level interfaces as well:
![Prompt Scheduling](examples/prompt-scheduling_old.png)
## Interleaving Multiple Prompts Simultaneously (aka Prompt Entanglement, aka Prompt Superposition)
Binary file not shown.

After

Width:  |  Height:  |  Size: 972 KiB

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

+6 -1
View File
@@ -4,6 +4,7 @@ from .debug import NODE_CLASS_MAPPINGS as ncm0, NODE_DISPLAY_NAME_MAPPINGS as nd
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
from .sinusoidal import NODE_CLASS_MAPPINGS as ncm3, NODE_DISPLAY_NAME_MAPPINGS as ndnm3
from .pgroup import NODE_CLASS_MAPPINGS as ncm4, NODE_DISPLAY_NAME_MAPPINGS as ndnm4
# there's probably a cleaner, more-dummy-proof way to do this.
@@ -12,10 +13,14 @@ NODE_CLASS_MAPPINGS.update(ncm0)
NODE_CLASS_MAPPINGS.update(ncm1)
NODE_CLASS_MAPPINGS.update(ncm2)
NODE_CLASS_MAPPINGS.update(ncm3)
NODE_CLASS_MAPPINGS.update(ncm4)
NODE_DISPLAY_NAME_MAPPINGS.update(ndnm0)
NODE_DISPLAY_NAME_MAPPINGS.update(ndnm1)
NODE_DISPLAY_NAME_MAPPINGS.update(ndnm2)
NODE_DISPLAY_NAME_MAPPINGS.update(ndnm3)
NODE_DISPLAY_NAME_MAPPINGS.update(ndnm4)
__all__ =["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
__all__ =["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
# TODO: ensure installed: keyframed toolz
+62 -89
View File
@@ -248,6 +248,42 @@ class KfConditioningAddx10:
return [((cond_t_out, cond_d_out),)] #((cond_t_out, cond_d_out),)
class KfConditioningAddx10_alt:
CATEGORY = CATEGORY
FUNCTION = "main"
RETURN_TYPES = ("CONDITIONING",)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"cond_0": ("CONDITIONING",{"forceInput": True,}),
},
"optional": {
"cond_1": ("CONDITIONING",{"forceInput": True, "default": 0}),
"cond_2": ("CONDITIONING",{"forceInput": True, "default": 0}),
"cond_3": ("CONDITIONING",{"forceInput": True, "default": 0}),
"cond_4": ("CONDITIONING",{"forceInput": True, "default": 0}),
"cond_5": ("CONDITIONING",{"forceInput": True, "default": 0}),
"cond_6": ("CONDITIONING",{"forceInput": True, "default": 0}),
"cond_7": ("CONDITIONING",{"forceInput": True, "default": 0}),
"cond_8": ("CONDITIONING",{"forceInput": True, "default": 0}),
"cond_9": ("CONDITIONING",{"forceInput": True, "default": 0}),
},
}
def main(self, cond_0, **kwargs):
((cond_t_out, cond_d_out),) = deepcopy(cond_0)
for v in kwargs.values():
if v not in (0, None):
((cond_t,cond_d),) = v
cond_t, cond_d = deepcopy(cond_t), deepcopy(cond_d)
cond_t_out = cond_t_out + cond_t
cond_d_out["pooled_output"] = cond_d_out["pooled_output"] + cond_d["pooled_output"]
return [((cond_t_out, cond_d_out),)] #((cond_t_out, cond_d_out),)
# class KfCurveInverse:
# CATEGORY = CATEGORY
# FUNCTION = "main"
@@ -270,7 +306,7 @@ class KfConditioningAddx10:
# return (curve,)
def plot_curve(curve, n):
def plot_curve(curve, n, show_legend, is_pgroup=False):
"""
"""
@@ -288,7 +324,7 @@ def plot_curve(curve, n):
m=3
if n < m:
n = self.duration + 1
n = curve.duration + 1
n = max(m, n)
@@ -313,12 +349,14 @@ def plot_curve(curve, n):
kfy = [curve[x] for x in kfx]
plt.scatter(kfx, kfy, color=line[0].get_color())
if isinstance(curve, kf.ParameterGroup):
#if isinstance(curve, kf.ParameterGroup): # type collision with kf.Composition
if is_pgroup:
for c in curve.parameters.values():
draw_curve(c)
else:
draw_curve(curve)
plt.legend()
if show_legend:
plt.legend()
#width, height = 10, 5 #inches
@@ -356,11 +394,12 @@ class KfCurveDraw:
"required": {
"curve": ("KEYFRAMED_CURVE", {"forceInput": True,}),
"n": ("INT", {"default": 64}),
"show_legend": ("BOOLEAN", {"default": True}),
}
}
def main(self, curve, n):
img_tensor = plot_curve(curve, n)
def main(self, curve, n, show_legend):
img_tensor = plot_curve(curve, n, show_legend, is_pgroup=False)
return (img_tensor,)
class KfPGroupDraw:
@@ -374,11 +413,12 @@ class KfPGroupDraw:
"required": {
"parameter_group": ("PARAMETER_GROUP", {"forceInput": True,}),
"n": ("INT", {"default": 64}),
"show_legend": ("BOOLEAN", {"default": True}),
}
}
def main(self, parameter_group, n):
img_tensor = plot_curve(parameter_group, n)
def main(self, parameter_group, n, show_legend):
img_tensor = plot_curve(parameter_group, n, show_legend, is_pgroup=True)
return (img_tensor,)
###########################################
@@ -429,7 +469,7 @@ class KfCurvesAddx10:
},
}
def main(self, curve_0, curve_1, curve_2, curve_3, curve_4, curve_5, curve_6, curve_7, curve_8, curve_9):
def main(self, curve_0=0, curve_1=0, curve_2=0, curve_3=0, curve_4=0, curve_5=0, curve_6=0, curve_7=0, curve_8=0, curve_9=0):
#curve_1 = deepcopy(curve_1)
#curve_2 = deepcopy(curve_2)
#return (curve_1 + curve_2, )
@@ -499,15 +539,15 @@ class KfCurvesMultiplyx10:
"curve_0": ("KEYFRAMED_CURVE",{"forceInput": True,}),
},
"optional": {
"curve_1": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 0}),
"curve_2": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 0}),
"curve_3": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 0}),
"curve_4": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 0}),
"curve_5": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 0}),
"curve_6": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 0}),
"curve_7": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 0}),
"curve_8": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 0}),
"curve_9": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 0}),
"curve_1": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 1}),
"curve_2": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 1}),
"curve_3": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 1}),
"curve_4": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 1}),
"curve_5": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 1}),
"curve_6": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 1}),
"curve_7": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 1}),
"curve_8": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 1}),
"curve_9": ("KEYFRAMED_CURVE",{"forceInput": True, "default": 1}),
},
}
@@ -568,72 +608,6 @@ class KfCurveConstant:
return (curve,)
##################################################################
### TODO: Working with parameter groups
# add curve(s) to parameter group
## inputs: pgroup, curve
## returns pgroup
## if pgroup not provided, new one created
class KfAddCurveToPGroup:
CATEGORY = CATEGORY
FUNCTION = "main"
#RETURN_TYPES = ("KEYFRAMED_CURVE",)
RETURN_TYPES = ("PARAMETER_GROUP",)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"curve": ("KEYFRAMED_CURVE",{"forceInput": True,}),
},
"optional": {
"parameter_group": ("PARAMETER_GROUP",{"forceInput": True,}),
},
}
def main(self, curve, parameter_group=None):
curve = deepcopy(curve)
if parameter_group is None:
#parameter_group = kf.ParameterGroup({curve.label:curve})
parameter_group = kf.ParameterGroup([curve])
else:
parameter_group = deepcopy(parameter_group)
parameter_group.parameters[curve.label] = curve
return (parameter_group,)
class KfGetCurveFromPGroup:
CATEGORY = CATEGORY
FUNCTION = "main"
RETURN_TYPES = ("KEYFRAMED_CURVE",)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"curve_label": ("STRING",{"default": "My Curve",}),
"parameter_group": ("PARAMETER_GROUP",{"forceInput": True,}),
},
}
def main(self, curve_label, parameter_group):
curve = parameter_group.parameters[curve_label]
return (deepcopy(curve),)
# get curve from parameter group
## inputs: pgroup, label
## returns curve
# extract a time slice from the parameter group
##################################################################
##################################################################
@@ -665,14 +639,14 @@ NODE_CLASS_MAPPINGS = {
"KfApplyCurveToCond": KfApplyCurveToCond,
"KfConditioningAdd": KfConditioningAdd,
#######################################
"KfAddCurveToPGroup": KfAddCurveToPGroup,
"KfGetCurveFromPGroup": KfGetCurveFromPGroup,
"KfCurveDraw": KfCurveDraw,
"KfPGroupDraw": KfPGroupDraw,
"KfSetCurveLabel":KfSetCurveLabel,
#######################################
#"KfCurveToAcnLatentKeyframe": KfCurveToAcnLatentKeyframe,
#######################################
#"KfCurveInverse": KfCurveInverse,
"KfCurveDraw": KfCurveDraw,
"KfPGroupDraw": KfPGroupDraw,
"KfCurvesAdd": KfCurvesAdd,
"KfCurvesSubtract": KfCurvesSubtract,
"KfCurvesMultiply": KfCurvesMultiply,
@@ -682,7 +656,6 @@ NODE_CLASS_MAPPINGS = {
"KfConditioningAddx10":KfConditioningAddx10,
"KfCurvesAddx10":KfCurvesAddx10,
"KfCurvesMultiplyx10":KfCurvesMultiplyx10,
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
+211
View File
@@ -0,0 +1,211 @@
import keyframed as kf
import logging
from copy import deepcopy
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
CATEGORY = "keyframed/parameter group"
class KfAddCurveToPGroup:
CATEGORY = CATEGORY
FUNCTION = "main"
#RETURN_TYPES = ("KEYFRAMED_CURVE",)
RETURN_TYPES = ("PARAMETER_GROUP",)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"curve": ("KEYFRAMED_CURVE",{"forceInput": True,}),
},
"optional": {
"parameter_group": ("PARAMETER_GROUP",{"forceInput": True,}),
},
}
def main(self, curve, parameter_group=None):
curve = deepcopy(curve)
if parameter_group is None:
#parameter_group = kf.ParameterGroup({curve.label:curve})
parameter_group = kf.ParameterGroup([curve])
else:
parameter_group = deepcopy(parameter_group)
parameter_group.parameters[curve.label] = curve
return (parameter_group,)
class KfAddCurveToPGroupx10:
CATEGORY = CATEGORY
FUNCTION = "main"
#RETURN_TYPES = ("KEYFRAMED_CURVE",)
RETURN_TYPES = ("PARAMETER_GROUP",)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"curve0": ("KEYFRAMED_CURVE",{"forceInput": True,}),
},
"optional": {
"parameter_group": ("PARAMETER_GROUP",{"forceInput": True,}),
"curve1": ("KEYFRAMED_CURVE",{"forceInput": True,}),
"curve2": ("KEYFRAMED_CURVE",{"forceInput": True,}),
"curve3": ("KEYFRAMED_CURVE",{"forceInput": True,}),
"curve4": ("KEYFRAMED_CURVE",{"forceInput": True,}),
"curve5": ("KEYFRAMED_CURVE",{"forceInput": True,}),
"curve6": ("KEYFRAMED_CURVE",{"forceInput": True,}),
"curve7": ("KEYFRAMED_CURVE",{"forceInput": True,}),
"curve8": ("KEYFRAMED_CURVE",{"forceInput": True,}),
"curve9": ("KEYFRAMED_CURVE",{"forceInput": True,}),
},
}
def main(self, parameter_group=None, **kwargs):
if parameter_group is None:
#parameter_group = kf.ParameterGroup({curve.label:curve})
parameter_group = kf.ParameterGroup(kwargs)
else:
parameter_group = deepcopy(parameter_group)
for curve in parameter_group.values():
parameter_group.parameters[curve.label] = curve
return (parameter_group,)
class KfGetCurveFromPGroup:
CATEGORY = CATEGORY
FUNCTION = "main"
RETURN_TYPES = ("KEYFRAMED_CURVE",)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"curve_label": ("STRING",{"default": "My Curve",}),
"parameter_group": ("PARAMETER_GROUP",{"forceInput": True,}),
},
}
def main(self, curve_label, parameter_group):
curve = parameter_group.parameters[curve_label]
return (deepcopy(curve),)
##################################################################
# PGroup Arithmetic
class KfPGroupCurveAdd:
CATEGORY = CATEGORY
FUNCTION = "main"
RETURN_TYPES = ("PARAMETER_GROUP",)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"parameter_group": ("PARAMETER_GROUP",{"forceInput": True,}),
"curve": ("KEYFRAMED_CURVE",{"forceInput": True,}),
},
}
def main(self, parameter_group, curve):
parameter_group = deepcopy(parameter_group)
curve = deepcopy(curve)
return (parameter_group + curve, )
class KfPGroupCurveMultiply:
CATEGORY = CATEGORY
FUNCTION = "main"
RETURN_TYPES = ("PARAMETER_GROUP",)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"parameter_group": ("PARAMETER_GROUP",{"forceInput": True,}),
"curve": ("KEYFRAMED_CURVE",{"forceInput": True,}),
},
}
def main(self, parameter_group, curve):
parameter_group = deepcopy(parameter_group)
curve = deepcopy(curve)
return (parameter_group * curve, )
# not behaving as expected :(
class KfPGroupSum:
#CATEGORY = CATEGORY
CATEGORY = "keyframed/experimental"
FUNCTION = "main"
RETURN_TYPES = ("KEYFRAMED_CURVE",)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"parameter_group": ("PARAMETER_GROUP",{"forceInput": True,}),
},
}
def main(self, parameter_group):
parameter_group = deepcopy(parameter_group)
#curve = kf.Composition(parameter_group, reduction='sum')
#curve = sum(parameter_group.parameters.values())
outv = kf.Curve(0)
for curve in parameter_group.parameters.values():
outv += curve
return (outv,)
#return (curve,)
class KfPGroupProd:
pass
# CATEGORY = CATEGORY
# FUNCTION = "main"
# RETURN_TYPES = ("KEYFRAMED_CURVE",)
# @classmethod
# def INPUT_TYPES(s):
# return {
# "required": {
# "parameter_group": ("PARAMETER_GROUP",{"forceInput": True,}),
# },
# }
# def main(self, parameter_group):
# parameter_group = deepcopy(parameter_group)
# curve = kf.Composition(parameter_group, reduction='prod')
# return (curve,)
##################################################################
NODE_CLASS_MAPPINGS = {
#"KfCurveDraw": KfCurveDraw,
#"KfPGroupDraw": KfPGroupDraw,
#"KfSetCurveLabel":KfSetCurveLabel,
"KfAddCurveToPGroup": KfAddCurveToPGroup,
"KfGetCurveFromPGroup": KfGetCurveFromPGroup,
"KfAddCurveToPGroupx10": KfAddCurveToPGroupx10,
"KfPGroupCurveAdd":KfPGroupCurveAdd,
"KfPGroupCurveMultiply":KfPGroupCurveMultiply,
"KfPGroupSum": KfPGroupSum,
"KfPGroupProd": KfPGroupProd,
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
#"KfSetCurveLabel": "Set Curve Label",
"KfAddCurveToPGroup": "Add Curve To Parameter Group",
"KfGetCurveFromPGroup": "Get Curve From Parameter Group",
"KfAddCurveToPGroupx10": "Add Curve To Parameter Group (x10)",
"KfPGroupCurveAdd": "Parameter Group + Curve (addition)",
"KfPGroupCurveMultiply": "Parameter Group * Curve (multiply)",
"KfPGroupSum": "Sum Over Parameter Group",
"KfPGroupProd": "Product Over Parameter Group",
}
+135 -27
View File
@@ -27,9 +27,10 @@ class KfKeyframedCondition:
return {
"required": {
"conditioning": ("CONDITIONING", {}),
"time": ("FLOAT", {"default": 0}),
"time": ("FLOAT", {"default": 0, "step": 1}),
#"weight": ("FLOAT", {"default": 1}), # maybe i should hide this attribute
"interpolation_method": (list(kf.interpolation.INTERPOLATORS.keys()),),
#"interpolation_method": (list(kf.interpolation.INTERPOLATORS.keys()),),
"interpolation_method": (list(kf.interpolation.EASINGS.keys()), {"default":"linear"}),
},
}
@@ -52,6 +53,74 @@ class KfKeyframedCondition:
return ({"kf_cond_t":kf_cond_t, "kf_cond_pooled":kf_cond_pooled, "cond_dict":cond_dict},)
def set_keyframed_condition(keyframed_condition, schedule=None):
keyframed_condition = deepcopy(keyframed_condition)
cond_dict = keyframed_condition.pop("cond_dict")
#cond_dict = deepcopy(cond_dict)
if schedule is None:
# get a new copy of the tensor
kf_cond_t = keyframed_condition["kf_cond_t"]
#kf_cond_t.value = kf_cond_t.value.clone() # should be redundant with the deepcopy
curve_tokenized = kf.Curve([kf_cond_t], label="kf_cond_t")
curves = [curve_tokenized]
if keyframed_condition["kf_cond_pooled"] is not None:
kf_cond_pooled = keyframed_condition["kf_cond_pooled"]
curve_pooled = kf.Curve([kf_cond_pooled], label="kf_cond_pooled")
curves.append(curve_pooled)
schedule = (kf.ParameterGroup(curves), cond_dict)
else:
schedule = deepcopy(schedule)
schedule, old_cond_dict = schedule
for k, v in keyframed_condition.items():
if (v is not None):
# for now, assume we already have a schedule for k.
# Not sure how to handle new conditioning type appearing.
schedule.parameters[k][v.t] = v
old_cond_dict.update(cond_dict) # NB: mutating this is probably bad
schedule = (schedule, old_cond_dict)
return schedule
class KfKeyframedConditionWithText(KfKeyframedCondition):
"""
Attaches a condition to a keyframe
"""
CATEGORY=CATEGORY
FUNCTION = 'main'
RETURN_TYPES = ("KEYFRAMED_CONDITION","CONDITIONING", "SCHEDULE")
@classmethod
def INPUT_TYPES(s):
return {
"required": {
#"conditioning": ("CONDITIONING", {}),
"clip": ("CLIP",),
"text": ("STRING", {
"multiline": True,
"default": ""
}),
"time": ("FLOAT", {"default": 0, "step": 1}),
#"weight": ("FLOAT", {"default": 1}), # maybe i should hide this attribute
#"interpolation_method": (list(kf.interpolation.INTERPOLATORS.keys()),),
"interpolation_method": (list(kf.interpolation.EASINGS.keys()), {"default":"linear"}),
},
"optional": {
"schedule": ("SCHEDULE", {}),
}
}
#def main(self, conditioning, time, interpolation_method):
def main(self, clip, text, time, interpolation_method, schedule=None):
tokens = clip.tokenize(text)
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
conditioning = [[cond, {"pooled_output": pooled}]]
keyframed_condition = super().main(conditioning, time, interpolation_method)[0]
keyframed_condition["kf_cond_t"].label = text # attach prompt string as a label to the xattn keyframe for drawing
schedule = set_keyframed_condition(keyframed_condition, schedule)
return (keyframed_condition, conditioning, schedule)
class KfSetKeyframe:
CATEGORY=CATEGORY
FUNCTION = 'main'
@@ -68,31 +137,7 @@ class KfSetKeyframe:
}
}
def main(self, keyframed_condition, schedule=None):
keyframed_condition = deepcopy(keyframed_condition)
cond_dict = keyframed_condition.pop("cond_dict")
#cond_dict = deepcopy(cond_dict)
if schedule is None:
# get a new copy of the tensor
kf_cond_t = keyframed_condition["kf_cond_t"]
#kf_cond_t.value = kf_cond_t.value.clone() # should be redundant with the deepcopy
curve_tokenized = kf.Curve([kf_cond_t], label="kf_cond_t")
curves = [curve_tokenized]
if keyframed_condition["kf_cond_pooled"] is not None:
kf_cond_pooled = keyframed_condition["kf_cond_pooled"]
curve_pooled = kf.Curve([kf_cond_pooled], label="kf_cond_pooled")
curves.append(curve_pooled)
schedule = (kf.ParameterGroup(curves), cond_dict)
else:
schedule = deepcopy(schedule)
schedule, old_cond_dict = schedule
for k, v in keyframed_condition.items():
if (v is not None):
# for now, assume we already have a schedule for k.
# Not sure how to handle new conditioning type appearing.
schedule.parameters[k][v.t] = v
old_cond_dict.update(cond_dict) # NB: mutating this is probably bad
schedule = (schedule, old_cond_dict)
schedule = set_keyframed_condition(keyframed_condition, schedule)
return (schedule,)
@@ -163,15 +208,78 @@ class KfGetScheduleConditionSlice:
###################################################################
from toolz.itertoolz import sliding_window
def schedule_to_weight_curves(schedule):
"""
Given a keyfrmaed curve, returns a curve for each keyframe giving the contribution
of that keyframe to the schedule. Motivation here is to facilitate plotting
"""
schedule, _ = schedule
schedule = schedule.parameters["kf_cond_t"]
schedule = deepcopy(schedule)
curves = []
keyframes = list(schedule._data.values())
if len(keyframes) == 1:
keyframe = keyframes[0]
#curves = kf.ParameterGroup({kf.label: 1})
curves = kf.ParameterGroup({keyframe.label: keyframe.Curve(1)})
return curves
for (frame_in, frame_curr, frame_out) in sliding_window(3, keyframes):
frame_in.value, frame_curr.value, frame_out.value = 0,1,0
c = kf.Curve({frame_in.t:frame_in, frame_curr.t:frame_curr, frame_out.t:frame_out},
label=frame_curr.label)
c = deepcopy(c)
curves.append(c)
begin, end = keyframes[:2], keyframes[-2:]
#outv = [begin]
begin[0].value = 1
begin[1].value = 0
end[0].value = 0
end[1].value = 1
outv = [kf.Curve(begin, label=begin[0].label)]
if len(keyframes) == 2:
return outv
outv += curves
outv += [kf.Curve(end, label=end[1].label)]
return kf.ParameterGroup(outv)
from .core import plot_curve
class KfDrawSchedule:
CATEGORY=CATEGORY
FUNCTION = 'main'
RETURN_TYPES = ("IMAGE",)
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"schedule": ("SCHEDULE", {"forceInput": True,}),
"n": ("INT", {"default": 64}),
"show_legend": ("BOOLEAN", {"default": True}),
}
}
def main(self, schedule, n, show_legend):
curves = schedule_to_weight_curves(schedule)
img_tensor = plot_curve(curves, n, show_legend, is_pgroup=True)
return (img_tensor,)
###################################################################
NODE_CLASS_MAPPINGS = {
"KfKeyframedCondition": KfKeyframedCondition,
"KfKeyframedConditionWithText":KfKeyframedConditionWithText,
"KfSetKeyframe": KfSetKeyframe,
"KfGetScheduleConditionAtTime": KfGetScheduleConditionAtTime,
"KfGetScheduleConditionSlice": KfGetScheduleConditionSlice,
"KfDrawSchedule": KfDrawSchedule,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"KfKeyframedCondition": "Keyframed Condition",
"KfKeyframedConditionWithText": "Schedule Prompt",
"KfSetKeyframe": "Set Keyframe",
"KfGetScheduleConditionAtTime": "Evaluate Schedule At T",
"KfGetScheduleConditionSlice": "Evaluate Schedule At T (Batch)",