Compare commits

...
10 Commits
Author SHA1 Message Date
Dr.Lt.Data cd89590a7d FEAT: List Bridge node is added. 2024-11-07 23:33:59 +09:00
Vedat Baday dc70f40eff perf: cache pip installed packages (#803) 2024-11-03 04:13:37 +09:00
Vedat Baday 825296c3e0 fix: install manual (#794) 2024-11-02 05:39:08 +09:00
Dr.Lt.Data bf0e86a10d FIXED: ImageListToImageBatch - make sure output is not list when item size is less than 2
https://github.com/comfyanonymous/ComfyUI/issues/5039
2024-11-01 02:28:15 +09:00
Dr.Lt.Data 727295b52e fix: wildcard bugs
- cannot remove comment
- cannot populate <single-wildcard-pattern> if `{count$$<single-wildcard-pattern>}`

https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/789

https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/788
2024-10-24 00:25:57 +09:00
Dr.Lt.Data 6c23f7691d fixed: wildcard - invalid regex syntax
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/790
2024-10-22 12:20:59 +09:00
Dr.Lt.Data 6248f31402 improved: lora in wildcard - Allows omitting leading zero when specifying floating-point values less than 1.
e.g. .1 instead of 0.1

https://github.com/ltdrdata/ComfyUI-Inspire-Pack/issues/178
2024-10-21 12:44:00 +09:00
Dr.Lt.Data cb0b652703 REFACTOR: GeneralSwitch - better implementation 2024-10-16 21:42:41 +09:00
Dr.Lt.Data 9c4fa5fb27 FIXED: switch any <-> inverse switch disconnection issue
https://github.com/ltdrdata/ComfyUI-extension-tutorials/issues/58#issuecomment-2415330114
2024-10-16 20:27:33 +09:00
Dr.Lt.Data 759b4d0dc9 fix: invalid version check code
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/782
2024-10-15 23:34:25 +09:00
10 changed files with 122 additions and 18 deletions
+1
View File
@@ -286,6 +286,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
* `Negative Cond Placeholder` - Models like FLUX.1 do not use Negative Conditioning. This is a placeholder node for them. You can use FLUX.1 by replacing the Negative Conditioning used in Impact KSampler, KSampler (Inspire), and Detailer with this node. * `Negative Cond Placeholder` - Models like FLUX.1 do not use Negative Conditioning. This is a placeholder node for them. You can use FLUX.1 by replacing the Negative Conditioning used in Impact KSampler, KSampler (Inspire), and Detailer with this node.
* `Execution Order Controller` - A helper node that can forcibly control the execution order of nodes. * `Execution Order Controller` - A helper node that can forcibly control the execution order of nodes.
* Connect the output of the node that should be executed first to the signal, and make the input of the node that should be executed later pass through this node. * Connect the output of the node that should be executed first to the signal, and make the input of the node that should be executed later pass through this node.
* `List Bridge` - When passing the list output through this node, it collects and organizes the data before forwarding it, which ensures that the previous stage's sub-workflow has been completed.
## MMDet nodes (DEPRECATED) - Don't use these nodes ## MMDet nodes (DEPRECATED) - Don't use these nodes
+2
View File
@@ -273,6 +273,7 @@ NODE_CLASS_MAPPINGS = {
"StringListToString": StringListToString, "StringListToString": StringListToString,
"WildcardPromptFromString": WildcardPromptFromString, "WildcardPromptFromString": WildcardPromptFromString,
"ImpactExecutionOrderController": ImpactExecutionOrderController, "ImpactExecutionOrderController": ImpactExecutionOrderController,
"ImpactListBridge": ImpactListBridge,
"RemoveNoiseMask": RemoveNoiseMask, "RemoveNoiseMask": RemoveNoiseMask,
@@ -393,6 +394,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"ImpactSwitch": "Switch (Any)", "ImpactSwitch": "Switch (Any)",
"ImpactInversedSwitch": "Inversed Switch (Any)", "ImpactInversedSwitch": "Inversed Switch (Any)",
"ImpactExecutionOrderController": "Execution Order Controller", "ImpactExecutionOrderController": "Execution Order Controller",
"ImpactListBridge": "List Bridge",
"MasksToMaskList": "Mask Batch to Mask List", "MasksToMaskList": "Mask Batch to Mask List",
"MaskListToMaskBatch": "Mask List to Mask Batch", "MaskListToMaskBatch": "Mask List to Mask Batch",
+39
View File
@@ -1,10 +1,13 @@
import functools
import os import os
import re
import shutil import shutil
import sys import sys
import subprocess import subprocess
import threading import threading
import locale import locale
import traceback import traceback
from typing import Set
if sys.argv[0] == 'install.py': if sys.argv[0] == 'install.py':
@@ -66,6 +69,42 @@ def process_wrap(cmd_str, cwd=None, handler=None, env=None):
stderr_thread.join() stderr_thread.join()
return process.wait() return process.wait()
@functools.cache
def get_installed_packages() -> Set[str]:
try:
result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True)
pip_list = set([line.split()[0].lower() for line in result.split('\n') if line.strip()])
return pip_list
except subprocess.CalledProcessError as e:
raise Exception(f"[ComfyUI-Impact-Pack] Failed to retrieve the information of installed pip packages.")
def is_package_installed(name: str) -> bool:
name = name.strip()
pattern = r'([^<>!=]+)([<>!=]=?)'
match = re.search(pattern, name)
if match:
name = match.group(1)
result = name.lower() in get_installed_packages()
return result
def is_requirements_installed(file_path: str) -> bool:
print(f"Requirements file: {file_path}")
if os.path.exists(file_path):
with open(file_path, "r") as file:
lines = file.readlines()
for line in lines:
if not is_package_installed(line):
return False
pip_install = [sys.executable, "-m", "pip", "install", "-U"]
# --- # ---
+7 -3
View File
@@ -353,7 +353,7 @@ app.registerExtension({
} }
this.outputs[0].type = origin_type; this.outputs[0].type = origin_type;
this.outputs[0].name = origin_type; this.outputs[0].name = 'output1';
} }
return; return;
@@ -483,8 +483,12 @@ app.registerExtension({
if(this.inputs[0].type == '*'){ if(this.inputs[0].type == '*'){
const node = app.graph.getNodeById(link_info.origin_id); const node = app.graph.getNodeById(link_info.origin_id);
let origin_type = node.outputs[link_info.origin_slot].type; let origin_type = node.outputs[link_info.origin_slot]?.type;
if(link_info.target_slot == 0 && this.inputs.length > 1) {
origin_type = this.inputs[1].type;
node.connect(link_info.origin_slot, node.id, 'input1');
}
if(origin_type == '*') { if(origin_type == '*') {
this.disconnectInput(link_info.target_slot); this.disconnectInput(link_info.target_slot);
return; return;
+2 -2
View File
@@ -119,7 +119,7 @@ class PreviewBridge:
is_empty_mask = torch.all(mask == 0) is_empty_mask = torch.all(mask == 0)
if block and is_empty_mask and core.is_execution_model_version_supported: if block and is_empty_mask and core.is_execution_model_version_supported():
from comfy_execution.graph import ExecutionBlocker from comfy_execution.graph import ExecutionBlocker
result = ExecutionBlocker(None), ExecutionBlocker(None) result = ExecutionBlocker(None), ExecutionBlocker(None)
elif block and is_empty_mask: elif block and is_empty_mask:
@@ -362,7 +362,7 @@ class PreviewBridgeLatent:
res_latent = latent res_latent = latent
if block and is_empty_mask and core.is_execution_model_version_supported: if block and is_empty_mask and core.is_execution_model_version_supported():
from comfy_execution.graph import ExecutionBlocker from comfy_execution.graph import ExecutionBlocker
result = ExecutionBlocker(None), ExecutionBlocker(None) result = ExecutionBlocker(None), ExecutionBlocker(None)
elif block and is_empty_mask: elif block and is_empty_mask:
+1 -1
View File
@@ -1,7 +1,7 @@
import configparser import configparser
import os import os
version_code = [7, 10] version_code = [7, 11]
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
dependency_version = 23 dependency_version = 23
+24 -1
View File
@@ -691,7 +691,7 @@ class ImpactControlBridge:
def doit(self, value, mode, behavior="Stop", unique_id=None, prompt=None, extra_pnginfo=None): def doit(self, value, mode, behavior="Stop", unique_id=None, prompt=None, extra_pnginfo=None):
global error_skip_flag global error_skip_flag
if core.is_execution_model_version_supported: if core.is_execution_model_version_supported():
from comfy_execution.graph import ExecutionBlocker from comfy_execution.graph import ExecutionBlocker
else: else:
print("[Impact Pack] ImpactControlBridge: ComfyUI is outdated. The 'Stop' behavior cannot function properly.") print("[Impact Pack] ImpactControlBridge: ComfyUI is outdated. The 'Stop' behavior cannot function properly.")
@@ -766,6 +766,29 @@ class ImpactExecutionOrderController:
return signal, value return signal, value
class ImpactListBridge:
@classmethod
def INPUT_TYPES(cls):
return {"required": {
"list_input": (any_typ,),
}}
FUNCTION = "doit"
DESCRIPTION = "When passing the list output through this node, it collects and organizes the data before forwarding it, which ensures that the previous stage's sub-workflow has been completed."
CATEGORY = "ImpactPack/Util"
RETURN_TYPES = (any_typ, )
RETURN_NAMES = ("list_output", )
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True, )
@staticmethod
def doit(list_input):
return (list_input,)
original_handle_execution = execution.PromptExecutor.handle_execution_error original_handle_execution = execution.PromptExecutor.handle_execution_error
+13 -6
View File
@@ -17,9 +17,16 @@ class GeneralSwitch:
dyn_inputs = {"input1": (any_typ, {"lazy": True, "tooltip": "Any input. When connected, one more input slot is added."}), } dyn_inputs = {"input1": (any_typ, {"lazy": True, "tooltip": "Any input. When connected, one more input slot is added."}), }
if core.is_execution_model_version_supported(): if core.is_execution_model_version_supported():
stack = inspect.stack() stack = inspect.stack()
if stack[2].function == 'get_input_info' and stack[3].function == 'add_node': if stack[2].function == 'get_input_info':
for x in range(2, 200): # bypass validation
dyn_inputs[f"input{x}"] = (any_typ, {"lazy": True}) class AllContainer:
def __contains__(self, item):
return True
def __getitem__(self, key):
return any_typ, {"lazy": True}
dyn_inputs = AllContainer()
inputs = {"required": { inputs = {"required": {
"select": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1, "tooltip": "The input number you want to output among the inputs"}), "select": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1, "tooltip": "The input number you want to output among the inputs"}),
@@ -163,7 +170,7 @@ class GeneralInversedSwitch:
CATEGORY = "ImpactPack/Util" CATEGORY = "ImpactPack/Util"
def doit(self, select, prompt, unique_id, input, **kwargs): def doit(self, select, prompt, unique_id, input, **kwargs):
if core.is_execution_model_version_supported: if core.is_execution_model_version_supported():
from comfy_execution.graph import ExecutionBlocker from comfy_execution.graph import ExecutionBlocker
else: else:
print("[Impact Pack] InversedSwitch: ComfyUI is outdated. The 'select_on_execution' mode cannot function properly.") print("[Impact Pack] InversedSwitch: ComfyUI is outdated. The 'select_on_execution' mode cannot function properly.")
@@ -181,7 +188,7 @@ class GeneralInversedSwitch:
for i in range(0, cnt + 1): for i in range(0, cnt + 1):
if select == i+1: if select == i+1:
res.append(input) res.append(input)
elif core.is_execution_model_version_supported: elif core.is_execution_model_version_supported():
res.append(ExecutionBlocker(None)) res.append(ExecutionBlocker(None))
else: else:
res.append(None) res.append(None)
@@ -366,7 +373,7 @@ class ImageListToImageBatch:
def doit(self, images): def doit(self, images):
if len(images) <= 1: if len(images) <= 1:
return (images,) return (images[0],)
else: else:
image1 = images[0] image1 = images[0]
for image2 in images[1:]: for image2 in images[1:]:
+32 -4
View File
@@ -58,11 +58,11 @@ def read_wildcard_dict(wildcard_path):
try: try:
with open(file_path, 'r', encoding="ISO-8859-1") as f: with open(file_path, 'r', encoding="ISO-8859-1") as f:
lines = f.read().splitlines() lines = f.read().splitlines()
wildcard_dict[key] = lines wildcard_dict[key] = [x for x in lines if not x.strip().startswith('#')]
except yaml.reader.ReaderError: except yaml.reader.ReaderError:
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f: with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
lines = f.read().splitlines() lines = f.read().splitlines()
wildcard_dict[key] = lines wildcard_dict[key] = [x for x in lines if not x.strip().startswith('#')]
elif file.endswith('.yaml'): elif file.endswith('.yaml'):
file_path = os.path.join(root, file) file_path = os.path.join(root, file)
@@ -150,7 +150,7 @@ def process(text, seed=None):
matches = re.findall(wildcard_pattern, multi_select_pattern[1]) matches = re.findall(wildcard_pattern, multi_select_pattern[1])
if len(options) == 1 and matches: if len(options) == 1 and matches:
# count$$<single wildcard> # count$$<single wildcard>
options = local_wildcard_dict.get(matches[0]) options = get_wildcard_options(multi_select_pattern[1])
else: else:
# count$$opt1|opt2|... # count$$opt1|opt2|...
options[0] = multi_select_pattern[1] options[0] = multi_select_pattern[1]
@@ -199,6 +199,34 @@ def process(text, seed=None):
return replaced_string, replacements_found return replaced_string, replacements_found
def get_wildcard_options(string):
pattern = r"__([\w.\-+/*\\]+?)__"
matches = re.findall(pattern, string)
options = []
for match in matches:
keyword = match.lower()
keyword = wildcard_normalize(keyword)
if keyword in local_wildcard_dict:
options.extend(local_wildcard_dict[keyword])
elif '*' in keyword:
subpattern = keyword.replace('*', '.*').replace('+', '\\+')
total_patterns = []
found = False
for k, v in local_wildcard_dict.items():
if re.match(subpattern, k) is not None or re.match(subpattern, k+'/') is not None:
total_patterns += v
found = True
if found:
options.extend(total_patterns)
elif '/' not in keyword:
string_fallback = string.replace(f"__{match}__", f"__*/{match}__", 1)
options.extend(get_wildcard_options(string_fallback))
return options
def replace_wildcard(string): def replace_wildcard(string):
pattern = r"__([\w.\-+/*\\]+?)__" pattern = r"__([\w.\-+/*\\]+?)__"
matches = re.findall(pattern, string) matches = re.findall(pattern, string)
@@ -259,7 +287,7 @@ def process(text, seed=None):
def is_numeric_string(input_str): def is_numeric_string(input_str):
return re.match(r'^-?\d+(\.\d+)?$', input_str) is not None return re.match(r'^-?(\d*\.?\d+|\d+\.?\d*)$', input_str) is not None
def safe_float(x): def safe_float(x):
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-impact-pack" name = "comfyui-impact-pack"
description = "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler." description = "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler."
version = "7.10" version = "7.11"
license = { file = "LICENSE.txt" } license = { file = "LICENSE.txt" }
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"] dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]