Files
Hellrunner 595a67ea23 v1.0 - release
Added Load Mask-Map, Mojo Maker, Save Mojo, Mojo Loader and Adjust Mojo

* Magical Save Node
    - added "Active" Boolean (On/Off Switch), to get it more in line with other nodes that support advanced comfy-flow usability.
    - changed "Save_gen_data_to_txt" and "Save_gen_data_to_png" to be actual booleans instead of selection boxes
    - added "Formatting" selection for future meta parser compatibility options.
2024-09-05 18:02:41 +02:00

811 lines
36 KiB
Python

import torch
import os
import json
import time
import re
import folder_paths
import nodes
import node_helpers
import numpy as np
from safetensors.torch import save_file, load
def getVer(full_path):
tmp = 0
file = open(full_path, "rb")
data = file.read()
loaded = load(data)
tmp = int(loaded["Ver"][0][0])
file.close()
return tmp
def getInfo(full_path):
tmp = 0
file = open(full_path, "rb")
data = file.read()
loaded = load(data)
ver = int(loaded["Ver"][0][0])
info = json.loads("".join(map(chr, loaded["Info"])))
file.close()
return ver, info
class SaveMojo:
@classmethod
def INPUT_TYPES(s):
return {"required": {"Active": ("BOOLEAN", {"default": True, "label_on":"On", "label_off":"Off", "tooltip":'Boolean On/Off Switch for better integration in complex comfy-flows'}),
"Output_Path": ("STRING", {"default": 'Hellkars/Mojos', "multiline": False, "tooltip":'Subfolder Path into "output"'}),
"Name": ("STRING", {"default": "ShareMy", "tooltip":'Mojo File Name'}),
"Override": ("BOOLEAN", {"default": True, "label_on":"On", "label_off":"Off", "tooltip":'Saves over the exact file name on "True/On" or creates numbered unique files on "False/Off"'}),
"latent": ("LATENT", {"tooltip":'Latents to Mojo file. Provide decoding info.'}),
"Positive_Mojo": ("MOJO", {"tooltip":'Positive Mojo Flow'}),
"Negative_Mojo": ("MOJO", {"tooltip":'Negative Mojo Flow'}),
"Info": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "tooltip":'Extra Mojo information. Provide models used. With what VAE to decode the latent... and such'})}}
OUTPUT_IS_LIST = ()
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = 'energize'
CATEGORY = "Hellrunner's/Mojo"
DESCRIPTION = "Saves a .mojo file using safetensors with lightweight information, to further refine or recreate an image. Provide various clip sources to make your mojo more versatile."
def energize(self, Active, Output_Path='Hellkars/Mojos', Name='ShareMy', Override=True, latent=None, Positive_Mojo=None, Negative_Mojo=None, Info=""):
if not Active:
return ()
path = Output_Path
if Name == "":
Name="ShareMy"
addPth = Name.replace('\ ','/').split("/")
filename = addPth.pop()
for folder in addPth:
path = os.path.join(path, folder)
full_output_folder = os.path.join(folder_paths.get_output_directory(), path)
if not os.path.exists(full_output_folder):
os.makedirs(full_output_folder, exist_ok=True)
file = f'{filename}.mojo'
if not Override:
List = os.listdir(full_output_folder)
counter = 1
for FileName in List:
if (FileName.split(".")).pop() == 'mojo':
counter+=1
file = f"{filename}_{counter:05}.mojo"
while os.path.exists(os.path.join(full_output_folder, file)):
counter += 1
file = f"{filename}_{counter:05}.mojo"
Buffer = {}
TokIndex = 0
Buffer["Lnt"] = latent["samples"]
for Tok in Positive_Mojo:
TikIndex = 0
for Tik in Tok[0]:
for TE in Tik:
Buffer[f"Pos.{TokIndex}.Tik.{TikIndex}.{TE}"] = torch.frombuffer(bytearray(json.dumps(Tok[0][TikIndex][TE]), 'utf-8'), dtype=torch.uint8)
TikIndex += 1
for extra in Tok[1]:
if type(Tok[1][extra]) is torch.Tensor:
Buffer[f"Pos.{TokIndex}.Mask.{extra}"] = Tok[1][extra].contiguous()
else:
try:
Buffer[f"Pos.{TokIndex}.Mask.{extra}"] = torch.frombuffer(bytearray(json.dumps(Tok[1][extra]), 'utf-8'), dtype=torch.uint8)
except (TypeError):
print(f'{Tok[1][extra]} excluded')
for extraToo in Tok[2]:
if type(Tok[2][extraToo]) is torch.Tensor:
Buffer[f"Pos.{TokIndex}.Extra.{extraToo}"] = Tok[2][extraToo].contiguous()
else:
try:
Buffer[f"Pos.{TokIndex}.Extra.{extraToo}"] = torch.frombuffer(bytearray(json.dumps(Tok[2][extraToo]), 'utf-8'), dtype=torch.uint8)
except (TypeError):
print(f'{Tok[2][extraToo]} excluded')
TokIndex += 1
TokIndex = 0
for Tok in Negative_Mojo:
TikIndex = 0
for Tik in Tok[0]:
for TE in Tik:
Buffer[f"Neg.{TokIndex}.Tik.{TikIndex}.{TE}"] = torch.frombuffer(bytearray(json.dumps(Tok[0][TikIndex][TE]), 'utf-8'), dtype=torch.uint8)
TikIndex += 1
for extra in Tok[1]:
if type(Tok[1][extra]) is torch.Tensor:
Buffer[f"Neg.{TokIndex}.Mask.{extra}"] = Tok[1][extra].contiguous()
else:
try:
Buffer[f"Neg.{TokIndex}.Mask.{extra}"] = torch.frombuffer(bytearray(json.dumps(Tok[1][extra]), 'utf-8'), dtype=torch.uint8)
except (TypeError):
print(f'{Tok[1][extra]} excluded')
for extraToo in Tok[2]:
if type(Tok[2][extraToo]) is torch.Tensor:
Buffer[f"Neg.{TokIndex}.Extra.{extraToo}"] = Tok[2][extraToo].contiguous()
else:
try:
Buffer[f"Neg.{TokIndex}.Extra.{extraToo}"] = torch.frombuffer(bytearray(json.dumps(Tok[2][extraToo]), 'utf-8'), dtype=torch.uint8)
except (TypeError):
print(f'{Tok[2][extraToo]} excluded')
TokIndex += 1
Buffer["Info"] = torch.frombuffer(bytearray(json.dumps(Info), 'utf-8'), dtype=torch.uint8)
full_path = os.path.join(full_output_folder, file)
if os.path.exists(full_path):
Buffer["Ver"] = torch.tensor([[getVer(full_path)+1]])
save_file(Buffer, full_path)
print(f'{full_path} updated')
else:
Buffer["Ver"] = torch.tensor([[0]])
save_file(Buffer, full_path)
print(f'{full_path} saved')
return ()
def makeMaskSlot(mask, strength, set_cond_area, Cond = None):
set_area_to_bounds = False
if set_cond_area != "default":
set_area_to_bounds = True
returnSlot = {"mask": mask, "set_area_to_bounds": set_area_to_bounds, "mask_strength": strength}
C = []
if Cond is not None:
if len(mask.shape) < 3:
mask = mask.unsqueeze(0)
maskSlot = {"mask": mask, "set_area_to_bounds": set_area_to_bounds, "mask_strength": strength}
C = node_helpers.conditioning_set_values(Cond, maskSlot)
return C, returnSlot
def condition(clip, Tokens, guidance, width, height, crop_w, crop_h, target_width, target_height, start_percent, end_percent, mask, strength, set_cond_area):
output = clip.encode_from_tokens(Tokens, return_pooled=True, return_dict=True)
cond = output.pop("cond")
output["guidance"] = guidance
output["width"] = width
output["height"] = height
output["crop_w"] = crop_w
output["crop_h"] = crop_h
output["target_width"] = target_width
output["target_height"] = target_height
C = [[cond, output]]
C = node_helpers.conditioning_set_values(C, {"start_percent": start_percent,
"end_percent": end_percent})
returnSlot = {}
if mask is not None:
C, returnSlot = makeMaskSlot(mask, strength, set_cond_area, C)
return C, returnSlot
def ChToNr(choice):
match choice:
case "Main":
return 0
case "Additional":
return 1
case "Special":
return 2
case _:
return 0
def NrToCh(number):
match number:
case 0:
return "Main"
case 1:
return "Additional"
case 2:
return "Special"
case _:
return "Main"
def makeSettings(settings, guidance, width, height, crop_w, crop_h, target_width, target_height, start_percent, end_percent):
settings["guidance"] = guidance
settings["width"] = width
settings["height"] = height
settings["crop_w"] = crop_w
settings["crop_h"] = crop_h
settings["target_width"] = target_width
settings["target_height"] = target_height
settings["start_percent"] = 0.0
settings["end_percent"]= 1.0
if start_percent >=0.0:
settings["start_percent"] = start_percent
if end_percent >=0.0:
settings["end_percent"] = end_percent
return settings
def SetMojo(Mojo, guidance, width, height, crop_w, crop_h, target_width, target_height, start_percent=-1.0, end_percent=-1.0):
for i in range(len(Mojo)):
sp = start_percent
ep = end_percent
if sp < 0.0 and "start_percent" in Mojo[i][2]:
sp = Mojo[i][2]["start_percent"]
if ep < 0.0 and "end_percent" in Mojo[i][2]:
ep = Mojo[i][2]["end_percent"]
settings = makeSettings({}, guidance, width, height, crop_w, crop_h, target_width, target_height, sp, ep)
Mojo[i][2] = settings
def ClipTest(clip):
clipT = clip.tokenize("a woman")
return list(clipT.keys())
def SqueezeMojo(Mojo_In, Squeeze_Mojo, Merge_Mojo=None, Strength=1.0):
if Squeeze_Mojo == 'Disabled':
return Mojo_In
KeepMojo = []
MergeMojo = []
MergeSet = {}
MergerContainer = []
Sig=[]
MergeTok = [[],{},{}]
for Tok in Mojo_In:
if Squeeze_Mojo == 'Keep Masks':
if "mask" in Tok[1]:
KeepMojo.append(Tok)
continue
lenTok = len(Tok[0])
for i in range(lenTok):
for key in Tok[0][i]:
if len(MergerContainer) < (i+1):
MergerContainer.append({})
Sig.append({})
if not key in MergerContainer[i]:
MergerContainer[i][key]=[]
sig=[]
tmpL = len(Tok[0][i][key][0][0])
for r in range(tmpL):
if type(Tok[0][i][key][0][0][r]) is int:
sig.append("int")
elif type(Tok[0][i][key][0][0][r]) is float:
sig.append("float")
elif type(Tok[0][i][key][0][0][r]) is complex:
sig.append("complex")
Sig[i][key] = sig
MergerContainer[i][key].append(Tok[0][i][key])
MClen = len(MergerContainer)
if MClen > 0:
for m in range(MClen):
for key in MergerContainer[m]:
cont = None
for g in range(len(MergerContainer[m][key])):
npArr = np.array(MergerContainer[m][key][g], np.float32)
if cont is None:
cont = npArr
else:
cont = np.maximum(cont, npArr)
if Merge_Mojo is not None:
lastInd = len(Merge_Mojo)-1
if m < len(Merge_Mojo[lastInd][0][m]):
if key in Merge_Mojo[lastInd][0][m]:
Merge_Mojo_npArr = np.array(Merge_Mojo[lastInd][0][m][key], np.float32)
tch = torch.from_numpy(cont)
MMtch = torch.from_numpy(Merge_Mojo_npArr)
cont = torch.lerp(MMtch,tch,Strength).numpy()
if len(MergeTok[0]) < (m+1):
MergeTok[0].append({})
lister=[]
for element in cont[0]:
L=[]
for T in range(len(element)):
match Sig[i][key][T]:
case "int":
L.append(int(round(element[T],6)))
case "float":
L.append(float(element[T]))
case "complex":
L.append(complex(element[T]))
lister.append(L)
MergeTok[0][m][key] = [lister]
MergeTok[2] = Mojo_In[len(Mojo_In)-1][2]
KeepMojo.append(MergeTok)
return KeepMojo
def TakeIn(OutM, step_start, step_end, Mojo_Intake, Squeeze_Mojo_Intake, Intake_Strength, setInfo):
outMojo = []
outMojo = OutM
if Mojo_Intake is not None and Intake_Strength>0.0:
if Intake_Strength==1.0:
InMojo_Intake = SqueezeMojo(Mojo_Intake, Squeeze_Mojo_Intake)
SetMojo(InMojo_Intake, setInfo["guidance"], setInfo["width"], setInfo["height"], setInfo["crop_w"], setInfo["crop_h"], setInfo["target_width"], setInfo["target_height"])
for a in InMojo_Intake:
outMojo.append(a)
else:
InMojo_Intake = SqueezeMojo(Mojo_Intake, Squeeze_Mojo_Intake, outMojo, Intake_Strength)
SetMojo(InMojo_Intake, setInfo["guidance"], setInfo["width"], setInfo["height"], setInfo["crop_w"], setInfo["crop_h"], setInfo["target_width"], setInfo["target_height"])
inStart = InMojo_Intake[len(InMojo_Intake)-1][2]["start_percent"]
inEnd = InMojo_Intake[len(InMojo_Intake)-1][2]["end_percent"]
startA = min(inStart, step_start)
endA = max(inEnd,step_end)
startOv = max(inStart, step_start)
endOv = min(inEnd,step_end)
OM = []
if startOv-endOv<0:
for a in InMojo_Intake:
OM.append(a)
OM[len(OM)-1][2]["start_percent"] = startOv
OM[len(OM)-1][2]["end_percent"] = endOv
if step_start<startOv:
for a in outMojo:
OM.append(outMojo[a])
OM[len(OM)-1][2]["start_percent"] = step_start
OM[len(OM)-1][2]["end_percent"] = startOv
elif inStart<startOv:
MI = SqueezeMojo(Mojo_Intake, Squeeze_Mojo_Intake)
SetMojo(MI, setInfo["guidance"], setInfo["width"], setInfo["height"], setInfo["crop_w"], setInfo["crop_h"], setInfo["target_width"], setInfo["target_height"])
for a in MI:
OM.append(a)
OM[len(OM)-1][2]["start_percent"] = inStart
OM[len(OM)-1][2]["end_percent"] = startOv
if step_end>endOv:
for a in outMojo:
OM.append(outMojo[a])
OM[len(OM)-1][2]["start_percent"] = endOv
OM[len(OM)-1][2]["end_percent"] = endA
elif inEnd>endOv:
MI = SqueezeMojo(Mojo_Intake, Squeeze_Mojo_Intake)
SetMojo(MI, setInfo["guidance"], setInfo["width"], setInfo["height"], setInfo["crop_w"], setInfo["crop_h"], setInfo["target_width"], setInfo["target_height"])
for a in MI:
OM.append(a)
OM[len(OM)-1][2]["start_percent"] = endOv
OM[len(OM)-1][2]["end_percent"] = inEnd
outMojo=OM
else:
for a in InMojo_Intake:
outMojo.append(a)
return outMojo
def makeCond(clip, ClipT, Buffer, choice):
mask = None
srt = 1.0
setarea = False
compatibe = True
if "mask" in Buffer[1]:
mask = Buffer[1]["mask"]
srt = Buffer[1]["mask_strength"]
setarea = Buffer[1]["set_area_to_bounds"]
Tok = {}
def clipDetect(Clip_T, Buffer, indPref, AllArr):
Tok = {}
ind = 0
SatArr = Clip_T.copy()
Msg = {}
for c in Buffer:
for ck in c.keys():
if not ck in AllArr:
AllArr.append(ck)
for key in Clip_T:
if key in c.keys():
if key in Tok.keys():
if ind == indPref:
Tok[key] = c[key]
Msg[key] = f', {key} from {NrToCh(ind)}'
else:
Tok[key] = c[key]
Msg[key] = f', {key} from {NrToCh(ind)}'
SatArr.remove(key)
ind+=1
Biff = ""
if len(SatArr) > 0:
Biff += " has no matching encoder signature and has been excluded. Main_clip invalid."
compatibe = False
for c in Msg:
Biff += Msg[c]
return Tok, Biff, AllArr
AllArr = []
Tok, Buff, AllArr = clipDetect(ClipT, Buffer[0], ChToNr(choice), AllArr)
C = []
M = {}
if compatibe:
C, M = condition(clip, Tok,
Buffer[2]["guidance"],
Buffer[2]["width"],
Buffer[2]["height"],
Buffer[2]["crop_w"],
Buffer[2]["crop_h"],
Buffer[2]["target_width"],
Buffer[2]["target_height"],
Buffer[2]["start_percent"],
Buffer[2]["end_percent"],
mask,
srt,
"mask bounds" if setarea else "default")
return C, M, Buff, AllArr
def CondMojo(Main_clip, Buffer, ClipT, Clip_Choice, Info, Name="Mojo"):
inx = 0
C = []
Masks = []
for borfer in Buffer:
Info += f'\n\n{Name} {inx}'
outC_Pos, outMask, outInfo, outTE = makeCond(Main_clip, ClipT, borfer, Clip_Choice)
C += outC_Pos
if "mask" in outMask:
Masks.append(outMask["mask"])
Info += outInfo
Info += f' {outTE}'
inx+=1
return C, Masks, Info
def MaskMojo(Buffer):
Masks = []
for borfer in range(len(Buffer)):
if "mask" in Buffer[borfer][1]:
Masks.append(Buffer[borfer][1]["mask"])
return Masks
class MojoLoader:
full_output_folder = os.path.join(folder_paths.get_output_directory(), 'Hellkars/Mojos')
flileList = []
if os.path.exists(full_output_folder):
flileList = os.listdir(full_output_folder)
@classmethod
def INPUT_TYPES(self):
return {"required": {
"Main_clip": ("CLIP", {"tooltip":'Main Clip source'}),
"Output_Path": ("STRING", {"default": 'Hellkars/Mojos', "multiline": False, "tooltip":'Subfolder Path into "output"'}),
"Name": ("STRING", {"default": "ShareMy", "tooltip":'Mojo File Name'}),
"Clip_Choice": (['Main', 'Additional', 'Special'],{"default":'Main', "tooltip":'Biases the assembly towards a clip source. IF such source is provided. The only mandatory source is Main. Usage is heavily dependent on how the .mojo file was made.'}),
"Conditioning_Output": (['Disabled','Mojo (Combine)'],{"default":'Mojo (Combine)', "tooltip":'How to construct the Conditioning Output'}),}}
RETURN_TYPES = ('LATENT','CONDITIONING','CONDITIONING','MOJO','MOJO', 'STRING', 'MASK', 'MASK')
RETURN_NAMES = ('Latents','Positive', 'Negative', 'Positive_Mojo', 'Negative_Mojo', 'Info', 'Positive_Masks', 'Negative_Masks')
OUTPUT_IS_LIST = (False, False, False, False, False, False, True, True)
OUTPUT_NODE = False
OUTPUT_TOOLTIPS = ('Latents',
'Positive Conditionings, if not "Disabled"',
'Negative Conditionings, if not "Disabled"',
'Positive Mojo Flow',
'Negative Mojo Flow',
"Info String with useful information about the Mojo and it's assembly.",
'Positive Masks, if provided',
'Negative Masks, if provided')
FUNCTION = 'energize'
CATEGORY = "Hellrunner's/Mojo"
DESCRIPTION = "Loads a .mojo file for further refinement or recreation of an image. The usage of the content may vary dependent on it's construction."
@classmethod
def IS_CHANGED(self, Main_clip, Output_Path, Name, Clip_Choice, Conditioning_Output):
return getVer(os.path.join(self.full_output_folder, f'{Name}.mojo'))
@classmethod
def VALIDATE_INPUTS(self, Main_clip, Output_Path, Name, Clip_Choice, Conditioning_Output):
if not os.path.exists(os.path.join(self.full_output_folder, f'{Name}.mojo')):
return "Invalid Mojo File: {}".format(Name)
return True
def energize(self, Main_clip, Output_Path, Name, Clip_Choice, Conditioning_Output):
self.full_output_folder = os.path.join(folder_paths.get_output_directory(), Output_Path)
self.flileList = os.listdir(self.full_output_folder)
compatibe = True
Buffer = {}
file = open(os.path.join(self.full_output_folder, f'{Name}.mojo'), "rb")
data = file.read()
loaded = load(data)
file.close()
for Key in loaded.keys():
TenK = loaded[Key]
if '.' in Key:
SplitArray = Key.split('.')
if not SplitArray[0] in Buffer:
Buffer[SplitArray[0]] = []
index = int(SplitArray[1])
while len(Buffer[SplitArray[0]]) < (index+1):
Buffer[SplitArray[0]].append([[],{},{}])
if SplitArray[2] == "Tik":
Tik_index = int(SplitArray[3])
while len(Buffer[SplitArray[0]][index][0]) < (Tik_index+1):
Buffer[SplitArray[0]][index][0].append({})
Buffer[SplitArray[0]][index][0][Tik_index][SplitArray[4]] = json.loads("".join(map(chr, TenK)))
if SplitArray[2] == "Mask":
if SplitArray[3] == "mask":
Buffer[SplitArray[0]][index][1][SplitArray[3]] = TenK
else:
Buffer[SplitArray[0]][index][1][SplitArray[3]] = json.loads("".join(map(chr, TenK)))
if SplitArray[2] == "Extra":
Buffer[SplitArray[0]][index][2][SplitArray[3]] = json.loads("".join(map(chr, TenK)))
else:
if Key == "Info":
Buffer[Key] = json.loads("".join(map(chr, TenK)))
else:
Buffer[Key] = TenK
C_Pos=[]
C_Neg=[]
Buffer["PosMasks"]=[]
Buffer["NegMasks"]=[]
if Conditioning_Output != "Disabled":
ClipT = ClipTest(Main_clip)
C_Pos, Buffer["PosMasks"], Buffer["Info"] = CondMojo(Main_clip, Buffer["Pos"], ClipT, Clip_Choice, Buffer["Info"],"Positive")
C_Neg, Buffer["NegMasks"], Buffer["Info"] = CondMojo(Main_clip, Buffer["Neg"], ClipT, Clip_Choice, Buffer["Info"],"Negative")
else:
Buffer["PosMasks"] = MaskMojo(Buffer["Pos"])
Buffer["NegMasks"] = MaskMojo(Buffer["Neg"])
Buffer['Info'] += f'\n\nMojo File iteration {Buffer["Ver"][0][0]}\n\n'
return ({"samples": Buffer['Lnt']}, C_Pos, C_Neg, Buffer['Pos'], Buffer['Neg'], Buffer['Info'], Buffer["PosMasks"], Buffer["NegMasks"])
class MojoMaker:
@classmethod
def INPUT_TYPES(s):
return{
"required":{
"Main_clip": ("CLIP", {"tooltip":'Main Clip source'}),},
"optional":{
"Alternative_clip": ("CLIP", {"tooltip":'Alternative Clip source. Enrich the possibilities of your Mojo'}),
"Special_clip": ("CLIP", {"tooltip":'YES... MORE.. more clip... or t5, who knows?!'}),
"Mojo_Intake": ("MOJO", {"default": None, "tooltip":'Join the incoming Mojo Flow into the output'}),
"Phrases": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": True, "tooltip":'l,hydit_clip,h - Normal Prompt. Comma separated, short phrases. Object and detail focused. (Local, Character)'}),
"Tags": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": True, "tooltip":'g - Simplified, tag-based prompting. Strong conceptional influence. (General, Action)'}),
"Sentences": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": True, "tooltip":'t5xxl,pile_t5xl,mt5xl - Go nuts. Write an epic novel. But set the mood... the t5s want dinner first. ^^ Might be generalized and reliant on lower clip concepts.'}),
"Mask_set_cond_area": (["default", "mask bounds"], {"default":"default", "tooltip":'Mask area behavior. "default": black pixel = (tag:0) - merges well with other areas "mask bounds": black pixel = tag removed... off gone from vocabulary - hard separation'}),
"Mask_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01, "tooltip":'Mask strength. leave at 1.0 if you are using Mask-Maps'}),
"guidance": ("FLOAT", {"default": 3.5, "min": -100.0, "max": 100.0, "step": 0.1, "tooltip":'Flux specific. Parameter for simulated guidance based on conditioning'}),
"width": ("INT", {"default": 1024, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Set the current image resolution to match the Mojo perfectly.'}),
"height": ("INT", {"default": 1024, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Set the current image resolution to match the Mojo perfectly.'}),
"crop_w": ("INT", {"default": 0, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Crop Factor Width'}),
"crop_h": ("INT", {"default": 0, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Crop Factor Height'}),
"target_width": ("INT", {"default": 1024, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Set the target image resolution after upscale.'}),
"target_height": ("INT", {"default": 1024, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Set the target image resolution after upscale.'}),
"step_start": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.001, "tooltip":'Step influence clamp start. Starts at 0-1. <0 Disabled'}),
"step_end": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.001, "tooltip":'Step influence clamp end. Ends at 0-1. <0 Disabled'}),
"Squeeze_Mojo_Intake": (['Disabled', 'Squeeze + Toss', 'Keep Masks'],{"default":'Disabled', "tooltip":'Merge all Intake Mojo to the smallest possible count. Only maskless Mojo can be squeezed.'}),
"Intake_Strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip":'Mixing strength of the squeezed Intake.'}),
"Conditioning_Output": (['Disabled','Mojo', 'Mojo + Intake (Combine)'],{"default":'Mojo', "tooltip":'How to construct the Conditioning Output. "Mojo + Intake (Combine)" is equivalent to the "Mojo (Combine)" Loader method'}),
"mask": ("MASK", {"tooltip":'Weave a Mask into the Mojo Flow'})}}
RETURN_TYPES = ('MOJO','CONDITIONING')
RETURN_NAMES = ("Mojo", "Conditioning")
FUNCTION = "MojoMe"
OUTPUT_TOOLTIPS = ('Mojo Flow',
'Main_clip Conditioning, if not "Disabled"')
CATEGORY = "Hellrunner's/Mojo"
DESCRIPTION = "Weaves the clips and prompts into a unified Mojo Flow and outputs the Main_clip conditioning. Ensures feature complete usage of common models."
@classmethod
def VALIDATE_INPUTS(s, Main_clip=None, Alternative_clip=None, Special_clip=None, Mojo_Intake=None, Phrases="", Tags="", Sentences="", guidance=3.5, width=1024.0, height=1024.0, crop_w=0, crop_h=0, target_width=1024.0, target_height=1024.0, step_start=-1.0, step_end=-1.0, mask=None, Mask_strength=1.0, Mask_set_cond_area = "default", Squeeze_Mojo_Intake='Disabled', Intake_Strength=1.0, Conditioning_Output='Mojo'):
if Phrases == "" and Tags == "" and Sentences == "":
return "Nothing to convert"
return True
def MojoMe(self, Main_clip=None, Alternative_clip=None, Special_clip=None, Mojo_Intake=None, Phrases="", Tags="", Sentences="", guidance=3.5, width=1024.0, height=1024.0, crop_w=0, crop_h=0, target_width=1024.0, target_height=1024.0, step_start=-1.0, step_end=-1.0, mask=None, Mask_strength=1.0, Mask_set_cond_area = "default", Squeeze_Mojo_Intake='Disabled', Intake_Strength=1.0, Conditioning_Output='Mojo'):
if Phrases == "":
if Sentences == "":
Phrases = Tags
else:
Phrases = Sentences
if Sentences == "":
Sentences = Phrases
if Tags == "":
Tags = Phrases
def makeTik(clip, p,t,s):
Tik = {}
valid_l = ['l','hydit_clip', 'h']
valid_g = ['g']
valid_t5 = ['t5xxl','pile_t5xl','mt5xl']
toDo = []
tokens=clip.tokenize(p)
for key in tokens.keys():
if key in valid_l:
Tik[key] = tokens[key]
continue
toDo.append(key)
for typ in toDo:
if typ in valid_g:
Tik[typ]=clip.tokenize(t)[typ]
elif typ in valid_t5:
Tik[typ]=clip.tokenize(s)[typ]
return Tik
Tok = [[],{},{}]
Tok[0].append(makeTik(Main_clip, Phrases, Tags, Sentences))
if Alternative_clip is not None:
Tok[0].append(makeTik(Alternative_clip, Phrases, Tags, Sentences))
if Special_clip is not None:
Tok[0].append(makeTik(Special_clip, Phrases, Tags, Sentences))
C = []
if Conditioning_Output == 'Mojo':
start=0.0
end=1.0
if step_start >= 0.0:
start = step_start
if step_end >= 0.0:
end = step_end
C, Tok[1] = condition(Main_clip, Tok[0][0], guidance, width, height, crop_w, crop_h, target_width, target_height, start, end, mask, Mask_strength, Mask_set_cond_area)
else:
C, Tok[1] = makeMaskSlot(mask, Mask_strength, Mask_set_cond_area)
Tok[2]=makeSettings({}, guidance, width, height, crop_w, crop_h, target_width, target_height, step_start, step_end)
outMojo = [Tok]
setInfo={}
setInfo["guidance"] = guidance
setInfo["width"] = width
setInfo["height"] = height
setInfo["crop_w"] = crop_w
setInfo["crop_h"] = crop_h
setInfo["target_width"] = target_width
setInfo["target_height"] = target_height
outMojo = TakeIn(outMojo, step_start, step_end, Mojo_Intake, Squeeze_Mojo_Intake, Intake_Strength, setInfo)
if Conditioning_Output == 'Mojo + Intake (Combine)':
ClipT = ClipTest(Main_clip)
C, Masks, Info = CondMojo(Main_clip, outMojo, ClipT, "Main", "")
return (outMojo, C)
class AdjustMojo:
@classmethod
def INPUT_TYPES(s):
return{
"required":{
"Mojo": ("MOJO", {"default": None, "tooltip":'Mojo Flow'}),},
"optional":{
"Main_clip": ("CLIP", {"tooltip":'Main Clip source. Needed if Conditioning is enabled'}),
"Mojo_Intake": ("MOJO", {"default": None, "tooltip":'Join the incoming Mojo Flow into the output'}),
"guidance": ("FLOAT", {"default": 3.5, "min": -100.0, "max": 100.0, "step": 0.1, "tooltip":'Flux specific. Parameter for simulated guidance based on conditioning'}),
"width": ("INT", {"default": 1024, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Set the current image resolution to match the Mojo perfectly.'}),
"height": ("INT", {"default": 1024, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Set the current image resolution to match the Mojo perfectly.'}),
"crop_w": ("INT", {"default": 0, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Crop Factor Width'}),
"crop_h": ("INT", {"default": 0, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Crop Factor Height'}),
"target_width": ("INT", {"default": 1024, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Set the target image resolution after upscale.'}),
"target_height": ("INT", {"default": 1024, "min": 0, "max": nodes.MAX_RESOLUTION, "tooltip":'SDXL and Pony specific. Set the target image resolution after upscale.'}),
"step_start": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.001, "tooltip":'Step influence clamp start. Starts at 0-1. <0 Disabled'}),
"step_end": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.001, "tooltip":'Step influence clamp end. Ends at 0-1. <0 Disabled'}),
"Squeeze_Mojo": (['Disabled', 'Squeeze + Toss', 'Keep Masks'],{"default":'Disabled', "tooltip":'Merge all Mojo to the smallest possible count. Only maskless Mojo can be squeezed.'}),
"Squeeze_Mojo_Intake": (['Disabled', 'Squeeze + Toss', 'Keep Masks'],{"default":'Disabled', "tooltip":'Merge all Intake Mojo to the smallest possible count. Only maskless Mojo can be squeezed.'}),
"Intake_Strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip":'Mixing strength of the squeezed Intake.'}),
"Clip_Choice": (['Main', 'Additional', 'Special'],{"default":'Main', "tooltip":'Biases the assembly towards a clip source. IF such source is provided. The only mandatory source is Main. Usage is heavily dependent on how the Mojo was made.'}),
"Conditioning_Output": (['Disabled','Mojo (Combine)', 'Intake (Combine)', 'Mojo + Intake (Combine)'],{"default":'Mojo (Combine)', "tooltip":'How to construct the Conditioning Output.'})}}
RETURN_TYPES = ('MOJO','CONDITIONING','STRING')
RETURN_NAMES = ("Mojo", "Conditioning", "Info")
FUNCTION = "AdjustMe"
OUTPUT_TOOLTIPS = ('Mojo Flow',
'Main_clip Conditioning, if not "Disabled"',
'Info String with useful information about the Mojo and the assembly of it.')
CATEGORY = "Hellrunner's/Mojo"
DESCRIPTION = "Inject new values into a Mojo flow and/or weave in another"
def AdjustMe(self, Mojo, Main_clip=None, Mojo_Intake=None, guidance=3.5, width=1024.0, height=1024.0, crop_w=0, crop_h=0, target_width=1024.0, target_height=1024.0, step_start=-1.0, step_end=-1.0, Squeeze_Mojo='Disabled', Squeeze_Mojo_Intake='Disabled', Intake_Strength=1.0, Clip_Choice='Main', Conditioning_Output='Mojo (Combine)'):
InMojo = SqueezeMojo(Mojo, Squeeze_Mojo)
SetMojo(InMojo, guidance, width, height, crop_w, crop_h, target_width, target_height, step_start, step_end)
outMojo = InMojo
setInfo={}
setInfo["guidance"] = guidance
setInfo["width"] = width
setInfo["height"] = height
setInfo["crop_w"] = crop_w
setInfo["crop_h"] = crop_h
setInfo["target_width"] = target_width
setInfo["target_height"] = target_height
outMojo = TakeIn(outMojo, step_start, step_end, Mojo_Intake, Squeeze_Mojo_Intake, Intake_Strength, setInfo)
C=[]
Info=""
if Conditioning_Output != "Disabled":
if Main_clip is None:
print('Adjust Mojo - Main_clip required')
else:
ClipT = ClipTest(Main_clip)
match Conditioning_Output:
case 'Mojo (Combine)':
C, Masks, Info = CondMojo(Main_clip, InMojo, ClipT, Clip_Choice, Info)
case 'Intake (Combine)':
if InMojo_Intake is not None:
C, Masks, Info = CondMojo(Main_clip, InMojo_Intake, ClipT, Clip_Choice, Info)
else:
C, Masks, Info = CondMojo(Main_clip, InMojo, ClipT, Clip_Choice, Info)
case 'Mojo + Intake (Combine)':
C, Masks, Info = CondMojo(Main_clip, outMojo, ClipT, Clip_Choice, Info)
return (outMojo, C, Info)
NODE_CLASS_MAPPINGS = {
"MojoMaker": MojoMaker,
"SaveMojo": SaveMojo,
"MojoLoader": MojoLoader,
"AdjustMojo": AdjustMojo,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"MojoMaker": "Mojo Maker",
"SaveMojo": "Save Mojo",
"MojoLoader": "Mojo Loader",
"AdjustMojo": "Adjust Mojo",
}