Browse Source

Fix a few issues with the custom_nodes PR.

There only needs to be one example in the folder.
pull/14/head
comfyanonymous 2 years ago
parent
commit
1688f5024d
  1. 87
      custom_nodes/example_folder/main.py
  2. 4
      custom_nodes/example_folder/utils.py
  3. 49
      custom_nodes/example_node.py.example
  4. 22
      nodes.py

87
custom_nodes/example_folder/main.py

@ -1,87 +0,0 @@
from utils import waste_cpu_resource
class ExampleFolder:
"""
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.
FUNCTION (`str`):
The name of the entry-point method which will return a tuple. For example, if `FUNCTION = "execute"` then it will run Example().execute()
OUTPUT_NODE ([`bool`]):
WIP
CATEGORY (`str`):
WIP
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.
The type can be a string indicate a type or a list indicate selection.
Prebuilt types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
Input in type "INT", "STRING" or "FLOAT" will be converted automatically from a string to the corresponse Python type before passing and have special config
Argument: s (`None`): Useless ig
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": {
"string_field": ("STRING", {
"multiline": True, #Allow the input to be multilined
"default": "Hello World!"
}),
"int_field": ("INT", {
"default": 0,
"min": 0, #Minimum value
"max": 4096, #Maximum value
"step": 64 #Slider's step
}),
#Like INT
"print_to_screen": (["Enable", "Disable"], {"default": "Enable"})
},
#"hidden": {
# "prompt": "PROMPT",
# "extra_pnginfo": "EXTRA_PNGINFO"
#},
}
RETURN_TYPES = ("STRING", "INT", "FLOAT", "STRING")
FUNCTION = "test"
#OUTPUT_NODE = True
CATEGORY = "Example"
def test(self, string_field, int_field, print_to_screen):
rand_float = waste_cpu_resource()
if print_to_screen == "Enable":
print(f"""Your input contains:
string_field aka input text: {string_field}
int_field: {int_field}
A random float number: {rand_float}
""")
return (string_field, int_field, rand_float, print_to_screen)
NODE_CLASS_MAPPINGS = {
"ExampleFolder": ExampleFolder
}
"""
NODE_CLASS_MAPPINGS (dict): A dictionary contains all nodes you want to export
"""

4
custom_nodes/example_folder/utils.py

@ -1,4 +0,0 @@
import torch
def waste_cpu_resource():
x = torch.rand(1, 1e6, dtype=torch.float64).cpu()
return x.numpy()[0, 1]

49
custom_nodes/example.py → custom_nodes/example_node.py.example

@ -12,11 +12,13 @@ class Example:
RETURN_TYPES (`tuple`):
The type of each element in the output tulple.
FUNCTION (`str`):
The name of the entry-point method which will return a tuple. For example, if `FUNCTION = "execute"` then it will run Example().execute()
The name of the entry-point method. For example, if `FUNCTION = "execute"` then it will run Example().execute()
OUTPUT_NODE ([`bool`]):
WIP
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`):
WIP
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`.
@ -28,10 +30,10 @@ class Example:
def INPUT_TYPES(s):
"""
Return a dictionary which contains config for all input fields.
The type can be a string indicate a type or a list indicate selection.
Prebuilt types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
Input in type "INT", "STRING" or "FLOAT" will be converted automatically from a string to the corresponse Python type before passing and have special config
Argument: s (`None`): Useless ig
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:
@ -42,46 +44,43 @@ class Example:
"""
return {
"required": {
"string_field": ("STRING", {
"multiline": True, #Allow the input to be multilined
"default": "Hello World!"
}),
"image": ("IMAGE",),
"int_field": ("INT", {
"default": 0,
"min": 0, #Minimum value
"max": 4096, #Maximum value
"step": 64 #Slider's step
}),
#Like INT
"float_field": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
"print_to_screen": (["Enable", "Disable"], {"default": "Enable"})
"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!"
}),
},
#"hidden": {
# "prompt": "PROMPT",
# "extra_pnginfo": "EXTRA_PNGINFO"
#},
}
RETURN_TYPES = ("STRING", "INT", "FLOAT", "STRING")
RETURN_TYPES = ("IMAGE",)
FUNCTION = "test"
#OUTPUT_NODE = True
#OUTPUT_NODE = False
CATEGORY = "Example"
def test(self, string_field, int_field, float_field, print_to_screen):
if print_to_screen == "Enable":
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}
""")
return (string_field, int_field, float_field, print_to_screen)
#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
}
"""
NODE_CLASS_MAPPINGS (dict): A dictionary contains all nodes you want to export
"""

22
nodes.py

@ -5,6 +5,7 @@ import sys
import json
import hashlib
import copy
import traceback
from PIL import Image
from PIL.PngImagePlugin import PngInfo
@ -751,29 +752,28 @@ NODE_CLASS_MAPPINGS = {
CUSTOM_NODE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "custom_nodes")
def load_custom_nodes():
possible_modules = os.listdir(CUSTOM_NODE_PATH)
try:
#Comment out these two lines if you want to test
possible_modules.remove("example.py")
possible_modules.remove("example_folder")
if "__pycache__" in possible_modules:
possible_modules.remove("__pycache__")
except ValueError: pass
for possible_module in possible_modules:
module_path = os.path.join(CUSTOM_NODE_PATH, possible_module)
if os.path.isfile(module_path) and os.path.splitext(module_path)[1] != ".py": continue
module_name = "custom_node_module.{}".format(possible_module)
try:
if os.path.isfile(module_path):
module_spec = importlib.util.spec_from_file_location(os.path.basename(module_path), module_path)
module_spec = importlib.util.spec_from_file_location(module_name, module_path)
else:
module_spec = importlib.util.spec_from_file_location(module_path, "main.py")
module_spec = importlib.util.spec_from_file_location(module_name, os.path.join(module_path, "__init__.py"))
module = importlib.util.module_from_spec(module_spec)
sys.modules[module_name] = module
module_spec.loader.exec_module(module)
if getattr(module, "NODE_CLASS_MAPPINGS") is not None:
if hasattr(module, "NODE_CLASS_MAPPINGS") and getattr(module, "NODE_CLASS_MAPPINGS") is not None:
NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS)
else:
print(f"Skip {possible_module} module for custom nodes due to the lack of NODE_CLASS_MAPPINGS.")
except ImportError as e:
print(f"Cannot import {possible_module} module for custom nodes.")
print(e)
except Exception as e:
print(traceback.format_exc())
print(f"Cannot import {possible_module} module for custom nodes:", e)
load_custom_nodes()
Loading…
Cancel
Save