diff --git a/__init__.py b/__init__.py index d7ba953..52fb0b3 100644 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1,9 @@ -from .node_loaders import * -from .node_processors import * -from .node_utils import * +from .node_conditioning import * +from .node_io import * from .node_ipadapter_loader import * +from .node_loaders import * +from .node_processing import * +from .node_utils import * from . import api @@ -20,37 +22,51 @@ __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] # ============================================================================= NODE_CLASS_MAPPINGS = { + + # loaders "LoadModelSnapshot": LoadModelSnapshot, "UnpackModelSnapshot": UnpackModelSnapshot, + # io + "SaveImageWithEmbeddedMasks": SaveImageWithEmbeddedMasks, + + # conditioning "ApplyDenseDiffusionSDXL": ApplyDenseDiffusionSDXL, "ApplyIPAdaperSDXL": ApplyIPAdaperSDXL, "IPAdapterUnifiedLoaderClone": IPAdapterUnifiedLoaderClone, - "PreviewEnvironmentalPrompts": PreviewEnvironmentalPrompts, - "PreviewMaterialPrompts": PreviewMaterialPrompts, + # processing + "ProcessEnvironmentalPrompts": ProcessEnvironmentalPrompts, + "ProcessMaterialPrompts": ProcessMaterialPrompts, "ProcessImagePrompt": ProcessImagePrompt, + # utils "BlurMask": BlurMask, - "PreviewStrings": PreviewStrings, "ConcatStrings": ConcatStrings, } NODE_DISPLAY_NAME_MAPPINGS = { + + # loaders "LoadModelSnapshot": "Load Model Snapshot", "UnpackModelSnapshot": "Unpack Model Snapshot", + # io + "SaveImageWithEmbeddedMasks": "Save Image with Embedded Masks", + + # conditioning "ApplyDenseDiffusionSDXL": "Apply Dense Diffusion Conditioning (SDXL)", "ApplyIPAdaperSDXL": "Apply IPAdaper Conditioning (SDXL)", "IPAdapterUnifiedLoaderClone": "IPAdapter Unified Loader (Clone)", - "PreviewEnvironmentalPrompts": "Preview Environmental Prompt Guidence", - "PreviewMaterialPrompts": "Preview Material Prompt Guidence", + # processing + "ProcessEnvironmentalPrompts": "Process Environmental Prompt Guidence", + "ProcessMaterialPrompts": "Process Material Prompt Guidence", "ProcessImagePrompt": "Process Image Prompt", + # utils "BlurMask": "Blur Mask", - "PreviewStrings": "Preview Strings", "ConcatStrings": "Concat Strings", } \ No newline at end of file diff --git a/node_processors.py b/node_conditioning.py similarity index 99% rename from node_processors.py rename to node_conditioning.py index 6257e37..a7c73f3 100644 --- a/node_processors.py +++ b/node_conditioning.py @@ -51,7 +51,7 @@ class ApplyDenseDiffusionSDXL: FUNCTION = "func" - CATEGORY = "Pseudocomfy/Processors" + CATEGORY = "Pseudocomfy/Conditioning" def func(self, model, clip, mat_txts_lst, mat_msks_lst, env_scene, env_style, env_negative, width, height): # material inputs are expected to be lists @@ -142,7 +142,7 @@ class ApplyIPAdaperSDXL: OUTPUT_IS_LIST = (False, False, False,) FUNCTION = "func" - CATEGORY = "Pseudocomfy/Processors" + CATEGORY = "Pseudocomfy/Conditioning" def func(self, model, clip, ipadapter, mat_txts_lst, mat_imgs_lst, mat_msks_lst, env_scene, env_style, env_negative, width, height, env_cond_strength, mat_cond_strength, ipadapter_weight, start_at, end_at): # material inputs are expected to be lists diff --git a/node_io.py b/node_io.py new file mode 100644 index 0000000..ef5c674 --- /dev/null +++ b/node_io.py @@ -0,0 +1,131 @@ + +import os, json, io, base64 + +import numpy as np +from PIL import Image +from PIL.PngImagePlugin import PngInfo + +import folder_paths +from comfy.cli_args import args + +from .helpers.imgutil import scale_tensor_image + + +# ============================================================================== +# adpated from ComfyUI SaveImagne node +# ============================================================================== +class SaveImageWithEmbeddedMasks: + def __init__(self): + self.output_dir = folder_paths.get_output_directory() + self.type = "output" + self.prefix_append = "" + self.compress_level = 4 + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "imgs": ("IMAGE", {"tooltip": "The images to save."}), + "filename_prefix": ("STRING", {"default": "pseudocomfy", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes."}) + }, + "optional": { + "msks": ("IMAGE", {"tooltip": "The masks to embed in the image."}), + }, + "hidden": { + "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO" + }, + } + + INPUT_IS_LIST = True + + RETURN_TYPES = () + FUNCTION = "save_images" + + OUTPUT_NODE = True + + CATEGORY = "Pseudocomfy/IO" + DESCRIPTION = "Saves the input images with embedded masks as extra channels." + + def save_images(self, imgs, filename_prefix="pseudocomfy", msks=[], prompt=None, extra_pnginfo=None): + # given that INPUT_IS_LIST, msks is a list of masks that should be embedded in each image in each batch + # given that INPUT_IS_LIST, imgs is a list of image batches + image_batches = imgs + typical_image_shape = image_batches[0][0].shape + + # if inputs that are meant to be singletons are a list, use the first element + if isinstance(filename_prefix, list) and len(filename_prefix)>0: filename_prefix = filename_prefix[0] + if isinstance(prompt, list) and len(prompt)>0: prompt = prompt[0] + if isinstance(extra_pnginfo, list) and len(extra_pnginfo)>0: extra_pnginfo = extra_pnginfo[0] + + print(f"[pseudocomfy] SaveImageWithEmbeddedMasks") + print(f"\tfilename_prefix = {filename_prefix}") + print(f"\ttypical_image_shape = {tuple(typical_image_shape)}") + print(f"\timage_batches count = {len(image_batches)}") + for i, img_batch in enumerate(image_batches): + print(f"\timg_batch[{i}] shape = {tuple(img_batch.shape)}") + print(f"\tmsks count = {len(msks)}") + for i, msk in enumerate(msks): + print(f"\tmsks[{i}] shape = {tuple(msk.shape)}") + + + # Ensure all masks have shape [1, width, height] + + for i in range(len(msks)): + mask = msks[i] + # Check mask has three dimensions and the first dimension is 1 + if not (isinstance(mask.shape, tuple) and len(mask.shape) == 3 and mask.shape[0] == 1): + raise ValueError(f"Mask at index {i} must have shape [1, width, height], got {mask.shape}") + + # scale the mask to the desired width and height if necessary + if mask.shape[-2] != typical_image_shape[0] or mask.shape[-1] != typical_image_shape[1]: + msks[i] = scale_tensor_image(mask, typical_image_shape[1], typical_image_shape[0]) # note that for scale_tensor_image, the order of width and height is reversed from order of tensor shape + + + print(f"\tmsks count = {len(msks)}") + for i, msk in enumerate(msks): + print(f"\tmsks[{i}] shape = {tuple(msk.shape)}") + + + filename_prefix += self.prefix_append + full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, typical_image_shape[1], typical_image_shape[0]) + results = list() + + for img_batch in image_batches: + for (batch_number, image) in enumerate(img_batch): + i = 255. * image.cpu().numpy() + image = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) + metadata = None + if not args.disable_metadata: + metadata = PngInfo() + if prompt is not None: + metadata.add_text("prompt", json.dumps(prompt)) + if extra_pnginfo is not None: + for x in extra_pnginfo: + metadata.add_text(x, json.dumps(extra_pnginfo[x])) + + # Embed ALL masks as base64 PNGs in metadata + for mask_idx, mask in enumerate(msks): + mask_np = mask.squeeze().cpu().numpy() + mask_img = Image.fromarray(np.clip(255 * mask_np, 0, 255).astype(np.uint8), mode="L") + buf = io.BytesIO() + mask_img.save(buf, format="PNG") + b64 = base64.b64encode(buf.getvalue()).decode("utf-8") + mask_json = { + "format": "PNG", + "encoding": "base64", + "size": list(mask_img.size), + "data": b64 + } + metadata.add_text(f"pseudocomfy_mask_{mask_idx:02d}", json.dumps(mask_json)) + + filename_with_batch_num = filename.replace("%batch_num%", str(batch_number)) + file = f"{filename_with_batch_num}_{counter:05}_.png" + image.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level) + results.append({ + "filename": file, + "subfolder": subfolder, + "type": self.type + }) + counter += 1 + + return { "ui": { "images": results } } \ No newline at end of file diff --git a/node_processing.py b/node_processing.py new file mode 100644 index 0000000..5705bf7 --- /dev/null +++ b/node_processing.py @@ -0,0 +1,214 @@ + +import copy +import torch + +from .helpers.imgutil import tensor_to_base64 +from .helpers.imgutil import make_multiple_of_64, scale_tensor_image + + +class ProcessEnvironmentalPrompts: + """ + Utility class for previewing environmental prompts. + Returns environmental prompt inputs unaltered. + Inputs: + env_scene (str): Scene description for the environment prompt. + env_style (str): Style description for the environment prompt. + env_negative (str): Negative prompt for the environment. + Outputs: + env_scene (str): Deep-copied scene description. + env_style (str): Deep-copied style description. + env_negative (str): Deep-copied negative prompt. + env_all (str): Concatenated string of scene, style, and negative prompt. + """ + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "env_scene": ("STRING", {"forceInput": True}), + "env_style": ("STRING", {"forceInput": True}), + "env_negative": ("STRING", {"forceInput": True}), + }, + "hidden": { + "unique_id": "UNIQUE_ID", + "extra_pnginfo": "EXTRA_PNGINFO", + }, + } + + #INPUT_IS_LIST = False + RETURN_TYPES = ("STRING","STRING","STRING","STRING",) + RETURN_NAMES = ("env_scene","env_style","env_negative","env_all",) + FUNCTION = "notify" + OUTPUT_NODE = True + + CATEGORY = "Pseudocomfy/Utils" + + def notify(self, env_scene, env_style, env_negative, unique_id=None, extra_pnginfo=None): + if unique_id is not None and extra_pnginfo is not None: + # it looks like extra_pnginfo is only a list in earlier versions of comfyui + # this component might work perfectly fine without it + if ( + isinstance(extra_pnginfo, list) + and len(extra_pnginfo) > 0 + and isinstance(extra_pnginfo[0], dict) + and "workflow" in extra_pnginfo[0] + ): + workflow = extra_pnginfo[0]["workflow"] + node = next( + (x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])), + None, + ) + if node: + node["env_scene"] = env_scene + node["env_style"] = env_style + node["env_negative"] = env_negative + else: + pass + #print("[pseudocomfy] PreviewEnvironmentalPrompts\n\tError: extra_pnginfo is not a valid list or missing 'workflow' key") + + parts = [s for s in [env_scene, env_style, env_negative] if s is not None and str(s).strip() != ""] + env_all = "; ".join(parts) + + return { #comfyui expects all values in ui to be wrapped in a list + "ui": {"env_scene": [env_scene], "env_style": [env_style], "env_negative": [env_negative]}, + "result": (env_scene, env_style, env_negative, env_all,) + } + + +class ProcessMaterialPrompts: + """ + Utility class for previewing material prompts + Returns inputs unaltered. + Inputs: + mat_txts_lst (list of str): List of material prompt texts. + mat_imgs_lst (list of tensor): List of image tensors as [1, H, W, 3] corresponding to the material prompts. + mat_msks_lst (list of tensor): List of mask tensors as [1, H, W] corresponding to the material prompts. + Outputs: + mat_txts (list of str): Deep-copied list of material prompt texts. + mat_imgs (list of tensor): Deep-copied list of image tensors. + mat_msks (list of tensor): Deep-copied list of mask tensors. + mat_txts_all (str): Concatenated string of all material prompt texts. + mat_msks_concat (tensor): Concatenated (maximum value of mat_msks_lst) mask tensor as [1, H, W]. + mat_msks_invert (tensor): Inverted (inversion of mat_msks_concat) mask tensor as [1, H, W]. + Additional Information: + - The class encodes image and mask tensors to base64 for UI display. + - All outputs are wrapped in lists to comply with ComfyUI requirements. + - Designed for use in the "Pseudocomfy/Utils" category. + """ + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "mat_txts_lst": ("STRING", {"forceInput": True}), + "mat_imgs_lst": ("IMAGE", {"forceInput": True}), + "mat_msks_lst": ("IMAGE", {"forceInput": True}), + } + } + + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,True,True,False,False,False) + RETURN_TYPES = ("STRING", "IMAGE", "IMAGE", "STRING", "IMAGE", "IMAGE",) + RETURN_NAMES = ("mat_txts", "mat_imgs", "mat_msks", "mat_txts_all", "mat_msks_concat", "mat_msks_invert",) + FUNCTION = "func" + OUTPUT_NODE = True + + CATEGORY = "Pseudocomfy/Utils" + + def func(self, mat_txts_lst, mat_imgs_lst, mat_msks_lst): + print("[pseudocomfy] ProcessMaterialPrompts") + + # all inputs are expected to be lists + mat_txts = mat_txts_lst + mat_imgs = mat_imgs_lst + mat_msks = mat_msks_lst + + mat_imgs_b64 = [tensor_to_base64(t) for t in mat_imgs] + mat_msks_b64 = [tensor_to_base64(t) for t in mat_msks] + + # concatenate all mask texts + parts = [s for s in mat_txts if s is not None and str(s).strip() != ""] + mat_txts_all = "; ".join(parts) + #print(f"\tmat_txts_all: '{mat_txts_all}'", parts) + + # concatenate all masks + def concat_masks(*masks): + """ + Takes a series of mask tensors of shape [1, H, W] and returns a tensor [1, H, W] + containing the maximum value for each pixel across all masks. + """ + # Stack along a new dimension and take max + stacked = torch.stack(masks, dim=0) # shape: [N, 1, H, W] + result, _ = torch.max(stacked, dim=0) # shape: [1, H, W] + return result + + mat_msks_concat = concat_masks(*mat_msks) + + # invert the mask + mat_msks_invert = 1.0 - mat_msks_concat + + + return { #comfyui expects all values in ui to be wrapped in a list, since these are all lists we're fine. + "ui": {"mat_txts": mat_txts, "mat_imgs": mat_imgs_b64, "mat_msks": mat_msks_b64}, + "result": ( + copy.deepcopy(mat_txts), + copy.deepcopy(mat_imgs), + copy.deepcopy(mat_msks), + mat_txts_all, + mat_msks_concat, + mat_msks_invert, + ) + } + + +class ProcessImagePrompt: + """ + Utility class for scaling images and returning both the scaled image and relevant metadata. + Inputs: + given_width (int): The original width of the image. + given_height (int): The original height of the image. + img (tensor): The input image tensor, expected shape [1, H, W, 3]. + scale_by (float): The scaling factor to apply to the image dimensions (default: 2.0, min: 1.0, max: 4.0, step: 0.5). + Outputs: + scaled_width (int): The width of the scaled image (multiple of 64). + scaled_height (int): The height of the scaled image (multiple of 64). + img (tensor): The scaled image tensor, same shape as given. + """ + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "given_width": ("INT", {"forceInput": True}), + "given_height": ("INT", {"forceInput": True}), + "img": ("IMAGE", {"forceInput": True}), + "scale_by": ("FLOAT", {"default": 2.0, "min": 1.0, "max": 4.0, "step": 0.5}), + } + } + + RETURN_TYPES = ("INT", "INT", "IMAGE",) + RETURN_NAMES = ("scaled_width", "scaled_height", "img",) + FUNCTION = "func" + OUTPUT_NODE = True + CATEGORY = "Pseudocomfy/Utils" + + def func(self, given_width, given_height, img, scale_by): + print(f"[pseudocomfy] ProcessImagePrompt\n\tgiven w,h: ({given_width},{given_height})\n\tscale_by: {scale_by}\n\timg: {tuple(img.shape)}") + + scaled_width = int(make_multiple_of_64(given_width * scale_by)) + scaled_height = int(make_multiple_of_64(given_height * scale_by)) + image = scale_tensor_image(img, scaled_width, scaled_height) + #print("completed scaling to: ", scaled_width, scaled_height, img.shape) + #return (scaled_width, scaled_height,image,) + return { + "ui": { #comfyui expects all values in ui to be wrapped in a list + "img": [tensor_to_base64(image)], + "given_width": [given_width], + "given_height": [given_height], + "scaled_width": [scaled_width], + "scaled_height": [scaled_height] + }, + "result": ( + scaled_width, + scaled_height, + image, + ) + } + diff --git a/node_utils.py b/node_utils.py index b32284c..8bcf755 100644 --- a/node_utils.py +++ b/node_utils.py @@ -10,192 +10,11 @@ import torch import torch.nn.functional as F import time, hashlib -from .helpers.imgutil import tensor_to_base64 -import copy import base64, io from PIL import Image -from .helpers.imgutil import make_multiple_of_64, scale_tensor_image -class PreviewEnvironmentalPrompts: - """ - Utility class for previewing environmental prompts. - Returns environmental prompt inputs unaltered. - Inputs: - env_scene (str): Scene description for the environment prompt. - env_style (str): Style description for the environment prompt. - env_negative (str): Negative prompt for the environment. - Outputs: - env_scene (str): Deep-copied scene description. - env_style (str): Deep-copied style description. - env_negative (str): Deep-copied negative prompt. - env_all (str): Concatenated string of scene, style, and negative prompt. - """ - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "env_scene": ("STRING", {"forceInput": True}), - "env_style": ("STRING", {"forceInput": True}), - "env_negative": ("STRING", {"forceInput": True}), - }, - "hidden": { - "unique_id": "UNIQUE_ID", - "extra_pnginfo": "EXTRA_PNGINFO", - }, - } - - #INPUT_IS_LIST = False - RETURN_TYPES = ("STRING","STRING","STRING","STRING",) - RETURN_NAMES = ("env_scene","env_style","env_negative","env_all",) - FUNCTION = "notify" - OUTPUT_NODE = True - - CATEGORY = "Pseudocomfy/Utils" - - def notify(self, env_scene, env_style, env_negative, unique_id=None, extra_pnginfo=None): - if unique_id is not None and extra_pnginfo is not None: - # it looks like extra_pnginfo is only a list in earlier versions of comfyui - # this component might work perfectly fine without it - if ( - isinstance(extra_pnginfo, list) - and len(extra_pnginfo) > 0 - and isinstance(extra_pnginfo[0], dict) - and "workflow" in extra_pnginfo[0] - ): - workflow = extra_pnginfo[0]["workflow"] - node = next( - (x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])), - None, - ) - if node: - node["env_scene"] = env_scene - node["env_style"] = env_style - node["env_negative"] = env_negative - else: - pass - #print("[pseudocomfy] PreviewEnvironmentalPrompts\n\tError: extra_pnginfo is not a valid list or missing 'workflow' key") - - parts = [s for s in [env_scene, env_style, env_negative] if s is not None and str(s).strip() != ""] - env_all = "; ".join(parts) - - return { #comfyui expects all values in ui to be wrapped in a list - "ui": {"env_scene": [env_scene], "env_style": [env_style], "env_negative": [env_negative]}, - "result": (env_scene, env_style, env_negative, env_all,) - } - - -class PreviewMaterialPrompts: - """ - Utility class for previewing material prompts - Returns inputs unaltered. - Inputs: - mat_txts_lst (list of str): List of material prompt texts. - mat_imgs_lst (list of tensor): List of image tensors as [1, H, W, 3] corresponding to the material prompts. - mat_msks_lst (list of tensor): List of mask tensors as [1, H, W] corresponding to the material prompts. - Outputs: - mat_txts (list of str): Deep-copied list of material prompt texts. - mat_imgs (list of tensor): Deep-copied list of image tensors. - mat_msks (list of tensor): Deep-copied list of mask tensors. - mat_txts_all (str): Concatenated string of all material prompt texts. - Additional Information: - - The class encodes image and mask tensors to base64 for UI display. - - All outputs are wrapped in lists to comply with ComfyUI requirements. - - Designed for use in the "Pseudocomfy/Utils" category. - """ - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mat_txts_lst": ("STRING", {"forceInput": True}), - "mat_imgs_lst": ("IMAGE", {"forceInput": True}), - "mat_msks_lst": ("IMAGE", {"forceInput": True}), - } - } - - INPUT_IS_LIST = True - OUTPUT_IS_LIST = (True,True,True,) - RETURN_TYPES = ("STRING","IMAGE","IMAGE","STRING",) - RETURN_NAMES = ("mat_txts", "mat_imgs", "mat_msks", "mat_txts_all",) - FUNCTION = "notify" - OUTPUT_NODE = True - - CATEGORY = "Pseudocomfy/Utils" - - def notify(self, mat_txts_lst, mat_imgs_lst, mat_msks_lst): - #print("[pseudocomfy] PreviewMaterialPrompts\n\t mat_msks is len: ", len(mat_msks)) - - # all inputs are expected to be lists - mat_txts = mat_txts_lst - mat_imgs = mat_imgs_lst - mat_msks = mat_msks_lst - - mat_imgs_b64 = [tensor_to_base64(t) for t in mat_imgs] - mat_msks_b64 = [tensor_to_base64(t) for t in mat_msks] - - parts = [s for s in mat_txts if s is not None and str(s).strip() != ""] - mat_txts_all = "; ".join(parts) - - return { #comfyui expects all values in ui to be wrapped in a list, since these are all lists we're fine. - "ui": {"mat_txts": mat_txts, "mat_imgs": mat_imgs_b64, "mat_msks": mat_msks_b64}, - "result": (copy.deepcopy(mat_txts),copy.deepcopy(mat_imgs),copy.deepcopy(mat_msks),mat_txts_all,) - } - - -class ProcessImagePrompt: - """ - Utility class for scaling images and returning both the scaled image and relevant metadata. - Inputs: - given_width (int): The original width of the image. - given_height (int): The original height of the image. - img (tensor): The input image tensor, expected shape [1, H, W, 3]. - scale_by (float): The scaling factor to apply to the image dimensions (default: 2.0, min: 1.0, max: 4.0, step: 0.5). - Outputs: - scaled_width (int): The width of the scaled image (multiple of 64). - scaled_height (int): The height of the scaled image (multiple of 64). - img (tensor): The scaled image tensor, same shape as given. - """ - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "given_width": ("INT", {"forceInput": True}), - "given_height": ("INT", {"forceInput": True}), - "img": ("IMAGE", {"forceInput": True}), - "scale_by": ("FLOAT", {"default": 2.0, "min": 1.0, "max": 4.0, "step": 0.5}), - } - } - - RETURN_TYPES = ("INT", "INT", "IMAGE",) - RETURN_NAMES = ("scaled_width", "scaled_height", "img",) - FUNCTION = "func" - OUTPUT_NODE = True - CATEGORY = "Pseudocomfy/Utils" - - def func(self, given_width, given_height, img, scale_by): - print(f"[pseudocomfy] ProcessImagePrompt\n\tgiven w,h: ({given_width},{given_height})\n\tscale_by: {scale_by}\n\timg: {tuple(img.shape)}") - - scaled_width = int(make_multiple_of_64(given_width * scale_by)) - scaled_height = int(make_multiple_of_64(given_height * scale_by)) - image = scale_tensor_image(img, scaled_width, scaled_height) - #print("completed scaling to: ", scaled_width, scaled_height, img.shape) - #return (scaled_width, scaled_height,image,) - return { - "ui": { #comfyui expects all values in ui to be wrapped in a list - "img": [tensor_to_base64(image)], - "given_width": [given_width], - "given_height": [given_height], - "scaled_width": [scaled_width], - "scaled_height": [scaled_height] - }, - "result": ( - scaled_width, - scaled_height, - image, - ) - } - class BlurMask: """ diff --git a/web/node/preview_env_prompts.js b/web/node/process_env_prompts.js similarity index 98% rename from web/node/preview_env_prompts.js rename to web/node/process_env_prompts.js index 66dca31..2991bc8 100644 --- a/web/node/preview_env_prompts.js +++ b/web/node/process_env_prompts.js @@ -3,7 +3,7 @@ import { ComfyWidgets } from "../../../scripts/widgets.js"; // Displays input text on a node -const _ID = "PreviewEnvironmentalPrompts"; +const _ID = "ProcessEnvironmentalPrompts"; app.registerExtension({ name: 'pseudocomfy.' + _ID, diff --git a/web/node/preview_mat_prompts.js b/web/node/process_mat_prompts.js similarity index 95% rename from web/node/preview_mat_prompts.js rename to web/node/process_mat_prompts.js index 8f31245..e6ab5f2 100644 --- a/web/node/preview_mat_prompts.js +++ b/web/node/process_mat_prompts.js @@ -1,7 +1,7 @@ import { app } from "../../../scripts/app.js"; import { ComfyWidgets } from "../../../scripts/widgets.js"; -const _ID = "PreviewMaterialPrompts"; +const _ID = "ProcessMaterialPrompts"; app.registerExtension({ @@ -80,8 +80,14 @@ app.registerExtension({ promptWidgetEl.parentNode.replaceChild(newPromptWidget, promptWidgetEl); promptWidgetEl = newPromptWidget; } else { - // Just update value + // Always update value and image if present promptWidgetEl.value = matTxts.length > 0 ? matTxts[state.idx] : ""; + // If side-by-side, also update the image + if (isSideBySide && promptWidgetEl.children.length > 1) { + const imgDiv = promptWidgetEl.children[1]; + const img = imgDiv.querySelector("img"); + if (img) img.src = hasImg ? matImgs[state.idx] : ""; + } } } };