File size: 13,541 Bytes
1e3b872 |
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 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
"""
@author: rgthree
@title: Comfy Nodes
@nickname: rgthree
@description: A bunch of nodes I created that I also find useful.
"""
from glob import glob
import json
import os
import shutil
import re
import random
import execution
from .py.log import log
from .py.config import get_config_value
from .py.rgthree_server import *
from .py.context import RgthreeContext
from .py.context_switch import RgthreeContextSwitch
from .py.context_switch_big import RgthreeContextSwitchBig
from .py.display_any import RgthreeDisplayAny, RgthreeDisplayInt
from .py.lora_stack import RgthreeLoraLoaderStack
from .py.seed import RgthreeSeed
from .py.sdxl_empty_latent_image import RgthreeSDXLEmptyLatentImage
from .py.power_prompt import RgthreePowerPrompt
from .py.power_prompt_simple import RgthreePowerPromptSimple
from .py.image_inset_crop import RgthreeImageInsetCrop
from .py.context_big import RgthreeBigContext
from .py.ksampler_config import RgthreeKSamplerConfig
from .py.sdxl_power_prompt_postive import RgthreeSDXLPowerPromptPositive
from .py.sdxl_power_prompt_simple import RgthreeSDXLPowerPromptSimple
from .py.any_switch import RgthreeAnySwitch
from .py.context_merge import RgthreeContextMerge
from .py.context_merge_big import RgthreeContextMergeBig
from .py.image_comparer import RgthreeImageComparer
from .py.power_lora_loader import RgthreePowerLoraLoader
NODE_CLASS_MAPPINGS = {
RgthreeBigContext.NAME: RgthreeBigContext,
RgthreeContext.NAME: RgthreeContext,
RgthreeContextSwitch.NAME: RgthreeContextSwitch,
RgthreeContextSwitchBig.NAME: RgthreeContextSwitchBig,
RgthreeContextMerge.NAME: RgthreeContextMerge,
RgthreeContextMergeBig.NAME: RgthreeContextMergeBig,
RgthreeDisplayInt.NAME: RgthreeDisplayInt,
RgthreeDisplayAny.NAME: RgthreeDisplayAny,
RgthreeLoraLoaderStack.NAME: RgthreeLoraLoaderStack,
RgthreeSeed.NAME: RgthreeSeed,
RgthreeImageInsetCrop.NAME: RgthreeImageInsetCrop,
RgthreePowerPrompt.NAME: RgthreePowerPrompt,
RgthreePowerPromptSimple.NAME: RgthreePowerPromptSimple,
RgthreeKSamplerConfig.NAME: RgthreeKSamplerConfig,
RgthreeSDXLEmptyLatentImage.NAME: RgthreeSDXLEmptyLatentImage,
RgthreeSDXLPowerPromptPositive.NAME: RgthreeSDXLPowerPromptPositive,
RgthreeSDXLPowerPromptSimple.NAME: RgthreeSDXLPowerPromptSimple,
RgthreeAnySwitch.NAME: RgthreeAnySwitch,
RgthreeImageComparer.NAME: RgthreeImageComparer,
RgthreePowerLoraLoader.NAME: RgthreePowerLoraLoader,
}
# WEB_DIRECTORY is the comfyui nodes directory that ComfyUI will link and auto-load.
WEB_DIRECTORY = "./web/comfyui"
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
DIR_WEB = os.path.abspath(f'{THIS_DIR}/{WEB_DIRECTORY}')
DIR_PY = os.path.abspath(f'{THIS_DIR}/py')
# remove old directories
OLD_DIRS = [
os.path.abspath(f'{THIS_DIR}/../../web/extensions/rgthree'),
os.path.abspath(f'{THIS_DIR}/../../web/extensions/rgthree-comfy'),
]
for old_dir in OLD_DIRS:
if os.path.exists(old_dir):
shutil.rmtree(old_dir)
__all__ = ['NODE_CLASS_MAPPINGS', 'WEB_DIRECTORY']
NOT_NODES = ['constants', 'log', 'utils', 'rgthree', 'rgthree_server', 'image_clipbaord', 'config']
nodes = []
for file in glob(os.path.join(DIR_PY, '*.py')) + glob(os.path.join(DIR_WEB, '*.js')):
name = os.path.splitext(os.path.basename(file))[0]
if name in NOT_NODES or name in nodes:
continue
if name.startswith('_') or name.startswith('base') or 'utils' in name:
continue
nodes.append(name)
if name == 'display_any':
nodes.append('display_int')
print()
adjs = ['exciting', 'extraordinary', 'epic', 'fantastic', 'magnificent']
log(f'Loaded {len(nodes)} {random.choice(adjs)} nodes.', color='BRIGHT_GREEN')
# Alright, I don't like doing this, but until https://github.com/comfyanonymous/ComfyUI/issues/1502
# and/or https://github.com/comfyanonymous/ComfyUI/pull/1503 is pulled into ComfyUI, we need a way
# to optimize the recursion that happens on prompt eval. This is particularly important for
# rgthree nodes because workflows can contain many context nodes, but the problem would exist for
# other nodes' (like "pipe" nodes, efficieny nodes). With `Context Big` nodes being
# introduced, the number of input recursion that happens in these methods is exponential with a
# saving of 1000's of percentage points over.
# We'll use this to check if we _can_ patch execution. Other work to change the execution may
# remove these methods, and we want to ensure people's apps do not break.
could_patch_execution = (hasattr(execution, 'recursive_output_delete_if_changed') and
hasattr(execution, 'recursive_will_execute') and
hasattr(execution.PromptExecutor, 'execute'))
if get_config_value('features.patch_recursive_execution') is True:
if not could_patch_execution:
log("NOTE: Will NOT use rgthree's optimized recursive execution as ComfyUI has changed.",
color='YELLOW')
else:
log("Will use rgthree's optimized recursive execution.", color='BRIGHT_GREEN')
class RgthreePatchRecursiveExecute_Set_patch_recursive_execution_to_false_if_not_working:
"""A fake 'list' that the caller for recursive_will_execute expects but we override such that
`len(inst)` will return the count number, and `inst[-1]` will return the unique_id. Since that
all the caller cares about, we can save several minutes and many MB of ram by simply counting
numbers instead of concatenating a list of millions (only to count it). However the caller
expects such a list, so we fake it with this.
This mimics the enhancement from https://github.com/rgthree/ComfyUI/commit/50b3fb1 but without
modifying the execution.py
"""
def __init__(self, unique_id):
self.unique_id = unique_id
self.count = 0
def add(self, value):
self.count += value
def __getitem__(self, key):
"""Returns the `unique_id` with '-1' since that's what the caller expects."""
if key == -1:
return self.unique_id
# This one would future proof the proposed changes, in that case "0" is the count
if key == 0:
return self.count
else:
return -1
def __len__(self):
"""Returns the "count" of the "list" as if we were building up a list instea of just
incrementing `count`.
"""
return self.count
# The following (hopefully) future proofs if https://github.com/rgthree/ComfyUI/commit/50b3fb1
# goes in, which changes from using `len` on a list, to sort directly (and, thus "<" and ">").
def __gt__(self, other):
return self.count > other
def __lt__(self, other):
return self.count < other
def __str__(self):
return str((
self.count,
self.unique_id,
))
# Caches which will be cleared on each run
execution.rgthree_cache_recursive_output_delete_if_changed_output = {}
execution.rgthree_cache_recursive_will_execute = {}
execution.rgthree_is_currently_optimized = False
def rgthree_execute(self, *args, **kwargs):
""" A patch of ComfyUI's default execution for optimization (or un-optimization) via config."""
if get_config_value('features.patch_recursive_execution') is True:
if could_patch_execution:
log("Using rgthree's optimized recursive execution.", color='GREEN')
# When we execute, we'll reset our global cache here.
execution.rgthree_cache_recursive_output_delete_if_changed_output = {}
execution.rgthree_cache_recursive_will_execute = {}
if not execution.rgthree_is_currently_optimized:
log("First run patching recursive_output_delete_if_changed and recursive_will_execute.",
color='GREEN',
msg_color='RESET')
log(
"Note: \33[0mIf execution seems broken due to forward ComfyUI changes, you can disable " +
"the optimization from rgthree settings in ComfyUI.",
color='YELLOW')
execution.rgthree_old_recursive_output_delete_if_changed = execution.recursive_output_delete_if_changed
execution.recursive_output_delete_if_changed = rgthree_recursive_output_delete_if_changed
execution.rgthree_old_recursive_will_execute = execution.recursive_will_execute
execution.recursive_will_execute = rgthree_recursive_will_execute
execution.rgthree_is_currently_optimized = True
elif execution.rgthree_is_currently_optimized:
log("Removing optimizations to recursive_output_delete_if_changed and recursive_will_execute.",
color='YELLOW',
msg_color='RESET')
log("You can enable optimization in the rgthree settings in ComfyUI.", color='CYAN')
execution.recursive_output_delete_if_changed = execution.rgthree_old_recursive_output_delete_if_changed
execution.recursive_will_execute = execution.rgthree_old_recursive_will_execute
execution.rgthree_is_currently_optimized = False
# We always call the original execute, it's just whether we patch or unpacth first.
return self.rgthree_old_execute(*args, **kwargs)
# We always patch execute, so we can check if we want to do work. Up in rgthree_execute we will
# either patch or unpatch recursive_will_execute recursive_output_delete_if_changed at runtime when
# config changes.
execution.PromptExecutor.rgthree_old_execute = execution.PromptExecutor.execute
execution.PromptExecutor.execute = rgthree_execute
def rgthree_recursive_will_execute(prompt, outputs, current_item, *args, **kwargs):
"""Patches recursive_will_execute function to cache the result of each output."""
unique_id = current_item
inputs = prompt[unique_id]['inputs']
will_execute = RgthreePatchRecursiveExecute_Set_patch_recursive_execution_to_false_if_not_working(
unique_id)
if unique_id in outputs:
return will_execute
will_execute.add(1)
for x in inputs:
input_data = inputs[x]
if isinstance(input_data, list):
input_unique_id = input_data[0]
output_index = input_data[1]
node_output_cache_key = f'{input_unique_id}.{output_index}'
will_execute_value = None
# If this node's output has already been recursively evaluated, then we can reuse.
if node_output_cache_key in execution.rgthree_cache_recursive_will_execute:
will_execute_value = execution.rgthree_cache_recursive_will_execute[node_output_cache_key]
elif input_unique_id not in outputs:
will_execute_value = execution.recursive_will_execute(prompt, outputs, input_unique_id,
*args, **kwargs)
execution.rgthree_cache_recursive_will_execute[node_output_cache_key] = will_execute_value
if will_execute_value is not None:
will_execute.add(len(will_execute_value))
return will_execute
def rgthree_recursive_output_delete_if_changed(prompt, old_prompt, outputs, current_item, *args,
**kwargs):
"""Patches recursive_output_delete_if_changed function to cache the result of each output."""
unique_id = current_item
inputs = prompt[unique_id]['inputs']
class_type = prompt[unique_id]['class_type']
class_def = execution.nodes.NODE_CLASS_MAPPINGS[class_type]
is_changed_old = ''
is_changed = ''
to_delete = False
if hasattr(class_def, 'IS_CHANGED'):
if unique_id in old_prompt and 'is_changed' in old_prompt[unique_id]:
is_changed_old = old_prompt[unique_id]['is_changed']
if 'is_changed' not in prompt[unique_id]:
input_data_all = execution.get_input_data(inputs, class_def, unique_id, outputs)
if input_data_all is not None:
try:
#is_changed = class_def.IS_CHANGED(**input_data_all)
is_changed = execution.map_node_over_list(class_def, input_data_all, "IS_CHANGED")
prompt[unique_id]['is_changed'] = is_changed
except:
to_delete = True
else:
is_changed = prompt[unique_id]['is_changed']
if unique_id not in outputs:
return True
if not to_delete:
if is_changed != is_changed_old:
to_delete = True
elif unique_id not in old_prompt:
to_delete = True
elif inputs == old_prompt[unique_id]['inputs']:
for x in inputs:
input_data = inputs[x]
if isinstance(input_data, list):
input_unique_id = input_data[0]
output_index = input_data[1]
node_output_cache_key = f'{input_unique_id}.{output_index}'
# If this node's output has already been recursively evaluated, then we can stop.
if node_output_cache_key in execution.rgthree_cache_recursive_output_delete_if_changed_output:
to_delete = execution.rgthree_cache_recursive_output_delete_if_changed_output[
node_output_cache_key]
elif input_unique_id in outputs:
to_delete = execution.recursive_output_delete_if_changed(prompt, old_prompt, outputs,
input_unique_id, *args,
**kwargs)
execution.rgthree_cache_recursive_output_delete_if_changed_output[
node_output_cache_key] = to_delete
else:
to_delete = True
if to_delete:
break
else:
to_delete = True
if to_delete:
d = outputs.pop(unique_id)
del d
return to_delete
print()
|