From 5bb31511ac5becf5e13e4b4191d12343a5ecd26b Mon Sep 17 00:00:00 2001 From: TSC <112517630+LucianoCirino@users.noreply.github.com> Date: Sun, 13 Aug 2023 10:41:59 -0500 Subject: [PATCH] Bug Fixes + Tiled VAE moved to KSamplers This commit includes many changes: - Improved installation of required python packages and improved logic when installation fails - Moved VAE tile option from node_settings.json to the Efficient KSampler's "vae_decode" input - Changed preview image logic, now its much less buggy --- efficiency_nodes.py | 365 ++++++++++++++++++++------------------------ node_settings.json | 6 +- tsc_utils.py | 66 ++++---- 3 files changed, 205 insertions(+), 232 deletions(-) diff --git a/efficiency_nodes.py b/efficiency_nodes.py index 41ac1eb..259fc8b 100644 --- a/efficiency_nodes.py +++ b/efficiency_nodes.py @@ -39,12 +39,9 @@ import comfy.sd import comfy.utils import comfy.latent_formats -# Import my library +# Import my utility functions from tsc_utils import * -# Import dependencies -import simpleeval - MAX_RESOLUTION=8192 ######################################################################################################################## @@ -262,7 +259,7 @@ class TSC_KSampler: "latent_image": ("LATENT",), "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), "preview_method": (["auto", "latent2rgb", "taesd", "none"],), - "vae_decode": (["true", "false", "output only"],), + "vae_decode": (["true", "true (tiled)", "false", "output only", "output only (tiled)"],), }, "optional": { "optional_vae": ("VAE",), "script": ("SCRIPT",),}, @@ -294,22 +291,6 @@ class TSC_KSampler: print('\033[33mKSampler(Efficient) Warning:\033[0m No vae input detected, proceeding as if vae_decode was false.\n') vae_decode = "false" - # Extract node_settings from json - def get_settings(): - # Get the directory path of the current file - my_dir = os.path.dirname(os.path.abspath(__file__)) - # Construct the file path for node_settings.json - settings_file = os.path.join(my_dir, 'node_settings.json') - # Load the settings from the JSON file - with open(settings_file, 'r') as file: - node_settings = json.load(file) - # Retrieve the settings - kse_vae_tiled = node_settings.get("KSampler (Efficient)", {}).get('vae_tiled', False) - xy_vae_tiled = node_settings.get("XY Plot", {}).get('vae_tiled', False) - return kse_vae_tiled, xy_vae_tiled - - kse_vae_tiled, xy_vae_tiled = get_settings() - # Functions for previewing images in Ksampler def map_filename(filename): prefix_len = len(os.path.basename(filename_prefix)) @@ -325,7 +306,7 @@ class TSC_KSampler: input = input.replace("%height%", str(images[0].shape[0])) return input - def preview_images(images, filename_prefix): + def preview_image(images, filename_prefix): if images == list(): return list() @@ -387,8 +368,8 @@ class TSC_KSampler: last_helds[key].append((new_value, my_unique_id)) return True - def vae_decode_latent(latent, kse_vae_tiled): - return vae.decode_tiled(latent).cpu() if kse_vae_tiled else vae.decode(latent).cpu() + def vae_decode_latent(latent, vae_decode): + return vae.decode_tiled(latent).cpu() if "tiled" in vae_decode else vae.decode(latent).cpu() # Clean globally stored objects of non-existant nodes globals_cleanup(prompt) @@ -429,13 +410,8 @@ class TSC_KSampler: set_preview_method(preview_method) # Define commands arguments to send to front-end via websocket - if preview_method != "none": - if vae_decode == "true": - sendBlob = False - else: - # Send back the last blob image through websocket for display - sendBlob = True - send_command_to_frontend(startListening=True, maxCount=steps-1, sendBlob=sendBlob) + if preview_method != "none" and "true" in vae_decode: + send_command_to_frontend(startListening=True, maxCount=steps-1, sendBlob=False) # Sample the latent_image(s) using the Comfy KSampler nodes if ksampler_adv_flag == False: @@ -450,43 +426,22 @@ class TSC_KSampler: # Cache latent samples in the 'last_helds' dictionary "latent" update_value_by_id("latent", my_unique_id, latent) - # Define next Hold's vae_decode behavior + # Define node output images & next Hold's vae_decode behavior + output_images = node_images = get_latest_image() ### if vae_decode == "false": - # Enable vae decode on next Hold update_value_by_id("vae_decode_flag", my_unique_id, True) - else: - # Disable vae decode on next Hold - update_value_by_id("vae_decode_flag", my_unique_id, False) - - # Define node image outputs - if vae_decode == "false": - if preview_method == "none": + if preview_method == "none" or output_images == list(): output_images = TSC_KSampler.empty_image - node_images = list() - else: - output_images = node_images = get_latest_image() - - elif vae_decode == "true": - decoded_image = vae_decode_latent(latent, kse_vae_tiled) - if preview_method == "none": - output_images = node_images = decoded_image - else: - output_images = node_images = decoded_image - - elif vae_decode == "output only": - decoded_image = vae_decode_latent(latent, kse_vae_tiled) - if preview_method == "none": - output_images = decoded_image - node_images = list() - else: - output_images = decoded_image - node_images = get_latest_image() + else: + update_value_by_id("vae_decode_flag", my_unique_id, False) + decoded_image = vae_decode_latent(latent, vae_decode) + output_images = node_images = decoded_image # Cache output images to global 'last_helds' dictionary "output_images" update_value_by_id("output_images", my_unique_id, output_images) # Generate preview_images (PIL) - preview_images = preview_images(node_images, filename_prefix) + preview_images = preview_image(node_images, filename_prefix) # Cache node preview images to global 'last_helds' dictionary "preview_images" update_value_by_id("preview_images", my_unique_id, preview_images) @@ -494,32 +449,32 @@ class TSC_KSampler: # Set xy_plot_flag to 'False' and set the stored (if any) XY Plot image tensor to 'None' update_value_by_id("xy_plot_flag", my_unique_id, False) update_value_by_id("xy_plot_image", my_unique_id, None) - + + if "output only" in vae_decode: + preview_images = list() + if preview_method != "none": # Send message to front-end to revoke the last blob image from browser's memory (fixes preview duplication bug) send_command_to_frontend(startListening=False) - - return {"ui": {"images": preview_images}, - "result": (model, positive, negative, {"samples": latent}, vae, output_images,)} + + result = (model, positive, negative, {"samples": latent}, vae, output_images,) + return result if not preview_images else {"ui": {"images": preview_images}, "result": result} # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # If the sampler state is "Hold" elif sampler_state == "Hold": + output_images = last_output_images + preview_images = last_preview_images if "true" in vae_decode else list() if get_value_by_id("vae_decode_flag", my_unique_id): - if vae_decode in ["true", "output only"]: - output_images = node_images = vae_decode_latent(last_latent["samples"], kse_vae_tiled) + if "true" in vae_decode or "output only" in vae_decode: + output_images = node_images = vae_decode_latent(last_latent["samples"], vae_decode) update_value_by_id("vae_decode_flag", my_unique_id, False) update_value_by_id("output_images", my_unique_id, output_images) - - if vae_decode == "true": - preview_images = preview_images(node_images, filename_prefix) - update_value_by_id("preview_images", my_unique_id, preview_images) - else: # image output only - preview_images = last_preview_images - else: - output_images = last_output_images - preview_images = last_preview_images + preview_images = preview_image(node_images, filename_prefix) + update_value_by_id("preview_images", my_unique_id, preview_images) + if "output only" in vae_decode: + preview_images = list() # Check if holding an XY Plot image elif get_value_by_id("xy_plot_flag", my_unique_id): @@ -532,14 +487,10 @@ class TSC_KSampler: output_images = get_value_by_id("xy_plot_image", my_unique_id) else: output_images = get_value_by_id("output_images", my_unique_id) - preview_images = last_preview_images #if vae_decode == "true" else list() + preview_images = last_preview_images else: output_images = last_output_images - preview_images = last_preview_images if vae_decode == "true" else list() - - else: - output_images = last_output_images - preview_images = last_preview_images if vae_decode == "true" else list() + preview_images = last_preview_images if "true" in vae_decode else list() return {"ui": {"images": preview_images}, "result": (model, positive, negative, {"samples": last_latent["samples"]}, vae, output_images,)} @@ -564,7 +515,7 @@ class TSC_KSampler: "result": (model, positive, negative, last_latent, vae, TSC_KSampler.empty_image,)} # If vae_decode is not set to true, print message that changing it to true - if vae_decode != "true": + if "true" not in vae_decode: print('\033[33mKSampler(Efficient) Warning:\033[0m VAE decoding must be set to \'true\'' ' for XY Plot script, proceeding as if \'true\'.\n') @@ -965,7 +916,7 @@ class TSC_KSampler: # ______________________________________________________________________________________________________ # The below function is used to generate the results based on all the processed variables def process_values(model, add_noise, seed, steps, start_at_step, end_at_step, return_with_leftover_noise, - cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, vae, + cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, vae, vae_decode, ksampler_adv_flag, latent_list=[], image_tensor_list=[], image_pil_list=[]): if preview_method != "none": @@ -987,10 +938,7 @@ class TSC_KSampler: latent_list.append(latent) # Decode the latent tensor - if xy_vae_tiled == False: - image = vae.decode(latent).cpu() - else: - image = vae.decode_tiled(latent).cpu() + image = vae_decode_latent(latent, vae_decode) # Add the resulting image tensor to image_tensor_list image_tensor_list.append(image) @@ -1053,7 +1001,7 @@ class TSC_KSampler: latent_list, image_tensor_list, image_pil_list = \ process_values(model, add_noise, seed_updated, steps, start_at_step, end_at_step, return_with_leftover_noise, cfg, sampler_name, scheduler[0], - positive, negative, latent_image, denoise, vae, ksampler_adv_flag) + positive, negative, latent_image, denoise, vae, vae_decode, ksampler_adv_flag) elif X_type != "Nothing" and Y_type != "Nothing": # Seed control based on loop index during Batch @@ -1080,7 +1028,7 @@ class TSC_KSampler: latent_list, image_tensor_list, image_pil_list = \ process_values(model, add_noise, seed_updated, steps, start_at_step, end_at_step, return_with_leftover_noise, cfg, sampler_name, scheduler[0], - positive, negative, latent_image, denoise, vae, ksampler_adv_flag) + positive, negative, latent_image, denoise, vae, vae_decode, ksampler_adv_flag) # Clean up cache if cache_models == "False": @@ -1470,7 +1418,7 @@ class TSC_KSampler: update_value_by_id("xy_plot_flag", my_unique_id, True) # Generate the preview_images and cache results - preview_images = preview_images(xy_plot_image, filename_prefix) + preview_images = preview_image(xy_plot_image, filename_prefix) update_value_by_id("preview_images", my_unique_id, preview_images) # Generate output_images and cache results @@ -1519,7 +1467,7 @@ class TSC_KSamplerAdvanced(TSC_KSampler): "end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}), "return_with_leftover_noise": (["disable", "enable"],), "preview_method": (["auto", "latent2rgb", "taesd", "none"],), - "vae_decode": (["true", "false", "output only"],), + "vae_decode": (["true", "true (tiled)", "false", "output only", "output only (tiled)"],), }, "optional": {"optional_vae": ("VAE",), "script": ("SCRIPT",), }, @@ -2781,111 +2729,6 @@ class TSC_ImageOverlay: # Return the edited base image return (base_image,) -######################################################################################################################## -# TSC Evaluate Integers (https://github.com/danthedeckie/simpleeval) -class TSC_EvaluateInts: - @classmethod - def INPUT_TYPES(cls): - return {"required": { - "python_expression": ("STRING", {"default": "((a + b) - c) / 2", "multiline": False}), - "print_to_console": (["False", "True"],),}, - "optional": { - "a": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 1}), - "b": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 1}), - "c": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 1}),}, - } - RETURN_TYPES = ("INT", "FLOAT", "STRING",) - OUTPUT_NODE = True - FUNCTION = "evaluate" - CATEGORY = "Efficiency Nodes/Simple Eval" - - def evaluate(self, python_expression, print_to_console, a=0, b=0, c=0): - # simple_eval doesn't require the result to be converted to a string - result = simpleeval.simple_eval(python_expression, names={'a': a, 'b': b, 'c': c}) - int_result = int(result) - float_result = float(result) - string_result = str(result) - if print_to_console == "True": - print("\n\033[31mEvaluate Integers:\033[0m") - print(f"\033[90m{{a = {a} , b = {b} , c = {c}}} \033[0m") - print(f"{python_expression} = \033[92m INT: " + str(int_result) + " , FLOAT: " + str( - float_result) + ", STRING: " + string_result + "\033[0m") - return (int_result, float_result, string_result,) - -#======================================================================================================================= -# TSC Evaluate Floats (https://github.com/danthedeckie/simpleeval) -class TSC_EvaluateFloats: - @classmethod - def INPUT_TYPES(cls): - return {"required": { - "python_expression": ("STRING", {"default": "((a + b) - c) / 2", "multiline": False}), - "print_to_console": (["False", "True"],),}, - "optional": { - "a": ("FLOAT", {"default": 0, "min": -sys.float_info.max, "max": sys.float_info.max, "step": 1}), - "b": ("FLOAT", {"default": 0, "min": -sys.float_info.max, "max": sys.float_info.max, "step": 1}), - "c": ("FLOAT", {"default": 0, "min": -sys.float_info.max, "max": sys.float_info.max, "step": 1}),}, - } - RETURN_TYPES = ("INT", "FLOAT", "STRING",) - OUTPUT_NODE = True - FUNCTION = "evaluate" - CATEGORY = "Efficiency Nodes/Simple Eval" - - def evaluate(self, python_expression, print_to_console, a=0, b=0, c=0): - # simple_eval doesn't require the result to be converted to a string - result = simpleeval.simple_eval(python_expression, names={'a': a, 'b': b, 'c': c}) - int_result = int(result) - float_result = float(result) - string_result = str(result) - if print_to_console == "True": - print("\n\033[31mEvaluate Floats:\033[0m") - print(f"\033[90m{{a = {a} , b = {b} , c = {c}}} \033[0m") - print(f"{python_expression} = \033[92m INT: " + str(int_result) + " , FLOAT: " + str( - float_result) + ", STRING: " + string_result + "\033[0m") - return (int_result, float_result, string_result,) - -#======================================================================================================================= -# TSC Evaluate Strings (https://github.com/danthedeckie/simpleeval) -class TSC_EvaluateStrs: - @classmethod - def INPUT_TYPES(cls): - return {"required": { - "python_expression": ("STRING", {"default": "a + b + c", "multiline": False}), - "print_to_console": (["False", "True"],)}, - "optional": { - "a": ("STRING", {"default": "Hello", "multiline": False}), - "b": ("STRING", {"default": " World", "multiline": False}), - "c": ("STRING", {"default": "!", "multiline": False}),} - } - RETURN_TYPES = ("STRING",) - OUTPUT_NODE = True - FUNCTION = "evaluate" - CATEGORY = "Efficiency Nodes/Simple Eval" - - def evaluate(self, python_expression, print_to_console, a="", b="", c=""): - variables = {'a': a, 'b': b, 'c': c} # Define the variables for the expression - - functions = simpleeval.DEFAULT_FUNCTIONS.copy() - functions.update({"len": len}) # Add the functions for the expression - - result = simpleeval.simple_eval(python_expression, names=variables, functions=functions) - if print_to_console == "True": - print("\n\033[31mEvaluate Strings:\033[0m") - print(f"\033[90ma = {a} \nb = {b} \nc = {c}\033[0m") - print(f"{python_expression} = \033[92m" + str(result) + "\033[0m") - return (str(result),) # Convert result to a string before returning - -#======================================================================================================================= -# TSC Simple Eval Examples (https://github.com/danthedeckie/simpleeval) -class TSC_EvalExamples: - @classmethod - def INPUT_TYPES(cls): - filepath = os.path.join(my_dir, 'workflows', 'SimpleEval_Node_Examples.txt') - with open(filepath, 'r') as file: - examples = file.read() - return {"required": { "models_text": ("STRING", {"default": examples ,"multiline": True}),},} - RETURN_TYPES = () - CATEGORY = "Efficiency Nodes/Simple Eval" - ######################################################################################################################## # NODE MAPPING NODE_CLASS_MAPPINGS = { @@ -2917,9 +2760,129 @@ NODE_CLASS_MAPPINGS = { "XY Input: Manual XY Entry": TSC_XYplot_Manual_XY_Entry, "Manual XY Entry Info": TSC_XYplot_Manual_XY_Entry_Info, "Join XY Inputs of Same Type": TSC_XYplot_JoinInputs, - "Image Overlay": TSC_ImageOverlay, - "Evaluate Integers": TSC_EvaluateInts, - "Evaluate Floats": TSC_EvaluateFloats, - "Evaluate Strings": TSC_EvaluateStrs, - "Simple Eval Examples": TSC_EvalExamples + "Image Overlay": TSC_ImageOverlay } +######################################################################################################################## +# Simpleeval Nodes +try: + import simpleeval + + # TSC Evaluate Integers (https://github.com/danthedeckie/simpleeval) + class TSC_EvaluateInts: + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "python_expression": ("STRING", {"default": "((a + b) - c) / 2", "multiline": False}), + "print_to_console": (["False", "True"],), }, + "optional": { + "a": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 1}), + "b": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 1}), + "c": ("INT", {"default": 0, "min": -48000, "max": 48000, "step": 1}), }, + } + + RETURN_TYPES = ("INT", "FLOAT", "STRING",) + OUTPUT_NODE = True + FUNCTION = "evaluate" + CATEGORY = "Efficiency Nodes/Simple Eval" + + def evaluate(self, python_expression, print_to_console, a=0, b=0, c=0): + # simple_eval doesn't require the result to be converted to a string + result = simpleeval.simple_eval(python_expression, names={'a': a, 'b': b, 'c': c}) + int_result = int(result) + float_result = float(result) + string_result = str(result) + if print_to_console == "True": + print("\n\033[31mEvaluate Integers:\033[0m") + print(f"\033[90m{{a = {a} , b = {b} , c = {c}}} \033[0m") + print(f"{python_expression} = \033[92m INT: " + str(int_result) + " , FLOAT: " + str( + float_result) + ", STRING: " + string_result + "\033[0m") + return (int_result, float_result, string_result,) + + + # ================================================================================================================== + # TSC Evaluate Floats (https://github.com/danthedeckie/simpleeval) + class TSC_EvaluateFloats: + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "python_expression": ("STRING", {"default": "((a + b) - c) / 2", "multiline": False}), + "print_to_console": (["False", "True"],), }, + "optional": { + "a": ("FLOAT", {"default": 0, "min": -sys.float_info.max, "max": sys.float_info.max, "step": 1}), + "b": ("FLOAT", {"default": 0, "min": -sys.float_info.max, "max": sys.float_info.max, "step": 1}), + "c": ("FLOAT", {"default": 0, "min": -sys.float_info.max, "max": sys.float_info.max, "step": 1}), }, + } + + RETURN_TYPES = ("INT", "FLOAT", "STRING",) + OUTPUT_NODE = True + FUNCTION = "evaluate" + CATEGORY = "Efficiency Nodes/Simple Eval" + + def evaluate(self, python_expression, print_to_console, a=0, b=0, c=0): + # simple_eval doesn't require the result to be converted to a string + result = simpleeval.simple_eval(python_expression, names={'a': a, 'b': b, 'c': c}) + int_result = int(result) + float_result = float(result) + string_result = str(result) + if print_to_console == "True": + print("\n\033[31mEvaluate Floats:\033[0m") + print(f"\033[90m{{a = {a} , b = {b} , c = {c}}} \033[0m") + print(f"{python_expression} = \033[92m INT: " + str(int_result) + " , FLOAT: " + str( + float_result) + ", STRING: " + string_result + "\033[0m") + return (int_result, float_result, string_result,) + + + # ================================================================================================================== + # TSC Evaluate Strings (https://github.com/danthedeckie/simpleeval) + class TSC_EvaluateStrs: + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "python_expression": ("STRING", {"default": "a + b + c", "multiline": False}), + "print_to_console": (["False", "True"],)}, + "optional": { + "a": ("STRING", {"default": "Hello", "multiline": False}), + "b": ("STRING", {"default": " World", "multiline": False}), + "c": ("STRING", {"default": "!", "multiline": False}), } + } + + RETURN_TYPES = ("STRING",) + OUTPUT_NODE = True + FUNCTION = "evaluate" + CATEGORY = "Efficiency Nodes/Simple Eval" + + def evaluate(self, python_expression, print_to_console, a="", b="", c=""): + variables = {'a': a, 'b': b, 'c': c} # Define the variables for the expression + + functions = simpleeval.DEFAULT_FUNCTIONS.copy() + functions.update({"len": len}) # Add the functions for the expression + + result = simpleeval.simple_eval(python_expression, names=variables, functions=functions) + if print_to_console == "True": + print("\n\033[31mEvaluate Strings:\033[0m") + print(f"\033[90ma = {a} \nb = {b} \nc = {c}\033[0m") + print(f"{python_expression} = \033[92m" + str(result) + "\033[0m") + return (str(result),) # Convert result to a string before returning + + + # ================================================================================================================== + # TSC Simple Eval Examples (https://github.com/danthedeckie/simpleeval) + class TSC_EvalExamples: + @classmethod + def INPUT_TYPES(cls): + filepath = os.path.join(my_dir, 'workflows', 'SimpleEval_Node_Examples.txt') + with open(filepath, 'r') as file: + examples = file.read() + return {"required": {"models_text": ("STRING", {"default": examples, "multiline": True}), }, } + + RETURN_TYPES = () + CATEGORY = "Efficiency Nodes/Simple Eval" + + # ================================================================================================================== + NODE_CLASS_MAPPINGS.update({"Evaluate Integers": TSC_EvaluateInts}) + NODE_CLASS_MAPPINGS.update({"Evaluate Floats": TSC_EvaluateFloats}) + NODE_CLASS_MAPPINGS.update({"Evaluate Strings": TSC_EvaluateStrs}) + NODE_CLASS_MAPPINGS.update({"Simple Eval Examples": TSC_EvalExamples}) + +except ImportError: + print(f"\r\033[33mEfficiency Nodes Warning:\033[0m Failed to import python package 'simpleeval'; related nodes disabled.\n") diff --git a/node_settings.json b/node_settings.json index 24f28c0..5b9f9bf 100644 --- a/node_settings.json +++ b/node_settings.json @@ -6,16 +6,12 @@ "lora": 1 } }, - "KSampler (Efficient)": { - "vae_tiled": false - }, "XY Plot": { "model_cache": { "ckpt": 5, "vae": 5, "lora": 5 - }, - "vae_tiled": false + } } } diff --git a/tsc_utils.py b/tsc_utils.py index f2c0ade..767f46e 100644 --- a/tsc_utils.py +++ b/tsc_utils.py @@ -462,8 +462,10 @@ def global_preview_method(): #----------------------------------------------------------------------------------------------------------------------- # Auto install Efficiency Nodes Python package dependencies import subprocess - -# Note: Auto installer targets ComfyUI's python_embedded folder +# Note: Auto installer install packages inside the requirements.txt. +# It first trys ComfyUI's python_embedded folder if python.exe exists inside ...\ComfyUI_windows_portable\python_embeded. +# If no python.exe is found, it attempts a general global pip install of packages. +# On an error, an user is directed to attempt manually installing the packages themselves. def install_packages(my_dir): # Compute path to the target site-packages @@ -481,12 +483,21 @@ def install_packages(my_dir): for pkg in required_packages: if pkg not in installed_packages: print(f"\033[32mEfficiency Nodes:\033[0m Installing required package '{pkg}'...", end='', flush=True) - if use_embedded: # Targeted installation - subprocess.check_call(['pip', 'install', pkg, '--target=' + target_dir, '--no-warn-script-location', - '--disable-pip-version-check'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - else: # Untargeted installation - subprocess.check_call(['pip', 'install', pkg], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - print(f"\r\033[32mEfficiency Nodes:\033[0m Installing required package '{pkg}'... Installed!", flush=True) + try: + if use_embedded: # Targeted installation + subprocess.check_call(['pip', 'install', pkg, '--target=' + target_dir, '--no-warn-script-location', + '--disable-pip-version-check'], stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) + else: # Untargeted installation + subprocess.check_call(['pip', 'install', pkg], stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) + print(f"\r\033[32mEfficiency Nodes:\033[0m Installing required package '{pkg}'... Installed!", flush=True) + + except subprocess.CalledProcessError as e: # Failed installation + base_message = f"\r\033[31mEfficiency Nodes Error:\033[0m Failed to install python package '{pkg}'. " + if e.stderr: + error_message = e.stderr.decode() + print(base_message + f"Error message: {error_message}") + else: + print(base_message + "\nPlease check your permissions, network connectivity, or try a manual installation.") def packages(python_exe=None, versions=False): # Get packages of the active or embedded Python environment @@ -517,25 +528,27 @@ shutil.copy2(source_path, destination_path) #----------------------------------------------------------------------------------------------------------------------- # Establish a websocket connection to communicate with "efficiency-nodes.js" under: # ComfyUI\web\extensions\efficiency-nodes-comfyui\ -import websockets -import asyncio -import threading -import base64 -from io import BytesIO -from torchvision import transforms - -latest_image = list() -connected_client = None -websocket_status = True - def handle_websocket_failure(): global websocket_status if websocket_status: # Ensures the message is printed only once websocket_status = False - print(f"\r\033[33mEfficiency Nodes Warning:\033[0m Websocket connection failure.\n" - f"Live-generated preview images from the KSampler (Efficient) may not be cleared correctly. " - f"This can lead to extra images appearing in the node's preview results when live generation " - f"preview is enabled and vae decoding is set to 'true`.") + print(f"\r\033[33mEfficiency Nodes Warning:\033[0m Websocket connection failure." + f"\nEfficient KSampler's live preview images may not clear when vae decoding is set to 'true'.") + +# Initialize websocket related global variables +websocket_status = True +latest_image = list() +connected_client = None + +try: + import websockets + import asyncio + import threading + import base64 + from io import BytesIO + from torchvision import transforms +except ImportError: + handle_websocket_failure() async def server_logic(websocket, path): global latest_image, connected_client, websocket_status @@ -586,6 +599,7 @@ def send_command_to_frontend(startListening=False, maxCount=0, sendBlob=False): handle_websocket_failure() # Start the WebSocket server in a separate thread -server_thread = threading.Thread(target=run_server) -server_thread.daemon = True -server_thread.start() \ No newline at end of file +if websocket_status == True: + server_thread = threading.Thread(target=run_server) + server_thread.daemon = True + server_thread.start() \ No newline at end of file