thusfar
This commit is contained in:
@@ -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.
|
||||
@@ -0,0 +1,3 @@
|
||||
# things to ignore
|
||||
|
||||
**/__pycache__/**
|
||||
+14
@@ -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' ]
|
||||
@@ -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)
|
||||
|
||||
@@ -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, )
|
||||
|
||||
|
||||
Reference in New Issue
Block a user