diff --git a/README.md b/README.md index 5c6e14c..c04c515 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,11 @@ 🚧 Work In Progress 🚧 - ComfyUI nodes to facilitate value keyframing by providing an interface for using [keyframed](https://github.com/dmarx/keyframed) in ComfyUI workflows. -...Open question: if I make this, what will differentiate it from https://github.com/FizzleDorf/ComfyUI_FizzNodes ? \ No newline at end of file +...Open question: if I make this, what will differentiate it from https://github.com/FizzleDorf/ComfyUI_FizzNodes ? + +* easier curve composition +* easier to change interpolators/easing functions + +# Philosophy + +Curves, interpolators, and keyframes are objects that can be passed around, plugged and unplugged, and interchanged. \ No newline at end of file diff --git a/__init__.py b/__init__.py index e69de29..186760a 100644 --- a/__init__.py +++ b/__init__.py @@ -0,0 +1,3 @@ +from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ =["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/example.py b/example.py new file mode 100644 index 0000000..0b61415 --- /dev/null +++ b/example.py @@ -0,0 +1,102 @@ +class Example: + """ + A example node + + Class methods + ------------- + INPUT_TYPES (dict): + Tell the main program input parameters of nodes. + + Attributes + ---------- + RETURN_TYPES (`tuple`): + The type of each element in the output tulple. + RETURN_NAMES (`tuple`): + Optional: The name of each output in the output tulple. + FUNCTION (`str`): + The name of the entry-point method. For example, if `FUNCTION = "execute"` then it will run Example().execute() + OUTPUT_NODE ([`bool`]): + If this node is an output node that outputs a result/image from the graph. The SaveImage node is an example. + The backend iterates on these output nodes and tries to execute all their parents if their parent graph is properly connected. + Assumed to be False if not present. + CATEGORY (`str`): + The category the node should appear in the UI. + execute(s) -> tuple || None: + The entry point method. The name of this method must be the same as the value of property `FUNCTION`. + For example, if `FUNCTION = "execute"` then this method's name must be `execute`, if `FUNCTION = "foo"` then it must be `foo`. + """ + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + """ + Return a dictionary which contains config for all input fields. + Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT". + Input types "INT", "STRING" or "FLOAT" are special values for fields on the node. + The type can be a list for selection. + + Returns: `dict`: + - Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required` + - Value input_fields (`dict`): Contains input fields config: + * Key field_name (`string`): Name of a entry-point method's argument + * Value field_config (`tuple`): + + First value is a string indicate the type of field or a list for selection. + + Secound value is a config for type "INT", "STRING" or "FLOAT". + """ + return { + "required": { + "image": ("IMAGE",), + "int_field": ("INT", { + "default": 0, + "min": 0, #Minimum value + "max": 4096, #Maximum value + "step": 64, #Slider's step + "display": "number" # Cosmetic only: display as "number" or "slider" + }), + "float_field": ("FLOAT", { + "default": 1.0, + "min": 0.0, + "max": 10.0, + "step": 0.01, + "round": 0.001, #The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "number"}), + "print_to_screen": (["enable", "disable"],), + "string_field": ("STRING", { + "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node + "default": "Hello World!" + }), + }, + } + + RETURN_TYPES = ("IMAGE",) + #RETURN_NAMES = ("image_output_name",) + + FUNCTION = "test" + + #OUTPUT_NODE = False + + CATEGORY = "Example" + + def test(self, image, string_field, int_field, float_field, print_to_screen): + if print_to_screen == "enable": + print(f"""Your input contains: + string_field aka input text: {string_field} + int_field: {int_field} + float_field: {float_field} + """) + #do some processing on the image, in this example I just invert it + image = 1.0 - image + return (image,) + + +# A dictionary that contains all nodes you want to export with their names +# NOTE: names should be globally unique +NODE_CLASS_MAPPINGS = { + "Example": Example +} + +# A dictionary that contains the friendly/humanly readable titles for the nodes +NODE_DISPLAY_NAME_MAPPINGS = { + "Example": "Example Node" +} \ No newline at end of file diff --git a/nodes.py b/nodes.py index f181243..8193bfb 100644 --- a/nodes.py +++ b/nodes.py @@ -1,4 +1,77 @@ import keyframed as kf +from keyframed.dsl import curve_from_cn_string CATEGORY = "keyframed" +class _BASE: + CATEGORY=CATEGORY + FUNCTION = 'main' + +class KfCurveFromString(_BASE): + RETURN_TYPES = ("KEYFRAMED_CURVE",) + + @classmethod + def INPUT_TYPES(s): + return { + "required": {"chigozie_string": ("STRING", { + "multiline": True, #True if you want the field to look like the one on the ClipTextEncode node + "default": "0: 1" + }), + }, + } + + def main(self, chigozie_string): + return curve_from_cn_string(chigozie_string) + +class KfCurveFromYAML(_BASE): + RETURN_TYPES = ("KEYFRAMED_CURVE",) + + @classmethod + def INPUT_TYPES(s): + return { + "required": {"yaml": ("STRING", { + "multiline": True, #True if you want the field to look like the one on the ClipTextEncode node + "default": """curve: +- - 0 + - 0 + - linear +- - 1 + - 1 +loop: false +bounce: false +duration: 1 +label: foo""" + }), + }, + } + + def main(self, yaml): + return kf.serialization.from_yaml(yaml) + +class KfEvaluateCurveAtT(_BASE): + RETURN_TYPES = ("FLOAT","INT") + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "curve": ("KEYFRAMED_CURVE",), + "t": ("INT",) + }, + } + + def main(self, curve, t): + return curve[t] + +NODE_CLASS_MAPPINGS = { + "KfCurveFromString": KfCurveFromString, + "KfCurveFromYAML": KfCurveFromYAML, + "KfEvaluateCurveAtT": KfEvaluateCurveAtT, +} + +# A dictionary that contains the friendly/humanly readable titles for the nodes +NODE_DISPLAY_NAME_MAPPINGS = { + "KfCurveFromString": "Curve From String", + "KfCurveFromYAML": "Curve From YAML", + "KfEvaluateCurveAtT": "Evaluate Curve At T", +} \ No newline at end of file