From 72a9a3789fcebd2cee892b6dbcf9a04ea7782748 Mon Sep 17 00:00:00 2001 From: bobmagicii Date: Tue, 2 Apr 2024 15:39:29 -0500 Subject: [PATCH] thusfar --- LICENSE => .github/LICENSE.md | 0 .github/README.md | 21 +++++ .gitignore | 3 + __init__.py | 14 ++++ nodes/LoraWithMeta.py | 145 ++++++++++++++++++++++++++++++++++ nodes/TypecasterImage.py | 38 +++++++++ 6 files changed, 221 insertions(+) rename LICENSE => .github/LICENSE.md (100%) create mode 100644 .github/README.md create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 nodes/LoraWithMeta.py create mode 100644 nodes/TypecasterImage.py diff --git a/LICENSE b/.github/LICENSE.md similarity index 100% rename from LICENSE rename to .github/LICENSE.md diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..b443441 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,21 @@ +# ComfyKit Custom Nodes + +## LoraWithMetadata + +Accepts 3 LoRAs to blend and is able to load trigger presets from text files +named the same as the LoRA file, but with `.tp01.txt`, `.tp02.txt`, etc, instead +of the `.safetensors`. + +I like to copy the trigger words out of the website, put them in those presets +and then have a text widget show me all the triggers in play so I can work them +into my prompts. + +## TypecasterImage + +Takes an image, returns the same image. Hard typecasted. This was to fix +problems in nodes that try too hard to be dynamic not knowing what the datatype +is when you first load a workflow.json, which required manual reconnection +to make the workflow work that first time. + +TODO: make more typecasters. been abusing rg3 contexts for it but they also are +very bulky for many cases. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a39078 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# things to ignore + +**/__pycache__/** diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..7ed4ded --- /dev/null +++ b/__init__.py @@ -0,0 +1,14 @@ +from .nodes.LoraWithMeta import * +from .nodes.TypecasterImage import * + +NODE_CLASS_MAPPINGS = { + "LoraWithMeta": LoraWithMeta, + "TypecasterImage": TypecasterImage +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LoraWithMeta": "LoRA+Metadata", + "Recast Image": TypecasterImage +} + +__all__ = [ 'NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS' ] diff --git a/nodes/LoraWithMeta.py b/nodes/LoraWithMeta.py new file mode 100644 index 0000000..33ce0ea --- /dev/null +++ b/nodes/LoraWithMeta.py @@ -0,0 +1,145 @@ + +import os +import comfy.sd +import folder_paths + +class LoraWithMeta: + + def __init__(self): + pass + + RETURN_TYPES = ("MODEL", "CLIP", "STRING") + RETURN_NAMES = ("MODEL", "CLIP", "TRIGGERS") + + FUNCTION = "onRun" + CATEGORY = "bobmagicii" + + Bumper = 1 + + ################################################################ + ################################################################ + + @classmethod + def INPUT_TYPES(s): + + loraList = folder_paths.get_filename_list("loras") + loraList.insert(0, "None") + + fieldPreset = ("INT", { + "default": 0, "min": 0, "max": 99, + "step": 1, "display": "number" + }) + + fieldStr = ("FLOAT", { + "default": 1.0, "min": -16.0, "max": 16.0, + "step": 0.1, "display": "number" + }) + + output = { "required": { + "model": ("MODEL", ), + "clip": ("CLIP", ), + "lora1": (loraList, ), + "lora1_tgr_preset": fieldPreset, + "lora1_str_model": fieldStr, + "lora1_str_clip": fieldStr, + "lora2": (loraList, ), + "lora2_tgr_preset": fieldPreset, + "lora2_str_model": fieldStr, + "lora2_str_clip": fieldStr, + "lora3": (loraList, ), + "lora3_tgr_preset": fieldPreset, + "lora3_str_model": fieldStr, + "lora3_str_clip": fieldStr + } } + + return output + + ################################################################ + ################################################################ + + @classmethod + def IS_CHANGED( + self, model, clip, + lora1, lora1_tgr_preset, lora1_str_model, lora1_str_clip, + lora2, lora2_tgr_preset, lora2_str_model, lora2_str_clip, + lora3, lora3_tgr_preset, lora3_str_model, lora3_str_clip + ): + + ##self.Bumper += self.Bumper + + return self.Bumper + + ################################################################ + ################################################################ + + def onRun( + self, model, clip, + lora1, lora1_tgr_preset, lora1_str_model, lora1_str_clip, + lora2, lora2_tgr_preset, lora2_str_model, lora2_str_clip, + lora3, lora3_tgr_preset, lora3_str_model, lora3_str_clip + ): + + l1 = { "name": lora1, "tp": lora1_tgr_preset, "sm": lora1_str_model, "sc": lora1_str_clip } + l2 = { "name": lora2, "tp": lora2_tgr_preset, "sm": lora2_str_model, "sc": lora2_str_clip } + l3 = { "name": lora3, "tp": lora3_tgr_preset, "sm": lora3_str_model, "sc": lora3_str_clip } + + mblend, cblend = self.applyLoraStack( + model, clip, [ l1, l2, l3 ] + ) + + tblend = self.fetchLoraTriggers( + [ l1, l2, l3 ] + ) + + return (mblend, cblend, tblend) + + def applyLoraStack(self, model, clip, listOfLora): + + mblend = model + cblend = clip + + ######## + + for lora in listOfLora: + if(lora['name'] == "None"): + continue + + lpath = folder_paths.get_full_path("loras", lora['name']) + lfile = comfy.utils.load_torch_file(lpath, safe_load=True) + mblend, cblend = comfy.sd.load_lora_for_models(mblend, cblend, lfile, lora['sm'], lora['sc']) + print(f"[LoRA+Metadata Apply] {lpath}, {lora['sm']}, {lora['sc']}") + + ######## + + return (mblend, cblend) + + def fetchLoraTriggers(self, listOfLora): + + output = [] + + ######## + + for lora in listOfLora: + if(lora['name'] == 'None'): + continue + + if(lora['tp'] <= 0): + continue + + lpath = folder_paths.get_full_path('loras', lora['name']) + lpath = lpath.replace('.safetensors', f".tp{lora['tp']:02d}.txt") + + if(not os.path.isfile(lpath)): + print(f'[LoRA+Metadata Triggers] Preset Not Found: {lpath}') + continue + + print(f'[LoRA+Metadata Triggers] {lpath}') + + lfile = open(lpath, 'r') + output.append(lfile.readline()) + lfile.close() + + ######## + + return ', '.join(output) + diff --git a/nodes/TypecasterImage.py b/nodes/TypecasterImage.py new file mode 100644 index 0000000..1d47312 --- /dev/null +++ b/nodes/TypecasterImage.py @@ -0,0 +1,38 @@ + +import os +import comfy.sd +import folder_paths + +class TypecasterImage: + + def __init__(self): + pass + + RETURN_TYPES = ("IMAGE", ) + RETURN_NAMES = ("IMAGE", ) + + FUNCTION = "onRun" + CATEGORY = "bobmagicii" + + Bumper = 1 + + ################################################################ + ################################################################ + + @classmethod + def INPUT_TYPES(s): + + inputs = { "required": { + "image": ("IMAGE", ) + } } + + return inputs + + ################################################################ + ################################################################ + + def onRun(self, image): + + return (image, ) + +