Compare commits

...
34 Commits
Author SHA1 Message Date
Dr.Lt.Data 70d0540895 improved: tooltips 2025-01-14 00:47:27 +09:00
Dr.Lt.Data 7330577a0f version marker 2025-01-10 00:49:38 +09:00
Symbiomatrix b1d760291f Dummy shortcircuit. (#880) 2025-01-10 00:49:06 +09:00
Dr.Lt.Data 12e838a320 improved: refresh wildcard
Now refresh feature is moved to menu item.
`Edit > Impact: Refresh Wildcard`
2025-01-06 22:59:33 +09:00
Dr.Lt.Data 8e8621df49 update .gitignore 2024-12-30 02:32:46 +09:00
Dr.Lt.Data cdb7b4d3b0 fix: compatibility patch for VAEEncodeTiled's overlap
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/863#issuecomment-2560861500
2024-12-25 16:14:51 +09:00
Dr.Lt.Data 21eecb0c03 fix: install script - folder_paths error 2024-12-22 18:40:09 +09:00
Dr.Lt.Data 9402ecf4f9 refactor: use ControlNetApplyAdvanced instead of legacy ControlNetApply 2024-12-21 18:21:58 +09:00
Dr.Lt.Data c21b361e2a fix: VAEEncodeTiled - overlap compatibility patch
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/863
2024-12-21 17:41:24 +09:00
Dr.Lt.Data 8f04714145 fixed: inversed_switch - If the select is pointing to an out-of-range output, remove the input connection instead of the output.
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/851#issuecomment-2543353847
2024-12-19 23:29:34 +09:00
Dr.Lt.Data a311f9278d fixed: sam detector - invalid position
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/853
2024-12-19 23:06:49 +09:00
Dr.Lt.Data 0ec21f1a4b version marker 2024-12-19 22:03:11 +09:00
Andrés Zsögön 40a3df3acd Added Mask Rect Area and Mask Rect Area (Advanced) nodes (#861)
Added nodes for simple rectangular masks of arbitrary size and position with built-in preview canvas. Closes issue #856.
2024-12-19 22:02:40 +09:00
Dr.Lt.Data 9ba92862c4 update README.md 2024-12-17 11:03:02 +09:00
Dr.Lt.Data 2c2e148205 update README.md 2024-12-17 10:58:17 +09:00
Dr.Lt.Data ff9c30787c Robustly remove legacy subpack directories. 2024-12-11 23:24:00 +09:00
Dr.Lt.Data 64f709741a The Impact Subpack must now be installed separately. 2024-12-11 21:56:33 +09:00
Dr.Lt.Data 455e993354 Merge branch 'Main' into refactor/subpack 2024-12-11 21:54:51 +09:00
Dr.Lt.Data ce23d436fc improve: switch - disconnect output connection instead of input connection if select_on_prompt 2024-12-11 05:25:01 +09:00
Dr.Lt.Data 5f630466fb fixed: block if empty slot is selected and select_on_prompt
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/846
2024-12-11 03:36:12 +09:00
Dr.Lt.Data 81045cc845 fixed: remove from lazy input if empty slot is selected.
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/846
2024-12-11 03:25:29 +09:00
Ikko Eltociear Ashimine 20f2cac6f0 docs: update README.md (#844)
minor fix
2024-12-06 05:10:09 +09:00
Dr.Lt.Data 348c3dcb6b improve: PreviewBridgeLatent - add Latent2RGB-LTXV
improve: LatentSender - add multiple latent formats
2024-12-01 02:02:05 +09:00
Dr.Lt.Data 1ec3037613 FIXED: wildcard, lora selector bug
from https://github.com/ltdrdata/ComfyUI-Impact-Pack/pull/838/files)

FIXED: SAM Detector
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/836
2024-11-30 22:48:00 +09:00
filtered d4136bc955 Prefer widget.callback over stack trace check (#838)
* Prefer widget.callback over stack trace check

Update to LiteGraph included a rename of a local function.  This value was being checked to prevent unwanted value sets.

This has been replaced with widget callback(), which for combo boxes is always called when a value is clicked, regardless of any change.

* nit

* Fix error caused by bug in litegraph

Known issue w/widget.callback and setter/getter.
2024-11-30 21:47:22 +09:00
Dr.Lt.Data c9b3aecd1a fix: install script exception handling for torchvision.download_url 2024-11-26 18:33:27 +09:00
Dr.Lt.Data a2bb6f7c91 feat: LTXV[default] scheduler is added 2024-11-26 01:16:57 +09:00
Dr.Lt.Data e1e408d8c9 improve: add description to wildcard nodes 2024-11-22 21:37:23 +09:00
Jonathan Nogueira f7686845c6 Update core.py - save mask when edited (#817)
* Update core.py - save mask when edited

In order to facilitate the `restore_mask` functionality better, one needs to save the mask to the cache when the mask is edited.

* Update core.py - add missing unsqueeze(0)

mask cache was missing unsqueeze function, giving the resulting mask the wrong size.
2024-11-22 13:41:55 +09:00
FennelFetish 37f465d4a9 Fixes call to make_3d_mask in MakeMaskBatch (#823) 2024-11-22 13:28:50 +09:00
Dr.Lt.Data 48b4254e81 fix: compatibility patch for updated ComfyUI
https://github.com/comfyanonymous/ComfyUI/commit/156a28786be9ba6352061090096462bfb1b485bb

https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/821
2024-11-20 09:46:42 +09:00
Dr.Lt.Data 66bf101341 Merge branch 'Main' into refactor/subpack 2024-11-02 05:39:28 +09:00
Dr.Lt.Data 5c0535942a Merge branch 'Main' into refactor/subpack 2024-11-01 02:31:49 +09:00
Dr.Lt.Data 6be257254e Separate Impact Pack and Impact Subpack 2024-11-01 02:25:14 +09:00
22 changed files with 1228 additions and 462 deletions
+2
View File
@@ -7,3 +7,5 @@ subpack
impact_subpack
*.txt
*.yaml
!requirements.txt
!LICENSE.txt
+18 -63
View File
@@ -2,11 +2,13 @@
# ComfyUI-Impact-Pack
**Custom nodes pack for ComfyUI**
This custom node helps to conveniently enhance images through Detector, Detailer, Upscaler, Pipe, and more.
**Custom node pack for ComfyUI**
This node pack helps to conveniently enhance images through Detector, Detailer, Upscaler, Pipe, and more.
NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pack. To use the UltralyticsDetectorProvider node, please install the ComfyUI-Impact-Subpack separately.
## NOTICE
* V8.0: The `Impact Subpack` is no longer installed automatically. To use `UltralyticsDetectorProvider` nodes, please install the `Impact Subpack` separately.
* V7.6: Automatic installation is no longer supported. Please install using ComfyUI-Manager, or manually install requirements.txt and run install.py to complete the installation.
* V7.0: Supports Switch based on Execution Model Inversion.
* V6.0: Supports FLUX.1 model in Impact KSampler, Detailers, PreviewBridgeLatent
@@ -33,9 +35,6 @@ This custom node helps to conveniently enhance images through Detector, Detailer
## Custom Nodes
### [Detector nodes](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/detectors.md)
* `SAMLoader` - Loads the SAM model.
* `UltralyticsDetectorProvider` - Loads the Ultralystics model to provide SEGM_DETECTOR, BBOX_DETECTOR.
- Unlike `MMDetDetectorProvider`, for segm models, `BBOX_DETECTOR` is also provided.
- The various models available in UltralyticsDetectorProvider can be downloaded through **ComfyUI-Manager**.
* `ONNXDetectorProvider` - Loads the ONNX model to provide BBOX_DETECTOR.
* `CLIPSegDetectorProvider` - Wrapper for CLIPSeg to provide BBOX_DETECTOR.
* You need to install the ComfyUI-CLIPSeg node extension.
@@ -49,7 +48,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
@@ -69,6 +68,8 @@ This custom node helps to conveniently enhance images through Detector, Detailer
* `Dilate Mask` - Dilate Mask.
* Support erosion for negative value.
* `Gaussian Blur Mask` - Apply Gaussian Blur to Mask. You can utilize this for mask feathering.
* `Mask Rect Area` - Create a rectangular mask defined by percentages with preview canvas.
* `Mask Rect Area (Advanced)` - Create a rectangular mask defined by pixels and image size.
### [Detailer nodes](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/detailers.md)
* `Detailer (SEGS)` - Refines the image based on SEGS.
@@ -274,6 +275,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
* For supported labels, please refer to the `config.json` of the respective HuggingFace repository.
* `#Female` and `#Male` are symbols that group multiple labels such as `Female, women, woman, ...`, for convenience, rather than being single labels.
### Etc nodes
* `Impact Scheduler Adapter` - With the addition of AYS to the scheduler of the Impact Pack and Inspire Pack, there is an issue of incompatibility when the existing scheduler widget is converted to input. The Impact Scheduler Adapter allows for an indirect connection to be possible.
* `StringListToString` - Convert String List to String
@@ -289,66 +291,25 @@ This custom node helps to conveniently enhance images through Detector, Detailer
* `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
* MMDetDetectorProvider - Loads the MMDet model to provide BBOX_DETECTOR and SEGM_DETECTOR.
* To use the existing MMDetDetectorProvider, you need to enable the MMDet usage configuration.
## Feature
* `Interactive SAM Detector (Clipspace)` - When you right-click on a node that has 'MASK' and 'IMAGE' outputs, a context menu will open. From this menu, you can either open a dialog to create a SAM Mask using 'Open in SAM Detector', or copy the content (likely mask data) using 'Copy (Clipspace)' and generate a mask using 'Impact SAM Detector' from the clipspace menu, and then paste it using 'Paste (Clipspace)'.
* Providing a feature to detect errors that occur when mixing models and clips from checkpoints such as `SDXL Base`, `SDXL Refiner`, `SD1.x`, `SD2.x` during sample execution, and reporting appropriate errors.
## Deprecated
* The following nodes have been kept only for compatibility with existing workflows, and are no longer supported. Please replace them with new nodes.
* ONNX Detector (SEGS) - BBOX Detector (SEGS)
* MMDetLoader -> MMDetDetectorProvider
* SegsMaskCombine -> SEGS to MASK (combined)
* BboxDetectorForEach -> BBOX Detector (SEGS)
* SegmDetectorForEach -> SEGM Detector (SEGS)
* BboxDetectorCombined -> BBOX Detector (combined)
* SegmDetectorCombined -> SEGM Detector (combined)
* MaskPainter -> PreviewBridge
* To use the existing deprecated legacy nodes, you need to enable the MMDet usage configuration.
## Ultralytics models
* When using ultralytics models, save them separately in `models/ultralytics/bbox` and `models/ultralytics/segm` depending on the type of model. Many models can be downloaded by searching for `ultralytics` in the Model Manager of ComfyUI-Manager.
* huggingface.co/Bingsu/[adetailer](https://huggingface.co/Bingsu/adetailer/tree/main) - You can download face, people detection models, and clothing detection models.
* ultralytics/[assets](https://github.com/ultralytics/assets/releases/) - You can download various types of detection models other than faces or people.
* civitai/[adetailer](https://civitai.com/search/models?sortBy=models_v5&query=adetailer) - You can download various types detection models....Many models are associated with NSFW content.
## How to activate 'MMDet usage' (DEPRECATED)
* Upon the initial execution, an `impact-pack.ini` file will be generated in the custom_nodes/ComfyUI-Impact-Pack directory.
```
[default]
dependency_version = 2
mmdet_skip = True
```
* Change `mmdet_skip = True` to `mmdet_skip = False`
```
[default]
dependency_version = 2
mmdet_skip = False
```
* Restart ComfyUI
## Installation
## How To Install?
### Install via ComfyUI-Manager (Recommended)
* Search `ComfyUI Impact Pack` in ComfyUI-Manager and click `Install` button.
### Manual Install (Not Recommended)
1. `cd custom_nodes`
2. `git clone https://github.com/ltdrdata/ComfyUI-Impact-Pack.git`
2. `git clone https://github.com/ltdrdata/ComfyUI-Impact-Pack`
3. `cd ComfyUI-Impact-Pack`
4. (optional) `git clone https://github.com/ltdrdata/ComfyUI-Impact-Subpack impact_subpack`
* Impact Pack will automatically download subpack during its initial launch.
5. (optional) `python install-manual.py`
* Impact Pack will automatically install its dependencies during its initial launch.
* For the portable version, you should execute the command `..\..\..\python_embeded\python.exe install-manual.py` to run the installation script.
6. Restart ComfyUI
4. `pip install -r requirements.txt`
* **IMPORTANT**:
* You must install it within the Python environment where ComfyUI is running.
* For the portable version, use `<installed path>\python_embeded\python.exe -m pip` instead of `pip`. For a `venv`, activate the `venv` first and then use `pip`.
5. Restart ComfyUI
* NOTE1: If an error occurs during the installation process, please refer to [Troubleshooting Page](troubleshooting/TROUBLESHOOTING.md) for assistance.
* NOTE2: You can use this colab notebook [colab notebook](https://colab.research.google.com/github/ltdrdata/ComfyUI-Impact-Pack/blob/Main/notebook/comfyui_colab_impact_pack.ipynb) to launch it. This notebook automatically downloads the impact pack to the custom_nodes directory, installs the tested dependencies, and runs it.
@@ -359,11 +320,9 @@ mmdet_skip = False
* pip install
* segment-anything
* ultralytics
* scikit-image
* piexif
* opencv-python
* GitPython
* scipy
* numpy<2
* dill
@@ -372,9 +331,6 @@ mmdet_skip = False
* (deprecated) openmim # for mim
* (deprecated) pycocotools # for mim
* mim install (deprecated)
* mmcv==2.0.0, mmdet==3.0.0, mmengine==0.7.2
* linux packages (ubuntu)
* libgl1-mesa-glx
* libglib2.0-0
@@ -397,17 +353,16 @@ sam_editor_model = sam_vit_b_01ec64.pth
```
## Other Materials (auto-download on initial startup)
## Other Materials (auto-download when installing)
* ComfyUI/models/mmdets/bbox <= https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/bbox/mmdet_anime-face_yolov3.pth
* ComfyUI/models/mmdets/bbox <= https://raw.githubusercontent.com/Bing-su/dddetailer/master/config/mmdet_anime-face_yolov3.py
* ComfyUI/models/sams <= https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth
## Troubleshooting page
* [Troubleshooting Page](troubleshooting/TROUBLESHOOTING.md)
## How to use (DDetailer feature)
## How To Use (DDetailer feature)
#### 1. Basic auto face detection and refine exapmle.
![simple](https://github.com/ltdrdata/ComfyUI-extension-tutorials/raw/Main/ComfyUI-Impact-Pack/images/simple.png)
+4 -19
View File
@@ -13,7 +13,6 @@ import traceback
comfy_path = os.path.dirname(folder_paths.__file__)
impact_path = os.path.join(os.path.dirname(__file__))
subpack_path = os.path.join(os.path.dirname(__file__), "impact_subpack")
modules_path = os.path.join(os.path.dirname(__file__), "modules")
sys.path.append(modules_path)
@@ -22,14 +21,9 @@ import impact.config
import impact.sample_error_enhancer
print(f"### Loading: ComfyUI-Impact-Pack ({impact.config.version})")
sys.path.append(subpack_path)
# Core
# recheck dependencies for colab
try:
import impact.subpack_nodes # This import must be done before cv2.
import folder_paths
import torch
import cv2
@@ -143,6 +137,8 @@ NODE_CLASS_MAPPINGS = {
"BitwiseAndMask": BitwiseAndMask,
"SubtractMask": SubtractMask,
"AddMask": AddMask,
"MaskRectArea": MaskRectArea,
"MaskRectAreaAdvanced": MaskRectAreaAdvanced,
"ImpactSegsAndMask": SegsBitwiseAndMask,
"ImpactSegsAndMaskForEach": SegsBitwiseAndMaskForEach,
"EmptySegs": EmptySEGS,
@@ -324,6 +320,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"BitwiseAndMask": "Pixelwise(MASK & MASK)",
"SubtractMask": "Pixelwise(MASK - MASK)",
"AddMask": "Pixelwise(MASK + MASK)",
"MaskRectArea": "Mask Rect Area",
"MaskRectAreaAdvanced": "Mask Rect Area (Advanced)",
"ImpactFlattenMask": "Flatten Mask Batch",
"DetailerForEach": "Detailer (SEGS)",
"DetailerForEachPipe": "Detailer (SEGS/pipe)",
@@ -465,19 +463,6 @@ if not impact.config.get_config()['mmdet_skip']:
"SegmDetectorCombined": "SegmDetectorCombined (Legacy)",
})
try:
import impact.subpack_nodes
NODE_CLASS_MAPPINGS.update(impact.subpack_nodes.NODE_CLASS_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(impact.subpack_nodes.NODE_DISPLAY_NAME_MAPPINGS)
except Exception as e:
print("### ComfyUI-Impact-Pack: (IMPORT FAILED) Subpack\n")
print(" The module at the `custom_nodes/ComfyUI-Impact-Pack/impact_subpack` path appears to be incomplete.")
print(" Recommended to delete the path and restart ComfyUI.")
print(" If the issue persists, please report it to https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues.")
print("\n---------------------------------")
traceback.print_exc()
print("---------------------------------\n")
# NOTE: Inject directly into EXTENSION_WEB_DIRS instead of WEB_DIRECTORY
# Provide the js path fixed as ComfyUI-Impact-Pack instead of the path name, making it available for external use
-183
View File
@@ -1,183 +0,0 @@
import functools
import os
import re
import shutil
import sys
import subprocess
import threading
import locale
import traceback
from typing import Set
if sys.argv[0] == 'install.py':
sys.path.append('.') # for portable version
impact_path = os.path.join(os.path.dirname(__file__), "modules")
subpack_path = os.path.join(os.path.dirname(__file__), "impact_subpack")
subpack_repo = "https://github.com/ltdrdata/ComfyUI-Impact-Subpack"
comfy_path = os.environ.get('COMFYUI_PATH')
if comfy_path is None:
print(f"\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `{os.path.dirname(__file__)}/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
comfy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
model_path = os.environ.get('COMFYUI_MODEL_PATH')
if model_path is None:
try:
import folder_paths
model_path = folder_paths.models_dir
except:
pass
if model_path is None:
model_path = os.path.abspath(os.path.join(comfy_path, 'models'))
print(f"\n[bold yellow]WARN: The `COMFYUI_MODEL_PATH` environment variable is not set. Assuming `{model_path}` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
sys.path.append(impact_path)
sys.path.append(comfy_path)
# ---
def handle_stream(stream, is_stdout):
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
for msg in stream:
if is_stdout:
print(msg, end="", file=sys.stdout)
else:
print(msg, end="", file=sys.stderr)
def process_wrap(cmd_str, cwd=None, handler=None, env=None):
print(f"[Impact Pack] EXECUTE: {cmd_str} in '{cwd}'")
process = subprocess.Popen(cmd_str, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, text=True, bufsize=1)
if handler is None:
handler = handle_stream
stdout_thread = threading.Thread(target=handler, args=(process.stdout, True))
stderr_thread = threading.Thread(target=handler, args=(process.stderr, False))
stdout_thread.start()
stderr_thread.start()
stdout_thread.join()
stderr_thread.join()
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"]
# ---
try:
import platform
import folder_paths
from torchvision.datasets.utils import download_url
import impact.config
print("### ComfyUI-Impact-Pack: Check dependencies")
def ensure_subpack():
import git
if os.path.exists(subpack_path):
try:
repo = git.Repo(subpack_path)
repo.remotes.origin.pull()
except:
traceback.print_exc()
if platform.system() == 'Windows':
print(f"[ComfyUI-Impact-Pack] Please turn off ComfyUI and remove '{subpack_path}' and restart ComfyUI.")
else:
shutil.rmtree(subpack_path)
git.Repo.clone_from(subpack_repo, subpack_path)
else:
git.Repo.clone_from(subpack_repo, subpack_path)
def install():
subpack_install_script = os.path.join(subpack_path, "install.py")
print(f"### ComfyUI-Impact-Pack: Updating subpack")
ensure_subpack() # The installation of the subpack must take place before ensure_pip. cv2 triggers a permission error.
new_env = os.environ.copy()
new_env["COMFYUI_PATH"] = comfy_path
new_env["COMFYUI_MODEL_PATH"] = model_path
if os.path.exists(subpack_install_script):
if not is_requirements_installed(os.path.join(subpack_path, 'requirements.txt')):
process_wrap(pip_install + ['-r', 'requirements.txt'], cwd=subpack_path)
process_wrap([sys.executable, 'install.py'], cwd=subpack_path, env=new_env)
else:
print(f"### ComfyUI-Impact-Pack: (Install Failed) Subpack\nFile not found: `{subpack_install_script}`")
# Download model
print("### ComfyUI-Impact-Pack: Check basic models")
sam_path = os.path.join(model_path, "sams")
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)
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(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(onnx_path):
print(f"### ComfyUI-Impact-Pack: onnx model directory created ({onnx_path})")
os.mkdir(onnx_path)
impact.config.write_config()
install()
except Exception as e:
print("[ERROR] ComfyUI-Impact-Pack: Dependency installation has failed. Please install manually.")
traceback.print_exc()
+26 -42
View File
@@ -12,13 +12,11 @@ if sys.argv[0] == 'install.py':
impact_path = os.path.join(os.path.dirname(__file__), "modules")
subpack_path = os.path.join(os.path.dirname(__file__), "impact_subpack")
subpack_repo = "https://github.com/ltdrdata/ComfyUI-Impact-Subpack"
comfy_path = os.environ.get('COMFYUI_PATH')
if comfy_path is None:
print(f"\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `{os.path.dirname(__file__)}/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
print(f"\nWARN: The `COMFYUI_PATH` environment variable is not set. Assuming `{os.path.dirname(__file__)}/../../` as the ComfyUI path.", file=sys.stderr)
comfy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
model_path = os.environ.get('COMFYUI_MODEL_PATH')
@@ -31,7 +29,7 @@ if model_path is None:
if model_path is None:
model_path = os.path.abspath(os.path.join(comfy_path, 'models'))
print(f"\n[bold yellow]WARN: The `COMFYUI_MODEL_PATH` environment variable is not set. Assuming `{model_path}` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
print(f"\nWARN: The `COMFYUI_MODEL_PATH` environment variable is not set. Assuming `{model_path}` as the ComfyUI path.", file=sys.stderr)
sys.path.append(impact_path)
@@ -71,62 +69,37 @@ def process_wrap(cmd_str, cwd=None, handler=None, env=None):
try:
import platform
import folder_paths
from torchvision.datasets.utils import download_url
import impact.config
print("### ComfyUI-Impact-Pack: Check dependencies")
def ensure_subpack():
import git
if os.path.exists(subpack_path):
try:
repo = git.Repo(subpack_path)
repo.remotes.origin.pull()
except:
traceback.print_exc()
if platform.system() == 'Windows':
print(f"[ComfyUI-Impact-Pack] Please turn off ComfyUI and remove '{subpack_path}' and restart ComfyUI.")
else:
shutil.rmtree(subpack_path)
git.Repo.clone_from(subpack_repo, subpack_path)
else:
git.Repo.clone_from(subpack_repo, subpack_path)
def install():
subpack_install_script = os.path.join(subpack_path, "install.py")
print(f"### ComfyUI-Impact-Pack: Updating subpack")
ensure_subpack() # The installation of the subpack must take place before ensure_pip. cv2 triggers a permission error.
new_env = os.environ.copy()
new_env["COMFYUI_PATH"] = comfy_path
new_env["COMFYUI_MODEL_PATH"] = model_path
if os.path.exists(subpack_install_script):
process_wrap([sys.executable, 'install.py'], cwd=subpack_path, env=new_env)
else:
print(f"### ComfyUI-Impact-Pack: (Install Failed) Subpack\nFile not found: `{subpack_install_script}`")
# Download model
print("### ComfyUI-Impact-Pack: Check basic models")
sam_path = os.path.join(model_path, "sams")
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})")
@@ -134,6 +107,17 @@ try:
impact.config.write_config()
# Remove legacy subpack
subpack_path = os.path.join(os.path.dirname(__file__), 'impact_subpack')
if os.path.exists(subpack_path):
shutil.rmtree(subpack_path)
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
subpack_path = os.path.join(os.path.dirname(__file__), 'subpack')
if os.path.exists(subpack_path):
shutil.rmtree(subpack_path)
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
install()
except Exception as e:
+58 -35
View File
@@ -222,6 +222,31 @@ api.addEventListener("executed", progressExecuteHandler);
app.registerExtension({
name: "Comfy.Impack",
commands: [
{
id: 'refresh-impact-wildcard',
label: 'Impact: Refresh Wildcard',
function: async () => {
await api.fetchApi('/impact/wildcards/refresh');
await load_wildcards();
app.extensionManager.toast.add({
severity: 'info',
summary: 'Refreshed!',
detail: 'Impact Wildcard List is refreshed!!',
life: 3000
});
}
}
],
menuCommands: [
{
path: ['Edit'],
commands: ['refresh-impact-wildcard']
}
],
loadedGraphNode(node, app) {
if (node.comfyClass == "MaskPainter") {
input_dirty[node.id + ""] = true;
@@ -587,17 +612,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 +692,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 +715,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 += `<lora:${lora_name}>`;
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 += `<lora:${lora_name}>`;
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"; }
+12 -8
View File
@@ -262,7 +262,7 @@ class ImpactSamEditorDialog extends ComfyDialog {
const pointsCanvas = document.createElement('canvas');
imgCanvas.id = "imageCanvas";
maskCanvas.id = "maskCanvas";
maskCanvas.id = "samEditorMaskCanvas";
pointsCanvas.id = "pointsCanvas";
this.setlayout(imgCanvas, maskCanvas, pointsCanvas);
@@ -353,13 +353,16 @@ class ImpactSamEditorDialog extends ComfyDialog {
imgCtx.drawImage(orig_image, 0, 0, drawWidth, drawHeight);
// update mask
pointsCanvas.width = drawWidth;
pointsCanvas.height = drawHeight;
let w = (drawWidth * imgCanvas.clientWidth/imgCanvas.width) + "px";
let h = (drawHeight * imgCanvas.clientHeight/imgCanvas.height) + "px";
pointsCanvas.width = drawWidth * imgCanvas.clientWidth/imgCanvas.width;
pointsCanvas.height = drawHeight * imgCanvas.clientHeight/imgCanvas.height;
pointsCanvas.style.top = imgCanvas.offsetTop + "px";
pointsCanvas.style.left = imgCanvas.offsetLeft + "px";
maskCanvas.width = drawWidth;
maskCanvas.height = drawHeight;
maskCanvas.width = pointsCanvas.width;
maskCanvas.height = pointsCanvas.height;
maskCanvas.style.top = imgCanvas.offsetTop + "px";
maskCanvas.style.left = imgCanvas.offsetLeft + "px";
@@ -473,8 +476,9 @@ class ImpactSamEditorDialog extends ComfyDialog {
for(const i in self.prompt_points) {
const [is_positive, x, y] = self.prompt_points[i];
const point = [x,y];
if(is_positive)
if(is_positive) {
positive_points.push(point);
}
else
negative_points.push(point);
}
@@ -508,8 +512,8 @@ class ImpactSamEditorDialog extends ComfyDialog {
const x = event.offsetX || event.targetTouches[0].clientX - maskRect.left;
const y = event.offsetY || event.targetTouches[0].clientY - maskRect.top;
const originalX = x * self.image.width / self.pointsCanvas.width;
const originalY = y * self.image.height / self.pointsCanvas.height;
const originalX = x * self.image.width / self.pointsCanvas.clientWidth;
const originalY = y * self.image.height / self.pointsCanvas.clientHeight;
var point = null;
if (event.button == 0) {
-20
View File
@@ -1,20 +0,0 @@
import { ComfyApp, app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
let refresh_btn = document.getElementById('comfy-refresh-button');
let refresh_btn2 = document.querySelector('button[title="Refresh widgets in nodes to find new models or files"]');
let orig = refresh_btn.onclick;
if(refresh_btn) {
refresh_btn.onclick = function() {
orig();
api.fetchApi('/impact/wildcards/refresh');
};
}
if(refresh_btn2) {
refresh_btn2?.addEventListener('click', function() {
api.fetchApi('/impact/wildcards/refresh');
});
}
+381
View File
@@ -0,0 +1,381 @@
import { app } from "/scripts/app.js";
function showPreviewCanvas(node, app) {
const widget = {
type: "customCanvas",
name: "mask-rect-area-canvas",
get value() {
return this.canvas.value;
},
set value(x) {
this.canvas.value = x;
},
draw: function (ctx, node, widgetWidth, widgetY) {
// If we are initially offscreen when created we wont have received a resize event
// Calculate it here instead
if (!node.canvasHeight) {
computeCanvasSize(node, node.size);
}
const visible = true;
const t = ctx.getTransform();
const margin = 12;
const border = 2;
const widgetHeight = node.canvasHeight;
const width = Math.round(node.properties["width"]);
const height = Math.round(node.properties["height"]);
const scale = Math.min((widgetWidth - margin * 3) / width, (widgetHeight - margin * 3) / height);
const blurRadius = node.properties["blur_radius"] || 0;
const index = 0;
Object.assign(this.canvas.style, {
left: `${t.e}px`,
top: `${t.f + (widgetY * t.d)}px`,
width: `${widgetWidth * t.a}px`,
height: `${widgetHeight * t.d}px`,
position: "absolute",
zIndex: 1,
fontSize: `${t.d * 10.0}px`,
pointerEvents: "none"
});
this.canvas.hidden = !visible;
let backgroundWidth = width * scale;
let backgroundHeight = height * scale;
let xOffset = margin;
if (backgroundWidth < widgetWidth) {
xOffset += (widgetWidth - backgroundWidth) / 2 - margin;
}
let yOffset = (margin / 2);
if (backgroundHeight < widgetHeight) {
yOffset += (widgetHeight - backgroundHeight) / 2 - margin;
}
let widgetX = xOffset;
widgetY = widgetY + yOffset;
// Draw the background border
ctx.fillStyle = globalThis.LiteGraph.WIDGET_OUTLINE_COLOR;
ctx.fillRect(widgetX - border, widgetY - border, backgroundWidth + border * 2, backgroundHeight + border * 2)
// Draw the main background area
ctx.fillStyle = globalThis.LiteGraph.WIDGET_BGCOLOR;
ctx.fillRect(widgetX, widgetY, backgroundWidth, backgroundHeight);
// Draw the conditioning zone
let [x, y, w, h] = getDrawArea(node, backgroundWidth, backgroundHeight);
ctx.fillStyle = getDrawColor(0, "80");
ctx.fillRect(widgetX + x, widgetY + y, w, h);
ctx.beginPath();
ctx.lineWidth = 1;
// Draw grid lines
for (let x = 0; x <= width / 64; x += 1) {
ctx.moveTo(widgetX + x * 64 * scale, widgetY);
ctx.lineTo(widgetX + x * 64 * scale, widgetY + backgroundHeight);
}
for (let y = 0; y <= height / 64; y += 1) {
ctx.moveTo(widgetX, widgetY + y * 64 * scale);
ctx.lineTo(widgetX + backgroundWidth, widgetY + y * 64 * scale);
}
ctx.strokeStyle = "#66666650";
ctx.stroke();
ctx.closePath();
// Draw current zone
let [sx, sy, sw, sh] = getDrawArea(node, backgroundWidth, backgroundHeight);
ctx.fillStyle = getDrawColor(0, "80");
ctx.fillRect(widgetX + sx, widgetY + sy, sw, sh);
ctx.fillStyle = getDrawColor(0, "40");
ctx.fillRect(widgetX + sx + border, widgetY + sy + border, sw - border * 2, sh - border * 2);
// Draw white border around the current zone
ctx.strokeStyle = globalThis.LiteGraph.NODE_SELECTED_TITLE_COLOR;
ctx.lineWidth = 2;
ctx.strokeRect(widgetX + sx, widgetY + sy, sw, sh);
// Display
ctx.beginPath();
ctx.arc(LiteGraph.NODE_SLOT_HEIGHT * 0.5, LiteGraph.NODE_SLOT_HEIGHT * (index + 0.5) + 4, 4, 0, Math.PI * 2);
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = "white";
ctx.stroke();
ctx.lineWidth = 1;
ctx.closePath();
// Draw progress bar canvas
if (backgroundWidth < widgetWidth) {
xOffset += (widgetWidth - backgroundWidth) / 2 - margin;
}
// Ajustar las coordenadas X e Y
const barHeight = 8;
let widgetYBar = widgetY + backgroundHeight + margin;
// Dibujar el borde negro alrededor de la barra
ctx.fillStyle = globalThis.LiteGraph.WIDGET_OUTLINE_COLOR;
ctx.fillRect(
widgetX - border,
widgetYBar - border,
backgroundWidth + border * 2,
barHeight + border * 2
);
// Dibujar el área principal de la barra (fondo)
ctx.fillStyle = globalThis.LiteGraph.WIDGET_BGCOLOR; // Mismo color de fondo que el canvas
ctx.fillRect(
widgetX,
widgetYBar,
backgroundWidth,
barHeight
);
// Draw progress bar grid
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#66666650";
// Calcular el número de líneas en función del tamaño de la barra
const numLines = Math.floor(backgroundWidth / 64);
// Dibujar líneas del grid
for (let x = 0; x <= width / 64; x += 1) {
ctx.moveTo(widgetX + x * 64 * scale, widgetYBar);
ctx.lineTo(widgetX + x * 64 * scale, widgetYBar + barHeight);
}
ctx.stroke();
ctx.closePath();
// Dibujar progreso (basado en blur_radius)
const progress = Math.min(blurRadius / 255, 1);
ctx.fillStyle = "rgba(0, 120, 255, 0.5)";
ctx.fillRect(
widgetX,
widgetYBar,
backgroundWidth * progress,
barHeight
);
}
};
widget.canvas = document.createElement("canvas");
widget.canvas.className = "mask-rect-area-canvas";
widget.parent = node;
document.body.appendChild(widget.canvas);
node.addCustomWidget(widget);
app.canvas.onDrawBackground = function () {
// Draw node isnt fired once the node is off the screen
// if it goes off screen quickly, the input may not be removed
// this shifts it off screen so it can be moved back if the node is visible.
for (let n in app.graph._nodes) {
n = app.graph._nodes[n];
for (let w in n.widgets) {
let wid = n.widgets[w];
if (Object.hasOwn(wid, "canvas")) {
wid.canvas.style.left = -8000 + "px";
wid.canvas.style.position = "absolute";
}
}
}
};
node.onResize = function (size) {
computeCanvasSize(node, size);
};
return {minWidth: 200, minHeight: 200, widget};
}
app.registerExtension({
name: 'drltdata.MaskRectAreaAdvanced',
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "MaskRectAreaAdvanced") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
this.setProperty("width", 512);
this.setProperty("height", 512);
this.setProperty("x", 0);
this.setProperty("y", 0);
this.setProperty("w", 256);
this.setProperty("h", 256);
this.setProperty("blur_radius", 0);
this.selected = false;
this.index = 3;
this.serialize_widgets = true;
CUSTOM_INT(this, "x", 0, function (v, _, node) {
const s = this.options.step / 10;
this.value = Math.round(v / s) * s;
node.properties["x"] = this.value;
});
CUSTOM_INT(this, "y", 0, function (v, _, node) {
const s = this.options.step / 10;
this.value = Math.round(v / s) * s;
node.properties["y"] = this.value;
});
CUSTOM_INT(this, "width", 256, function (v, _, node) {
const s = this.options.step / 10;
this.value = Math.round(v / s) * s;
node.properties["w"] = this.value;
});
CUSTOM_INT(this, "height", 256, function (v, _, node) {
const s = this.options.step / 10;
this.value = Math.round(v / s) * s;
node.properties["h"] = this.value;
});
CUSTOM_INT(this, "image_width", 512, function (v, _, node) {
const s = this.options.step / 10;
this.value = Math.round(v / s) * s;
node.properties["width"] = this.value;
});
CUSTOM_INT(this, "image_height", 512, function (v, _, node) {
const s = this.options.step / 10;
this.value = Math.round(v / s) * s;
node.properties["height"] = this.value;
});
CUSTOM_INT(this, "blur_radius", 0, function (v, _, node) {
this.value = Math.round(v) || 0;
node.properties["blur_radius"] = this.value;
},
{"min": 0, "max": 255, "step": 10}
);
showPreviewCanvas(this, app);
this.onSelected = function () {
this.selected = true;
};
this.onDeselected = function () {
this.selected = false;
};
return r;
};
}
}
});
// Calculate the drawing area using individual properties.
function getDrawArea(node, backgroundWidth, backgroundHeight) {
let x = node.properties["x"] * backgroundWidth / node.properties["width"];
let y = node.properties["y"] * backgroundHeight / node.properties["height"];
let w = node.properties["w"] * backgroundWidth / node.properties["width"];
let h = node.properties["h"] * backgroundHeight / node.properties["height"];
if (x > backgroundWidth) {
x = backgroundWidth;
}
if (y > backgroundHeight) {
y = backgroundHeight;
}
if (x + w > backgroundWidth) {
w = Math.max(0, backgroundWidth - x);
}
if (y + h > backgroundHeight) {
h = Math.max(0, backgroundHeight - y);
}
return [x, y, w, h];
}
function CUSTOM_INT(node, inputName, val, func, config = {}) {
return {
widget: node.addWidget(
"number",
inputName,
val,
func,
Object.assign({}, {min: 0, max: 4096, step: 640, precision: 0}, config)
)
};
}
function getDrawColor(percent, alpha) {
let h = 360 * percent;
let s = 50;
let l = 50;
l /= 100;
const a = s * Math.min(l, 1 - l) / 100;
const f = n => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color).toString(16).padStart(2, '0'); // convert to Hex and prefix "0" if needed
};
return `#${f(0)}${f(8)}${f(4)}${alpha}`;
}
function computeCanvasSize(node, size) {
if (node.widgets[0].last_y == null) {
return;
}
const MIN_HEIGHT = 220;
const MIN_WIDTH = 240;
let y = LiteGraph.NODE_WIDGET_HEIGHT * Math.max(node.inputs.length, node.outputs.length) + 5;
let freeSpace = size[1] - y;
// Compute the height of all non-customCanvas widgets
let widgetHeight = 0;
for (let i = 0; i < node.widgets.length; i++) {
const w = node.widgets[i];
if (w.type !== "customCanvas") {
if (w.computeSize) {
widgetHeight += w.computeSize()[1] + 4;
} else {
widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 5;
}
}
}
// Ensure there is enough vertical space
freeSpace -= widgetHeight;
// Adjust the height of the node if needed
if (freeSpace < MIN_HEIGHT) {
freeSpace = MIN_HEIGHT;
node.size[1] = y + widgetHeight + freeSpace;
node.graph.setDirtyCanvas(true);
}
// Ensure the node width meets the minimum width requirement
if (node.size[0] < MIN_WIDTH) {
node.size[0] = MIN_WIDTH;
node.graph.setDirtyCanvas(true);
}
// Position each of the widgets
for (const w of node.widgets) {
w.y = y;
if (w.type === "customCanvas") {
y += freeSpace;
} else if (w.computeSize) {
y += w.computeSize()[1] + 4;
} else {
y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
}
}
node.canvasHeight = freeSpace;
}
+366
View File
@@ -0,0 +1,366 @@
import { app } from "/scripts/app.js";
function showPreviewCanvas(node, app) {
const widget = {
type: "customCanvas",
name: "mask-rect-area-canvas",
get value() {
return this.canvas.value;
},
set value(x) {
this.canvas.value = x;
},
draw: function (ctx, node, widgetWidth, widgetY) {
// If we are initially offscreen when created we wont have received a resize event
// Calculate it here instead
if (!node.canvasHeight) {
computeCanvasSize(node, node.size);
}
const visible = true;
const t = ctx.getTransform();
const margin = 12;
const border = 2;
const widgetHeight = node.canvasHeight;
const width = 512;
const height = 512;
const scale = Math.min((widgetWidth - margin * 3) / width, (widgetHeight - margin * 3) / height);
const blurRadius = node.properties["blur_radius"] || 0;
const index = 0;
Object.assign(this.canvas.style, {
left: `${t.e}px`,
top: `${t.f + (widgetY * t.d)}px`,
width: `${widgetWidth * t.a}px`,
height: `${widgetHeight * t.d}px`,
position: "absolute",
zIndex: 1,
fontSize: `${t.d * 10.0}px`,
pointerEvents: "none"
});
this.canvas.hidden = !visible;
let backgroundWidth = width * scale;
let backgroundHeight = height * scale;
let xOffset = margin;
if (backgroundWidth < widgetWidth) {
xOffset += (widgetWidth - backgroundWidth) / 2 - margin;
}
let yOffset = (margin / 2);
if (backgroundHeight < widgetHeight) {
yOffset += (widgetHeight - backgroundHeight) / 2 - margin;
}
let widgetX = xOffset;
widgetY = widgetY + yOffset;
// Draw the background border
ctx.fillStyle = globalThis.LiteGraph.WIDGET_OUTLINE_COLOR;
ctx.fillRect(widgetX - border, widgetY - border, backgroundWidth + border * 2, backgroundHeight + border * 2);
// Draw the main background area
ctx.fillStyle = globalThis.LiteGraph.WIDGET_BGCOLOR;
ctx.fillRect(widgetX, widgetY, backgroundWidth, backgroundHeight);
// Draw the conditioning zone
let [x, y, w, h] = getDrawArea(node, backgroundWidth, backgroundHeight);
ctx.fillStyle = getDrawColor(0, "80");
ctx.fillRect(widgetX + x, widgetY + y, w, h);
ctx.beginPath();
ctx.lineWidth = 1;
// Draw grid lines
for (let x = 0; x <= width / 64; x += 1) {
ctx.moveTo(widgetX + x * 64 * scale, widgetY);
ctx.lineTo(widgetX + x * 64 * scale, widgetY + backgroundHeight);
}
for (let y = 0; y <= height / 64; y += 1) {
ctx.moveTo(widgetX, widgetY + y * 64 * scale);
ctx.lineTo(widgetX + backgroundWidth, widgetY + y * 64 * scale);
}
ctx.strokeStyle = "#66666650";
ctx.stroke();
ctx.closePath();
// Draw current zone
let [sx, sy, sw, sh] = getDrawArea(node, backgroundWidth, backgroundHeight);
ctx.fillStyle = getDrawColor(0, "80");
ctx.fillRect(widgetX + sx, widgetY + sy, sw, sh);
ctx.fillStyle = getDrawColor(0, "40");
ctx.fillRect(widgetX + sx + border, widgetY + sy + border, sw - border * 2, sh - border * 2);
// Draw white border around the current zone
ctx.strokeStyle = globalThis.LiteGraph.NODE_SELECTED_TITLE_COLOR;
ctx.lineWidth = 2;
ctx.strokeRect(widgetX + sx, widgetY + sy, sw, sh);
//ctx.strokeRect(finalSX, finalSY, finalSW, finalSH);
// Display
ctx.beginPath();
ctx.arc(LiteGraph.NODE_SLOT_HEIGHT * 0.5, LiteGraph.NODE_SLOT_HEIGHT * (index + 0.5) + 4, 4, 0, Math.PI * 2);
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = "white";
ctx.stroke();
ctx.lineWidth = 1;
ctx.closePath();
// Draw progress bar canvas
if (backgroundWidth < widgetWidth) {
xOffset += (widgetWidth - backgroundWidth) / 2 - margin;
}
const barHeight = 8;
let widgetYBar = widgetY + backgroundHeight + margin;
// Draw progress bar border
ctx.fillStyle = globalThis.LiteGraph.WIDGET_OUTLINE_COLOR;
ctx.fillRect(
widgetX - border,
widgetYBar - border,
backgroundWidth + border * 2,
barHeight + border * 2
);
// Draw progress bar area
ctx.fillStyle = globalThis.LiteGraph.WIDGET_BGCOLOR; // Mismo color de fondo que el canvas
ctx.fillRect(
widgetX,
widgetYBar,
backgroundWidth,
barHeight
);
// Draw progress bar grid
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#66666650";
// Determine max lines
const numLines = Math.floor(backgroundWidth / 64);
// Draw progress bar grid
for (let x = 0; x <= width / 64; x += 1) {
ctx.moveTo(widgetX + x * 64 * scale, widgetYBar);
ctx.lineTo(widgetX + x * 64 * scale, widgetYBar + barHeight);
}
ctx.stroke();
ctx.closePath();
// Draw progress bar
const progress = Math.min(blurRadius / 255, 1);
ctx.fillStyle = "rgba(0, 120, 255, 0.5)";
ctx.fillRect(
widgetX,
widgetYBar,
backgroundWidth * progress,
barHeight
);
}
};
widget.canvas = document.createElement("canvas");
widget.canvas.className = "mask-rect-area-canvas";
widget.parent = node;
document.body.appendChild(widget.canvas);
node.addCustomWidget(widget);
app.canvas.onDrawBackground = function () {
// Draw node isnt fired once the node is off the screen
// if it goes off screen quickly, the input may not be removed
// this shifts it off screen so it can be moved back if the node is visible.
for (let n in app.graph._nodes) {
n = app.graph._nodes[n];
for (let w in n.widgets) {
let wid = n.widgets[w];
if (Object.hasOwn(wid, "canvas")) {
wid.canvas.style.left = -8000 + "px";
wid.canvas.style.position = "absolute";
}
}
}
};
node.onResize = function (size) {
computeCanvasSize(node, size);
};
return {minWidth: 200, minHeight: 200, widget};
}
app.registerExtension({
name: 'drltdata.MaskRectArea',
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "MaskRectArea") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
this.setProperty("width", 512);
this.setProperty("height", 512);
this.setProperty("x", 0);
this.setProperty("y", 0);
this.setProperty("w", 50);
this.setProperty("h", 50);
this.setProperty("blur_radius", 0);
this.selected = false;
this.index = 3;
this.serialize_widgets = true;
CUSTOM_INT(this, "x", 0, function (v, _, node) {
this.value = Math.max(0, Math.min(100, Math.round(v))); // Limitar entre 0 y 100
node.properties["x"] = this.value;
});
CUSTOM_INT(this, "y", 0, function (v, _, node) {
this.value = Math.max(0, Math.min(100, Math.round(v)));
node.properties["y"] = this.value;
});
CUSTOM_INT(this, "w", 50, function (v, _, node) {
this.value = Math.max(0, Math.min(100, Math.round(v)));
node.properties["w"] = this.value;
});
CUSTOM_INT(this, "h", 50, function (v, _, node) {
this.value = Math.max(0, Math.min(100, Math.round(v)));
node.properties["h"] = this.value;
});
CUSTOM_INT(this, "blur_radius", 0, function (v, _, node) {
this.value = Math.round(v) || 0;
node.properties["blur_radius"] = this.value;
},
{"min": 0, "max": 255, "step": 10}
);
showPreviewCanvas(this, app);
this.onSelected = function () {
this.selected = true;
};
this.onDeselected = function () {
this.selected = false;
};
return r;
};
}
}
});
// Calculate the drawing area using percentage-based properties.
function getDrawArea(node, backgroundWidth, backgroundHeight) {
// Convert percentages to actual pixel values based on the background dimensions
let x = (node.properties["x"] / 100) * backgroundWidth;
let y = (node.properties["y"] / 100) * backgroundHeight;
let w = (node.properties["w"] / 100) * backgroundWidth;
let h = (node.properties["h"] / 100) * backgroundHeight;
// Ensure the values do not exceed the background boundaries
if (x > backgroundWidth) {
x = backgroundWidth;
}
if (y > backgroundHeight) {
y = backgroundHeight;
}
// Adjust width and height to fit within the background dimensions
if (x + w > backgroundWidth) {
w = Math.max(0, backgroundWidth - x);
}
if (y + h > backgroundHeight) {
h = Math.max(0, backgroundHeight - y);
}
return [x, y, w, h];
}
function CUSTOM_INT(node, inputName, val, func, config = {}) {
return {
widget: node.addWidget(
"number",
inputName,
val,
func,
Object.assign({}, {min: 0, max: 100, step: 10, precision: 0}, config)
)
};
}
function getDrawColor(percent, alpha) {
let h = 360 * percent;
let s = 50;
let l = 50;
l /= 100;
const a = s * Math.min(l, 1 - l) / 100;
const f = n => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color).toString(16).padStart(2, '0'); // convert to Hex and prefix "0" if needed
};
return `#${f(0)}${f(8)}${f(4)}${alpha}`;
}
function computeCanvasSize(node, size) {
if (node.widgets[0].last_y == null) {
return;
}
const MIN_HEIGHT = 200;
const MIN_WIDTH = 200;
let y = LiteGraph.NODE_WIDGET_HEIGHT * Math.max(node.inputs.length, node.outputs.length) + 5;
let freeSpace = size[1] - y;
// Compute the height of all non-customCanvas widgets
let widgetHeight = 0;
for (let i = 0; i < node.widgets.length; i++) {
const w = node.widgets[i];
if (w.type !== "customCanvas") {
if (w.computeSize) {
widgetHeight += w.computeSize()[1] + 4;
} else {
widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 5;
}
}
}
// Ensure there is enough vertical space
freeSpace -= widgetHeight;
// Adjust the height of the node if needed
if (freeSpace < MIN_HEIGHT) {
freeSpace = MIN_HEIGHT;
node.size[1] = y + widgetHeight + freeSpace;
node.graph.setDirtyCanvas(true);
}
// Ensure the node width meets the minimum width requirement
if (node.size[0] < MIN_WIDTH) {
node.size[0] = MIN_WIDTH;
node.graph.setDirtyCanvas(true);
}
// Position each of the widgets
for (const w of node.widgets) {
w.y = y;
if (w.type === "customCanvas") {
y += freeSpace;
} else if (w.computeSize) {
y += w.computeSize()[1] + 4;
} else {
y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
}
}
node.canvasHeight = freeSpace;
}
+15 -10
View File
@@ -27,7 +27,7 @@ class SEGSDetailerForAnimateDiff:
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (core.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
"basic_pipe": ("BASIC_PIPE",),
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
},
"optional": {
@@ -60,7 +60,7 @@ class SEGSDetailerForAnimateDiff:
new_segs = []
cnet_image_list = []
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
if not (isinstance(model, str) and model == "DUMMY") and noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
for seg in segs[1]:
@@ -94,13 +94,18 @@ class SEGSDetailerForAnimateDiff:
for condition, details in negative
]
enhanced_image_tensor, cnet_images = core.enhance_detail_for_animatediff(cropped_image_frames, model, clip, vae, guide_size, guide_size_for, max_size,
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
cropped_positive, cropped_negative, denoise, seg.cropped_mask,
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
if not (isinstance(model, str) and model == "DUMMY"):
enhanced_image_tensor, cnet_images = core.enhance_detail_for_animatediff(cropped_image_frames, model, clip, vae, guide_size, guide_size_for, max_size,
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
cropped_positive, cropped_negative, denoise, seg.cropped_mask,
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
else:
enhanced_image_tensor = cropped_image_frames
cnet_images = None
if cnet_images is not None:
cnet_image_list.extend(cnet_images)
@@ -143,7 +148,7 @@ class DetailerForEachPipeForAnimateDiff:
"scheduler": (core.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
"basic_pipe": ("BASIC_PIPE", ),
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
},
"optional": {
+11 -1
View File
@@ -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.")
+1 -1
View File
@@ -1,7 +1,7 @@
import configparser
import os
version_code = [7, 11, 3]
version_code = [8, 3, 1]
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
dependency_version = 24
+44 -13
View File
@@ -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:
@@ -1371,9 +1384,14 @@ def vae_decode(vae, samples, use_tile, hook, tile_size=512, overlap=64):
return pixels
def vae_encode(vae, pixels, use_tile, hook, tile_size=512):
def vae_encode(vae, pixels, use_tile, hook, tile_size=512, overlap=64):
if use_tile:
samples = nodes.VAEEncodeTiled().encode(vae, pixels, tile_size)[0]
encoder = nodes.VAEEncodeTiled()
if 'overlap' in inspect.signature(encoder.encode).parameters:
samples = encoder.encode(vae, pixels, tile_size, overlap=overlap)[0]
else:
print(f"[Impact Pack] Your ComfyUI is outdated.")
samples = encoder.encode(vae, pixels, tile_size)[0]
else:
samples = nodes.VAEEncode().encode(vae, pixels)[0]
@@ -1399,7 +1417,7 @@ def latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_t
if hook is not None:
pixels = hook.post_upscale(pixels)
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), 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, overlap=64):
@@ -1420,7 +1438,7 @@ def latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use
if hook is not None:
pixels = hook.post_upscale(pixels)
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), 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, overlap=64):
@@ -1451,7 +1469,7 @@ def latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upsca
if hook is not None:
pixels = hook.post_upscale(pixels)
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), old_pixels
def latent_upscale_on_pixel_space_with_model(samples, scale_method, upscale_model, scale_factor, vae, use_tile=False,
@@ -1487,7 +1505,7 @@ def latent_upscale_on_pixel_space_with_model2(samples, scale_method, upscale_mod
if hook is not None:
pixels = hook.post_upscale(pixels)
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), old_pixels
class TwoSamplersForMaskUpscaler:
@@ -1657,8 +1675,14 @@ class PixelKSampleUpscaler:
preprocessor = nodes.NODE_CLASS_MAPPINGS['TilePreprocessor']()
# might add capacity to set pyrUp_iters later, not needed for now though
preprocessed = preprocessor.execute(images, pyrUp_iters=3, resolution=min(image_w, image_h))[0]
apply_cnet = getattr(nodes.ControlNetApply(), nodes.ControlNetApply.FUNCTION)
positive = apply_cnet(positive, self.tile_cnet, preprocessed, strength=self.tile_cnet_strength)[0]
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive=positive,
negative=negative,
control_net=self.tile_cnet,
image=preprocessed,
strength=self.tile_cnet_strength,
start_percent=0,
end_percent=1.0,
vae=self.vae)
refined_latent = impact_sampling.impact_sample(model, seed, steps, cfg, sampler_name, scheduler,
positive, negative, upscaled_latent, denoise, scheduler_func=self.scheduler_func)
@@ -1929,7 +1953,7 @@ class PixelTiledKSampleUpscaler:
def __init__(self, scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative,
denoise,
tile_width, tile_height, tiling_strategy,
upscale_model_opt=None, hook_opt=None, tile_cnet_opt=None, tile_size=512, tile_cnet_strength=1.0):
upscale_model_opt=None, hook_opt=None, tile_cnet_opt=None, tile_size=512, tile_cnet_strength=1.0, overlap=64):
self.params = scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise
self.vae = vae
self.tile_params = tile_width, tile_height, tiling_strategy
@@ -1939,6 +1963,7 @@ class PixelTiledKSampleUpscaler:
self.tile_size = tile_size
self.is_tiled = True
self.tile_cnet_strength = tile_cnet_strength
self.overlap = overlap
def tiled_ksample(self, latent, images):
if "BNK_TiledKSampler" in nodes.NODE_CLASS_MAPPINGS:
@@ -1961,8 +1986,14 @@ class PixelTiledKSampleUpscaler:
preprocessor = nodes.NODE_CLASS_MAPPINGS['TilePreprocessor']()
# might add capacity to set pyrUp_iters later, not needed for now though
preprocessed = preprocessor.execute(images, pyrUp_iters=3, resolution=min(image_w, image_h))[0]
apply_cnet = getattr(nodes.ControlNetApply(), nodes.ControlNetApply.FUNCTION)
positive = apply_cnet(positive, self.tile_cnet, preprocessed, strength=self.tile_cnet_strength)[0]
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive=positive,
negative=negative,
control_net=self.tile_cnet,
image=preprocessed,
strength=self.tile_cnet_strength,
start_percent=0, end_percent=1.0,
vae=self.vae)
return TiledKSampler().sample(model, seed, tile_width, tile_height, tiling_strategy, steps, cfg, sampler_name,
scheduler, positive, negative, latent, denoise)[0]
+220 -42
View File
@@ -28,6 +28,7 @@ import base64
import impact.wildcards as wildcards
from . import hooks
from . import utils
import inspect
try:
@@ -191,7 +192,7 @@ class DetailerForEach:
return {"required": {
"image": ("IMAGE", ),
"segs": ("SEGS", ),
"model": ("MODEL",),
"model": ("MODEL", {"tooltip": "If the `ImpactDummyInput` is connected to the model, the inference stage is skipped."}),
"clip": ("CLIP",),
"vae": ("VAE",),
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
@@ -225,6 +226,10 @@ class DetailerForEach:
CATEGORY = "ImpactPack/Detailer"
@staticmethod
def get_core_module():
return core
@staticmethod
def do_detail(image, segs, model, clip, vae, guide_size, guide_size_for_bbox, max_size, seed, steps, cfg, sampler_name, scheduler,
positive, negative, denoise, feather, noise_mask, force_inpaint, wildcard_opt=None, detailer_hook=None,
@@ -265,7 +270,7 @@ class DetailerForEach:
else:
ordered_segs = segs[1]
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
if not (isinstance(model, str) and model == "DUMMY") and noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
for i, seg in enumerate(ordered_segs):
@@ -293,13 +298,16 @@ class DetailerForEach:
seg_seed = seed + i if seg_seed is None else seg_seed
cropped_positive = [
[condition, {
k: core.crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v
for k, v in details.items()
}]
for condition, details in positive
]
if not isinstance(positive, str):
cropped_positive = [
[condition, {
k: core.crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v
for k, v in details.items()
}]
for condition, details in positive
]
else:
cropped_positive = positive
if not isinstance(negative, str):
cropped_negative = [
@@ -320,16 +328,20 @@ class DetailerForEach:
break
orig_cropped_image = cropped_image.clone()
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,
wildcard_opt=wildcard_item, wildcard_opt_concat_mode=wildcard_concat_mode,
detailer_hook=detailer_hook,
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
scheduler_func=scheduler_func_opt)
if not (isinstance(model, str) and model == "DUMMY"):
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,
wildcard_opt=wildcard_item, wildcard_opt_concat_mode=wildcard_concat_mode,
detailer_hook=detailer_hook,
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
scheduler_func=scheduler_func_opt)
else:
enhanced_image = cropped_image
cnet_pils = None
if cnet_pils is not None:
cnet_pil_list.extend(cnet_pils)
@@ -401,7 +413,7 @@ class DetailerForEachPipe:
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
"force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
"basic_pipe": ("BASIC_PIPE", ),
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
"wildcard": ("STRING", {"multiline": True, "dynamicPrompts": False}),
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
@@ -458,7 +470,7 @@ class FaceDetailer:
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE", ),
"model": ("MODEL",),
"model": ("MODEL", {"tooltip": "If the `ImpactDummyInput` is connected to the model, the inference stage is skipped."}),
"clip": ("CLIP",),
"vae": ("VAE",),
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
@@ -982,6 +994,7 @@ class PixelTiledKSampleUpscalerProvider:
"pk_hook_opt": ("PK_HOOK", ),
"tile_cnet_opt": ("CONTROL_NET", ),
"tile_cnet_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"overlap": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32}),
}
}
@@ -991,11 +1004,11 @@ class PixelTiledKSampleUpscalerProvider:
CATEGORY = "ImpactPack/Upscale"
def doit(self, scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise, tile_width, tile_height, tiling_strategy, upscale_model_opt=None,
pk_hook_opt=None, tile_cnet_opt=None, tile_cnet_strength=1.0):
pk_hook_opt=None, tile_cnet_opt=None, tile_cnet_strength=1.0, overlap=64):
if "BNK_TiledKSampler" in nodes.NODE_CLASS_MAPPINGS:
upscaler = core.PixelTiledKSampleUpscaler(scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise,
tile_width, tile_height, tiling_strategy, upscale_model_opt, pk_hook_opt, tile_cnet_opt,
tile_size=max(tile_width, tile_height), tile_cnet_strength=tile_cnet_strength)
tile_size=max(tile_width, tile_height), tile_cnet_strength=tile_cnet_strength, overlap=overlap)
return (upscaler, )
else:
utils.try_install_custom_node('https://github.com/BlenderNeko/ComfyUI_TiledKSampler',
@@ -1308,7 +1321,11 @@ class IterativeImageUpscale:
core.update_node_status(unique_id, "VAEEncode (first)", 0)
if upscaler.is_tiled:
latent = nodes.VAEEncodeTiled().encode(vae, pixels, upscaler.tile_size)[0]
encoder = nodes.VAEEncodeTiled()
if 'overlap' in inspect.signature(encoder.encode).parameters:
latent = encoder.encode(vae, pixels, upscaler.tile_size, overlap=upscaler.overlap)[0]
else:
latent = encoder.encode(vae, pixels, upscaler.tile_size)[0]
else:
latent = nodes.VAEEncode().encode(vae, pixels)[0]
@@ -1330,7 +1347,7 @@ class FaceDetailerPipe:
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE", ),
"detailer_pipe": ("DETAILER_PIPE",),
"detailer_pipe": ("DETAILER_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the detailer_pipe, the inference stage is skipped."}),
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
"guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}),
"max_size": ("FLOAT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
@@ -1830,6 +1847,135 @@ def get_file_item(base_type, path):
}
class MaskRectArea:
# Creates a rectangle mask using percentage.
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
},
"hidden": {"extra_pnginfo": "EXTRA_PNGINFO", "unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("MASK",)
CATEGORY = "ImpactPack/Operation"
FUNCTION = "create_mask"
def create_mask(self, extra_pnginfo, unique_id, **kwargs):
# search for node
node_found = False
for node in extra_pnginfo["workflow"]["nodes"]:
if node["id"] == int(unique_id):
min_x = node["properties"].get("x", 0) / 100
min_y = node["properties"].get("y", 0) / 100
width = node["properties"].get("w", 0) / 100
height = node["properties"].get("h", 0) / 100
blur_radius = node["properties"].get("blur_radius", 0)
node_found = True
break
if not node_found:
raise ValueError(f"No node found with unique_id {unique_id}.")
# Create a mask with standard resolution (e.g., 512x512)
resolution = 512
mask = torch.zeros((resolution, resolution))
# Calculate pixel coordinates
min_x_px = int(min_x * resolution)
min_y_px = int(min_y * resolution)
max_x_px = int((min_x + width) * resolution)
max_y_px = int((min_y + height) * resolution)
# Draw the rectangle on the mask
mask[min_y_px:max_y_px, min_x_px:max_x_px] = 1
# Apply blur if the radii are greater than 0
if blur_radius > 0:
dx = blur_radius * 2 + 1
dy = blur_radius * 2 + 1
# Convert the mask to a format compatible with OpenCV (numpy array)
mask_np = mask.cpu().numpy().astype("float32")
# Apply Gaussian Blur
blurred_mask = cv2.GaussianBlur(mask_np, (dx, dy), 0)
# Convert back to tensor
mask = torch.from_numpy(blurred_mask)
# Return the mask as a tensor with an additional channel
return (mask.unsqueeze(0),)
class MaskRectAreaAdvanced:
# Creates a rectangle mask using pixels relative to image size.
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
},
"hidden": {"extra_pnginfo": "EXTRA_PNGINFO", "unique_id": "UNIQUE_ID"}
}
RETURN_TYPES = ("MASK",)
CATEGORY = "ImpactPack/Operation"
FUNCTION = "create_mask_advanced"
def create_mask_advanced(self, extra_pnginfo, unique_id, **kwargs):
# search for node
node_found = False
for node in extra_pnginfo["workflow"]["nodes"]:
if node["id"] == int(unique_id):
min_x = node["properties"]["x"]
min_y = node["properties"]["y"]
width = node["properties"]["w"]
height = node["properties"]["h"]
image_width = node["properties"]["width"]
image_height = node["properties"]["height"]
blur_radius = node["properties"]["blur_radius"]
node_found = True
break
if not node_found:
raise ValueError(f"No node found with unique_id {unique_id}.")
# Calculate maximum coordinates
max_x = min_x + width
max_y = min_y + height
# Create a mask with the image dimensions
mask = torch.zeros((image_height, image_width))
# Draw the rectangle on the mask
mask[int(min_y):int(max_y), int(min_x):int(max_x)] = 1
# Apply blur if the radii are greater than 0
if blur_radius > 0:
dx = blur_radius * 2 + 1
dy = blur_radius * 2 + 1
# Convert the mask to a format compatible with OpenCV (numpy array)
mask_np = mask.cpu().numpy().astype("float32")
# Apply Gaussian Blur
blurred_mask = cv2.GaussianBlur(mask_np, (dx, dy), 0)
# Convert back to tensor
mask = torch.from_numpy(blurred_mask)
# Return the mask as a tensor with an additional channel
return (mask.unsqueeze(0),)
class ImageReceiver:
@classmethod
def INPUT_TYPES(s):
@@ -2027,7 +2173,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"},
}
@@ -2069,14 +2220,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)
@@ -2150,16 +2320,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"
@@ -2178,17 +2351,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"
@@ -2213,7 +2391,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"
+2
View File
@@ -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)
+28 -7
View File
@@ -22,6 +22,7 @@ import comfy
from io import BytesIO
import random
from server import PromptServer
import logging
sam_predictor = None
@@ -79,7 +80,7 @@ async def sam_prepare(request):
model_name = os.path.join(impact_pack.model_path, "sams", model_name)
print(f"[INFO] ComfyUI-Impact-Pack: Loading SAM model '{impact_pack.model_path}'")
logging.info(f"[Impact Pack] Loading SAM model '{impact_pack.model_path}'")
filename, image_dir = folder_paths.annotated_filepath(data["filename"])
@@ -95,7 +96,7 @@ async def sam_prepare(request):
thread = threading.Thread(target=async_prepare_sam, args=(image_dir, model_name, filename,))
thread.start()
print(f"[INFO] ComfyUI-Impact-Pack: SAM model loaded. ")
logging.info("[Impact Pack] SAM model loaded. ")
return web.Response(status=200)
@@ -107,7 +108,7 @@ async def release_sam(request):
del sam_predictor
sam_predictor = None
print(f"[INFO] ComfyUI-Impact-Pack: unloading SAM model")
logging.info("[Impact Pack]: unloading SAM model")
@PromptServer.instance.routes.post("/sam/detect")
@@ -315,6 +316,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:
@@ -322,20 +325,24 @@ def onprompt_for_switch(json_data):
cls = v['class_type']
if cls == 'ImpactInversedSwitch':
# if 'sel_mode' is 'select_on_prompt'
if 'sel_mode' in v['inputs'] and v['inputs']['sel_mode'] and 'select' in v['inputs']:
select_input = v['inputs']['select']
# if 'select' is converted input
if isinstance(select_input, list) and len(select_input) == 2:
input_node = json_data['prompt'][select_input[0]]
if input_node['class_type'] == 'ImpactInt' and 'inputs' in input_node and 'value' in input_node['inputs']:
inversed_switch_info[k] = input_node['inputs']['value']
else:
print(f"\n##### ##### #####\n[WARN] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactInversedSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
logging.warning(f"\n##### ##### #####\n[Impact Pack] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactInversedSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
else:
inversed_switch_info[k] = select_input
elif cls in ['ImpactSwitch', 'LatentSwitch', 'SEGSSwitch', 'ImpactMakeImageList']:
# if 'sel_mode' is 'select_on_prompt'
if 'sel_mode' in v['inputs'] and v['inputs']['sel_mode'] and 'select' in v['inputs']:
select_input = v['inputs']['select']
# if 'select' is converted input
if isinstance(select_input, list) and len(select_input) == 2:
input_node = json_data['prompt'][select_input[0]]
if input_node['class_type'] == 'ImpactInt' and 'inputs' in input_node and 'value' in input_node['inputs']:
@@ -344,10 +351,14 @@ def onprompt_for_switch(json_data):
if isinstance(input_node['inputs']['select'], int):
onprompt_switch_info[k] = input_node['inputs']['select']
else:
print(f"\n##### ##### #####\n[WARN] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
logging.warning(f"\n##### ##### #####\n[Impact Pack] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
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']
@@ -371,6 +382,11 @@ def onprompt_for_switch(json_data):
if vv[0] in inversed_switch_info:
if vv[1] + 1 != inversed_switch_info[vv[0]]:
disable_targets.add(kk)
else:
del inversed_switch_info[k]
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]}"
@@ -387,6 +403,11 @@ def onprompt_for_switch(json_data):
for kk in disable_targets:
del v['inputs'][kk]
# inversed_switch - select out of range
for target in inversed_switch_info.keys():
del json_data['prompt'][target]['inputs']['input']
def onprompt_for_pickers(json_data):
detected_pickers = set()
@@ -470,7 +491,7 @@ def onprompt_populate_wildcards(json_data):
if not isinstance(input_seed, int):
continue
else:
print(f"[Impact Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
logging.info(f"[Impact Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
continue
except:
continue
@@ -533,7 +554,7 @@ def onprompt(json_data):
regional_sampler_seed_update(json_data)
core.current_prompt = json_data
except Exception as e:
print(f"[WARN] ComfyUI-Impact-Pack: Error on prompt - several features will not work.\n{e}")
logging.warning(f"[Impact Pack] ComfyUI-Impact-Pack: Error on prompt - several features will not work.\n{e}")
return json_data
+13 -9
View File
@@ -38,7 +38,7 @@ class SEGSDetailer:
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
"force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
"basic_pipe": ("BASIC_PIPE",),
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 100}),
@@ -76,7 +76,7 @@ class SEGSDetailer:
new_segs = []
cnet_pil_list = []
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
if not (isinstance(model, str) and model == "DUMMY") and noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
for i in range(batch_size):
@@ -113,13 +113,17 @@ class SEGSDetailer:
for condition, details in negative
]
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for, max_size,
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
control_net_wrapper=seg.control_net_wrapper, cycle=cycle,
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
if not (isinstance(model, str) and model == "DUMMY"):
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for, max_size,
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
control_net_wrapper=seg.control_net_wrapper, cycle=cycle,
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
else:
enhanced_image = cropped_image
cnet_pils = None
if cnet_pils is not None:
cnet_pil_list.extend(cnet_pils)
+6 -1
View File
@@ -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:
+19 -5
View File
@@ -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,)
@@ -523,6 +526,9 @@ class ReencodeLatent:
"output_vae": ("VAE", ),
"tile_size": ("INT", {"default": 512, "min": 320, "max": 4096, "step": 64}),
},
"optional": {
"overlap": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32, "tooltip": "This setting applies when 'tile_mode' is enabled."}),
}
}
CATEGORY = "ImpactPack/Util"
@@ -530,14 +536,22 @@ class ReencodeLatent:
RETURN_TYPES = ("LATENT", )
FUNCTION = "doit"
def doit(self, samples, tile_mode, input_vae, output_vae, tile_size=512):
def doit(self, samples, tile_mode, input_vae, output_vae, tile_size=512, overlap=64):
if tile_mode in ["Both", "Decode(input) only"]:
pixels = nodes.VAEDecodeTiled().decode(input_vae, samples, tile_size)[0]
decoder = nodes.VAEDecodeTiled()
if 'overlap' in inspect.signature(decoder.decode).parameters:
pixels = decoder.decode(input_vae, samples, tile_size, overlap=overlap)[0]
else:
pixels = decoder.decode(input_vae, samples, tile_size, overlap=overlap)[0]
else:
pixels = nodes.VAEDecode().decode(input_vae, samples)[0]
if tile_mode in ["Both", "Encode(output) only"]:
return nodes.VAEEncodeTiled().encode(output_vae, pixels, tile_size)
encoder = nodes.VAEEncodeTiled()
if 'overlap' in inspect.signature(encoder.encode).parameters:
return encoder.encode(output_vae, pixels, tile_size, overlap=overlap)
else:
return encoder.encode(output_vae, pixels, tile_size)
else:
return nodes.VAEEncode().encode(output_vae, pixels)
+2 -2
View File
@@ -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.11.3"
description = "This node pack offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler."
version = "8.3.1"
license = { file = "LICENSE.txt" }
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]
-1
View File
@@ -3,7 +3,6 @@ scikit-image
piexif
transformers
opencv-python-headless
GitPython
scipy>=1.11.4
numpy<2
dill