-
Notifications
You must be signed in to change notification settings - Fork 372
Expand file tree
/
Copy pathnodes_registry.py
More file actions
156 lines (118 loc) · 5.47 KB
/
nodes_registry.py
File metadata and controls
156 lines (118 loc) · 5.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import re
from functools import wraps
from typing import Callable, Optional, Type
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
NODES_DISPLAY_NAME_PREFIX = "🅛🅣🅧"
EXPERIMENTAL_DISPLAY_NAME_PREFIX = "(Experimental 🧪)"
DEPRECATED_DISPLAY_NAME_PREFIX = "(Deprecated 🚫)"
DEFAULT_CATEGORY_NAME = "Lightricks"
def register_node(node_class: Type, name: str, description: str) -> None:
"""
Register a ComfyUI node class to ComfyUI's global nodes' registry.
Args:
node_class (Type): The class of the node to be registered.
name (str): The name of the node.
description (str): The short user-friendly description of the node.
Raises:
ValueError: If `node_class` is not a class, or `class_name` or `display_name` is not a string.
"""
if not isinstance(node_class, type):
raise ValueError("`node_class` must be a class")
if not isinstance(name, str):
raise ValueError("`name` must be a string")
if not isinstance(description, str):
raise ValueError("`description` must be a string")
NODE_CLASS_MAPPINGS[name] = node_class
NODE_DISPLAY_NAME_MAPPINGS[name] = description
def _is_v3_node(node_class: Type) -> bool:
"""Check if the node class is a v3 node (has define_schema method)."""
return hasattr(node_class, "define_schema") and callable(
getattr(node_class, "define_schema")
)
def _wrap_define_schema(node_class: Type, display_name: str) -> None:
"""Wrap the define_schema method to inject the display_name into the schema."""
original_define_schema = node_class.define_schema
@classmethod
@wraps(original_define_schema.__func__)
def wrapped_define_schema(cls):
schema = original_define_schema.__func__(cls)
# Only set display_name if not already set in the schema
if schema.display_name is None:
schema.display_name = display_name
return schema
node_class.define_schema = wrapped_define_schema
def comfy_node(
node_class: Optional[Type] = None,
name: Optional[str] = None,
description: Optional[str] = None,
experimental: bool = False,
deprecated: bool = False,
skip: bool = False,
) -> Callable:
"""
Decorator for registering a node class with optional name, description, and status flags.
Args:
node_class (Type): The class of the node to be registered.
name (str, optional): The name of the class. If not provided, the class name will be used.
description (str, optional): The description of the class.
If not provided, an auto-formatted description will be used based on the class name.
experimental (bool): Flag indicating if the class is experimental. Defaults to False.
deprecated (bool): Flag indicating if the class is deprecated. Defaults to False.
skip (bool): Flag indicating if the node registration should be skipped. Defaults to False.
This is useful for conditionally registering nodes based on certain conditions
(e.g. unavailability of certain dependencies).
Returns:
Callable: The decorator function.
Raises:
ValueError: If `node_class` is not a class.
"""
def decorator(node_class: Type) -> Type:
if skip:
return node_class
if not isinstance(node_class, type):
raise ValueError("`node_class` must be a class")
nonlocal name, description
if name is None:
name = node_class.__name__
# Remove possible "Node" suffix from the class name, e.g. "EditImageNode -> EditImage"
if name is not None and name.endswith("Node"):
name = name[:-4]
description = _format_description(description, name, experimental, deprecated)
# For v3 nodes, wrap define_schema to inject the display_name
if _is_v3_node(node_class):
_wrap_define_schema(node_class, description)
register_node(node_class, name, description)
return node_class
# If the decorator is used without parentheses
if node_class is None:
return decorator
else:
return decorator(node_class)
def _format_description(
description: str, class_name: str, experimental: bool, deprecated: bool
) -> str:
"""Format nodes display name to a standard format"""
# If description is not provided, auto-generate one based on the class name
if description is None:
description = camel_case_to_spaces(class_name)
# Strip the prefix if it's already there
prefix_len = len(NODES_DISPLAY_NAME_PREFIX)
if description.startswith(NODES_DISPLAY_NAME_PREFIX):
description = description[prefix_len:].lstrip()
# Add the deprecated / experimental prefixes
if deprecated:
description = f"{DEPRECATED_DISPLAY_NAME_PREFIX} {description}"
elif experimental:
description = f"{EXPERIMENTAL_DISPLAY_NAME_PREFIX} {description}"
# Add the prefix
description = f"{NODES_DISPLAY_NAME_PREFIX} {description}"
return description
def camel_case_to_spaces(text: str) -> str:
# Add space before each capital letter except the first one
spaced_text = re.sub(r"(?<=[a-z])(?=[A-Z])", " ", text)
# Handle sequences of uppercase letters followed by a lowercase letter
spaced_text = re.sub(r"(?<=[A-Z])(?=[A-Z][a-z])", " ", spaced_text)
# Handle sequences of uppercase letters not followed by a lowercase letter
spaced_text = re.sub(r"(?<=[A-Z])(?=[A-Z][A-Z][a-z])", " ", spaced_text)
return spaced_text