Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20f1f16d36 |
@@ -1,25 +0,0 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'ltdrdata' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
@@ -2,9 +2,6 @@
|
||||
This repository offers various extension nodes for ComfyUI. Nodes here have different characteristics compared to those in the ComfyUI Impact Pack. The Impact Pack has become too large now...
|
||||
|
||||
## Notice:
|
||||
* V1.18: To use the 'OSS' Scheduler, please update to ComfyUI version 0.3.28 or later (April 13th or newer) and Impact Pack version V8.11 or higher.
|
||||
* V1.9.1 To avoid confusion with the `NOISE` type in core, the type name has been changed to `NOISE_IMAGE`.
|
||||
* V0.73 The Variation Seed feature is added to Regional Prompt nodes, and it is only compatible with versions Impact Pack V5.10 and above.
|
||||
* V0.69 incompatible with the outdated **ComfyUI IPAdapter Plus**. (A version dated March 24th or later is required.)
|
||||
* V0.64 add sigma_factor to RegionalPrompt... nodes required Impact Pack V4.76 or later.
|
||||
* V0.62 support faceid in Regional IPAdapter
|
||||
@@ -13,68 +10,46 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* WARN: If you use version **0.12 to 0.12.2** without a GlobalSeed node, your workflow's seed may have been erased. Please update immediately.
|
||||
|
||||
## Nodes
|
||||
### Lora Block Weight - This is a node that provides functionality related to Lora block weight.
|
||||
* This provides similar functionality to [sd-webui-lora-block-weight](https://github.com/hako-mikan/sd-webui-lora-block-weight)
|
||||
* `LoRA Loader (Block Weight)`: When loading Lora, the block weight vector is applied.
|
||||
* In the block vector, you can use numbers, R, A, a, B, and b.
|
||||
* R is determined sequentially based on a random seed, while A and B represent the values of the A and B parameters, respectively. a and b are half of the values of A and B, respectively.
|
||||
* `XY Input: LoRA Block Weight`: This is a node in the [Efficiency Nodes](https://github.com/LucianoCirino/efficiency-nodes-comfyui)' XY Plot that allows you to use Lora block weight.
|
||||
* You must ensure that X and Y connections are made, and dependencies should be connected to the XY Plot.
|
||||
* Note: To use this feature, update `Efficient Nodes` to a version released after September 3rd.
|
||||
* Make LoRA Block Weight: Instead of directly applying the LoRA Block Weight to the MODEL, it is generated in a separate LBW_MODEL form
|
||||
* Apply LoRA Block Weight: Apply LBW_MODEL to MODEL and CLIP
|
||||
* Save LoRA Block Weight: Save LBW_MODEL as a .lbw.safetensors file
|
||||
* Load LoRA Block Weight: Load LBW_MODEL from .lbw.safetensors file
|
||||
* Lora Block Weight - This is a node that provides functionality related to Lora block weight.
|
||||
* This provides similar functionality to [sd-webui-lora-block-weight](https://github.com/hako-mikan/sd-webui-lora-block-weight)
|
||||
* `Lora Loader (Block Weight)`: When loading Lora, the block weight vector is applied.
|
||||
* In the block vector, you can use numbers, R, A, a, B, and b.
|
||||
* R is determined sequentially based on a random seed, while A and B represent the values of the A and B parameters, respectively. a and b are half of the values of A and B, respectively.
|
||||
* `XY Input: Lora Block Weight`: This is a node in the [Efficiency Nodes](https://github.com/LucianoCirino/efficiency-nodes-comfyui)' XY Plot that allows you to use Lora block weight.
|
||||
* You must ensure that X and Y connections are made, and dependencies should be connected to the XY Plot.
|
||||
* Note: To use this feature, update `Efficient Nodes` to a version released after September 3rd.
|
||||
|
||||
* SEGS Supports nodes - This is a node that supports ApplyControlNet (SEGS) from the Impact Pack.
|
||||
* `OpenPose Preprocessor Provider (SEGS)`: OpenPose preprocessor is applied for the purpose of using OpenPose ControlNet in SEGS.
|
||||
* You need to install [ControlNet Auxiliary Preprocessors](https://github.com/Fannovel16/comfyui_controlnet_aux) to use this.
|
||||
* `Canny Preprocessor Provider (SEGS)`: Canny preprocessor is applied for the purpose of using Canny ControlNet in SEGS.
|
||||
* `DW Preprocessor Provider (SEGS)`, `MiDaS Depth Map Preprocessor Provider (SEGS)`, `LeReS Depth Map Preprocessor Provider (SEGS)`,
|
||||
`MediaPipe FaceMesh Preprocessor Provider (SEGS)`, `HED Preprocessor Provider (SEGS)`, `Fake Scribble Preprocessor (SEGS)`,
|
||||
`AnimeLineArt Preprocessor Provider (SEGS)`, `Manga2Anime LineArt Preprocessor Provider (SEGS)`, `LineArt Preprocessor Provider (SEGS)`,
|
||||
`Color Preprocessor Provider (SEGS)`, `Inpaint Preprocessor Provider (SEGS)`, `Tile Preprocessor Provider (SEGS)`, `MeshGraphormer Depth Map Preprocessor Provider (SEGS)`
|
||||
* `MediaPipeFaceMeshDetectorProvider`: This node provides `BBOX_DETECTOR` and `SEGM_DETECTOR` that can be used in Impact Pack's Detector using the `MediaPipe-FaceMesh Preprocessor` of ControlNet Auxiliary Preprocessors.
|
||||
|
||||
### SEGS Supports nodes - This is a node that supports ApplyControlNet (SEGS) from the Impact Pack.
|
||||
* `OpenPose Preprocessor Provider (SEGS)`: OpenPose preprocessor is applied for the purpose of using OpenPose ControlNet in SEGS.
|
||||
* You need to install [ControlNet Auxiliary Preprocessors](https://github.com/Fannovel16/comfyui_controlnet_aux) to use this.
|
||||
* `Canny Preprocessor Provider (SEGS)`: Canny preprocessor is applied for the purpose of using Canny ControlNet in SEGS.
|
||||
* `DW Preprocessor Provider (SEGS)`, `MiDaS Depth Map Preprocessor Provider (SEGS)`, `LeReS Depth Map Preprocessor Provider (SEGS)`,
|
||||
`MediaPipe FaceMesh Preprocessor Provider (SEGS)`, `HED Preprocessor Provider (SEGS)`, `Fake Scribble Preprocessor (SEGS)`,
|
||||
`AnimeLineArt Preprocessor Provider (SEGS)`, `Manga2Anime LineArt Preprocessor Provider (SEGS)`, `LineArt Preprocessor Provider (SEGS)`,
|
||||
`Color Preprocessor Provider (SEGS)`, `Inpaint Preprocessor Provider (SEGS)`, `Tile Preprocessor Provider (SEGS)`, `MeshGraphormer Depth Map Preprocessor Provider (SEGS)`
|
||||
* `MediaPipeFaceMeshDetectorProvider`: This node provides `BBOX_DETECTOR` and `SEGM_DETECTOR` that can be used in Impact Pack's Detector using the `MediaPipe-FaceMesh Preprocessor` of ControlNet Auxiliary Preprocessors.
|
||||
* A1111 Compatibility support - These nodes assists in replicating the creation of A1111 in ComfyUI exactly.
|
||||
* `KSampler (Inspire)`: ComfyUI uses the CPU for generating random noise, while A1111 uses the GPU. One of the three factors that significantly impact reproducing A1111's results in ComfyUI can be addressed using `KSampler (Inspire)`.
|
||||
* Other point #1 : Please make sure you haven't forgotten to include 'embedding:' in the embedding used in the prompt, like 'embedding:easynegative.'
|
||||
* Other point #2 : ComfyUI and A1111 have different interpretations of weighting. To align them, you need to use [BlenderNeko/Advanced CLIP Text Encode](https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb).
|
||||
* `KSamplerAdvanced (Inspire)`: Inspire Pack version of `KSampler (Advanced)`.
|
||||
* Common Parameters
|
||||
* `batch_seed_mode` determines how seeds are applied to batch latents:
|
||||
* `comfy`: This method applies the noise to batch latents all at once. This is advantageous to prevent duplicate images from being generated due to seed duplication when creating images.
|
||||
* `incremental`: Similar to the A1111 case, this method incrementally increases the seed and applies noise sequentially for each batch. This approach is beneficial for straightforward reproduction using only the seed.
|
||||
* `variation_strength`: In each batch, the variation strength starts from the set `variation_strength` and increases by `xxx`.
|
||||
* `variation_seed` and `variation_strength` - Initial noise generated by the seed is transformed to the shape of `variation_seed` by `variation_strength`. If `variation_strength` is 0, it only relies on the influence of the seed, and if `variation_strength` is 1.0, it is solely influenced by `variation_seed`.
|
||||
* These parameters are used when you want to maintain the composition of an image generated by the seed but wish to introduce slight changes.
|
||||
|
||||
|
||||
### A1111 Compatibility support - These nodes assists in replicating the creation of A1111 in ComfyUI exactly.
|
||||
* `KSampler (Inspire)`: ComfyUI uses the CPU for generating random noise, while A1111 uses the GPU. One of the three factors that significantly impact reproducing A1111's results in ComfyUI can be addressed using `KSampler (Inspire)`.
|
||||
* Other point #1 : Please make sure you haven't forgotten to include 'embedding:' in the embedding used in the prompt, like 'embedding:easynegative.'
|
||||
* Other point #2 : ComfyUI and A1111 have different interpretations of weighting. To align them, you need to use [BlenderNeko/Advanced CLIP Text Encode](https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb).
|
||||
* `KSamplerAdvanced (Inspire)`: Inspire Pack version of `KSampler (Advanced)`.
|
||||
* `RandomNoise (inspire)`: Inspire Pack version of `RandomNoise`.
|
||||
* Common Parameters
|
||||
* `batch_seed_mode` determines how seeds are applied to batch latents:
|
||||
* `comfy`: This method applies the noise to batch latents all at once. This is advantageous to prevent duplicate images from being generated due to seed duplication when creating images.
|
||||
* `incremental`: Similar to the A1111 case, this method incrementally increases the seed and applies noise sequentially for each batch. This approach is beneficial for straightforward reproduction using only the seed.
|
||||
* `variation_strength`: In each batch, the variation strength starts from the set `variation_strength` and increases by `xxx`.
|
||||
* `variation_seed` and `variation_strength` - Initial noise generated by the seed is transformed to the shape of `variation_seed` by `variation_strength`. If `variation_strength` is 0, it only relies on the influence of the seed, and if `variation_strength` is 1.0, it is solely influenced by `variation_seed`.
|
||||
* These parameters are used when you want to maintain the composition of an image generated by the seed but wish to introduce slight changes.
|
||||
|
||||
|
||||
### Sampler nodes
|
||||
* `KSampler Progress (Inspire)` - In KSampler, the sampling process generates latent batches. By using `Video Combine` node from [ComfyUI-VideoHelperSuite](https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite), you can create a video from the progress.
|
||||
* `Scheduled CFGGuider (Inspire)` - This is a CFGGuider that adjusts the schedule from from_cfg to to_cfg using linear, log, and exp methods.
|
||||
* `Scheduled PerpNeg CFGGuider (Inspire)` - This is a PerpNeg CFGGuider that adjusts the schedule from from_cfg to to_cfg using linear, log, and exp methods.
|
||||
|
||||
|
||||
### Prompt Support - These are nodes for supporting prompt processing.
|
||||
* Prompt Support - These are nodes for supporting prompt processing.
|
||||
* `Load Prompts From Dir (Inspire)`: It sequentially reads prompts files from the specified directory. The output it returns is ZIPPED_PROMPT.
|
||||
* Specify the directories located under `ComfyUI-Inspire-Pack/prompts/`
|
||||
* One prompts file can have multiple prompts separated by `---`.
|
||||
* e.g. `prompts/example`
|
||||
* **NOTE**: This node provides advanced option via `Show advanced`
|
||||
* load_cap, start_index
|
||||
|
||||
* `Load Prompts From File (Inspire)`: It sequentially reads prompts from the specified file. The output it returns is ZIPPED_PROMPT.
|
||||
* `Load Prompts From File (Inspire)`: It sequentially reads prompts from the specified file. The output it returns is ZIPPED_PROMPT.
|
||||
* Specify the file located under `ComfyUI-Inspire-Pack/prompts/`
|
||||
* e.g. `prompts/example/prompt2.txt`
|
||||
* **NOTE**: This node provides advanced option via `Show advanced`
|
||||
* load_cap, start_index
|
||||
|
||||
* `Load Single Prompt From File (Inspire)`: Loads a single prompt from a file containing multiple prompts by using an index.
|
||||
* The prompts file directory can be specified as `inspire_prompts` in `extra_model_paths.yaml`
|
||||
* e.g. `prompts/example/prompt2.txt`
|
||||
* `Unzip Prompt (Inspire)`: Separate ZIPPED_PROMPT into `positive`, `negative`, and name components.
|
||||
* `positive` and `negative` represent text prompts, while `name` represents the name of the prompt. When loaded from a file using `Load Prompts From File (Inspire)`, the name corresponds to the file name.
|
||||
* `Zip Prompt (Inspire)`: Create ZIPPED_PROMPT from positive, negative, and name_opt.
|
||||
@@ -96,13 +71,12 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* In the `seed_prompt`, the first seed is considered the initial seed, and the reflection rate is omitted, always defaulting to 1.0.
|
||||
* Each prompt is separated by a comma, and from the second seed onwards, it should follow the format `seed:strength`.
|
||||
* Pressing the "Add to prompt" button will append `additional_seed:additional_strength` to the prompt.
|
||||
* `Composite Noise (Inspire)`: This node overwrites a specific area on top of the destination noise with the source noise.
|
||||
* `Random Generator for List (Inspire)`: When connecting the list output to the signal input, this node generates random values for all items in the list.
|
||||
* `Make Basic Pipe (Inspire)`: This is a node that creates a BASIC_PIPE using Wildcard Encode. The `Add select to` determines whether the selected item from the `Select to...` combo will be input as positive wildcard text or negative wildcard text.
|
||||
* `Remove ControlNet (Inspire)`, `Remove ControlNet [RegionalPrompts] (Inspire)`: Remove ControlNet from CONDITIONING or REGIONAL_PROMPTS.
|
||||
* `Remove ControlNet [RegionalPrompts] (Inspire)` requires Impact Pack V4.73.1 or above.
|
||||
|
||||
### Regional Nodes - These node simplifies the application of prompts by region.
|
||||
* Regional Nodes - These node simplifies the application of prompts by region.
|
||||
* Regional Sampler - These nodes assists in the easy utilization of the regional sampler in the `Impact Pack`.
|
||||
* `Regional Prompt Simple (Inspire)`: This node takes `mask` and `basic_pipe` as inputs and simplifies the creation of `REGIONAL_PROMPTS`.
|
||||
* `Regional Prompt By Color Mask (Inspire)`: Similar to `Regional Prompt Simple (Inspire)`, this function accepts a color mask image as input and defines the region using the color value that will be used as the mask, instead of directly receiving the mask.
|
||||
@@ -117,18 +91,8 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* Regional Seed Explorer - These nodes restrict the variation through a seed prompt, applying it only to the masked areas.
|
||||
* `Regional Seed Explorer By Mask (Inspire)`
|
||||
* `Regional Seed Explorer By Color Mask (Inspire)`
|
||||
* `Regional CFG (Inspire)` - By applying a mask as a multiplier to the configured cfg, it allows different areas to have different cfg settings.
|
||||
* `Color Mask To Depth Mask (Inspire)` - Convert the color map from the spec text into a mask with depth values ranging from 0.0 to 1.0.
|
||||
* The range of the mask value is limited to 0.0 to 1.0.
|
||||
* base_value: Sets the value of the base mask.
|
||||
* dilation: Dilation applied to each mask layer before flattening.
|
||||
* flatten_method: The method of flattening the mask layers.
|
||||
* The layers are flattened including the base layer set by base_value.
|
||||
* override: Each pixel is overwritten by the non-zero value of the upper layer.
|
||||
* sum: Each pixel is flattened by summing the values of all layers.
|
||||
* max: Each pixel is flattened by taking the maximum value from all layers.
|
||||
|
||||
### Image Util
|
||||
* Image Util
|
||||
* `Load Image Batch From Dir (Inspire)`: This is almost same as `LoadImagesFromDirectory` of [ComfyUI-Advanced-Controlnet](https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet). This is just a modified version. Just note that this node forcibly normalizes the size of the loaded image to match the size of the first image, even if they are not the same size, to create a batch image.
|
||||
* `Load Image List From Dir (Inspire)`: This is almost same as `Load Image Batch From Dir (Inspire)`. However, note that this node loads data in a list format, not as a batch, so it returns images at their original size without normalizing the size.
|
||||
* `Load Image (Inspire)`: This node is similar to LoadImage, but the loaded image information is stored in the workflow. The image itself is stored in the workflow, making it easier to reproduce image generation on other computers.
|
||||
@@ -138,8 +102,10 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* `ImageBatchSplitter //Inspire`, `LatentBatchSplitter //Inspire`: The script divides a batch of images/latents into individual images/latents, each with a quantity equal to the specified `split_count`. An additional output slot is added for each `split_count`. If the number of images/latents exceeds the `split_count`, the remaining ones are returned as the "remained" output.
|
||||
* `Color Map To Masks (Inspire)`: From the color_map, it extracts the top max_count number of colors and creates masks. min_pixels represents the minimum number of pixels for each color.
|
||||
* `Select Nth Mask (Inspire)`: Extracts the nth mask from the mask batch.
|
||||
|
||||
* KSampler Progress - In KSampler, the sampling process generates latent batches. By using `Video Combine` node from [ComfyUI-VideoHelperSuite](https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite), you can create a video from the progress.
|
||||
|
||||
### Backend Cache - Nodes for storing arbitrary data from the backend in a cache and sharing it across multiple workflows.
|
||||
* Backend Cache - Nodes for storing arbitrary data from the backend in a cache and sharing it across multiple workflows.
|
||||
* `Cache Backend Data (Inspire)`: Stores any backend data in the cache using a string key. Tags are for quick reference.
|
||||
* `Retrieve Backend Data (Inspire)`: Retrieves cached backend data using a string key.
|
||||
* `Remove Backend Data (Inspire)`: Removes cached backend data.
|
||||
@@ -154,32 +120,20 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* `Shared Checkpoint Loader (Inspire)`: When loading a checkpoint through this loader, it is automatically cached in the backend cache. Additionally, if it is already cached, it retrieves it from the cache instead of loading it anew.
|
||||
* When `key_opt` is empty, the `ckpt_name` is set as the cache key. The cache key output can be used for deletion purposes with Remove Back End.
|
||||
* This node resolves the issue of reloading checkpoints during workflow switching.
|
||||
* `Shared Diffusion Model Loader (Inspire)`: Similar to the `Shared Checkpoint Loader (Inspire)` but used for loading Diffusion models instead of Checkpoints.
|
||||
* `Shared Text Encoder Loader (Inspire)`: Similar to the `Shared Checkpoint Loader (Inspire)` but used for loading Text Encoder models instead of Checkpoints.
|
||||
* This node also functions as a unified node for `CLIPLoader`, `DualCLIPLoader`, and `TripleCLIPLoader`.
|
||||
* `Stable Cascade Checkpoint Loader (Inspire)`: This node provides a feature that allows you to load the `stage_b` and `stage_c` checkpoints of Stable Cascade at once, and it also provides a backend caching feature, optionally.
|
||||
* `Is Cached (Inspire)`: Returns whether the cache exists.
|
||||
|
||||
### Conditioning - Nodes for conditionings
|
||||
* Conditioning - Nodes for conditionings
|
||||
* `Concat Conditionings with Multiplier (Inspire)`: Concatenating an arbitrary number of Conditionings while applying a multiplier for each Conditioning. The multiplier depends on `comfy_PoP`, so [comfy_PoP](https://github.com/picturesonpictures/comfy_PoP) must be installed.
|
||||
* `Conditioning Upscale (Inspire)`: When upscaling an image, it helps to expand the conditioning area according to the upscale factor. Taken from [ComfyUI_Dave_CustomNode](https://github.com/Davemane42/ComfyUI_Dave_CustomNode)
|
||||
* `Conditioning Stretch (Inspire)`: When upscaling an image, it helps to expand the conditioning area by specifying the original resolution and the new resolution to be applied. Taken from [ComfyUI_Dave_CustomNode](https://github.com/Davemane42/ComfyUI_Dave_CustomNode)
|
||||
|
||||
### Models - Nodes for models
|
||||
* Models - Nodes for models
|
||||
* `IPAdapter Model Helper (Inspire)`: This provides presets that allow for easy loading of the IPAdapter related models. However, it is essential for the model's name to be accurate.
|
||||
* You can download the appropriate model through ComfyUI-Manager.
|
||||
|
||||
### List - Nodes for List processing
|
||||
* Util - Utilities
|
||||
* `Float Range (Inspire)`: Create a float list that increases the value by `step` from `start` to `stop`. A list as large as the maximum limit is created, and when `ensure_end` is enabled, the last value of the list becomes the stop value.
|
||||
* `Worklist To Item List (Inspire)`: The list in ComfyUI allows for repeated execution of a sub-workflow. This groups these repetitions (a.k.a. list) into a single ITEM_LIST output. ITEM_LIST can then be used in ForeachList.
|
||||
* `▶Foreach List (Inspire)`: A starting node for performing iterative tasks by retrieving items one by one from the ITEM_LIST.\nGenerate a new intermediate_output using item and intermediate_output as inputs, then connect it to ForeachListEnd.\nNOTE:If initial_input is omitted, the first item in item_list is used as the initial value, and the processing starts from the second item in item_list.
|
||||
* `Foreach List◀ (Inspire)`: An end node for performing iterative tasks by retrieving items one by one from the ITEM_LIST.\nNOTE:Directly connect the outputs of ForeachListBegin to 'flow_control' and 'remained_list'.
|
||||
* `Drop List (Inspire)`: Removes all items from the ITEM_LIST. If the ITEM_LIST generated through this node is passed to ForeachListEnd, the process is immediately terminated.
|
||||
|
||||
### Util - Utilities
|
||||
* `ToIPAdapterPipe (Inspire)`, `FromIPAdapterPipe (Inspire)`: These nodes assists in conveniently using the bundled ipadapter_model, clip_vision, and model required for applying IPAdapter.
|
||||
* `List Counter (Inspire)`: When each item in the list traverses through this node, it increments a counter by one, generating an integer value.
|
||||
* `RGB Hex To HSV (Inspire)`: Convert an RGB hex string like `#FFD500` to HSV:
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
@@ -195,10 +149,4 @@ Kosinkadink/[ComfyUI-Advanced-Controlnet](https://github.com/Kosinkadink/ComfyUI
|
||||
|
||||
Trung0246/[ComfyUI-0246](https://github.com/Trung0246/ComfyUI-0246) - Nice bypass hack!
|
||||
|
||||
cubiq/[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - IPAdapter related nodes depend on this extension.
|
||||
|
||||
Davemane42/[ComfyUI_Dave_CustomNode](https://github.com/Davemane42/ComfyUI_Dave_CustomNode) - Original author of ConditioningStretch, ConditioningUpscale
|
||||
|
||||
BlenderNeko/[ComfyUI_Noise](https://github.com/BlenderNeko/ComfyUI_Noise) - slerp code for noise variation
|
||||
|
||||
BadCafeCode/[execution-inversion-demo-comfyui](https://github.com/BadCafeCode/execution-inversion-demo-comfyui) - reference loop implementation for ComfyUI
|
||||
cubiq/[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - IPAdapter related nodes depend on this extension.
|
||||
+14
-6
@@ -2,15 +2,14 @@
|
||||
@author: Dr.Lt.Data
|
||||
@title: Inspire Pack
|
||||
@nickname: Inspire Pack
|
||||
@description: This extension provides various nodes to support Lora Block Weight, Regional Nodes, Backend Cache, Prompt Utils, List Utils and the Impact Pack.
|
||||
@description: This extension provides various nodes to support Lora Block Weight and the Impact Pack.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
|
||||
version_code = [1, 19, 1]
|
||||
version_code = [0, 69, 2]
|
||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
logging.info(f"### Loading: ComfyUI-Inspire-Pack ({version_str})")
|
||||
print(f"### Loading: ComfyUI-Inspire-Pack ({version_str})")
|
||||
|
||||
node_list = [
|
||||
"lora_block_weight",
|
||||
@@ -24,8 +23,7 @@ node_list = [
|
||||
"backend_support",
|
||||
"list_nodes",
|
||||
"conditioning_nodes",
|
||||
"model_nodes",
|
||||
"util_nodes"
|
||||
"model_nodes"
|
||||
]
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
@@ -39,3 +37,13 @@ for module_name in node_list:
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
|
||||
try:
|
||||
import cm_global
|
||||
cm_global.register_extension('ComfyUI-Inspire-Pack',
|
||||
{'version': version_code,
|
||||
'name': 'Inspire Pack',
|
||||
'nodes': set(NODE_CLASS_MAPPINGS.keys()),
|
||||
'description': 'This extension provides various nodes to support Lora Block Weight and the Impact Pack. Provides many easily applicable regional features and applications for Variation Seed.', })
|
||||
except:
|
||||
pass
|
||||
|
||||
+28
-139
@@ -5,77 +5,14 @@ from einops import rearrange
|
||||
import random
|
||||
import math
|
||||
from .libs import common
|
||||
import logging
|
||||
|
||||
|
||||
supported_noise_modes = ["GPU(=A1111)", "CPU", "GPU+internal_seed", "CPU+internal_seed"]
|
||||
|
||||
|
||||
class Inspire_RandomNoise:
|
||||
def __init__(self, seed, mode, incremental_seed_mode, variation_seed, variation_strength, variation_method="linear", internal_seed=None):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
# HOTFIX: https://github.com/comfyanonymous/ComfyUI/commit/916d1e14a93ef331adef7c0deff2fdcf443b05cf#commitcomment-151914788
|
||||
# seed value should be different with generated noise
|
||||
|
||||
self.seed = internal_seed
|
||||
self.noise_seed = seed
|
||||
self.noise_device = "cpu" if mode == "CPU" else device
|
||||
self.incremental_seed_mode = incremental_seed_mode
|
||||
self.variation_seed = variation_seed
|
||||
self.variation_strength = variation_strength
|
||||
self.variation_method = variation_method
|
||||
|
||||
def generate_noise(self, input_latent):
|
||||
latent_image = input_latent["samples"]
|
||||
batch_inds = input_latent["batch_index"] if "batch_index" in input_latent else None
|
||||
noise = utils.prepare_noise(latent_image, self.noise_seed, batch_inds, self.noise_device, self.incremental_seed_mode,
|
||||
variation_seed=self.variation_seed, variation_strength=self.variation_strength, variation_method=self.variation_method)
|
||||
return noise.cpu()
|
||||
|
||||
|
||||
class RandomNoise:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed for the initial noise applied to the latent."}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"batch_seed_mode": (["incremental", "comfy", "variation str inc:0.01", "variation str inc:0.05"],),
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"internal_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed used for generating noise in intermediate steps when using ancestral and SDE-based samplers.\nNOTE: If `noise_mode` is in GPU mode and `internal_seed` is the same as `seed`, the generated image may be distorted."}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
FUNCTION = "get_noise"
|
||||
CATEGORY = "InspirePack/a1111_compat"
|
||||
|
||||
def get_noise(self, noise_seed, noise_mode, batch_seed_mode, variation_seed, variation_strength, variation_method="linear", internal_seed=None):
|
||||
if internal_seed is None:
|
||||
internal_seed = noise_seed
|
||||
|
||||
return (Inspire_RandomNoise(noise_seed, noise_mode, batch_seed_mode, variation_seed, variation_strength, variation_method=variation_method, internal_seed=internal_seed),)
|
||||
|
||||
|
||||
def inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0,
|
||||
noise_mode="CPU", disable_noise=False, start_step=None, last_step=None, force_full_denoise=False,
|
||||
incremental_seed_mode="comfy", variation_seed=None, variation_strength=None, noise=None, callback=None, variation_method="linear",
|
||||
scheduler_func=None, internal_seed=None):
|
||||
incremental_seed_mode="comfy", variation_seed=None, variation_strength=None, noise=None, callback=None):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
noise_device = "cpu" if 'cpu' in noise_mode.lower() else device
|
||||
noise_device = "cpu" if noise_mode == "CPU" else device
|
||||
latent_image = latent["samples"]
|
||||
if hasattr(comfy.sample, 'fix_empty_latent_channels'):
|
||||
latent_image = comfy.sample.fix_empty_latent_channels(model, latent_image)
|
||||
|
||||
latent = latent.copy()
|
||||
|
||||
if noise is not None and latent_image.shape[1] != noise.shape[1]:
|
||||
logging.info("[Inspire Pack] inspire_ksampler: The type of latent input for noise generation does not match the model's latent type. When using the SD3 model, you must use the SD3 Empty Latent.")
|
||||
raise Exception("The type of latent input for noise generation does not match the model's latent type. When using the SD3 model, you must use the SD3 Empty Latent.")
|
||||
|
||||
if noise is None:
|
||||
if disable_noise:
|
||||
@@ -83,8 +20,7 @@ def inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device=noise_device)
|
||||
else:
|
||||
batch_inds = latent["batch_index"] if "batch_index" in latent else None
|
||||
noise = utils.prepare_noise(latent_image, seed, batch_inds, noise_device, incremental_seed_mode,
|
||||
variation_seed=variation_seed, variation_strength=variation_strength, variation_method=variation_method)
|
||||
noise = utils.prepare_noise(latent_image, seed, batch_inds, noise_device, incremental_seed_mode, variation_seed=variation_seed, variation_strength=variation_strength)
|
||||
|
||||
if start_step is None:
|
||||
if denoise == 1.0:
|
||||
@@ -94,27 +30,9 @@ def inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
start_step = advanced_steps - steps
|
||||
steps = advanced_steps
|
||||
|
||||
if internal_seed is None:
|
||||
internal_seed = seed
|
||||
|
||||
if 'internal_seed' in noise_mode:
|
||||
seed = internal_seed
|
||||
|
||||
try:
|
||||
samples = common.impact_sampling(
|
||||
model=model, add_noise=not disable_noise, seed=seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler, positive=positive, negative=negative,
|
||||
latent_image=latent, start_at_step=start_step, end_at_step=last_step, return_with_leftover_noise=not force_full_denoise, noise=noise, callback=callback,
|
||||
scheduler_func=scheduler_func)
|
||||
except Exception as e:
|
||||
if "unexpected keyword argument 'scheduler_func'" in str(e):
|
||||
logging.info("[Inspire Pack] Impact Pack is outdated. (Cannot use GITS scheduler.)")
|
||||
|
||||
samples = common.impact_sampling(
|
||||
model=model, add_noise=not disable_noise, seed=seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler, positive=positive, negative=negative,
|
||||
latent_image=latent, start_at_step=start_step, end_at_step=last_step, return_with_leftover_noise=not force_full_denoise, noise=noise, callback=callback)
|
||||
else:
|
||||
raise e
|
||||
|
||||
samples = common.impact_sampling(
|
||||
model=model, add_noise=not disable_noise, seed=seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler, positive=positive, negative=negative,
|
||||
latent_image=latent, start_at_step=start_step, end_at_step=last_step, return_with_leftover_noise=not force_full_denoise, noise=noise, callback=callback)
|
||||
return samples, noise
|
||||
|
||||
|
||||
@@ -123,7 +41,7 @@ class KSampler_inspire:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"model": ("MODEL",),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed for the initial noise applied to the latent."}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
@@ -132,17 +50,11 @@ class KSampler_inspire:
|
||||
"negative": ("CONDITIONING", ),
|
||||
"latent_image": ("LATENT", ),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (supported_noise_modes,),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"batch_seed_mode": (["incremental", "comfy", "variation str inc:0.01", "variation str inc:0.05"],),
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"internal_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed used for generating noise in intermediate steps when using ancestral and SDE-based samplers.\nNOTE: If `noise_mode` is in GPU mode and `internal_seed` is the same as `seed`, the generated image may be distorted."}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
@@ -150,13 +62,8 @@ class KSampler_inspire:
|
||||
|
||||
CATEGORY = "InspirePack/a1111_compat"
|
||||
|
||||
@staticmethod
|
||||
def doit(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode,
|
||||
batch_seed_mode="comfy", variation_seed=None, variation_strength=None, variation_method="linear", scheduler_func_opt=None,
|
||||
internal_seed=None):
|
||||
return (inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode,
|
||||
incremental_seed_mode=batch_seed_mode, variation_seed=variation_seed, variation_strength=variation_strength, variation_method=variation_method,
|
||||
scheduler_func=scheduler_func_opt, internal_seed=internal_seed)[0], )
|
||||
def doit(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, batch_seed_mode="comfy", variation_seed=None, variation_strength=None):
|
||||
return (inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, incremental_seed_mode=batch_seed_mode, variation_seed=variation_seed, variation_strength=variation_strength)[0],)
|
||||
|
||||
|
||||
class KSamplerAdvanced_inspire:
|
||||
@@ -165,7 +72,7 @@ class KSamplerAdvanced_inspire:
|
||||
return {"required":
|
||||
{"model": ("MODEL",),
|
||||
"add_noise": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed for the initial noise applied to the latent."}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
@@ -175,7 +82,7 @@ class KSamplerAdvanced_inspire:
|
||||
"latent_image": ("LATENT", ),
|
||||
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
||||
"end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}),
|
||||
"noise_mode": (supported_noise_modes,),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}),
|
||||
"batch_seed_mode": (["incremental", "comfy", "variation str inc:0.01", "variation str inc:0.05"],),
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
@@ -183,10 +90,7 @@ class KSamplerAdvanced_inspire:
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"noise_opt": ("NOISE_IMAGE",),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"internal_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed used for generating noise in intermediate steps when using ancestral and SDE-based samplers.\nNOTE: If `noise_mode` is in GPU mode and `internal_seed` is the same as `seed`, the generated image may be distorted."}),
|
||||
"noise_opt": ("NOISE",),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +100,7 @@ class KSamplerAdvanced_inspire:
|
||||
CATEGORY = "InspirePack/a1111_compat"
|
||||
|
||||
@staticmethod
|
||||
def sample(model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step, noise_mode, return_with_leftover_noise,
|
||||
denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None, callback=None, variation_method="linear", scheduler_func_opt=None, internal_seed=None):
|
||||
def sample(model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step, noise_mode, return_with_leftover_noise, denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None, callback=None):
|
||||
force_full_denoise = True
|
||||
|
||||
if return_with_leftover_noise:
|
||||
@@ -211,8 +114,7 @@ class KSamplerAdvanced_inspire:
|
||||
return inspire_ksampler(model, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image,
|
||||
denoise=denoise, disable_noise=disable_noise, start_step=start_at_step, last_step=end_at_step,
|
||||
force_full_denoise=force_full_denoise, noise_mode=noise_mode, incremental_seed_mode=batch_seed_mode,
|
||||
variation_seed=variation_seed, variation_strength=variation_strength, noise=noise_opt, callback=callback, variation_method=variation_method,
|
||||
scheduler_func=scheduler_func_opt, internal_seed=internal_seed)
|
||||
variation_seed=variation_seed, variation_strength=variation_strength, noise=noise_opt, callback=callback)
|
||||
|
||||
def doit(self, *args, **kwargs):
|
||||
return (self.sample(*args, **kwargs)[0],)
|
||||
@@ -223,23 +125,18 @@ class KSampler_inspire_pipe:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"basic_pipe": ("BASIC_PIPE",),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed for the initial noise applied to the latent."}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (common.SCHEDULERS, ),
|
||||
"latent_image": ("LATENT", ),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (supported_noise_modes,),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"batch_seed_mode": (["incremental", "comfy", "variation str inc:0.01", "variation str inc:0.05"],),
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"internal_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed used for generating noise in intermediate steps when using ancestral and SDE-based samplers.\nNOTE: If `noise_mode` is in GPU mode and `internal_seed` is the same as `seed`, the generated image may be distorted."}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT", "VAE")
|
||||
@@ -247,11 +144,9 @@ class KSampler_inspire_pipe:
|
||||
|
||||
CATEGORY = "InspirePack/a1111_compat"
|
||||
|
||||
def sample(self, basic_pipe, seed, steps, cfg, sampler_name, scheduler, latent_image, denoise, noise_mode, batch_seed_mode="comfy",
|
||||
variation_seed=None, variation_strength=None, scheduler_func_opt=None, internal_seed=None):
|
||||
def sample(self, basic_pipe, seed, steps, cfg, sampler_name, scheduler, latent_image, denoise, noise_mode, batch_seed_mode="comfy", variation_seed=None, variation_strength=None):
|
||||
model, clip, vae, positive, negative = basic_pipe
|
||||
latent = inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, incremental_seed_mode=batch_seed_mode,
|
||||
variation_seed=variation_seed, variation_strength=variation_strength, scheduler_func=scheduler_func_opt, internal_seed=internal_seed)[0]
|
||||
latent = inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, incremental_seed_mode=batch_seed_mode, variation_seed=variation_seed, variation_strength=variation_strength)[0]
|
||||
return latent, vae
|
||||
|
||||
|
||||
@@ -261,7 +156,7 @@ class KSamplerAdvanced_inspire_pipe:
|
||||
return {"required":
|
||||
{"basic_pipe": ("BASIC_PIPE",),
|
||||
"add_noise": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed for the initial noise applied to the latent."}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
@@ -269,7 +164,7 @@ class KSamplerAdvanced_inspire_pipe:
|
||||
"latent_image": ("LATENT", ),
|
||||
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
||||
"end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}),
|
||||
"noise_mode": (supported_noise_modes,),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}),
|
||||
"batch_seed_mode": (["incremental", "comfy", "variation str inc:0.01", "variation str inc:0.05"],),
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
@@ -277,9 +172,7 @@ class KSamplerAdvanced_inspire_pipe:
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"noise_opt": ("NOISE_IMAGE",),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"internal_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed used for generating noise in intermediate steps when using ancestral and SDE-based samplers.\nNOTE: If `noise_mode` is in GPU mode and `internal_seed` is the same as `seed`, the generated image may be distorted."}),
|
||||
"noise_opt": ("NOISE",),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,8 +181,7 @@ class KSamplerAdvanced_inspire_pipe:
|
||||
|
||||
CATEGORY = "InspirePack/a1111_compat"
|
||||
|
||||
def sample(self, basic_pipe, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, latent_image, start_at_step, end_at_step, noise_mode, return_with_leftover_noise,
|
||||
denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None, scheduler_func_opt=None, internal_seed=None):
|
||||
def sample(self, basic_pipe, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, latent_image, start_at_step, end_at_step, noise_mode, return_with_leftover_noise, denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None):
|
||||
model, clip, vae, positive, negative = basic_pipe
|
||||
latent = KSamplerAdvanced_inspire().sample(model=model, add_noise=add_noise, noise_seed=noise_seed,
|
||||
steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler,
|
||||
@@ -297,8 +189,7 @@ class KSamplerAdvanced_inspire_pipe:
|
||||
start_at_step=start_at_step, end_at_step=end_at_step,
|
||||
noise_mode=noise_mode, return_with_leftover_noise=return_with_leftover_noise,
|
||||
denoise=denoise, batch_seed_mode=batch_seed_mode, variation_seed=variation_seed,
|
||||
variation_strength=variation_strength, noise_opt=noise_opt, scheduler_func_opt=scheduler_func_opt,
|
||||
internal_seed=internal_seed)[0]
|
||||
variation_strength=variation_strength, noise_opt=noise_opt)[0]
|
||||
return latent, vae
|
||||
|
||||
|
||||
@@ -413,7 +304,7 @@ class HyperTileInspire:
|
||||
nh = random_divisor(h, latent_tile_size * factor, swap_size, rand_obj)
|
||||
nw = random_divisor(w, latent_tile_size * factor, swap_size, rand_obj)
|
||||
|
||||
logging.debug(f"factor: {factor} <--- params.depth: {apply_to.index(model_chans)} / scale_depth: {scale_depth} / latent_tile_size={latent_tile_size}")
|
||||
print(f"factor: {factor} <--- params.depth: {apply_to.index(model_chans)} / scale_depth: {scale_depth} / latent_tile_size={latent_tile_size}")
|
||||
# print(f"h: {h}, w:{w} --> nh: {nh}, nw: {nw}")
|
||||
|
||||
if nh * nw > 1:
|
||||
@@ -422,7 +313,7 @@ class HyperTileInspire:
|
||||
# else:
|
||||
# temp = None
|
||||
|
||||
logging.debug(f"q={q} / k={k} / v={v}")
|
||||
print(f"q={q} / k={k} / v={v}")
|
||||
return q, k, v
|
||||
|
||||
return q, k, v
|
||||
@@ -447,7 +338,6 @@ NODE_CLASS_MAPPINGS = {
|
||||
"KSamplerAdvanced //Inspire": KSamplerAdvanced_inspire,
|
||||
"KSamplerPipe //Inspire": KSampler_inspire_pipe,
|
||||
"KSamplerAdvancedPipe //Inspire": KSamplerAdvanced_inspire_pipe,
|
||||
"RandomNoise //Inspire": RandomNoise,
|
||||
"HyperTile //Inspire": HyperTileInspire
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -455,6 +345,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"KSamplerAdvanced //Inspire": "KSamplerAdvanced (inspire)",
|
||||
"KSamplerPipe //Inspire": "KSampler [pipe] (inspire)",
|
||||
"KSamplerAdvancedPipe //Inspire": "KSamplerAdvanced [pipe] (inspire)",
|
||||
"RandomNoise //Inspire": "RandomNoise (inspire)",
|
||||
"HyperTile //Inspire": "HyperTile (Inspire)"
|
||||
}
|
||||
|
||||
+18
-269
@@ -1,6 +1,5 @@
|
||||
import json
|
||||
import os
|
||||
from .libs import common
|
||||
|
||||
import folder_paths
|
||||
import nodes
|
||||
@@ -8,15 +7,13 @@ from server import PromptServer
|
||||
|
||||
from .libs.utils import TaggedCache, any_typ
|
||||
|
||||
import logging
|
||||
|
||||
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
settings_file = os.path.join(root_dir, 'cache_settings.json')
|
||||
try:
|
||||
with open(settings_file) as f:
|
||||
cache_settings = json.load(f)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
print(e)
|
||||
cache_settings = {}
|
||||
cache = TaggedCache(cache_settings)
|
||||
cache_count = {}
|
||||
@@ -60,13 +57,11 @@ class CacheBackendData:
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
@staticmethod
|
||||
def doit(key, tag, data):
|
||||
def doit(self, key, tag, data):
|
||||
global cache
|
||||
|
||||
if key == '*':
|
||||
logging.warning("[Inspire Pack] CacheBackendData: '*' is reserved key. Cannot use that key")
|
||||
return (None,)
|
||||
print(f"[Inspire Pack] CacheBackendData: '*' is reserved key. Cannot use that key")
|
||||
|
||||
update_cache(key, tag, (False, data))
|
||||
return (data,)
|
||||
@@ -92,8 +87,7 @@ class CacheBackendDataNumberKey:
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
@staticmethod
|
||||
def doit(key, tag, data):
|
||||
def doit(self, key, tag, data):
|
||||
global cache
|
||||
|
||||
update_cache(key, tag, (False, data))
|
||||
@@ -123,13 +117,11 @@ class CacheBackendDataList:
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
@staticmethod
|
||||
def doit(key, tag, data):
|
||||
def doit(self, key, tag, data):
|
||||
global cache
|
||||
|
||||
if key == '*':
|
||||
logging.warning("[Inspire Pack] CacheBackendDataList: '*' is reserved key. Cannot use that key")
|
||||
return (None,)
|
||||
print(f"[Inspire Pack] CacheBackendDataList: '*' is reserved key. Cannot use that key")
|
||||
|
||||
update_cache(key[0], tag[0], (True, data))
|
||||
return (data,)
|
||||
@@ -188,7 +180,7 @@ class RetrieveBackendData:
|
||||
v = cache.get(key)
|
||||
|
||||
if v is None:
|
||||
logging.warning(f"[RetrieveBackendData] '{key}' is unregistered key.")
|
||||
print(f"[RetrieveBackendData] '{key}' is unregistered key.")
|
||||
return (None,)
|
||||
|
||||
is_list, data = v[1]
|
||||
@@ -243,7 +235,7 @@ class RemoveBackendData:
|
||||
elif key in cache:
|
||||
del cache[key]
|
||||
else:
|
||||
logging.warning(f"[Inspire Pack] RemoveBackendData: invalid data key {key}")
|
||||
print(f"[Inspire Pack] RemoveBackendData: invalid data key {key}")
|
||||
|
||||
return (signal_opt,)
|
||||
|
||||
@@ -267,7 +259,7 @@ class RemoveBackendDataNumberKey(RemoveBackendData):
|
||||
if key in cache:
|
||||
del cache[key]
|
||||
else:
|
||||
logging.warning(f"[Inspire Pack] RemoveBackendDataNumberKey: invalid data key {key}")
|
||||
print(f"[Inspire Pack] RemoveBackendDataNumberKey: invalid data key {key}")
|
||||
|
||||
return (signal_opt,)
|
||||
|
||||
@@ -327,6 +319,7 @@ class ShowCachedInfo:
|
||||
# tag settings is not changed
|
||||
return
|
||||
|
||||
# print(f'set to {new_tag_settings}')
|
||||
new_cache = TaggedCache(new_tag_settings)
|
||||
for k, v in cache.items():
|
||||
new_cache[k] = v
|
||||
@@ -374,10 +367,10 @@ class CheckpointLoaderSimpleShared(nodes.CheckpointLoaderSimple):
|
||||
res = self.load_checkpoint(ckpt_name)
|
||||
update_cache(key, "ckpt", (False, res))
|
||||
cache_kind = 'ckpt'
|
||||
logging.info(f"[Inspire Pack] CheckpointLoaderSimpleShared: Ckpt '{ckpt_name}' is cached to '{key}'.")
|
||||
print(f"[Inspire Pack] CheckpointLoaderSimpleShared: Ckpt '{ckpt_name}' is cached to '{key}'.")
|
||||
else:
|
||||
cache_kind, (_, res) = cache[key]
|
||||
logging.info(f"[Inspire Pack] CheckpointLoaderSimpleShared: Cached ckpt '{key}' is loaded. (Loading skip)")
|
||||
print(f"[Inspire Pack] CheckpointLoaderSimpleShared: Cached ckpt '{key}' is loaded. (Loading skip)")
|
||||
|
||||
if cache_kind == 'ckpt':
|
||||
model, clip, vae = res
|
||||
@@ -407,174 +400,6 @@ class CheckpointLoaderSimpleShared(nodes.CheckpointLoaderSimple):
|
||||
return (None, cache_weak_hash(key))
|
||||
|
||||
|
||||
class LoadDiffusionModelShared(nodes.UNETLoader):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "model_name": (folder_paths.get_filename_list("diffusion_models"), {"tooltip": "Diffusion Model Name"}),
|
||||
"weight_dtype": (["default", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e5m2"],),
|
||||
"key_opt": ("STRING", {"multiline": False, "placeholder": "If empty, use 'model_name' as the key."}),
|
||||
"mode": (['Auto', 'Override Cache', 'Read Only'],),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("MODEL", "STRING")
|
||||
RETURN_NAMES = ("model", "cache key")
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Backend"
|
||||
|
||||
def doit(self, model_name, weight_dtype, key_opt, mode='Auto'):
|
||||
if mode == 'Read Only':
|
||||
if key_opt.strip() == '':
|
||||
raise Exception("[LoadDiffusionModelShared] key_opt cannot be omit if mode is 'Read Only'")
|
||||
key = key_opt.strip()
|
||||
elif key_opt.strip() == '':
|
||||
key = f"{model_name}_{weight_dtype}"
|
||||
else:
|
||||
key = key_opt.strip()
|
||||
|
||||
if key not in cache or mode == 'Override Cache':
|
||||
model = self.load_unet(model_name, weight_dtype)[0]
|
||||
update_cache(key, "diffusion", (False, model))
|
||||
logging.info(f"[Inspire Pack] LoadDiffusionModelShared: diffusion model '{model_name}' is cached to '{key}'.")
|
||||
else:
|
||||
_, (_, model) = cache[key]
|
||||
logging.info(f"[Inspire Pack] LoadDiffusionModelShared: Cached diffusion model '{key}' is loaded. (Loading skip)")
|
||||
|
||||
return model, key
|
||||
|
||||
@staticmethod
|
||||
def IS_CHANGED(model_name, weight_dtype, key_opt, mode='Auto'):
|
||||
if mode == 'Read Only':
|
||||
if key_opt.strip() == '':
|
||||
raise Exception("[LoadDiffusionModelShared] key_opt cannot be omit if mode is 'Read Only'")
|
||||
key = key_opt.strip()
|
||||
elif key_opt.strip() == '':
|
||||
key = f"{model_name}_{weight_dtype}"
|
||||
else:
|
||||
key = key_opt.strip()
|
||||
|
||||
if mode == 'Read Only':
|
||||
return None, cache_weak_hash(key)
|
||||
elif mode == 'Override Cache':
|
||||
return model_name, key
|
||||
|
||||
return None, cache_weak_hash(key)
|
||||
|
||||
|
||||
class LoadTextEncoderShared:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "model_name1": (folder_paths.get_filename_list("text_encoders"), ),
|
||||
"model_name2": (["None"] + folder_paths.get_filename_list("text_encoders"), ),
|
||||
"model_name3": (["None"] + folder_paths.get_filename_list("text_encoders"), ),
|
||||
"type": (["stable_diffusion", "stable_cascade", "sd3", "stable_audio", "mochi", "ltxv", "pixart", "cosmos", "sdxl", "flux", "hunyuan_video"], ),
|
||||
"key_opt": ("STRING", {"multiline": False, "placeholder": "If empty, use 'model_name' as the key."}),
|
||||
"mode": (['Auto', 'Override Cache', 'Read Only'],),
|
||||
},
|
||||
"optional": { "device": (["default", "cpu"], {"advanced": True}), }
|
||||
}
|
||||
RETURN_TYPES = ("CLIP", "STRING")
|
||||
RETURN_NAMES = ("clip", "cache key")
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Backend"
|
||||
|
||||
DESCRIPTION = \
|
||||
("[Recipes single]\n"
|
||||
"stable_diffusion: clip-l\n"
|
||||
"stable_cascade: clip-g\n"
|
||||
"sd3: t5 / clip-g / clip-l\n"
|
||||
"stable_audio: t5\n"
|
||||
"mochi: t5\n"
|
||||
"cosmos: old t5 xxl\n\n"
|
||||
"[Recipes dual]\n"
|
||||
"sdxl: clip-l, clip-g\n"
|
||||
"sd3: clip-l, clip-g / clip-l, t5 / clip-g, t5\n"
|
||||
"flux: clip-l, t5\n\n"
|
||||
"[Recipes triple]\n"
|
||||
"sd3: clip-l, clip-g, t5")
|
||||
|
||||
def doit(self, model_name1, model_name2, model_name3, type, key_opt, mode='Auto', device="default"):
|
||||
if mode == 'Read Only':
|
||||
if key_opt.strip() == '':
|
||||
raise Exception("[LoadTextEncoderShared] key_opt cannot be omit if mode is 'Read Only'")
|
||||
key = key_opt.strip()
|
||||
elif key_opt.strip() == '':
|
||||
key = model_name1
|
||||
if model_name2 is not None:
|
||||
key += f"_{model_name2}"
|
||||
if model_name3 is not None:
|
||||
key += f"_{model_name3}"
|
||||
key += f"_{type}_{device}"
|
||||
else:
|
||||
key = key_opt.strip()
|
||||
|
||||
if key not in cache or mode == 'Override Cache':
|
||||
if model_name2 != "None" and model_name3 != "None": # triple text encoder
|
||||
if len({model_name1, model_name2, model_name3}) < 3:
|
||||
logging.error("[LoadTextEncoderShared] The same model has been selected multiple times.")
|
||||
raise ValueError("The same model has been selected multiple times.")
|
||||
|
||||
if type not in ["sd3"]:
|
||||
logging.error("[LoadTextEncoderShared] Currently, the triple text encoder is only supported in `sd3`.")
|
||||
raise ValueError("Currently, the triple text encoder is only supported in `sd3`.")
|
||||
|
||||
res = nodes.NODE_CLASS_MAPPINGS["TripleCLIPLoader"]().load_clip(model_name1, model_name2, model_name3)[0]
|
||||
|
||||
elif model_name2 != "None" or model_name3 != "None": # dual text encoder
|
||||
second_model = model_name2 if model_name2 != "None" else model_name3
|
||||
|
||||
if model_name1 == second_model:
|
||||
logging.error("[LoadTextEncoderShared] You have selected the same model for both.")
|
||||
raise ValueError("[LoadTextEncoderShared] You have selected the same model for both.")
|
||||
|
||||
if type not in ["sdxl", "sd3", "flux", "hunyuan_video"]:
|
||||
logging.error("[LoadTextEncoderShared] Currently, the triple text encoder is only supported in `sdxl, sd3, flux, hunyuan_video`.")
|
||||
raise ValueError("Currently, the triple text encoder is only supported in `sdxl, sd3, flux, hunyuan_video`.")
|
||||
|
||||
res = nodes.NODE_CLASS_MAPPINGS["DualCLIPLoader"]().load_clip(model_name1, second_model, type=type, device=device)[0]
|
||||
|
||||
else: # single text encoder
|
||||
if type not in ["stable_diffusion", "stable_cascade", "sd3", "stable_audio", "mochi", "ltxv", "pixart", "cosmos"]:
|
||||
logging.error("[LoadTextEncoderShared] Currently, the single text encoder is only supported in `stable_diffusion, stable_cascade, sd3, stable_audio, mochi, ltxv, pixart, cosmos`.")
|
||||
raise ValueError("Currently, the single text encoder is only supported in `stable_diffusion, stable_cascade, sd3, stable_audio, mochi, ltxv, pixart, cosmos`.")
|
||||
|
||||
res = nodes.NODE_CLASS_MAPPINGS["CLIPLoader"]().load_clip(model_name1, type=type, device=device)[0]
|
||||
|
||||
update_cache(key, "diffusion", (False, res))
|
||||
logging.info(f"[Inspire Pack] LoadTextEncoderShared: text encoder model set is cached to '{key}'.")
|
||||
else:
|
||||
_, (_, res) = cache[key]
|
||||
logging.info(f"[Inspire Pack] LoadTextEncoderShared: Cached text encoder model set '{key}' is loaded. (Loading skip)")
|
||||
|
||||
return res, key
|
||||
|
||||
@staticmethod
|
||||
def IS_CHANGED(model_name1, model_name2, model_name3, type, key_opt, mode='Auto', device="default"):
|
||||
if mode == 'Read Only':
|
||||
if key_opt.strip() == '':
|
||||
raise Exception("[LoadTextEncoderShared] key_opt cannot be omit if mode is 'Read Only'")
|
||||
key = key_opt.strip()
|
||||
elif key_opt.strip() == '':
|
||||
key = model_name1
|
||||
if model_name2 is not None:
|
||||
key += f"_{model_name2}"
|
||||
if model_name3 is not None:
|
||||
key += f"_{model_name3}"
|
||||
key += f"_{type}_{device}"
|
||||
else:
|
||||
key = key_opt.strip()
|
||||
|
||||
if mode == 'Read Only':
|
||||
return None, cache_weak_hash(key)
|
||||
elif mode == 'Override Cache':
|
||||
return f"{model_name1}_{model_name2}_{model_name3}_{type}_{device}", key
|
||||
|
||||
return None, cache_weak_hash(key)
|
||||
|
||||
|
||||
class StableCascade_CheckpointLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -630,10 +455,10 @@ class StableCascade_CheckpointLoader:
|
||||
if key_b not in cache:
|
||||
res_b = nodes.CheckpointLoaderSimple().load_checkpoint(ckpt_name=stage_b)
|
||||
update_cache(key_b, "ckpt", (False, res_b))
|
||||
logging.info(f"[Inspire Pack] StableCascade_CheckpointLoader: Ckpt '{stage_b}' is cached to '{key_b}'.")
|
||||
print(f"[Inspire Pack] StableCascade_CheckpointLoader: Ckpt '{stage_b}' is cached to '{key_b}'.")
|
||||
else:
|
||||
_, (_, res_b) = cache[key_b]
|
||||
logging.info(f"[Inspire Pack] StableCascade_CheckpointLoader: Cached ckpt '{key_b}' is loaded. (Loading skip)")
|
||||
print(f"[Inspire Pack] StableCascade_CheckpointLoader: Cached ckpt '{key_b}' is loaded. (Loading skip)")
|
||||
b_model, clip, b_vae = res_b
|
||||
else:
|
||||
b_model, clip, b_vae = nodes.CheckpointLoaderSimple().load_checkpoint(ckpt_name=stage_b)
|
||||
@@ -642,10 +467,10 @@ class StableCascade_CheckpointLoader:
|
||||
if key_c not in cache:
|
||||
res_c = nodes.unCLIPCheckpointLoader().load_checkpoint(ckpt_name=stage_c)
|
||||
update_cache(key_c, "unclip_ckpt", (False, res_c))
|
||||
logging.info(f"[Inspire Pack] StableCascade_CheckpointLoader: Ckpt '{stage_c}' is cached to '{key_c}'.")
|
||||
print(f"[Inspire Pack] StableCascade_CheckpointLoader: Ckpt '{stage_c}' is cached to '{key_c}'.")
|
||||
else:
|
||||
_, (_, res_c) = cache[key_c]
|
||||
logging.info(f"[Inspire Pack] StableCascade_CheckpointLoader: Cached ckpt '{key_c}' is loaded. (Loading skip)")
|
||||
print(f"[Inspire Pack] StableCascade_CheckpointLoader: Cached ckpt '{key_c}' is loaded. (Loading skip)")
|
||||
c_model, _, c_vae, clip_vision = res_c
|
||||
else:
|
||||
c_model, _, c_vae, clip_vision = nodes.unCLIPCheckpointLoader().load_checkpoint(ckpt_name=stage_c)
|
||||
@@ -653,74 +478,6 @@ class StableCascade_CheckpointLoader:
|
||||
return b_model, b_vae, c_model, c_vae, clip_vision, clip, key_b, key_c
|
||||
|
||||
|
||||
class IsCached:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"key": ("STRING", {"multiline": False}),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID"
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Backend"
|
||||
|
||||
@staticmethod
|
||||
def IS_CHANGED(key, unique_id):
|
||||
return common.is_changed(unique_id, key in cache)
|
||||
|
||||
def doit(self, key, unique_id):
|
||||
return (key in cache,)
|
||||
|
||||
|
||||
# WIP: not properly working, yet
|
||||
class CacheBridge:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"value": (any_typ,),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_off": "cached", "label_on": "passthrough"}),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID"
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_typ, )
|
||||
RETURN_NAMES = ("value",)
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Backend"
|
||||
|
||||
@staticmethod
|
||||
def IS_CHANGED(value, mode, unique_id):
|
||||
if not mode and unique_id in common.changed_cache:
|
||||
return common.not_changed_value(unique_id)
|
||||
else:
|
||||
return common.changed_value(unique_id)
|
||||
|
||||
def doit(self, value, mode, unique_id):
|
||||
if not mode:
|
||||
# cache mode
|
||||
if unique_id not in common.changed_cache:
|
||||
common.changed_cache[unique_id] = value
|
||||
common.changed_count_cache[unique_id] = 0
|
||||
|
||||
return (common.changed_cache[unique_id],)
|
||||
else:
|
||||
common.changed_cache[unique_id] = value
|
||||
common.changed_count_cache[unique_id] = 0
|
||||
|
||||
return (common.changed_cache[unique_id],)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"CacheBackendData //Inspire": CacheBackendData,
|
||||
"CacheBackendDataNumberKey //Inspire": CacheBackendDataNumberKey,
|
||||
@@ -732,11 +489,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"RemoveBackendDataNumberKey //Inspire": RemoveBackendDataNumberKey,
|
||||
"ShowCachedInfo //Inspire": ShowCachedInfo,
|
||||
"CheckpointLoaderSimpleShared //Inspire": CheckpointLoaderSimpleShared,
|
||||
"LoadDiffusionModelShared //Inspire": LoadDiffusionModelShared,
|
||||
"LoadTextEncoderShared //Inspire": LoadTextEncoderShared,
|
||||
"StableCascade_CheckpointLoader //Inspire": StableCascade_CheckpointLoader,
|
||||
"IsCached //Inspire": IsCached,
|
||||
# "CacheBridge //Inspire": CacheBridge,
|
||||
"StableCascade_CheckpointLoader //Inspire": StableCascade_CheckpointLoader
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -750,9 +503,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RemoveBackendDataNumberKey //Inspire": "Remove Backend Data [NumberKey] (Inspire)",
|
||||
"ShowCachedInfo //Inspire": "Show Cached Info (Inspire)",
|
||||
"CheckpointLoaderSimpleShared //Inspire": "Shared Checkpoint Loader (Inspire)",
|
||||
"LoadDiffusionModelShared //Inspire": "Shared Diffusion Model Loader (Inspire)",
|
||||
"LoadTextEncoderShared //Inspire": "Shared Text Encoder Loader (Inspire)",
|
||||
"StableCascade_CheckpointLoader //Inspire": "Stable Cascade Checkpoint Loader (Inspire)",
|
||||
"IsCached //Inspire": "Is Cached (Inspire)",
|
||||
# "CacheBridge //Inspire": "Cache Bridge (Inspire)"
|
||||
"StableCascade_CheckpointLoader //Inspire": "Stable Cascade Checkpoint Loader (Inspire)"
|
||||
}
|
||||
|
||||
+14
-103
@@ -2,32 +2,25 @@ import torch
|
||||
import nodes
|
||||
import inspect
|
||||
from .libs import utils
|
||||
from nodes import MAX_RESOLUTION
|
||||
import logging
|
||||
|
||||
|
||||
class ConcatConditioningsWithMultiplier:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
flex_inputs = {}
|
||||
|
||||
stack = inspect.stack()
|
||||
if stack[1].function == 'get_input_info':
|
||||
if stack[1].function == 'get_input_data':
|
||||
# bypass validation
|
||||
class AllContainer:
|
||||
def __contains__(self, item):
|
||||
return True
|
||||
|
||||
def __getitem__(self, key):
|
||||
return "FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}
|
||||
|
||||
return {
|
||||
"required": {"conditioning1": ("CONDITIONING",), },
|
||||
"optional": AllContainer()
|
||||
}
|
||||
for x in range(0, 100):
|
||||
flex_inputs[f"multiplier{x}"] = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01})
|
||||
else:
|
||||
flex_inputs["multiplier1"] = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01})
|
||||
|
||||
return {
|
||||
"required": {"conditioning1": ("CONDITIONING",), },
|
||||
"optional": {"multiplier1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), },
|
||||
}
|
||||
"required": {"conditioning1": ("CONDITIONING",), },
|
||||
"optional": flex_inputs
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
FUNCTION = "doit"
|
||||
@@ -52,9 +45,9 @@ class ConcatConditioningsWithMultiplier:
|
||||
|
||||
out = []
|
||||
if len(conditioning_from) > 1:
|
||||
logging.warning(f"[Inspire Pack] ConcatConditioningsWithMultiplier {k} contains more than 1 cond, only the first one will actually be applied to conditioning1.")
|
||||
print(f"Warning: ConcatConditioningsWithMultiplier {k} contains more than 1 cond, only the first one will actually be applied to conditioning1.")
|
||||
|
||||
mkey = 'multiplier' + k[12:]
|
||||
mkey = 'multiplier'+k[12:]
|
||||
multiplier = float(kwargs[mkey])
|
||||
conditioning_from = obj.multiply_conditioning_strength(conditioning=conditioning_from, multiplier=multiplier)[0]
|
||||
cond_from = conditioning_from[0][0]
|
||||
@@ -68,98 +61,16 @@ class ConcatConditioningsWithMultiplier:
|
||||
conditioning_to = out
|
||||
|
||||
if out is None:
|
||||
return (kwargs['conditioning1'],)
|
||||
return (kwargs['conditioning1'], )
|
||||
else:
|
||||
return (out,)
|
||||
|
||||
|
||||
# CREDIT for ConditioningStretch, ConditioningUpscale: Davemane42
|
||||
# Imported to support archived custom nodes.
|
||||
# original code: https://github.com/Davemane42/ComfyUI_Dave_CustomNode/blob/main/MultiAreaConditioning.py
|
||||
class ConditioningStretch:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"conditioning": ("CONDITIONING",),
|
||||
"resolutionX": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 64}),
|
||||
"resolutionY": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 64}),
|
||||
"newWidth": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 64}),
|
||||
"newHeight": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 64}),
|
||||
# "scalar": ("INT", {"default": 2, "min": 1, "max": 100, "step": 0.5}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
CATEGORY = "InspirePack/conditioning"
|
||||
|
||||
FUNCTION = 'upscale'
|
||||
|
||||
@staticmethod
|
||||
def upscale(conditioning, resolutionX, resolutionY, newWidth, newHeight, scalar=1):
|
||||
c = []
|
||||
for t in conditioning:
|
||||
|
||||
n = [t[0], t[1].copy()]
|
||||
if 'area' in n[1]:
|
||||
newWidth *= scalar
|
||||
newHeight *= scalar
|
||||
|
||||
x = ((n[1]['area'][3] * 8) * newWidth / resolutionX) // 8
|
||||
y = ((n[1]['area'][2] * 8) * newHeight / resolutionY) // 8
|
||||
w = ((n[1]['area'][1] * 8) * newWidth / resolutionX) // 8
|
||||
h = ((n[1]['area'][0] * 8) * newHeight / resolutionY) // 8
|
||||
|
||||
n[1]['area'] = tuple(map(lambda x: (((int(x) + 7) >> 3) << 3), [h, w, y, x]))
|
||||
|
||||
c.append(n)
|
||||
|
||||
return (c,)
|
||||
|
||||
|
||||
class ConditioningUpscale:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"conditioning": ("CONDITIONING",),
|
||||
"scalar": ("INT", {"default": 2, "min": 1, "max": 100, "step": 0.5}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
CATEGORY = "InspirePack/conditioning"
|
||||
|
||||
FUNCTION = 'upscale'
|
||||
|
||||
@staticmethod
|
||||
def upscale(conditioning, scalar):
|
||||
c = []
|
||||
for t in conditioning:
|
||||
|
||||
n = [t[0], t[1].copy()]
|
||||
if 'area' in n[1]:
|
||||
n[1]['area'] = tuple(map(lambda x: ((x * scalar + 7) >> 3) << 3, n[1]['area']))
|
||||
|
||||
c.append(n)
|
||||
|
||||
return (c,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ConcatConditioningsWithMultiplier //Inspire": ConcatConditioningsWithMultiplier,
|
||||
"ConditioningUpscale //Inspire": ConditioningUpscale,
|
||||
"ConditioningStretch //Inspire": ConditioningStretch,
|
||||
}
|
||||
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ConcatConditioningsWithMultiplier //Inspire": "Concat Conditionings with Multiplier (Inspire)",
|
||||
"ConditioningUpscale //Inspire": "Conditioning Upscale (Inspire)",
|
||||
"ConditioningStretch //Inspire": "Conditioning Stretch (Inspire)",
|
||||
}
|
||||
|
||||
+10
-25
@@ -2,19 +2,11 @@ import os
|
||||
|
||||
import torch
|
||||
from PIL import ImageOps
|
||||
try:
|
||||
import pillow_jxl # noqa: F401
|
||||
jxl = True
|
||||
except ImportError:
|
||||
jxl = False
|
||||
import comfy
|
||||
import folder_paths
|
||||
import base64
|
||||
from io import BytesIO
|
||||
from .libs.utils import ByPassTypeTuple, empty_pil_tensor, empty_latent
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import logging
|
||||
from .libs.utils import *
|
||||
|
||||
|
||||
class LoadImagesFromDirBatch:
|
||||
@@ -26,7 +18,7 @@ class LoadImagesFromDirBatch:
|
||||
},
|
||||
"optional": {
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"start_index": ("INT", {"default": 0, "min": -1, "max": 0xffffffffffffffff, "step": 1}),
|
||||
"start_index": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"load_always": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}
|
||||
}
|
||||
@@ -52,8 +44,6 @@ class LoadImagesFromDirBatch:
|
||||
|
||||
# Filter files by extension
|
||||
valid_extensions = ['.jpg', '.jpeg', '.png', '.webp']
|
||||
if jxl:
|
||||
valid_extensions.extend('.jxl')
|
||||
dir_files = [f for f in dir_files if any(f.lower().endswith(ext) for ext in valid_extensions)]
|
||||
|
||||
dir_files = sorted(dir_files)
|
||||
@@ -104,10 +94,10 @@ class LoadImagesFromDirBatch:
|
||||
image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "bilinear", "center").movedim(1, -1)
|
||||
image1 = torch.cat((image1, image2), dim=0)
|
||||
|
||||
for mask2 in masks:
|
||||
for mask2 in masks[1:]:
|
||||
if has_non_empty_mask:
|
||||
if image1.shape[1:3] != mask2.shape:
|
||||
mask2 = torch.nn.functional.interpolate(mask2.unsqueeze(0).unsqueeze(0), size=(image1.shape[1], image1.shape[2]), mode='bilinear', align_corners=False)
|
||||
mask2 = torch.nn.functional.interpolate(mask2.unsqueeze(0).unsqueeze(0), size=(image1.shape[2], image1.shape[1]), mode='bilinear', align_corners=False)
|
||||
mask2 = mask2.squeeze(0)
|
||||
else:
|
||||
mask2 = mask2.unsqueeze(0)
|
||||
@@ -131,14 +121,13 @@ class LoadImagesFromDirList:
|
||||
},
|
||||
"optional": {
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"start_index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
|
||||
"start_index": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"load_always": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK", "STRING")
|
||||
RETURN_NAMES = ("IMAGE", "MASK", "FILE PATH")
|
||||
OUTPUT_IS_LIST = (True, True, True)
|
||||
RETURN_TYPES = ("IMAGE", "MASK")
|
||||
OUTPUT_IS_LIST = (True, True)
|
||||
|
||||
FUNCTION = "load_images"
|
||||
|
||||
@@ -160,8 +149,6 @@ class LoadImagesFromDirList:
|
||||
|
||||
# Filter files by extension
|
||||
valid_extensions = ['.jpg', '.jpeg', '.png', '.webp']
|
||||
if jxl:
|
||||
valid_extensions.extend('.jxl')
|
||||
dir_files = [f for f in dir_files if any(f.lower().endswith(ext) for ext in valid_extensions)]
|
||||
|
||||
dir_files = sorted(dir_files)
|
||||
@@ -172,7 +159,6 @@ class LoadImagesFromDirList:
|
||||
|
||||
images = []
|
||||
masks = []
|
||||
file_paths = []
|
||||
|
||||
limit_images = False
|
||||
if image_load_cap > 0:
|
||||
@@ -198,10 +184,9 @@ class LoadImagesFromDirList:
|
||||
|
||||
images.append(image)
|
||||
masks.append(mask)
|
||||
file_paths.append(str(image_path))
|
||||
image_count += 1
|
||||
|
||||
return (images, masks, file_paths)
|
||||
return images, masks
|
||||
|
||||
|
||||
class LoadImageInspire:
|
||||
@@ -260,7 +245,7 @@ class ChangeImageBatchSize:
|
||||
output_tensor = input_tensor[:batch_size, :, :, :]
|
||||
return output_tensor
|
||||
else:
|
||||
logging.warning(f"[Inspire Pack] ChangeImage(Latent)BatchSize: Unknown mode `{mode}` - ignored")
|
||||
print(f"[WARN] ChangeImage(Latent)BatchSize: Unknown mode `{mode}` - ignored")
|
||||
return input_tensor
|
||||
|
||||
@staticmethod
|
||||
@@ -405,7 +390,7 @@ class ColorMapToMasks:
|
||||
|
||||
def doit(self, color_map, max_count, min_pixels):
|
||||
if len(color_map) > 0:
|
||||
logging.warning("[Inspire Pack] ColorMapToMasks - Sure, here's the translation: `color_map` can only be a single image. Only the first image will be processed. If you want to utilize the remaining images, convert the Image Batch to an Image List.")
|
||||
print(f"[Inspire Pack] WARN: ColorMapToMasks - Sure, here's the translation: `color_map` can only be a single image. Only the first image will be processed. If you want to utilize the remaining images, convert the Image Batch to an Image List.")
|
||||
|
||||
top_colors = top_k_colors(color_map[0], max_count, min_pixels)
|
||||
|
||||
|
||||
+18
-54
@@ -6,8 +6,6 @@ from enum import Enum
|
||||
from . import prompt_support
|
||||
from aiohttp import web
|
||||
from . import backend_support
|
||||
from .libs import common
|
||||
import logging
|
||||
|
||||
|
||||
max_seed = 2**32 - 1
|
||||
@@ -51,7 +49,7 @@ async def set_cache_settings(request):
|
||||
try:
|
||||
backend_support.ShowCachedInfo.set_cache_settings(data)
|
||||
return web.Response(text='OK', status=200)
|
||||
except Exception as e:
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
return web.Response(text=f"{e}", status=500)
|
||||
|
||||
|
||||
@@ -264,26 +262,16 @@ def populate_wildcards(json_data):
|
||||
|
||||
if 'ImpactWildcardProcessor' in nodes.NODE_CLASS_MAPPINGS:
|
||||
if not hasattr(nodes.NODE_CLASS_MAPPINGS['ImpactWildcardProcessor'], 'process'):
|
||||
logging.warning("[Inspire Pack] Your Impact Pack is outdated. Please update to the latest version.")
|
||||
print(f"[Inspire Pack] Your Impact Pack is outdated. Please update to the latest version.")
|
||||
return
|
||||
|
||||
|
||||
wildcard_process = nodes.NODE_CLASS_MAPPINGS['ImpactWildcardProcessor'].process
|
||||
updated_widget_values = {}
|
||||
mbp_updated_widget_values = {}
|
||||
for k, v in prompt.items():
|
||||
if 'class_type' in v and v['class_type'] == 'WildcardEncode //Inspire':
|
||||
inputs = v['inputs']
|
||||
|
||||
# legacy adapter
|
||||
if isinstance(inputs['mode'], bool):
|
||||
if inputs['mode']:
|
||||
new_mode = 'populate'
|
||||
else:
|
||||
new_mode = 'fixed'
|
||||
|
||||
inputs['mode'] = new_mode
|
||||
|
||||
if inputs['mode'] == 'populate' and isinstance(inputs['populated_text'], str):
|
||||
if inputs['mode'] and isinstance(inputs['populated_text'], str):
|
||||
if isinstance(inputs['seed'], list):
|
||||
try:
|
||||
input_node = prompt[inputs['seed'][0]]
|
||||
@@ -296,7 +284,7 @@ def populate_wildcards(json_data):
|
||||
if not isinstance(input_seed, int):
|
||||
continue
|
||||
else:
|
||||
logging.warning("[Inspire Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
|
||||
print(f"[Inspire Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
|
||||
continue
|
||||
except:
|
||||
continue
|
||||
@@ -304,17 +292,14 @@ def populate_wildcards(json_data):
|
||||
input_seed = int(inputs['seed'])
|
||||
|
||||
inputs['populated_text'] = wildcard_process(text=inputs['wildcard_text'], seed=input_seed)
|
||||
inputs['mode'] = 'reproduce'
|
||||
inputs['mode'] = False
|
||||
|
||||
server.PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": k, "widget_name": "populated_text", "type": "text", "data": inputs['populated_text']})
|
||||
updated_widget_values[k] = inputs['populated_text']
|
||||
|
||||
if inputs['mode'] == 'reproduce':
|
||||
server.PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": k, "widget_name": "mode", "type": "text", "value": 'populate'})
|
||||
|
||||
elif 'class_type' in v and v['class_type'] == 'MakeBasicPipe //Inspire':
|
||||
inputs = v['inputs']
|
||||
if inputs['wildcard_mode'] == 'populate' and (isinstance(inputs['positive_populated_text'], str) or isinstance(inputs['negative_populated_text'], str)):
|
||||
if inputs['wildcard_mode'] and (isinstance(inputs['positive_populated_text'], str) or isinstance(inputs['negative_populated_text'], str)):
|
||||
if isinstance(inputs['seed'], list):
|
||||
try:
|
||||
input_node = prompt[inputs['seed'][0]]
|
||||
@@ -327,7 +312,7 @@ def populate_wildcards(json_data):
|
||||
if not isinstance(input_seed, int):
|
||||
continue
|
||||
else:
|
||||
logging.warning("[Inspire Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
|
||||
print(f"[Inspire Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
|
||||
continue
|
||||
except:
|
||||
continue
|
||||
@@ -342,24 +327,19 @@ def populate_wildcards(json_data):
|
||||
inputs['negative_populated_text'] = wildcard_process(text=inputs['negative_wildcard_text'], seed=input_seed)
|
||||
server.PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": k, "widget_name": "negative_populated_text", "type": "text", "data": inputs['negative_populated_text']})
|
||||
|
||||
inputs['wildcard_mode'] = 'reproduce'
|
||||
inputs['wildcard_mode'] = False
|
||||
mbp_updated_widget_values[k] = inputs['positive_populated_text'], inputs['negative_populated_text']
|
||||
|
||||
if inputs['wildcard_mode'] == 'reproduce':
|
||||
server.PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": k, "widget_name": "wildcard_mode", "type": "text", "value": 'populate'})
|
||||
|
||||
if 'extra_data' in json_data and 'extra_pnginfo' in json_data['extra_data']:
|
||||
extra_pnginfo = json_data['extra_data']['extra_pnginfo']
|
||||
if 'workflow' in extra_pnginfo and extra_pnginfo['workflow'] is not None and 'nodes' in extra_pnginfo['workflow']:
|
||||
for node in extra_pnginfo['workflow']['nodes']:
|
||||
key = str(node['id'])
|
||||
if key in updated_widget_values:
|
||||
node['widgets_values'][3] = updated_widget_values[key]
|
||||
node['widgets_values'][4] = 'reproduce'
|
||||
if key in mbp_updated_widget_values:
|
||||
node['widgets_values'][7] = mbp_updated_widget_values[key][0]
|
||||
node['widgets_values'][8] = mbp_updated_widget_values[key][1]
|
||||
node['widgets_values'][5] = 'reproduce'
|
||||
for node in json_data['extra_data']['extra_pnginfo']['workflow']['nodes']:
|
||||
key = str(node['id'])
|
||||
if key in updated_widget_values:
|
||||
node['widgets_values'][3] = updated_widget_values[key]
|
||||
node['widgets_values'][4] = False
|
||||
if key in mbp_updated_widget_values:
|
||||
node['widgets_values'][7] = mbp_updated_widget_values[key][0]
|
||||
node['widgets_values'][8] = mbp_updated_widget_values[key][1]
|
||||
node['widgets_values'][5] = False
|
||||
|
||||
|
||||
def force_reset_useless_params(json_data):
|
||||
@@ -372,21 +352,6 @@ def force_reset_useless_params(json_data):
|
||||
return json_data
|
||||
|
||||
|
||||
def clear_unused_node_changed_cache(json_data):
|
||||
prompt = json_data['prompt']
|
||||
|
||||
unused = []
|
||||
for x in common.changed_cache.keys():
|
||||
if x not in prompt:
|
||||
unused.append(x)
|
||||
|
||||
for x in unused:
|
||||
del common.changed_cache[x]
|
||||
del common.changed_count_cache[x]
|
||||
|
||||
return json_data
|
||||
|
||||
|
||||
def onprompt(json_data):
|
||||
prompt_support.list_counter_map = {}
|
||||
|
||||
@@ -400,7 +365,6 @@ def onprompt(json_data):
|
||||
populate_wildcards(json_data)
|
||||
|
||||
force_reset_useless_params(json_data)
|
||||
clear_unused_node_changed_cache(json_data)
|
||||
|
||||
return json_data
|
||||
|
||||
|
||||
+2
-71
@@ -1,83 +1,14 @@
|
||||
import comfy
|
||||
import nodes
|
||||
from . import utils
|
||||
import logging
|
||||
from server import PromptServer
|
||||
|
||||
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', "GITS[coeff=1.2]", 'OSS FLUX', 'OSS Wan']
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD']
|
||||
|
||||
|
||||
def impact_sampling(*args, **kwargs):
|
||||
if 'RegionalSampler' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/ltdrdata/ComfyUI-Impact-Pack',
|
||||
"'Impact Pack' extension is required.")
|
||||
raise Exception("[ERROR] You need to install 'ComfyUI-Impact-Pack'")
|
||||
raise Exception(f"[ERROR] You need to install 'ComfyUI-Impact-Pack'")
|
||||
|
||||
return nodes.NODE_CLASS_MAPPINGS['RegionalSampler'].separated_sample(*args, **kwargs)
|
||||
|
||||
|
||||
changed_count_cache = {}
|
||||
changed_cache = {}
|
||||
|
||||
|
||||
def changed_value(uid):
|
||||
v = changed_count_cache.get(uid, 0)
|
||||
changed_count_cache[uid] = v + 1
|
||||
return v + 1
|
||||
|
||||
|
||||
def not_changed_value(uid):
|
||||
return changed_count_cache.get(uid, 0)
|
||||
|
||||
|
||||
def is_changed(uid, value):
|
||||
if uid not in changed_cache or changed_cache[uid] != value:
|
||||
res = changed_value(uid)
|
||||
else:
|
||||
res = not_changed_value(uid)
|
||||
|
||||
changed_cache[uid] = value
|
||||
|
||||
logging.info(f"keys: {changed_cache.keys()}")
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def update_node_status(node, text, progress=None):
|
||||
if PromptServer.instance.client_id is None:
|
||||
return
|
||||
|
||||
PromptServer.instance.send_sync("inspire/update_status", {
|
||||
"node": node,
|
||||
"progress": progress,
|
||||
"text": text
|
||||
}, PromptServer.instance.client_id)
|
||||
|
||||
|
||||
class ListWrapper:
|
||||
def __init__(self, data, aux=None):
|
||||
if isinstance(data, ListWrapper):
|
||||
self._data = data
|
||||
if aux is None:
|
||||
self.aux = data.aux
|
||||
else:
|
||||
self.aux = aux
|
||||
else:
|
||||
self._data = list(data)
|
||||
self.aux = aux
|
||||
|
||||
def __getitem__(self, index):
|
||||
if isinstance(index, slice):
|
||||
return ListWrapper(self._data[index], self.aux)
|
||||
else:
|
||||
return self._data[index]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
self._data[index] = value
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
def __repr__(self):
|
||||
return f"ListWrapper({self._data}, aux={self.aux})"
|
||||
|
||||
+11
-105
@@ -4,12 +4,9 @@ import numpy as np
|
||||
import torch
|
||||
from PIL import Image, ImageDraw
|
||||
import math
|
||||
import cv2
|
||||
import folder_paths
|
||||
import logging
|
||||
|
||||
|
||||
def apply_variation_noise(latent_image, noise_device, variation_seed, variation_strength, mask=None, variation_method='linear'):
|
||||
def apply_variation_noise(latent_image, noise_device, variation_seed, variation_strength, mask=None):
|
||||
latent_size = latent_image.size()
|
||||
latent_size_1batch = [1, latent_size[1], latent_size[2], latent_size[3]]
|
||||
|
||||
@@ -30,50 +27,12 @@ def apply_variation_noise(latent_image, noise_device, variation_seed, variation_
|
||||
result = (1 - variation_strength) * latent_image + variation_strength * variation_noise
|
||||
else:
|
||||
# this seems precision is not enough when variation_strength is 0.0
|
||||
mixed_noise = mix_noise(latent_image, variation_noise, variation_strength, variation_method=variation_method)
|
||||
result = (mask == 1).float() * mixed_noise + (mask == 0).float() * latent_image
|
||||
result = (mask == 1).float() * ((1 - variation_strength) * latent_image + variation_strength * variation_noise * mask) + (mask == 0).float() * latent_image
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# CREDIT: https://github.com/BlenderNeko/ComfyUI_Noise/blob/afb14757216257b12268c91845eac248727a55e2/nodes.py#L68
|
||||
# https://discuss.pytorch.org/t/help-regarding-slerp-function-for-generative-model-sampling/32475/3
|
||||
def slerp(val, low, high):
|
||||
dims = low.shape
|
||||
|
||||
low = low.reshape(dims[0], -1)
|
||||
high = high.reshape(dims[0], -1)
|
||||
|
||||
low_norm = low/torch.norm(low, dim=1, keepdim=True)
|
||||
high_norm = high/torch.norm(high, dim=1, keepdim=True)
|
||||
|
||||
low_norm[low_norm != low_norm] = 0.0
|
||||
high_norm[high_norm != high_norm] = 0.0
|
||||
|
||||
omega = torch.acos((low_norm*high_norm).sum(1))
|
||||
so = torch.sin(omega)
|
||||
res = (torch.sin((1.0-val)*omega)/so).unsqueeze(1)*low + (torch.sin(val*omega)/so).unsqueeze(1) * high
|
||||
|
||||
return res.reshape(dims)
|
||||
|
||||
|
||||
def mix_noise(from_noise, to_noise, strength, variation_method):
|
||||
to_noise = to_noise.to(from_noise.device)
|
||||
|
||||
if variation_method == 'slerp':
|
||||
mixed_noise = slerp(strength, from_noise, to_noise)
|
||||
else:
|
||||
# linear
|
||||
mixed_noise = (1 - strength) * from_noise + strength * to_noise
|
||||
|
||||
# NOTE: Since the variance of the Gaussian noise in mixed_noise has changed, it must be corrected through scaling.
|
||||
scale_factor = math.sqrt((1 - strength) ** 2 + strength ** 2)
|
||||
mixed_noise /= scale_factor
|
||||
|
||||
return mixed_noise
|
||||
|
||||
|
||||
def prepare_noise(latent_image, seed, noise_inds=None, noise_device="cpu", incremental_seed_mode="comfy", variation_seed=None, variation_strength=None, variation_method="linear"):
|
||||
def prepare_noise(latent_image, seed, noise_inds=None, noise_device="cpu", incremental_seed_mode="comfy", variation_seed=None, variation_strength=None):
|
||||
"""
|
||||
creates random noise given a latent image and a seed.
|
||||
optional arg skip can be used to skip and discard x number of noise generations for a given seed
|
||||
@@ -104,10 +63,13 @@ def prepare_noise(latent_image, seed, noise_inds=None, noise_device="cpu", incre
|
||||
strength += strength_up
|
||||
|
||||
variation_noise = variation_latent.expand(input_latent.size()[0], -1, -1, -1)
|
||||
mixed_noise = (1 - strength) * input_latent + strength * variation_noise
|
||||
|
||||
mixed_noise = mix_noise(input_latent, variation_noise, strength, variation_method)
|
||||
# NOTE: Since the variance of the Gaussian noise in mixed_noise has changed, it must be corrected through scaling.
|
||||
scale_factor = math.sqrt((1 - strength) ** 2 + strength ** 2)
|
||||
corrected_noise = mixed_noise / scale_factor
|
||||
|
||||
return mixed_noise
|
||||
return corrected_noise
|
||||
|
||||
# method: incremental seed batch noise
|
||||
if noise_inds is None and incremental_seed_mode == "incremental":
|
||||
@@ -199,9 +161,9 @@ def try_install_custom_node(custom_node_url, msg):
|
||||
import cm_global
|
||||
cm_global.try_call(api='cm.try-install-custom-node',
|
||||
sender="Inspire Pack", custom_node_url=custom_node_url, msg=msg)
|
||||
except Exception as e: # noqa: F841
|
||||
logging.error(msg)
|
||||
logging.error("[Inspire Pack] ComfyUI-Manager is outdated. The custom node installation feature is not available.")
|
||||
except Exception as e:
|
||||
print(msg)
|
||||
print(f"[Inspire Pack] ComfyUI-Manager is outdated. The custom node installation feature is not available.")
|
||||
|
||||
|
||||
def empty_latent():
|
||||
@@ -293,59 +255,3 @@ class TaggedCache:
|
||||
def clear(self):
|
||||
# clear all cache
|
||||
self._data = {}
|
||||
|
||||
|
||||
def make_3d_mask(mask):
|
||||
if len(mask.shape) == 4:
|
||||
return mask.squeeze(0)
|
||||
|
||||
elif len(mask.shape) == 2:
|
||||
return mask.unsqueeze(0)
|
||||
|
||||
return mask
|
||||
|
||||
|
||||
def dilate_mask(mask: torch.Tensor, dilation_factor: float) -> torch.Tensor:
|
||||
"""Dilate a mask using a square kernel with a given dilation factor."""
|
||||
kernel_size = int(dilation_factor * 2) + 1
|
||||
kernel = np.ones((abs(kernel_size), abs(kernel_size)), np.uint8)
|
||||
|
||||
masks = make_3d_mask(mask).numpy()
|
||||
dilated_masks = []
|
||||
for m in masks:
|
||||
if dilation_factor > 0:
|
||||
m2 = cv2.dilate(m, kernel, iterations=1)
|
||||
else:
|
||||
m2 = cv2.erode(m, kernel, iterations=1)
|
||||
|
||||
dilated_masks.append(torch.from_numpy(m2))
|
||||
|
||||
return torch.stack(dilated_masks)
|
||||
|
||||
|
||||
def flatten_non_zero_override(masks: torch.Tensor):
|
||||
"""
|
||||
flatten multiple layer mask tensor to 1 layer mask tensor.
|
||||
Override the lower layer with the tensor from the upper layer, but only override non-zero values.
|
||||
|
||||
:param masks: 3d mask
|
||||
:return: flatten mask
|
||||
"""
|
||||
final_mask = masks[0]
|
||||
|
||||
for i in range(1, masks.size(0)):
|
||||
non_zero_mask = masks[i] != 0
|
||||
final_mask[non_zero_mask] = masks[i][non_zero_mask]
|
||||
|
||||
return final_mask
|
||||
|
||||
|
||||
def add_folder_path_and_extensions(folder_name, full_folder_paths, extensions):
|
||||
for full_folder_path in full_folder_paths:
|
||||
folder_paths.add_model_folder_path(folder_name, full_folder_path)
|
||||
if folder_name in folder_paths.folder_names_and_paths:
|
||||
current_paths, current_extensions = folder_paths.folder_names_and_paths[folder_name]
|
||||
updated_extensions = current_extensions | extensions
|
||||
folder_paths.folder_names_and_paths[folder_name] = (current_paths, updated_extensions)
|
||||
else:
|
||||
folder_paths.folder_names_and_paths[folder_name] = (full_folder_paths, extensions)
|
||||
|
||||
+3
-221
@@ -1,9 +1,3 @@
|
||||
import logging
|
||||
|
||||
from comfy_execution.graph_utils import GraphBuilder, is_link
|
||||
from .libs.utils import any_typ
|
||||
from .libs.common import update_node_status, ListWrapper
|
||||
|
||||
class FloatRange:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -21,17 +15,12 @@ class FloatRange:
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/List"
|
||||
CATEGORY = "InspirePack/Util"
|
||||
|
||||
def doit(self, start, stop, step, limit, ensure_end):
|
||||
if start == stop or step == 0:
|
||||
if start >= stop or step == 0:
|
||||
return ([start], )
|
||||
|
||||
reverse = False
|
||||
if start > stop:
|
||||
reverse = True
|
||||
start, stop = stop, start
|
||||
|
||||
res = []
|
||||
x = start
|
||||
last = x
|
||||
@@ -47,220 +36,13 @@ class FloatRange:
|
||||
|
||||
res.append(stop)
|
||||
|
||||
if reverse:
|
||||
res.reverse()
|
||||
|
||||
return (res, )
|
||||
|
||||
|
||||
class WorklistToItemList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"item": (any_typ, ),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
RETURN_TYPES = ("ITEM_LIST",)
|
||||
RETURN_NAMES = ("item_list",)
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
DESCRIPTION = "The list in ComfyUI allows for repeated execution of a sub-workflow.\nThis groups these repetitions (a.k.a. list) into a single ITEM_LIST output.\nITEM_LIST can then be used in ForeachList."
|
||||
|
||||
CATEGORY = "InspirePack/List"
|
||||
|
||||
def doit(self, item):
|
||||
return (item, )
|
||||
|
||||
|
||||
# Loop nodes are implemented based on BadCafeCode's reference loop implementation
|
||||
# https://github.com/BadCafeCode/execution-inversion-demo-comfyui/blob/main/flow_control.py
|
||||
|
||||
class ForeachListBegin:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"item_list": ("ITEM_LIST", {"tooltip": "ITEM_LIST containing items to be processed iteratively."}),
|
||||
},
|
||||
"optional": {
|
||||
"initial_input": (any_typ, {"tooltip": "If initial_input is omitted, the first item in item_list is used as the initial value, and the processing starts from the second item in item_list."}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FOREACH_LIST_CONTROL", "ITEM_LIST", any_typ, any_typ)
|
||||
RETURN_NAMES = ("flow_control", "remained_list", "item", "intermediate_output")
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Pass ForeachListEnd as is to indicate the end of the iteration.",
|
||||
"Output the ITEM_LIST containing the remaining items during the iteration, passing ForeachListEnd as is to indicate the end of the iteration.",
|
||||
"Output the current item during the iteration.",
|
||||
"Output the intermediate results during the iteration.")
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
DESCRIPTION = "A starting node for performing iterative tasks by retrieving items one by one from the ITEM_LIST.\nGenerate a new intermediate_output using item and intermediate_output as inputs, then connect it to ForeachListEnd.\nNOTE:If initial_input is omitted, the first item in item_list is used as the initial value, and the processing starts from the second item in item_list."
|
||||
|
||||
CATEGORY = "InspirePack/List"
|
||||
|
||||
def doit(self, item_list, initial_input=None):
|
||||
if initial_input is None:
|
||||
initial_input = item_list[0]
|
||||
item_list = item_list[1:]
|
||||
|
||||
if len(item_list) > 0:
|
||||
next_list = ListWrapper(item_list[1:])
|
||||
next_item = item_list[0]
|
||||
else:
|
||||
next_list = ListWrapper([])
|
||||
next_item = None
|
||||
|
||||
if next_list.aux is None:
|
||||
next_list.aux = len(item_list), None
|
||||
|
||||
return "stub", next_list, next_item, initial_input
|
||||
|
||||
|
||||
class ForeachListEnd:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"flow_control": ("FOREACH_LIST_CONTROL", {"rawLink": True, "tooltip": "Directly connect the output of ForeachListBegin, the starting node of the iteration."}),
|
||||
"remained_list": ("ITEM_LIST", {"tooltip":"Directly connect the output of ForeachListBegin, the starting node of the iteration."}),
|
||||
"intermediate_output": (any_typ, {"tooltip":"Connect the intermediate outputs processed within the iteration here."}),
|
||||
},
|
||||
"hidden": {
|
||||
"dynprompt": "DYNPROMPT",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_typ,)
|
||||
RETURN_NAMES = ("result",)
|
||||
OUTPUT_TOOLTIPS = ("This is the final output value.",)
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
DESCRIPTION = "A end node for performing iterative tasks by retrieving items one by one from the ITEM_LIST.\nNOTE:Directly connect the outputs of ForeachListBegin to 'flow_control' and 'remained_list'."
|
||||
|
||||
CATEGORY = "InspirePack/List"
|
||||
|
||||
def explore_dependencies(self, node_id, dynprompt, upstream):
|
||||
node_info = dynprompt.get_node(node_id)
|
||||
if "inputs" not in node_info:
|
||||
return
|
||||
for k, v in node_info["inputs"].items():
|
||||
if is_link(v):
|
||||
parent_id = v[0]
|
||||
if parent_id not in upstream:
|
||||
upstream[parent_id] = []
|
||||
self.explore_dependencies(parent_id, dynprompt, upstream)
|
||||
upstream[parent_id].append(node_id)
|
||||
|
||||
def collect_contained(self, node_id, upstream, contained):
|
||||
if node_id not in upstream:
|
||||
return
|
||||
for child_id in upstream[node_id]:
|
||||
if child_id not in contained:
|
||||
contained[child_id] = True
|
||||
self.collect_contained(child_id, upstream, contained)
|
||||
|
||||
def doit(self, flow_control, remained_list, intermediate_output, dynprompt, unique_id):
|
||||
if hasattr(remained_list, "aux"):
|
||||
if remained_list.aux[1] is None:
|
||||
remained_list.aux = (remained_list.aux[0], unique_id)
|
||||
|
||||
update_node_status(remained_list.aux[1], f"{(remained_list.aux[0]-len(remained_list))}/{remained_list.aux[0]} steps", (remained_list.aux[0]-len(remained_list))/remained_list.aux[0])
|
||||
else:
|
||||
logging.warning("[Inspire Pack] ForeachListEnd: `remained_list` did not come from ForeachList.")
|
||||
|
||||
if len(remained_list) == 0:
|
||||
return (intermediate_output,)
|
||||
|
||||
# We want to loop
|
||||
upstream = {}
|
||||
|
||||
# Get the list of all nodes between the open and close nodes
|
||||
self.explore_dependencies(unique_id, dynprompt, upstream)
|
||||
|
||||
contained = {}
|
||||
open_node = flow_control[0]
|
||||
self.collect_contained(open_node, upstream, contained)
|
||||
contained[unique_id] = True
|
||||
contained[open_node] = True
|
||||
|
||||
# We'll use the default prefix, but to avoid having node names grow exponentially in size,
|
||||
# we'll use "Recurse" for the name of the recursively-generated copy of this node.
|
||||
graph = GraphBuilder()
|
||||
for node_id in contained:
|
||||
original_node = dynprompt.get_node(node_id)
|
||||
node = graph.node(original_node["class_type"], "Recurse" if node_id == unique_id else node_id)
|
||||
node.set_override_display_id(node_id)
|
||||
|
||||
for node_id in contained:
|
||||
original_node = dynprompt.get_node(node_id)
|
||||
node = graph.lookup_node("Recurse" if node_id == unique_id else node_id)
|
||||
for k, v in original_node["inputs"].items():
|
||||
if is_link(v) and v[0] in contained:
|
||||
parent = graph.lookup_node(v[0])
|
||||
node.set_input(k, parent.out(v[1]))
|
||||
else:
|
||||
node.set_input(k, v)
|
||||
|
||||
new_open = graph.lookup_node(open_node)
|
||||
|
||||
new_open.set_input("item_list", remained_list)
|
||||
new_open.set_input("initial_input", intermediate_output)
|
||||
|
||||
my_clone = graph.lookup_node("Recurse" )
|
||||
result = (my_clone.out(0),)
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"expand": graph.finalize(),
|
||||
}
|
||||
|
||||
|
||||
class DropItems:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": { "item_list": ("ITEM_LIST", {"tooltip":"Directly connect the output of ForeachListBegin, the starting node of the iteration."}), },
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_typ,)
|
||||
RETURN_NAMES = ("ITEM_LIST",)
|
||||
OUTPUT_TOOLTIPS = ("This is the final output value.",)
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
DESCRIPTION = ""
|
||||
|
||||
CATEGORY = "InspirePack/List"
|
||||
|
||||
def doit(self, item_list):
|
||||
l = ListWrapper([])
|
||||
if hasattr(item_list, 'aux'):
|
||||
l.aux = item_list.aux
|
||||
else:
|
||||
logging.warning("[Inspire Pack] DropItems: `item_list` did not come from ForeachList.")
|
||||
|
||||
return (l,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"FloatRange //Inspire": FloatRange,
|
||||
"WorklistToItemList //Inspire": WorklistToItemList,
|
||||
"ForeachListBegin //Inspire": ForeachListBegin,
|
||||
"ForeachListEnd //Inspire": ForeachListEnd,
|
||||
"DropItems //Inspire": DropItems,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"FloatRange //Inspire": "Float Range (Inspire)",
|
||||
"WorklistToItemList //Inspire": "Worklist To Item List (Inspire)",
|
||||
"ForeachListBegin //Inspire": "▶Foreach List (Inspire)",
|
||||
"ForeachListEnd //Inspire": "Foreach List◀ (Inspire)",
|
||||
"DropItems //Inspire": "Drop Items (Inspire)",
|
||||
"FloatRange //Inspire": "Float Range (Inspire)"
|
||||
}
|
||||
|
||||
+68
-575
@@ -6,20 +6,11 @@ import torch
|
||||
import numpy as np
|
||||
import nodes
|
||||
import re
|
||||
import json
|
||||
from comfy.cli_args import args
|
||||
from safetensors.torch import safe_open
|
||||
import ast
|
||||
import logging
|
||||
|
||||
from server import PromptServer
|
||||
from .libs import utils
|
||||
|
||||
|
||||
model_path = folder_paths.models_dir
|
||||
utils.add_folder_path_and_extensions("lbw_models", [os.path.join(model_path, "lbw_models")], {'.safetensors'})
|
||||
|
||||
|
||||
def is_numeric_string(input_str):
|
||||
return re.match(r'^-?\d+(\.\d+)?$', input_str) is not None
|
||||
|
||||
@@ -43,73 +34,6 @@ def load_lbw_preset(filename):
|
||||
return []
|
||||
|
||||
|
||||
def parse_unet_num(s):
|
||||
if s[1] == '.':
|
||||
return int(s[0])
|
||||
else:
|
||||
return int(s)
|
||||
|
||||
|
||||
class MakeLBW:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
preset = ["Preset"] # 20
|
||||
preset += load_lbw_preset("lbw-preset.txt")
|
||||
preset += load_lbw_preset("lbw-preset.custom.txt")
|
||||
preset = [name for name in preset if not name.startswith('@')]
|
||||
|
||||
lora_names = folder_paths.get_filename_list("loras")
|
||||
lora_dirs = [os.path.dirname(name) for name in lora_names]
|
||||
lora_dirs = ["All"] + list(set(lora_dirs))
|
||||
|
||||
return {"required": {"model": ("MODEL",),
|
||||
"clip": ("CLIP", ),
|
||||
"category_filter": (lora_dirs,),
|
||||
"lora_name": (lora_names, ),
|
||||
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False", "tooltip": "Apply the following weights for each block:\nTrue: 1 - weight\nFalse: weight"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": ""}),
|
||||
"A": ("FLOAT", {"default": 4.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"B": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"preset": (preset,),
|
||||
"block_vector": ("STRING", {"multiline": True, "placeholder": "block weight vectors", "default": "1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1", "pysssss.autocomplete": False}),
|
||||
"bypass": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LBW_MODEL", "STRING")
|
||||
RETURN_NAMES = ("lbw_model", "populated_vector")
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Instead of directly applying the LoRA Block Weight to the MODEL, it is generated in a separate LBW_MODEL form."
|
||||
|
||||
def __init__(self):
|
||||
self.loaded_lora = None
|
||||
|
||||
def doit(self, model, clip, lora_name, inverse, seed, A, B, preset, block_vector, bypass=False, category_filter=None):
|
||||
lora_path = folder_paths.get_full_path("loras", lora_name)
|
||||
lora = None
|
||||
if self.loaded_lora is not None:
|
||||
if self.loaded_lora[0] == lora_path:
|
||||
lora = self.loaded_lora[1]
|
||||
else:
|
||||
temp = self.loaded_lora
|
||||
self.loaded_lora = None
|
||||
del temp
|
||||
|
||||
if lora is None:
|
||||
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
|
||||
self.loaded_lora = (lora_path, lora)
|
||||
|
||||
block_weights, muted_weights, populated_vector = LoraLoaderBlockWeight.load_lbw(model, clip, lora, inverse, seed, A, B, block_vector)
|
||||
lbw_model = {
|
||||
'blocks': block_weights,
|
||||
'muted': muted_weights
|
||||
}
|
||||
return lbw_model, populated_vector
|
||||
|
||||
|
||||
class LoraLoaderBlockWeight:
|
||||
def __init__(self):
|
||||
self.loaded_lora = None
|
||||
@@ -131,8 +55,8 @@ class LoraLoaderBlockWeight:
|
||||
"lora_name": (lora_names, ),
|
||||
"strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False", "tooltip": "Apply the following weights for each block:\nTrue: 1 - weight\nFalse: weight"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": ""}),
|
||||
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"A": ("FLOAT", {"default": 4.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"B": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"preset": (preset,),
|
||||
@@ -206,150 +130,11 @@ class LoraLoaderBlockWeight:
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def block_spec_parser(loaded, spec):
|
||||
if not spec.startswith("%"):
|
||||
return spec
|
||||
else:
|
||||
items = [x.strip() for x in spec[1:].split(',')]
|
||||
|
||||
input_blocks_set = set()
|
||||
middle_blocks_set= set()
|
||||
output_blocks_set = set()
|
||||
double_blocks_set = set()
|
||||
single_blocks_set = set()
|
||||
|
||||
for key, v in loaded.items():
|
||||
if isinstance(key, tuple):
|
||||
k = key[0]
|
||||
else:
|
||||
k = key
|
||||
|
||||
k_unet = k[len("diffusion_model."):]
|
||||
|
||||
if k_unet.startswith("input_blocks."):
|
||||
k_unet_num = k_unet[len("input_blocks."):len("input_blocks.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
input_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("middle_block."):
|
||||
k_unet_num = k_unet[len("middle_block."):len("middle_block.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
middle_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("output_blocks."):
|
||||
k_unet_num = k_unet[len("output_blocks."):len("output_blocks.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
output_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("double_blocks."):
|
||||
k_unet_num = k_unet[len("double_blocks."):len("double_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
double_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("single_blocks."):
|
||||
k_unet_num = k_unet[len("single_blocks."):len("single_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
single_blocks_set.add(k_unet_int)
|
||||
|
||||
pat1 = re.compile(r"(default|base)=([0-9.]+)")
|
||||
pat2 = re.compile(r"(in|out|mid|double|single)([0-9]+)-([0-9]+)=([0-9.]+)")
|
||||
pat3 = re.compile(r"(in|out|mid|double|single)([0-9]+)=([0-9.]+)")
|
||||
pat4 = re.compile(r"(in|out|mid|double|single)=([0-9.]+)")
|
||||
|
||||
base_spec = None
|
||||
default_spec = 1.0
|
||||
|
||||
for item in items:
|
||||
match = pat1.match(item)
|
||||
if match:
|
||||
if match[1] == 'base':
|
||||
base_spec = match[2]
|
||||
continue
|
||||
|
||||
if match[1] == 'default':
|
||||
default_spec = match[2]
|
||||
continue
|
||||
|
||||
if base_spec is None:
|
||||
base_spec = default_spec
|
||||
|
||||
input_blocks = [default_spec] * len(input_blocks_set)
|
||||
middle_blocks = [default_spec] * len(middle_blocks_set)
|
||||
output_blocks = [default_spec] * len(output_blocks_set)
|
||||
double_blocks = [default_spec] * len(double_blocks_set)
|
||||
single_blocks = [default_spec] * len(single_blocks_set)
|
||||
|
||||
for item in items:
|
||||
match = pat2.match(item)
|
||||
if match:
|
||||
for x in range(int(match[2])-1, int(match[3])):
|
||||
value = float(match[4])
|
||||
|
||||
if x < 0:
|
||||
continue
|
||||
|
||||
if match[1] == 'in' and len(input_blocks) > x:
|
||||
input_blocks[x] = value
|
||||
elif match[1] == 'out' and len(output_blocks) > x:
|
||||
output_blocks[x] = value
|
||||
elif match[1] == 'mid' and len(middle_blocks) > x:
|
||||
middle_blocks[x] = value
|
||||
elif match[1] == 'double' and len(double_blocks) > x:
|
||||
double_blocks[x] = value
|
||||
elif match[1] == 'single' and len(single_blocks) > x:
|
||||
single_blocks[x] = value
|
||||
|
||||
continue
|
||||
|
||||
match = pat3.match(item)
|
||||
if match:
|
||||
value = float(match[3])
|
||||
x = int(match[2]) - 1
|
||||
|
||||
if x < 0:
|
||||
continue
|
||||
|
||||
if match[1] == 'in' and len(input_blocks) > x:
|
||||
input_blocks[x] = value
|
||||
elif match[1] == 'out' and len(output_blocks) > x:
|
||||
output_blocks[x] = value
|
||||
elif match[1] == 'mid' and len(middle_blocks) > x:
|
||||
middle_blocks[x] = value
|
||||
elif match[1] == 'double' and len(double_blocks) > x:
|
||||
double_blocks[x] = value
|
||||
elif match[1] == 'single' and len(single_blocks) > x:
|
||||
single_blocks[x] = value
|
||||
|
||||
continue
|
||||
|
||||
match = pat4.match(item)
|
||||
if match:
|
||||
value = float(match[2])
|
||||
|
||||
if match[1] == 'in':
|
||||
input_blocks = [value] * len(input_blocks)
|
||||
elif match[1] == 'out':
|
||||
output_blocks = [value] * len(output_blocks)
|
||||
elif match[1] == 'mid':
|
||||
middle_blocks = [value] * len(middle_blocks)
|
||||
elif match[1] == 'double':
|
||||
double_blocks = [value] * len(double_blocks)
|
||||
elif match[1] == 'single':
|
||||
single_blocks = [value] * len(single_blocks)
|
||||
|
||||
continue
|
||||
|
||||
# concat specs
|
||||
res = [str(base_spec)]
|
||||
for x in (input_blocks + middle_blocks + output_blocks + double_blocks + single_blocks):
|
||||
res.append(str(x))
|
||||
|
||||
return ",".join(res)
|
||||
|
||||
@staticmethod
|
||||
def load_lbw(model, clip, lora, inverse, seed, A, B, block_vector):
|
||||
def load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector):
|
||||
key_map = comfy.lora.model_lora_keys_unet(model.model)
|
||||
key_map = comfy.lora.model_lora_keys_clip(clip.cond_stage_model, key_map)
|
||||
loaded = comfy.lora.load_lora(lora, key_map)
|
||||
|
||||
block_vector = LoraLoaderBlockWeight.block_spec_parser(loaded, block_vector)
|
||||
|
||||
block_vector = block_vector.split(":")
|
||||
if len(block_vector) > 1:
|
||||
block_vector = block_vector[1]
|
||||
@@ -357,6 +142,7 @@ class LoraLoaderBlockWeight:
|
||||
block_vector = block_vector[0]
|
||||
|
||||
vector = block_vector.split(",")
|
||||
vector_i = 1
|
||||
|
||||
if not LoraLoaderBlockWeight.validate(vector):
|
||||
preset_dict = load_preset_dict()
|
||||
@@ -365,19 +151,22 @@ class LoraLoaderBlockWeight:
|
||||
else:
|
||||
raise ValueError(f"[LoraLoaderBlockWeight] invalid block_vector '{block_vector}'")
|
||||
|
||||
last_k_unet_num = None
|
||||
new_modelpatcher = model.clone()
|
||||
populated_ratio = strength_model
|
||||
|
||||
def parse_unet_num(s):
|
||||
if s[1] == '.':
|
||||
return int(s[0])
|
||||
else:
|
||||
return int(s)
|
||||
|
||||
# sort: input, middle, output, others
|
||||
input_blocks = []
|
||||
middle_blocks = []
|
||||
output_blocks = []
|
||||
double_blocks = []
|
||||
single_blocks = []
|
||||
others = []
|
||||
for key, v in loaded.items():
|
||||
if isinstance(key, tuple):
|
||||
k = key[0]
|
||||
else:
|
||||
k = key
|
||||
|
||||
for k, v in loaded.items():
|
||||
k_unet = k[len("diffusion_model."):]
|
||||
|
||||
if k_unet.startswith("input_blocks."):
|
||||
@@ -389,34 +178,18 @@ class LoraLoaderBlockWeight:
|
||||
elif k_unet.startswith("output_blocks."):
|
||||
k_unet_num = k_unet[len("output_blocks."):len("output_blocks.")+2]
|
||||
output_blocks.append((k, v, parse_unet_num(k_unet_num), k_unet))
|
||||
elif k_unet.startswith("double_blocks."):
|
||||
k_unet_num = k_unet[len("double_blocks."):len("double_blocks.")+2]
|
||||
double_blocks.append((key, v, parse_unet_num(k_unet_num), k_unet))
|
||||
elif k_unet.startswith("single_blocks."):
|
||||
k_unet_num = k_unet[len("single_blocks."):len("single_blocks.")+2]
|
||||
single_blocks.append((key, v, parse_unet_num(k_unet_num), k_unet))
|
||||
else:
|
||||
others.append((k, v, k_unet))
|
||||
|
||||
input_blocks = sorted(input_blocks, key=lambda x: x[2])
|
||||
middle_blocks = sorted(middle_blocks, key=lambda x: x[2])
|
||||
output_blocks = sorted(output_blocks, key=lambda x: x[2])
|
||||
double_blocks = sorted(double_blocks, key=lambda x: x[2])
|
||||
single_blocks = sorted(single_blocks, key=lambda x: x[2])
|
||||
|
||||
# prepare patch
|
||||
np.random.seed(seed % (2**31))
|
||||
populated_vector_list = []
|
||||
ratios = []
|
||||
ratio = 1.0
|
||||
vector_i = 1
|
||||
|
||||
last_k_unet_num = None
|
||||
|
||||
block_weights = {}
|
||||
muted_weights = []
|
||||
|
||||
for k, v, k_unet_num, k_unet in (input_blocks + middle_blocks + output_blocks + double_blocks + single_blocks):
|
||||
for k, v, k_unet_num, k_unet in (input_blocks + middle_blocks + output_blocks):
|
||||
if last_k_unet_num != k_unet_num and len(vector) > vector_i:
|
||||
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[vector_i].strip())
|
||||
ratio = ratios.pop(0)
|
||||
@@ -432,8 +205,6 @@ class LoraLoaderBlockWeight:
|
||||
else:
|
||||
if len(ratios) > 0:
|
||||
ratio = ratios.pop(0)
|
||||
else:
|
||||
pass # use last used ratio if no more user specified ratio is given
|
||||
|
||||
if inverse:
|
||||
populated_ratio = 1 - ratio
|
||||
@@ -442,10 +213,11 @@ class LoraLoaderBlockWeight:
|
||||
|
||||
last_k_unet_num = k_unet_num
|
||||
|
||||
if populated_ratio != 0:
|
||||
block_weights[k] = v, populated_ratio
|
||||
else:
|
||||
muted_weights.append(k)
|
||||
new_modelpatcher.add_patches({k: v}, strength_model * populated_ratio)
|
||||
# if inverse:
|
||||
# print(f"\t{k_unet} -> inv({ratio}) ")
|
||||
# else:
|
||||
# print(f"\t{k_unet} -> ({ratio}) ")
|
||||
|
||||
# prepare base patch
|
||||
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[0].strip())
|
||||
@@ -454,43 +226,25 @@ class LoraLoaderBlockWeight:
|
||||
if inverse:
|
||||
populated_ratio = 1 - ratio
|
||||
else:
|
||||
populated_ratio = ratio
|
||||
populated_ratio = 1
|
||||
|
||||
populated_vector_list.insert(0, LoraLoaderBlockWeight.norm_value(populated_ratio))
|
||||
|
||||
for k, v, k_unet in others:
|
||||
if populated_ratio != 0:
|
||||
block_weights[k] = v, populated_ratio
|
||||
else:
|
||||
muted_weights.append(k)
|
||||
new_modelpatcher.add_patches({k: v}, strength_model * populated_ratio)
|
||||
# if inverse:
|
||||
# print(f"\t{k_unet} -> inv({ratio}) ")
|
||||
# else:
|
||||
# print(f"\t{k_unet} -> ({ratio}) ")
|
||||
|
||||
populated_vector = ','.join(map(str, populated_vector_list))
|
||||
return block_weights, muted_weights, populated_vector
|
||||
|
||||
@staticmethod
|
||||
def load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector):
|
||||
block_weights, muted_weights, populated_vector = LoraLoaderBlockWeight.load_lbw(model, clip, lora, inverse, seed, A, B, block_vector)
|
||||
|
||||
new_modelpatcher = model.clone()
|
||||
new_clip = clip.clone()
|
||||
|
||||
muted_weights = set(muted_weights)
|
||||
|
||||
for k, v in block_weights.items():
|
||||
weights, ratio = v
|
||||
|
||||
if k in muted_weights:
|
||||
pass
|
||||
elif 'text' in k or 'encoder' in k:
|
||||
new_clip.add_patches({k: weights}, strength_clip * ratio)
|
||||
else:
|
||||
new_modelpatcher.add_patches({k: weights}, strength_model * ratio)
|
||||
|
||||
return new_modelpatcher, new_clip, populated_vector
|
||||
new_clip.add_patches(loaded, strength_clip)
|
||||
populated_vector = ','.join(map(str, populated_vector_list))
|
||||
return (new_modelpatcher, new_clip, populated_vector)
|
||||
|
||||
def doit(self, model, clip, lora_name, strength_model, strength_clip, inverse, seed, A, B, preset, block_vector, bypass=False, category_filter=None):
|
||||
if strength_model == 0 and strength_clip == 0 or bypass:
|
||||
return model, clip, ""
|
||||
return (model, clip, "")
|
||||
|
||||
lora_path = folder_paths.get_full_path("loras", lora_name)
|
||||
lora = None
|
||||
@@ -507,48 +261,7 @@ class LoraLoaderBlockWeight:
|
||||
self.loaded_lora = (lora_path, lora)
|
||||
|
||||
model_lora, clip_lora, populated_vector = LoraLoaderBlockWeight.load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector)
|
||||
return model_lora, clip_lora, populated_vector
|
||||
|
||||
|
||||
class ApplyLBW:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"model": ("MODEL", ),
|
||||
"clip": ("CLIP", ),
|
||||
"strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"lbw_model": ("LBW_MODEL",),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP")
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Apply LBW_MODEL to MODEL and CLIP"
|
||||
|
||||
@staticmethod
|
||||
def doit(model, clip, strength_model, strength_clip, lbw_model):
|
||||
block_weights = lbw_model['blocks']
|
||||
muted_weights = lbw_model['muted']
|
||||
|
||||
new_modelpatcher = model.clone()
|
||||
new_clip = clip.clone()
|
||||
|
||||
muted_weights = set(muted_weights)
|
||||
|
||||
for k, v in block_weights.items():
|
||||
weights, ratio = v
|
||||
|
||||
if k in muted_weights:
|
||||
pass
|
||||
elif 'text' in k or 'encoder' in k:
|
||||
new_clip.add_patches({k: weights}, strength_clip * ratio)
|
||||
else:
|
||||
new_modelpatcher.add_patches({k: weights}, strength_model * ratio)
|
||||
|
||||
return new_modelpatcher, new_clip
|
||||
return (model_lora, clip_lora, populated_vector)
|
||||
|
||||
|
||||
class XY_Capsule_LoraBlockWeight:
|
||||
@@ -570,10 +283,10 @@ class XY_Capsule_LoraBlockWeight:
|
||||
|
||||
def set_result(self, image, latent):
|
||||
if self.another_capsule is not None:
|
||||
logging.info(f"XY_Capsule_LoraBlockWeight: ({self.another_capsule.x, self.y}) is processed.")
|
||||
print(f"XY_Capsule_LoraBlockWeight: ({self.another_capsule.x, self.y}) is processed.")
|
||||
self.storage[(self.another_capsule.x, self.y)] = image
|
||||
else:
|
||||
logging.info(f"XY_Capsule_LoraBlockWeight: ({self.x, self.y}) is processed.")
|
||||
print(f"XY_Capsule_LoraBlockWeight: ({self.x, self.y}) is processed.")
|
||||
|
||||
def patch_model(self, model, clip):
|
||||
lora_name, strength_model, strength_clip, inverse, block_vectors, seed, A, B, heatmap_palette, heatmap_alpha, heatmap_strength, xyplot_mode = self.params
|
||||
@@ -615,7 +328,6 @@ class XY_Capsule_LoraBlockWeight:
|
||||
else:
|
||||
image = torch.abs(weighted_image - reference_image)
|
||||
self.storage[(self.another_capsule.x, self.y)] = image
|
||||
|
||||
elif self.y == 3:
|
||||
import matplotlib.cm as cm
|
||||
# heatmap
|
||||
@@ -624,7 +336,7 @@ class XY_Capsule_LoraBlockWeight:
|
||||
if image == "fail":
|
||||
image = utils.empty_pil_tensor(8,8)
|
||||
latent = utils.empty_latent()
|
||||
return image, latent
|
||||
return (image, latent)
|
||||
else:
|
||||
image = image.clone()
|
||||
|
||||
@@ -656,7 +368,7 @@ class XY_Capsule_LoraBlockWeight:
|
||||
image = heatmap_alpha * heatmap + (1 - heatmap_alpha) * image
|
||||
|
||||
latent = nodes.VAEEncode().encode(vae, image)[0]
|
||||
return image, latent
|
||||
return (image, latent)
|
||||
|
||||
def getLabel(self):
|
||||
return self.label
|
||||
@@ -773,7 +485,7 @@ class XYInput_LoraBlockWeight:
|
||||
XY_Capsule_LoraBlockWeight(0, 2, '', 'diff', storage, common_params),
|
||||
XY_Capsule_LoraBlockWeight(0, 3, '', 'heatmap', storage, common_params)]
|
||||
|
||||
return (xy_type, x_values), (xy_type, y_values),
|
||||
return ((xy_type, x_values), (xy_type, y_values), )
|
||||
|
||||
|
||||
class LoraBlockInfo:
|
||||
@@ -819,29 +531,12 @@ class LoraBlockInfo:
|
||||
output_blocks = []
|
||||
output_blocks_map = {}
|
||||
|
||||
text_block_count1 = set()
|
||||
text_blocks1 = []
|
||||
text_blocks_map1 = {}
|
||||
|
||||
text_block_count2 = set()
|
||||
text_blocks2 = []
|
||||
text_blocks_map2 = {}
|
||||
|
||||
double_block_count = set()
|
||||
double_blocks = []
|
||||
double_blocks_map = {}
|
||||
|
||||
single_block_count = set()
|
||||
single_blocks = []
|
||||
single_blocks_map = {}
|
||||
text_block_count = set()
|
||||
text_blocks = []
|
||||
text_blocks_map = {}
|
||||
|
||||
others = []
|
||||
for key, v in loaded.items():
|
||||
if isinstance(key, tuple):
|
||||
k = key[0]
|
||||
else:
|
||||
k = key
|
||||
|
||||
for k, v in loaded.items():
|
||||
k_unet = k[len("diffusion_model."):]
|
||||
|
||||
if k_unet.startswith("input_blocks."):
|
||||
@@ -877,49 +572,16 @@ class LoraBlockInfo:
|
||||
else:
|
||||
output_blocks_map[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("double_blocks."):
|
||||
k_unet_num = k_unet[len("double_blocks."):len("double_blocks.") + 2]
|
||||
elif k_unet.startswith("_model.encoder.layers."):
|
||||
k_unet_num = k_unet[len("_model.encoder.layers."):len("_model.encoder.layers.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
double_block_count.add(k_unet_int)
|
||||
double_blocks.append(k_unet)
|
||||
if k_unet_int in double_blocks_map:
|
||||
double_blocks_map[k_unet_int].append(k_unet)
|
||||
text_block_count.add(k_unet_int)
|
||||
text_blocks.append(k_unet)
|
||||
if k_unet_int in text_blocks_map:
|
||||
text_blocks_map[k_unet_int].append(k_unet)
|
||||
else:
|
||||
double_blocks_map[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("single_blocks."):
|
||||
k_unet_num = k_unet[len("single_blocks."):len("single_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
single_block_count.add(k_unet_int)
|
||||
single_blocks.append(k_unet)
|
||||
if k_unet_int in single_blocks_map:
|
||||
single_blocks_map[k_unet_int].append(k_unet)
|
||||
else:
|
||||
single_blocks_map[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("er.text_model.encoder.layers."):
|
||||
k_unet_num = k_unet[len("er.text_model.encoder.layers."):len("er.text_model.encoder.layers.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
text_block_count1.add(k_unet_int)
|
||||
text_blocks1.append(k_unet)
|
||||
if k_unet_int in text_blocks_map1:
|
||||
text_blocks_map1[k_unet_int].append(k_unet)
|
||||
else:
|
||||
text_blocks_map1[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("r.encoder.block."):
|
||||
k_unet_num = k_unet[len("r.encoder.block."):len("r.encoder.block.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
text_block_count2.add(k_unet_int)
|
||||
text_blocks2.append(k_unet)
|
||||
if k_unet_int in text_blocks_map2:
|
||||
text_blocks_map2[k_unet_int].append(k_unet)
|
||||
else:
|
||||
text_blocks_map2[k_unet_int] = [k_unet]
|
||||
text_blocks_map[k_unet_int] = [k_unet]
|
||||
|
||||
else:
|
||||
others.append(k_unet)
|
||||
@@ -929,49 +591,29 @@ class LoraBlockInfo:
|
||||
input_blocks = sorted(input_blocks)
|
||||
middle_blocks = sorted(middle_blocks)
|
||||
output_blocks = sorted(output_blocks)
|
||||
double_blocks = sorted(double_blocks)
|
||||
single_blocks = sorted(single_blocks)
|
||||
others = sorted(others)
|
||||
|
||||
if len(input_block_count) > 0:
|
||||
text += f"\n-------[Input blocks] ({len(input_block_count)}, Subs={len(input_blocks)})-------\n"
|
||||
input_keys = sorted(input_blocks_map.keys())
|
||||
for x in input_keys:
|
||||
text += f" IN{x}: {len(input_blocks_map[x])}\n"
|
||||
text += f"\n-------[Input blocks] ({len(input_block_count)}, Subs={len(input_blocks)})-------\n"
|
||||
input_keys = sorted(input_blocks_map.keys())
|
||||
for x in input_keys:
|
||||
text += f" IN{x}: {len(input_blocks_map[x])}\n"
|
||||
|
||||
if len(middle_block_count) > 0:
|
||||
text += f"\n-------[Middle blocks] ({len(middle_block_count)}, Subs={len(middle_blocks)})-------\n"
|
||||
middle_keys = sorted(middle_blocks_map.keys())
|
||||
for x in middle_keys:
|
||||
text += f" MID{x}: {len(middle_blocks_map[x])}\n"
|
||||
text += f"\n-------[Middle blocks] ({len(middle_block_count)}, Subs={len(middle_blocks)})-------\n"
|
||||
middle_keys = sorted(middle_blocks_map.keys())
|
||||
for x in middle_keys:
|
||||
text += f" MID{x}: {len(middle_blocks_map[x])}\n"
|
||||
|
||||
if len(output_block_count) > 0:
|
||||
text += f"\n-------[Output blocks] ({len(output_block_count)}, Subs={len(output_blocks)})-------\n"
|
||||
output_keys = sorted(output_blocks_map.keys())
|
||||
for x in output_keys:
|
||||
text += f" OUT{x}: {len(output_blocks_map[x])}\n"
|
||||
text += f"\n-------[Output blocks] ({len(output_block_count)}, Subs={len(output_blocks)})-------\n"
|
||||
output_keys = sorted(output_blocks_map.keys())
|
||||
for x in output_keys:
|
||||
text += f" OUT{x}: {len(output_blocks_map[x])}\n"
|
||||
|
||||
if len(double_block_count) > 0:
|
||||
text += f"\n-------[Double blocks(MMDiT)] ({len(double_block_count)}, Subs={len(double_blocks)})-------\n"
|
||||
double_keys = sorted(double_blocks_map.keys())
|
||||
for x in double_keys:
|
||||
text += f" DOUBLE{x}: {len(double_blocks_map[x])}\n"
|
||||
|
||||
if len(single_block_count) > 0:
|
||||
text += f"\n-------[Single blocks(DiT)] ({len(single_block_count)}, Subs={len(single_blocks)})-------\n"
|
||||
single_keys = sorted(single_blocks_map.keys())
|
||||
for x in single_keys:
|
||||
text += f" SINGLE{x}: {len(single_blocks_map[x])}\n"
|
||||
|
||||
text += f"\n-------[Base blocks] ({len(text_block_count1) + len(text_block_count2) + len(others)}, Subs={len(text_blocks1) + len(text_blocks2) + len(others)})-------\n"
|
||||
text_keys1 = sorted(text_blocks_map1.keys())
|
||||
for x in text_keys1:
|
||||
text += f" TXT_ENC{x}: {len(text_blocks_map1[x])}\n"
|
||||
|
||||
text_keys2 = sorted(text_blocks_map2.keys())
|
||||
for x in text_keys2:
|
||||
text += f" TXT_ENC{x} [B]: {len(text_blocks_map2[x])}\n"
|
||||
text += f"\n-------[Text blocks] ({len(text_block_count)}, Subs={len(text_blocks)})-------\n"
|
||||
text_keys = sorted(text_blocks_map.keys())
|
||||
for x in text_keys:
|
||||
text += f" CLIP{x}: {len(text_blocks_map[x])}\n"
|
||||
|
||||
text += f"\n-------[Base blocks] ({len(others)})-------\n"
|
||||
for x in others:
|
||||
text += f" {x}\n"
|
||||
|
||||
@@ -987,162 +629,13 @@ class LoraBlockInfo:
|
||||
return {}
|
||||
|
||||
|
||||
class LoadLBW:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
files = folder_paths.get_filename_list('lbw_models')
|
||||
return {"required": {
|
||||
"lbw_model": [sorted(files), ]},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LBW_MODEL",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Load LBW_MODEL from .lbw.safetensors file"
|
||||
|
||||
@staticmethod
|
||||
def decode_dict(encoded_dict, tensor_dict):
|
||||
original_dict = {}
|
||||
|
||||
def decode_value(value):
|
||||
if isinstance(value, str) and value.startswith('t') and value[1:].isdigit():
|
||||
return tensor_dict[value]
|
||||
return value
|
||||
|
||||
for k, tuple_value in encoded_dict.items():
|
||||
decoded_tuple = tuple(decode_value(v) for v in tuple_value[0][1])
|
||||
key = ast.literal_eval(k) if isinstance(k, str) and (k.startswith('(') or k.startswith('[')) else k
|
||||
original_dict[key] = ((tuple_value[0][0], decoded_tuple), tuple_value[1])
|
||||
|
||||
return original_dict
|
||||
|
||||
@staticmethod
|
||||
def load(file):
|
||||
tensor_dict = comfy.utils.load_torch_file(file)
|
||||
|
||||
with safe_open(file, framework="pt") as f:
|
||||
metadata = f.metadata()
|
||||
|
||||
encoded_dict = json.loads(metadata.get('blocks', '{}'))
|
||||
muted_blocks = ast.literal_eval(metadata.get('muted_blocks', '[]'))
|
||||
|
||||
decoded_dict = LoadLBW.decode_dict(encoded_dict, tensor_dict)
|
||||
|
||||
lbw_model = {
|
||||
'blocks': decoded_dict,
|
||||
'muted': muted_blocks
|
||||
}
|
||||
|
||||
return lbw_model, metadata
|
||||
|
||||
def doit(self, lbw_model):
|
||||
lbw_path = folder_paths.get_full_path("lbw_models", lbw_model)
|
||||
lbw_model, _ = LoadLBW.load(lbw_path)
|
||||
return (lbw_model,)
|
||||
|
||||
|
||||
class SaveLBW:
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_folder_paths('lbw_models')[-1]
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "lbw_model": ("LBW_MODEL", ),
|
||||
"filename_prefix": ("STRING", {"default": "ComfyUI"}) },
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "doit"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Save LBW_MODEL as a .lbw.safetensors file"
|
||||
|
||||
@staticmethod
|
||||
def encode_dict(original_dict):
|
||||
tensor_dict = {}
|
||||
encoded_dict = {}
|
||||
counter = 0
|
||||
|
||||
def generate_unique_id():
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
return f"t{counter}"
|
||||
|
||||
def encode_value(value):
|
||||
if isinstance(value, torch.Tensor):
|
||||
unique_id = generate_unique_id()
|
||||
tensor_dict[unique_id] = value
|
||||
return unique_id
|
||||
return value
|
||||
|
||||
for k, tuple_value in original_dict.items():
|
||||
encoded_tuple = tuple(encode_value(v) for v in tuple_value[0][1])
|
||||
encoded_dict[str(k)] = (tuple_value[0][0], encoded_tuple), tuple_value[1]
|
||||
|
||||
return encoded_dict, tensor_dict
|
||||
|
||||
@staticmethod
|
||||
def save(lbw_model, file, metadata):
|
||||
metadata['format'] = 'Inspire LBW 1.0'
|
||||
weighted_blocks = lbw_model['blocks']
|
||||
metadata['muted_blocks'] = str(lbw_model['muted'])
|
||||
encoded_dict, tensor_dict = SaveLBW.encode_dict(weighted_blocks)
|
||||
metadata['blocks'] = json.dumps(encoded_dict)
|
||||
|
||||
comfy.utils.save_torch_file(tensor_dict, file, metadata=metadata)
|
||||
|
||||
def doit(self, lbw_model, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
|
||||
|
||||
# support save metadata for lbw sharing
|
||||
prompt_info = ""
|
||||
if prompt is not None:
|
||||
prompt_info = json.dumps(prompt)
|
||||
|
||||
metadata = {}
|
||||
if not args.disable_metadata:
|
||||
metadata = {"prompt": prompt_info}
|
||||
if extra_pnginfo is not None:
|
||||
for x in extra_pnginfo:
|
||||
metadata[x] = json.dumps(extra_pnginfo[x])
|
||||
|
||||
file = f"{filename}_{counter:05}_.lbw.safetensors"
|
||||
|
||||
results = list()
|
||||
results.append({
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": "output"
|
||||
})
|
||||
|
||||
file = os.path.join(full_output_folder, file)
|
||||
|
||||
SaveLBW.save(lbw_model, file, metadata)
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"XY Input: Lora Block Weight //Inspire": XYInput_LoraBlockWeight,
|
||||
"LoraLoaderBlockWeight //Inspire": LoraLoaderBlockWeight,
|
||||
"LoraBlockInfo //Inspire": LoraBlockInfo,
|
||||
"MakeLBW //Inspire": MakeLBW,
|
||||
"ApplyLBW //Inspire": ApplyLBW,
|
||||
"SaveLBW //Inspire": SaveLBW,
|
||||
"LoadLBW //Inspire": LoadLBW,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"XY Input: Lora Block Weight //Inspire": "XY Input: LoRA Block Weight",
|
||||
"LoraLoaderBlockWeight //Inspire": "LoRA Loader (Block Weight)",
|
||||
"LoraBlockInfo //Inspire": "LoRA Block Info",
|
||||
"MakeLBW //Inspire": "Make LoRA Block Weight",
|
||||
"ApplyLBW //Inspire": "Apply LoRA Block Weight",
|
||||
"SaveLBW //Inspire": "Save LoRA Block Weight",
|
||||
"LoadLBW //Inspire": "Load LoRA Block Weight",
|
||||
"XY Input: Lora Block Weight //Inspire": "XY Input: Lora Block Weight",
|
||||
"LoraLoaderBlockWeight //Inspire": "Lora Loader (Block Weight)",
|
||||
"LoraBlockInfo //Inspire": "Lora Block Info",
|
||||
}
|
||||
|
||||
+9
-20
@@ -5,8 +5,6 @@ import server
|
||||
from .libs import utils
|
||||
from . import backend_support
|
||||
from comfy import sdxl_clip
|
||||
import logging
|
||||
|
||||
|
||||
model_preset = {
|
||||
# base
|
||||
@@ -21,7 +19,6 @@ model_preset = {
|
||||
"SDXL ViT-H": ("ip-adapter_sdxl_vit-h", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
"SDXL Plus ViT-H": ("ip-adapter-plus_sdxl_vit-h", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
"SDXL Plus Face ViT-H": ("ip-adapter-plus-face_sdxl_vit-h", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
"Kolors Plus": ("Kolors-IP-Adapter-Plus", "clip-vit-large-patch14-336", None, False),
|
||||
|
||||
# faceid
|
||||
"SD1.5 FaceID": ("ip-adapter-faceid_sd15", "CLIP-ViT-H-14-laion2B-s32B-b79K", "ip-adapter-faceid_sd15_lora", True),
|
||||
@@ -30,10 +27,8 @@ model_preset = {
|
||||
"SD1.5 FaceID Portrait v11": ("ip-adapter-faceid-portrait-v11_sd15", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, True),
|
||||
"SD1.5 FaceID Portrait": ("ip-adapter-faceid-portrait_sd15", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, True),
|
||||
"SDXL FaceID": ("ip-adapter-faceid_sdxl", "CLIP-ViT-H-14-laion2B-s32B-b79K", "ip-adapter-faceid_sdxl_lora", True),
|
||||
"SDXL FaceID Plus v2": ("ip-adapter-faceid-plusv2_sdxl", "CLIP-ViT-H-14-laion2B-s32B-b79K", "ip-adapter-faceid-plusv2_sdxl_lora", True),
|
||||
"SDXL FaceID Portrait": ("ip-adapter-faceid-portrait_sdxl", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, True),
|
||||
"SDXL FaceID Portrait unnorm": ("ip-adapter-faceid-portrait_sdxl_unnorm", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, True),
|
||||
"Kolors FaceID Plus": ("Kolors-IP-Adapter-FaceID-Plus", "clip-vit-large-patch14-336", None, True),
|
||||
|
||||
# composition
|
||||
"SD1.5 Plus Composition": ("ip-adapter_sd15", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
@@ -51,7 +46,7 @@ def lookup_model(model_dir, name):
|
||||
if len(resolved_name) > 0:
|
||||
return resolved_name[0], "OK"
|
||||
else:
|
||||
logging.error(f"[Inspire Pack] IPAdapterModelHelper: The `{name}` model file does not exist in `{model_dir}` model dir.")
|
||||
print(f"[ERROR] IPAdapterModelHelper: The `{name}` model file does not exist in `{model_dir}` model dir.")
|
||||
return None, "FAIL"
|
||||
|
||||
|
||||
@@ -61,16 +56,13 @@ class IPAdapterModelHelper:
|
||||
return {
|
||||
"required": {
|
||||
"model": ("MODEL",),
|
||||
"clip": ("CLIP",),
|
||||
"preset": (list(model_preset.keys()),),
|
||||
"lora_strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||
"lora_strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||
"insightface_provider": (["CPU", "CUDA", "ROCM"], ),
|
||||
"cache_mode": (["insightface only", "clip_vision only", "all", "none"], {"default": "insightface only"}),
|
||||
},
|
||||
"optional": {
|
||||
"clip": ("CLIP",),
|
||||
"insightface_model_name": (['buffalo_l', 'antelopev2'],),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"}
|
||||
}
|
||||
|
||||
@@ -80,17 +72,14 @@ class IPAdapterModelHelper:
|
||||
|
||||
CATEGORY = "InspirePack/models"
|
||||
|
||||
def doit(self, model, preset, lora_strength_model, lora_strength_clip, insightface_provider, clip=None, cache_mode="none", unique_id=None, insightface_model_name='buffalo_l'):
|
||||
def doit(self, model, clip, preset, lora_strength_model, lora_strength_clip, insightface_provider, cache_mode="none", unique_id=None):
|
||||
if 'IPAdapter' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/cubiq/ComfyUI_IPAdapter_plus',
|
||||
"To use 'IPAdapterModelHelper' node, 'ComfyUI IPAdapter Plus' extension is required.")
|
||||
raise Exception("[ERROR] To use IPAdapterModelHelper, you need to install 'ComfyUI IPAdapter Plus'")
|
||||
raise Exception(f"[ERROR] To use IPAdapterModelHelper, you need to install 'ComfyUI IPAdapter Plus'")
|
||||
|
||||
is_sdxl_preset = 'SDXL' in preset
|
||||
if clip is not None:
|
||||
is_sdxl_model = isinstance(clip.tokenizer, sdxl_clip.SDXLTokenizer)
|
||||
else:
|
||||
is_sdxl_model = False
|
||||
is_sdxl_model = isinstance(clip.tokenizer, sdxl_clip.SDXLTokenizer)
|
||||
|
||||
if is_sdxl_preset != is_sdxl_model:
|
||||
server.PromptServer.instance.send_sync("inspire-node-output-label", {"node_id": unique_id, "output_idx": 1, "label": "IPADAPTER (fail)"})
|
||||
@@ -98,7 +87,7 @@ class IPAdapterModelHelper:
|
||||
server.PromptServer.instance.send_sync("inspire-node-output-label", {"node_id": unique_id, "output_idx": 3, "label": "INSIGHTFACE (fail)"})
|
||||
server.PromptServer.instance.send_sync("inspire-node-output-label", {"node_id": unique_id, "output_idx": 4, "label": "MODEL (fail)"})
|
||||
server.PromptServer.instance.send_sync("inspire-node-output-label", {"node_id": unique_id, "output_idx": 5, "label": "CLIP (fail)"})
|
||||
logging.error("[Inspire Pack] IPAdapterModelHelper: You cannot mix SDXL and SD1.5 in the checkpoint and IPAdapter.")
|
||||
print(f"[ERROR] IPAdapterModelHelper: You cannot mix SDXL and SD1.5 in the checkpoint and IPAdapter.")
|
||||
raise Exception("[ERROR] You cannot mix SDXL and SD1.5 in the checkpoint and IPAdapter.")
|
||||
|
||||
ipadapter, clipvision, lora, is_insightface = model_preset[preset]
|
||||
@@ -157,18 +146,18 @@ class IPAdapterModelHelper:
|
||||
if 'IPAdapterInsightFaceLoader' in nodes.NODE_CLASS_MAPPINGS:
|
||||
insight_face_loader = nodes.NODE_CLASS_MAPPINGS['IPAdapterInsightFaceLoader']().load_insightface
|
||||
else:
|
||||
logging.warning("'ComfyUI IPAdapter Plus' extension is either too outdated or not installed.")
|
||||
print("'ComfyUI IPAdapter Plus' extension is either too outdated or not installed.")
|
||||
insight_face_loader = None
|
||||
|
||||
icache_key = ""
|
||||
if is_insightface:
|
||||
if insight_face_loader is None:
|
||||
raise Exception("[ERROR] 'ComfyUI IPAdapter Plus' extension is either too outdated or not installed.")
|
||||
raise Exception(f"[ERROR] 'ComfyUI IPAdapter Plus' extension is either too outdated or not installed.")
|
||||
|
||||
if cache_mode in ["insightface only", "all"]:
|
||||
icache_key = 'insightface-' + insightface_provider
|
||||
if icache_key not in backend_support.cache:
|
||||
backend_support.update_cache(icache_key, "insightface", (False, insight_face_loader(provider=insightface_provider, model_name=insightface_model_name)[0]))
|
||||
backend_support.update_cache(icache_key, "insightface", (False, insight_face_loader(insightface_provider)[0]))
|
||||
_, (_, insightface) = backend_support.cache[icache_key]
|
||||
else:
|
||||
insightface = insight_face_loader(insightface_provider)[0]
|
||||
|
||||
+156
-428
@@ -12,24 +12,20 @@ import folder_paths
|
||||
import comfy
|
||||
import traceback
|
||||
import random
|
||||
import hashlib
|
||||
|
||||
from server import PromptServer
|
||||
from .libs import utils, common
|
||||
from .backend_support import CheckpointLoaderSimpleShared
|
||||
|
||||
import logging
|
||||
|
||||
model_path = folder_paths.models_dir
|
||||
utils.add_folder_path_and_extensions("inspire_prompts", [os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "prompts"))], {'.txt'})
|
||||
|
||||
|
||||
prompt_builder_preset = {}
|
||||
|
||||
|
||||
resource_path = os.path.join(os.path.dirname(__file__), "..", "resources")
|
||||
resource_path = os.path.abspath(resource_path)
|
||||
|
||||
prompts_path = os.path.join(os.path.dirname(__file__), "..", "prompts")
|
||||
prompts_path = os.path.abspath(prompts_path)
|
||||
|
||||
|
||||
try:
|
||||
pb_yaml_path = os.path.join(resource_path, 'prompt-builder.yaml')
|
||||
@@ -40,259 +36,20 @@ try:
|
||||
|
||||
with open(pb_yaml_path, 'r', encoding="utf-8") as f:
|
||||
prompt_builder_preset = yaml.load(f, Loader=yaml.FullLoader)
|
||||
except Exception as e: # noqa: F841
|
||||
logging.error("[Inspire Pack] Failed to load 'prompt-builder.yaml'\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
except Exception as e:
|
||||
print(f"[Inspire Pack] Failed to load 'prompt-builder.yaml'")
|
||||
|
||||
|
||||
class LoadPromptsFromDir:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
global prompts_path
|
||||
try:
|
||||
prompt_dirs = []
|
||||
for x in folder_paths.get_folder_paths('inspire_prompts'):
|
||||
for d in os.listdir(x):
|
||||
if os.path.isdir(os.path.join(x, d)):
|
||||
prompt_dirs.append(d)
|
||||
prompt_dirs = [d for d in os.listdir(prompts_path) if os.path.isdir(os.path.join(prompts_path, d))]
|
||||
except Exception:
|
||||
prompt_dirs = []
|
||||
|
||||
return {"required": {
|
||||
"prompt_dir": (prompt_dirs,)
|
||||
},
|
||||
"optional": {
|
||||
"reload": ("BOOLEAN", { "default": False, "label_on": "if file changed", "label_off": "if value changed"}),
|
||||
"load_cap": ("INT", {"default": 0, "min": 0, "step": 1, "advanced": True, "tooltip": "The amount of prompts to load at once:\n0: Load all\n1 or higher: Load a specified number"}),
|
||||
"start_index": ("INT", {"default": 0, "min": -1, "step": 1, "max": 0xffffffffffffffff, "advanced": True, "tooltip": "Starting index for loading prompts:\n-1: The last prompt\n0 or higher: Load from the specified index"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT", "INT", "INT")
|
||||
RETURN_NAMES = ("zipped_prompt", "count", "remaining_count")
|
||||
OUTPUT_IS_LIST = (True, False, False)
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
@staticmethod
|
||||
def IS_CHANGED(prompt_dir, reload=False, load_cap=0, start_index=-1):
|
||||
if not reload:
|
||||
return prompt_dir, load_cap, start_index
|
||||
else:
|
||||
candidates = []
|
||||
for d in folder_paths.get_folder_paths('inspire_prompts'):
|
||||
candidates.append(os.path.join(d, prompt_dir))
|
||||
|
||||
prompt_files = []
|
||||
for x in candidates:
|
||||
for root, dirs, files in os.walk(x):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
prompt_files.append(os.path.join(root, file))
|
||||
|
||||
prompt_files.sort()
|
||||
|
||||
md5 = hashlib.md5()
|
||||
|
||||
for file_name in prompt_files:
|
||||
md5.update(file_name.encode('utf-8'))
|
||||
with open(folder_paths.get_full_path('inspire_prompts', file_name), 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
md5.update(chunk)
|
||||
|
||||
return md5.hexdigest(), load_cap, start_index
|
||||
|
||||
@staticmethod
|
||||
def doit(prompt_dir, reload=False, load_cap=0, start_index=-1):
|
||||
candidates = []
|
||||
for d in folder_paths.get_folder_paths('inspire_prompts'):
|
||||
candidates.append(os.path.join(d, prompt_dir))
|
||||
|
||||
prompt_files = []
|
||||
for x in candidates:
|
||||
for root, dirs, files in os.walk(x):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
prompt_files.append(os.path.join(root, file))
|
||||
|
||||
prompt_files.sort()
|
||||
|
||||
prompts = []
|
||||
for file_name in prompt_files:
|
||||
logging.info(f"file_name: {file_name}")
|
||||
try:
|
||||
with open(file_name, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
|
||||
for prompt in prompt_list:
|
||||
pattern = r"^(?:(?:positive:(?P<positive>.*?)|negative:(?P<negative>.*?)|name:(?P<name>.*?))\n*)+$"
|
||||
matches = re.search(pattern, prompt, re.DOTALL | re.IGNORECASE)
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group('positive').strip()
|
||||
negative_text = matches.group('negative').strip()
|
||||
name_text = matches.group('name').strip() if matches.group('name') else file_name
|
||||
result_tuple = (positive_text, negative_text, name_text)
|
||||
prompts.append(result_tuple)
|
||||
else:
|
||||
logging.warning(f"[Inspire Pack] LoadPromptsFromDir: invalid prompt format in '{file_name}'")
|
||||
except Exception as e:
|
||||
logging.error(f"[Inspire Pack] LoadPromptsFromDir: an error occurred while processing '{file_name}': {str(e)}\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
|
||||
# slicing [start_index ~ start_index + load_cap]
|
||||
total_prompts = len(prompts)
|
||||
prompts = prompts[start_index:]
|
||||
remaining_count = False
|
||||
if load_cap > 0:
|
||||
remaining_count = max(0, len(prompts) - load_cap)
|
||||
prompts = prompts[:load_cap]
|
||||
|
||||
return prompts, total_prompts, remaining_count
|
||||
|
||||
|
||||
class LoadPromptsFromFile:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
prompt_files = []
|
||||
try:
|
||||
prompts_paths = folder_paths.get_folder_paths('inspire_prompts')
|
||||
for prompts_path in prompts_paths:
|
||||
for root, dirs, files in os.walk(prompts_path):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, prompts_path)
|
||||
prompt_files.append(rel_path)
|
||||
except Exception:
|
||||
prompt_files = []
|
||||
|
||||
return {"required": {
|
||||
"prompt_file": (prompt_files,)
|
||||
},
|
||||
"optional": {
|
||||
"text_data_opt": ("STRING", {"defaultInput": True}),
|
||||
"reload": ("BOOLEAN", {"default": False, "label_on": "if file changed", "label_off": "if value changed"}),
|
||||
"load_cap": ("INT", {"default": 0, "min": 0, "step": 1, "advanced": True, "tooltip": "The amount of prompts to load at once:\n0: Load all\n1 or higher: Load a specified number"}),
|
||||
"start_index": ("INT", {"default": 0, "min": -1, "max": 0xffffffffffffffff, "step": 1, "advanced": True, "tooltip": "Starting index for loading prompts:\n-1: The last prompt\n0 or higher: Load from the specified index"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT", "INT", "INT")
|
||||
RETURN_NAMES = ("zipped_prompt", "count", "remaining_count")
|
||||
OUTPUT_IS_LIST = (True, False, False)
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
@staticmethod
|
||||
def IS_CHANGED(prompt_file, text_data_opt=None, reload=False, load_cap=0, start_index=-1):
|
||||
md5 = hashlib.md5()
|
||||
|
||||
if text_data_opt is not None:
|
||||
md5.update(text_data_opt)
|
||||
return md5.hexdigest(), load_cap, start_index
|
||||
elif not reload:
|
||||
return prompt_file, load_cap, start_index
|
||||
else:
|
||||
matched_path = None
|
||||
for x in folder_paths.get_folder_paths('inspire_prompts'):
|
||||
matched_path = os.path.join(x, prompt_file)
|
||||
if not os.path.exists(matched_path):
|
||||
matched_path = None
|
||||
else:
|
||||
break
|
||||
|
||||
if matched_path is None:
|
||||
return float('NaN')
|
||||
|
||||
with open(matched_path, 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
md5.update(chunk)
|
||||
|
||||
return md5.hexdigest(), load_cap, start_index
|
||||
|
||||
@staticmethod
|
||||
def doit(prompt_file, text_data_opt=None, reload=False, load_cap=0, start_index=-1):
|
||||
matched_path = None
|
||||
for d in folder_paths.get_folder_paths('inspire_prompts'):
|
||||
matched_path = os.path.join(d, prompt_file)
|
||||
if os.path.exists(matched_path):
|
||||
break
|
||||
else:
|
||||
matched_path = None
|
||||
|
||||
if matched_path:
|
||||
logging.info(f"[Inspire Pack] LoadPromptsFromFile: file found '{prompt_file}'")
|
||||
else:
|
||||
logging.warning(f"[Inspire Pack] LoadPromptsFromFile: file not found '{prompt_file}'")
|
||||
|
||||
prompts = []
|
||||
try:
|
||||
if not text_data_opt:
|
||||
with open(matched_path, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
else:
|
||||
prompt_data = text_data_opt
|
||||
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
|
||||
pattern = r"^(?:(?:positive:(?P<positive>.*?)|negative:(?P<negative>.*?)|name:(?P<name>.*?))\n*)+$"
|
||||
|
||||
for p in prompt_list:
|
||||
matches = re.search(pattern, p, re.DOTALL)
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group('positive').strip()
|
||||
negative_text = matches.group('negative').strip()
|
||||
name_text = matches.group('name').strip() if matches.group('name') else prompt_file
|
||||
result_tuple = (positive_text, negative_text, name_text)
|
||||
prompts.append(result_tuple)
|
||||
else:
|
||||
logging.warning(f"[Inspire Pack] LoadPromptsFromFile: invalid prompt format in '{prompt_file}'")
|
||||
except Exception as e:
|
||||
logging.error(f"[Inspire Pack] LoadPromptsFromFile: an error occurred while processing '{prompt_file}': {str(e)}\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
|
||||
# slicing [start_index ~ start_index + load_cap]
|
||||
total_prompts = len(prompts)
|
||||
prompts = prompts[start_index:]
|
||||
remaining_count = 0
|
||||
if load_cap > 0:
|
||||
remaining_count = max(0, len(prompts) - load_cap)
|
||||
prompts = prompts[:load_cap]
|
||||
|
||||
return prompts, total_prompts, remaining_count
|
||||
|
||||
|
||||
class LoadSinglePromptFromFile:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
prompt_files = []
|
||||
try:
|
||||
prompts_paths = folder_paths.get_folder_paths('inspire_prompts')
|
||||
for prompts_path in prompts_paths:
|
||||
for root, dirs, files in os.walk(prompts_path):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, prompts_path)
|
||||
prompt_files.append(rel_path)
|
||||
except Exception:
|
||||
prompt_files = []
|
||||
|
||||
return {"required": {
|
||||
"prompt_file": (prompt_files,),
|
||||
"index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
},
|
||||
"optional": {"text_data_opt": ("STRING", {"defaultInput": True})}
|
||||
}
|
||||
return {"required": {"prompt_dir": (prompt_dirs,)}}
|
||||
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
@@ -301,49 +58,141 @@ class LoadSinglePromptFromFile:
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
@staticmethod
|
||||
def doit(prompt_file, index, text_data_opt=None):
|
||||
prompt_path = None
|
||||
prompts_paths = folder_paths.get_folder_paths('inspire_prompts')
|
||||
for d in prompts_paths:
|
||||
prompt_path = os.path.join(d, prompt_file)
|
||||
if os.path.exists(prompt_path):
|
||||
break
|
||||
else:
|
||||
prompt_path = None
|
||||
def doit(self, prompt_dir):
|
||||
global prompts_path
|
||||
prompt_dir = os.path.join(prompts_path, prompt_dir)
|
||||
files = [f for f in os.listdir(prompt_dir) if f.endswith(".txt")]
|
||||
files.sort()
|
||||
|
||||
if prompt_path:
|
||||
logging.info(f"[Inspire Pack] LoadSinglePromptFromFile: file found '{prompt_file}'")
|
||||
else:
|
||||
logging.warning(f"[Inspire Pack] LoadSinglePromptFromFile: file not found '{prompt_file}'")
|
||||
prompts = []
|
||||
for file_name in files:
|
||||
print(f"file_name: {file_name}")
|
||||
try:
|
||||
with open(os.path.join(prompt_dir, file_name), "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
|
||||
for prompt in prompt_list:
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group(1).strip()
|
||||
negative_text = matches.group(2).strip()
|
||||
result_tuple = (positive_text, negative_text, file_name)
|
||||
prompts.append(result_tuple)
|
||||
else:
|
||||
print(f"[WARN] LoadPromptsFromDir: invalid prompt format in '{file_name}'")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] LoadPromptsFromDir: an error occurred while processing '{file_name}': {str(e)}")
|
||||
|
||||
return (prompts, )
|
||||
|
||||
|
||||
class LoadPromptsFromFile:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
global prompts_path
|
||||
try:
|
||||
prompt_files = []
|
||||
for root, dirs, files in os.walk(prompts_path):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, prompts_path)
|
||||
prompt_files.append(rel_path)
|
||||
except Exception:
|
||||
prompt_files = []
|
||||
|
||||
return {"required": {"prompt_file": (prompt_files,)}}
|
||||
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
def doit(self, prompt_file):
|
||||
prompt_path = os.path.join(prompts_path, prompt_file)
|
||||
|
||||
prompts = []
|
||||
try:
|
||||
if not text_data_opt:
|
||||
with open(prompt_path, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
else:
|
||||
prompt_data = text_data_opt
|
||||
with open(prompt_path, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
try:
|
||||
prompt = prompt_list[index]
|
||||
except Exception:
|
||||
prompt = prompt_list[-1]
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
|
||||
pattern = r"^(?:(?:positive:(?P<positive>.*?)|negative:(?P<negative>.*?)|name:(?P<name>.*?))\n*)+$"
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
for prompt in prompt_list:
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group('positive').strip()
|
||||
negative_text = matches.group('negative').strip()
|
||||
name_text = matches.group('name').strip() if matches.group('name') else prompt_file
|
||||
result_tuple = (positive_text, negative_text, name_text)
|
||||
prompts.append(result_tuple)
|
||||
else:
|
||||
logging.warning(f"[Inspire Pack] LoadSinglePromptFromFile: invalid prompt format in '{prompt_file}'")
|
||||
if matches:
|
||||
positive_text = matches.group(1).strip()
|
||||
negative_text = matches.group(2).strip()
|
||||
result_tuple = (positive_text, negative_text, prompt_file)
|
||||
prompts.append(result_tuple)
|
||||
else:
|
||||
print(f"[WARN] LoadPromptsFromFile: invalid prompt format in '{prompt_file}'")
|
||||
except Exception as e:
|
||||
logging.error(f"[Inspire Pack] LoadSinglePromptFromFile: an error occurred while processing '{prompt_file}': {str(e)}\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
print(f"[ERROR] LoadPromptsFromFile: an error occurred while processing '{prompt_file}': {str(e)}")
|
||||
|
||||
return (prompts, )
|
||||
|
||||
|
||||
class LoadSinglePromptFromFile:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
global prompts_path
|
||||
try:
|
||||
prompt_files = []
|
||||
for root, dirs, files in os.walk(prompts_path):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, prompts_path)
|
||||
prompt_files.append(rel_path)
|
||||
except Exception:
|
||||
prompt_files = []
|
||||
|
||||
return {"required": {
|
||||
"prompt_file": (prompt_files,),
|
||||
"index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
def doit(self, prompt_file, index):
|
||||
prompt_path = os.path.join(prompts_path, prompt_file)
|
||||
|
||||
prompts = []
|
||||
try:
|
||||
with open(prompt_path, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
try:
|
||||
prompt = prompt_list[index]
|
||||
except Exception:
|
||||
prompt = prompt_list[-1]
|
||||
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group(1).strip()
|
||||
negative_text = matches.group(2).strip()
|
||||
result_tuple = (positive_text, negative_text, prompt_file)
|
||||
prompts.append(result_tuple)
|
||||
else:
|
||||
print(f"[WARN] LoadPromptsFromFile: invalid prompt format in '{prompt_file}'")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] LoadPromptsFromFile: an error occurred while processing '{prompt_file}': {str(e)}")
|
||||
|
||||
return (prompts, )
|
||||
|
||||
@@ -386,8 +235,9 @@ class ZipPrompt:
|
||||
return ((positive, negative, name_opt), )
|
||||
|
||||
|
||||
prompt_blacklist = set(['filename_prefix'])
|
||||
|
||||
prompt_blacklist = set([
|
||||
'filename_prefix'
|
||||
])
|
||||
|
||||
class PromptExtractor:
|
||||
@classmethod
|
||||
@@ -487,37 +337,6 @@ class GlobalSeed:
|
||||
return {}
|
||||
|
||||
|
||||
class SeedLogger:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"seeds": ("STRING", {"multiline": True, "dynamicPrompts": False, "control_after_generate": False}),
|
||||
"limit": ("INT", {"default": 5, "min": 0, "max": 0xffffffffffffffff}),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def doit(self, seed, seeds: str, limit, unique_id):
|
||||
|
||||
if limit > 0:
|
||||
lines = seeds.split('\n')
|
||||
res = str(seed) + '\n' + '\n'.join(lines[:limit-1])
|
||||
else:
|
||||
res = str(seed) + '\n' + seeds
|
||||
|
||||
PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": unique_id, "widget_name": "seeds", "type": "text", "data": res})
|
||||
return {}
|
||||
|
||||
|
||||
class GlobalSampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -595,7 +414,7 @@ class BNK_EncoderWrapper:
|
||||
if 'BNK_CLIPTextEncodeAdvanced' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb',
|
||||
"To use 'WildcardEncodeInspire' node, 'ComfyUI_ADV_CLIP_emb' extension is required.")
|
||||
raise Exception("[ERROR] To use WildcardEncodeInspire, you need to install 'Advanced CLIP Text Encode'")
|
||||
raise Exception(f"[ERROR] To use WildcardEncodeInspire, you need to install 'Advanced CLIP Text Encode'")
|
||||
return nodes.NODE_CLASS_MAPPINGS['BNK_CLIPTextEncodeAdvanced']().encode(clip, text, self.token_normalization, self.weight_interpretation)
|
||||
|
||||
|
||||
@@ -609,13 +428,7 @@ class WildcardEncodeInspire:
|
||||
"weight_interpretation": (["comfy", "A1111", "compel", "comfy++", "down_weight"], {'default': 'comfy++'}),
|
||||
"wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False, 'placeholder': 'Wildcard Prompt (User Input)'}),
|
||||
"populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, 'placeholder': 'Populated Prompt (Will be generated automatically)'}),
|
||||
|
||||
"mode": (["populate", "fixed", "reproduce"], {"default": "populate", "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.\n"
|
||||
"reproduce: This mode operates as 'fixed' mode only once for reproduction, and then it switches to 'populate' mode."
|
||||
}),
|
||||
|
||||
"mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed"}),
|
||||
"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}),
|
||||
@@ -636,11 +449,10 @@ class WildcardEncodeInspire:
|
||||
if 'ImpactWildcardEncode' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/ltdrdata/ComfyUI-Impact-Pack',
|
||||
"To use 'Wildcard Encode (Inspire)' node, 'Impact Pack' extension is required.")
|
||||
raise Exception("[ERROR] To use 'Wildcard Encode (Inspire)', you need to install 'Impact Pack'")
|
||||
raise Exception(f"[ERROR] To use 'Wildcard Encode (Inspire)', you need to install 'Impact Pack'")
|
||||
|
||||
processed = []
|
||||
model, clip, conditioning = nodes.NODE_CLASS_MAPPINGS['ImpactWildcardEncode'].process_with_loras(wildcard_opt=populated, model=kwargs['model'], clip=kwargs['clip'], seed=kwargs['seed'], clip_encoder=clip_encoder, processed=processed)
|
||||
return (model, clip, conditioning, processed[0])
|
||||
model, clip, conditioning = nodes.NODE_CLASS_MAPPINGS['ImpactWildcardEncode'].process_with_loras(wildcard_opt=populated, model=kwargs['model'], clip=kwargs['clip'], clip_encoder=clip_encoder)
|
||||
return (model, clip, conditioning, populated)
|
||||
|
||||
|
||||
class MakeBasicPipe:
|
||||
@@ -656,11 +468,7 @@ class MakeBasicPipe:
|
||||
"Add selection to": ("BOOLEAN", {"default": True, "label_on": "Positive", "label_off": "Negative"}),
|
||||
"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"],),
|
||||
"wildcard_mode": (["populate", "fixed", "reproduce"], {"default": "populate", "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.\n"
|
||||
"reproduce: This mode operates as 'fixed' mode only once for reproduction, and then it switches to 'populate' mode."
|
||||
}),
|
||||
"wildcard_mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed"}),
|
||||
|
||||
"positive_populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, 'placeholder': 'Populated Positive Prompt (Will be generated automatically)'}),
|
||||
"negative_populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, 'placeholder': 'Populated Negative Prompt (Will be generated automatically)'}),
|
||||
@@ -669,6 +477,7 @@ class MakeBasicPipe:
|
||||
"weight_interpretation": (["comfy", "A1111", "compel", "comfy++", "down_weight"], {'default': 'comfy++'}),
|
||||
|
||||
"stop_at_clip_layer": ("INT", {"default": -2, "min": -24, "max": -1, "step": 1}),
|
||||
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
},
|
||||
"optional": {
|
||||
@@ -691,7 +500,7 @@ class MakeBasicPipe:
|
||||
if 'ImpactWildcardEncode' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/ltdrdata/ComfyUI-Impact-Pack',
|
||||
"To use 'Make Basic Pipe (Inspire)' node, 'Impact Pack' extension is required.")
|
||||
raise Exception("[ERROR] To use 'Make Basic Pipe (Inspire)', you need to install 'Impact Pack'")
|
||||
raise Exception(f"[ERROR] To use 'Make Basic Pipe (Inspire)', you need to install 'Impact Pack'")
|
||||
|
||||
model, clip, vae, key = CheckpointLoaderSimpleShared().doit(ckpt_name=kwargs['ckpt_name'], key_opt=kwargs['ckpt_key_opt'])
|
||||
clip = nodes.CLIPSetLastLayer().set_last_layer(clip, kwargs['stop_at_clip_layer'])[0]
|
||||
@@ -740,21 +549,16 @@ class SeedExplorer:
|
||||
"additional_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"initial_batch_seed_mode": (["incremental", "comfy"],),
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"model": ("MODEL",),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE_IMAGE",)
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
@staticmethod
|
||||
def apply_variation(start_noise, seed_items, noise_device, mask=None, variation_method='linear'):
|
||||
def apply_variation(start_noise, seed_items, noise_device, mask=None):
|
||||
noise = start_noise
|
||||
for x in seed_items:
|
||||
if isinstance(x, str):
|
||||
@@ -767,20 +571,15 @@ class SeedExplorer:
|
||||
variation_seed = int(item[0])
|
||||
variation_strength = float(item[1])
|
||||
|
||||
noise = utils.apply_variation_noise(noise, noise_device, variation_seed, variation_strength, mask=mask, variation_method=variation_method)
|
||||
noise = utils.apply_variation_noise(noise, noise_device, variation_seed, variation_strength, mask=mask)
|
||||
except Exception:
|
||||
logging.error(f"[Inspire Pack] IGNORED: SeedExplorer failed to processing '{x}'")
|
||||
print(f"[ERROR] IGNORED: SeedExplorer failed to processing '{x}'")
|
||||
traceback.print_exc()
|
||||
return noise
|
||||
|
||||
@staticmethod
|
||||
def doit(latent, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode,
|
||||
initial_batch_seed_mode, variation_method='linear', model=None):
|
||||
def doit(self, latent, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode,
|
||||
initial_batch_seed_mode):
|
||||
latent_image = latent["samples"]
|
||||
|
||||
if hasattr(comfy.sample, 'fix_empty_latent_channels') and model is not None:
|
||||
latent_image = comfy.sample.fix_empty_latent_channels(model, latent_image)
|
||||
|
||||
device = comfy.model_management.get_torch_device()
|
||||
noise_device = "cpu" if noise_mode == "CPU" else device
|
||||
|
||||
@@ -804,13 +603,13 @@ class SeedExplorer:
|
||||
|
||||
noise = utils.prepare_noise(latent_image, hd_seed, None, noise_device, initial_batch_seed_mode)
|
||||
noise = noise.to(device)
|
||||
noise = SeedExplorer.apply_variation(noise, tl, noise_device, variation_method=variation_method)
|
||||
noise = SeedExplorer.apply_variation(noise, tl, noise_device)
|
||||
noise = noise.cpu()
|
||||
|
||||
return (noise,)
|
||||
|
||||
except Exception:
|
||||
logging.error("[Inspire Pack] IGNORED: SeedExplorer failed")
|
||||
print(f"[ERROR] IGNORED: SeedExplorer failed")
|
||||
traceback.print_exc()
|
||||
|
||||
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout,
|
||||
@@ -818,72 +617,6 @@ class SeedExplorer:
|
||||
return (noise,)
|
||||
|
||||
|
||||
class CompositeNoise:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"destination": ("NOISE_IMAGE",),
|
||||
"source": ("NOISE_IMAGE",),
|
||||
"mode": (["center", "left-top", "right-top", "left-bottom", "right-bottom", "xy"], ),
|
||||
"x": ("INT", {"default": 0, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
"y": ("INT", {"default": 0, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE_IMAGE",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
def doit(self, destination, source, mode, x, y):
|
||||
new_tensor = destination.clone()
|
||||
|
||||
if mode == 'center':
|
||||
y1 = (new_tensor.size(2) - source.size(2)) // 2
|
||||
x1 = (new_tensor.size(3) - source.size(3)) // 2
|
||||
elif mode == 'left-top':
|
||||
y1 = 0
|
||||
x1 = 0
|
||||
elif mode == 'right-top':
|
||||
y1 = 0
|
||||
x1 = new_tensor.size(2) - source.size(2)
|
||||
elif mode == 'left-bottom':
|
||||
y1 = new_tensor.size(3) - source.size(3)
|
||||
x1 = 0
|
||||
elif mode == 'right-bottom':
|
||||
y1 = new_tensor.size(3) - source.size(3)
|
||||
x1 = new_tensor.size(2) - source.size(2)
|
||||
else: # mode == 'xy':
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
|
||||
# raw coordinates
|
||||
y2 = y1 + source.size(2)
|
||||
x2 = x1 + source.size(3)
|
||||
|
||||
# bounding for destination
|
||||
top = max(0, y1)
|
||||
left = max(0, x1)
|
||||
bottom = min(new_tensor.size(2), y2)
|
||||
right = min(new_tensor.size(3), x2)
|
||||
|
||||
# bounding for source
|
||||
left_gap = left - x1
|
||||
top_gap = top - y1
|
||||
|
||||
width = right - left
|
||||
height = bottom - top
|
||||
|
||||
height = min(height, y1 + source.size(2) - top)
|
||||
width = min(width, x1 + source.size(3) - left)
|
||||
|
||||
# composite
|
||||
new_tensor[:, :, top:top + height, left:left + width] = source[:, :, top_gap:top_gap + height, left_gap:left_gap + width]
|
||||
|
||||
return (new_tensor,)
|
||||
|
||||
|
||||
list_counter_map = {}
|
||||
|
||||
|
||||
@@ -1024,7 +757,6 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ZipPrompt //Inspire": ZipPrompt,
|
||||
"PromptExtractor //Inspire": PromptExtractor,
|
||||
"GlobalSeed //Inspire": GlobalSeed,
|
||||
"SeedLogger //Inspire": SeedLogger,
|
||||
"GlobalSampler //Inspire": GlobalSampler,
|
||||
"BindImageListPromptList //Inspire": BindImageListPromptList,
|
||||
"WildcardEncode //Inspire": WildcardEncodeInspire,
|
||||
@@ -1036,9 +768,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"MakeBasicPipe //Inspire": MakeBasicPipe,
|
||||
"RemoveControlNet //Inspire": RemoveControlNet,
|
||||
"RemoveControlNetFromRegionalPrompts //Inspire": RemoveControlNetFromRegionalPrompts,
|
||||
"CompositeNoise //Inspire": CompositeNoise,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LoadPromptsFromDir //Inspire": "Load Prompts From Dir (Inspire)",
|
||||
"LoadPromptsFromFile //Inspire": "Load Prompts From File (Inspire)",
|
||||
@@ -1047,7 +777,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ZipPrompt //Inspire": "Zip Prompt (Inspire)",
|
||||
"PromptExtractor //Inspire": "Prompt Extractor (Inspire)",
|
||||
"GlobalSeed //Inspire": "Global Seed (Inspire)",
|
||||
"SeedLogger //Inspire": "Seed Logger (Inspire)",
|
||||
"GlobalSampler //Inspire": "Global Sampler (Inspire)",
|
||||
"BindImageListPromptList //Inspire": "Bind [ImageList, PromptList] (Inspire)",
|
||||
"WildcardEncode //Inspire": "Wildcard Encode (Inspire)",
|
||||
@@ -1058,6 +787,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RandomGeneratorForList //Inspire": "Random Generator for List (Inspire)",
|
||||
"MakeBasicPipe //Inspire": "Make Basic Pipe (Inspire)",
|
||||
"RemoveControlNet //Inspire": "Remove ControlNet (Inspire)",
|
||||
"RemoveControlNetFromRegionalPrompts //Inspire": "Remove ControlNet [RegionalPrompts] (Inspire)",
|
||||
"CompositeNoise //Inspire": "Composite Noise (Inspire)"
|
||||
"RemoveControlNetFromRegionalPrompts //Inspire": "Remove ControlNet [RegionalPrompts] (Inspire)"
|
||||
}
|
||||
|
||||
+37
-195
@@ -3,13 +3,9 @@ import traceback
|
||||
import comfy
|
||||
import nodes
|
||||
import torch
|
||||
import re
|
||||
import webcolors
|
||||
|
||||
from . import prompt_support
|
||||
from .libs import utils, common
|
||||
|
||||
import logging
|
||||
|
||||
class RegionalPromptSimple:
|
||||
@classmethod
|
||||
@@ -25,12 +21,6 @@ class RegionalPromptSimple:
|
||||
"controlnet_in_pipe": ("BOOLEAN", {"default": False, "label_on": "Keep", "label_off": "Override"}),
|
||||
"sigma_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
},
|
||||
"optional": {
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("REGIONAL_PROMPTS", )
|
||||
@@ -38,13 +28,11 @@ class RegionalPromptSimple:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(basic_pipe, mask, cfg, sampler_name, scheduler, wildcard_prompt,
|
||||
controlnet_in_pipe=False, sigma_factor=1.0, variation_seed=0, variation_strength=0.0, variation_method='linear', scheduler_func_opt=None):
|
||||
def doit(self, basic_pipe, mask, cfg, sampler_name, scheduler, wildcard_prompt, controlnet_in_pipe=False, sigma_factor=1.0):
|
||||
if 'RegionalPrompt' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/ltdrdata/ComfyUI-Impact-Pack',
|
||||
"To use 'RegionalPromptSimple' node, 'Impact Pack' extension is required.")
|
||||
raise Exception("[ERROR] To use RegionalPromptSimple, you need to install 'ComfyUI-Impact-Pack'")
|
||||
raise Exception(f"[ERROR] To use RegionalPromptSimple, you need to install 'ComfyUI-Impact-Pack'")
|
||||
|
||||
model, clip, vae, positive, negative = basic_pipe
|
||||
|
||||
@@ -53,7 +41,7 @@ class RegionalPromptSimple:
|
||||
rp = nodes.NODE_CLASS_MAPPINGS['RegionalPrompt']()
|
||||
|
||||
if wildcard_prompt != "":
|
||||
model, clip, new_positive, _ = iwe.doit(model=model, clip=clip, populated_text=wildcard_prompt, seed=None)
|
||||
model, clip, new_positive, _ = iwe.doit(model=model, clip=clip, populated_text=wildcard_prompt)
|
||||
|
||||
if controlnet_in_pipe:
|
||||
prev_cnet = None
|
||||
@@ -72,24 +60,20 @@ class RegionalPromptSimple:
|
||||
|
||||
basic_pipe = model, clip, vae, new_positive, negative
|
||||
|
||||
sampler = kap.doit(cfg, sampler_name, scheduler, basic_pipe, sigma_factor=sigma_factor, scheduler_func_opt=scheduler_func_opt)[0]
|
||||
try:
|
||||
regional_prompts = rp.doit(mask, sampler, variation_seed=variation_seed, variation_strength=variation_strength, variation_method=variation_method)[0]
|
||||
except:
|
||||
raise Exception("[Inspire-Pack] ERROR: Impact Pack is outdated. Update Impact Pack to latest version to use this.")
|
||||
sampler = kap.doit(cfg, sampler_name, scheduler, basic_pipe, sigma_factor=sigma_factor)[0]
|
||||
regional_prompts = rp.doit(mask, sampler)[0]
|
||||
|
||||
return (regional_prompts, )
|
||||
|
||||
|
||||
def color_to_mask(color_mask, mask_color):
|
||||
try:
|
||||
if mask_color.startswith("#") or mask_color.isalpha():
|
||||
hex = mask_color[1:] if mask_color.startswith("#") else webcolors.name_to_hex(mask_color)[1:]
|
||||
selected = int(hex, 16)
|
||||
if mask_color.startswith("#"):
|
||||
selected = int(mask_color[1:], 16)
|
||||
else:
|
||||
selected = int(mask_color, 10)
|
||||
except Exception:
|
||||
raise Exception("[ERROR] Invalid mask_color value. mask_color should be a color value for RGB")
|
||||
raise Exception(f"[ERROR] Invalid mask_color value. mask_color should be a color value for RGB")
|
||||
|
||||
temp = (torch.clamp(color_mask, 0, 1.0) * 255.0).round().to(torch.int)
|
||||
temp = torch.bitwise_left_shift(temp[:, :, :, 0], 16) + torch.bitwise_left_shift(temp[:, :, :, 1], 8) + temp[:, :, :, 2]
|
||||
@@ -112,12 +96,6 @@ class RegionalPromptColorMask:
|
||||
"controlnet_in_pipe": ("BOOLEAN", {"default": False, "label_on": "Keep", "label_off": "Override"}),
|
||||
"sigma_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
},
|
||||
"optional": {
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("REGIONAL_PROMPTS", "MASK")
|
||||
@@ -125,13 +103,10 @@ class RegionalPromptColorMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(basic_pipe, color_mask, mask_color, cfg, sampler_name, scheduler, wildcard_prompt,
|
||||
controlnet_in_pipe=False, sigma_factor=1.0, variation_seed=0, variation_strength=0.0, variation_method="linear", scheduler_func_opt=None):
|
||||
def doit(self, basic_pipe, color_mask, mask_color, cfg, sampler_name, scheduler, wildcard_prompt, controlnet_in_pipe=False, sigma_factor=1.0):
|
||||
mask = color_to_mask(color_mask, mask_color)
|
||||
rp = RegionalPromptSimple().doit(basic_pipe, mask, cfg, sampler_name, scheduler, wildcard_prompt, controlnet_in_pipe,
|
||||
sigma_factor=sigma_factor, variation_seed=variation_seed, variation_strength=variation_strength, variation_method=variation_method, scheduler_func_opt=scheduler_func_opt)[0]
|
||||
return rp, mask
|
||||
rp = RegionalPromptSimple().doit(basic_pipe, mask, cfg, sampler_name, scheduler, wildcard_prompt, controlnet_in_pipe, sigma_factor=sigma_factor)[0]
|
||||
return (rp, mask)
|
||||
|
||||
|
||||
class RegionalConditioningSimple:
|
||||
@@ -152,8 +127,7 @@ class RegionalConditioningSimple:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(clip, mask, strength, set_cond_area, prompt):
|
||||
def doit(self, clip, mask, strength, set_cond_area, prompt):
|
||||
conditioning = nodes.CLIPTextEncode().encode(clip, prompt)[0]
|
||||
conditioning = nodes.ConditioningSetMask().append(conditioning, mask, set_cond_area, strength)[0]
|
||||
return (conditioning, )
|
||||
@@ -171,9 +145,6 @@ class RegionalConditioningColorMask:
|
||||
"set_cond_area": (["default", "mask bounds"],),
|
||||
"prompt": ("STRING", {"multiline": True, "placeholder": "prompt"}),
|
||||
},
|
||||
"optional": {
|
||||
"dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING", "MASK")
|
||||
@@ -181,16 +152,12 @@ class RegionalConditioningColorMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(clip, color_mask, mask_color, strength, set_cond_area, prompt, dilation=0):
|
||||
def doit(self, clip, color_mask, mask_color, strength, set_cond_area, prompt):
|
||||
mask = color_to_mask(color_mask, mask_color)
|
||||
|
||||
if dilation != 0:
|
||||
mask = utils.dilate_mask(mask, dilation)
|
||||
|
||||
conditioning = nodes.CLIPTextEncode().encode(clip, prompt)[0]
|
||||
conditioning = nodes.ConditioningSetMask().append(conditioning, mask, set_cond_area, strength)[0]
|
||||
return conditioning, mask
|
||||
return (conditioning, mask)
|
||||
|
||||
|
||||
class ToIPAdapterPipe:
|
||||
@@ -212,8 +179,7 @@ class ToIPAdapterPipe:
|
||||
|
||||
CATEGORY = "InspirePack/Util"
|
||||
|
||||
@staticmethod
|
||||
def doit(ipadapter, model, clip_vision, insightface=None):
|
||||
def doit(self, ipadapter, model, clip_vision, insightface=None):
|
||||
pipe = ipadapter, model, clip_vision, insightface, lambda x: x
|
||||
|
||||
return (pipe,)
|
||||
@@ -261,7 +227,7 @@ class IPAdapterConditioning:
|
||||
if 'IPAdapterAdvanced' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/cubiq/ComfyUI_IPAdapter_plus',
|
||||
"To use 'Regional IPAdapter' node, 'ComfyUI IPAdapter Plus' extension is required.")
|
||||
raise Exception("[ERROR] To use IPAdapterModelHelper, you need to install 'ComfyUI IPAdapter Plus'")
|
||||
raise Exception(f"[ERROR] To use IPAdapterModelHelper, you need to install 'ComfyUI IPAdapter Plus'")
|
||||
|
||||
if self.embeds is None:
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['IPAdapterAdvanced']
|
||||
@@ -278,22 +244,6 @@ class IPAdapterConditioning:
|
||||
return model
|
||||
|
||||
|
||||
IPADAPTER_WEIGHT_TYPES_CACHE = None
|
||||
|
||||
|
||||
def IPADAPTER_WEIGHT_TYPES():
|
||||
global IPADAPTER_WEIGHT_TYPES_CACHE
|
||||
|
||||
if IPADAPTER_WEIGHT_TYPES_CACHE is None:
|
||||
try:
|
||||
IPADAPTER_WEIGHT_TYPES_CACHE = nodes.NODE_CLASS_MAPPINGS['IPAdapterAdvanced']().INPUT_TYPES()['required']['weight_type'][0]
|
||||
except Exception:
|
||||
logging.error("[Inspire Pack] IPAdapterPlus is not installed.")
|
||||
IPADAPTER_WEIGHT_TYPES_CACHE = ["IPAdapterPlus is not installed"]
|
||||
|
||||
return IPADAPTER_WEIGHT_TYPES_CACHE
|
||||
|
||||
|
||||
class RegionalIPAdapterMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -304,7 +254,7 @@ class RegionalIPAdapterMask:
|
||||
"image": ("IMAGE",),
|
||||
"weight": ("FLOAT", {"default": 0.7, "min": -1, "max": 3, "step": 0.05}),
|
||||
"noise": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"weight_type": (IPADAPTER_WEIGHT_TYPES(), ),
|
||||
"weight_type": (["original", "linear", "channel penalty"],),
|
||||
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"unfold_batch": ("BOOLEAN", {"default": False}),
|
||||
@@ -322,8 +272,7 @@ class RegionalIPAdapterMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(mask, image, weight, noise, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, faceid_v2=False, weight_v2=False, combine_embeds="concat", neg_image=None):
|
||||
def doit(self, mask, image, weight, noise, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, faceid_v2=False, weight_v2=False, combine_embeds="concat", neg_image=None):
|
||||
cond = IPAdapterConditioning(mask, weight, weight_type, noise=noise, image=image, neg_image=neg_image, start_at=start_at, end_at=end_at, unfold_batch=unfold_batch, weight_v2=weight_v2, combine_embeds=combine_embeds)
|
||||
return (cond, )
|
||||
|
||||
@@ -335,10 +284,11 @@ class RegionalIPAdapterColorMask:
|
||||
"required": {
|
||||
"color_mask": ("IMAGE",),
|
||||
"mask_color": ("STRING", {"multiline": False, "default": "#FFFFFF"}),
|
||||
|
||||
"image": ("IMAGE",),
|
||||
"weight": ("FLOAT", {"default": 0.7, "min": -1, "max": 3, "step": 0.05}),
|
||||
"noise": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"weight_type": (IPADAPTER_WEIGHT_TYPES(), ),
|
||||
"weight_type": (["original", "linear", "channel penalty"], ),
|
||||
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"unfold_batch": ("BOOLEAN", {"default": False}),
|
||||
@@ -356,8 +306,7 @@ class RegionalIPAdapterColorMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(color_mask, mask_color, image, weight, noise, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, faceid_v2=False, weight_v2=False, combine_embeds="concat", neg_image=None):
|
||||
def doit(self, color_mask, mask_color, image, weight, noise, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, faceid_v2=False, weight_v2=False, combine_embeds="concat", neg_image=None):
|
||||
mask = color_to_mask(color_mask, mask_color)
|
||||
cond = IPAdapterConditioning(mask, weight, weight_type, noise=noise, image=image, neg_image=neg_image, start_at=start_at, end_at=end_at, unfold_batch=unfold_batch, weight_v2=weight_v2, combine_embeds=combine_embeds)
|
||||
return (cond, mask)
|
||||
@@ -372,7 +321,7 @@ class RegionalIPAdapterEncodedMask:
|
||||
|
||||
"embeds": ("EMBEDS",),
|
||||
"weight": ("FLOAT", {"default": 0.7, "min": -1, "max": 3, "step": 0.05}),
|
||||
"weight_type": (IPADAPTER_WEIGHT_TYPES(), ),
|
||||
"weight_type": (["original", "linear", "channel penalty"],),
|
||||
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"unfold_batch": ("BOOLEAN", {"default": False}),
|
||||
@@ -387,8 +336,7 @@ class RegionalIPAdapterEncodedMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(mask, embeds, weight, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, neg_embeds=None):
|
||||
def doit(self, mask, embeds, weight, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, neg_embeds=None):
|
||||
cond = IPAdapterConditioning(mask, weight, weight_type, embeds=embeds, start_at=start_at, end_at=end_at, unfold_batch=unfold_batch, neg_embeds=neg_embeds)
|
||||
return (cond, )
|
||||
|
||||
@@ -403,7 +351,7 @@ class RegionalIPAdapterEncodedColorMask:
|
||||
|
||||
"embeds": ("EMBEDS",),
|
||||
"weight": ("FLOAT", {"default": 0.7, "min": -1, "max": 3, "step": 0.05}),
|
||||
"weight_type": (IPADAPTER_WEIGHT_TYPES(), ),
|
||||
"weight_type": (["original", "linear", "channel penalty"],),
|
||||
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"unfold_batch": ("BOOLEAN", {"default": False}),
|
||||
@@ -418,8 +366,7 @@ class RegionalIPAdapterEncodedColorMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(color_mask, mask_color, embeds, weight, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, neg_embeds=None):
|
||||
def doit(self, color_mask, mask_color, embeds, weight, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, neg_embeds=None):
|
||||
mask = color_to_mask(color_mask, mask_color)
|
||||
cond = IPAdapterConditioning(mask, weight, weight_type, embeds=embeds, start_at=start_at, end_at=end_at, unfold_batch=unfold_batch, neg_embeds=neg_embeds)
|
||||
return (cond, mask)
|
||||
@@ -439,8 +386,7 @@ class ApplyRegionalIPAdapters:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(**kwargs):
|
||||
def doit(self, **kwargs):
|
||||
ipadapter_pipe = kwargs['ipadapter_pipe']
|
||||
ipadapter, model, clip_vision, insightface, lora_loader = ipadapter_pipe
|
||||
|
||||
@@ -460,24 +406,21 @@ class RegionalSeedExplorerMask:
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
|
||||
"noise": ("NOISE_IMAGE",),
|
||||
"noise": ("NOISE",),
|
||||
"seed_prompt": ("STRING", {"multiline": True, "dynamicPrompts": False, "pysssss.autocomplete": False}),
|
||||
"enable_additional": ("BOOLEAN", {"default": True, "label_on": "true", "label_off": "false"}),
|
||||
"additional_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"additional_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
},
|
||||
"optional":
|
||||
{"variation_method": (["linear", "slerp"],), }
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE_IMAGE",)
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(mask, noise, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode, variation_method='linear'):
|
||||
def doit(self, mask, noise, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
noise_device = "cpu" if noise_mode == "CPU" else device
|
||||
|
||||
@@ -499,9 +442,9 @@ class RegionalSeedExplorerMask:
|
||||
if enable_additional:
|
||||
items.append((additional_seed, additional_strength))
|
||||
|
||||
noise = prompt_support.SeedExplorer.apply_variation(noise, items, noise_device, mask, variation_method=variation_method)
|
||||
noise = prompt_support.SeedExplorer.apply_variation(noise, items, noise_device, mask)
|
||||
except Exception:
|
||||
logging.error("[Inspire Pack] IGNORED: RegionalSeedExplorerColorMask is failed.")
|
||||
print(f"[ERROR] IGNORED: RegionalSeedExplorerColorMask is failed.")
|
||||
traceback.print_exc()
|
||||
|
||||
noise = noise.cpu()
|
||||
@@ -517,24 +460,21 @@ class RegionalSeedExplorerColorMask:
|
||||
"color_mask": ("IMAGE",),
|
||||
"mask_color": ("STRING", {"multiline": False, "default": "#FFFFFF"}),
|
||||
|
||||
"noise": ("NOISE_IMAGE",),
|
||||
"noise": ("NOISE",),
|
||||
"seed_prompt": ("STRING", {"multiline": True, "dynamicPrompts": False, "pysssss.autocomplete": False}),
|
||||
"enable_additional": ("BOOLEAN", {"default": True, "label_on": "true", "label_off": "false"}),
|
||||
"additional_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"additional_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
},
|
||||
"optional":
|
||||
{"variation_method": (["linear", "slerp"],), }
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE_IMAGE", "MASK")
|
||||
RETURN_TYPES = ("NOISE", "MASK")
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(color_mask, mask_color, noise, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode, variation_method='linear'):
|
||||
def doit(self, color_mask, mask_color, noise, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
noise_device = "cpu" if noise_mode == "CPU" else device
|
||||
|
||||
@@ -557,9 +497,9 @@ class RegionalSeedExplorerColorMask:
|
||||
if enable_additional:
|
||||
items.append((additional_seed, additional_strength))
|
||||
|
||||
noise = prompt_support.SeedExplorer.apply_variation(noise, items, noise_device, mask, variation_method=variation_method)
|
||||
noise = prompt_support.SeedExplorer.apply_variation(noise, items, noise_device, mask)
|
||||
except Exception:
|
||||
logging.error("[Inspire Pack] IGNORED: RegionalSeedExplorerColorMask is failed.")
|
||||
print(f"[ERROR] IGNORED: RegionalSeedExplorerColorMask is failed.")
|
||||
traceback.print_exc()
|
||||
|
||||
color_mask.cpu()
|
||||
@@ -568,100 +508,6 @@ class RegionalSeedExplorerColorMask:
|
||||
return (noise, original_mask)
|
||||
|
||||
|
||||
class ColorMaskToDepthMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"color_mask": ("IMAGE",),
|
||||
"spec": ("STRING", {"multiline": True, "default": "#FF0000:1.0\n#000000:1.0"}),
|
||||
"base_value": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0}),
|
||||
"dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}),
|
||||
"flatten_method": (["override", "sum", "max"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, color_mask, spec, base_value, dilation, flatten_method):
|
||||
specs = spec.split('\n')
|
||||
pat = re.compile("(?P<color_code>#[A-F0-9]+):(?P<cfg>[0-9]+(.[0-9]*)?)")
|
||||
|
||||
masks = [torch.ones((1, color_mask.shape[1], color_mask.shape[2])) * base_value]
|
||||
for x in specs:
|
||||
match = pat.match(x)
|
||||
if match:
|
||||
mask = color_to_mask(color_mask=color_mask, mask_color=match['color_code']) * float(match['cfg'])
|
||||
mask = utils.dilate_mask(mask, dilation)
|
||||
masks.append(mask)
|
||||
|
||||
if masks:
|
||||
masks = torch.cat(masks, dim=0)
|
||||
if flatten_method == 'override':
|
||||
masks = utils.flatten_non_zero_override(masks)
|
||||
elif flatten_method == 'max':
|
||||
masks = torch.max(masks, dim=0)[0]
|
||||
else: # flatten_method == 'sum':
|
||||
masks = torch.sum(masks, dim=0)
|
||||
|
||||
masks = torch.clamp(masks, min=0.0, max=1.0)
|
||||
masks = masks.unsqueeze(0)
|
||||
else:
|
||||
masks = torch.tensor([])
|
||||
|
||||
return (masks, )
|
||||
|
||||
|
||||
class RegionalCFG:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"model": ("MODEL",),
|
||||
"mask": ("MASK",),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(model, mask):
|
||||
if len(mask.shape) == 2:
|
||||
mask = mask.unsqueeze(0).unsqueeze(0)
|
||||
elif len(mask.shape) == 3:
|
||||
mask = mask.unsqueeze(0)
|
||||
|
||||
size = None
|
||||
|
||||
def regional_cfg(args):
|
||||
nonlocal mask
|
||||
nonlocal size
|
||||
|
||||
x = args['input']
|
||||
|
||||
if mask.device != x.device:
|
||||
mask = mask.to(x.device)
|
||||
|
||||
if size != (x.shape[2], x.shape[3]):
|
||||
size = (x.shape[2], x.shape[3])
|
||||
mask = torch.nn.functional.interpolate(mask, size=size, mode='bilinear', align_corners=False)
|
||||
|
||||
cond_pred = args["cond_denoised"]
|
||||
uncond_pred = args["uncond_denoised"]
|
||||
cond_scale = args["cond_scale"]
|
||||
|
||||
cfg_result = uncond_pred + (cond_pred - uncond_pred) * cond_scale * mask
|
||||
|
||||
return x - cfg_result
|
||||
|
||||
m = model.clone()
|
||||
m.set_model_sampler_cfg_function(regional_cfg)
|
||||
return (m,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RegionalPromptSimple //Inspire": RegionalPromptSimple,
|
||||
"RegionalPromptColorMask //Inspire": RegionalPromptColorMask,
|
||||
@@ -676,8 +522,6 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ToIPAdapterPipe //Inspire": ToIPAdapterPipe,
|
||||
"FromIPAdapterPipe //Inspire": FromIPAdapterPipe,
|
||||
"ApplyRegionalIPAdapters //Inspire": ApplyRegionalIPAdapters,
|
||||
"RegionalCFG //Inspire": RegionalCFG,
|
||||
"ColorMaskToDepthMask //Inspire": ColorMaskToDepthMask,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -693,7 +537,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RegionalSeedExplorerColorMask //Inspire": "Regional Seed Explorer By Color Mask (Inspire)",
|
||||
"ToIPAdapterPipe //Inspire": "ToIPAdapterPipe (Inspire)",
|
||||
"FromIPAdapterPipe //Inspire": "FromIPAdapterPipe (Inspire)",
|
||||
"ApplyRegionalIPAdapters //Inspire": "Apply Regional IPAdapters (Inspire)",
|
||||
"RegionalCFG //Inspire": "Regional CFG (Inspire)",
|
||||
"ColorMaskToDepthMask //Inspire": "Color Mask To Depth Mask (Inspire)",
|
||||
"ApplyRegionalIPAdapters //Inspire": "Apply Regional IPAdapters (Inspire)"
|
||||
}
|
||||
|
||||
+51
-277
@@ -2,33 +2,27 @@ import torch
|
||||
from . import a1111_compat
|
||||
import comfy
|
||||
from .libs import common
|
||||
from comfy.samplers import CFGGuider
|
||||
from comfy_extras.nodes_perpneg import Guider_PerpNeg
|
||||
import math
|
||||
from comfy import model_management
|
||||
|
||||
|
||||
class KSampler_progress(a1111_compat.KSampler_inspire):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"model": ("MODEL",),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (common.SCHEDULERS, ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"latent_image": ("LATENT", ),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (a1111_compat.supported_noise_modes,),
|
||||
"interval": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"omit_start_latent": ("BOOLEAN", {"default": True, "label_on": "True", "label_off": "False"}),
|
||||
"omit_final_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
},
|
||||
"optional": {
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
return {"required":
|
||||
{"model": ("MODEL",),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (common.SCHEDULERS, ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"latent_image": ("LATENT", ),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"interval": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"omit_start_latent": ("BOOLEAN", {"default": True, "label_on": "True", "label_off": "False"}),
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "InspirePack/analysis"
|
||||
@@ -36,29 +30,22 @@ class KSampler_progress(a1111_compat.KSampler_inspire):
|
||||
RETURN_TYPES = ("LATENT", "LATENT")
|
||||
RETURN_NAMES = ("latent", "progress_latent")
|
||||
|
||||
@staticmethod
|
||||
def doit(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode,
|
||||
interval, omit_start_latent, omit_final_latent, scheduler_func_opt=None):
|
||||
def doit(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, interval, omit_start_latent):
|
||||
adv_steps = int(steps / denoise)
|
||||
|
||||
if omit_start_latent:
|
||||
result = []
|
||||
else:
|
||||
result = [comfy.sample.fix_empty_latent_channels(model, latent_image['samples']).cpu()]
|
||||
result = [latent_image['samples']]
|
||||
|
||||
result = []
|
||||
|
||||
def progress_callback(step, x0, x, total_steps):
|
||||
if (total_steps-1) != step and step % interval != 0:
|
||||
return
|
||||
x0 = model.model.process_latent_out(x0)
|
||||
x0 = x0.to(model_management.intermediate_device())
|
||||
result.append(x0)
|
||||
|
||||
x = model.model.process_latent_out(x)
|
||||
x = x.cpu()
|
||||
result.append(x)
|
||||
|
||||
latent_image, noise = a1111_compat.KSamplerAdvanced_inspire.sample(model, True, seed, adv_steps, cfg, sampler_name, scheduler, positive, negative, latent_image, (adv_steps-steps),
|
||||
adv_steps, noise_mode, False, callback=progress_callback, scheduler_func_opt=scheduler_func_opt)
|
||||
|
||||
if not omit_final_latent:
|
||||
result.append(latent_image['samples'].cpu())
|
||||
latent_image, noise = a1111_compat.KSamplerAdvanced_inspire.sample(model, True, seed, adv_steps, cfg, sampler_name, scheduler, positive, negative, latent_image, (adv_steps-steps), adv_steps, noise_mode, False, callback=progress_callback)
|
||||
|
||||
if len(result) > 0:
|
||||
result = torch.cat(result)
|
||||
@@ -72,29 +59,25 @@ class KSampler_progress(a1111_compat.KSampler_inspire):
|
||||
class KSamplerAdvanced_progress(a1111_compat.KSamplerAdvanced_inspire):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"model": ("MODEL",),
|
||||
"add_noise": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (common.SCHEDULERS, ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"latent_image": ("LATENT", ),
|
||||
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
||||
"end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}),
|
||||
"noise_mode": (a1111_compat.supported_noise_modes,),
|
||||
"return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}),
|
||||
"interval": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"omit_start_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
"omit_final_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
},
|
||||
"optional": {
|
||||
"prev_progress_latent_opt": ("LATENT",),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
return {"required":
|
||||
{"model": ("MODEL",),
|
||||
"add_noise": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (common.SCHEDULERS, ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"latent_image": ("LATENT", ),
|
||||
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
||||
"end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}),
|
||||
"interval": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"omit_start_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
},
|
||||
"optional": {"prev_progress_latent_opt": ("LATENT",), }
|
||||
}
|
||||
|
||||
FUNCTION = "doit"
|
||||
@@ -104,28 +87,22 @@ class KSamplerAdvanced_progress(a1111_compat.KSamplerAdvanced_inspire):
|
||||
RETURN_TYPES = ("LATENT", "LATENT")
|
||||
RETURN_NAMES = ("latent", "progress_latent")
|
||||
|
||||
def doit(self, model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image,
|
||||
start_at_step, end_at_step, noise_mode, return_with_leftover_noise, interval, omit_start_latent, omit_final_latent,
|
||||
prev_progress_latent_opt=None, scheduler_func_opt=None):
|
||||
|
||||
def doit(self, model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step,
|
||||
noise_mode, return_with_leftover_noise, interval, omit_start_latent, prev_progress_latent_opt=None):
|
||||
if omit_start_latent:
|
||||
result = []
|
||||
else:
|
||||
result = [latent_image['samples']]
|
||||
|
||||
def progress_callback(step, x0, x, total_steps):
|
||||
if (total_steps-1) != step and step % interval != 0:
|
||||
return
|
||||
result = []
|
||||
|
||||
x = model.model.process_latent_out(x)
|
||||
x = x.cpu()
|
||||
result.append(x)
|
||||
def progress_callback(step, x0, x, total_steps):
|
||||
x0 = model.model.process_latent_out(x0)
|
||||
x0 = x0.to(model_management.intermediate_device())
|
||||
result.append(x0)
|
||||
|
||||
latent_image, noise = a1111_compat.KSamplerAdvanced_inspire.sample(model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step,
|
||||
noise_mode, return_with_leftover_noise, callback=progress_callback, scheduler_func_opt=scheduler_func_opt)
|
||||
|
||||
if not omit_final_latent:
|
||||
result.append(latent_image['samples'].cpu())
|
||||
noise_mode, False, callback=progress_callback)
|
||||
|
||||
if len(result) > 0:
|
||||
result = torch.cat(result)
|
||||
@@ -139,215 +116,12 @@ class KSamplerAdvanced_progress(a1111_compat.KSamplerAdvanced_inspire):
|
||||
return latent_image, result
|
||||
|
||||
|
||||
def exponential_interpolation(from_cfg, to_cfg, i, steps):
|
||||
if i == steps-1:
|
||||
return to_cfg
|
||||
|
||||
if from_cfg == to_cfg:
|
||||
return from_cfg
|
||||
|
||||
if from_cfg == 0:
|
||||
return to_cfg * (1 - math.exp(-5 * i / steps)) / (1 - math.exp(-5))
|
||||
elif to_cfg == 0:
|
||||
return from_cfg * (math.exp(-5 * i / steps) - math.exp(-5)) / (1 - math.exp(-5))
|
||||
else:
|
||||
log_from = math.log(from_cfg)
|
||||
log_to = math.log(to_cfg)
|
||||
log_value = log_from + (log_to - log_from) * i / steps
|
||||
return math.exp(log_value)
|
||||
|
||||
|
||||
def logarithmic_interpolation(from_cfg, to_cfg, i, steps):
|
||||
if i == 0:
|
||||
return from_cfg
|
||||
|
||||
if i == steps-1:
|
||||
return to_cfg
|
||||
|
||||
log_i = math.log(i + 1)
|
||||
log_steps = math.log(steps + 1)
|
||||
|
||||
t = log_i / log_steps
|
||||
|
||||
return from_cfg + (to_cfg - from_cfg) * t
|
||||
|
||||
|
||||
def cosine_interpolation(from_cfg, to_cfg, i, steps):
|
||||
if (i == 0) or (i == steps-1):
|
||||
return from_cfg
|
||||
|
||||
t = (1.0 + math.cos(math.pi*2*(i/steps))) / 2
|
||||
|
||||
return from_cfg + (to_cfg - from_cfg) * t
|
||||
|
||||
|
||||
class Guider_scheduled(CFGGuider):
|
||||
def __init__(self, model_patcher, sigmas, from_cfg, to_cfg, schedule):
|
||||
super().__init__(model_patcher)
|
||||
self.default_cfg = self.cfg
|
||||
self.sigmas = sigmas
|
||||
self.cfg_sigmas = None
|
||||
self.cfg_sigmas_i = None
|
||||
self.from_cfg = from_cfg
|
||||
self.to_cfg = to_cfg
|
||||
self.schedule = schedule
|
||||
self.last_i = 0
|
||||
self.renew_cfg_sigmas()
|
||||
|
||||
def set_cfg(self, cfg):
|
||||
self.default_cfg = cfg
|
||||
self.renew_cfg_sigmas()
|
||||
|
||||
def renew_cfg_sigmas(self):
|
||||
self.cfg_sigmas = {}
|
||||
self.cfg_sigmas_i = {}
|
||||
i = 0
|
||||
steps = len(self.sigmas) - 1
|
||||
for x in self.sigmas:
|
||||
k = float(x)
|
||||
delta = self.to_cfg - self.from_cfg
|
||||
if self.schedule == 'exp':
|
||||
self.cfg_sigmas[k] = exponential_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'log':
|
||||
self.cfg_sigmas[k] = logarithmic_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'cos':
|
||||
self.cfg_sigmas[k] = cosine_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
else:
|
||||
self.cfg_sigmas[k] = self.from_cfg + delta * i / steps, i
|
||||
|
||||
self.cfg_sigmas_i[i] = self.cfg_sigmas[k]
|
||||
i += 1
|
||||
|
||||
def predict_noise(self, x, timestep, model_options={}, seed=None):
|
||||
k = float(timestep[0])
|
||||
|
||||
v = self.cfg_sigmas.get(k)
|
||||
if v is None:
|
||||
# fallback
|
||||
v = self.cfg_sigmas_i[self.last_i+1]
|
||||
self.cfg_sigmas[k] = v
|
||||
|
||||
self.last_i = v[1]
|
||||
self.cfg = v[0]
|
||||
|
||||
return super().predict_noise(x, timestep, model_options, seed)
|
||||
|
||||
|
||||
class Guider_PerpNeg_scheduled(Guider_PerpNeg):
|
||||
def __init__(self, model_patcher, sigmas, from_cfg, to_cfg, schedule, neg_scale):
|
||||
super().__init__(model_patcher)
|
||||
self.default_cfg = self.cfg
|
||||
self.sigmas = sigmas
|
||||
self.cfg_sigmas = None
|
||||
self.cfg_sigmas_i = None
|
||||
self.from_cfg = from_cfg
|
||||
self.to_cfg = to_cfg
|
||||
self.schedule = schedule
|
||||
self.neg_scale = neg_scale
|
||||
self.last_i = 0
|
||||
self.renew_cfg_sigmas()
|
||||
|
||||
def set_cfg(self, cfg):
|
||||
self.default_cfg = cfg
|
||||
self.renew_cfg_sigmas()
|
||||
|
||||
def renew_cfg_sigmas(self):
|
||||
self.cfg_sigmas = {}
|
||||
self.cfg_sigmas_i = {}
|
||||
i = 0
|
||||
steps = len(self.sigmas) - 1
|
||||
for x in self.sigmas:
|
||||
k = float(x)
|
||||
delta = self.to_cfg - self.from_cfg
|
||||
if self.schedule == 'exp':
|
||||
self.cfg_sigmas[k] = exponential_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'log':
|
||||
self.cfg_sigmas[k] = logarithmic_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'cos':
|
||||
self.cfg_sigmas[k] = cosine_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
else:
|
||||
self.cfg_sigmas[k] = self.from_cfg + delta * i / steps, i
|
||||
|
||||
self.cfg_sigmas_i[i] = self.cfg_sigmas[k]
|
||||
i += 1
|
||||
|
||||
def predict_noise(self, x, timestep, model_options={}, seed=None):
|
||||
k = float(timestep[0])
|
||||
|
||||
v = self.cfg_sigmas.get(k)
|
||||
if v is None:
|
||||
# fallback
|
||||
v = self.cfg_sigmas_i[self.last_i+1]
|
||||
self.cfg_sigmas[k] = v
|
||||
|
||||
self.last_i = v[1]
|
||||
self.cfg = v[0]
|
||||
|
||||
return super().predict_noise(x, timestep, model_options, seed)
|
||||
|
||||
|
||||
class ScheduledCFGGuider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"model": ("MODEL", ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"sigmas": ("SIGMAS", ),
|
||||
"from_cfg": ("FLOAT", {"default": 6.5, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"to_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"schedule": (["linear", "log", "exp", "cos"], {'default': 'log'})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("GUIDER", "SIGMAS")
|
||||
|
||||
FUNCTION = "get_guider"
|
||||
CATEGORY = "sampling/custom_sampling/guiders"
|
||||
|
||||
def get_guider(self, model, positive, negative, sigmas, from_cfg, to_cfg, schedule):
|
||||
guider = Guider_scheduled(model, sigmas, from_cfg, to_cfg, schedule)
|
||||
guider.set_conds(positive, negative)
|
||||
return guider, sigmas
|
||||
|
||||
|
||||
class ScheduledPerpNegCFGGuider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"model": ("MODEL", ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"empty_conditioning": ("CONDITIONING", ),
|
||||
"neg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
|
||||
"sigmas": ("SIGMAS", ),
|
||||
"from_cfg": ("FLOAT", {"default": 6.5, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"to_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"schedule": (["linear", "log", "exp", "cos"], {'default': 'log'})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("GUIDER", "SIGMAS")
|
||||
|
||||
FUNCTION = "get_guider"
|
||||
CATEGORY = "sampling/custom_sampling/guiders"
|
||||
|
||||
def get_guider(self, model, positive, negative, empty_conditioning, neg_scale, sigmas, from_cfg, to_cfg, schedule):
|
||||
guider = Guider_PerpNeg_scheduled(model, sigmas, from_cfg, to_cfg, schedule, neg_scale)
|
||||
guider.set_conds(positive, negative, empty_conditioning)
|
||||
return guider, sigmas
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"KSamplerProgress //Inspire": KSampler_progress,
|
||||
"KSamplerAdvancedProgress //Inspire": KSamplerAdvanced_progress,
|
||||
"ScheduledCFGGuider //Inspire": ScheduledCFGGuider,
|
||||
"ScheduledPerpNegCFGGuider //Inspire": ScheduledPerpNegCFGGuider
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"KSamplerProgress //Inspire": "KSampler Progress (Inspire)",
|
||||
"KSamplerAdvancedProgress //Inspire": "KSampler Advanced Progress (Inspire)",
|
||||
"ScheduledCFGGuider //Inspire": "Scheduled CFGGuider (Inspire)",
|
||||
"ScheduledPerpNegCFGGuider //Inspire": "Scheduled PerpNeg CFGGuider (Inspire)"
|
||||
}
|
||||
|
||||
+20
-40
@@ -2,7 +2,7 @@ import nodes
|
||||
import numpy as np
|
||||
import torch
|
||||
from .libs import utils
|
||||
import logging
|
||||
|
||||
|
||||
def normalize_size_base_64(w, h):
|
||||
short_side = min(w, h)
|
||||
@@ -28,21 +28,18 @@ class MediaPipeFaceMeshDetector:
|
||||
if 'MediaPipe-FaceMeshPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'MediaPipeFaceMeshDetector' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use MediaPipeFaceMeshDetector, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use MediaPipeFaceMeshDetector, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
if 'MediaPipeFaceMeshToSEGS' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/ltdrdata/ComfyUI-Impact-Pack',
|
||||
"To use 'MediaPipeFaceMeshDetector' node, 'Impact Pack' extension is required.")
|
||||
raise Exception("[ERROR] To use MediaPipeFaceMeshDetector, you need to install 'ComfyUI-Impact-Pack'")
|
||||
raise Exception(f"[ERROR] To use MediaPipeFaceMeshDetector, you need to install 'ComfyUI-Impact-Pack'")
|
||||
|
||||
pre_obj = nodes.NODE_CLASS_MAPPINGS['MediaPipe-FaceMeshPreprocessor']
|
||||
seg_obj = nodes.NODE_CLASS_MAPPINGS['MediaPipeFaceMeshToSEGS']
|
||||
|
||||
resolution = normalize_size_base_64(image.shape[2], image.shape[1])
|
||||
facemesh_image = pre_obj().detect(image, self.max_faces, threshold, resolution=resolution)[0]
|
||||
|
||||
facemesh_image = nodes.ImageScale().upscale(facemesh_image, "bilinear", image.shape[2], image.shape[1], "disabled")[0]
|
||||
|
||||
segs = seg_obj().doit(facemesh_image, crop_factor, not self.is_segm, crop_min_size, drop_size, dilation,
|
||||
self.face, self.mouth, self.left_eyebrow, self.left_eye, self.left_pupil,
|
||||
self.right_eyebrow, self.right_eye, self.right_pupil)[0]
|
||||
@@ -63,7 +60,7 @@ class MediaPipe_FaceMesh_Preprocessor_wrapper:
|
||||
if 'MediaPipe-FaceMeshPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'MediaPipe_FaceMesh_Preprocessor_Provider_for_SEGS' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use MediaPipe_FaceMesh_Preprocessor_Provider_for_SEGS, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use MediaPipe_FaceMesh_Preprocessor_Provider_for_SEGS, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
if self.upscale_factor != 1.0:
|
||||
image = nodes.ImageScaleBy().upscale(image, 'bilinear', self.upscale_factor)[0]
|
||||
@@ -78,7 +75,7 @@ class AnimeLineArt_Preprocessor_wrapper:
|
||||
if 'AnimeLineArtPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'AnimeLineArt_Preprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use AnimeLineArt_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use AnimeLineArt_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['AnimeLineArtPreprocessor']()
|
||||
resolution = normalize_size_base_64(image.shape[2], image.shape[1])
|
||||
@@ -90,7 +87,7 @@ class Manga2Anime_LineArt_Preprocessor_wrapper:
|
||||
if 'Manga2Anime_LineArt_Preprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'Manga2Anime_LineArt_Preprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use Manga2Anime_LineArt_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use Manga2Anime_LineArt_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['Manga2Anime_LineArt_Preprocessor']()
|
||||
resolution = normalize_size_base_64(image.shape[2], image.shape[1])
|
||||
@@ -102,7 +99,7 @@ class Color_Preprocessor_wrapper:
|
||||
if 'ColorPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'Color_Preprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use Color_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use Color_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['ColorPreprocessor']()
|
||||
resolution = normalize_size_base_64(image.shape[2], image.shape[1])
|
||||
@@ -110,29 +107,17 @@ class Color_Preprocessor_wrapper:
|
||||
|
||||
|
||||
class InpaintPreprocessor_wrapper:
|
||||
def __init__(self, black_pixel_for_xinsir_cn):
|
||||
self.black_pixel_for_xinsir_cn = black_pixel_for_xinsir_cn
|
||||
|
||||
def apply(self, image, mask=None):
|
||||
if 'InpaintPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'InpaintPreprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use InpaintPreprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use InpaintPreprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['InpaintPreprocessor']()
|
||||
if mask is None:
|
||||
mask = torch.ones((image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu").unsqueeze(0)
|
||||
|
||||
try:
|
||||
res = obj.preprocess(image, mask, black_pixel_for_xinsir_cn=self.black_pixel_for_xinsir_cn)[0]
|
||||
except Exception as e:
|
||||
if self.black_pixel_for_xinsir_cn:
|
||||
raise e
|
||||
else:
|
||||
res = obj.preprocess(image, mask)[0]
|
||||
logging.warning("[Inspire Pack] Installed 'ComfyUI's ControlNet Auxiliary Preprocessors.' is outdated.")
|
||||
|
||||
return res
|
||||
return obj.preprocess(image, mask)[0]
|
||||
|
||||
|
||||
class TilePreprocessor_wrapper:
|
||||
@@ -143,7 +128,7 @@ class TilePreprocessor_wrapper:
|
||||
if 'TilePreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'TilePreprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use TilePreprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use TilePreprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['TilePreprocessor']()
|
||||
resolution = normalize_size_base_64(image.shape[2], image.shape[1])
|
||||
@@ -155,7 +140,7 @@ class MeshGraphormerDepthMapPreprocessorProvider_wrapper:
|
||||
if 'MeshGraphormer-DepthMapPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'MeshGraphormerDepthMapPreprocessorProvider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use MeshGraphormerDepthMapPreprocessorProvider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use MeshGraphormerDepthMapPreprocessorProvider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['MeshGraphormer-DepthMapPreprocessor']()
|
||||
resolution = normalize_size_base_64(image.shape[2], image.shape[1])
|
||||
@@ -170,7 +155,7 @@ class LineArt_Preprocessor_wrapper:
|
||||
if 'LineArtPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'LineArt_Preprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use LineArt_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use LineArt_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
coarse = 'enable' if self.coarse else 'disable'
|
||||
|
||||
@@ -190,7 +175,7 @@ class OpenPose_Preprocessor_wrapper:
|
||||
if 'OpenposePreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'OpenPose_Preprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use OpenPose_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use OpenPose_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
detect_hand = 'enable' if self.detect_hand else 'disable'
|
||||
detect_body = 'enable' if self.detect_body else 'disable'
|
||||
@@ -217,7 +202,7 @@ class DWPreprocessor_wrapper:
|
||||
if 'DWPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'DWPreprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use DWPreprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use DWPreprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
detect_hand = 'enable' if self.detect_hand else 'disable'
|
||||
detect_body = 'enable' if self.detect_body else 'disable'
|
||||
@@ -241,7 +226,7 @@ class LeReS_DepthMap_Preprocessor_wrapper:
|
||||
if 'LeReS-DepthMapPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'LeReS_DepthMap_Preprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use LeReS_DepthMap_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use LeReS_DepthMap_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
boost = 'enable' if self.boost else 'disable'
|
||||
|
||||
@@ -259,7 +244,7 @@ class MiDaS_DepthMap_Preprocessor_wrapper:
|
||||
if 'MiDaS-DepthMapPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'MiDaS_DepthMap_Preprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use MiDaS_DepthMap_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use MiDaS_DepthMap_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['MiDaS-DepthMapPreprocessor']()
|
||||
resolution = normalize_size_base_64(image.shape[2], image.shape[1])
|
||||
@@ -271,7 +256,7 @@ class Zoe_DepthMap_Preprocessor_wrapper:
|
||||
if 'Zoe-DepthMapPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
"To use 'Zoe_DepthMap_Preprocessor_Provider' node, 'ComfyUI's ControlNet Auxiliary Preprocessors.' extension is required.")
|
||||
raise Exception("[ERROR] To use Zoe_DepthMap_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
raise Exception(f"[ERROR] To use Zoe_DepthMap_Preprocessor_Provider, you need to install 'ComfyUI's ControlNet Auxiliary Preprocessors.'")
|
||||
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['Zoe-DepthMapPreprocessor']()
|
||||
resolution = normalize_size_base_64(image.shape[2], image.shape[1])
|
||||
@@ -559,19 +544,14 @@ class Color_Preprocessor_Provider_for_SEGS:
|
||||
class InpaintPreprocessor_Provider_for_SEGS:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {},
|
||||
"optional": {
|
||||
"black_pixel_for_xinsir_cn": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}),
|
||||
}
|
||||
}
|
||||
return {"required": {}}
|
||||
RETURN_TYPES = ("SEGS_PREPROCESSOR",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/SEGS/ControlNet"
|
||||
|
||||
def doit(self, black_pixel_for_xinsir_cn=False):
|
||||
obj = InpaintPreprocessor_wrapper(black_pixel_for_xinsir_cn)
|
||||
def doit(self):
|
||||
obj = InpaintPreprocessor_wrapper()
|
||||
return (obj, )
|
||||
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import colorsys
|
||||
|
||||
|
||||
def hex_to_hsv(hex_color):
|
||||
hex_color = hex_color.lstrip('#')
|
||||
r, g, b = tuple(int(hex_color[i:i+2], 16) / 255.0 for i in (0, 2, 4))
|
||||
|
||||
h, s, v = colorsys.rgb_to_hsv(r, g, b)
|
||||
|
||||
hue = h * 360
|
||||
|
||||
saturation = s
|
||||
value = v
|
||||
|
||||
return hue, saturation, value
|
||||
|
||||
|
||||
class RGB_HexToHSV:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"rgb_hex": ("STRING", {"defaultInput": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT")
|
||||
RETURN_NAMES = ("hue", "saturation", "value")
|
||||
FUNCTION = "doit"
|
||||
CATEGORY = "InspirePack/Util"
|
||||
|
||||
def doit(self, rgb_hex):
|
||||
return hex_to_hsv(rgb_hex)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RGB_HexToHSV //Inspire": RGB_HexToHSV,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RGB_HexToHSV //Inspire": "RGB Hex To HSV (Inspire)",
|
||||
}
|
||||
+1
-1
@@ -44,7 +44,7 @@ app.registerExtension({
|
||||
|
||||
Object.defineProperty(node, 'imgs', {
|
||||
set(v) {
|
||||
if (v && !v[0].complete) {
|
||||
if (!v[0].complete) {
|
||||
let orig_onload = v[0].onload;
|
||||
v[0].onload = function(v2) {
|
||||
if(orig_onload)
|
||||
|
||||
+15
-57
@@ -158,57 +158,21 @@ export function register_concat_conditionings_with_multiplier_node(nodeType, nod
|
||||
}
|
||||
|
||||
async function ensure_multipliers() {
|
||||
if(self.ensuring_multipliers) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
self.ensuring_multipliers = true;
|
||||
let ncon = get_input_count('conditioning', true);
|
||||
let nmul = get_input_count('multiplier', false) + get_widget_count('multiplier');
|
||||
|
||||
let ncon = get_input_count('conditioning', true);
|
||||
let nmul = get_input_count('multiplier', false) + get_widget_count('multiplier');
|
||||
if(ncon == 0 && nmul == 0)
|
||||
ncon = 1;
|
||||
|
||||
if(ncon == 0 && nmul == 0)
|
||||
ncon = 1;
|
||||
|
||||
for(let i = nmul+1; i<=ncon; i++) {
|
||||
let config = { min: 0, max: 10, step: 0.1, round: 0.01, precision: 2 };
|
||||
|
||||
// NOTE: addWidget trigger calling ensure_multipliers
|
||||
let widget = await self.addWidget("number", `multiplier${i}`, 1.0, function (v) {
|
||||
if (config.round) {
|
||||
self.value = Math.round(v/config.round)*config.round;
|
||||
} else {
|
||||
self.value = v;
|
||||
}
|
||||
}, config);
|
||||
}
|
||||
}
|
||||
finally{
|
||||
self.ensuring_multipliers = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function recover_multipliers() {
|
||||
if(self.recover_multipliers) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
self.recover_multipliers = true;
|
||||
for(let i = 1; i<self.widgets_values.length; i++) {
|
||||
let config = { min: 0, max: 10, step: 0.1, round: 0.01, precision: 2 };
|
||||
|
||||
// NOTE: addWidget trigger calling recover_multipliers
|
||||
let widget = await self.addWidget("number", `multiplier${i+1}`, 1.0, function (v) {
|
||||
if (config.round) {
|
||||
self.value = Math.round(v/config.round)*config.round;
|
||||
} else {
|
||||
self.value = v;
|
||||
}
|
||||
}, config);
|
||||
}
|
||||
}
|
||||
finally{
|
||||
self.recover_multipliers = null;
|
||||
for(let i = nmul+1; i<=ncon; i++) {
|
||||
let config = { min: 0, max: 10, step: 0.1, round: 0.01, precision: 2 };
|
||||
let widget = await self.addWidget("number", `multiplier${i}`, 1.0, function (v) {
|
||||
if (config.round) {
|
||||
self.value = Math.round(v/config.round)*config.round;
|
||||
} else {
|
||||
self.value = v;
|
||||
}
|
||||
}, config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,18 +183,12 @@ export function register_concat_conditionings_with_multiplier_node(nodeType, nod
|
||||
}
|
||||
}
|
||||
|
||||
const stackTrace = new Error().stack;
|
||||
if(!stackTrace.includes('loadGraphData') && !stackTrace.includes('pasteFromClipboard')) {
|
||||
if(!Error().stack.includes('pasteFromClipboard')) {
|
||||
await remove_garbage();
|
||||
await ensure_inputs();
|
||||
}
|
||||
|
||||
if(!stackTrace.includes('loadGraphData')) {
|
||||
await ensure_multipliers();
|
||||
}
|
||||
else {
|
||||
await recover_multipliers();
|
||||
}
|
||||
await ensure_multipliers();
|
||||
|
||||
await this.setSize( this.computeSize() );
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { inspireProgressBadge } from "./progress-badge.js"
|
||||
|
||||
export function register_loop_node(nodeType, nodeData, app) {
|
||||
if(nodeData.name == 'ForeachListEnd //Inspire') {
|
||||
inspireProgressBadge.addStatusHandler(nodeType);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
import { ComfyApp, app } from "../../scripts/app.js";
|
||||
import { register_concat_conditionings_with_multiplier_node, register_splitter } from "./inspire-flex.js";
|
||||
import { register_cache_info } from "./inspire-backend.js";
|
||||
import { register_loop_node } from "./inspire-loop.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.Inspire",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
await register_concat_conditionings_with_multiplier_node(nodeType, nodeData, app);
|
||||
await register_loop_node(nodeType, nodeData, app);
|
||||
},
|
||||
|
||||
nodeCreated(node, app) {
|
||||
|
||||
+90
-94
@@ -4,7 +4,7 @@ app.registerExtension({
|
||||
name: "Comfy.Inspire.LBW",
|
||||
|
||||
nodeCreated(node, app) {
|
||||
if(node.comfyClass == "LoraLoaderBlockWeight //Inspire" || node.comfyClass == "MakeLBW //Inspire") {
|
||||
if(node.comfyClass == "LoraLoaderBlockWeight //Inspire") {
|
||||
// category filter
|
||||
const lora_names_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'lora_name')];
|
||||
var full_lora_list = lora_names_widget.options.values;
|
||||
@@ -26,25 +26,21 @@ app.registerExtension({
|
||||
// vector selector
|
||||
let preset_i = 9;
|
||||
let vector_i = 10;
|
||||
|
||||
if(node.comfyClass == "MakeLBW //Inspire") {
|
||||
preset_i = 7;
|
||||
vector_i = 8;
|
||||
}
|
||||
|
||||
node._value = "Preset";
|
||||
|
||||
node.widgets[preset_i].callback = (v, canvas, node, pos, e) => {
|
||||
node.widgets[vector_i].value = node._value.split(':')[1];
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[vector_i] = node.widgets[preset_i].value;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[preset_i], "value", {
|
||||
set: (value) => {
|
||||
if(value != "Preset")
|
||||
node._value = value;
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Preset") {
|
||||
node.widgets[vector_i].value = value.split(':')[1];
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[vector_i] = node.widgets[preset_i].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node._value = value;
|
||||
},
|
||||
get: () => {
|
||||
return node._value;
|
||||
@@ -75,86 +71,86 @@ app.registerExtension({
|
||||
let preset_i = 9;
|
||||
let vector_i = 10;
|
||||
node._value = "Preset";
|
||||
|
||||
node.widgets[preset_i].callback = (v, canvas, node, pos, e) => {
|
||||
let value = node._value;
|
||||
if(!value.startsWith('@') && node.widgets[vector_i].value != "")
|
||||
node.widgets[vector_i].value += "\n";
|
||||
if(value.startsWith('@')) {
|
||||
let spec = value.split(':')[1];
|
||||
var n;
|
||||
var sub_n = null;
|
||||
var block = null;
|
||||
|
||||
if(isNaN(spec)) {
|
||||
let sub_spec = spec.split(',');
|
||||
|
||||
if(sub_spec.length != 3) {
|
||||
node.widgets_values[vector_i] = '!! SPEC ERROR !!';
|
||||
node._value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
n = parseInt(sub_spec[0].trim());
|
||||
sub_n = parseInt(sub_spec[1].trim());
|
||||
block = parseInt(sub_spec[2].trim());
|
||||
}
|
||||
else {
|
||||
n = parseInt(spec.trim());
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value = "";
|
||||
if(sub_n == null) {
|
||||
for(let i=1; i<=n; i++) {
|
||||
var temp = "";
|
||||
for(let j=1; j<=n; j++) {
|
||||
if(temp!='')
|
||||
temp += ',';
|
||||
if(j==i)
|
||||
temp += 'A';
|
||||
else
|
||||
temp += '0';
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value += `B${i}:${temp}\n`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(let i=1; i<=sub_n; i++) {
|
||||
var temp = "";
|
||||
for(let j=1; j<=n; j++) {
|
||||
if(temp!='')
|
||||
temp += ',';
|
||||
|
||||
if(block!=j)
|
||||
temp += '0';
|
||||
else {
|
||||
temp += ' ';
|
||||
for(let k=1; k<=sub_n; k++) {
|
||||
if(k==i)
|
||||
temp += 'A ';
|
||||
else
|
||||
temp += '0 ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value += `B${block}.SUB${i}:${temp}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.widgets[vector_i].value += `${value}/${value.split(':')[0]}`;
|
||||
}
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[vector_i] = node.widgets[preset_i].value;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[preset_i], "value", {
|
||||
set: (value) => {
|
||||
if(value != 'Preset')
|
||||
node._value = value;
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Preset") {
|
||||
if(!value.startsWith('@') && node.widgets[vector_i].value != "")
|
||||
node.widgets[vector_i].value += "\n";
|
||||
if(value.startsWith('@')) {
|
||||
let spec = value.split(':')[1];
|
||||
var n;
|
||||
var sub_n = null;
|
||||
var block = null;
|
||||
|
||||
if(isNaN(spec)) {
|
||||
let sub_spec = spec.split(',');
|
||||
|
||||
if(sub_spec.length != 3) {
|
||||
node.widgets_values[vector_i] = '!! SPEC ERROR !!';
|
||||
node._value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
n = parseInt(sub_spec[0].trim());
|
||||
sub_n = parseInt(sub_spec[1].trim());
|
||||
block = parseInt(sub_spec[2].trim());
|
||||
}
|
||||
else {
|
||||
n = parseInt(spec.trim());
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value = "";
|
||||
if(sub_n == null) {
|
||||
for(let i=1; i<=n; i++) {
|
||||
var temp = "";
|
||||
for(let j=1; j<=n; j++) {
|
||||
if(temp!='')
|
||||
temp += ',';
|
||||
if(j==i)
|
||||
temp += 'A';
|
||||
else
|
||||
temp += '0';
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value += `B${i}:${temp}\n`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(let i=1; i<=sub_n; i++) {
|
||||
var temp = "";
|
||||
for(let j=1; j<=n; j++) {
|
||||
if(temp!='')
|
||||
temp += ',';
|
||||
|
||||
if(block!=j)
|
||||
temp += '0';
|
||||
else {
|
||||
temp += ' ';
|
||||
for(let k=1; k<=sub_n; k++) {
|
||||
if(k==i)
|
||||
temp += 'A ';
|
||||
else
|
||||
temp += '0 ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value += `B${block}.SUB${i}:${temp}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.widgets[vector_i].value += `${value}/${value.split(':')[0]}`;
|
||||
}
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[vector_i] = node.widgets[preset_i].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node._value = value;
|
||||
},
|
||||
get: () => {
|
||||
return node._value;
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
// copying from https://github.com/pythongosssss/ComfyUI-WD14-Tagger
|
||||
class InspireProgressBadge {
|
||||
constructor() {
|
||||
if (!window.__progress_badge__) {
|
||||
window.__progress_badge__ = Symbol("__inspire_progress_badge__");
|
||||
}
|
||||
this.symbol = window.__progress_badge__;
|
||||
}
|
||||
|
||||
getState(node) {
|
||||
return node[this.symbol] || {};
|
||||
}
|
||||
|
||||
setState(node, state) {
|
||||
node[this.symbol] = state;
|
||||
app.canvas.setDirty(true);
|
||||
}
|
||||
|
||||
addStatusHandler(nodeType) {
|
||||
if (nodeType[this.symbol]?.statusTagHandler) {
|
||||
return;
|
||||
}
|
||||
if (!nodeType[this.symbol]) {
|
||||
nodeType[this.symbol] = {};
|
||||
}
|
||||
nodeType[this.symbol] = {
|
||||
statusTagHandler: true,
|
||||
};
|
||||
|
||||
api.addEventListener("inspire/update_status", ({ detail }) => {
|
||||
let { node, progress, text } = detail;
|
||||
const n = app.graph.getNodeById(+(node || app.runningNodeId));
|
||||
if (!n) return;
|
||||
const state = this.getState(n);
|
||||
state.status = Object.assign(state.status || {}, { progress: text ? progress : null, text: text || null });
|
||||
this.setState(n, state);
|
||||
});
|
||||
|
||||
const self = this;
|
||||
const onDrawForeground = nodeType.prototype.onDrawForeground;
|
||||
nodeType.prototype.onDrawForeground = function (ctx) {
|
||||
const r = onDrawForeground?.apply?.(this, arguments);
|
||||
const state = self.getState(this);
|
||||
if (!state?.status?.text) {
|
||||
return r;
|
||||
}
|
||||
|
||||
const { fgColor, bgColor, text, progress, progressColor } = { ...state.status };
|
||||
|
||||
ctx.save();
|
||||
ctx.font = "12px sans-serif";
|
||||
const sz = ctx.measureText(text);
|
||||
ctx.fillStyle = bgColor || "dodgerblue";
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(0, -LiteGraph.NODE_TITLE_HEIGHT - 20, sz.width + 12, 20, 5);
|
||||
ctx.fill();
|
||||
|
||||
if (progress) {
|
||||
ctx.fillStyle = progressColor || "green";
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(0, -LiteGraph.NODE_TITLE_HEIGHT - 20, (sz.width + 12) * progress, 20, 5);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
ctx.fillStyle = fgColor || "#fff";
|
||||
ctx.fillText(text, 6, -LiteGraph.NODE_TITLE_HEIGHT - 6);
|
||||
ctx.restore();
|
||||
return r;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const inspireProgressBadge = new InspireProgressBadge();
|
||||
|
||||
+82
-94
@@ -42,38 +42,35 @@ app.registerExtension({
|
||||
// lora selector, wildcard selector
|
||||
let combo_id = 5;
|
||||
|
||||
// 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id], "value", {
|
||||
set: (value) => {
|
||||
if (value !== "Select the LoRA to add to the text")
|
||||
node._value = value;
|
||||
},
|
||||
|
||||
get: () => { return "Select the LoRA to add to the text"; }
|
||||
});
|
||||
|
||||
// wildcard
|
||||
node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => {
|
||||
if(wildcard_text_widget.value != '')
|
||||
wildcard_text_widget.value += ', '
|
||||
|
||||
wildcard_text_widget.value += node._wildcard_value;
|
||||
}
|
||||
wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
}
|
||||
},
|
||||
get: () => { return "Select the LoRA to add to the text"; }
|
||||
});
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id+1], "value", {
|
||||
set: (value) => {
|
||||
if (value !== "Select the Wildcard to add to the text")
|
||||
node._wildcard_value = value;
|
||||
},
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Select the Wildcard to add to the text") {
|
||||
if(wildcard_text_widget.value != '')
|
||||
wildcard_text_widget.value += ', '
|
||||
|
||||
wildcard_text_widget.value += value;
|
||||
}
|
||||
}
|
||||
},
|
||||
get: () => { return "Select the Wildcard to add to the text"; }
|
||||
});
|
||||
|
||||
@@ -95,20 +92,14 @@ app.registerExtension({
|
||||
// mode combo
|
||||
Object.defineProperty(mode_widget, "value", {
|
||||
set: (value) => {
|
||||
if(value == true)
|
||||
node._mode_value = "populate";
|
||||
else if(value == false)
|
||||
node._mode_value = "fixed";
|
||||
else
|
||||
node._mode_value = value; // combo value
|
||||
|
||||
populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
|
||||
node._mode_value = value == true || value == "Populate";
|
||||
populated_text_widget.inputEl.disabled = value == true || value == "Populate";
|
||||
},
|
||||
get: () => {
|
||||
if(node._mode_value != undefined)
|
||||
return node._mode_value;
|
||||
else
|
||||
return 'populate';
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -124,46 +115,47 @@ app.registerExtension({
|
||||
// lora selector, wildcard selector
|
||||
let combo_id = 5;
|
||||
|
||||
node.widgets[combo_id].callback = (value, canvas, node, pos, e) => {
|
||||
let lora_name = node._lora_value;
|
||||
if (lora_name.endsWith('.safetensors')) {
|
||||
lora_name = lora_name.slice(0, -12);
|
||||
}
|
||||
|
||||
if(direction_widget.value) {
|
||||
pos_wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
else {
|
||||
neg_wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
}
|
||||
Object.defineProperty(node.widgets[combo_id], "value", {
|
||||
set: (value) => {
|
||||
if (value !== "Select the LoRA to add to the text")
|
||||
node._lora_value = 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);
|
||||
}
|
||||
|
||||
if(direction_widget.value) {
|
||||
pos_wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
else {
|
||||
neg_wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
get: () => { return "Select the LoRA to add to the text"; }
|
||||
});
|
||||
|
||||
node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => {
|
||||
let w = null;
|
||||
if(direction_widget.value) {
|
||||
w = pos_wildcard_text_widget;
|
||||
}
|
||||
else {
|
||||
w = neg_wildcard_text_widget;
|
||||
}
|
||||
|
||||
if(w.value != '')
|
||||
w.value += ', '
|
||||
|
||||
w.value += node._wildcard_value;
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id+1], "value", {
|
||||
set: (value) => {
|
||||
if (value !== "Select the Wildcard to add to the text")
|
||||
node._wildcard_value = value;
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Select the Wildcard to add to the text") {
|
||||
let w = null;
|
||||
if(direction_widget.value) {
|
||||
w = pos_wildcard_text_widget;
|
||||
}
|
||||
else {
|
||||
w = neg_wildcard_text_widget;
|
||||
}
|
||||
|
||||
if(w.value != '')
|
||||
w.value += ', '
|
||||
|
||||
w.value += value;
|
||||
}
|
||||
}
|
||||
},
|
||||
get: () => { return "Select the Wildcard to add to the text"; }
|
||||
});
|
||||
@@ -186,21 +178,15 @@ app.registerExtension({
|
||||
// mode combo
|
||||
Object.defineProperty(mode_widget, "value", {
|
||||
set: (value) => {
|
||||
if(value == true)
|
||||
node._mode_value = "populate";
|
||||
else if(value == false)
|
||||
node._mode_value = "fixed";
|
||||
else
|
||||
node._mode_value = value; // combo value
|
||||
|
||||
pos_populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
|
||||
neg_populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
|
||||
pos_populated_text_widget.inputEl.disabled = node._mode_value;
|
||||
neg_populated_text_widget.inputEl.disabled = node._mode_value;
|
||||
node._mode_value = value;
|
||||
},
|
||||
get: () => {
|
||||
if(node._mode_value != undefined)
|
||||
return node._mode_value;
|
||||
else
|
||||
return 'populate';
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -219,22 +205,24 @@ app.registerExtension({
|
||||
}
|
||||
});
|
||||
|
||||
preset_widget.callback = (value, canvas, node, pos, e) => {
|
||||
if(node.widgets[2].value) {
|
||||
node.widgets[2].value += ', ';
|
||||
}
|
||||
|
||||
const y = node._preset_value.split(':');
|
||||
if(y.length == 2)
|
||||
node.widgets[2].value += y[1].trim();
|
||||
else
|
||||
node.widgets[2].value += node._preset_value.trim();
|
||||
}
|
||||
|
||||
Object.defineProperty(preset_widget, "value", {
|
||||
set: (value) => {
|
||||
if (value !== "#PRESET")
|
||||
node._preset_value = value;
|
||||
set: (x) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(node.widgets[2].value) {
|
||||
node.widgets[2].value += ', ';
|
||||
}
|
||||
|
||||
const y = x.split(':');
|
||||
if(y.length == 2)
|
||||
node.widgets[2].value += y[1].trim();
|
||||
else
|
||||
node.widgets[2].value += x.trim();
|
||||
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[2] = node.widgets[2].values;
|
||||
}
|
||||
};
|
||||
},
|
||||
get: () => { return '#PRESET'; }
|
||||
});
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
[project]
|
||||
name = "comfyui-inspire-pack"
|
||||
description = "This extension provides various nodes to support Lora Block Weight, Regional Nodes, Backend Cache, Prompt Utils, List Utils, Noise(Seed) Utils, ... and the Impact Pack."
|
||||
version = "1.19.1"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["matplotlib", "cachetools"]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/ltdrdata/ComfyUI-Inspire-Pack"
|
||||
# Used by Comfy Registry https://comfyregistry.org
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "drltdata"
|
||||
DisplayName = "ComfyUI Inspire Pack"
|
||||
Icon = ""
|
||||
+1
-4
@@ -1,5 +1,2 @@
|
||||
matplotlib
|
||||
cachetools
|
||||
numpy
|
||||
webcolors
|
||||
opencv-python
|
||||
cachetools
|
||||
@@ -29,16 +29,6 @@ SDXL-LyC-ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
SDXL-LyC-INALL:1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
SDXL-LyC-MIDALL:1,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0
|
||||
SDXL-LyC-OUTALL:1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1
|
||||
FLUX-DBL-ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
FLUX-DBL-FRONT7:1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
FLUX-DBL-MID6:1,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0
|
||||
FLUX-DBL-TAIL6:1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1
|
||||
FLUX-SINGLE-ALL:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
FLUX-SINGLE-1to10:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
FLUX-SINGLE-11to20:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
FLUX-SINGLE-21to30:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0
|
||||
FLUX-SINGLE-31to37:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1
|
||||
FLUX-ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
@SD-FULL-TEST:17
|
||||
@SD-BLOCK1-TEST:17,12,1
|
||||
@SD-BLOCK2-TEST:17,12,2
|
||||
@@ -59,64 +49,4 @@ FLUX-ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
@SD-BLOCK17-TEST:17,12,17
|
||||
@SD-LyC-FULL-TEST:27
|
||||
@SDXL-FULL-TEST:12
|
||||
@SDXL-LyC-FULL-TEST:21
|
||||
@FLUX-DBL-FULL:19
|
||||
@FLUX-DBL-SGL-FULL:58
|
||||
@FLUX-DBL0-TEST:19,14,2
|
||||
@FLUX-DBL1-TEST:19,14,3
|
||||
@FLUX-DBL2-TEST:19,14,4
|
||||
@FLUX-DBL3-TEST:19,14,5
|
||||
@FLUX-DBL4-TEST:19,14,6
|
||||
@FLUX-DBL5-TEST:19,14,7
|
||||
@FLUX-DBL6-TEST:19,14,8
|
||||
@FLUX-DBL7-TEST:19,14,9
|
||||
@FLUX-DBL8-TEST:19,14,10
|
||||
@FLUX-DBL9-TEST:19,14,11
|
||||
@FLUX-DBL10-TEST:19,14,12
|
||||
@FLUX-DBL11-TEST:19,14,13
|
||||
@FLUX-DBL12-TEST:19,14,14
|
||||
@FLUX-DBL13-TEST:19,14,15
|
||||
@FLUX-DBL14-TEST:19,14,16
|
||||
@FLUX-DBL15-TEST:19,14,17
|
||||
@FLUX-DBL16-TEST:19,14,18
|
||||
@FLUX-DBL17-TEST:19,14,19
|
||||
@FLUX-DBL18-TEST:19,14,20
|
||||
@FLUX-SGL0-TEST:58,6,21
|
||||
@FLUX-SGL1-TEST:58,6,22
|
||||
@FLUX-SGL2-TEST:58,6,23
|
||||
@FLUX-SGL3-TEST:58,6,24
|
||||
@FLUX-SGL4-TEST:58,6,25
|
||||
@FLUX-SGL5-TEST:58,6,26
|
||||
@FLUX-SGL6-TEST:58,6,27
|
||||
@FLUX-SGL7-TEST:58,6,28
|
||||
@FLUX-SGL8-TEST:58,6,29
|
||||
@FLUX-SGL9-TEST:58,6,30
|
||||
@FLUX-SGL10-TEST:58,6,31
|
||||
@FLUX-SGL11-TEST:58,6,32
|
||||
@FLUX-SGL12-TEST:58,6,33
|
||||
@FLUX-SGL13-TEST:58,6,34
|
||||
@FLUX-SGL14-TEST:58,6,35
|
||||
@FLUX-SGL15-TEST:58,6,36
|
||||
@FLUX-SGL16-TEST:58,6,37
|
||||
@FLUX-SGL17-TEST:58,6,38
|
||||
@FLUX-SGL18-TEST:58,6,39
|
||||
@FLUX-SGL19-TEST:58,6,40
|
||||
@FLUX-SGL20-TEST:58,6,41
|
||||
@FLUX-SGL21-TEST:58,6,42
|
||||
@FLUX-SGL22-TEST:58,6,43
|
||||
@FLUX-SGL23-TEST:58,6,44
|
||||
@FLUX-SGL24-TEST:58,6,45
|
||||
@FLUX-SGL25-TEST:58,6,46
|
||||
@FLUX-SGL26-TEST:58,6,47
|
||||
@FLUX-SGL27-TEST:58,6,48
|
||||
@FLUX-SGL28-TEST:58,6,49
|
||||
@FLUX-SGL29-TEST:58,6,50
|
||||
@FLUX-SGL30-TEST:58,6,51
|
||||
@FLUX-SGL31-TEST:58,6,52
|
||||
@FLUX-SGL32-TEST:58,6,53
|
||||
@FLUX-SGL33-TEST:58,6,54
|
||||
@FLUX-SGL34-TEST:58,6,55
|
||||
@FLUX-SGL35-TEST:58,6,56
|
||||
@FLUX-SGL36-TEST:58,6,57
|
||||
@FLUX-SGL37-TEST:58,6,58
|
||||
@FLUX-SGL38-TEST:58,6,59
|
||||
@SDXL-LyC-FULL-TEST:21
|
||||
Reference in New Issue
Block a user