From b2c7e06e79ba7490ec72e940ffd9b7816b368a84 Mon Sep 17 00:00:00 2001 From: "Dr.Lt.Data" Date: Tue, 27 Aug 2024 00:05:33 +0900 Subject: [PATCH] improve: ControlBridge - add `Stop` behavior improve: Detailer - new detailer wildcard syntax - [ASC-SIZE], [DSC-SIZE], [SKIP], [STOP] --- modules/impact/config.py | 2 +- modules/impact/impact_pack.py | 15 +++- modules/impact/logics.py | 134 +++++++++++++++++++--------------- modules/impact/wildcards.py | 27 ++++--- pyproject.toml | 2 +- 5 files changed, 104 insertions(+), 76 deletions(-) diff --git a/modules/impact/config.py b/modules/impact/config.py index d5a2f74..a244e49 100644 --- a/modules/impact/config.py +++ b/modules/impact/config.py @@ -1,7 +1,7 @@ import configparser import os -version_code = [7, 2, 2] +version_code = [7, 3] version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') dependency_version = 22 diff --git a/modules/impact/impact_pack.py b/modules/impact/impact_pack.py index eb2b956..5740a9e 100644 --- a/modules/impact/impact_pack.py +++ b/modules/impact/impact_pack.py @@ -238,11 +238,16 @@ class DetailerForEach: else: wmode, wildcard_chooser = None, None - if wmode in ['ASC', 'DSC']: + if wmode in ['ASC', 'DSC', 'ASC-SIZE', 'DSC-SIZE']: if wmode == 'ASC': ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[0], x.bbox[1])) - else: + elif wmode == 'DSC': ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[0], x.bbox[1]), reverse=True) + elif wmode == 'ASC-SIZE': + ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[2]-x.bbox[0]) * (x.bbox[3]-x.bbox[1])) + + else: # wmode == 'DSC-SIZE' + ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[2]-x.bbox[0]) * (x.bbox[3]-x.bbox[1]), reverse=True) else: ordered_segs = segs[1] @@ -291,6 +296,12 @@ class DetailerForEach: # Negative Conditioning is placeholder such as FLUX.1 cropped_negative = negative + if wildcard_item and wildcard_item.strip() == '[SKIP]': + continue + + if wildcard_item and wildcard_item.strip() == '[STOP]': + break + enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for_bbox, max_size, seg.bbox, seg_seed, steps, cfg, sampler_name, scheduler, cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint, diff --git a/modules/impact/logics.py b/modules/impact/logics.py index 5e348ea..096ee8e 100644 --- a/modules/impact/logics.py +++ b/modules/impact/logics.py @@ -9,7 +9,7 @@ import impact.core as core import re import nodes import traceback - +from comfy_execution.graph import ExecutionBlocker class ImpactCompare: @classmethod @@ -631,85 +631,99 @@ class ImpactControlBridge: def INPUT_TYPES(cls): return {"required": { "value": (any_typ,), - "mode": ("BOOLEAN", {"default": True, "label_on": "Active", "label_off": "Mute/Bypass"}), - "behavior": ("BOOLEAN", {"default": True, "label_on": "Mute", "label_off": "Bypass"}), + "mode": ("BOOLEAN", {"default": True, "label_on": "Active", "label_off": "Stop/Mute/Bypass"}), + "behavior": (["Stop", "Mute", "Bypass"], ), }, "hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"} } FUNCTION = "doit" - CATEGORY = "ImpactPack/Logic/_for_test" + CATEGORY = "ImpactPack/Logic" RETURN_TYPES = (any_typ,) RETURN_NAMES = ("value",) OUTPUT_NODE = True + DESCRIPTION = ("When behavior is Stop and mode is active, the input value is passed directly to the output.\n" + "When behavior is Mute/Bypass and mode is active, the node connected to the output is changed to active state.\n" + "When behavior is Stop and mode is Stop/Mute/Bypass, the workflow execution of the current node is halted.\n" + "When behavior is Mute/Bypass and mode is Stop/Mute/Bypass, the node connected to the output is changed to Mute/Bypass state.") + @classmethod - def IS_CHANGED(self, value, mode, behavior=True, unique_id=None, prompt=None, extra_pnginfo=None): - # NOTE: extra_pnginfo is not populated for IS_CHANGED. - # so extra_pnginfo is useless in here - try: - workflow = core.current_prompt['extra_data']['extra_pnginfo']['workflow'] - except: - print(f"[Impact Pack] core.current_prompt['extra_data']['extra_pnginfo']['workflow']") - return 0 + def IS_CHANGED(self, value, mode, behavior="Stop", unique_id=None, prompt=None, extra_pnginfo=None): + if behavior == "Stop": + return value, mode, behavior + else: + # NOTE: extra_pnginfo is not populated for IS_CHANGED. + # so extra_pnginfo is useless in here + try: + workflow = core.current_prompt['extra_data']['extra_pnginfo']['workflow'] + except: + print(f"[Impact Pack] core.current_prompt['extra_data']['extra_pnginfo']['workflow']") + return 0 - nodes, links = workflow_to_map(workflow) - next_nodes = [] + nodes, links = workflow_to_map(workflow) + next_nodes = [] - for link in nodes[unique_id]['outputs'][0]['links']: - node_id = str(links[link][2]) - impact.utils.collect_non_reroute_nodes(nodes, links, next_nodes, node_id) + for link in nodes[unique_id]['outputs'][0]['links']: + node_id = str(links[link][2]) + impact.utils.collect_non_reroute_nodes(nodes, links, next_nodes, node_id) return next_nodes - def doit(self, value, mode, behavior=True, 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 - workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow']) - - active_nodes = [] - mute_nodes = [] - bypass_nodes = [] - - for link in workflow_nodes[unique_id]['outputs'][0]['links']: - node_id = str(links[link][2]) - - next_nodes = [] - impact.utils.collect_non_reroute_nodes(workflow_nodes, links, next_nodes, node_id) - - for next_node_id in next_nodes: - node_mode = workflow_nodes[next_node_id]['mode'] - - if node_mode == 0: - active_nodes.append(next_node_id) - elif node_mode == 2: - mute_nodes.append(next_node_id) - elif node_mode == 4: - bypass_nodes.append(next_node_id) - - if mode: - # active - should_be_active_nodes = mute_nodes + bypass_nodes - if len(should_be_active_nodes) > 0: - PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'actives': list(should_be_active_nodes)}) - nodes.interrupt_processing() - - elif behavior: - # mute - should_be_mute_nodes = active_nodes + bypass_nodes - if len(should_be_mute_nodes) > 0: - PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'mutes': list(should_be_mute_nodes)}) - nodes.interrupt_processing() - + if behavior == "Stop": + if mode: + return (value, ) + else: + return (ExecutionBlocker(None), ) else: - # bypass - should_be_bypass_nodes = active_nodes + mute_nodes - if len(should_be_bypass_nodes) > 0: - PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'bypasses': list(should_be_bypass_nodes)}) - nodes.interrupt_processing() + workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow']) - return (value, ) + active_nodes = [] + mute_nodes = [] + bypass_nodes = [] + + for link in workflow_nodes[unique_id]['outputs'][0]['links']: + node_id = str(links[link][2]) + + next_nodes = [] + impact.utils.collect_non_reroute_nodes(workflow_nodes, links, next_nodes, node_id) + + for next_node_id in next_nodes: + node_mode = workflow_nodes[next_node_id]['mode'] + + if node_mode == 0: + active_nodes.append(next_node_id) + elif node_mode == 2: + mute_nodes.append(next_node_id) + elif node_mode == 4: + bypass_nodes.append(next_node_id) + + if mode: + # active + should_be_active_nodes = mute_nodes + bypass_nodes + if len(should_be_active_nodes) > 0: + PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'actives': list(should_be_active_nodes)}) + nodes.interrupt_processing() + + elif behavior == "Mute" or behavior == True: + # mute + should_be_mute_nodes = active_nodes + bypass_nodes + if len(should_be_mute_nodes) > 0: + PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'mutes': list(should_be_mute_nodes)}) + nodes.interrupt_processing() + + else: + # bypass + should_be_bypass_nodes = active_nodes + mute_nodes + if len(should_be_bypass_nodes) > 0: + PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'bypasses': list(should_be_bypass_nodes)}) + nodes.interrupt_processing() + + return (value, ) class ImpactExecutionOrderController: diff --git a/modules/impact/wildcards.py b/modules/impact/wildcards.py index f39d22d..5c14666 100644 --- a/modules/impact/wildcards.py +++ b/modules/impact/wildcards.py @@ -425,7 +425,7 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None, def starts_with_regex(pattern, text): regex = re.compile(pattern) - return bool(regex.match(text)) + return regex.match(text) def split_to_dict(text): @@ -507,18 +507,21 @@ def process_wildcard_for_segs(wildcard): return 'LAB', WildcardChooserDict(items) - elif starts_with_regex(r"\[(ASC|DSC|RND)\]", wildcard): - mode = wildcard[1:4] - items = split_string_with_sep(wildcard[5:]) - - if mode == 'RND': - random.shuffle(items) - return mode, WildcardChooser(items, True) - else: - return mode, WildcardChooser(items, False) - else: - return None, WildcardChooser([(None, wildcard)], False) + match = starts_with_regex(r"\[(ASC-SIZE|DSC-SIZE|ASC|DSC|RND)\]", wildcard) + + if match: + mode = match[1] + items = split_string_with_sep(wildcard[len(match[0]):]) + + if mode == 'RND': + random.shuffle(items) + return mode, WildcardChooser(items, True) + else: + return mode, WildcardChooser(items, False) + + else: + return None, WildcardChooser([(None, wildcard)], False) def wildcard_load(): diff --git a/pyproject.toml b/pyproject.toml index 0067bd9..51d7e5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] 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." -version = "7.2.2" +version = "7.3" license = { file = "LICENSE.txt" } dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]