diff --git a/README.md b/README.md index 23b1532..30ec162 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer ### ControlNet, IPAdapter * `ControlNetApply (SEGS)` - To apply ControlNet in SEGS, you need to use the Preprocessor Provider node from the Inspire Pack to utilize this node. - * `segs_preprocessor` and `control_image` can be selectively applied. If an `control_image` is given, `segs_preprocessor` will be ignored. + * `segs_preprocessor` and `control_image` can be selectively applied. If a `control_image` is given, `segs_preprocessor` will be ignored. * If set to `control_image`, you can preview the cropped cnet image through `SEGSPreview (CNET Image)`. Images generated by `segs_preprocessor` should be verified through the `cnet_images` output of each Detailer. * The `segs_preprocessor` operates by applying preprocessing on-the-fly based on the cropped image during the detailing process, while `control_image` will be cropped and used as input to `ControlNetApply (SEGS)`. * `ControlNetClear (SEGS)` - Clear applied ControlNet in SEGS @@ -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. * `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. + * `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 diff --git a/__init__.py b/__init__.py index 3a15141..497ebe3 100644 --- a/__init__.py +++ b/__init__.py @@ -267,6 +267,7 @@ NODE_CLASS_MAPPINGS = { "StringListToString": StringListToString, "WildcardPromptFromString": WildcardPromptFromString, "ImpactExecutionOrderController": ImpactExecutionOrderController, + "ImpactListBridge": ImpactListBridge, "RemoveNoiseMask": RemoveNoiseMask, @@ -387,6 +388,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "ImpactSwitch": "Switch (Any)", "ImpactInversedSwitch": "Inversed Switch (Any)", "ImpactExecutionOrderController": "Execution Order Controller", + "ImpactListBridge": "List Bridge", "MasksToMaskList": "Mask Batch to Mask List", "MaskListToMaskBatch": "Mask List to Mask Batch", diff --git a/install-manual.py b/install-manual.py index cc2b70c..5d15423 100644 --- a/install-manual.py +++ b/install-manual.py @@ -1,3 +1,4 @@ +import functools import os import re import shutil @@ -70,6 +71,7 @@ def process_wrap(cmd_str, cwd=None, handler=None, env=None): return process.wait() +@functools.cache def get_installed_packages() -> Set[str]: try: result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True) diff --git a/install.py b/install.py index 6fbb9b5..5803d79 100644 --- a/install.py +++ b/install.py @@ -114,19 +114,22 @@ try: onnx_path = os.path.join(model_path, "onnx") if not os.path.exists(os.path.join(os.path.dirname(__file__), '..', 'skip_download_model')): - if not impact.config.get_config()['mmdet_skip']: - bbox_path = os.path.join(model_path, "mmdets", "bbox") - if not os.path.exists(bbox_path): - os.makedirs(bbox_path) + try: + if not impact.config.get_config()['mmdet_skip']: + bbox_path = os.path.join(model_path, "mmdets", "bbox") + if not os.path.exists(bbox_path): + os.makedirs(bbox_path) - if not os.path.exists(os.path.join(bbox_path, "mmdet_anime-face_yolov3.pth")): - download_url("https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/bbox/mmdet_anime-face_yolov3.pth", bbox_path) + if not os.path.exists(os.path.join(bbox_path, "mmdet_anime-face_yolov3.pth")): + download_url("https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/bbox/mmdet_anime-face_yolov3.pth", bbox_path) - if not os.path.exists(os.path.join(bbox_path, "mmdet_anime-face_yolov3.py")): - download_url("https://raw.githubusercontent.com/Bing-su/dddetailer/master/config/mmdet_anime-face_yolov3.py", bbox_path) + if not os.path.exists(os.path.join(bbox_path, "mmdet_anime-face_yolov3.py")): + download_url("https://raw.githubusercontent.com/Bing-su/dddetailer/master/config/mmdet_anime-face_yolov3.py", bbox_path) - if not os.path.exists(os.path.join(sam_path, "sam_vit_b_01ec64.pth")): - download_url("https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth", sam_path) + if not os.path.exists(os.path.join(sam_path, "sam_vit_b_01ec64.pth")): + download_url("https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth", sam_path) + except: + print(f"[Impact Pack] Failed to auto-download model files. Please download them manually.") if not os.path.exists(onnx_path): print(f"### ComfyUI-Impact-Pack: onnx model directory created ({onnx_path})") diff --git a/js/impact-pack.js b/js/impact-pack.js index 97ca6ce..21dd006 100644 --- a/js/impact-pack.js +++ b/js/impact-pack.js @@ -587,17 +587,17 @@ app.registerExtension({ } if(node.comfyClass == "ImpactSEGSLabelFilter" || node.comfyClass == "SEGSLabelFilterDetailerHookProvider") { + node.widgets[0].callback = (value, canvas, node, pos, e) => { + if(node.widgets[1].value.trim() != "" && !node.widgets[1].value.trim().endsWith(",")) + node.widgets[1].value += ", " + + node.widgets[1].value += value; + if(node.widgets_values) + node.widgets_values[1] = node.widgets[1].value; + } + Object.defineProperty(node.widgets[0], "value", { set: (value) => { - const stackTrace = new Error().stack; - if(stackTrace.includes('inner_value_change')) { - if(node.widgets[1].value.trim() != "" && !node.widgets[1].value.trim().endsWith(",")) - node.widgets[1].value += ", " - - node.widgets[1].value += value; - node.widgets_values[1] = node.widgets[1].value; - } - node._value = value; }, get: () => { @@ -667,18 +667,18 @@ app.registerExtension({ break; } + node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => { + if(node.widgets[tbox_id].value != '') + node.widgets[tbox_id].value += ', ' + + node.widgets[tbox_id].value += node._wildcard_value; + } + Object.defineProperty(node.widgets[combo_id+1], "value", { set: (value) => { - const stackTrace = new Error().stack; - if(stackTrace.includes('inner_value_change')) { - if(value != "Select the Wildcard to add to the text") { - if(node.widgets[tbox_id].value != '') - node.widgets[tbox_id].value += ', ' - - node.widgets[tbox_id].value += value; - } - } - }, + if (value !== "Select the Wildcard to add to the text") + node._wildcard_value = value; + }, get: () => { return "Select the Wildcard to add to the text"; } }); @@ -690,24 +690,22 @@ app.registerExtension({ }); if(has_lora) { + node.widgets[combo_id].callback = (value, canvas, node, pos, e) => { + let lora_name = node._value; + if(lora_name.endsWith('.safetensors')) { + lora_name = lora_name.slice(0, -12); + } + + node.widgets[tbox_id].value += ``; + if(node.widgets_values) { + node.widgets_values[tbox_id] = node.widgets[tbox_id].value; + } + } + Object.defineProperty(node.widgets[combo_id], "value", { set: (value) => { - const stackTrace = new Error().stack; - if(stackTrace.includes('inner_value_change')) { - if(value != "Select the LoRA to add to the text") { - let lora_name = value; - if (lora_name.endsWith('.safetensors')) { - lora_name = lora_name.slice(0, -12); - } - - node.widgets[tbox_id].value += ``; - if(node.widgets_values) { - node.widgets_values[tbox_id] = node.widgets[tbox_id].value; - } - } - } - - node._value = value; + if (value !== "Select the LoRA to add to the text") + node._value = value; }, get: () => { return "Select the LoRA to add to the text"; } diff --git a/js/impact-sam-editor.js b/js/impact-sam-editor.js index e7bf6f2..cb34309 100644 --- a/js/impact-sam-editor.js +++ b/js/impact-sam-editor.js @@ -353,13 +353,16 @@ class ImpactSamEditorDialog extends ComfyDialog { imgCtx.drawImage(orig_image, 0, 0, drawWidth, drawHeight); // update mask + let w = (drawWidth * imgCanvas.clientWidth/imgCanvas.width) + "px"; + let h = (drawHeight * imgCanvas.clientHeight/imgCanvas.height) + "px"; + pointsCanvas.width = drawWidth; pointsCanvas.height = drawHeight; pointsCanvas.style.top = imgCanvas.offsetTop + "px"; pointsCanvas.style.left = imgCanvas.offsetLeft + "px"; - maskCanvas.width = drawWidth; - maskCanvas.height = drawHeight; + maskCanvas.style.width = w; + maskCanvas.style.height = h; maskCanvas.style.top = imgCanvas.offsetTop + "px"; maskCanvas.style.left = imgCanvas.offsetLeft + "px"; diff --git a/modules/impact/bridge_nodes.py b/modules/impact/bridge_nodes.py index 326618a..26381c0 100644 --- a/modules/impact/bridge_nodes.py +++ b/modules/impact/bridge_nodes.py @@ -186,6 +186,9 @@ def decode_latent(latent, preview_method, vae_opt=None): elif preview_method == "Latent2RGB-FLUX.1": latent_format = latent_formats.Flux() method = LatentPreviewMethod.Latent2RGB + elif preview_method == "Latent2RGB-LTXV": + latent_format = latent_formats.LTXV() + method = LatentPreviewMethod.Latent2RGB else: print(f"[Impact Pack] PreviewBridgeLatent: '{preview_method}' is unsupported preview method.") latent_format = latent_formats.SD15() @@ -211,6 +214,7 @@ class PreviewBridgeLatent: "Latent2RGB-SDXL", "Latent2RGB-SD15", "Latent2RGB-SD3", "Latent2RGB-SD-X4", "Latent2RGB-Playground-2.5", "Latent2RGB-SC-Prior", "Latent2RGB-SC-B", + "Latent2RGB-LTXV", "TAEF1", "TAESDXL", "TAESD15", "TAESD3"],), }, "optional": { @@ -274,7 +278,13 @@ class PreviewBridgeLatent: def doit(self, latent, image, preview_method, vae_opt=None, block=False, unique_id=None, restore_mask='never', prompt=None, extra_pnginfo=None): latent_channels = latent['samples'].shape[1] - preview_method_channels = 16 if 'SD3' in preview_method or 'SC-Prior' in preview_method or 'FLUX.1' in preview_method or 'TAEF1' == preview_method else 4 + + if 'SD3' in preview_method or 'SC-Prior' in preview_method or 'FLUX.1' in preview_method or 'TAEF1' == preview_method: + preview_method_channels = 16 + elif 'LTXV' in preview_method: + preview_method_channels = 128 + else: + preview_method_channels = 4 if vae_opt is None and latent_channels != preview_method_channels: print(f"[PreviewBridgeLatent] The version of latent is not compatible with preview_method.\nSD3, SD1/SD2, SDXL, SC-Prior, SC-B and FLUX.1 are not compatible with each other.") diff --git a/modules/impact/config.py b/modules/impact/config.py index 25807f0..abcb40f 100644 --- a/modules/impact/config.py +++ b/modules/impact/config.py @@ -1,10 +1,10 @@ import configparser import os -version_code = [7, 10, 7] +version_code = [7, 14, 3] version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') -dependency_version = 23 +dependency_version = 24 my_path = os.path.dirname(__file__) old_config_path = os.path.join(my_path, "impact-pack.ini") diff --git a/modules/impact/core.py b/modules/impact/core.py index a1bf30b..f5e4ef6 100644 --- a/modules/impact/core.py +++ b/modules/impact/core.py @@ -11,6 +11,7 @@ from impact.utils import * from collections import namedtuple import numpy as np from skimage.measure import label +from PIL import ImageOps import nodes import comfy_extras.nodes_upscale_model as model_upscale @@ -47,7 +48,7 @@ preview_bridge_last_mask_cache = {} current_prompt = None -SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]'] +SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]'] def is_execution_model_version_supported(): @@ -69,6 +70,13 @@ def set_previewbridge_image(node_id, file, item): pb_id = f"${node_id}-{pb_id_cnt}" preview_bridge_image_id_map[pb_id] = (file, item) preview_bridge_image_name_map[node_id, file] = (pb_id, item) + if os.path.isfile(file): + i = Image.open(file) + i = ImageOps.exif_transpose(i) + if 'A' in i.getbands(): + mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0 + mask = 1. - torch.from_numpy(mask) + preview_bridge_last_mask_cache[node_id] = mask.unsqueeze(0) pb_id_cnt += 1 return pb_id @@ -319,7 +327,12 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max # prepare mask if noise_mask is not None and inpaint_model: - positive, negative, latent_image = nodes.InpaintModelConditioning().encode(positive, negative, upscaled_image, vae, noise_mask) + imc_encode = nodes.InpaintModelConditioning().encode + if 'noise_mask' in inspect.signature(imc_encode).parameters: + positive, negative, latent_image = imc_encode(positive, negative, upscaled_image, vae, mask=noise_mask, noise_mask=True) + else: + print(f"[Impact Pack] ComfyUI is an outdated version.") + positive, negative, latent_image = imc_encode(positive, negative, upscaled_image, vae, noise_mask) else: latent_image = to_latent_image(upscaled_image, vae) if noise_mask is not None: @@ -1354,9 +1367,14 @@ def segs_to_masklist(segs): return masks -def vae_decode(vae, samples, use_tile, hook, tile_size=512): +def vae_decode(vae, samples, use_tile, hook, tile_size=512, overlap=64): if use_tile: - pixels = nodes.VAEDecodeTiled().decode(vae, samples, tile_size)[0] + decoder = nodes.VAEDecodeTiled() + if 'overlap' in inspect.signature(decoder.decode).parameters: + pixels = decoder.decode(vae, samples, tile_size, overlap=overlap)[0] + else: + print(f"[Impact Pack] Your ComfyUI is outdated.") + pixels = decoder.decode(vae, samples, tile_size)[0] else: pixels = nodes.VAEDecode().decode(vae, samples)[0] @@ -1378,12 +1396,12 @@ def vae_encode(vae, pixels, use_tile, hook, tile_size=512): return samples -def latent_upscale_on_pixel_space_shape(samples, scale_method, w, h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None): - return latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_tile, tile_size, save_temp_prefix, hook)[0] +def latent_upscale_on_pixel_space_shape(samples, scale_method, w, h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64): + return latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_tile, tile_size, save_temp_prefix, hook, overlap=overlap)[0] -def latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None): - pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size) +def latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64): + pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size, overlap=overlap) if save_temp_prefix is not None: nodes.PreviewImage().save_images(pixels, filename_prefix=save_temp_prefix) @@ -1397,12 +1415,12 @@ def latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_t return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels) -def latent_upscale_on_pixel_space(samples, scale_method, scale_factor, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None): - return latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use_tile, tile_size, save_temp_prefix, hook)[0] +def latent_upscale_on_pixel_space(samples, scale_method, scale_factor, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64): + return latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use_tile, tile_size, save_temp_prefix, hook, overlap=overlap)[0] -def latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None): - pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size) +def latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64): + pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size, overlap=overlap) if save_temp_prefix is not None: nodes.PreviewImage().save_images(pixels, filename_prefix=save_temp_prefix) @@ -1418,12 +1436,12 @@ def latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels) -def latent_upscale_on_pixel_space_with_model_shape(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None): - return latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile, tile_size, save_temp_prefix, hook)[0] +def latent_upscale_on_pixel_space_with_model_shape(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64): + return latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile, tile_size, save_temp_prefix, hook, overlap=overlap)[0] -def latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None): - pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size) +def latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64): + pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size, overlap=overlap) if save_temp_prefix is not None: nodes.PreviewImage().save_images(pixels, filename_prefix=save_temp_prefix) @@ -1450,12 +1468,12 @@ def latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upsca def latent_upscale_on_pixel_space_with_model(samples, scale_method, upscale_model, scale_factor, vae, use_tile=False, - tile_size=512, save_temp_prefix=None, hook=None): - return latent_upscale_on_pixel_space_with_model2(samples, scale_method, upscale_model, scale_factor, vae, use_tile, tile_size, save_temp_prefix, hook)[0] + tile_size=512, save_temp_prefix=None, hook=None, overlap=64): + return latent_upscale_on_pixel_space_with_model2(samples, scale_method, upscale_model, scale_factor, vae, use_tile, tile_size, save_temp_prefix, hook, overlap=overlap)[0] def latent_upscale_on_pixel_space_with_model2(samples, scale_method, upscale_model, scale_factor, vae, use_tile=False, - tile_size=512, save_temp_prefix=None, hook=None): - pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size) + tile_size=512, save_temp_prefix=None, hook=None, overlap=64): + pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size, overlap=overlap) if save_temp_prefix is not None: nodes.PreviewImage().save_images(pixels, filename_prefix=save_temp_prefix) diff --git a/modules/impact/impact_pack.py b/modules/impact/impact_pack.py index 966c29a..9dbdb3f 100644 --- a/modules/impact/impact_pack.py +++ b/modules/impact/impact_pack.py @@ -2031,7 +2031,12 @@ class LatentSender(nodes.SaveLatent): "samples": ("LATENT", ), "filename_prefix": ("STRING", {"default": "latents/LatentSender"}), "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "preview_method": (["Latent2RGB-SDXL", "Latent2RGB-SD15", "TAESDXL", "TAESD15"],) + "preview_method": (["Latent2RGB-FLUX.1", + "Latent2RGB-SDXL", "Latent2RGB-SD15", "Latent2RGB-SD3", + "Latent2RGB-SD-X4", "Latent2RGB-Playground-2.5", + "Latent2RGB-SC-Prior", "Latent2RGB-SC-B", + "Latent2RGB-LTXV", + "TAEF1", "TAESDXL", "TAESD15", "TAESD3"],) }, "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, } @@ -2073,14 +2078,33 @@ class LatentSender(nodes.SaveLatent): if preview_method == "Latent2RGB-SD15": latent_format = latent_formats.SD15() method = LatentPreviewMethod.Latent2RGB - elif preview_method == "TAESD15": + elif preview_method == "Latent2RGB-SDXL": + latent_format = latent_formats.SDXL() + method = LatentPreviewMethod.Latent2RGB + elif preview_method == "Latent2RGB-SD3": + latent_format = latent_formats.SD3() + method = LatentPreviewMethod.Latent2RGB + elif preview_method == "Latent2RGB-SD-X4": + latent_format = latent_formats.SD_X4() + method = LatentPreviewMethod.Latent2RGB + elif preview_method == "Latent2RGB-Playground-2.5": + latent_format = latent_formats.SDXL_Playground_2_5() + method = LatentPreviewMethod.Latent2RGB + elif preview_method == "Latent2RGB-SC-Prior": + latent_format = latent_formats.SC_Prior() + method = LatentPreviewMethod.Latent2RGB + elif preview_method == "Latent2RGB-SC-B": + latent_format = latent_formats.SC_B() + method = LatentPreviewMethod.Latent2RGB + elif preview_method == "Latent2RGB-FLUX.1": + latent_format = latent_formats.Flux() + method = LatentPreviewMethod.Latent2RGB + elif preview_method == "Latent2RGB-LTXV": + latent_format = latent_formats.LTXV() + method = LatentPreviewMethod.Latent2RGB + else: + print(f"[Impact Pack] LatentSender: '{preview_method}' is unsupported preview method.") latent_format = latent_formats.SD15() - method = LatentPreviewMethod.TAESD - elif preview_method == "TAESDXL": - latent_format = latent_formats.SDXL() - method = LatentPreviewMethod.TAESD - else: # preview_method == "Latent2RGB-SDXL" - latent_format = latent_formats.SDXL() method = LatentPreviewMethod.Latent2RGB previewer = core.get_previewer("cpu", latent_format=latent_format, force=True, method=method) @@ -2154,16 +2178,19 @@ class ImpactWildcardProcessor: @classmethod def INPUT_TYPES(s): return {"required": { - "wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False}), - "populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False}), - "mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed"}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "Enter a prompt using wildcard syntax."}), + "populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "The actual value passed during the execution of 'ImpactWildcardProcessor' is what is shown here. The behavior varies slightly depending on the mode. Wildcard syntax can also be used in 'populated_text'."}), + "mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed", "tooltip": "Populate: Before running the workflow, it overwrites the existing value of 'populated_text' with the prompt processed from 'wildcard_text'. In this mode, 'populated_text' cannot be edited.\nFixed: Ignores wildcard_text and keeps 'populated_text' as is. You can edit 'populated_text' in this mode."}), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Determines the random seed to be used for wildcard processing."}), "Select to add Wildcard": (["Select the Wildcard to add to the text"],), }, } CATEGORY = "ImpactPack/Prompt" + DESCRIPTION = ("The 'ImpactWildcardProcessor' processes text prompts written in wildcard syntax and outputs the processed text prompt.\n\n" + "TIP: Before the workflow is executed, the processing result of 'wildcard_text' is displayed in 'populated_text', and the populated text is saved along with the workflow. If you want to use a seed converted as input, write the prompt directly in 'populated_text' instead of 'wildcard_text', and set the mode to 'Fixed'.") + RETURN_TYPES = ("STRING", ) FUNCTION = "doit" @@ -2182,17 +2209,22 @@ class ImpactWildcardEncode: return {"required": { "model": ("MODEL",), "clip": ("CLIP",), - "wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False}), - "populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False}), - "mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed"}), + "wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "Enter a prompt using wildcard syntax."}), + "populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "The actual value passed during the execution of 'ImpactWildcardEncode' is what is shown here. The behavior varies slightly depending on the mode. Wildcard syntax can also be used in 'populated_text'."}), + "mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed", "tooltip": "Populate: Before running the workflow, it overwrites the existing value of 'populated_text' with the prompt processed from 'wildcard_text'. In this mode, 'populated_text' cannot be edited.\n" + "Fixed: Ignores wildcard_text and keeps 'populated_text' as is. You can edit 'populated_text' in this mode."}), "Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"), ), "Select to add Wildcard": (["Select the Wildcard to add to the text"], ), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Determines the random seed to be used for wildcard processing."}), }, } CATEGORY = "ImpactPack/Prompt" + DESCRIPTION = ("The 'ImpactWildcardEncode' node processes text prompts written in wildcard syntax and outputs them as conditioning. It also supports LoRA syntax, with the applied LoRA reflected in the model's output.\n\n" + "TIP1: Before the workflow is executed, the processing result of 'wildcard_text' is displayed in 'populated_text', and the populated text is saved along with the workflow. If you want to use a seed converted as input, write the prompt directly in 'populated_text' instead of 'wildcard_text', and set the mode to 'Fixed'.\n" + "TIP2: If the 'Inspire Pack' is installed, LBW(LoRA Block Weight) syntax can also be applied.") + RETURN_TYPES = ("MODEL", "CLIP", "CONDITIONING", "STRING") RETURN_NAMES = ("model", "clip", "conditioning", "populated_text") FUNCTION = "doit" @@ -2217,7 +2249,7 @@ class ImpactSchedulerAdapter: def INPUT_TYPES(s): return {"required": { "scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"defaultInput": True, }), - "extra_scheduler": (['None', 'AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]'],), + "extra_scheduler": (['None', 'AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]'],), }} CATEGORY = "ImpactPack/Util" diff --git a/modules/impact/impact_sampling.py b/modules/impact/impact_sampling.py index ea1eb45..2a35894 100644 --- a/modules/impact/impact_sampling.py +++ b/modules/impact/impact_sampling.py @@ -27,6 +27,8 @@ def calculate_sigmas(model, sampler, scheduler, steps): sigmas = nodes.NODE_CLASS_MAPPINGS['AlignYourStepsScheduler']().get_sigmas(scheduler[4:], steps, denoise=1.0)[0] elif scheduler.startswith('GITS[coeff='): sigmas = nodes.NODE_CLASS_MAPPINGS['GITSScheduler']().get_sigmas(float(scheduler[11:-1]), steps, denoise=1.0)[0] + elif scheduler == 'LTXV[default]': + sigmas = nodes.NODE_CLASS_MAPPINGS['LTXVScheduler']().get_sigmas(20, 2.05, 0.95, True, 0.1)[0] else: sigmas = samplers.calculate_sigmas(model.get_model_object("model_sampling"), scheduler, steps) diff --git a/modules/impact/impact_server.py b/modules/impact/impact_server.py index 4568857..24036b0 100644 --- a/modules/impact/impact_server.py +++ b/modules/impact/impact_server.py @@ -315,6 +315,8 @@ def onprompt_for_switch(json_data): inversed_switch_info = {} onprompt_switch_info = {} onprompt_cond_branch_info = {} + disabled_switch = set() + for k, v in json_data['prompt'].items(): if 'class_type' not in v: @@ -348,6 +350,10 @@ def onprompt_for_switch(json_data): else: onprompt_switch_info[k] = select_input + if k in onprompt_switch_info and f'input{onprompt_switch_info[k]}' not in v['inputs']: + # disconnect output + disabled_switch.add(k) + elif cls == 'ImpactConditionalBranchSelMode': if 'sel_mode' in v['inputs'] and v['inputs']['sel_mode'] and 'cond' in v['inputs']: cond_input = v['inputs']['cond'] @@ -372,6 +378,9 @@ def onprompt_for_switch(json_data): if vv[1] + 1 != inversed_switch_info[vv[0]]: disable_targets.add(kk) + if vv[0] in disabled_switch: + disable_targets.add(kk) + if k in onprompt_switch_info: selected_slot_name = f"input{onprompt_switch_info[k]}" for kk, vv in v['inputs'].items(): diff --git a/modules/impact/logics.py b/modules/impact/logics.py index eeea845..d943342 100644 --- a/modules/impact/logics.py +++ b/modules/impact/logics.py @@ -766,6 +766,29 @@ class ImpactExecutionOrderController: 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 diff --git a/modules/impact/segs_nodes.py b/modules/impact/segs_nodes.py index 9ba4d25..37bb95f 100644 --- a/modules/impact/segs_nodes.py +++ b/modules/impact/segs_nodes.py @@ -896,7 +896,7 @@ class From_SEG_ELT_bbox: CATEGORY = "ImpactPack/Util" def doit(self, bbox): - return bbox + return [int(c) for c in bbox] class From_SEG_ELT_crop_region: @@ -1101,10 +1101,10 @@ class SEG_ELT_BBOX_ScaleBy: x1, y1, x2, y2 = x1-cx1, y1-cy1, x2-cx1, y2-cy1 h, w = mask.shape - x1 = min(w-1, max(0, x1)) - x2 = min(w-1, max(0, x2)) - y1 = min(h-1, max(0, y1)) - y2 = min(h-1, max(0, y2)) + x1 = int(min(w-1, max(0, x1))) + x2 = int(min(w-1, max(0, x2))) + y1 = int(min(h-1, max(0, y1))) + y2 = int(min(h-1, max(0, y2))) mask_cropped = mask.copy() mask_cropped[:, :x1] = 0 # zero fill left side diff --git a/modules/impact/segs_upscaler.py b/modules/impact/segs_upscaler.py index 08bbf95..3696855 100644 --- a/modules/impact/segs_upscaler.py +++ b/modules/impact/segs_upscaler.py @@ -106,7 +106,12 @@ def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, schedu # prepare mask if noise_mask is not None and inpaint_model: - positive, negative, latent_image = nodes.InpaintModelConditioning().encode(positive, negative, image, vae, noise_mask) + imc_encode = nodes.InpaintModelConditioning().encode + if 'noise_mask' in inspect.signature(imc_encode).parameters: + positive, negative, latent_image = imc_encode(positive, negative, image, vae, mask=noise_mask, noise_mask=True) + else: + print(f"[Impact Pack] ComfyUI is an outdated version.") + positive, negative, latent_image = imc_encode(positive, negative, image, vae, noise_mask) else: latent_image = to_latent_image(image, vae) if noise_mask is not None: diff --git a/modules/impact/util_nodes.py b/modules/impact/util_nodes.py index 734f43d..2afba50 100644 --- a/modules/impact/util_nodes.py +++ b/modules/impact/util_nodes.py @@ -52,7 +52,10 @@ class GeneralSwitch: print(f"SELECTED: {input_name}") - return [input_name] + if input_name in kwargs: + return [input_name] + else: + return [] @staticmethod def doit(*args, **kwargs): @@ -501,7 +504,7 @@ class MakeMaskBatch: def doit(self, **kwargs): mask1 = kwargs['mask1'] del kwargs['mask1'] - masks = [utils.make_3d_mask(value) for value in kwargs.values()] + masks = [make_3d_mask(value) for value in kwargs.values()] if len(masks) == 0: return (mask1,) diff --git a/pyproject.toml b/pyproject.toml index 9c821d8..35d13c0 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.10.7" +version = "7.14.3" license = { file = "LICENSE.txt" } dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]