diff --git a/__init__.py b/__init__.py index c861e65..ead1738 100644 --- a/__init__.py +++ b/__init__.py @@ -26,6 +26,9 @@ from custom_nodes.comfyui_gr85.nodes.prompt.wildcard.random_title_character impo from custom_nodes.comfyui_gr85.nodes.prompt.wildcard.random_wildcard_tag_picker import RandomWildcardTagPicker from custom_nodes.comfyui_gr85.nodes.utils.flux_model_merge_parameters import FluxModelMergeParameters from custom_nodes.comfyui_gr85.nodes.utils.next_seed import NextSeed +from custom_nodes.comfyui_gr85.nodes.utils.random_float import RandomFloat +from custom_nodes.comfyui_gr85.nodes.utils.random_int import RandomInt +from custom_nodes.comfyui_gr85.nodes.utils.save_text_file import SaveTextFile from custom_nodes.comfyui_gr85.nodes.utils.str_safe import StrSafe NODE_CLASS_MAPPINGS = { @@ -57,6 +60,9 @@ NODE_CLASS_MAPPINGS = { "GR85_UpdateT5Blocks": UpdateT5Blocks, "GR85_GenerateDefaultClipValues": GenerateDefaultCLIPValues, "GR85_FluxAttentionSeeker2": FluxAttentionSeeker2, + "GR85_RandomFloat": RandomFloat, + "GR85_RandomInt": RandomInt, + "GR85_SaveTextFile": SaveTextFile, "GR85_FluxModelMergeParameters": FluxModelMergeParameters, "GR85_NextSeed": NextSeed, @@ -95,6 +101,9 @@ NODE_DISPLAY_NAME_MAPPINGS = { "GR85_UpdateT5Blocks": "Update T5 Blocks", "GR85_GenerateDefaultClipValues": "Generate Default CLIP Values", "GR85_FluxAttentionSeeker2": "Flux Attention Seeker 2", + "GR85_RandomFloat": "Random Float", + "GR85_RandomInt": "Random Int", + "GR85_SaveTextFile": "Save Text File", "GR85_FluxModelMergeParameters": "Flux Model Merge Parameters", "GR85_NextSeed": "Next Seed", diff --git a/nodes/prompt/integer_sequence_modifier.py b/nodes/prompt/integer_sequence_modifier.py index c27edfb..5585425 100644 --- a/nodes/prompt/integer_sequence_modifier.py +++ b/nodes/prompt/integer_sequence_modifier.py @@ -40,7 +40,7 @@ class IntegerSequenceModifier: RETURN_NAMES = ("comma_separated_list",) FUNCTION = "modify_sequence" - CATEGORY = "Custom/ComfyUI" + CATEGORY = "GR85/Utils" def modify_sequence(self, starting_int: int, amount_of_ints: int, index: int) -> tuple: """ diff --git a/nodes/prompt/tag_injector.py b/nodes/prompt/tag_injector.py index a1d0d60..0d6498d 100644 --- a/nodes/prompt/tag_injector.py +++ b/nodes/prompt/tag_injector.py @@ -9,7 +9,6 @@ class TagInjector: def INPUT_TYPES(cls): return { "required": { - "seed": ("INT", {"forceInput": True, "default": 0}), "template": ("STRING", {"multiline": True, "dynamicPrompts": True, "default": "__elements__"}), }, @@ -28,7 +27,7 @@ class TagInjector: FUNCTION = "inject_tag" CATEGORY = "GR85/Prompt" - def inject_tag(self, seed, template, tag_1=None, tag_2=None, tag_3=None, + def inject_tag(self, template, tag_1=None, tag_2=None, tag_3=None, tag_name_1="elements", tag_name_2="stuff", tag_name_3="things"): """ Inject tags into the template based on the given tag names and values. diff --git a/nodes/prompt/tag_injector_large.py b/nodes/prompt/tag_injector_large.py index dede3cd..69a95f4 100644 --- a/nodes/prompt/tag_injector_large.py +++ b/nodes/prompt/tag_injector_large.py @@ -9,7 +9,6 @@ class TagInjectorLarge: def INPUT_TYPES(cls): return { "required": { - "seed": ("INT", {"forceInput": True, "default": 0}), "template": ("STRING", {"multiline": True, "dynamicPrompts": True, "default": "In a __location__ under a __weather__ sky, a __personality__ person shows __emotion__ while wearing __style__. The __time__ is perfect for a __action__ amidst __mood__."}), }, @@ -42,7 +41,7 @@ class TagInjectorLarge: FUNCTION = "inject_tag" CATEGORY = "GR85/Prompt" - def inject_tag(self, seed, template, tag_1=None, tag_2=None, tag_3=None, tag_4=None, tag_5=None, + def inject_tag(self, template, tag_1=None, tag_2=None, tag_3=None, tag_4=None, tag_5=None, tag_6=None, tag_7=None, tag_8=None, tag_9=None, tag_10=None, tag_name_1="location", tag_name_2="color", tag_name_3="object", tag_name_4="emotion", tag_name_5="weather", tag_name_6="personality", tag_name_7="time", tag_name_8="action", diff --git a/nodes/utils/flux_model_merge_parameters.py b/nodes/utils/flux_model_merge_parameters.py index be87299..98969bb 100644 --- a/nodes/utils/flux_model_merge_parameters.py +++ b/nodes/utils/flux_model_merge_parameters.py @@ -78,6 +78,7 @@ class FluxModelMergeParameters: # Defines the types of values returned by the node RETURN_TYPES = ( "STRING", # Comma-separated values + "FLOAT", *IMAGE_INPUTS, *DOUBLE_BLOCKS, *SINGLE_BLOCKS, @@ -93,6 +94,7 @@ class FluxModelMergeParameters: # Combines all return names into a single tuple RETURN_NAMES = ( "comma_separated_values", + "average_value", *IMAGE_INPUT_NAMES, *DOUBLE_BLOCK_NAMES, *SINGLE_BLOCK_NAMES, @@ -213,5 +215,7 @@ class FluxModelMergeParameters: # Create a comma-separated string of all values for reference comma_separated_values = ", ".join(map(str, value_list)) + average_value = sum(value_list) / len(value_list) + # Return the comma-separated string and unpack the list of values - return (comma_separated_values, *value_list) + return (comma_separated_values, average_value, *value_list) diff --git a/nodes/utils/random_float.py b/nodes/utils/random_float.py new file mode 100644 index 0000000..6b422ef --- /dev/null +++ b/nodes/utils/random_float.py @@ -0,0 +1,47 @@ +import random + +class RandomFloat: + """ + A ComfyUI node class that generates a random float based on given inputs. + + Attributes: + INPUT_TYPES (dict): Defines the required and optional input types for the node. + RETURN_TYPES (tuple): Specifies the types of values returned by the node. + RETURN_NAMES (tuple): Specifies the names of the returned values for better identification. + FUNCTION (str): The function name used for processing parameters. + CATEGORY (str): The category under which this node is classified. + """ + + @classmethod + def INPUT_TYPES(cls) -> dict: + return { + "required": { + "seed": ("INT", {"default": 0, "min": 0, "display": "number"}), + "min_value": ("FLOAT", {"default": 0.0, "min": -1e-10, "max": 1e10, "step": 0.0001, "display": "number"}), + "max_value": ("FLOAT", {"default": 1.0, "min": -1e-10, "max": 1e10, "step": 0.0001, "display": "number"}), + "decimal_places": ("INT", {"default": 10, "min": 0, "display": "number"}), + } + } + + RETURN_TYPES = ("FLOAT",) + RETURN_NAMES = ("random_float",) + + FUNCTION = "generate_random_float" + CATEGORY = "GR85/Utils" + + def generate_random_float(self, seed: int, min_value: float, max_value: float, decimal_places: int) -> tuple: + """ + Generates a random float based on the given seed, min, max, and decimal places. + + Args: + seed (int): The seed value for random number generation. + min_value (float): The minimum value of the generated float. + max_value (float): The maximum value of the generated float. + decimal_places (int): The number of decimal places for the generated float. + + Returns: + tuple: A tuple containing the generated random float. + """ + random.seed(seed) + random_float = round(random.uniform(min_value, max_value), decimal_places) + return (random_float,) diff --git a/nodes/utils/random_int.py b/nodes/utils/random_int.py new file mode 100644 index 0000000..37573d8 --- /dev/null +++ b/nodes/utils/random_int.py @@ -0,0 +1,45 @@ +import random + +class RandomInt: + """ + A ComfyUI node class that generates a random integer based on given inputs. + + Attributes: + INPUT_TYPES (dict): Defines the required and optional input types for the node. + RETURN_TYPES (tuple): Specifies the types of values returned by the node. + RETURN_NAMES (tuple): Specifies the names of the returned values for better identification. + FUNCTION (str): The function name used for processing parameters. + CATEGORY (str): The category under which this node is classified. + """ + + @classmethod + def INPUT_TYPES(cls) -> dict: + return { + "required": { + "seed": ("INT", {"default": 0, "min": 0, "display": "number"}), + "min_value": ("INT", {"default": 0, "min": -1e10, "max": 1e10, "display": "number"}), + "max_value": ("INT", {"default": 100, "min": -1e10, "max": 1e10, "display": "number"}), + } + } + + RETURN_TYPES = ("INT",) + RETURN_NAMES = ("random_int",) + + FUNCTION = "generate_random_int" + CATEGORY = "GR85/Utils" + + def generate_random_int(self, seed: int, min_value: int, max_value: int) -> tuple: + """ + Generates a random integer based on the given seed, min, and max values. + + Args: + seed (int): The seed value for random number generation. + min_value (int): The minimum value of the generated integer. + max_value (int): The maximum value of the generated integer. + + Returns: + tuple: A tuple containing the generated random integer. + """ + random.seed(seed) + random_int = random.randint(min_value, max_value) + return (random_int,) diff --git a/nodes/utils/save_text_file.py b/nodes/utils/save_text_file.py new file mode 100644 index 0000000..1dd128b --- /dev/null +++ b/nodes/utils/save_text_file.py @@ -0,0 +1,101 @@ +import os +import re + +class SaveTextFile: + """ + A ComfyUI node class that saves text to a file based on given inputs. + + Attributes: + INPUT_TYPES (dict): Defines the required and optional input types for the node. + RETURN_TYPES (tuple): Specifies the types of values returned by the node. + RETURN_NAMES (tuple): Specifies the names of the returned values for better identification. + FUNCTION (str): The function name used for processing parameters. + CATEGORY (str): The category under which this node is classified. + """ + + @classmethod + def INPUT_TYPES(cls) -> dict: + return { + "required": { + "text": ("STRING", {"forceInput": True, "multiline": True}), # The text content to be saved. + "path": ("STRING", {"default": './ComfyUI/output/', "multiline": True}), # Directory path where the file will be saved. + "filename_prefix": ("STRING", {"default": "ComfyUI"}), # Prefix for the filename. + "filename_delimiter": ("STRING", {"default": "_"}), # Delimiter between prefix and number. + "filename_number_padding": ("INT", {"default": 4, "min": 0, "max": 9, "step": 1}), # Number of padding zeros for the filename. + }, + "optional": { + "file_extension": ("STRING", {"default": ".txt"}), # File extension for the saved file. + "encoding": ("STRING", {"default": "utf-8"}) # Encoding to use when writing the file. + } + } + + OUTPUT_NODE = True + RETURN_TYPES = () + FUNCTION = "save_text_file" + CATEGORY = "GR85/Utils" + + def save_text_file(self, text: str, path: str, filename_prefix: str = 'ComfyUI', filename_delimiter: str = '_', filename_number_padding: int = 4, file_extension: str = '.txt', encoding: str = 'utf-8'): + # Ensure the directory exists, create if it does not exist + if not os.path.exists(path): + print(f"The path `{path}` doesn't exist! Creating it...") + try: + os.makedirs(path, exist_ok=True) + except OSError as e: + # If directory creation fails, print an error and return + print(f"The path `{path}` could not be created! Is there write access?\n{e}") + return + + # If the text is empty, print an error and return + if text.strip() == '': + print(f"There is no text specified to save! Text is empty.") + return + + delimiter = filename_delimiter + number_padding = int(filename_number_padding) + # Generate the filename using the specified parameters + filename = self.generate_filename(path, filename_prefix, delimiter, number_padding, file_extension) + file_path = os.path.join(path, filename) + # Write the text content to the file + self.write_text_file(file_path, text, encoding) + return (text, {"ui": {"string": text}}) + + def generate_filename(self, path: str, prefix: str, delimiter: str, number_padding: int, extension: str) -> str: + # Generate a filename based on the given parameters + if number_padding == 0: + # If number_padding is 0, don't use a numerical suffix + filename = f"{prefix}{extension}" + else: + # Create a regex pattern to match existing files with the specified prefix and delimiter + pattern = f"{re.escape(prefix)}{re.escape(delimiter)}(\d{{{number_padding}}})" + # Find all existing files that match the pattern and extract their counters + existing_counters = [ + int(re.search(pattern, filename).group(1)) + for filename in os.listdir(path) + if re.match(pattern, filename) + ] + # Sort counters in descending order to find the highest value + existing_counters.sort(reverse=True) + if existing_counters: + # Set the next counter value to the highest existing value plus one + counter = existing_counters[0] + 1 + else: + # If no existing files, start with counter 1 + counter = 1 + # Format the filename with the prefix, delimiter, counter, and extension + filename = f"{prefix}{delimiter}{counter:0{number_padding}}{extension}" + # Ensure the generated filename does not already exist + while os.path.exists(os.path.join(path, filename)): + counter += 1 + filename = f"{prefix}{delimiter}{counter:0{number_padding}}{extension}" + return filename + + def write_text_file(self, file: str, content: str, encoding: str): + # Write the content to the specified file with the given encoding + try: + with open(file, 'w', encoding=encoding, newline='\n') as f: + f.write(content) + except OSError: + # Print an error message if the file could not be saved + print(f"Unable to save file `{file}`") + +# Let me know if you need to customize or extend this node further!