Compare commits

..
Author SHA1 Message Date
Jedrzej Kosinski 27a67fee80 Merge pull request #258 from Kosinkadink/chore/bump-version-1.6.0
Bump version to 1.6.0
2026-07-28 04:39:29 -07:00
Jedrzej KosinskiandAmp d5c6a5ec8f Bump version to 1.6.0
Amp-Thread-ID: https://ampcode.com/threads/T-019fa859-4259-74d5-ac32-0e4101490f8f
Co-authored-by: Amp <amp@ampcode.com>
2026-07-28 04:39:02 -07:00
Jedrzej Kosinski 516906affe Merge pull request #257 from Kosinkadink/fix/advanced-inpaint-control
Add modern ControlNet inpainting support
2026-07-28 04:35:32 -07:00
Jedrzej Kosinski ff45185d3b Use standard Apply nodes for inpainting 2026-07-28 04:30:35 -07:00
Jedrzej Kosinski ffa6350020 Merge remote-tracking branch 'origin/main' into fix/advanced-inpaint-control 2026-07-18 00:08:06 -07:00
Jedrzej Kosinski 9e60d8a9c8 Merge pull request #256 from Kosinkadink/fix/control-regressions
Fix LLLite masks and control scheduling regressions
2026-07-18 00:07:42 -07:00
Jedrzej Kosinski b5764c344f Skip controls with zero effect masks 2026-07-17 23:48:35 -07:00
Jedrzej Kosinski 42cdfe6c88 Fix T2I Adapter sliding context hints 2026-07-17 23:41:20 -07:00
Jedrzej Kosinski 2f9dd25d93 Fix Flux effect masks for odd latent sizes 2026-07-17 22:53:08 -07:00
Jedrzej Kosinski 0e70221a05 Add modern ControlNet inpainting support 2026-07-17 22:20:27 -07:00
Jedrzej Kosinski a4f01ba9cc Fix control scheduling regressions 2026-07-17 21:19:49 -07:00
Jedrzej Kosinski d25dbc8fc3 Merge pull request #255 from Kosinkadink/refactor/v3-node-api
Migrate all nodes to the ComfyUI V3 API
2026-07-17 20:40:57 -07:00
Jedrzej Kosinski 0a0c10b25b Migrate all nodes to the V3 API 2026-07-17 19:06:02 -07:00
Jedrzej Kosinski 4b37dbd421 Merge pull request #254 from Kosinkadink/cleanup/remove-obsolete-frontend
Remove obsolete frontend extensions
2026-07-17 18:13:20 -07:00
19 changed files with 1589 additions and 1179 deletions
+5 -3
View File
@@ -1,8 +1,10 @@
from .adv_control.nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS from .adv_control.nodes import AdvancedControlNetExtension
from .adv_control.dinklink import init_dinklink from .adv_control.dinklink import init_dinklink
from .adv_control.sampling import prepare_dinklink_acn_wrapper from .adv_control.sampling import prepare_dinklink_acn_wrapper
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
init_dinklink() init_dinklink()
prepare_dinklink_acn_wrapper() prepare_dinklink_acn_wrapper()
async def comfy_entrypoint() -> AdvancedControlNetExtension:
return AdvancedControlNetExtension()
+12 -9
View File
@@ -64,22 +64,22 @@ class ControlNetAdvanced(ControlNet, AdvancedControlBase):
# make cond_hint appropriate dimensions # make cond_hint appropriate dimensions
# TODO: change this to not require cond_hint upscaling every step when self.sub_idxs are present # TODO: change this to not require cond_hint upscaling every step when self.sub_idxs are present
if self.sub_idxs is not None or self.cond_hint is None or x_noisy.shape[2] * self.real_compression_ratio != self.cond_hint.shape[2] or x_noisy.shape[3] * self.real_compression_ratio != self.cond_hint.shape[3]: if self.sub_idxs is not None or self.cond_hint is None or x_noisy.shape[-2] * self.real_compression_ratio != self.cond_hint.shape[-2] or x_noisy.shape[-1] * self.real_compression_ratio != self.cond_hint.shape[-1]:
if self.cond_hint is not None: if self.cond_hint is not None:
del self.cond_hint del self.cond_hint
self.cond_hint = None self.cond_hint = None
self.real_compression_ratio = self.compression_ratio self.real_compression_ratio = self.compression_ratio
compression_ratio = self.compression_ratio compression_ratio = self.compression_ratio
if self.vae is not None and self.mult_by_ratio_when_vae: if self.vae is not None and self.mult_by_ratio_when_vae:
compression_ratio *= self.vae.downscale_ratio compression_ratio *= self.vae.spacial_compression_encode()
# if self.cond_hint_original length greater or equal to real latent count, subdivide it before scaling # if self.cond_hint_original length greater or equal to real latent count, subdivide it before scaling
if self.sub_idxs is not None: if self.sub_idxs is not None:
actual_cond_hint_orig = self.cond_hint_original actual_cond_hint_orig = self.cond_hint_original
if self.cond_hint_original.size(0) < self.full_latent_length: if self.cond_hint_original.size(0) < self.full_latent_length:
actual_cond_hint_orig = extend_to_batch_size(tensor=actual_cond_hint_orig, batch_size=self.full_latent_length) actual_cond_hint_orig = extend_to_batch_size(tensor=actual_cond_hint_orig, batch_size=self.full_latent_length)
self.cond_hint = comfy.utils.common_upscale(actual_cond_hint_orig[self.sub_idxs], x_noisy.shape[3] * compression_ratio, x_noisy.shape[2] * compression_ratio, self.upscale_algorithm, "center") self.cond_hint = comfy.utils.common_upscale(actual_cond_hint_orig[self.sub_idxs], x_noisy.shape[-1] * compression_ratio, x_noisy.shape[-2] * compression_ratio, self.upscale_algorithm, "center")
else: else:
self.cond_hint = comfy.utils.common_upscale(self.cond_hint_original, x_noisy.shape[3] * compression_ratio, x_noisy.shape[2] * compression_ratio, self.upscale_algorithm, "center") self.cond_hint = comfy.utils.common_upscale(self.cond_hint_original, x_noisy.shape[-1] * compression_ratio, x_noisy.shape[-2] * compression_ratio, self.upscale_algorithm, "center")
self.cond_hint = self.preprocess_image(self.cond_hint) self.cond_hint = self.preprocess_image(self.cond_hint)
if self.vae is not None: if self.vae is not None:
loaded_models = comfy.model_management.loaded_models(only_currently_used=True) loaded_models = comfy.model_management.loaded_models(only_currently_used=True)
@@ -93,7 +93,10 @@ class ControlNetAdvanced(ControlNet, AdvancedControlBase):
to_concat = [] to_concat = []
for c in self.extra_concat_orig: for c in self.extra_concat_orig:
c = c.to(self.cond_hint.device) c = c.to(self.cond_hint.device)
c = comfy.utils.common_upscale(c, self.cond_hint.shape[3], self.cond_hint.shape[2], self.upscale_algorithm, "center") c = comfy.utils.common_upscale(c, self.cond_hint.shape[-1], self.cond_hint.shape[-2], self.upscale_algorithm, "center")
if c.ndim < self.cond_hint.ndim:
c = c.unsqueeze(2)
c = comfy.utils.repeat_to_batch_size(c, self.cond_hint.shape[2], dim=2)
to_concat.append(comfy.utils.repeat_to_batch_size(c, self.cond_hint.shape[0])) to_concat.append(comfy.utils.repeat_to_batch_size(c, self.cond_hint.shape[0]))
self.cond_hint = torch.cat([self.cond_hint] + to_concat, dim=1) self.cond_hint = torch.cat([self.cond_hint] + to_concat, dim=1)
@@ -123,7 +126,7 @@ class ControlNetAdvanced(ControlNet, AdvancedControlBase):
return super().pre_run_advanced(*args, **kwargs) return super().pre_run_advanced(*args, **kwargs)
def apply_advanced_strengths_and_masks(self, x: Tensor, batched_number: int, flux_shape=None): def apply_advanced_strengths_and_masks(self, x: Tensor, batched_number: int, flux_shape=None):
if self.is_flux: if self.is_flux or x.ndim == 3:
flux_shape = self.x_noisy_shape flux_shape = self.x_noisy_shape
return super().apply_advanced_strengths_and_masks(x, batched_number, flux_shape) return super().apply_advanced_strengths_and_masks(x, batched_number, flux_shape)
@@ -216,7 +219,7 @@ class T2IAdapterAdvanced(T2IAdapter, AdvancedControlBase):
del self.cond_hint del self.cond_hint
self.cond_hint = None self.cond_hint = None
if full_cond_hint_original.size(0) < self.full_latent_length: if full_cond_hint_original.size(0) < self.full_latent_length:
actual_cond_hint_orig = extend_to_batch_size(tensor=full_cond_hint_original, batch_size=full_cond_hint_original.size(0)) actual_cond_hint_orig = extend_to_batch_size(tensor=full_cond_hint_original, batch_size=self.full_latent_length)
self.cond_hint_original = actual_cond_hint_orig[self.sub_idxs] self.cond_hint_original = actual_cond_hint_orig[self.sub_idxs]
# mask hints # mask hints
self.prepare_mask_cond_hint(x_noisy=x_noisy, t=t, cond=cond, batched_number=batched_number) self.prepare_mask_cond_hint(x_noisy=x_noisy, t=t, cond=cond, batched_number=batched_number)
@@ -810,7 +813,7 @@ def load_sparsectrl(ckpt_path: str, controlnet_data: dict[str, Tensor]=None, tim
if controlnet_config is None: if controlnet_config is None:
unet_dtype = comfy.model_management.unet_dtype() unet_dtype = comfy.model_management.unet_dtype()
controlnet_config = comfy.model_detection.model_config_from_unet(controlnet_data, prefix, unet_dtype, True).unet_config controlnet_config = comfy.model_detection.model_config_from_unet(controlnet_data, prefix, use_base_if_no_match=True).unet_config
load_device = comfy.model_management.get_torch_device() load_device = comfy.model_management.get_torch_device()
manual_cast_dtype = comfy.model_management.unet_manual_cast(unet_dtype, load_device) manual_cast_dtype = comfy.model_management.unet_manual_cast(unet_dtype, load_device)
if manual_cast_dtype is not None: if manual_cast_dtype is not None:
@@ -950,7 +953,7 @@ def load_svdcontrolnet(ckpt_path: str, controlnet_data: dict[str, Tensor]=None,
if controlnet_config is None: if controlnet_config is None:
unet_dtype = comfy.model_management.unet_dtype() unet_dtype = comfy.model_management.unet_dtype()
controlnet_config = comfy.model_detection.model_config_from_unet(controlnet_data, prefix, unet_dtype, True).unet_config controlnet_config = comfy.model_detection.model_config_from_unet(controlnet_data, prefix, use_base_if_no_match=True).unet_config
load_device = comfy.model_management.get_torch_device() load_device = comfy.model_management.get_torch_device()
manual_cast_dtype = comfy.model_management.unet_manual_cast(unet_dtype, load_device) manual_cast_dtype = comfy.model_management.unet_manual_cast(unet_dtype, load_device)
if manual_cast_dtype is not None: if manual_cast_dtype is not None:
+3 -3
View File
@@ -233,7 +233,7 @@ class LLLiteModule(torch.nn.Module):
mask = prepare_mask_batch(control.mask_cond_hint, (1, 1, h, w)).to(cx.dtype) mask = prepare_mask_batch(control.mask_cond_hint, (1, 1, h, w)).to(cx.dtype)
mask = mask.view(mask.shape[0], 1, h * w).permute(0, 2, 1) mask = mask.view(mask.shape[0], 1, h * w).permute(0, 2, 1)
if control.tk_mask_cond_hint is not None: if control.tk_mask_cond_hint is not None:
mask_tk = prepare_mask_batch(control.mask_cond_hint, (1, 1, h, w)).to(cx.dtype) mask_tk = prepare_mask_batch(control.tk_mask_cond_hint, (1, 1, h, w)).to(cx.dtype)
mask_tk = mask_tk.view(mask_tk.shape[0], 1, h * w).permute(0, 2, 1) mask_tk = mask_tk.view(mask_tk.shape[0], 1, h * w).permute(0, 2, 1)
# x in uncond/cond doubles batch size # x in uncond/cond doubles batch size
@@ -250,7 +250,7 @@ class LLLiteModule(torch.nn.Module):
if mask is None: if mask is None:
mask = 1.0 mask = 1.0
elif mask_tk is not None: if mask_tk is not None:
mask = mask * mask_tk mask = mask * mask_tk
#logger.info(f"cs: {cx.shape}, x: {x.shape}, is_conv2d: {self.is_conv2d}") #logger.info(f"cs: {cx.shape}, x: {x.shape}, is_conv2d: {self.is_conv2d}")
@@ -260,7 +260,7 @@ class LLLiteModule(torch.nn.Module):
if control.latent_keyframes is not None: if control.latent_keyframes is not None:
cx = cx * control.calc_latent_keyframe_mults(x=cx, batched_number=control.batched_number) cx = cx * control.calc_latent_keyframe_mults(x=cx, batched_number=control.batched_number)
if control.weights is not None and control.weights.has_uncond_multiplier: if control.weights is not None and control.weights.has_uncond_multiplier:
cond_or_uncond = control.batched_number.cond_or_uncond cond_or_uncond = control.cond_or_uncond
actual_length = cx.size(0) // control.batched_number actual_length = cx.size(0) // control.batched_number
for idx, cond_type in enumerate(cond_or_uncond): for idx, cond_type in enumerate(cond_or_uncond):
# if uncond, set to weight's uncond_multiplier # if uncond, set to weight's uncond_multiplier
+4 -3
View File
@@ -773,6 +773,9 @@ def refcn_diffusion_model_wrapper_factory(reference_injections: ReferenceInjecti
# if nothing related to reference controlnets, do nothing special # if nothing related to reference controlnets, do nothing special
if len(ref_controlnets) == 0 and len(context_controlnets) == 0: if len(ref_controlnets) == 0 and len(context_controlnets) == 0:
return executor(x, *args, **kwargs) return executor(x, *args, **kwargs)
adain_controlnets = []
context_adain_controlnets = []
orig_forward_timestep_embed = None
try: try:
# assign cond and uncond idxs # assign cond and uncond idxs
batched_number = len(transformer_options["cond_or_uncond"]) batched_number = len(transformer_options["cond_or_uncond"])
@@ -784,14 +787,12 @@ def refcn_diffusion_model_wrapper_factory(reference_injections: ReferenceInjecti
transformer_options[REF_COND_IDXS] = [i for i, z in enumerate(indiv_conds) if z == 0] transformer_options[REF_COND_IDXS] = [i for i, z in enumerate(indiv_conds) if z == 0]
# check which controlnets do which thing # check which controlnets do which thing
attn_controlnets = [] attn_controlnets = []
adain_controlnets = []
for control in ref_controlnets: for control in ref_controlnets:
if ReferenceType.is_attn(control.ref_opts.reference_type): if ReferenceType.is_attn(control.ref_opts.reference_type):
attn_controlnets.append(control) attn_controlnets.append(control)
if ReferenceType.is_adain(control.ref_opts.reference_type): if ReferenceType.is_adain(control.ref_opts.reference_type):
adain_controlnets.append(control) adain_controlnets.append(control)
context_attn_controlnets = [] context_attn_controlnets = []
context_adain_controlnets = []
# for ease of access, store current contextref_cond_idx value # for ease of access, store current contextref_cond_idx value
if len(context_controlnets) == 0: if len(context_controlnets) == 0:
transformer_options[CONTEXTREF_TEMP_COND_IDX] = -1 transformer_options[CONTEXTREF_TEMP_COND_IDX] = -1
@@ -877,7 +878,7 @@ def refcn_diffusion_model_wrapper_factory(reference_injections: ReferenceInjecti
finally: finally:
# make sure ref banks are cleared no matter what happens - otherwise, RIP VRAM # make sure ref banks are cleared no matter what happens - otherwise, RIP VRAM
reference_injections.clean_ref_module_mem() reference_injections.clean_ref_module_mem()
if len(adain_controlnets) > 0 or len(context_adain_controlnets) > 0: if orig_forward_timestep_embed is not None:
openaimodel.forward_timestep_embed = orig_forward_timestep_embed openaimodel.forward_timestep_embed = orig_forward_timestep_embed
return refcn_diffusion_model_wrapper return refcn_diffusion_model_wrapper
+52 -122
View File
@@ -1,4 +1,4 @@
import comfy.sample from comfy_api.latest import ComfyExtension, io
from .nodes_main import (ControlNetLoaderAdvanced, DiffControlNetLoaderAdvanced, AnimaLLLiteLoaderAdvanced, from .nodes_main import (ControlNetLoaderAdvanced, DiffControlNetLoaderAdvanced, AnimaLLLiteLoaderAdvanced,
AdvancedControlNetApply, AdvancedControlNetApplySingle) AdvancedControlNetApply, AdvancedControlNetApplySingle)
@@ -12,132 +12,62 @@ from .nodes_sparsectrl import SparseCtrlMergedLoaderAdvanced, SparseCtrlLoaderAd
from .nodes_reference import ReferenceControlNetNode, ReferenceControlFinetune, ReferencePreprocessorNode from .nodes_reference import ReferenceControlNetNode, ReferenceControlFinetune, ReferencePreprocessorNode
from .nodes_plusplus import PlusPlusLoaderAdvanced, PlusPlusLoaderSingle, PlusPlusInputNode from .nodes_plusplus import PlusPlusLoaderAdvanced, PlusPlusLoaderSingle, PlusPlusInputNode
from .nodes_ctrlora import CtrLoRALoader from .nodes_ctrlora import CtrLoRALoader
from .nodes_loosecontrol import ControlNetLoaderWithLoraAdvanced
from .nodes_deprecated import (LoadImagesFromDirectory, ScaledSoftUniversalWeightsDeprecated, from .nodes_deprecated import (LoadImagesFromDirectory, ScaledSoftUniversalWeightsDeprecated,
SoftControlNetWeightsDeprecated, CustomControlNetWeightsDeprecated, SoftControlNetWeightsDeprecated, CustomControlNetWeightsDeprecated,
SoftT2IAdapterWeightsDeprecated, CustomT2IAdapterWeightsDeprecated, SoftT2IAdapterWeightsDeprecated, CustomT2IAdapterWeightsDeprecated,
AdvancedControlNetApplyDEPR, AdvancedControlNetApplySingleDEPR, AdvancedControlNetApplyDEPR, AdvancedControlNetApplySingleDEPR,
ControlNetLoaderAdvancedDEPR, DiffControlNetLoaderAdvancedDEPR) ControlNetLoaderAdvancedDEPR, DiffControlNetLoaderAdvancedDEPR)
from .logger import logger
# NODE MAPPING
NODE_CLASS_MAPPINGS = {
# Keyframes
"TimestepKeyframe": TimestepKeyframeNode,
"ACN_TimestepKeyframeInterpolation": TimestepKeyframeInterpolationNode,
"ACN_TimestepKeyframeFromStrengthList": TimestepKeyframeFromStrengthListNode,
"LatentKeyframe": LatentKeyframeNode,
"LatentKeyframeTiming": LatentKeyframeInterpolationNode,
"LatentKeyframeBatchedGroup": LatentKeyframeBatchedGroupNode,
"LatentKeyframeGroup": LatentKeyframeGroupNode,
# Conditioning
"ACN_AdvancedControlNetApply_v2": AdvancedControlNetApply,
"ACN_AdvancedControlNetApplySingle_v2": AdvancedControlNetApplySingle,
# Loaders
"ACN_ControlNetLoaderAdvanced": ControlNetLoaderAdvanced,
"ACN_DiffControlNetLoaderAdvanced": DiffControlNetLoaderAdvanced,
"ACN_AnimaLLLiteLoaderAdvanced": AnimaLLLiteLoaderAdvanced,
# Weights
"ACN_ScaledSoftControlNetWeights": ScaledSoftUniversalWeights,
"ScaledSoftMaskedUniversalWeights": ScaledSoftMaskedUniversalWeights,
"ACN_SoftControlNetWeightsSD15": SoftControlNetWeightsSD15,
"ACN_CustomControlNetWeightsSD15": CustomControlNetWeightsSD15,
"ACN_CustomControlNetWeightsFlux": CustomControlNetWeightsFlux,
"ACN_CustomControlNetWeightsAnima": CustomControlNetWeightsAnima,
"ACN_SoftT2IAdapterWeights": SoftT2IAdapterWeights,
"ACN_CustomT2IAdapterWeights": CustomT2IAdapterWeights,
"ACN_DefaultUniversalWeights": DefaultWeights,
"ACN_ExtrasMiddleMult": ExtrasMiddleMultNode,
"ACN_AnimaLLLiteExtras": AnimaLLLiteExtras,
# SparseCtrl
"ACN_SparseCtrlRGBPreprocessor": RgbSparseCtrlPreprocessor,
"ACN_SparseCtrlLoaderAdvanced": SparseCtrlLoaderAdvanced,
"ACN_SparseCtrlMergedLoaderAdvanced": SparseCtrlMergedLoaderAdvanced,
"ACN_SparseCtrlIndexMethodNode": SparseIndexMethodNode,
"ACN_SparseCtrlSpreadMethodNode": SparseSpreadMethodNode,
"ACN_SparseCtrlWeightExtras": SparseWeightExtras,
# ControlNet++
"ACN_ControlNet++LoaderSingle": PlusPlusLoaderSingle,
"ACN_ControlNet++LoaderAdvanced": PlusPlusLoaderAdvanced,
"ACN_ControlNet++InputNode": PlusPlusInputNode,
# CtrLoRA
"ACN_CtrLoRALoader": CtrLoRALoader,
# Reference
"ACN_ReferencePreprocessor": ReferencePreprocessorNode,
"ACN_ReferenceControlNet": ReferenceControlNetNode,
"ACN_ReferenceControlNetFinetune": ReferenceControlFinetune,
# LOOSEControl
#"ACN_ControlNetLoaderWithLoraAdvanced": ControlNetLoaderWithLoraAdvanced,
# Deprecated
"LoadImagesFromDirectory": LoadImagesFromDirectory,
"ScaledSoftControlNetWeights": ScaledSoftUniversalWeightsDeprecated,
"SoftControlNetWeights": SoftControlNetWeightsDeprecated,
"CustomControlNetWeights": CustomControlNetWeightsDeprecated,
"SoftT2IAdapterWeights": SoftT2IAdapterWeightsDeprecated,
"CustomT2IAdapterWeights": CustomT2IAdapterWeightsDeprecated,
"ACN_AdvancedControlNetApply": AdvancedControlNetApplyDEPR,
"ACN_AdvancedControlNetApplySingle": AdvancedControlNetApplySingleDEPR,
"ControlNetLoaderAdvanced": ControlNetLoaderAdvancedDEPR,
"DiffControlNetLoaderAdvanced": DiffControlNetLoaderAdvancedDEPR,
}
NODE_DISPLAY_NAME_MAPPINGS = {
# Keyframes class AdvancedControlNetExtension(ComfyExtension):
"TimestepKeyframe": "Timestep Keyframe 🛂🅐🅒🅝", async def get_node_list(self) -> list[type[io.ComfyNode]]:
"ACN_TimestepKeyframeInterpolation": "Timestep Keyframe Interp. 🛂🅐🅒🅝", return [
"ACN_TimestepKeyframeFromStrengthList": "Timestep Keyframe From List 🛂🅐🅒🅝", TimestepKeyframeNode,
"LatentKeyframe": "Latent Keyframe 🛂🅐🅒🅝", TimestepKeyframeInterpolationNode,
"LatentKeyframeTiming": "Latent Keyframe Interp. 🛂🅐🅒🅝", TimestepKeyframeFromStrengthListNode,
"LatentKeyframeBatchedGroup": "Latent Keyframe From List 🛂🅐🅒🅝", LatentKeyframeNode,
"LatentKeyframeGroup": "Latent Keyframe Group 🛂🅐🅒🅝", LatentKeyframeInterpolationNode,
# Conditioning LatentKeyframeBatchedGroupNode,
"ACN_AdvancedControlNetApply_v2": "Apply Advanced ControlNet 🛂🅐🅒🅝", LatentKeyframeGroupNode,
"ACN_AdvancedControlNetApplySingle_v2": "Apply Advanced ControlNet(1) 🛂🅐🅒🅝", AdvancedControlNetApply,
# Loaders AdvancedControlNetApplySingle,
"ACN_ControlNetLoaderAdvanced": "Load Advanced ControlNet Model 🛂🅐🅒🅝", ControlNetLoaderAdvanced,
"ACN_DiffControlNetLoaderAdvanced": "Load Advanced ControlNet Model (diff) 🛂🅐🅒🅝", DiffControlNetLoaderAdvanced,
"ACN_AnimaLLLiteLoaderAdvanced": "Load Anima LLLite Model 🛂🅐🅒🅝", AnimaLLLiteLoaderAdvanced,
# Weights ScaledSoftUniversalWeights,
"ACN_ScaledSoftControlNetWeights": "Scaled Soft Weights 🛂🅐🅒🅝", ScaledSoftMaskedUniversalWeights,
"ScaledSoftMaskedUniversalWeights": "Scaled Soft Masked Weights 🛂🅐🅒🅝", SoftControlNetWeightsSD15,
"ACN_SoftControlNetWeightsSD15": "ControlNet Soft Weights [SD1.5] 🛂🅐🅒🅝", CustomControlNetWeightsSD15,
"ACN_CustomControlNetWeightsSD15": "ControlNet Custom Weights [SD1.5] 🛂🅐🅒🅝", CustomControlNetWeightsFlux,
"ACN_CustomControlNetWeightsFlux": "ControlNet Custom Weights [Flux] 🛂🅐🅒🅝", CustomControlNetWeightsAnima,
"ACN_CustomControlNetWeightsAnima": "ControlNet Custom Weights [Anima] 🛂🅐🅒🅝", SoftT2IAdapterWeights,
"ACN_SoftT2IAdapterWeights": "T2IAdapter Soft Weights 🛂🅐🅒🅝", CustomT2IAdapterWeights,
"ACN_CustomT2IAdapterWeights": "T2IAdapter Custom Weights 🛂🅐🅒🅝", DefaultWeights,
"ACN_DefaultUniversalWeights": "Default Weights 🛂🅐🅒🅝", ExtrasMiddleMultNode,
"ACN_ExtrasMiddleMult": "Middle Weight Extras 🛂🅐🅒🅝", AnimaLLLiteExtras,
"ACN_AnimaLLLiteExtras": "Anima LLLite Extras 🛂🅐🅒🅝", RgbSparseCtrlPreprocessor,
# SparseCtrl SparseCtrlLoaderAdvanced,
"ACN_SparseCtrlRGBPreprocessor": "RGB SparseCtrl 🛂🅐🅒🅝", SparseCtrlMergedLoaderAdvanced,
"ACN_SparseCtrlLoaderAdvanced": "Load SparseCtrl Model 🛂🅐🅒🅝", SparseIndexMethodNode,
"ACN_SparseCtrlMergedLoaderAdvanced": "🧪Load Merged SparseCtrl Model 🛂🅐🅒🅝", SparseSpreadMethodNode,
"ACN_SparseCtrlIndexMethodNode": "SparseCtrl Index Method 🛂🅐🅒🅝", SparseWeightExtras,
"ACN_SparseCtrlSpreadMethodNode": "SparseCtrl Spread Method 🛂🅐🅒🅝", PlusPlusLoaderSingle,
"ACN_SparseCtrlWeightExtras": "SparseCtrl Weight Extras 🛂🅐🅒🅝", PlusPlusLoaderAdvanced,
# ControlNet++ PlusPlusInputNode,
"ACN_ControlNet++LoaderSingle": "Load ControlNet++ Model (Single) 🛂🅐🅒🅝", CtrLoRALoader,
"ACN_ControlNet++LoaderAdvanced": "Load ControlNet++ Model (Multi) 🛂🅐🅒🅝", ReferencePreprocessorNode,
"ACN_ControlNet++InputNode": "ControlNet++ Input 🛂🅐🅒🅝", ReferenceControlNetNode,
# CtrLoRA ReferenceControlFinetune,
"ACN_CtrLoRALoader": "Load CtrLoRA Model 🛂🅐🅒🅝", LoadImagesFromDirectory,
# Reference ScaledSoftUniversalWeightsDeprecated,
"ACN_ReferencePreprocessor": "Reference Preproccessor 🛂🅐🅒🅝", SoftControlNetWeightsDeprecated,
"ACN_ReferenceControlNet": "Reference ControlNet 🛂🅐🅒🅝", CustomControlNetWeightsDeprecated,
"ACN_ReferenceControlNetFinetune": "Reference ControlNet (Finetune) 🛂🅐🅒🅝", SoftT2IAdapterWeightsDeprecated,
# LOOSEControl CustomT2IAdapterWeightsDeprecated,
#"ACN_ControlNetLoaderWithLoraAdvanced": "Load Adv. ControlNet Model w/ LoRA 🛂🅐🅒🅝", AdvancedControlNetApplyDEPR,
# Deprecated AdvancedControlNetApplySingleDEPR,
"LoadImagesFromDirectory": "🚫Load Images [DEPRECATED] 🛂🅐🅒🅝", ControlNetLoaderAdvancedDEPR,
"ScaledSoftControlNetWeights": "Scaled Soft Weights 🛂🅐🅒🅝", DiffControlNetLoaderAdvancedDEPR
"SoftControlNetWeights": "ControlNet Soft Weights 🛂🅐🅒🅝", ]
"CustomControlNetWeights": "ControlNet Custom Weights 🛂🅐🅒🅝",
"SoftT2IAdapterWeights": "T2IAdapter Soft Weights 🛂🅐🅒🅝",
"CustomT2IAdapterWeights": "T2IAdapter Custom Weights 🛂🅐🅒🅝",
"ACN_AdvancedControlNetApply": "Apply Advanced ControlNet 🛂🅐🅒🅝",
"ACN_AdvancedControlNetApplySingle": "Apply Advanced ControlNet(1) 🛂🅐🅒🅝",
"ControlNetLoaderAdvanced": "Load Advanced ControlNet Model 🛂🅐🅒🅝",
"DiffControlNetLoaderAdvanced": "Load Advanced ControlNet Model (diff) 🛂🅐🅒🅝",
}
+18 -15
View File
@@ -1,25 +1,28 @@
from comfy_api.latest import io
import folder_paths import folder_paths
from .control_ctrlora import load_ctrlora from .control_ctrlora import load_ctrlora
class CtrLoRALoader(io.ComfyNode):
class CtrLoRALoader:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_CtrLoRALoader',
"base": (folder_paths.get_filename_list("controlnet"), ), display_name='Load CtrLoRA Model 🛂🅐🅒🅝',
"lora": (folder_paths.get_filename_list("controlnet"), ), category='Adv-ControlNet 🛂🅐🅒🅝/CtrLoRA',
} inputs=[
} io.Combo.Input('base', options=folder_paths.get_filename_list("controlnet")),
io.Combo.Input('lora', options=folder_paths.get_filename_list("controlnet"))
],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET",)
FUNCTION = "load_controlnet_plusplus"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/CtrLoRA" @classmethod
def execute(cls, base: str, lora: str):
def load_controlnet_plusplus(self, base: str, lora: str):
base_path = folder_paths.get_full_path("controlnet", base) base_path = folder_paths.get_full_path("controlnet", base)
lora_path = folder_paths.get_full_path("controlnet", lora) lora_path = folder_paths.get_full_path("controlnet", lora)
controlnet = load_ctrlora(base_path, lora_path) controlnet = load_ctrlora(base_path, lora_path)
return (controlnet,) return io.NodeOutput(controlnet,)
+257 -255
View File
@@ -1,3 +1,4 @@
from comfy_api.latest import io
import os import os
import torch import torch
@@ -7,29 +8,31 @@ import numpy as np
from PIL import Image, ImageOps from PIL import Image, ImageOps
from .control import load_controlnet, is_advanced_controlnet from .control import load_controlnet, is_advanced_controlnet
from .nodes_main import AdvancedControlNetApply from .nodes_main import AdvancedControlNetApply
from .utils import BIGMAX, ControlWeights, TimestepKeyframeGroup, TimestepKeyframe, get_properly_arranged_t2i_weights from .utils import ControlWeights, TimestepKeyframeGroup, TimestepKeyframe, get_properly_arranged_t2i_weights
from .logger import logger
class LoadImagesFromDirectory(io.ComfyNode):
class LoadImagesFromDirectory:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='LoadImagesFromDirectory',
"directory": ("STRING", {"default": ""}), display_name='🚫Load Images [DEPRECATED] 🛂🅐🅒🅝',
}, category='',
"optional": { inputs=[
"image_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}), io.String.Input('directory', default=''),
"start_index": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}), io.Int.Input('image_load_cap', optional=True, default=0, max=9007199254740991, min=0, step=1),
} io.Int.Input('start_index', optional=True, default=0, max=9007199254740991, min=0, step=1)
} ],
outputs=[
io.Image.Output('IMAGE', is_output_list=False),
io.Mask.Output('MASK', is_output_list=False),
io.Int.Output('INT', is_output_list=False)
],
is_deprecated=True
)
RETURN_TYPES = ("IMAGE", "MASK", "INT")
FUNCTION = "load_images"
CATEGORY = "" @classmethod
def execute(cls, directory: str, image_load_cap: int = 0, start_index: int = 0):
def load_images(self, directory: str, image_load_cap: int = 0, start_index: int = 0):
if not os.path.isdir(directory): if not os.path.isdir(directory):
raise FileNotFoundError(f"Directory '{directory} cannot be found.'") raise FileNotFoundError(f"Directory '{directory} cannot be found.'")
dir_files = os.listdir(directory) dir_files = os.listdir(directory)
@@ -71,285 +74,283 @@ class LoadImagesFromDirectory:
if len(images) == 0: if len(images) == 0:
raise FileNotFoundError(f"No images could be loaded from directory '{directory}'.") raise FileNotFoundError(f"No images could be loaded from directory '{directory}'.")
return (torch.cat(images, dim=0), torch.stack(masks, dim=0), image_count) return io.NodeOutput(torch.cat(images, dim=0), torch.stack(masks, dim=0), image_count)
class ScaledSoftUniversalWeightsDeprecated(io.ComfyNode):
class ScaledSoftUniversalWeightsDeprecated:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ScaledSoftControlNetWeights',
"base_multiplier": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 1.0, "step": 0.001}, ), display_name='Scaled Soft Weights 🛂🅐🅒🅝',
"flip_weights": ("BOOLEAN", {"default": False}), category='',
}, inputs=[
"optional": { io.Float.Input('base_multiplier', default=0.825, max=1.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Boolean.Input('flip_weights', default=False),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = ("CN_WEIGHTS", "TK_SHORTCUT")
FUNCTION = "load_weights"
CATEGORY = "" @classmethod
def execute(cls, base_multiplier, flip_weights, uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
def load_weights(self, base_multiplier, flip_weights, uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
weights = ControlWeights.universal(base_multiplier=base_multiplier, uncond_multiplier=uncond_multiplier, extras=cn_extras) weights = ControlWeights.universal(base_multiplier=base_multiplier, uncond_multiplier=uncond_multiplier, extras=cn_extras)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class SoftControlNetWeightsDeprecated(io.ComfyNode):
class SoftControlNetWeightsDeprecated:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='SoftControlNetWeights',
"weight_00": ("FLOAT", {"default": 0.09941396206337118, "min": 0.0, "max": 10.0, "step": 0.001}, ), display_name='ControlNet Soft Weights 🛂🅐🅒🅝',
"weight_01": ("FLOAT", {"default": 0.12050177219802567, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='',
"weight_02": ("FLOAT", {"default": 0.14606275417942507, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"weight_03": ("FLOAT", {"default": 0.17704576264172736, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_00', default=0.09941396206337118, max=10.0, min=0.0, step=0.001),
"weight_04": ("FLOAT", {"default": 0.214600924414215, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_01', default=0.12050177219802567, max=10.0, min=0.0, step=0.001),
"weight_05": ("FLOAT", {"default": 0.26012233262329093, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_02', default=0.14606275417942507, max=10.0, min=0.0, step=0.001),
"weight_06": ("FLOAT", {"default": 0.3152997971191405, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_03', default=0.17704576264172736, max=10.0, min=0.0, step=0.001),
"weight_07": ("FLOAT", {"default": 0.3821815722656249, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_04', default=0.214600924414215, max=10.0, min=0.0, step=0.001),
"weight_08": ("FLOAT", {"default": 0.4632503906249999, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_05', default=0.26012233262329093, max=10.0, min=0.0, step=0.001),
"weight_09": ("FLOAT", {"default": 0.561515625, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_06', default=0.3152997971191405, max=10.0, min=0.0, step=0.001),
"weight_10": ("FLOAT", {"default": 0.6806249999999999, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_07', default=0.3821815722656249, max=10.0, min=0.0, step=0.001),
"weight_11": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_08', default=0.4632503906249999, max=10.0, min=0.0, step=0.001),
"weight_12": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_09', default=0.561515625, max=10.0, min=0.0, step=0.001),
"flip_weights": ("BOOLEAN", {"default": False}), io.Float.Input('weight_10', default=0.6806249999999999, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('weight_11', default=0.825, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('weight_12', default=1.0, max=10.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Boolean.Input('flip_weights', default=False),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
],
is_deprecated=True
)
DEPRECATED = True
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = ("CN_WEIGHTS", "TK_SHORTCUT")
FUNCTION = "load_weights"
CATEGORY = "" @classmethod
def execute(cls, weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06,
def load_weights(self, weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06,
weight_07, weight_08, weight_09, weight_10, weight_11, weight_12, flip_weights, weight_07, weight_08, weight_09, weight_10, weight_11, weight_12, flip_weights,
uncond_multiplier: float=1.0, cn_extras: dict[str]={}): uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
weights_output = [weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06, weights_output = [weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06,
weight_07, weight_08, weight_09, weight_10, weight_11] weight_07, weight_08, weight_09, weight_10, weight_11]
weights_middle = [weight_12] weights_middle = [weight_12]
weights = ControlWeights.controlnet(weights_output=weights_output, weights_middle=weights_middle, uncond_multiplier=uncond_multiplier, extras=cn_extras) weights = ControlWeights.controlnet(weights_output=weights_output, weights_middle=weights_middle, uncond_multiplier=uncond_multiplier, extras=cn_extras)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class CustomControlNetWeightsDeprecated(io.ComfyNode):
class CustomControlNetWeightsDeprecated:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='CustomControlNetWeights',
"weight_00": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), display_name='ControlNet Custom Weights 🛂🅐🅒🅝',
"weight_01": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='',
"weight_02": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"weight_03": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_00', default=1.0, max=10.0, min=0.0, step=0.001),
"weight_04": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_01', default=1.0, max=10.0, min=0.0, step=0.001),
"weight_05": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_02', default=1.0, max=10.0, min=0.0, step=0.001),
"weight_06": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_03', default=1.0, max=10.0, min=0.0, step=0.001),
"weight_07": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_04', default=1.0, max=10.0, min=0.0, step=0.001),
"weight_08": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_05', default=1.0, max=10.0, min=0.0, step=0.001),
"weight_09": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_06', default=1.0, max=10.0, min=0.0, step=0.001),
"weight_10": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_07', default=1.0, max=10.0, min=0.0, step=0.001),
"weight_11": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_08', default=1.0, max=10.0, min=0.0, step=0.001),
"weight_12": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_09', default=1.0, max=10.0, min=0.0, step=0.001),
"flip_weights": ("BOOLEAN", {"default": False}), io.Float.Input('weight_10', default=1.0, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('weight_11', default=1.0, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('weight_12', default=1.0, max=10.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Boolean.Input('flip_weights', default=False),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
],
is_deprecated=True
)
DEPRECATED = True
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = ("CN_WEIGHTS", "TK_SHORTCUT")
FUNCTION = "load_weights"
CATEGORY = "" @classmethod
def execute(cls, weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06,
def load_weights(self, weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06,
weight_07, weight_08, weight_09, weight_10, weight_11, weight_12, flip_weights, weight_07, weight_08, weight_09, weight_10, weight_11, weight_12, flip_weights,
uncond_multiplier: float=1.0, cn_extras: dict[str]={}): uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
weights_output = [weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06, weights_output = [weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06,
weight_07, weight_08, weight_09, weight_10, weight_11] weight_07, weight_08, weight_09, weight_10, weight_11]
weights_middle = [weight_12] weights_middle = [weight_12]
weights = ControlWeights.controlnet(weights_output=weights_output, weights_middle=weights_middle, uncond_multiplier=uncond_multiplier, extras=cn_extras) weights = ControlWeights.controlnet(weights_output=weights_output, weights_middle=weights_middle, uncond_multiplier=uncond_multiplier, extras=cn_extras)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class SoftT2IAdapterWeightsDeprecated(io.ComfyNode):
class SoftT2IAdapterWeightsDeprecated:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='SoftT2IAdapterWeights',
"weight_00": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 10.0, "step": 0.001}, ), display_name='T2IAdapter Soft Weights 🛂🅐🅒🅝',
"weight_01": ("FLOAT", {"default": 0.62, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='',
"weight_02": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"weight_03": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_00', default=0.25, max=10.0, min=0.0, step=0.001),
"flip_weights": ("BOOLEAN", {"default": False}), io.Float.Input('weight_01', default=0.62, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('weight_02', default=0.825, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('weight_03', default=1.0, max=10.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Boolean.Input('flip_weights', default=False),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
],
is_deprecated=True
)
DEPRECATED = True
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = ("CN_WEIGHTS", "TK_SHORTCUT")
FUNCTION = "load_weights"
CATEGORY = "" @classmethod
def execute(cls, weight_00, weight_01, weight_02, weight_03, flip_weights,
def load_weights(self, weight_00, weight_01, weight_02, weight_03, flip_weights,
uncond_multiplier: float=1.0, cn_extras: dict[str]={}): uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
weights = [weight_00, weight_01, weight_02, weight_03] weights = [weight_00, weight_01, weight_02, weight_03]
weights = get_properly_arranged_t2i_weights(weights) weights = get_properly_arranged_t2i_weights(weights)
weights = ControlWeights.t2iadapter(weights_input=weights, uncond_multiplier=uncond_multiplier, extras=cn_extras) weights = ControlWeights.t2iadapter(weights_input=weights, uncond_multiplier=uncond_multiplier, extras=cn_extras)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class CustomT2IAdapterWeightsDeprecated(io.ComfyNode):
class CustomT2IAdapterWeightsDeprecated:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='CustomT2IAdapterWeights',
"weight_00": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), display_name='T2IAdapter Custom Weights 🛂🅐🅒🅝',
"weight_01": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='',
"weight_02": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"weight_03": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('weight_00', default=1.0, max=10.0, min=0.0, step=0.001),
"flip_weights": ("BOOLEAN", {"default": False}), io.Float.Input('weight_01', default=1.0, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('weight_02', default=1.0, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('weight_03', default=1.0, max=10.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Boolean.Input('flip_weights', default=False),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
],
is_deprecated=True
)
DEPRECATED = True
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = ("CN_WEIGHTS", "TK_SHORTCUT")
FUNCTION = "load_weights"
CATEGORY = "" @classmethod
def execute(cls, weight_00, weight_01, weight_02, weight_03, flip_weights,
def load_weights(self, weight_00, weight_01, weight_02, weight_03, flip_weights,
uncond_multiplier: float=1.0, cn_extras: dict[str]={}): uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
weights = [weight_00, weight_01, weight_02, weight_03] weights = [weight_00, weight_01, weight_02, weight_03]
weights = get_properly_arranged_t2i_weights(weights) weights = get_properly_arranged_t2i_weights(weights)
weights = ControlWeights.t2iadapter(weights_input=weights, uncond_multiplier=uncond_multiplier, extras=cn_extras) weights = ControlWeights.t2iadapter(weights_input=weights, uncond_multiplier=uncond_multiplier, extras=cn_extras)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class AdvancedControlNetApplyDEPR(io.ComfyNode):
class AdvancedControlNetApplyDEPR:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_AdvancedControlNetApply',
"positive": ("CONDITIONING", ), display_name='Apply Advanced ControlNet 🛂🅐🅒🅝',
"negative": ("CONDITIONING", ), category='',
"control_net": ("CONTROL_NET", ), inputs=[
"image": ("IMAGE", ), io.Conditioning.Input('positive'),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), io.Conditioning.Input('negative'),
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), io.ControlNet.Input('control_net'),
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}) io.Image.Input('image'),
}, io.Float.Input('strength', default=1.0, max=10.0, min=0.0, step=0.01),
"optional": { io.Float.Input('start_percent', default=0.0, max=1.0, min=0.0, step=0.001),
"mask_optional": ("MASK", ), io.Float.Input('end_percent', default=1.0, max=1.0, min=0.0, step=0.001),
"timestep_kf": ("TIMESTEP_KEYFRAME", ), io.Mask.Input('mask_optional', display_name='effect_mask', optional=True),
"latent_kf_override": ("LATENT_KEYFRAME", ), io.Custom('TIMESTEP_KEYFRAME').Input('timestep_kf', optional=True),
"weights_override": ("CONTROL_NET_WEIGHTS", ), io.Custom('LATENT_KEYFRAME').Input('latent_kf_override', optional=True),
"model_optional": ("MODEL",), io.Custom('CONTROL_NET_WEIGHTS').Input('weights_override', optional=True),
"vae_optional": ("VAE",), io.Model.Input('model_optional', display_name='model', optional=True),
}, io.Vae.Input('vae_optional', display_name='vae', optional=True)
} ],
outputs=[
io.Conditioning.Output('positive', is_output_list=False),
io.Conditioning.Output('negative', is_output_list=False),
io.Model.Output('model_opt', is_output_list=False)
],
is_deprecated=True
)
DEPRECATED = True @classmethod
RETURN_TYPES = ("CONDITIONING","CONDITIONING","MODEL",) def execute(cls, positive, negative, control_net, image, strength, start_percent, end_percent,
RETURN_NAMES = ("positive", "negative", "model_opt")
FUNCTION = "apply_controlnet"
CATEGORY = ""
def apply_controlnet(self, positive, negative, control_net, image, strength, start_percent, end_percent,
mask_optional=None, model_optional=None, vae_optional=None, mask_optional=None, model_optional=None, vae_optional=None,
timestep_kf: TimestepKeyframeGroup=None, latent_kf_override=None, timestep_kf: TimestepKeyframeGroup=None, latent_kf_override=None,
weights_override: ControlWeights=None, control_apply_to_uncond=False): weights_override: ControlWeights=None, control_apply_to_uncond=False):
new_positive, new_negative = AdvancedControlNetApply.apply_controlnet(self, positive=positive, negative=negative, control_net=control_net, image=image, new_positive, new_negative = AdvancedControlNetApply.execute(positive=positive, negative=negative, control_net=control_net, image=image,
strength=strength, start_percent=start_percent, end_percent=end_percent, strength=strength, start_percent=start_percent, end_percent=end_percent,
mask_optional=mask_optional, vae_optional=vae_optional, mask_optional=mask_optional, vae_optional=vae_optional,
timestep_kf=timestep_kf, latent_kf_override=latent_kf_override, weights_override=weights_override,) timestep_kf=timestep_kf, latent_kf_override=latent_kf_override, weights_override=weights_override,).args
return (new_positive, new_negative, model_optional) return io.NodeOutput(new_positive, new_negative, model_optional)
class AdvancedControlNetApplySingleDEPR(io.ComfyNode):
class AdvancedControlNetApplySingleDEPR:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_AdvancedControlNetApplySingle',
"conditioning": ("CONDITIONING", ), display_name='Apply Advanced ControlNet(1) 🛂🅐🅒🅝',
"control_net": ("CONTROL_NET", ), category='',
"image": ("IMAGE", ), inputs=[
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), io.Conditioning.Input('conditioning'),
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), io.ControlNet.Input('control_net'),
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}) io.Image.Input('image'),
}, io.Float.Input('strength', default=1.0, max=10.0, min=0.0, step=0.01),
"optional": { io.Float.Input('start_percent', default=0.0, max=1.0, min=0.0, step=0.001),
"mask_optional": ("MASK", ), io.Float.Input('end_percent', default=1.0, max=1.0, min=0.0, step=0.001),
"timestep_kf": ("TIMESTEP_KEYFRAME", ), io.Mask.Input('mask_optional', display_name='effect_mask', optional=True),
"latent_kf_override": ("LATENT_KEYFRAME", ), io.Custom('TIMESTEP_KEYFRAME').Input('timestep_kf', optional=True),
"weights_override": ("CONTROL_NET_WEIGHTS", ), io.Custom('LATENT_KEYFRAME').Input('latent_kf_override', optional=True),
"model_optional": ("MODEL",), io.Custom('CONTROL_NET_WEIGHTS').Input('weights_override', optional=True),
"vae_optional": ("VAE",), io.Model.Input('model_optional', display_name='model', optional=True),
}, io.Vae.Input('vae_optional', display_name='vae', optional=True)
} ],
outputs=[
io.Conditioning.Output('CONDITIONING', is_output_list=False),
io.Model.Output('model_opt', is_output_list=False)
],
is_deprecated=True
)
DEPRECATED = True @classmethod
RETURN_TYPES = ("CONDITIONING","MODEL",) def execute(cls, conditioning, control_net, image, strength, start_percent, end_percent,
RETURN_NAMES = ("CONDITIONING", "model_opt")
FUNCTION = "apply_controlnet"
CATEGORY = ""
def apply_controlnet(self, conditioning, control_net, image, strength, start_percent, end_percent,
mask_optional=None, model_optional=None, vae_optional=None, mask_optional=None, model_optional=None, vae_optional=None,
timestep_kf: TimestepKeyframeGroup=None, latent_kf_override=None, timestep_kf: TimestepKeyframeGroup=None, latent_kf_override=None,
weights_override: ControlWeights=None): weights_override: ControlWeights=None):
values = AdvancedControlNetApply.apply_controlnet(self, positive=conditioning, negative=None, control_net=control_net, image=image, values = AdvancedControlNetApply.execute(positive=conditioning, negative=None, control_net=control_net, image=image,
strength=strength, start_percent=start_percent, end_percent=end_percent, strength=strength, start_percent=start_percent, end_percent=end_percent,
mask_optional=mask_optional, vae_optional=vae_optional, mask_optional=mask_optional, vae_optional=vae_optional,
timestep_kf=timestep_kf, latent_kf_override=latent_kf_override, weights_override=weights_override, timestep_kf=timestep_kf, latent_kf_override=latent_kf_override, weights_override=weights_override,
control_apply_to_uncond=True) control_apply_to_uncond=True)
return (values[0], model_optional) return io.NodeOutput(values.args[0], model_optional)
class ControlNetLoaderAdvancedDEPR(io.ComfyNode):
class ControlNetLoaderAdvancedDEPR:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ControlNetLoaderAdvanced',
"control_net_name": (folder_paths.get_filename_list("controlnet"), ), display_name='Load Advanced ControlNet Model 🛂🅐🅒🅝',
}, category='',
"optional": { inputs=[
"tk_optional": ("TIMESTEP_KEYFRAME", ), io.Combo.Input('control_net_name', options=folder_paths.get_filename_list("controlnet")),
} io.Custom('TIMESTEP_KEYFRAME').Input('tk_optional', display_name='timestep_kf', optional=True)
} ],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
],
is_deprecated=True
)
DEPRECATED = True @classmethod
RETURN_TYPES = ("CONTROL_NET", ) def execute(cls, control_net_name,
FUNCTION = "load_controlnet"
CATEGORY = ""
def load_controlnet(self, control_net_name,
tk_optional: TimestepKeyframeGroup=None, tk_optional: TimestepKeyframeGroup=None,
timestep_keyframe: TimestepKeyframeGroup=None, timestep_keyframe: TimestepKeyframeGroup=None,
): ):
@@ -357,29 +358,30 @@ class ControlNetLoaderAdvancedDEPR:
tk_optional = timestep_keyframe tk_optional = timestep_keyframe
controlnet_path = folder_paths.get_full_path("controlnet", control_net_name) controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
controlnet = load_controlnet(controlnet_path, tk_optional) controlnet = load_controlnet(controlnet_path, tk_optional)
return (controlnet,) return io.NodeOutput(controlnet,)
class DiffControlNetLoaderAdvancedDEPR: class DiffControlNetLoaderAdvancedDEPR(io.ComfyNode):
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='DiffControlNetLoaderAdvanced',
"model": ("MODEL",), display_name='Load Advanced ControlNet Model (diff) 🛂🅐🅒🅝',
"control_net_name": (folder_paths.get_filename_list("controlnet"), ) category='',
}, inputs=[
"optional": { io.Model.Input('model'),
"tk_optional": ("TIMESTEP_KEYFRAME", ), io.Combo.Input('control_net_name', options=folder_paths.get_filename_list("controlnet")),
}, io.Custom('TIMESTEP_KEYFRAME').Input('tk_optional', display_name='timestep_kf', optional=True)
} ],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
],
is_deprecated=True
)
DEPRECATED = True
RETURN_TYPES = ("CONTROL_NET", )
FUNCTION = "load_controlnet"
CATEGORY = "" @classmethod
def execute(cls, control_net_name, model,
def load_controlnet(self, control_net_name, model,
tk_optional: TimestepKeyframeGroup=None, tk_optional: TimestepKeyframeGroup=None,
timestep_keyframe: TimestepKeyframeGroup=None timestep_keyframe: TimestepKeyframeGroup=None
): ):
@@ -389,4 +391,4 @@ class DiffControlNetLoaderAdvancedDEPR:
controlnet = load_controlnet(controlnet_path, tk_optional, model) controlnet = load_controlnet(controlnet_path, tk_optional, model)
if is_advanced_controlnet(controlnet): if is_advanced_controlnet(controlnet):
controlnet.verify_all_weights() controlnet.verify_all_weights()
return (controlnet,) return io.NodeOutput(controlnet,)
+171 -171
View File
@@ -1,40 +1,40 @@
from comfy_api.latest import io
from typing import Union from typing import Union
import numpy as np import numpy as np
from collections.abc import Iterable from collections.abc import Iterable
from .utils import ControlWeights, TimestepKeyframe, TimestepKeyframeGroup, LatentKeyframe, LatentKeyframeGroup, BIGMIN, BIGMAX from .utils import ControlWeights, TimestepKeyframe, TimestepKeyframeGroup, LatentKeyframe, LatentKeyframeGroup
from .utils import StrengthInterpolation as SI from .utils import StrengthInterpolation as SI
from .logger import logger from .logger import logger
class TimestepKeyframeNode(io.ComfyNode):
class TimestepKeyframeNode:
OUTDATED_DUMMY = -39 OUTDATED_DUMMY = -39
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='TimestepKeyframe',
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}, ), display_name='Timestep Keyframe 🛂🅐🅒🅝',
}, category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
"optional": { inputs=[
"prev_timestep_kf": ("TIMESTEP_KEYFRAME", ), io.Float.Input('start_percent', default=0.0, max=1.0, min=0.0, step=0.001),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Custom('TIMESTEP_KEYFRAME').Input('prev_timestep_kf', optional=True),
"cn_weights": ("CONTROL_NET_WEIGHTS", ), io.Float.Input('strength', optional=True, default=1.0, max=10.0, min=0.0, step=0.001),
"latent_keyframe": ("LATENT_KEYFRAME", ), io.Custom('CONTROL_NET_WEIGHTS').Input('cn_weights', optional=True),
"null_latent_kf_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Custom('LATENT_KEYFRAME').Input('latent_keyframe', optional=True),
"inherit_missing": ("BOOLEAN", {"default": True}, ), io.Float.Input('null_latent_kf_strength', optional=True, default=0.0, max=10.0, min=0.0, step=0.001),
"guarantee_steps": ("INT", {"default": 1, "min": 0, "max": BIGMAX}), io.Boolean.Input('inherit_missing', optional=True, default=True),
"mask_optional": ("MASK", ), io.Int.Input('guarantee_steps', optional=True, default=1, max=9007199254740991, min=0),
}, io.Mask.Input('mask_optional', display_name='mask', optional=True)
} ],
outputs=[
io.Custom('TIMESTEP_KEYFRAME').Output('TIMESTEP_KF', is_output_list=False)
]
)
RETURN_NAMES = ("TIMESTEP_KF", )
RETURN_TYPES = ("TIMESTEP_KEYFRAME", )
FUNCTION = "load_keyframe"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" @classmethod
def execute(cls,
def load_keyframe(self,
start_percent: float, start_percent: float,
strength: float=1.0, strength: float=1.0,
cn_weights: ControlWeights=None, control_net_weights: ControlWeights=None, # old name cn_weights: ControlWeights=None, control_net_weights: ControlWeights=None, # old name
@@ -46,7 +46,7 @@ class TimestepKeyframeNode:
guarantee_usage=True, # old input guarantee_usage=True, # old input
mask_optional=None,): mask_optional=None,):
# if using outdated dummy value, means node on workflow is outdated and should appropriately convert behavior # if using outdated dummy value, means node on workflow is outdated and should appropriately convert behavior
if guarantee_steps == self.OUTDATED_DUMMY: if guarantee_steps == cls.OUTDATED_DUMMY:
guarantee_steps = int(guarantee_usage) guarantee_steps = int(guarantee_usage)
control_net_weights = control_net_weights if control_net_weights else cn_weights control_net_weights = control_net_weights if control_net_weights else cn_weights
prev_timestep_keyframe = prev_timestep_keyframe if prev_timestep_keyframe else prev_timestep_kf prev_timestep_keyframe = prev_timestep_keyframe if prev_timestep_keyframe else prev_timestep_kf
@@ -58,39 +58,39 @@ class TimestepKeyframeNode:
control_weights=control_net_weights, latent_keyframes=latent_keyframe, inherit_missing=inherit_missing, control_weights=control_net_weights, latent_keyframes=latent_keyframe, inherit_missing=inherit_missing,
guarantee_steps=guarantee_steps, mask_hint_orig=mask_optional) guarantee_steps=guarantee_steps, mask_hint_orig=mask_optional)
prev_timestep_keyframe.add(keyframe) prev_timestep_keyframe.add(keyframe)
return (prev_timestep_keyframe,) return io.NodeOutput(prev_timestep_keyframe,)
class TimestepKeyframeInterpolationNode: class TimestepKeyframeInterpolationNode(io.ComfyNode):
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_TimestepKeyframeInterpolation',
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001},), display_name='Timestep Keyframe Interp. 🛂🅐🅒🅝',
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
"strength_start": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001},), inputs=[
"strength_end": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001},), io.Float.Input('start_percent', default=0.0, max=1.0, min=0.0, step=0.001),
"interpolation": (SI._LIST, ), io.Float.Input('end_percent', default=1.0, max=1.0, min=0.0, step=0.001),
"intervals": ("INT", {"default": 50, "min": 2, "max": 100, "step": 1}), io.Float.Input('strength_start', default=1.0, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('strength_end', default=1.0, max=10.0, min=0.0, step=0.001),
"optional": { io.Combo.Input('interpolation', options=['linear', 'ease-in', 'ease-out', 'ease-in-out']),
"prev_timestep_kf": ("TIMESTEP_KEYFRAME", ), io.Int.Input('intervals', default=50, max=100, min=2, step=1),
"cn_weights": ("CONTROL_NET_WEIGHTS", ), io.Custom('TIMESTEP_KEYFRAME').Input('prev_timestep_kf', optional=True),
"latent_keyframe": ("LATENT_KEYFRAME", ), io.Custom('CONTROL_NET_WEIGHTS').Input('cn_weights', optional=True),
"null_latent_kf_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.001},), io.Custom('LATENT_KEYFRAME').Input('latent_keyframe', optional=True),
"inherit_missing": ("BOOLEAN", {"default": True},), io.Float.Input('null_latent_kf_strength', optional=True, default=0.0, max=10.0, min=0.0, step=0.001),
"mask_optional": ("MASK", ), io.Boolean.Input('inherit_missing', optional=True, default=True),
"print_keyframes": ("BOOLEAN", {"default": False}), io.Mask.Input('mask_optional', display_name='mask', optional=True),
}, io.Boolean.Input('print_keyframes', optional=True, default=False)
} ],
outputs=[
io.Custom('TIMESTEP_KEYFRAME').Output('TIMESTEP_KF', is_output_list=False)
]
)
RETURN_NAMES = ("TIMESTEP_KF", )
RETURN_TYPES = ("TIMESTEP_KEYFRAME", )
FUNCTION = "load_keyframe"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" @classmethod
def execute(cls,
def load_keyframe(self,
start_percent: float, end_percent: float, start_percent: float, end_percent: float,
strength_start: float, strength_end: float, interpolation: str, intervals: int, strength_start: float, strength_end: float, interpolation: str, intervals: int,
cn_weights: ControlWeights=None, cn_weights: ControlWeights=None,
@@ -119,36 +119,35 @@ class TimestepKeyframeInterpolationNode:
guarantee_steps=guarantee_steps, mask_hint_orig=mask_optional)) guarantee_steps=guarantee_steps, mask_hint_orig=mask_optional))
if print_keyframes: if print_keyframes:
logger.info(f"TimestepKeyframe - start_percent:{percent} = {strength}") logger.info(f"TimestepKeyframe - start_percent:{percent} = {strength}")
return (prev_timestep_kf,) return io.NodeOutput(prev_timestep_kf,)
class TimestepKeyframeFromStrengthListNode(io.ComfyNode):
class TimestepKeyframeFromStrengthListNode:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_TimestepKeyframeFromStrengthList',
"float_strengths": ("FLOAT", {"default": -1, "min": -1, "step": 0.001, "forceInput": True}), display_name='Timestep Keyframe From List 🛂🅐🅒🅝',
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001},), category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), inputs=[
}, io.Float.Input('float_strengths', default=-1, force_input=True, min=-1, step=0.001),
"optional": { io.Float.Input('start_percent', default=0.0, max=1.0, min=0.0, step=0.001),
"prev_timestep_kf": ("TIMESTEP_KEYFRAME", ), io.Float.Input('end_percent', default=1.0, max=1.0, min=0.0, step=0.001),
"cn_weights": ("CONTROL_NET_WEIGHTS", ), io.Custom('TIMESTEP_KEYFRAME').Input('prev_timestep_kf', optional=True),
"latent_keyframe": ("LATENT_KEYFRAME", ), io.Custom('CONTROL_NET_WEIGHTS').Input('cn_weights', optional=True),
"null_latent_kf_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.001},), io.Custom('LATENT_KEYFRAME').Input('latent_keyframe', optional=True),
"inherit_missing": ("BOOLEAN", {"default": True},), io.Float.Input('null_latent_kf_strength', optional=True, default=0.0, max=10.0, min=0.0, step=0.001),
"mask_optional": ("MASK", ), io.Boolean.Input('inherit_missing', optional=True, default=True),
"print_keyframes": ("BOOLEAN", {"default": False}), io.Mask.Input('mask_optional', display_name='mask', optional=True),
}, io.Boolean.Input('print_keyframes', optional=True, default=False)
} ],
outputs=[
io.Custom('TIMESTEP_KEYFRAME').Output('TIMESTEP_KF', is_output_list=False)
]
)
RETURN_NAMES = ("TIMESTEP_KF", )
RETURN_TYPES = ("TIMESTEP_KEYFRAME", )
FUNCTION = "load_keyframe"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" @classmethod
def execute(cls,
def load_keyframe(self,
start_percent: float, end_percent: float, start_percent: float, end_percent: float,
float_strengths: float, float_strengths: float,
cn_weights: ControlWeights=None, cn_weights: ControlWeights=None,
@@ -182,29 +181,27 @@ class TimestepKeyframeFromStrengthListNode:
guarantee_steps=guarantee_steps, mask_hint_orig=mask_optional)) guarantee_steps=guarantee_steps, mask_hint_orig=mask_optional))
if print_keyframes: if print_keyframes:
logger.info(f"TimestepKeyframe - start_percent:{percent} = {strength}") logger.info(f"TimestepKeyframe - start_percent:{percent} = {strength}")
return (prev_timestep_kf,) return io.NodeOutput(prev_timestep_kf,)
class LatentKeyframeNode(io.ComfyNode):
class LatentKeyframeNode:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='LatentKeyframe',
"batch_index": ("INT", {"default": 0, "min": BIGMIN, "max": BIGMAX, "step": 1}), display_name='Latent Keyframe 🛂🅐🅒🅝',
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
}, inputs=[
"optional": { io.Int.Input('batch_index', default=0, max=9007199254740991, min=-9007199254740991, step=1),
"prev_latent_kf": ("LATENT_KEYFRAME", ), io.Float.Input('strength', default=1.0, max=10.0, min=0.0, step=0.001),
}, io.Custom('LATENT_KEYFRAME').Input('prev_latent_kf', optional=True)
} ],
outputs=[
io.Custom('LATENT_KEYFRAME').Output('LATENT_KF', is_output_list=False)
]
)
RETURN_NAMES = ("LATENT_KF", ) @classmethod
RETURN_TYPES = ("LATENT_KEYFRAME", ) def execute(cls,
FUNCTION = "load_keyframe"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes"
def load_keyframe(self,
batch_index: int, batch_index: int,
strength: float, strength: float,
prev_latent_kf: LatentKeyframeGroup=None, prev_latent_kf: LatentKeyframeGroup=None,
@@ -217,30 +214,29 @@ class LatentKeyframeNode:
prev_latent_keyframe = prev_latent_keyframe.clone() prev_latent_keyframe = prev_latent_keyframe.clone()
keyframe = LatentKeyframe(batch_index, strength) keyframe = LatentKeyframe(batch_index, strength)
prev_latent_keyframe.add(keyframe) prev_latent_keyframe.add(keyframe)
return (prev_latent_keyframe,) return io.NodeOutput(prev_latent_keyframe,)
class LatentKeyframeGroupNode(io.ComfyNode):
class LatentKeyframeGroupNode:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='LatentKeyframeGroup',
"index_strengths": ("STRING", {"multiline": True, "default": ""}), display_name='Latent Keyframe Group 🛂🅐🅒🅝',
}, category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
"optional": { inputs=[
"prev_latent_kf": ("LATENT_KEYFRAME", ), io.String.Input('index_strengths', default='', multiline=True),
"latent_optional": ("LATENT", ), io.Custom('LATENT_KEYFRAME').Input('prev_latent_kf', optional=True),
"print_keyframes": ("BOOLEAN", {"default": False}), io.Latent.Input('latent_optional', display_name='latent', optional=True),
}, io.Boolean.Input('print_keyframes', optional=True, default=False)
} ],
outputs=[
io.Custom('LATENT_KEYFRAME').Output('LATENT_KF', is_output_list=False)
]
)
RETURN_NAMES = ("LATENT_KF", )
RETURN_TYPES = ("LATENT_KEYFRAME", )
FUNCTION = "load_keyframes"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" @staticmethod
def validate_index(index: int, latent_count: int = 0, is_range: bool = False, allow_negative = False) -> int:
def validate_index(self, index: int, latent_count: int = 0, is_range: bool = False, allow_negative = False) -> int:
# if part of range, do nothing # if part of range, do nothing
if is_range: if is_range:
return index return index
@@ -258,13 +254,15 @@ class LatentKeyframeGroupNode:
index = conv_index index = conv_index
return index return index
def convert_to_index_int(self, raw_index: str, latent_count: int = 0, is_range: bool = False, allow_negative = False) -> int: @classmethod
def convert_to_index_int(cls, raw_index: str, latent_count: int = 0, is_range: bool = False, allow_negative = False) -> int:
try: try:
return self.validate_index(int(raw_index), latent_count=latent_count, is_range=is_range, allow_negative=allow_negative) return cls.validate_index(int(raw_index), latent_count=latent_count, is_range=is_range, allow_negative=allow_negative)
except ValueError as e: except ValueError as e:
raise ValueError(f"index '{raw_index}' must be an integer.", e) raise ValueError(f"index '{raw_index}' must be an integer.", e)
def convert_to_latent_keyframes(self, latent_indeces: str, latent_count: int) -> set[LatentKeyframe]: @classmethod
def convert_to_latent_keyframes(cls, latent_indeces: str, latent_count: int) -> set[LatentKeyframe]:
if not latent_indeces: if not latent_indeces:
return set() return set()
int_latent_indeces = [i for i in range(0, latent_count)] int_latent_indeces = [i for i in range(0, latent_count)]
@@ -289,8 +287,8 @@ class LatentKeyframeGroupNode:
if ':' in g: if ':' in g:
index_range = g.split(":", 1) index_range = g.split(":", 1)
index_range = [r.strip() for r in index_range] index_range = [r.strip() for r in index_range]
start_index = self.convert_to_index_int(index_range[0], latent_count=latent_count, is_range=True, allow_negative=allow_negative) start_index = cls.convert_to_index_int(index_range[0], latent_count=latent_count, is_range=True, allow_negative=allow_negative)
end_index = self.convert_to_index_int(index_range[1], latent_count=latent_count, is_range=True, allow_negative=allow_negative) end_index = cls.convert_to_index_int(index_range[1], latent_count=latent_count, is_range=True, allow_negative=allow_negative)
# if latents were passed in, base indeces on known latent count # if latents were passed in, base indeces on known latent count
if len(int_latent_indeces) > 0: if len(int_latent_indeces) > 0:
for i in int_latent_indeces[start_index:end_index]: for i in int_latent_indeces[start_index:end_index]:
@@ -301,14 +299,16 @@ class LatentKeyframeGroupNode:
chosen_indeces.add(LatentKeyframe(i, strength)) chosen_indeces.add(LatentKeyframe(i, strength))
# parse individual indeces # parse individual indeces
else: else:
chosen_indeces.add(LatentKeyframe(self.convert_to_index_int(g, latent_count=latent_count, allow_negative=allow_negative), strength)) chosen_indeces.add(LatentKeyframe(cls.convert_to_index_int(g, latent_count=latent_count, allow_negative=allow_negative), strength))
return chosen_indeces return chosen_indeces
def load_keyframes(self, @classmethod
def execute(cls,
index_strengths: str, index_strengths: str,
prev_latent_kf: LatentKeyframeGroup=None, prev_latent_kf: LatentKeyframeGroup=None,
prev_latent_keyframe: LatentKeyframeGroup=None, # old name prev_latent_keyframe: LatentKeyframeGroup=None, # old name
latent_image_opt=None, latent_optional=None,
latent_image_opt=None, # old name
print_keyframes=False): print_keyframes=False):
prev_latent_keyframe = prev_latent_keyframe if prev_latent_keyframe else prev_latent_kf prev_latent_keyframe = prev_latent_keyframe if prev_latent_keyframe else prev_latent_kf
if not prev_latent_keyframe: if not prev_latent_keyframe:
@@ -317,10 +317,11 @@ class LatentKeyframeGroupNode:
prev_latent_keyframe = prev_latent_keyframe.clone() prev_latent_keyframe = prev_latent_keyframe.clone()
curr_latent_keyframe = LatentKeyframeGroup() curr_latent_keyframe = LatentKeyframeGroup()
latent_image_opt = latent_image_opt if latent_image_opt is not None else latent_optional
latent_count = -1 latent_count = -1
if latent_image_opt: if latent_image_opt:
latent_count = latent_image_opt['samples'].size()[0] latent_count = latent_image_opt['samples'].size()[0]
latent_keyframes = self.convert_to_latent_keyframes(index_strengths, latent_count=latent_count) latent_keyframes = cls.convert_to_latent_keyframes(index_strengths, latent_count=latent_count)
for latent_keyframe in latent_keyframes: for latent_keyframe in latent_keyframes:
curr_latent_keyframe.add(latent_keyframe) curr_latent_keyframe.add(latent_keyframe)
@@ -333,32 +334,32 @@ class LatentKeyframeGroupNode:
for latent_keyframe in prev_latent_keyframe.keyframes: for latent_keyframe in prev_latent_keyframe.keyframes:
curr_latent_keyframe.add(latent_keyframe) curr_latent_keyframe.add(latent_keyframe)
return (curr_latent_keyframe,) return io.NodeOutput(curr_latent_keyframe,)
class LatentKeyframeInterpolationNode: class LatentKeyframeInterpolationNode(io.ComfyNode):
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='LatentKeyframeTiming',
"batch_index_from": ("INT", {"default": 0, "min": BIGMIN, "max": BIGMAX, "step": 1}), display_name='Latent Keyframe Interp. 🛂🅐🅒🅝',
"batch_index_to_excl": ("INT", {"default": 0, "min": BIGMIN, "max": BIGMAX, "step": 1}), category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
"strength_from": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"strength_to": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Int.Input('batch_index_from', default=0, max=9007199254740991, min=-9007199254740991, step=1),
"interpolation": (SI._LIST, ), io.Int.Input('batch_index_to_excl', default=0, max=9007199254740991, min=-9007199254740991, step=1),
}, io.Float.Input('strength_from', default=1.0, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('strength_to', default=1.0, max=10.0, min=0.0, step=0.001),
"prev_latent_kf": ("LATENT_KEYFRAME", ), io.Combo.Input('interpolation', options=['linear', 'ease-in', 'ease-out', 'ease-in-out']),
"print_keyframes": ("BOOLEAN", {"default": False}), io.Custom('LATENT_KEYFRAME').Input('prev_latent_kf', optional=True),
}, io.Boolean.Input('print_keyframes', optional=True, default=False)
} ],
outputs=[
io.Custom('LATENT_KEYFRAME').Output('LATENT_KF', is_output_list=False)
]
)
RETURN_NAMES = ("LATENT_KF", ) @classmethod
RETURN_TYPES = ("LATENT_KEYFRAME", ) def execute(cls,
FUNCTION = "load_keyframe"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes"
def load_keyframe(self,
batch_index_from: int, batch_index_from: int,
strength_from: float, strength_from: float,
batch_index_to_excl: int, batch_index_to_excl: int,
@@ -407,28 +408,27 @@ class LatentKeyframeInterpolationNode:
for latent_keyframe in prev_latent_keyframe.keyframes: for latent_keyframe in prev_latent_keyframe.keyframes:
curr_latent_keyframe.add(latent_keyframe) curr_latent_keyframe.add(latent_keyframe)
return (curr_latent_keyframe,) return io.NodeOutput(curr_latent_keyframe,)
class LatentKeyframeBatchedGroupNode(io.ComfyNode):
class LatentKeyframeBatchedGroupNode:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='LatentKeyframeBatchedGroup',
"float_strengths": ("FLOAT", {"default": -1, "min": -1, "step": 0.001, "forceInput": True}), display_name='Latent Keyframe From List 🛂🅐🅒🅝',
}, category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
"optional": { inputs=[
"prev_latent_kf": ("LATENT_KEYFRAME", ), io.Float.Input('float_strengths', default=-1, force_input=True, min=-1, step=0.001),
"print_keyframes": ("BOOLEAN", {"default": False}), io.Custom('LATENT_KEYFRAME').Input('prev_latent_kf', optional=True),
}, io.Boolean.Input('print_keyframes', optional=True, default=False)
} ],
outputs=[
io.Custom('LATENT_KEYFRAME').Output('LATENT_KF', is_output_list=False)
]
)
RETURN_NAMES = ("LATENT_KF", ) @classmethod
RETURN_TYPES = ("LATENT_KEYFRAME", ) def execute(cls, float_strengths: Union[float, list[float]],
FUNCTION = "load_keyframe"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes"
def load_keyframe(self, float_strengths: Union[float, list[float]],
prev_latent_kf: LatentKeyframeGroup=None, prev_latent_kf: LatentKeyframeGroup=None,
prev_latent_keyframe: LatentKeyframeGroup=None, # old name prev_latent_keyframe: LatentKeyframeGroup=None, # old name
print_keyframes=False): print_keyframes=False):
@@ -458,4 +458,4 @@ class LatentKeyframeBatchedGroupNode:
for latent_keyframe in prev_latent_keyframe.keyframes: for latent_keyframe in prev_latent_keyframe.keyframes:
curr_latent_keyframe.add(latent_keyframe) curr_latent_keyframe.add(latent_keyframe)
return (curr_latent_keyframe,) return io.NodeOutput(curr_latent_keyframe,)
+129 -121
View File
@@ -1,123 +1,130 @@
from comfy_api.latest import io
from torch import Tensor from torch import Tensor
import folder_paths import folder_paths
from comfy.model_patcher import ModelPatcher import comfy.utils
from .control import load_controlnet, convert_to_advanced, is_advanced_controlnet, is_sd3_advanced_controlnet from .control import load_controlnet, convert_to_advanced, is_advanced_controlnet, is_sd3_advanced_controlnet
from .control_lllite import load_anima_lllite from .control_lllite import load_anima_lllite
from .utils import ControlWeights, LatentKeyframeGroup, TimestepKeyframeGroup, AbstractPreprocWrapper, BIGMAX from .utils import ControlWeights, LatentKeyframeGroup, TimestepKeyframeGroup, AbstractPreprocWrapper
from .logger import logger class ControlNetLoaderAdvanced(io.ComfyNode):
class ControlNetLoaderAdvanced:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_ControlNetLoaderAdvanced',
"cnet": (folder_paths.get_filename_list("controlnet"), ), display_name='Load Advanced ControlNet Model 🛂🅐🅒🅝',
}, category='Adv-ControlNet 🛂🅐🅒🅝',
"optional": { inputs=[
"_tk_opt": ("TIMESTEP_KEYFRAME", ), io.Combo.Input('cnet', options=folder_paths.get_filename_list("controlnet")),
} io.Custom('TIMESTEP_KEYFRAME').Input('_tk_opt', display_name='timestep_kf', optional=True)
} ],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET", ) @classmethod
FUNCTION = "load_controlnet" def execute(cls, cnet,
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝"
def load_controlnet(self, cnet,
_tk_opt: TimestepKeyframeGroup=None, _tk_opt: TimestepKeyframeGroup=None,
): ):
controlnet_path = folder_paths.get_full_path("controlnet", cnet) controlnet_path = folder_paths.get_full_path("controlnet", cnet)
controlnet = load_controlnet(controlnet_path, _tk_opt) controlnet = load_controlnet(controlnet_path, _tk_opt)
return (controlnet,) return io.NodeOutput(controlnet,)
class DiffControlNetLoaderAdvanced: class DiffControlNetLoaderAdvanced(io.ComfyNode):
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_DiffControlNetLoaderAdvanced',
"model": ("MODEL",), display_name='Load Advanced ControlNet Model (diff) 🛂🅐🅒🅝',
"cnet": (folder_paths.get_filename_list("controlnet"), ) category='Adv-ControlNet 🛂🅐🅒🅝',
}, inputs=[
"optional": { io.Model.Input('model'),
"_tk_opt": ("TIMESTEP_KEYFRAME", ), io.Combo.Input('cnet', options=folder_paths.get_filename_list("controlnet")),
}, io.Custom('TIMESTEP_KEYFRAME').Input('_tk_opt', display_name='timestep_kf', optional=True)
} ],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET", )
FUNCTION = "load_controlnet"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝" @classmethod
def execute(cls, cnet, model,
def load_controlnet(self, cnet, model,
_tk_opt: TimestepKeyframeGroup=None, _tk_opt: TimestepKeyframeGroup=None,
): ):
controlnet_path = folder_paths.get_full_path("controlnet", cnet) controlnet_path = folder_paths.get_full_path("controlnet", cnet)
controlnet = load_controlnet(controlnet_path, _tk_opt, model) controlnet = load_controlnet(controlnet_path, _tk_opt, model)
if is_advanced_controlnet(controlnet): if is_advanced_controlnet(controlnet):
controlnet.verify_all_weights() controlnet.verify_all_weights()
return (controlnet,) return io.NodeOutput(controlnet,)
class AnimaLLLiteLoaderAdvanced(io.ComfyNode):
class AnimaLLLiteLoaderAdvanced:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_AnimaLLLiteLoaderAdvanced',
"model_patch": (folder_paths.get_filename_list("model_patches"), ), display_name='Load Anima LLLite Model 🛂🅐🅒🅝',
}, category='Adv-ControlNet 🛂🅐🅒🅝/loaders',
"optional": { inputs=[
"timestep_kf": ("TIMESTEP_KEYFRAME", ), io.Combo.Input('model_patch', options=folder_paths.get_filename_list("model_patches")),
}, io.Custom('TIMESTEP_KEYFRAME').Input('timestep_kf', optional=True)
} ],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET", ) @classmethod
FUNCTION = "load_controlnet" def execute(cls, model_patch, timestep_kf: TimestepKeyframeGroup=None):
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/loaders"
def load_controlnet(self, model_patch, timestep_kf: TimestepKeyframeGroup=None):
model_patch_path = folder_paths.get_full_path_or_raise("model_patches", model_patch) model_patch_path = folder_paths.get_full_path_or_raise("model_patches", model_patch)
return (load_anima_lllite(model_patch_path, timestep_keyframe=timestep_kf),) return io.NodeOutput(load_anima_lllite(model_patch_path, timestep_keyframe=timestep_kf),)
class AdvancedControlNetApply(io.ComfyNode):
class AdvancedControlNetApply:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_AdvancedControlNetApply_v2',
"positive": ("CONDITIONING", ), display_name='Apply Advanced ControlNet 🛂🅐🅒🅝',
"negative": ("CONDITIONING", ), category='Adv-ControlNet 🛂🅐🅒🅝',
"control_net": ("CONTROL_NET", ), inputs=[
"image": ("IMAGE", ), io.Conditioning.Input('positive'),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), io.Conditioning.Input('negative'),
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), io.ControlNet.Input('control_net'),
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}) io.Image.Input('image'),
}, io.Float.Input('strength', default=1.0, max=10.0, min=0.0, step=0.01),
"optional": { io.Float.Input('start_percent', default=0.0, max=1.0, min=0.0, step=0.001),
"mask_optional": ("MASK", ), io.Float.Input('end_percent', default=1.0, max=1.0, min=0.0, step=0.001),
"timestep_kf": ("TIMESTEP_KEYFRAME", ), io.Mask.Input('mask_optional', display_name='effect_mask', optional=True),
"latent_kf_override": ("LATENT_KEYFRAME", ), io.Custom('TIMESTEP_KEYFRAME').Input('timestep_kf', optional=True),
"weights_override": ("CONTROL_NET_WEIGHTS", ), io.Custom('LATENT_KEYFRAME').Input('latent_kf_override', optional=True),
"vae_optional": ("VAE",), io.Custom('CONTROL_NET_WEIGHTS').Input('weights_override', optional=True),
}, io.Vae.Input('vae_optional', display_name='vae', optional=True),
} io.Mask.Input('inpaint_mask', optional=True)
],
outputs=[
io.Conditioning.Output('positive', is_output_list=False),
io.Conditioning.Output('negative', is_output_list=False)
]
)
RETURN_TYPES = ("CONDITIONING","CONDITIONING",) @classmethod
RETURN_NAMES = ("positive", "negative") def execute(cls, positive, negative, control_net, image, strength, start_percent, end_percent,
FUNCTION = "apply_controlnet"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝"
def apply_controlnet(self, positive, negative, control_net, image, strength, start_percent, end_percent,
mask_optional: Tensor=None, vae_optional=None, mask_optional: Tensor=None, vae_optional=None,
timestep_kf: TimestepKeyframeGroup=None, latent_kf_override: LatentKeyframeGroup=None, timestep_kf: TimestepKeyframeGroup=None, latent_kf_override: LatentKeyframeGroup=None,
weights_override: ControlWeights=None, control_apply_to_uncond=False): weights_override: ControlWeights=None, control_apply_to_uncond=False,
if strength == 0: inpaint_mask: Tensor=None):
return (positive, negative) if strength == 0 or (mask_optional is not None and mask_optional.count_nonzero().item() == 0):
return io.NodeOutput(positive, negative)
extra_concat = []
if inpaint_mask is not None and getattr(control_net, "concat_mask", False):
source_mask = 1.0 - inpaint_mask.reshape((-1, 1, inpaint_mask.shape[-2], inpaint_mask.shape[-1]))
mask_apply = comfy.utils.common_upscale(source_mask, image.shape[2], image.shape[1], "bilinear", "center").round()
image = image * mask_apply.movedim(1, -1).repeat(1, 1, 1, image.shape[3])
extra_concat = [source_mask]
control_hint = image.movedim(-1,1) control_hint = image.movedim(-1,1)
cnets = {} cnets = {}
@@ -137,7 +144,7 @@ class AdvancedControlNetApply:
if control_net is None: if control_net is None:
raise Exception("Passed in control_net is None; something must have went wrong when loading it from a Load ControlNet node.") raise Exception("Passed in control_net is None; something must have went wrong when loading it from a Load ControlNet node.")
# copy, convert to advanced if needed, and set cond # copy, convert to advanced if needed, and set cond
c_net = convert_to_advanced(control_net.copy()).set_cond_hint(control_hint, strength, (start_percent, end_percent), vae_optional) c_net = convert_to_advanced(control_net.copy()).set_cond_hint(control_hint, strength, (start_percent, end_percent), vae_optional, extra_concat)
if is_advanced_controlnet(c_net): if is_advanced_controlnet(c_net):
# disarm node check # disarm node check
c_net.disarm() c_net.disarm()
@@ -156,9 +163,9 @@ class AdvancedControlNetApply:
elif not vae_optional: elif not vae_optional:
# make sure SD3 ControlNet will get a special message instead of generic type mention # make sure SD3 ControlNet will get a special message instead of generic type mention
if is_sd3_advanced_controlnet(c_net): if is_sd3_advanced_controlnet(c_net):
raise Exception(f"SD3 ControlNet requires vae_optional input, but got None.") raise Exception(f"SD3 ControlNet requires vae input, but got None.")
else: else:
raise Exception(f"Type '{type(c_net).__name__}' requires vae_optional input, but got None.") raise Exception(f"Type '{type(c_net).__name__}' requires vae input, but got None.")
# apply optional parameters and overrides, if provided # apply optional parameters and overrides, if provided
if timestep_kf is not None: if timestep_kf is not None:
c_net.set_timestep_keyframes(timestep_kf) c_net.set_timestep_keyframes(timestep_kf)
@@ -183,43 +190,44 @@ class AdvancedControlNetApply:
n = [t[0], d] n = [t[0], d]
c.append(n) c.append(n)
out.append(c) out.append(c)
return (out[0], out[1]) return io.NodeOutput(out[0], out[1])
class AdvancedControlNetApplySingle: class AdvancedControlNetApplySingle(io.ComfyNode):
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_AdvancedControlNetApplySingle_v2',
"conditioning": ("CONDITIONING", ), display_name='Apply Advanced ControlNet(1) 🛂🅐🅒🅝',
"control_net": ("CONTROL_NET", ), category='Adv-ControlNet 🛂🅐🅒🅝',
"image": ("IMAGE", ), inputs=[
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), io.Conditioning.Input('conditioning'),
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), io.ControlNet.Input('control_net'),
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}) io.Image.Input('image'),
}, io.Float.Input('strength', default=1.0, max=10.0, min=0.0, step=0.01),
"optional": { io.Float.Input('start_percent', default=0.0, max=1.0, min=0.0, step=0.001),
"mask_optional": ("MASK", ), io.Float.Input('end_percent', default=1.0, max=1.0, min=0.0, step=0.001),
"timestep_kf": ("TIMESTEP_KEYFRAME", ), io.Mask.Input('mask_optional', display_name='effect_mask', optional=True),
"latent_kf_override": ("LATENT_KEYFRAME", ), io.Custom('TIMESTEP_KEYFRAME').Input('timestep_kf', optional=True),
"weights_override": ("CONTROL_NET_WEIGHTS", ), io.Custom('LATENT_KEYFRAME').Input('latent_kf_override', optional=True),
"vae_optional": ("VAE",), io.Custom('CONTROL_NET_WEIGHTS').Input('weights_override', optional=True),
}, io.Vae.Input('vae_optional', display_name='vae', optional=True),
} io.Mask.Input('inpaint_mask', optional=True)
],
outputs=[
io.Conditioning.Output('CONDITIONING', is_output_list=False),
io.Model.Output('model_opt', is_output_list=False)
]
)
RETURN_TYPES = ("CONDITIONING","MODEL",) @classmethod
RETURN_NAMES = ("CONDITIONING", "model_opt") def execute(cls, conditioning, control_net, image, strength, start_percent, end_percent,
FUNCTION = "apply_controlnet"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝"
def apply_controlnet(self, conditioning, control_net, image, strength, start_percent, end_percent,
mask_optional: Tensor=None, vae_optional=None, mask_optional: Tensor=None, vae_optional=None,
timestep_kf: TimestepKeyframeGroup=None, latent_kf_override: LatentKeyframeGroup=None, timestep_kf: TimestepKeyframeGroup=None, latent_kf_override: LatentKeyframeGroup=None,
weights_override: ControlWeights=None): weights_override: ControlWeights=None, inpaint_mask: Tensor=None):
values = AdvancedControlNetApply.apply_controlnet(self, positive=conditioning, negative=None, control_net=control_net, image=image, values = AdvancedControlNetApply.execute(positive=conditioning, negative=None, control_net=control_net, image=image,
strength=strength, start_percent=start_percent, end_percent=end_percent, strength=strength, start_percent=start_percent, end_percent=end_percent,
mask_optional=mask_optional, vae_optional=vae_optional, mask_optional=mask_optional, vae_optional=vae_optional,
timestep_kf=timestep_kf, latent_kf_override=latent_kf_override, weights_override=weights_override, timestep_kf=timestep_kf, latent_kf_override=latent_kf_override, weights_override=weights_override,
control_apply_to_uncond=True) control_apply_to_uncond=True, inpaint_mask=inpaint_mask)
return (values[0],) return io.NodeOutput(values.args[0], None)
+55 -51
View File
@@ -1,78 +1,82 @@
from comfy_api.latest import io
from torch import Tensor from torch import Tensor
import math import math
import folder_paths import folder_paths
from .control_plusplus import load_controlnetplusplus, PlusPlusType, PlusPlusInput, PlusPlusInputGroup, PlusPlusImageWrapper from .control_plusplus import load_controlnetplusplus, PlusPlusInput, PlusPlusInputGroup, PlusPlusImageWrapper
from .utils import BIGMAX
class PlusPlusLoaderAdvanced(io.ComfyNode):
class PlusPlusLoaderAdvanced:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_ControlNet++LoaderAdvanced',
"plus_input": ("PLUS_INPUT", ), display_name='Load ControlNet++ Model (Multi) 🛂🅐🅒🅝',
"name": (folder_paths.get_filename_list("controlnet"), ), category='Adv-ControlNet 🛂🅐🅒🅝/ControlNet++',
} inputs=[
} io.Custom('PLUS_INPUT').Input('plus_input'),
io.Combo.Input('name', options=folder_paths.get_filename_list("controlnet"))
],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False),
io.Image.Output('IMAGE', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET", "IMAGE",)
FUNCTION = "load_controlnet_plusplus"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/ControlNet++" @classmethod
def execute(cls, plus_input: PlusPlusInputGroup, name: str):
def load_controlnet_plusplus(self, plus_input: PlusPlusInputGroup, name: str):
controlnet_path = folder_paths.get_full_path("controlnet", name) controlnet_path = folder_paths.get_full_path("controlnet", name)
controlnet = load_controlnetplusplus(controlnet_path) controlnet = load_controlnetplusplus(controlnet_path)
controlnet.verify_control_type(name, plus_input) controlnet.verify_control_type(name, plus_input)
controlnet.allow_condhint_latents = True controlnet.allow_condhint_latents = True
return (controlnet, PlusPlusImageWrapper(plus_input),) return io.NodeOutput(controlnet, PlusPlusImageWrapper(plus_input),)
class PlusPlusLoaderSingle(io.ComfyNode):
class PlusPlusLoaderSingle:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_ControlNet++LoaderSingle',
"name": (folder_paths.get_filename_list("controlnet"), ), display_name='Load ControlNet++ Model (Single) 🛂🅐🅒🅝',
"control_type": (PlusPlusType._LIST_WITH_NONE, {"default": PlusPlusType.NONE}, ), category='Adv-ControlNet 🛂🅐🅒🅝/ControlNet++',
} inputs=[
} io.Combo.Input('name', options=folder_paths.get_filename_list("controlnet")),
io.Combo.Input('control_type', options=['openpose', 'depth', 'hed/pidi/scribble/ted', 'canny/lineart/mlsd', 'normal', 'segment', 'tile', 'inpaint/outpaint', 'none'], default='none')
],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET",)
FUNCTION = "load_controlnet_plusplus"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/ControlNet++" @classmethod
def execute(cls, name: str, control_type: str):
def load_controlnet_plusplus(self, name: str, control_type: str):
controlnet_path = folder_paths.get_full_path("controlnet", name) controlnet_path = folder_paths.get_full_path("controlnet", name)
controlnet = load_controlnetplusplus(controlnet_path) controlnet = load_controlnetplusplus(controlnet_path)
controlnet.single_control_type = control_type controlnet.single_control_type = control_type
controlnet.verify_control_type(name) controlnet.verify_control_type(name)
return (controlnet,) return io.NodeOutput(controlnet,)
class PlusPlusInputNode(io.ComfyNode):
class PlusPlusInputNode:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_ControlNet++InputNode',
"image": ("IMAGE",), display_name='ControlNet++ Input 🛂🅐🅒🅝',
"control_type": (PlusPlusType._LIST,), category='Adv-ControlNet 🛂🅐🅒🅝/ControlNet++',
}, inputs=[
"optional": { io.Image.Input('image'),
"prev_plus_input": ("PLUS_INPUT",), io.Combo.Input('control_type', options=['openpose', 'depth', 'hed/pidi/scribble/ted', 'canny/lineart/mlsd', 'normal', 'segment', 'tile', 'inpaint/outpaint']),
#"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": BIGMAX, "step": 0.01}), io.Custom('PLUS_INPUT').Input('prev_plus_input', optional=True)
}, ],
} outputs=[
io.Custom('PLUS_INPUT').Output('PLUS_INPUT', is_output_list=False)
]
)
RETURN_TYPES = ("PLUS_INPUT", )
FUNCTION = "wrap_images"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/ControlNet++" @classmethod
def execute(cls, image: Tensor, control_type: str, strength=1.0, prev_plus_input: PlusPlusInputGroup=None):
def wrap_images(self, image: Tensor, control_type: str, strength=1.0, prev_plus_input: PlusPlusInputGroup=None):
if prev_plus_input is None: if prev_plus_input is None:
prev_plus_input = PlusPlusInputGroup() prev_plus_input = PlusPlusInputGroup()
prev_plus_input = prev_plus_input.clone() prev_plus_input = prev_plus_input.clone()
@@ -82,4 +86,4 @@ class PlusPlusInputNode:
pp_input = PlusPlusInput(image, control_type, strength) pp_input = PlusPlusInput(image, control_type, strength)
prev_plus_input.add(pp_input) prev_plus_input.add(pp_input)
return (prev_plus_input,) return io.NodeOutput(prev_plus_input,)
+58 -53
View File
@@ -1,3 +1,4 @@
from comfy_api.latest import io
from torch import Tensor from torch import Tensor
from nodes import VAEEncode from nodes import VAEEncode
@@ -6,77 +7,81 @@ from comfy.sd import VAE
from .control_reference import ReferenceAdvanced, ReferenceOptions, ReferenceType, ReferencePreprocWrapper from .control_reference import ReferenceAdvanced, ReferenceOptions, ReferenceType, ReferencePreprocWrapper
# node for ReferenceCN # node for ReferenceCN
class ReferenceControlNetNode: class ReferenceControlNetNode(io.ComfyNode):
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_ReferenceControlNet',
"reference_type": (ReferenceType._LIST,), display_name='Reference ControlNet 🛂🅐🅒🅝',
"style_fidelity": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), category='Adv-ControlNet 🛂🅐🅒🅝/Reference',
"ref_weight": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), inputs=[
}, io.Combo.Input('reference_type', options=['reference_attn', 'reference_adain', 'reference_attn+adain']),
} io.Float.Input('style_fidelity', default=0.5, max=1.0, min=0.0, step=0.01),
io.Float.Input('ref_weight', default=1.0, max=1.0, min=0.0, step=0.01)
],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET", )
FUNCTION = "load_controlnet"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/Reference" @classmethod
def execute(cls, reference_type: str, style_fidelity: float, ref_weight: float):
def load_controlnet(self, reference_type: str, style_fidelity: float, ref_weight: float):
ref_opts = ReferenceOptions.create_combo(reference_type=reference_type, style_fidelity=style_fidelity, ref_weight=ref_weight) ref_opts = ReferenceOptions.create_combo(reference_type=reference_type, style_fidelity=style_fidelity, ref_weight=ref_weight)
controlnet = ReferenceAdvanced(ref_opts=ref_opts, timestep_keyframes=None) controlnet = ReferenceAdvanced(ref_opts=ref_opts, timestep_keyframes=None)
return (controlnet,) return io.NodeOutput(controlnet,)
class ReferenceControlFinetune(io.ComfyNode):
class ReferenceControlFinetune:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_ReferenceControlNetFinetune',
"attn_style_fidelity": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), display_name='Reference ControlNet (Finetune) 🛂🅐🅒🅝',
"attn_ref_weight": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), category='Adv-ControlNet 🛂🅐🅒🅝/Reference',
"attn_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), inputs=[
"adain_style_fidelity": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), io.Float.Input('attn_style_fidelity', default=0.5, max=1.0, min=0.0, step=0.01),
"adain_ref_weight": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), io.Float.Input('attn_ref_weight', default=1.0, max=1.0, min=0.0, step=0.01),
"adain_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), io.Float.Input('attn_strength', default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Float.Input('adain_style_fidelity', default=0.5, max=1.0, min=0.0, step=0.01),
} io.Float.Input('adain_ref_weight', default=1.0, max=1.0, min=0.0, step=0.01),
io.Float.Input('adain_strength', default=1.0, max=1.0, min=0.0, step=0.01)
],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET", )
FUNCTION = "load_controlnet"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/Reference" @classmethod
def execute(cls,
def load_controlnet(self,
attn_style_fidelity: float, attn_ref_weight: float, attn_strength: float, attn_style_fidelity: float, attn_ref_weight: float, attn_strength: float,
adain_style_fidelity: float, adain_ref_weight: float, adain_strength: float): adain_style_fidelity: float, adain_ref_weight: float, adain_strength: float):
ref_opts = ReferenceOptions(reference_type=ReferenceType.ATTN_ADAIN, ref_opts = ReferenceOptions(reference_type=ReferenceType.ATTN_ADAIN,
attn_style_fidelity=attn_style_fidelity, attn_ref_weight=attn_ref_weight, attn_strength=attn_strength, attn_style_fidelity=attn_style_fidelity, attn_ref_weight=attn_ref_weight, attn_strength=attn_strength,
adain_style_fidelity=adain_style_fidelity, adain_ref_weight=adain_ref_weight, adain_strength=adain_strength) adain_style_fidelity=adain_style_fidelity, adain_ref_weight=adain_ref_weight, adain_strength=adain_strength)
controlnet = ReferenceAdvanced(ref_opts=ref_opts, timestep_keyframes=None) controlnet = ReferenceAdvanced(ref_opts=ref_opts, timestep_keyframes=None)
return (controlnet,) return io.NodeOutput(controlnet,)
class ReferencePreprocessorNode(io.ComfyNode):
class ReferencePreprocessorNode:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_ReferencePreprocessor',
"image": ("IMAGE", ), display_name='Reference Preproccessor 🛂🅐🅒🅝',
"vae": ("VAE", ), category='Adv-ControlNet 🛂🅐🅒🅝/Reference/preprocess',
"latent_size": ("LATENT", ), inputs=[
} io.Image.Input('image'),
} io.Vae.Input('vae'),
io.Latent.Input('latent_size')
],
outputs=[
io.Image.Output('proc_IMAGE', is_output_list=False)
]
)
RETURN_TYPES = ("IMAGE",) @classmethod
RETURN_NAMES = ("proc_IMAGE",) def execute(cls, vae: VAE, image: Tensor, latent_size: Tensor):
FUNCTION = "preprocess_images"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/Reference/preprocess"
def preprocess_images(self, vae: VAE, image: Tensor, latent_size: Tensor):
# first, resize image to match latents # first, resize image to match latents
image = image.movedim(-1,1) image = image.movedim(-1,1)
image = comfy.utils.common_upscale(image, latent_size["samples"].shape[3] * 8, latent_size["samples"].shape[2] * 8, 'nearest-exact', "center") image = comfy.utils.common_upscale(image, latent_size["samples"].shape[3] * 8, latent_size["samples"].shape[2] * 8, 'nearest-exact', "center")
@@ -87,4 +92,4 @@ class ReferencePreprocessorNode:
except Exception: except Exception:
image = VAEEncode.vae_encode_crop_pixels(image) image = VAEEncode.vae_encode_crop_pixels(image)
encoded = vae.encode(image[:,:,:,:3]) encoded = vae.encode(image[:,:,:,:3])
return (ReferencePreprocWrapper(condhint=encoded),) return io.NodeOutput(ReferencePreprocWrapper(condhint=encoded),)
+118 -112
View File
@@ -1,3 +1,4 @@
from comfy_api.latest import io
from torch import Tensor from torch import Tensor
import folder_paths import folder_paths
@@ -7,68 +8,68 @@ from comfy.sd import VAE
from .utils import TimestepKeyframeGroup from .utils import TimestepKeyframeGroup
from .control_sparsectrl import SparseMethod, SparseIndexMethod, SparseSettings, SparseSpreadMethod, PreprocSparseRGBWrapper, SparseConst, SparseContextAware, get_idx_list_from_str from .control_sparsectrl import SparseMethod, SparseIndexMethod, SparseSettings, SparseSpreadMethod, PreprocSparseRGBWrapper, SparseConst, SparseContextAware, get_idx_list_from_str
from .control import load_sparsectrl, load_controlnet, ControlNetAdvanced, SparseCtrlAdvanced from .control import load_sparsectrl, load_controlnet, ControlNetAdvanced
# node for SparseCtrl loading # node for SparseCtrl loading
class SparseCtrlLoaderAdvanced: class SparseCtrlLoaderAdvanced(io.ComfyNode):
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_SparseCtrlLoaderAdvanced',
"sparsectrl_name": (folder_paths.get_filename_list("controlnet"), ), display_name='Load SparseCtrl Model 🛂🅐🅒🅝',
"use_motion": ("BOOLEAN", {"default": True}, ), category='Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl',
"motion_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"motion_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Combo.Input('sparsectrl_name', options=folder_paths.get_filename_list("controlnet")),
}, io.Boolean.Input('use_motion', default=True),
"optional": { io.Float.Input('motion_strength', default=1.0, max=10.0, min=0.0, step=0.001),
"sparse_method": ("SPARSE_METHOD", ), io.Float.Input('motion_scale', default=1.0, max=10.0, min=0.0, step=0.001),
"tk_optional": ("TIMESTEP_KEYFRAME", ), io.Custom('SPARSE_METHOD').Input('sparse_method', optional=True),
"context_aware": (SparseContextAware.LIST, ), io.Custom('TIMESTEP_KEYFRAME').Input('tk_optional', display_name='timestep_kf', optional=True),
"sparse_hint_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Combo.Input('context_aware', optional=True, options=['nearest_hint', 'off']),
"sparse_nonhint_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('sparse_hint_mult', optional=True, default=1.0, max=10.0, min=0.0, step=0.001),
"sparse_mask_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('sparse_nonhint_mult', optional=True, default=1.0, max=10.0, min=0.0, step=0.001),
} io.Float.Input('sparse_mask_mult', optional=True, default=1.0, max=10.0, min=0.0, step=0.001)
} ],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET", )
FUNCTION = "load_controlnet"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl" @classmethod
def execute(cls, sparsectrl_name: str, use_motion: bool, motion_strength: float, motion_scale: float, sparse_method: SparseMethod=SparseSpreadMethod(), tk_optional: TimestepKeyframeGroup=None,
def load_controlnet(self, sparsectrl_name: str, use_motion: bool, motion_strength: float, motion_scale: float, sparse_method: SparseMethod=SparseSpreadMethod(), tk_optional: TimestepKeyframeGroup=None,
context_aware=SparseContextAware.NEAREST_HINT, sparse_hint_mult=1.0, sparse_nonhint_mult=1.0, sparse_mask_mult=1.0): context_aware=SparseContextAware.NEAREST_HINT, sparse_hint_mult=1.0, sparse_nonhint_mult=1.0, sparse_mask_mult=1.0):
sparsectrl_path = folder_paths.get_full_path("controlnet", sparsectrl_name) sparsectrl_path = folder_paths.get_full_path("controlnet", sparsectrl_name)
sparse_settings = SparseSettings(sparse_method=sparse_method, use_motion=use_motion, motion_strength=motion_strength, motion_scale=motion_scale, sparse_settings = SparseSettings(sparse_method=sparse_method, use_motion=use_motion, motion_strength=motion_strength, motion_scale=motion_scale,
context_aware=context_aware, context_aware=context_aware,
sparse_mask_mult=sparse_mask_mult, sparse_hint_mult=sparse_hint_mult, sparse_nonhint_mult=sparse_nonhint_mult) sparse_mask_mult=sparse_mask_mult, sparse_hint_mult=sparse_hint_mult, sparse_nonhint_mult=sparse_nonhint_mult)
sparsectrl = load_sparsectrl(sparsectrl_path, timestep_keyframe=tk_optional, sparse_settings=sparse_settings) sparsectrl = load_sparsectrl(sparsectrl_path, timestep_keyframe=tk_optional, sparse_settings=sparse_settings)
return (sparsectrl,) return io.NodeOutput(sparsectrl,)
class SparseCtrlMergedLoaderAdvanced(io.ComfyNode):
class SparseCtrlMergedLoaderAdvanced:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_SparseCtrlMergedLoaderAdvanced',
"sparsectrl_name": (folder_paths.get_filename_list("controlnet"), ), display_name='🧪Load Merged SparseCtrl Model 🛂🅐🅒🅝',
"control_net_name": (folder_paths.get_filename_list("controlnet"), ), category='Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl/experimental',
"use_motion": ("BOOLEAN", {"default": True}, ), inputs=[
"motion_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Combo.Input('sparsectrl_name', options=folder_paths.get_filename_list("controlnet")),
"motion_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Combo.Input('control_net_name', options=folder_paths.get_filename_list("controlnet")),
}, io.Boolean.Input('use_motion', default=True),
"optional": { io.Float.Input('motion_strength', default=1.0, max=10.0, min=0.0, step=0.001),
"sparse_method": ("SPARSE_METHOD", ), io.Float.Input('motion_scale', default=1.0, max=10.0, min=0.0, step=0.001),
"tk_optional": ("TIMESTEP_KEYFRAME", ), io.Custom('SPARSE_METHOD').Input('sparse_method', optional=True),
} io.Custom('TIMESTEP_KEYFRAME').Input('tk_optional', display_name='timestep_kf', optional=True)
} ],
outputs=[
io.ControlNet.Output('CONTROL_NET', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET", )
FUNCTION = "load_controlnet"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl/experimental" @classmethod
def execute(cls, sparsectrl_name: str, control_net_name: str, use_motion: bool, motion_strength: float, motion_scale: float, sparse_method: SparseMethod=SparseSpreadMethod(), tk_optional: TimestepKeyframeGroup=None):
def load_controlnet(self, sparsectrl_name: str, control_net_name: str, use_motion: bool, motion_strength: float, motion_scale: float, sparse_method: SparseMethod=SparseSpreadMethod(), tk_optional: TimestepKeyframeGroup=None):
sparsectrl_path = folder_paths.get_full_path("controlnet", sparsectrl_name) sparsectrl_path = folder_paths.get_full_path("controlnet", sparsectrl_name)
controlnet_path = folder_paths.get_full_path("controlnet", control_net_name) controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
sparse_settings = SparseSettings(sparse_method=sparse_method, use_motion=use_motion, motion_strength=motion_strength, motion_scale=motion_scale, merged=True) sparse_settings = SparseSettings(sparse_method=sparse_method, use_motion=use_motion, motion_strength=motion_strength, motion_scale=motion_scale, merged=True)
@@ -85,64 +86,68 @@ class SparseCtrlMergedLoaderAdvanced:
new_state_dict[key] = value new_state_dict[key] = value
# now, reload sparsectrl with real settings # now, reload sparsectrl with real settings
sparsectrl = load_sparsectrl(sparsectrl_path, controlnet_data=new_state_dict, timestep_keyframe=tk_optional, sparse_settings=sparse_settings) sparsectrl = load_sparsectrl(sparsectrl_path, controlnet_data=new_state_dict, timestep_keyframe=tk_optional, sparse_settings=sparse_settings)
return (sparsectrl,) return io.NodeOutput(sparsectrl,)
class SparseIndexMethodNode(io.ComfyNode):
class SparseIndexMethodNode:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_SparseCtrlIndexMethodNode',
"indexes": ("STRING", {"default": "0"}), display_name='SparseCtrl Index Method 🛂🅐🅒🅝',
} category='Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl',
} inputs=[
io.String.Input('indexes', default='0')
],
outputs=[
io.Custom('SPARSE_METHOD').Output('SPARSE_METHOD', is_output_list=False)
]
)
RETURN_TYPES = ("SPARSE_METHOD",)
FUNCTION = "get_method"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl" @classmethod
def execute(cls, indexes: str):
def get_method(self, indexes: str):
idxs = get_idx_list_from_str(indexes) idxs = get_idx_list_from_str(indexes)
return (SparseIndexMethod(idxs),) return io.NodeOutput(SparseIndexMethod(idxs),)
class SparseSpreadMethodNode(io.ComfyNode):
class SparseSpreadMethodNode:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_SparseCtrlSpreadMethodNode',
"spread": (SparseSpreadMethod.LIST,), display_name='SparseCtrl Spread Method 🛂🅐🅒🅝',
} category='Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl',
} inputs=[
io.Combo.Input('spread', options=['uniform', 'starting', 'ending', 'center'])
],
outputs=[
io.Custom('SPARSE_METHOD').Output('SPARSE_METHOD', is_output_list=False)
]
)
RETURN_TYPES = ("SPARSE_METHOD",)
FUNCTION = "get_method"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl"
def get_method(self, spread: str):
return (SparseSpreadMethod(spread=spread),)
class RgbSparseCtrlPreprocessor:
@classmethod @classmethod
def INPUT_TYPES(s): def execute(cls, spread: str):
return { return io.NodeOutput(SparseSpreadMethod(spread=spread),)
"required": {
"image": ("IMAGE", ),
"vae": ("VAE", ),
"latent_size": ("LATENT", ),
},
}
RETURN_TYPES = ("IMAGE",) class RgbSparseCtrlPreprocessor(io.ComfyNode):
RETURN_NAMES = ("proc_IMAGE",) @classmethod
FUNCTION = "preprocess_images" def define_schema(cls) -> io.Schema:
return io.Schema(
node_id='ACN_SparseCtrlRGBPreprocessor',
display_name='RGB SparseCtrl 🛂🅐🅒🅝',
category='Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl/preprocess',
inputs=[
io.Image.Input('image'),
io.Vae.Input('vae'),
io.Latent.Input('latent_size')
],
outputs=[
io.Image.Output('proc_IMAGE', is_output_list=False)
]
)
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl/preprocess" @classmethod
def execute(cls, vae: VAE, image: Tensor, latent_size: Tensor):
def preprocess_images(self, vae: VAE, image: Tensor, latent_size: Tensor):
# first, resize image to match latents # first, resize image to match latents
image = image.movedim(-1,1) image = image.movedim(-1,1)
image = comfy.utils.common_upscale(image, latent_size["samples"].shape[3] * 8, latent_size["samples"].shape[2] * 8, 'nearest-exact', "center") image = comfy.utils.common_upscale(image, latent_size["samples"].shape[3] * 8, latent_size["samples"].shape[2] * 8, 'nearest-exact', "center")
@@ -153,30 +158,31 @@ class RgbSparseCtrlPreprocessor:
except Exception: except Exception:
image = VAEEncode.vae_encode_crop_pixels(image) image = VAEEncode.vae_encode_crop_pixels(image)
encoded = vae.encode(image[:,:,:,:3]) encoded = vae.encode(image[:,:,:,:3])
return (PreprocSparseRGBWrapper(condhint=encoded),) return io.NodeOutput(PreprocSparseRGBWrapper(condhint=encoded),)
class SparseWeightExtras(io.ComfyNode):
class SparseWeightExtras:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"optional": { node_id='ACN_SparseCtrlWeightExtras',
"cn_extras": ("CN_WEIGHTS_EXTRAS",), display_name='SparseCtrl Weight Extras 🛂🅐🅒🅝',
"sparse_hint_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl/extras',
"sparse_nonhint_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"sparse_mask_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True),
}, io.Float.Input('sparse_hint_mult', optional=True, default=1.0, max=10.0, min=0.0, step=0.001),
} io.Float.Input('sparse_nonhint_mult', optional=True, default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('sparse_mask_mult', optional=True, default=1.0, max=10.0, min=0.0, step=0.001)
],
outputs=[
io.Custom('CN_WEIGHTS_EXTRAS').Output('cn_extras', is_output_list=False)
]
)
RETURN_TYPES = ("CN_WEIGHTS_EXTRAS", )
RETURN_NAMES = ("cn_extras", )
FUNCTION = "create_weight_extras"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl/extras" @classmethod
def execute(cls, cn_extras: dict[str]={}, sparse_hint_mult=1.0, sparse_nonhint_mult=1.0, sparse_mask_mult=1.0):
def create_weight_extras(self, cn_extras: dict[str]={}, sparse_hint_mult=1.0, sparse_nonhint_mult=1.0, sparse_mask_mult=1.0):
cn_extras = cn_extras.copy() cn_extras = cn_extras.copy()
cn_extras[SparseConst.HINT_MULT] = sparse_hint_mult cn_extras[SparseConst.HINT_MULT] = sparse_hint_mult
cn_extras[SparseConst.NONHINT_MULT] = sparse_nonhint_mult cn_extras[SparseConst.NONHINT_MULT] = sparse_nonhint_mult
cn_extras[SparseConst.MASK_MULT] = sparse_mask_mult cn_extras[SparseConst.MASK_MULT] = sparse_mask_mult
return (cn_extras, ) return io.NodeOutput(cn_extras, )
+281 -259
View File
@@ -1,57 +1,56 @@
from comfy_api.latest import io
from torch import Tensor from torch import Tensor
import torch import torch
from .utils import TimestepKeyframe, TimestepKeyframeGroup, ControlWeights, Extras, get_properly_arranged_t2i_weights, linear_conversion from .utils import TimestepKeyframe, TimestepKeyframeGroup, ControlWeights, Extras, get_properly_arranged_t2i_weights, linear_conversion
from .control_lllite import AnimaLLLiteConst from .control_lllite import AnimaLLLiteConst
from .logger import logger
WEIGHTS_RETURN_NAMES = ("CN_WEIGHTS", "TK_SHORTCUT") WEIGHTS_RETURN_NAMES = ("CN_WEIGHTS", "TK_SHORTCUT")
class DefaultWeights(io.ComfyNode):
class DefaultWeights:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"optional": { node_id='ACN_DefaultUniversalWeights',
"cn_extras": ("CN_WEIGHTS_EXTRAS",), display_name='Default Weights 🛂🅐🅒🅝',
}, category='Adv-ControlNet 🛂🅐🅒🅝/weights',
} inputs=[
io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = WEIGHTS_RETURN_NAMES
FUNCTION = "load_weights"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" @classmethod
def execute(cls, cn_extras: dict[str]={}):
def load_weights(self, cn_extras: dict[str]={}):
weights = ControlWeights.default(extras=cn_extras) weights = ControlWeights.default(extras=cn_extras)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class ScaledSoftMaskedUniversalWeights(io.ComfyNode):
class ScaledSoftMaskedUniversalWeights:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ScaledSoftMaskedUniversalWeights',
"mask": ("MASK", ), display_name='Scaled Soft Masked Weights 🛂🅐🅒🅝',
"min_base_multiplier": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}, ), category='Adv-ControlNet 🛂🅐🅒🅝/weights',
"max_base_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}, ), inputs=[
#"lock_min": ("BOOLEAN", {"default": False}, ), io.Mask.Input('mask'),
#"lock_max": ("BOOLEAN", {"default": False}, ), io.Float.Input('min_base_multiplier', default=0.0, max=1.0, min=0.0, step=0.001),
}, io.Float.Input('max_base_multiplier', default=1.0, max=1.0, min=0.0, step=0.001),
"optional": { io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
"cn_extras": ("CN_WEIGHTS_EXTRAS",), ],
}, outputs=[
} io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = WEIGHTS_RETURN_NAMES
FUNCTION = "load_weights"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" @classmethod
def execute(cls, mask: Tensor, min_base_multiplier: float, max_base_multiplier: float, lock_min=False, lock_max=False,
def load_weights(self, mask: Tensor, min_base_multiplier: float, max_base_multiplier: float, lock_min=False, lock_max=False,
uncond_multiplier: float=1.0, cn_extras: dict[str]={}): uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
# normalize mask # normalize mask
mask = mask.clone() mask = mask.clone()
@@ -62,107 +61,107 @@ class ScaledSoftMaskedUniversalWeights:
else: else:
mask = linear_conversion(mask, x_min, x_max, min_base_multiplier, max_base_multiplier) mask = linear_conversion(mask, x_min, x_max, min_base_multiplier, max_base_multiplier)
weights = ControlWeights.universal_mask(weight_mask=mask, uncond_multiplier=uncond_multiplier, extras=cn_extras) weights = ControlWeights.universal_mask(weight_mask=mask, uncond_multiplier=uncond_multiplier, extras=cn_extras)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class ScaledSoftUniversalWeights(io.ComfyNode):
class ScaledSoftUniversalWeights:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_ScaledSoftControlNetWeights',
"base_multiplier": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 1.0, "step": 0.001}, ), display_name='Scaled Soft Weights 🛂🅐🅒🅝',
}, category='Adv-ControlNet 🛂🅐🅒🅝/weights',
"optional": { inputs=[
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Float.Input('base_multiplier', default=0.825, max=1.0, min=0.0, step=0.001),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = WEIGHTS_RETURN_NAMES
FUNCTION = "load_weights"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" @classmethod
def execute(cls, base_multiplier, uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
def load_weights(self, base_multiplier, uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
weights = ControlWeights.universal(base_multiplier=base_multiplier, uncond_multiplier=uncond_multiplier, extras=cn_extras) weights = ControlWeights.universal(base_multiplier=base_multiplier, uncond_multiplier=uncond_multiplier, extras=cn_extras)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class SoftControlNetWeightsSD15(io.ComfyNode):
class SoftControlNetWeightsSD15:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_SoftControlNetWeightsSD15',
"output_0": ("FLOAT", {"default": 0.09941396206337118, "min": 0.0, "max": 10.0, "step": 0.001}, ), display_name='ControlNet Soft Weights [SD1.5] 🛂🅐🅒🅝',
"output_1": ("FLOAT", {"default": 0.12050177219802567, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='Adv-ControlNet 🛂🅐🅒🅝/weights/ControlNet',
"output_2": ("FLOAT", {"default": 0.14606275417942507, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"output_3": ("FLOAT", {"default": 0.17704576264172736, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_0', default=0.09941396206337118, max=10.0, min=0.0, step=0.001),
"output_4": ("FLOAT", {"default": 0.214600924414215, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_1', default=0.12050177219802567, max=10.0, min=0.0, step=0.001),
"output_5": ("FLOAT", {"default": 0.26012233262329093, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_2', default=0.14606275417942507, max=10.0, min=0.0, step=0.001),
"output_6": ("FLOAT", {"default": 0.3152997971191405, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_3', default=0.17704576264172736, max=10.0, min=0.0, step=0.001),
"output_7": ("FLOAT", {"default": 0.3821815722656249, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_4', default=0.214600924414215, max=10.0, min=0.0, step=0.001),
"output_8": ("FLOAT", {"default": 0.4632503906249999, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_5', default=0.26012233262329093, max=10.0, min=0.0, step=0.001),
"output_9": ("FLOAT", {"default": 0.561515625, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_6', default=0.3152997971191405, max=10.0, min=0.0, step=0.001),
"output_10": ("FLOAT", {"default": 0.6806249999999999, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_7', default=0.3821815722656249, max=10.0, min=0.0, step=0.001),
"output_11": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_8', default=0.4632503906249999, max=10.0, min=0.0, step=0.001),
"middle_0": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_9', default=0.561515625, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('output_10', default=0.6806249999999999, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('output_11', default=0.825, max=10.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Float.Input('middle_0', default=1.0, max=10.0, min=0.0, step=0.001),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = WEIGHTS_RETURN_NAMES
FUNCTION = "load_weights"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights/ControlNet" @classmethod
def execute(cls, output_0, output_1, output_2, output_3, output_4, output_5, output_6,
def load_weights(self, output_0, output_1, output_2, output_3, output_4, output_5, output_6,
output_7, output_8, output_9, output_10, output_11, middle_0, output_7, output_8, output_9, output_10, output_11, middle_0,
uncond_multiplier: float=1.0, cn_extras: dict[str]={}): uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
return CustomControlNetWeightsSD15.load_weights(self, return CustomControlNetWeightsSD15.execute(
output_0=output_0, output_1=output_1, output_2=output_2, output_3=output_3, output_0=output_0, output_1=output_1, output_2=output_2, output_3=output_3,
output_4=output_4, output_5=output_5, output_6=output_6, output_7=output_7, output_4=output_4, output_5=output_5, output_6=output_6, output_7=output_7,
output_8=output_8, output_9=output_9, output_10=output_10, output_11=output_11, output_8=output_8, output_9=output_9, output_10=output_10, output_11=output_11,
middle_0=middle_0, middle_0=middle_0,
uncond_multiplier=uncond_multiplier, cn_extras=cn_extras) uncond_multiplier=uncond_multiplier, cn_extras=cn_extras)
class CustomControlNetWeightsSD15(io.ComfyNode):
class CustomControlNetWeightsSD15:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_CustomControlNetWeightsSD15',
"output_0": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), display_name='ControlNet Custom Weights [SD1.5] 🛂🅐🅒🅝',
"output_1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='Adv-ControlNet 🛂🅐🅒🅝/weights/ControlNet',
"output_2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"output_3": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_0', default=1.0, max=10.0, min=0.0, step=0.001),
"output_4": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_1', default=1.0, max=10.0, min=0.0, step=0.001),
"output_5": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_2', default=1.0, max=10.0, min=0.0, step=0.001),
"output_6": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_3', default=1.0, max=10.0, min=0.0, step=0.001),
"output_7": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_4', default=1.0, max=10.0, min=0.0, step=0.001),
"output_8": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_5', default=1.0, max=10.0, min=0.0, step=0.001),
"output_9": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_6', default=1.0, max=10.0, min=0.0, step=0.001),
"output_10": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_7', default=1.0, max=10.0, min=0.0, step=0.001),
"output_11": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_8', default=1.0, max=10.0, min=0.0, step=0.001),
"middle_0": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('output_9', default=1.0, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('output_10', default=1.0, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('output_11', default=1.0, max=10.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Float.Input('middle_0', default=1.0, max=10.0, min=0.0, step=0.001),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = WEIGHTS_RETURN_NAMES
FUNCTION = "load_weights"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights/ControlNet" @classmethod
def execute(cls, output_0, output_1, output_2, output_3, output_4, output_5, output_6,
def load_weights(self, output_0, output_1, output_2, output_3, output_4, output_5, output_6,
output_7, output_8, output_9, output_10, output_11, middle_0, output_7, output_8, output_9, output_10, output_11, middle_0,
uncond_multiplier: float=1.0, cn_extras: dict[str]={}): uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
weights_output = [output_0, output_1, output_2, output_3, output_4, output_5, output_6, weights_output = [output_0, output_1, output_2, output_3, output_4, output_5, output_6,
@@ -170,47 +169,47 @@ class CustomControlNetWeightsSD15:
weights_middle = [middle_0] weights_middle = [middle_0]
weights = ControlWeights.controlnet(weights_output=weights_output, weights_middle=weights_middle, uncond_multiplier=uncond_multiplier, weights = ControlWeights.controlnet(weights_output=weights_output, weights_middle=weights_middle, uncond_multiplier=uncond_multiplier,
extras=cn_extras, disable_applied_to=True) extras=cn_extras, disable_applied_to=True)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class CustomControlNetWeightsFlux(io.ComfyNode):
class CustomControlNetWeightsFlux:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_CustomControlNetWeightsFlux',
"input_0": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), display_name='ControlNet Custom Weights [Flux] 🛂🅐🅒🅝',
"input_1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='Adv-ControlNet 🛂🅐🅒🅝/weights/ControlNet',
"input_2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"input_3": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_0', default=1.0, max=10.0, min=0.0, step=0.001),
"input_4": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_1', default=1.0, max=10.0, min=0.0, step=0.001),
"input_5": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_2', default=1.0, max=10.0, min=0.0, step=0.001),
"input_6": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_3', default=1.0, max=10.0, min=0.0, step=0.001),
"input_7": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_4', default=1.0, max=10.0, min=0.0, step=0.001),
"input_8": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_5', default=1.0, max=10.0, min=0.0, step=0.001),
"input_9": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_6', default=1.0, max=10.0, min=0.0, step=0.001),
"input_10": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_7', default=1.0, max=10.0, min=0.0, step=0.001),
"input_11": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_8', default=1.0, max=10.0, min=0.0, step=0.001),
"input_12": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_9', default=1.0, max=10.0, min=0.0, step=0.001),
"input_13": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_10', default=1.0, max=10.0, min=0.0, step=0.001),
"input_14": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_11', default=1.0, max=10.0, min=0.0, step=0.001),
"input_15": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_12', default=1.0, max=10.0, min=0.0, step=0.001),
"input_16": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_13', default=1.0, max=10.0, min=0.0, step=0.001),
"input_17": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_14', default=1.0, max=10.0, min=0.0, step=0.001),
"input_18": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_15', default=1.0, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('input_16', default=1.0, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('input_17', default=1.0, max=10.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Float.Input('input_18', default=1.0, max=10.0, min=0.0, step=0.001),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = WEIGHTS_RETURN_NAMES
FUNCTION = "load_weights"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights/ControlNet" @classmethod
def execute(cls, input_0, input_1, input_2, input_3, input_4, input_5, input_6,
def load_weights(self, input_0, input_1, input_2, input_3, input_4, input_5, input_6,
input_7, input_8, input_9, input_10, input_11, input_12, input_13, input_7, input_8, input_9, input_10, input_11, input_12, input_13,
input_14, input_15, input_16, input_17, input_18, input_14, input_15, input_16, input_17, input_18,
uncond_multiplier: float=1.0, cn_extras: dict[str]={}): uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
@@ -218,139 +217,162 @@ class CustomControlNetWeightsFlux:
input_6, input_7, input_8, input_9, input_10, input_11, input_6, input_7, input_8, input_9, input_10, input_11,
input_12, input_13, input_14, input_15, input_16, input_17, input_18] input_12, input_13, input_14, input_15, input_16, input_17, input_18]
weights = ControlWeights.controlnet(weights_input=weights_input, uncond_multiplier=uncond_multiplier, extras=cn_extras, disable_applied_to=True) weights = ControlWeights.controlnet(weights_input=weights_input, uncond_multiplier=uncond_multiplier, extras=cn_extras, disable_applied_to=True)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class CustomControlNetWeightsAnima(io.ComfyNode):
class CustomControlNetWeightsAnima:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
required = { return io.Schema(
f"block_{index}": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}) node_id='ACN_CustomControlNetWeightsAnima',
for index in range(28) display_name='ControlNet Custom Weights [Anima] 🛂🅐🅒🅝',
} category='Adv-ControlNet 🛂🅐🅒🅝/weights/ControlNet',
return { inputs=[
"required": required, io.Float.Input('block_0', default=1.0, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('block_1', default=1.0, max=10.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), io.Float.Input('block_2', default=1.0, max=10.0, min=0.0, step=0.001),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('block_3', default=1.0, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('block_4', default=1.0, max=10.0, min=0.0, step=0.001),
} io.Float.Input('block_5', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_6', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_7', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_8', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_9', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_10', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_11', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_12', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_13', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_14', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_15', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_16', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_17', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_18', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_19', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_20', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_21', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_22', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_23', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_24', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_25', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_26', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('block_27', default=1.0, max=10.0, min=0.0, step=0.001),
io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",) @classmethod
RETURN_NAMES = WEIGHTS_RETURN_NAMES def execute(cls, uncond_multiplier: float=1.0, cn_extras: dict[str]={}, **kwargs):
FUNCTION = "load_weights"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights/ControlNet"
def load_weights(self, uncond_multiplier: float=1.0, cn_extras: dict[str]={}, **kwargs):
weights = [kwargs[f"block_{index}"] for index in range(28)] weights = [kwargs[f"block_{index}"] for index in range(28)]
control_weights = ControlWeights.controllllite( control_weights = ControlWeights.controllllite(
weights_input=weights, weights_input=weights,
uncond_multiplier=uncond_multiplier, uncond_multiplier=uncond_multiplier,
extras=cn_extras, extras=cn_extras,
) )
return (control_weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=control_weights))) return io.NodeOutput(control_weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=control_weights)))
class SoftT2IAdapterWeights(io.ComfyNode):
class SoftT2IAdapterWeights:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_SoftT2IAdapterWeights',
"input_0": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 10.0, "step": 0.001}, ), display_name='T2IAdapter Soft Weights 🛂🅐🅒🅝',
"input_1": ("FLOAT", {"default": 0.62, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='Adv-ControlNet 🛂🅐🅒🅝/weights/T2IAdapter',
"input_2": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"input_3": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_0', default=0.25, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('input_1', default=0.62, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('input_2', default=0.825, max=10.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Float.Input('input_3', default=1.0, max=10.0, min=0.0, step=0.001),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = WEIGHTS_RETURN_NAMES
FUNCTION = "load_weights"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights/T2IAdapter" @classmethod
def execute(cls, input_0, input_1, input_2, input_3,
def load_weights(self, input_0, input_1, input_2, input_3,
uncond_multiplier: float=1.0, cn_extras: dict[str]={}): uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
return CustomT2IAdapterWeights.load_weights(self, input_0=input_0, input_1=input_1, input_2=input_2, input_3=input_3, return CustomT2IAdapterWeights.execute(input_0=input_0, input_1=input_1, input_2=input_2, input_3=input_3,
uncond_multiplier=uncond_multiplier, cn_extras=cn_extras) uncond_multiplier=uncond_multiplier, cn_extras=cn_extras)
class CustomT2IAdapterWeights(io.ComfyNode):
class CustomT2IAdapterWeights:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_CustomT2IAdapterWeights',
"input_0": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), display_name='T2IAdapter Custom Weights 🛂🅐🅒🅝',
"input_1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), category='Adv-ControlNet 🛂🅐🅒🅝/weights/T2IAdapter',
"input_2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), inputs=[
"input_3": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), io.Float.Input('input_0', default=1.0, max=10.0, min=0.0, step=0.001),
}, io.Float.Input('input_1', default=1.0, max=10.0, min=0.0, step=0.001),
"optional": { io.Float.Input('input_2', default=1.0, max=10.0, min=0.0, step=0.001),
"uncond_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}, ), io.Float.Input('input_3', default=1.0, max=10.0, min=0.0, step=0.001),
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('uncond_multiplier', optional=True, default=1.0, max=1.0, min=0.0, step=0.01),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CONTROL_NET_WEIGHTS').Output('CN_WEIGHTS', is_output_list=False),
io.Custom('TIMESTEP_KEYFRAME').Output('TK_SHORTCUT', is_output_list=False)
]
)
RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",)
RETURN_NAMES = WEIGHTS_RETURN_NAMES
FUNCTION = "load_weights"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights/T2IAdapter" @classmethod
def execute(cls, input_0, input_1, input_2, input_3,
def load_weights(self, input_0, input_1, input_2, input_3,
uncond_multiplier: float=1.0, cn_extras: dict[str]={}): uncond_multiplier: float=1.0, cn_extras: dict[str]={}):
weights = [input_0, input_1, input_2, input_3] weights = [input_0, input_1, input_2, input_3]
weights = get_properly_arranged_t2i_weights(weights) weights = get_properly_arranged_t2i_weights(weights)
weights = ControlWeights.t2iadapter(weights_input=weights, uncond_multiplier=uncond_multiplier, extras=cn_extras, disable_applied_to=True) weights = ControlWeights.t2iadapter(weights_input=weights, uncond_multiplier=uncond_multiplier, extras=cn_extras, disable_applied_to=True)
return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights))) return io.NodeOutput(weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_weights=weights)))
class ExtrasMiddleMultNode(io.ComfyNode):
class ExtrasMiddleMultNode:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_ExtrasMiddleMult',
"middle_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}), display_name='Middle Weight Extras 🛂🅐🅒🅝',
}, category='Adv-ControlNet 🛂🅐🅒🅝/weights/extras',
"optional": { inputs=[
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Float.Input('middle_mult', default=1.0, max=10.0, min=0.0, step=0.001),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CN_WEIGHTS_EXTRAS').Output('cn_extras', is_output_list=False)
]
)
RETURN_TYPES = ("CN_WEIGHTS_EXTRAS",)
RETURN_NAMES = ("cn_extras",)
FUNCTION = "create_extras"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights/extras" @classmethod
def execute(cls, middle_mult: float, cn_extras: dict[str]={}):
def create_extras(self, middle_mult: float, cn_extras: dict[str]={}):
cn_extras = cn_extras.copy() cn_extras = cn_extras.copy()
cn_extras[Extras.MIDDLE_MULT] = middle_mult cn_extras[Extras.MIDDLE_MULT] = middle_mult
return (cn_extras,) return io.NodeOutput(cn_extras,)
class AnimaLLLiteExtras(io.ComfyNode):
class AnimaLLLiteExtras:
@classmethod @classmethod
def INPUT_TYPES(s): def define_schema(cls) -> io.Schema:
return { return io.Schema(
"required": { node_id='ACN_AnimaLLLiteExtras',
"inpaint_mask": ("MASK",), display_name='Anima LLLite Extras 🛂🅐🅒🅝',
}, category='Adv-ControlNet 🛂🅐🅒🅝/weights/extras',
"optional": { inputs=[
"cn_extras": ("CN_WEIGHTS_EXTRAS",), io.Mask.Input('inpaint_mask'),
}, io.Custom('CN_WEIGHTS_EXTRAS').Input('cn_extras', optional=True)
} ],
outputs=[
io.Custom('CN_WEIGHTS_EXTRAS').Output('cn_extras', is_output_list=False)
]
)
RETURN_TYPES = ("CN_WEIGHTS_EXTRAS",) @classmethod
RETURN_NAMES = ("cn_extras",) def execute(cls, inpaint_mask: Tensor, cn_extras: dict[str]={}):
FUNCTION = "create_extras"
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights/extras"
def create_extras(self, inpaint_mask: Tensor, cn_extras: dict[str]={}):
cn_extras = cn_extras.copy() cn_extras = cn_extras.copy()
cn_extras[AnimaLLLiteConst.INPAINT_MASK] = inpaint_mask.clone() cn_extras[AnimaLLLiteConst.INPAINT_MASK] = inpaint_mask.clone()
return (cn_extras,) return io.NodeOutput(cn_extras,)
+1 -1
View File
@@ -359,7 +359,7 @@ def prepare_mask_batch(mask: Tensor, shape: Tensor, multiplier: int=1, match_dim
mask = mask.clone() mask = mask.clone()
if flux_shape is not None: if flux_shape is not None:
multiplier = multiplier * 0.5 multiplier = multiplier * 0.5
mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(round(flux_shape[-2]*multiplier), round(flux_shape[-1]*multiplier)), mode="bilinear") mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(math.ceil(flux_shape[-2]*multiplier), math.ceil(flux_shape[-1]*multiplier)), mode="bilinear")
mask = rearrange(mask, "b c h w -> b (h w) c") mask = rearrange(mask, "b c h w -> b (h w) c")
else: else:
mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(round(shape[-2]*multiplier), round(shape[-1]*multiplier)), mode="bilinear") mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(round(shape[-2]*multiplier), round(shape[-1]*multiplier)), mode="bilinear")
+83
View File
@@ -0,0 +1,83 @@
# Qwen Image ControlNet inpainting
This reviewer example adapts the active inpainting branch of ComfyUI's official
Qwen Image workflow. It uses **Load Advanced ControlNet Model** and **Apply
Advanced ControlNet**, while retaining the official Qwen base pipeline and the
bypassed optional Lightning LoRA. Node titles are left at their ComfyUI
defaults; the workflow stores no node title overrides.
## Inputs and models
Download the official inputs to `ComfyUI/input` with these exact names:
- [`acn_qwen_inpaint_source.png`](https://huggingface.co/InstantX/Qwen-Image-ControlNet-Inpainting/resolve/main/assets/images/image1.png)
- [`acn_qwen_inpaint_mask.png`](https://huggingface.co/InstantX/Qwen-Image-ControlNet-Inpainting/resolve/main/assets/masks/mask1.png)
The model author's repository is
[`InstantX/Qwen-Image-ControlNet-Inpainting`](https://huggingface.co/InstantX/Qwen-Image-ControlNet-Inpainting).
Download every model below to the listed folder under `ComfyUI/models`:
| File and exact download | Folder |
| --- | --- |
| [`qwen_image_fp8_e4m3fn.safetensors`](https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_fp8_e4m3fn.safetensors) | `diffusion_models` |
| [`qwen_2.5_vl_7b_fp8_scaled.safetensors`](https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b_fp8_scaled.safetensors) | `text_encoders` |
| [`qwen_image_vae.safetensors`](https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/vae/qwen_image_vae.safetensors) | `vae` |
| [`Qwen-Image-InstantX-ControlNet-Inpainting.safetensors`](https://huggingface.co/Comfy-Org/Qwen-Image-InstantX-ControlNets/resolve/main/split_files/controlnet/Qwen-Image-InstantX-ControlNet-Inpainting.safetensors) | `controlnet` |
| [`Qwen-Image-Lightning-4steps-V1.0.safetensors`](https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-4steps-V1.0.safetensors) | `loras` (optional and bypassed) |
## Run
1. Download the two inputs and five model files to the folders above.
2. Load `qwen_image_inpainting.json` in ComfyUI.
3. Queue the workflow unchanged.
For command-line input reproduction:
```sh
curl -L https://huggingface.co/InstantX/Qwen-Image-ControlNet-Inpainting/resolve/main/assets/images/image1.png -o ComfyUI/input/acn_qwen_inpaint_source.png
curl -L https://huggingface.co/InstantX/Qwen-Image-ControlNet-Inpainting/resolve/main/assets/masks/mask1.png -o ComfyUI/input/acn_qwen_inpaint_mask.png
```
The unchanged example uses seed `134554158057228` (fixed), 20 steps, CFG 2.5,
Euler, the simple scheduler, denoise 1.0, model shift 3.1, control strength 1.0,
and control start/end 0.0/1.0. Its prompt is `The Queen, on a throne,
surrounded by Knights, HD, Realistic, Octane Render, Unreal engine`; the
negative prompt is one space. The source is scaled with area interpolation to a
maximum dimension of 1536. The optional 4-step LoRA remains bypassed; enabling
it requires changing the sampler settings appropriately.
The two native **Load Image** nodes are intentionally separate. **Image To
Mask** reads the red channel of the mask PNG. That source `inpaint_mask` defines
the region supplied to the inpainting ControlNet and the latent noise mask. It
is not the Advanced-ControlNet effect mask. `effect_mask` is left unconnected
and independently limits where control is injected. The Apply node also exposes
unconnected timestep keyframe, latent keyframe, and weights ports for focused
reviewer experiments.
## Measured validation evidence
These results were measured with fixed inputs and settings; they are recorded
here rather than inferred from the example image:
- A fresh isolated vanilla-versus-Advanced run had latent maximum/mean absolute
differences `0/0`, pixel maximum/mean differences `0/0`, and 0 changed
pixels.
- An all-one effect mask exactly equaled unmasked Advanced output at latent and
pixel level. An all-zero effect mask exactly equaled no ControlNet at latent
and pixel level.
- For right-half token-mask injection, relative to full control the left latent
mean delta was `0` and the right was `0.1332103`; relative to no control the
left was `0` and the right was `0.1835042`.
- In a per-latent batch, the sample with strength 0 exactly equaled no control
at latent and pixel level.
- Soft weights, timestep scheduling, and two-control stacking each executed
successfully.
- The existing Anima real workflow rerun retained exact before/after latent and
pixel equality.
Frontend and API validation confirms that the normal Apply node exposes the
optional source mask as `inpaint_mask`. When connected to a ControlNet without
source-mask support, that input is ignored and the normal control path is used.
Workflow and result screenshots are linked from the PR instead of stored here
to avoid repository growth.
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-advanced-controlnet" name = "comfyui-advanced-controlnet"
description = "Nodes for scheduling ControlNet strength across timesteps and batched latents, as well as applying custom weights and attention masks." description = "Nodes for scheduling ControlNet strength across timesteps and batched latents, as well as applying custom weights and attention masks."
version = "1.5.8" version = "1.6.0"
license = { file = "LICENSE" } license = { file = "LICENSE" }
dependencies = [] dependencies = []
+236
View File
@@ -0,0 +1,236 @@
import os
import sys
import unittest
from types import SimpleNamespace
from unittest.mock import Mock, patch, sentinel
comfyui_path = os.environ.get("COMFYUI_PATH")
if comfyui_path:
sys.path.insert(0, comfyui_path)
import torch
from comfy.controlnet import T2IAdapter
from adv_control.control import ControlNetAdvanced, T2IAdapterAdvanced
from adv_control.nodes_main import AdvancedControlNetApply
from adv_control.utils import ControlWeights
class StopControlModel(Exception):
pass
class ControlModel:
dtype = torch.float32
def __init__(self):
self.hint = None
def __call__(self, x, hint, timesteps, context, **kwargs):
self.hint = hint
raise StopControlModel
class VideoVAE:
downscale_ratio = (4, 8, 8)
def __init__(self):
self.encoded_shape = None
def spacial_compression_encode(self):
return 8
def encode(self, image):
self.encoded_shape = image.shape
return torch.ones((image.shape[0], 4, 2, 2, 2))
class ModernControlPreprocessingTests(unittest.TestCase):
def test_effect_mask_is_resized_to_qwen_tokens(self):
control = ControlNetAdvanced(ControlModel(), None)
control.x_noisy_shape = (1, 16, 4, 6)
control.mask_cond_hint = torch.tensor(
[[[[0.0, 0.0, 0.0, 1.0, 1.0, 1.0]] * 4]]
)
control.tk_mask_cond_hint = None
control.weights = SimpleNamespace(has_uncond_multiplier=False, has_uncond_mask=False)
control.latent_keyframes = None
control._current_timestep_keyframe = SimpleNamespace(strength=1.0)
output = torch.ones((1, 6, 4))
control.apply_advanced_strengths_and_masks(output, batched_number=1)
expected = torch.tensor(
[[[0.0] * 4, [0.5] * 4, [1.0] * 4, [0.0] * 4, [0.5] * 4, [1.0] * 4]]
)
torch.testing.assert_close(output, expected)
def test_effect_mask_matches_padded_flux_tokens_for_odd_latent_size(self):
control = ControlNetAdvanced(ControlModel(), None)
control.x_noisy_shape = (1, 16, 5, 7)
control.mask_cond_hint = torch.ones((1, 1, 5, 7))
control.tk_mask_cond_hint = None
control.weights = SimpleNamespace(has_uncond_multiplier=False, has_uncond_mask=False)
control.latent_keyframes = None
control._current_timestep_keyframe = SimpleNamespace(strength=1.0)
output = torch.ones((1, 12, 4))
control.apply_advanced_strengths_and_masks(output, batched_number=1)
torch.testing.assert_close(output, torch.ones_like(output))
def test_vae_compression_and_source_mask_match_5d_hint(self):
control_model = ControlModel()
vae = VideoVAE()
control = ControlNetAdvanced(control_model, None, compression_ratio=1, latent_format=SimpleNamespace(process_in=lambda value: value))
control.real_compression_ratio = 1
control.cond_hint_original = torch.ones((1, 3, 16, 16))
control.cond_hint = None
control.vae = vae
control.extra_concat_orig = [torch.zeros((1, 1, 16, 16))]
control.sub_idxs = None
control.model_sampling_current = SimpleNamespace(timestep=lambda value: value, calculate_input=lambda timestep, value: value)
control.prepare_mask_cond_hint = lambda **kwargs: None
with self.assertRaises(StopControlModel):
control.sliding_get_control(
torch.ones((1, 4, 2, 2, 2)),
torch.ones(1),
{"c_crossattn": torch.ones((1, 1, 1))},
1,
{},
)
self.assertEqual(tuple(vae.encoded_shape), (1, 16, 16, 3))
self.assertEqual(tuple(control_model.hint.shape), (1, 5, 2, 2, 2))
class T2IAdapterTests(unittest.TestCase):
def test_effect_masks_are_applied_to_adapter_features(self):
control = T2IAdapterAdvanced(SimpleNamespace(), None, channels_in=3)
control.weights = ControlWeights.t2iadapter()
control.latent_keyframes = None
control.tk_mask_cond_hint = None
control._current_timestep_keyframe = SimpleNamespace(strength=1.0)
masks = {
"zero": torch.zeros((1, 1, 8, 8)),
"one": torch.ones((1, 1, 8, 8)),
"half": torch.cat((torch.zeros((1, 1, 8, 4)), torch.ones((1, 1, 8, 4))), dim=3),
}
for name, mask in masks.items():
with self.subTest(name=name):
features = torch.ones((1, 4, 8, 8))
control.mask_cond_hint = mask
control.apply_advanced_strengths_and_masks(features, batched_number=1)
torch.testing.assert_close(features, mask.expand_as(features))
def test_sliding_context_extends_single_hint_to_full_latent_length(self):
control = T2IAdapterAdvanced(SimpleNamespace(), None, channels_in=3)
original_hint = torch.ones((1, 3, 8, 8))
control.cond_hint_original = original_hint
control.cond_hint = None
control.sub_idxs = [2, 3]
control.full_latent_length = 4
control.prepare_mask_cond_hint = lambda **kwargs: None
selected_hint = None
def get_control(adapter, *args, **kwargs):
nonlocal selected_hint
selected_hint = adapter.cond_hint_original.clone()
return sentinel.output
with patch.object(T2IAdapter, "get_control", get_control):
result = control.get_control_advanced(
torch.ones((2, 4, 8, 8)),
torch.ones(2),
{},
1,
{},
)
self.assertIs(result, sentinel.output)
self.assertEqual(tuple(selected_hint.shape), (2, 3, 8, 8))
torch.testing.assert_close(selected_hint, original_hint.repeat(2, 1, 1, 1))
self.assertIs(control.cond_hint_original, original_hint)
class AdvancedControlNetApplyTests(unittest.TestCase):
def apply_control(self, concat_mask, image, inpaint_mask, effect_mask=None):
control_net = SimpleNamespace(concat_mask=concat_mask, copy=Mock(return_value=sentinel.control_copy))
applied_control = SimpleNamespace(
allow_condhint_latents=False,
require_vae=False,
postpone_condhint_latents_check=False,
disarm=Mock(),
set_cond_hint=Mock(),
set_cond_hint_mask=Mock(),
set_previous_controlnet=Mock(),
verify_all_weights=Mock(),
)
applied_control.set_cond_hint.return_value = applied_control
positive = [[sentinel.positive_tensor, {}]]
with patch("adv_control.nodes_main.convert_to_advanced", return_value=applied_control), \
patch("adv_control.nodes_main.is_advanced_controlnet", return_value=True):
AdvancedControlNetApply.execute(
positive=positive,
negative=[],
control_net=control_net,
image=image,
strength=1.0,
start_percent=0.0,
end_percent=1.0,
mask_optional=effect_mask,
vae_optional=sentinel.vae,
inpaint_mask=inpaint_mask,
)
return applied_control
def test_all_zero_effect_mask_returns_original_conditioning(self):
positive = [[sentinel.positive_tensor, {"name": "positive"}]]
negative = [[sentinel.negative_tensor, {"name": "negative"}]]
result = AdvancedControlNetApply.execute(
positive=positive,
negative=negative,
control_net=sentinel.control_net,
image=torch.ones((1, 8, 8, 3)),
strength=1.0,
start_percent=0.0,
end_percent=1.0,
mask_optional=torch.zeros((1, 8, 8)),
)
self.assertIs(result.args[0], positive)
self.assertIs(result.args[1], negative)
def test_source_mask_and_effect_mask_stay_independent(self):
image = torch.ones((1, 2, 2, 3))
inpaint_mask = torch.tensor([[[1.0, 0.0], [1.0, 0.0]]])
effect_mask = torch.full((1, 2, 2), 0.25)
applied_control = self.apply_control(True, image, inpaint_mask, effect_mask)
inputs = applied_control.set_cond_hint.call_args.args
source_mask = 1.0 - inpaint_mask.unsqueeze(1)
torch.testing.assert_close(inputs[0], (image * source_mask.movedim(1, -1)).movedim(-1, 1))
torch.testing.assert_close(inputs[4][0], source_mask)
torch.testing.assert_close(applied_control.set_cond_hint_mask.call_args.args[0], effect_mask)
def test_inpaint_mask_is_ignored_for_other_controlnets(self):
image = torch.ones((1, 2, 2, 3))
inpaint_mask = torch.tensor([[[1.0, 0.0], [1.0, 0.0]]])
applied_control = self.apply_control(False, image, inpaint_mask)
inputs = applied_control.set_cond_hint.call_args.args
torch.testing.assert_close(
inputs[0],
image.movedim(-1, 1),
)
self.assertEqual(inputs[4], [])
if __name__ == "__main__":
unittest.main()
+104
View File
@@ -0,0 +1,104 @@
import os
import sys
import unittest
from types import SimpleNamespace
from unittest.mock import patch
comfyui_path = os.environ.get("COMFYUI_PATH")
if comfyui_path:
sys.path.insert(0, comfyui_path)
import torch
from comfy.controlnet import T2IAdapter
from adv_control.control import T2IAdapterAdvanced
from adv_control.control_lllite import LLLiteModule
from adv_control.control_reference import REF_CONTROL_LIST_ALL, RefConst, refcn_diffusion_model_wrapper_factory
class LLLiteRegressionTests(unittest.TestCase):
def create_module(self):
torch.manual_seed(1)
return LLLiteModule("test", False, 2, 1, 2, 2)
def create_control(self, effect_mask=None, timestep_mask=None, uncond_multiplier=1.0):
return SimpleNamespace(
sub_idxs=None,
cond_hint=torch.ones((1, 3, 8, 8)),
latent_dims_div2=None,
latent_dims_div4=None,
mask_cond_hint=effect_mask,
tk_mask_cond_hint=timestep_mask,
latent_keyframes=None,
weights=SimpleNamespace(
has_uncond_multiplier=uncond_multiplier != 1.0,
uncond_multiplier=uncond_multiplier,
),
batched_number=2,
cond_or_uncond=[0, 1],
strength=1.0,
_current_timestep_keyframe=SimpleNamespace(strength=1.0),
)
def test_unconditional_multiplier_uses_sampling_condition_order(self):
control = self.create_control(uncond_multiplier=0.25)
output = self.create_module()(torch.ones((2, 1, 2)), control)
torch.testing.assert_close(output[1], output[0] * 0.25)
def test_timestep_mask_applies_without_effect_mask(self):
control = self.create_control(timestep_mask=torch.zeros((1, 8, 8)))
output = self.create_module()(torch.ones((2, 1, 2)), control)
torch.testing.assert_close(output, torch.zeros_like(output))
def test_effect_and_timestep_masks_are_combined(self):
control = self.create_control(
effect_mask=torch.ones((1, 8, 8)),
timestep_mask=torch.zeros((1, 8, 8)),
)
output = self.create_module()(torch.ones((2, 1, 2)), control)
torch.testing.assert_close(output, torch.zeros_like(output))
class T2IAdapterRegressionTests(unittest.TestCase):
def test_sliding_context_extends_hint_to_full_latent_length(self):
adapter = object.__new__(T2IAdapterAdvanced)
adapter.sub_idxs = [2, 3]
adapter.full_latent_length = 4
adapter.cond_hint_original = torch.tensor([[[[7.0]]]])
adapter.cond_hint = None
adapter.prepare_mask_cond_hint = lambda **kwargs: None
with patch.object(T2IAdapter, "get_control", lambda self, *args, **kwargs: self.cond_hint_original.clone()):
output = adapter.get_control_advanced(torch.empty((2, 4, 1, 1)), None, None, 1, {})
self.assertEqual(output.flatten().tolist(), [7.0, 7.0])
self.assertEqual(adapter.cond_hint_original.flatten().tolist(), [7.0])
class ReferenceRegressionTests(unittest.TestCase):
def test_cleanup_does_not_hide_original_exception(self):
class ReferenceInjections:
cleaned = False
def clean_ref_module_mem(self):
self.cleaned = True
reference_injections = ReferenceInjections()
wrapper = refcn_diffusion_model_wrapper_factory(reference_injections)
transformer_options = {
REF_CONTROL_LIST_ALL: [SimpleNamespace(should_run=lambda: True)],
RefConst.REFCN_PRESENT_IN_CONDS: True,
}
with self.assertRaisesRegex(KeyError, "cond_or_uncond"):
wrapper(lambda *args, **kwargs: None, torch.zeros(1), None, None, None, None, transformer_options)
self.assertTrue(reference_injections.cleaned)
if __name__ == "__main__":
unittest.main()