Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f13701c489 | ||
|
|
31515b810e | ||
|
|
29e84e08a4 | ||
|
|
0ac9ad9757 | ||
|
|
9a432e0608 | ||
|
|
c83ba5fe7f | ||
|
|
1b55c743ea | ||
|
|
a93579376c | ||
|
|
eba49f3c68 | ||
|
|
3a3da49c69 | ||
|
|
3d68e48219 | ||
|
|
4351fa6a0e | ||
|
|
77222d2808 | ||
|
|
36db7e5a9a | ||
|
|
3572368f16 | ||
|
|
0755dc1462 | ||
|
|
94f81b7102 | ||
|
|
08f8fe3d7e | ||
|
|
a6cd383d67 | ||
|
|
10face6ab0 | ||
|
|
a6259ff600 | ||
|
|
d7d9e6cbfe | ||
|
|
8263609470 | ||
|
|
fc2367de76 | ||
|
|
9a4f2ebc70 | ||
|
|
812879610a | ||
|
|
c5e521ccc1 | ||
|
|
bc1c8fa351 | ||
|
|
7271fcf9c1 | ||
|
|
ace3b7707b | ||
|
|
9eb65cc4ee | ||
|
|
c5c2bc779c | ||
|
|
ae1751d9c0 | ||
|
|
d17583ef7d |
@@ -1,5 +1,7 @@
|
||||
> 适配了最新版comfyui的py3.11 ,torch 2.1.2+cu121
|
||||
|
||||
> [discord](https://discord.gg/cXs9vZSqeK)
|
||||
|
||||
## 🚀🚗🚚🏃 Workflow-to-APP
|
||||
- 新增AppInfo节点,可以通过简单的配置,把workflow转变为一个Web APP。
|
||||
- 支持多个web app 切换
|
||||
@@ -115,6 +117,11 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
- [Added DynamicDelayByText, enabling delayed execution based on input text length.](./workflow/audio-chatgpt-workflow.json)
|
||||
|
||||
- [使用CkptNames 对比不同的模型效果](./workflow/ckpts-image-workflow.json)
|
||||
|
||||
- [CkptNames compare the effects of different models.](./workflow/ckpts-image-workflow.json)
|
||||
|
||||
|
||||
|
||||
## Other Nodes
|
||||
|
||||
@@ -173,7 +180,7 @@ An improvement has been made to directly redirect to GitHub to search for missin
|
||||
|
||||
[Download Salesforce/blip-image-captioning-base](https://huggingface.co/Salesforce/blip-image-captioning-base), move to : models/clip_interrogator/Salesforce/blip-image-captioning-base
|
||||
|
||||
[Download succinctly/text2image-prompt-generator](https://huggingface.co/succinctly/text2image-prompt-generator/tree/main),move to:text_generator/text2image-prompt-generator
|
||||
[Download succinctly/text2image-prompt-generator](https://huggingface.co/succinctly/text2image-prompt-generator/tree/main),move to:prompt_generator/text2image-prompt-generator
|
||||
|
||||
[Download Helsinki-NLP/opus-mt-zh-en](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en/tree/main),move to:prompt_generator/opus-mt-zh-en
|
||||
|
||||
|
||||
+34
-29
@@ -192,7 +192,7 @@ def read_workflow_json_files(folder_path ):
|
||||
def get_workflows():
|
||||
# print("#####path::", current_path)
|
||||
workflow_path=os.path.join(current_path, "workflow")
|
||||
print('workflow_path: ',workflow_path)
|
||||
# print('workflow_path: ',workflow_path)
|
||||
if not os.path.exists(workflow_path):
|
||||
# 使用mkdir()方法创建新目录
|
||||
os.mkdir(workflow_path)
|
||||
@@ -231,8 +231,14 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
|
||||
})
|
||||
else:
|
||||
category=''
|
||||
input=None
|
||||
output=None
|
||||
if 'category' in x['app']:
|
||||
category=x['app']['category']
|
||||
if 'input' in x['app']:
|
||||
input=x['app']['input']
|
||||
if 'output' in x['app']:
|
||||
output=x['app']['output']
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
"category":category,
|
||||
@@ -244,6 +250,8 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
|
||||
"icon":(x['app']['icon'] if 'icon' in x['app'] else None),
|
||||
"name":x['app']['name'],
|
||||
"version":x['app']['version'],
|
||||
"input":input,
|
||||
"output":output
|
||||
}
|
||||
},
|
||||
"date":item["date"]
|
||||
@@ -271,8 +279,14 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
|
||||
# print(apps[0]['filename'] ,item["filename"])
|
||||
if apps[0]['filename']!=item["filename"]:
|
||||
category=''
|
||||
input=None
|
||||
output=None
|
||||
if 'category' in x['app']:
|
||||
category=x['app']['category']
|
||||
if 'input' in x['app']:
|
||||
input=x['app']['input']
|
||||
if 'output' in x['app']:
|
||||
output=x['app']['output']
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
# "category":category,
|
||||
@@ -284,6 +298,8 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
|
||||
"icon":(x['app']['icon'] if 'icon' in x['app'] else None),
|
||||
"name":x['app']['name'],
|
||||
"version":x['app']['version'],
|
||||
"input":input,
|
||||
"output":output
|
||||
}
|
||||
},
|
||||
"date":item["date"]
|
||||
@@ -414,7 +430,7 @@ async def new_start(self, address, port, verbose=True, call_on_start=None):
|
||||
PromptServer.start=new_start
|
||||
|
||||
# 创建路由表
|
||||
routes = web.RouteTableDef()
|
||||
routes = PromptServer.instance.routes
|
||||
|
||||
@routes.post('/mixlab')
|
||||
async def mixlab_hander(request):
|
||||
@@ -500,27 +516,6 @@ async def nodes_map_hander(request):
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
# 把插件自定义的路由添加到comfyui server里
|
||||
def new_add_routes(self):
|
||||
import nodes
|
||||
try:
|
||||
self.user_manager.add_routes(self.routes)
|
||||
except:
|
||||
print('pls update')
|
||||
|
||||
self.app.add_routes(routes)
|
||||
self.app.add_routes(self.routes)
|
||||
for name, dir in nodes.EXTENSION_WEB_DIRS.items():
|
||||
self.app.add_routes([
|
||||
web.static('/extensions/' + urllib.parse.quote(name), dir, follow_symlinks=True),
|
||||
])
|
||||
self.app.add_routes([
|
||||
web.static('/', self.web_root, follow_symlinks=True),
|
||||
])
|
||||
|
||||
PromptServer.add_routes=new_add_routes
|
||||
|
||||
|
||||
|
||||
# 扩展api接口
|
||||
# from server import PromptServer
|
||||
@@ -534,15 +529,15 @@ PromptServer.add_routes=new_add_routes
|
||||
|
||||
|
||||
# 导入节点
|
||||
from .nodes.PromptNode import EmbeddingPrompt,RandomPrompt,PromptSlide,PromptSimplification,PromptImage
|
||||
from .nodes.ImageNode import GetImageSize_,MirroredImage,ImageColorTransfer,NoiseImage,TransparentImage,GradientImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,AreaToMask,SmoothMask,FeatheredMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
|
||||
from .nodes.PromptNode import EmbeddingPrompt,RandomPrompt,PromptSlide,PromptSimplification,PromptImage,JoinWithDelimiter
|
||||
from .nodes.ImageNode import SaveImageToLocal,SplitImage,GridOutput,GetImageSize_,MirroredImage,ImageColorTransfer,NoiseImage,TransparentImage,GradientImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,CenterImage,AreaToMask,SmoothMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
|
||||
from .nodes.Vae import VAELoader,VAEDecode
|
||||
from .nodes.ScreenShareNode import ScreenShareNode,FloatingVideo
|
||||
from .nodes.Clipseg import CLIPSeg,CombineMasks
|
||||
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText
|
||||
from .nodes.Audio import GamePal,SpeechRecognition,SpeechSynthesis
|
||||
from .nodes.Utils import TESTNODE_,AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,MultiplicationNode
|
||||
from .nodes.Mask import OutlineMask
|
||||
from .nodes.Utils import CreateLoraNames,CreateSampler_names,CreateCkptNames,CreateSeedNode,TESTNODE_,AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,MultiplicationNode
|
||||
from .nodes.Mask import OutlineMask,FeatheredMask
|
||||
|
||||
|
||||
# 要导出的所有节点及其名称的字典
|
||||
@@ -569,6 +564,9 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImageColorTransfer":ImageColorTransfer,
|
||||
"ShowLayer":ShowLayer,
|
||||
"NewLayer":NewLayer,
|
||||
"SplitImage":SplitImage,
|
||||
"CenterImage":CenterImage,
|
||||
"GridOutput":GridOutput,
|
||||
"MergeLayers":MergeLayers,
|
||||
"SplitLongMask":SplitLongMask,
|
||||
"FeatheredMask":FeatheredMask,
|
||||
@@ -577,6 +575,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"AreaToMask":AreaToMask,
|
||||
"ImageCropByAlpha":ImageCropByAlpha,
|
||||
"VAELoaderConsistencyDecoder":VAELoader,
|
||||
"SaveImageToLocal":SaveImageToLocal,
|
||||
"VAEDecodeConsistencyDecoder":VAEDecode,
|
||||
"ScreenShare":ScreenShareNode,
|
||||
"FloatingVideo":FloatingVideo,
|
||||
@@ -598,7 +597,12 @@ NODE_CLASS_MAPPINGS = {
|
||||
"GetImageSize_":GetImageSize_,
|
||||
"SwitchByIndex":SwitchByIndex,
|
||||
"LimitNumber":LimitNumber,
|
||||
"OutlineMask":OutlineMask
|
||||
"OutlineMask":OutlineMask,
|
||||
"JoinWithDelimiter":JoinWithDelimiter,
|
||||
"Seed_":CreateSeedNode,
|
||||
"CkptNames_":CreateCkptNames,
|
||||
"SamplerNames_":CreateSampler_names,
|
||||
"LoraNames_":CreateLoraNames
|
||||
# "LaMaInpainting":LaMaInpainting
|
||||
# "GamePal":GamePal
|
||||
}
|
||||
@@ -625,7 +629,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PromptGenerate_Mix":"PromptGenerate ♾️Mixlab",
|
||||
"ChinesePrompt_Mix":"ChinesePrompt ♾️Mixlab",
|
||||
"GamePal":"GamePal ♾️Mixlab",
|
||||
"RembgNode_Mix":"Removebg"
|
||||
"RembgNode_Mix":"Removebg",
|
||||
"LoraNames_":"LoraName_TriggerWords.safetensors"
|
||||
}
|
||||
|
||||
# web ui的节点功能
|
||||
|
||||
@@ -4761,7 +4761,10 @@
|
||||
],
|
||||
"https://github.com/shadowcz007/comfyui-mixlab-nodes": [
|
||||
[
|
||||
"GridOutput",
|
||||
"SplitImage",
|
||||
"PromptGenerate_Mix",
|
||||
"JoinWithDelimiter",
|
||||
"ChinesePrompt_Mix",
|
||||
"3DImage",
|
||||
"AppInfo",
|
||||
@@ -4770,11 +4773,16 @@
|
||||
"ResizeImage",
|
||||
"NoiseImage",
|
||||
"PromptImage",
|
||||
"SaveImageToLocal",
|
||||
"AreaToMask",
|
||||
"CLIPSeg_",
|
||||
"CharacterInText",
|
||||
"ChatGPTOpenAI",
|
||||
"Color",
|
||||
"Seed_",
|
||||
"CkptNames_",
|
||||
"SamplerNames_",
|
||||
"LoraNames_",
|
||||
"CombineMasks_",
|
||||
"EnhanceImage",
|
||||
"GradientImage",
|
||||
@@ -4787,6 +4795,7 @@
|
||||
"LoadImagesFromURL",
|
||||
"MergeLayers",
|
||||
"NewLayer",
|
||||
"CenterImage",
|
||||
"RandomPrompt",
|
||||
"PromptSlide",
|
||||
"PromptSimplification",
|
||||
@@ -4807,7 +4816,8 @@
|
||||
"TextToNumber",
|
||||
"TextInput_",
|
||||
"DynamicDelayProcessor",
|
||||
"LaMaInpainting"
|
||||
"LaMaInpainting",
|
||||
"Moondream"
|
||||
],
|
||||
{
|
||||
"title_aux": "comfyui-mixlab-nodes"
|
||||
|
||||
@@ -24,6 +24,7 @@ def is_installed(package):
|
||||
return False
|
||||
return spec is not None
|
||||
|
||||
|
||||
try:
|
||||
if is_installed('clip_interrogator')==False:
|
||||
import subprocess
|
||||
@@ -36,20 +37,25 @@ try:
|
||||
#检查命令执行结果
|
||||
if result.returncode == 0:
|
||||
print("#install success")
|
||||
from transformers import AutoProcessor, BlipForConditionalGeneration
|
||||
from clip_interrogator import Config, Interrogator
|
||||
_available=True
|
||||
else:
|
||||
print("#install error")
|
||||
|
||||
else:
|
||||
from transformers import AutoProcessor, BlipForConditionalGeneration
|
||||
from clip_interrogator import Config, Interrogator
|
||||
_available=True
|
||||
|
||||
except:
|
||||
_available=False
|
||||
|
||||
try:
|
||||
from transformers import AutoProcessor, BlipForConditionalGeneration
|
||||
except:
|
||||
_available=False
|
||||
print('pls check transformers.__version__>=4.36.0:: AutoProcessor, BlipForConditionalGeneration')
|
||||
|
||||
|
||||
|
||||
def load_caption_model(model_path,config,t='blip-base'):
|
||||
dtype=torch.float16 if config.device == 'cuda' else torch.float32
|
||||
|
||||
+303
-82
@@ -9,11 +9,22 @@ from io import BytesIO
|
||||
import folder_paths
|
||||
import json,io
|
||||
from comfy.cli_args import args
|
||||
import cv2
|
||||
import math
|
||||
import cv2
|
||||
import string
|
||||
import math,glob
|
||||
from .Watcher import FolderWatcher
|
||||
|
||||
def generate_random_string(length):
|
||||
letters = string.ascii_letters + string.digits
|
||||
return ''.join(random.choice(letters) for _ in range(length))
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
any_type = AnyType("*")
|
||||
|
||||
|
||||
FONT_PATH= os.path.abspath(os.path.join(os.path.dirname(__file__),'../assets/王汉宗颜楷体繁.ttf'))
|
||||
@@ -847,79 +858,6 @@ class SmoothMask:
|
||||
|
||||
|
||||
|
||||
class FeatheredMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"start_offset":("INT", {"default": 1,
|
||||
"min": -150,
|
||||
"max": 150,
|
||||
"step": 1,
|
||||
"display": "slider"}),
|
||||
"feathering_weight":("FLOAT", {"default": 0.1,
|
||||
"min": 0.0,
|
||||
"max": 1,
|
||||
"step": 0.1,
|
||||
"display": "slider"})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ('MASK',)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Mask"
|
||||
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
# 运行的函数
|
||||
def run(self,mask,start_offset, feathering_weight):
|
||||
# print(mask.shape,mask.size())
|
||||
|
||||
image=tensor2pil(mask)
|
||||
|
||||
# Open the image using PIL
|
||||
image = image.convert("L")
|
||||
if start_offset>0:
|
||||
image=ImageOps.invert(image)
|
||||
|
||||
# Convert the image to a numpy array
|
||||
image_np = np.array(image)
|
||||
|
||||
# Use Canny edge detection to get black contours
|
||||
edges = cv2.Canny(image_np, 30, 150)
|
||||
|
||||
for i in range(0,abs(start_offset)):
|
||||
# int(100*feathering_weight)
|
||||
a=int(abs(start_offset)*0.1*i)
|
||||
# Dilate the black contours to make them wider
|
||||
kernel = np.ones((a, a), np.uint8)
|
||||
|
||||
dilated_edges = cv2.dilate(edges, kernel, iterations=1)
|
||||
# dilated_edges = cv2.erode(edges, kernel, iterations=1)
|
||||
# Smooth the dilated edges using Gaussian blur
|
||||
smoothed_edges = cv2.GaussianBlur(dilated_edges, (5, 5), 0)
|
||||
|
||||
# Adjust the feathering weight
|
||||
feathering_weight = max(0, min(feathering_weight, 1))
|
||||
|
||||
# Blend the smoothed edges with the original image to achieve feathering effect
|
||||
image_np = cv2.addWeighted(image_np, 1, smoothed_edges, feathering_weight, feathering_weight)
|
||||
|
||||
# Convert the result back to PIL image
|
||||
result_image = Image.fromarray(np.uint8(image_np))
|
||||
result_image=result_image.convert("L")
|
||||
|
||||
if start_offset>0:
|
||||
result_image=ImageOps.invert(result_image)
|
||||
|
||||
mask=pil2tensor(result_image)
|
||||
# print(mask.shape,mask.size())
|
||||
return mask
|
||||
|
||||
|
||||
|
||||
|
||||
class SplitLongMask:
|
||||
@@ -1249,7 +1187,7 @@ class ImageCropByAlpha:
|
||||
if a != 0:
|
||||
new_pixel_data[x, y] = (255, 255, 255, 255)
|
||||
else:
|
||||
new_pixel_data[x, y] = (r, g, b, a)
|
||||
new_pixel_data[x, y] = (0,0,0,0)
|
||||
|
||||
# 保存修改后的图像
|
||||
# new_image.save("output.png")
|
||||
@@ -1323,6 +1261,9 @@ class LoadImagesFromURL:
|
||||
return {"required": {
|
||||
"url": ("STRING",{"multiline": True,"default": "https://","dynamicPrompts": False}),
|
||||
},
|
||||
"optional":{
|
||||
"seed": (any_type, {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE","MASK",)
|
||||
@@ -1339,7 +1280,7 @@ class LoadImagesFromURL:
|
||||
global urls_image
|
||||
urls_image={}
|
||||
|
||||
def run(self,url):
|
||||
def run(self,url,seed=0):
|
||||
global urls_image
|
||||
print(urls_image)
|
||||
def filter_http_urls(urls):
|
||||
@@ -1671,6 +1612,183 @@ class NewLayer:
|
||||
|
||||
return (layer_n,)
|
||||
|
||||
|
||||
def splitImage(image, num):
|
||||
width, height = image.size
|
||||
|
||||
num_rows = int(num ** 0.5)
|
||||
num_cols = int(num / num_rows)
|
||||
|
||||
grid_width = width // num_cols
|
||||
grid_height = height // num_rows
|
||||
|
||||
grid_coordinates = []
|
||||
for i in range(num_rows):
|
||||
for j in range(num_cols):
|
||||
x = j * grid_width
|
||||
y = i * grid_height
|
||||
grid_coordinates.append((x, y, grid_width, grid_height))
|
||||
|
||||
return grid_coordinates
|
||||
|
||||
|
||||
def centerImage(margin,canvas):
|
||||
w,h=canvas.size
|
||||
|
||||
l,t,r,b=margin
|
||||
|
||||
x=l
|
||||
y=t
|
||||
width=w-r-l
|
||||
height=h-t-b
|
||||
|
||||
return (x,y,width,height)
|
||||
|
||||
# # 读取图片
|
||||
# image = Image.open("path_to_your_image.jpg")
|
||||
|
||||
# # 定义要切割的区域数量
|
||||
# num = 9
|
||||
|
||||
# # 切割图片
|
||||
# grid_coordinates = splitImage(image, num)
|
||||
|
||||
# # 输出切割区域坐标
|
||||
# for i, coordinates in enumerate(grid_coordinates):
|
||||
# print(f"Region {i + 1}: x={coordinates[0]}, y={coordinates[1]}, width={coordinates[2]}, height={coordinates[3]}")
|
||||
|
||||
|
||||
class SplitImage:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"num": ("INT",{
|
||||
"default": 4,
|
||||
"min": 1, #Minimum value
|
||||
"max": 500, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"seed": ("INT",{
|
||||
"default": 4,
|
||||
"min": 1, #Minimum value
|
||||
"max": 500, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("_GRID","_GRID",)
|
||||
RETURN_NAMES = ("grids","grid")
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Layer"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
# OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self,image,num,seed):
|
||||
image=tensor2pil(image)
|
||||
|
||||
grids=splitImage(image,num)
|
||||
|
||||
if seed>=num:
|
||||
num=int(seed / 500 * num)-1
|
||||
else:
|
||||
num=seed-1
|
||||
|
||||
num=max(0,num)
|
||||
|
||||
g=grids[num]
|
||||
|
||||
return (grids,g,)
|
||||
|
||||
|
||||
|
||||
class CenterImage:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"canvas": ("IMAGE",),
|
||||
"left": ("INT",{
|
||||
"default":24,
|
||||
"min": 0, #Minimum value
|
||||
"max": 5000, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"top": ("INT",{
|
||||
"default":24,
|
||||
"min": 0, #Minimum value
|
||||
"max": 5000, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"right": ("INT",{
|
||||
"default": 24,
|
||||
"min": 0, #Minimum value
|
||||
"max": 5000, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"bottom": ("INT",{
|
||||
"default": 24,
|
||||
"min": 0, #Minimum value
|
||||
"max": 5000, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("_GRID",)
|
||||
RETURN_NAMES = ("grid",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Layer"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
# OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self,canvas,left,top,right,bottom):
|
||||
canvas=tensor2pil(canvas)
|
||||
|
||||
grid=centerImage((left,top,right,bottom),canvas)
|
||||
|
||||
return (grid,)
|
||||
|
||||
|
||||
|
||||
|
||||
class GridOutput:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"grid": ("_GRID",)
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT","INT","INT","INT",)
|
||||
RETURN_NAMES = ("x","y","width","height",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Layer"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
# OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self,grid):
|
||||
x,y,w,h=grid
|
||||
return (x,y,w,h,)
|
||||
|
||||
class ShowLayer:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -2025,20 +2143,21 @@ class ResizeImage:
|
||||
"image": ("IMAGE",),
|
||||
"average_color": (["on",'off'],),
|
||||
"fill_color":("STRING",{"multiline": False,"default": "#FFFFFF","dynamicPrompts": False}),
|
||||
"mask": ("MASK",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE","IMAGE","STRING",)
|
||||
RETURN_NAMES = ("image","average_image","average_hex",)
|
||||
RETURN_TYPES = ("IMAGE","IMAGE","STRING","MASK",)
|
||||
RETURN_NAMES = ("image","average_image","average_hex","mask",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Image"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,True,True,)
|
||||
OUTPUT_IS_LIST = (True,True,True,True,)
|
||||
|
||||
def run(self,width,height,scale_option,image=None,average_color=['on'],fill_color=["#FFFFFF"]):
|
||||
def run(self,width,height,scale_option,image=None,average_color=['on'],fill_color=["#FFFFFF"],mask=None):
|
||||
|
||||
w=width[0]
|
||||
h=height[0]
|
||||
@@ -2047,6 +2166,7 @@ class ResizeImage:
|
||||
fill_color=fill_color[0]
|
||||
|
||||
imgs=[]
|
||||
masks=[]
|
||||
average_images=[]
|
||||
hexs=[]
|
||||
|
||||
@@ -2075,8 +2195,20 @@ class ResizeImage:
|
||||
a_im=pil2tensor(a_im)
|
||||
average_images.append(a_im)
|
||||
hexs.append(hex)
|
||||
|
||||
try:
|
||||
for mas in mask:
|
||||
for ma in mas:
|
||||
ma=tensor2pil(ma)
|
||||
ma=ma.convert('RGB')
|
||||
ma=resize_image(ma,scale_option,w,h,fill_color)
|
||||
ma=ma.convert('L')
|
||||
ma=pil2tensor(ma)
|
||||
masks.append(ma)
|
||||
except:
|
||||
print('')
|
||||
|
||||
return (imgs,average_images,hexs,)
|
||||
return (imgs,average_images,hexs,masks,)
|
||||
|
||||
|
||||
class MirroredImage:
|
||||
@@ -2179,3 +2311,92 @@ class ImageColorTransfer:
|
||||
|
||||
|
||||
|
||||
class SaveImageToLocal:
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_output_directory()
|
||||
self.type = "output"
|
||||
self.compress_level = 4
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"images": ("IMAGE", ),
|
||||
"file_path": ("STRING",{"multiline": True,"default": "","dynamicPrompts": False}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save_images"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "♾️Mixlab/Image"
|
||||
|
||||
def save_images(self, images,file_path , prompt=None, extra_pnginfo=None):
|
||||
filename_prefix = os.path.basename(file_path)
|
||||
if file_path=='':
|
||||
filename_prefix="ComfyUI"
|
||||
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
# 使用os.makedirs函数创建新目录
|
||||
os.makedirs(file_path)
|
||||
print("目录已创建")
|
||||
else:
|
||||
print("目录已存在")
|
||||
|
||||
# 使用glob模块获取当前目录下的所有文件
|
||||
if file_path=="":
|
||||
files = glob.glob(full_output_folder + '/*')
|
||||
else:
|
||||
files = glob.glob(file_path + '/*')
|
||||
# 统计文件数量
|
||||
file_count = len(files)
|
||||
counter+=file_count
|
||||
print('统计文件数量',file_count,counter)
|
||||
|
||||
results = list()
|
||||
for image in images:
|
||||
i = 255. * image.cpu().numpy()
|
||||
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||||
metadata = None
|
||||
if not args.disable_metadata:
|
||||
metadata = PngInfo()
|
||||
if prompt is not None:
|
||||
metadata.add_text("prompt", json.dumps(prompt))
|
||||
if extra_pnginfo is not None:
|
||||
for x in extra_pnginfo:
|
||||
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
|
||||
|
||||
file = f"{filename}_{counter:05}_.png"
|
||||
|
||||
if file_path=="":
|
||||
fp=os.path.join(full_output_folder, file)
|
||||
if os.path.exists(fp):
|
||||
file = f"{filename}_{counter:05}_{generate_random_string(8)}.png"
|
||||
fp=os.path.join(full_output_folder, file)
|
||||
img.save(fp, pnginfo=metadata, compress_level=self.compress_level)
|
||||
results.append({
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": self.type
|
||||
})
|
||||
|
||||
else:
|
||||
fp=os.path.join(file_path, file)
|
||||
if os.path.exists(fp):
|
||||
file = f"{filename}_{counter:05}_{generate_random_string(8)}.png"
|
||||
fp=os.path.join(file_path, file)
|
||||
|
||||
img.save(os.path.join(file_path, file), pnginfo=metadata, compress_level=self.compress_level)
|
||||
results.append({
|
||||
"filename": file,
|
||||
"subfolder": file_path,
|
||||
"type": self.type
|
||||
})
|
||||
counter += 1
|
||||
|
||||
return ()
|
||||
|
||||
+103
-3
@@ -1,10 +1,26 @@
|
||||
import numpy as np
|
||||
|
||||
import scipy.ndimage
|
||||
import torch
|
||||
import comfy.utils
|
||||
|
||||
from nodes import MAX_RESOLUTION
|
||||
|
||||
import numpy as np
|
||||
# from PIL import Image, ImageDraw
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
from comfy.cli_args import args
|
||||
import cv2
|
||||
|
||||
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
# Convert PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
|
||||
def grow(mask, expand, tapered_corners):
|
||||
c = 0 if tapered_corners else 1
|
||||
@@ -68,4 +84,88 @@ class OutlineMask:
|
||||
|
||||
m3=combine(m1,m2,0,0)
|
||||
|
||||
return (m3,)
|
||||
return (m3,)
|
||||
|
||||
|
||||
|
||||
|
||||
class FeatheredMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"start_offset":("INT", {"default": 1,
|
||||
"min": -150,
|
||||
"max": 150,
|
||||
"step": 1,
|
||||
"display": "slider"}),
|
||||
"feathering_weight":("FLOAT", {"default": 0.1,
|
||||
"min": 0.0,
|
||||
"max": 1,
|
||||
"step": 0.1,
|
||||
"display": "slider"})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ('MASK',)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Mask"
|
||||
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
# 运行的函数
|
||||
def run(self,mask,start_offset, feathering_weight):
|
||||
# print(mask.shape,mask.size())
|
||||
|
||||
num,_,_=mask.size()
|
||||
|
||||
masks=[]
|
||||
|
||||
for i in range(num):
|
||||
mm=mask[i]
|
||||
image=tensor2pil(mm)
|
||||
|
||||
# Open the image using PIL
|
||||
image = image.convert("L")
|
||||
if start_offset>0:
|
||||
image=ImageOps.invert(image)
|
||||
|
||||
# Convert the image to a numpy array
|
||||
image_np = np.array(image)
|
||||
|
||||
# Use Canny edge detection to get black contours
|
||||
edges = cv2.Canny(image_np, 30, 150)
|
||||
|
||||
for i in range(0,abs(start_offset)):
|
||||
# int(100*feathering_weight)
|
||||
a=int(abs(start_offset)*0.1*i)
|
||||
# Dilate the black contours to make them wider
|
||||
kernel = np.ones((a, a), np.uint8)
|
||||
|
||||
dilated_edges = cv2.dilate(edges, kernel, iterations=1)
|
||||
# dilated_edges = cv2.erode(edges, kernel, iterations=1)
|
||||
# Smooth the dilated edges using Gaussian blur
|
||||
smoothed_edges = cv2.GaussianBlur(dilated_edges, (5, 5), 0)
|
||||
|
||||
# Adjust the feathering weight
|
||||
feathering_weight = max(0, min(feathering_weight, 1))
|
||||
|
||||
# Blend the smoothed edges with the original image to achieve feathering effect
|
||||
image_np = cv2.addWeighted(image_np, 1, smoothed_edges, feathering_weight, feathering_weight)
|
||||
|
||||
# Convert the result back to PIL image
|
||||
result_image = Image.fromarray(np.uint8(image_np))
|
||||
result_image=result_image.convert("L")
|
||||
|
||||
if start_offset>0:
|
||||
result_image=ImageOps.invert(result_image)
|
||||
|
||||
result_image=result_image.convert("L")
|
||||
mt=pil2tensor(result_image)
|
||||
masks.append(mt)
|
||||
|
||||
# print( mt.size())
|
||||
return (masks,)
|
||||
|
||||
+50
-2
@@ -23,6 +23,19 @@ def get_files_with_extension(directory, extension):
|
||||
file_list.append(file_name)
|
||||
return file_list
|
||||
|
||||
def join_with_(text_list,delimiter):
|
||||
joined_text = delimiter.join(text_list)
|
||||
return joined_text
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
any_type = AnyType("*")
|
||||
|
||||
|
||||
default_prompt1='''Swing
|
||||
Slide
|
||||
@@ -65,7 +78,7 @@ def addWeight(text, weight=1):
|
||||
if weight == 1:
|
||||
return text
|
||||
else:
|
||||
return f"({text}:{round(weight,2)})"
|
||||
return f"({text}:{round(weight,3)})"
|
||||
|
||||
def prompt_delete_words(sentence, new_words_length):
|
||||
# 使用逗号分割句子,并去除空格
|
||||
@@ -308,6 +321,10 @@ class RandomPrompt:
|
||||
"default": 'sticker, Cartoon, ``'
|
||||
}),
|
||||
"random_sample": (["enable", "disable"],),
|
||||
# "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
|
||||
},
|
||||
"optional":{
|
||||
"seed": (any_type, {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +341,7 @@ class RandomPrompt:
|
||||
|
||||
|
||||
# 运行的函数
|
||||
def run(self,max_count,mutable_prompt,immutable_prompt,random_sample):
|
||||
def run(self,max_count,mutable_prompt,immutable_prompt,random_sample,seed=0):
|
||||
# print('#运行的函数',mutable_prompt,immutable_prompt,max_count,random_sample)
|
||||
|
||||
# Split the text into an array of words
|
||||
@@ -394,6 +411,7 @@ class EmbeddingPrompt:
|
||||
|
||||
# 运行的函数
|
||||
def run(self,embedding,weight):
|
||||
weight = round(weight, 3)
|
||||
prompt='embedding:'+embedding
|
||||
if weight!=1:
|
||||
prompt='('+prompt+':'+str(weight)+')'
|
||||
@@ -401,3 +419,33 @@ class EmbeddingPrompt:
|
||||
# return (new_prompt)
|
||||
return (prompt,)
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
|
||||
class JoinWithDelimiter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text_list": (any_type,),
|
||||
"delimiter":(["newline","comma"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Prompt"
|
||||
|
||||
INPUT_IS_LIST = True # 当true的时候,输入时list,当false的时候,如果输入是list,则会自动包一层for循环调用
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,text_list,delimiter):
|
||||
delimiter=delimiter[0]
|
||||
if delimiter =='newline':
|
||||
delimiter='\n'
|
||||
elif delimiter=='comma':
|
||||
delimiter=','
|
||||
t=''
|
||||
if isinstance(text_list, list):
|
||||
t=join_with_(text_list,delimiter)
|
||||
return (t,)
|
||||
+138
-12
@@ -7,6 +7,8 @@ import folder_paths
|
||||
import matplotlib.font_manager as fm
|
||||
import torch
|
||||
|
||||
|
||||
|
||||
def recursive_search(directory, excluded_dir_names=None):
|
||||
if not os.path.isdir(directory):
|
||||
return [], {}
|
||||
@@ -198,14 +200,17 @@ class TextToNumber:
|
||||
return {"required": {
|
||||
"text": ("STRING",{"multiline": False,"default": "1"}),
|
||||
"random_number": (["enable", "disable"],),
|
||||
"number":("INT", {
|
||||
"default": 0,
|
||||
"min": 0, #Minimum value
|
||||
"max_num":("INT", {
|
||||
"default": 10,
|
||||
"min":2, #Minimum value
|
||||
"max": 10000000000, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
},
|
||||
"optional":{
|
||||
"seed": (any_type, {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
@@ -218,7 +223,7 @@ class TextToNumber:
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,text,random_number,number):
|
||||
def run(self,text,random_number,max_num,seed=0):
|
||||
|
||||
numbers = re.findall(r'\d+', text)
|
||||
result=0
|
||||
@@ -227,7 +232,7 @@ class TextToNumber:
|
||||
# print(result)
|
||||
|
||||
if random_number=='enable' and result>0:
|
||||
result= random.randint(1, 10000000000)
|
||||
result= random.randint(1, max_num)
|
||||
return {"ui": {"text": [text],"num":[result]}, "result": (result,)}
|
||||
|
||||
|
||||
@@ -341,11 +346,18 @@ class MultiplicationNode:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"numberA":(any_type,),
|
||||
"numberB":("FLOAT", {
|
||||
"multiply_by":("FLOAT", {
|
||||
"default": 0,
|
||||
"min": -1, #Minimum value
|
||||
"min": -2, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.1, #Slider's step
|
||||
"step": 0.01, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"add_by":("FLOAT", {
|
||||
"default": 0,
|
||||
"min": -2000, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.01, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
})
|
||||
},
|
||||
@@ -360,9 +372,9 @@ class MultiplicationNode:
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
|
||||
def run(self,numberA,numberB):
|
||||
b=int(numberA*numberB)
|
||||
a=float(numberA*numberB)
|
||||
def run(self,numberA,multiply_by,add_by):
|
||||
b=int(numberA*multiply_by+add_by)
|
||||
a=float(numberA*multiply_by+add_by)
|
||||
return (a,b,)
|
||||
|
||||
class TextInput:
|
||||
@@ -418,7 +430,7 @@ class DynamicDelayProcessor:
|
||||
},
|
||||
"optional":{
|
||||
"any_input":(any_type,),
|
||||
"delay_by_text":("STRING",{"multiline":True,}),
|
||||
"delay_by_text":("STRING",{"multiline":True,"dynamicPrompts": False,}),
|
||||
"words_per_seconds":("FLOAT",{ "default":1.50,"min": 0.0,"max": 1000.00,"display":"Chars per second?"}),
|
||||
"replace_output": (["disable","enable"],),
|
||||
"replace_value":("INT",{ "default":-1,"min": 0,"max": 1000000,"display":"Replacement value"})
|
||||
@@ -705,3 +717,117 @@ class TESTNODE_:
|
||||
result = list_stats.count_types(ANY)
|
||||
|
||||
return {"ui": {"data": result,"type":[str(type(ANY[0]))]}, "result": (ANY,)}
|
||||
|
||||
|
||||
|
||||
class CreateSeedNode:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("seed",)
|
||||
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Utils"
|
||||
|
||||
def run(self, seed):
|
||||
return (seed,)
|
||||
|
||||
|
||||
class CreateCkptNames:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_names": ("STRING",{"multiline": True,"default": "\n".join(folder_paths.get_filename_list("checkpoints")),"dynamicPrompts": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("ckpt_names",)
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
# OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Utils"
|
||||
|
||||
def run(self, ckpt_names):
|
||||
ckpt_names=ckpt_names.split('\n')
|
||||
ckpt_names = [name for name in ckpt_names if name.strip()]
|
||||
return (ckpt_names,)
|
||||
|
||||
|
||||
class CreateLoraNames:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"lora_names": ("STRING",{"multiline": True,"default": "\n".join(folder_paths.get_filename_list("loras")),"dynamicPrompts": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,"STRING",)
|
||||
RETURN_NAMES = ("lora_names","prompt",)
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (True,True,)
|
||||
|
||||
# OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Utils"
|
||||
|
||||
def run(self, lora_names):
|
||||
lora_names=lora_names.split('\n')
|
||||
lora_names = [name for name in lora_names if name.strip()]
|
||||
prompts=[os.path.splitext(n)[0] for n in lora_names]
|
||||
return (lora_names,prompts,)
|
||||
|
||||
|
||||
|
||||
class CreateSampler_names:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"sampler_names": ("STRING",{"multiline": True,"default": "\n".join(comfy.samplers.KSampler.SAMPLERS),"dynamicPrompts": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("sampler_names",)
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
# OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Utils"
|
||||
|
||||
def run(self, sampler_names):
|
||||
sampler_names=sampler_names.split('\n')
|
||||
sampler_names = [name for name in sampler_names if name.strip()]
|
||||
return (sampler_names,)
|
||||
+2
-2
@@ -4,5 +4,5 @@ watchdog
|
||||
opencv-python-headless
|
||||
matplotlib
|
||||
openai
|
||||
# simple-lama-inpainting
|
||||
# clip-interrogator==0.6.0
|
||||
simple-lama-inpainting
|
||||
clip-interrogator==0.6.0
|
||||
+2
-2
@@ -391,14 +391,14 @@
|
||||
<a class="link" href="https://www.mixcomfy.com" target="_blank">ComfyUI中文爱好者社区推荐</a>
|
||||
</div>
|
||||
|
||||
<a target="_blank" href="https://github.com/shadowcz007/comfyui-mixlab-nodes" style="text-decoration: none;
|
||||
<a target="_blank" href="https://www.mixcomfy.com/blog/" style="text-decoration: none;
|
||||
color: black;font-size:12px">
|
||||
<svg height="32" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="32" data-view-component="true"
|
||||
class="octicon octicon-mark-github v-align-middle color-fg-default">
|
||||
<path
|
||||
d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z">
|
||||
</path>
|
||||
</svg> Code
|
||||
</svg> Community
|
||||
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -399,7 +399,7 @@ app.registerExtension({
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
console.log('#loadedGraphNode1111')
|
||||
// console.log('#loadedGraphNode1111')
|
||||
window._mixlab_app_json = null //切换workflow需要清空
|
||||
if (node.type === 'AppInfo') {
|
||||
let auto_save = node.widgets.filter(w => w.name == 'auto_save')[0]
|
||||
@@ -408,6 +408,9 @@ app.registerExtension({
|
||||
auto_save.value = 'enable'
|
||||
}
|
||||
}
|
||||
|
||||
// app.canvas.centerOnNode(node)
|
||||
// app.canvas.setZoom(0.45)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.13.0'
|
||||
const version = 'v0.15.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
@@ -156,6 +156,32 @@ const parseSvg = async svgContent => {
|
||||
return { data, image: base64, svgElement }
|
||||
}
|
||||
|
||||
|
||||
function findImages(nodeId) {
|
||||
// 检查当前节点是否有 imgs 字段
|
||||
const n = app.graph.getNodeById(nodeId)
|
||||
if (n.imgs) {
|
||||
return n.imgs;
|
||||
}
|
||||
|
||||
// 检查当前节点的 inputs 是否有 image 字段
|
||||
if (n.inputs) {
|
||||
for (let i = 0; i < n.inputs.length; i++) {
|
||||
if (n.inputs[i].name==='image'||n.inputs[i].name==='images') {
|
||||
// 获取新的 nodeId,并递归调用 findImages 函数
|
||||
var linkId = n.inputs[i]?.link;
|
||||
var origin_id = app.graph.links[linkId].origin_id
|
||||
return findImages(origin_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到 imgs 字段或者 image 字段,则返回 null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function setArea (cw, ch, topBase64, base64, data, fn) {
|
||||
let displayHeight = Math.round(window.screen.availHeight * 0.8)
|
||||
let div = document.createElement('div')
|
||||
@@ -571,15 +597,19 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
try {
|
||||
console.log('this.inputs', this.inputs)
|
||||
let topLinkId = this.inputs[0].link
|
||||
let topNodeId = app.graph.links[topLinkId].origin_id
|
||||
let topIm = app.graph.getNodeById(topNodeId).imgs[0]
|
||||
console.log('this.inputs', this.id)
|
||||
let imgs=findImages(this.id)
|
||||
|
||||
// let topLinkId = this.inputs[0].link
|
||||
// let topNodeId = app.graph.links[topLinkId].origin_id
|
||||
let topIm = imgs[0]
|
||||
|
||||
let linkId = this.inputs[3].link
|
||||
let nodeId = app.graph.links[linkId].origin_id
|
||||
// console.log(linkId,this.inputs)
|
||||
let im = app.graph.getNodeById(nodeId).imgs[0]
|
||||
let imgs2=findImages(nodeId)
|
||||
let im = imgs2[0]
|
||||
console.log(topIm,im)
|
||||
// let src = im.src
|
||||
setArea(
|
||||
im.naturalWidth,
|
||||
@@ -589,7 +619,9 @@ app.registerExtension({
|
||||
data,
|
||||
updateValue
|
||||
)
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1331,7 +1331,13 @@ app.registerExtension({
|
||||
let w = 360,
|
||||
s = widget.preview.videoWidth / widget.preview.videoHeight,
|
||||
h = w / s || w
|
||||
console.log(h)
|
||||
// console.log(h)
|
||||
|
||||
if (!window.documentPictureInPicture) {
|
||||
window.alert(
|
||||
'This feature is available only in secure contexts (HTTPS), in some or all supporting browsers. https://developer.mozilla.org/en-US/docs/Web/API/Document_Picture-in-Picture_API'
|
||||
)
|
||||
}
|
||||
|
||||
const pipWindow = await documentPictureInPicture.requestWindow({
|
||||
width: w,
|
||||
@@ -2137,8 +2143,8 @@ const node = {
|
||||
loadedGraphNode (node, app) {
|
||||
if (node.type === 'RandomPrompt') {
|
||||
try {
|
||||
let max_count = node.widgets.filter(w => w.name === "max_count")[0];
|
||||
max_count.value= node.widgets_values[0]
|
||||
let max_count = node.widgets.filter(w => w.name === 'max_count')[0]
|
||||
max_count.value = node.widgets_values[0]
|
||||
// console.log('RandomPrompt',max_count,node.widgets_values[0])
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
|
||||
@@ -266,16 +266,7 @@ app.registerExtension({
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'RandomPrompt') {
|
||||
// try {
|
||||
// let mutable_prompt = node.widgets.filter(w => w.name === 'mutable_prompt')[0]
|
||||
// // let ks = getLocalData(`_mixlab_PromptSlide`)
|
||||
// let uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
|
||||
// // console.log('##widget', uploadWidget.value)
|
||||
// let keywords = JSON.parse(uploadWidget.value)
|
||||
// if (keywords && keywords[0]) {
|
||||
// mutable_prompt.value=keywords.join('\n')
|
||||
// }
|
||||
// } catch (error) {}
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
const smart_connect_config_input = [
|
||||
{
|
||||
node_type: 'CLIPTextEncode',
|
||||
node_widget_name: 'text',
|
||||
inputNodeName: 'RandomPrompt',
|
||||
inputNode_output_name: 'STRING'
|
||||
},
|
||||
{
|
||||
node_type: 'CLIPTextEncode',
|
||||
node_widget_name: 'text',
|
||||
inputNodeName: 'EmbeddingPrompt',
|
||||
inputNode_output_name: 'STRING'
|
||||
},
|
||||
{
|
||||
node_type: 'CLIPTextEncode',
|
||||
node_widget_name: 'text',
|
||||
inputNodeName: 'ChinesePrompt_Mix',
|
||||
inputNode_output_name: 'prompt'
|
||||
},
|
||||
{
|
||||
node_type: 'CheckpointLoaderSimple',
|
||||
node_widget_name: 'ckpt_name',
|
||||
inputNodeName: 'CkptNames_',
|
||||
inputNode_output_name: 'ckpt_names'
|
||||
},
|
||||
{
|
||||
node_type: 'KSampler',
|
||||
node_widget_name: 'sampler_name',
|
||||
inputNodeName: 'SamplerNames_',
|
||||
inputNode_output_name: 'sampler_names'
|
||||
},
|
||||
{
|
||||
node_type: 'LoraLoaderModelOnly',
|
||||
node_widget_name: 'lora_name',
|
||||
inputNodeName: 'LoraNames_',
|
||||
inputNode_output_name: 'lora_names'
|
||||
},
|
||||
{
|
||||
node_type: 'LoadLoRA',
|
||||
node_widget_name: 'lora_name',
|
||||
inputNodeName: 'LoraNames_',
|
||||
inputNode_output_name: 'lora_names'
|
||||
},
|
||||
{
|
||||
node_type: 'Moondream',
|
||||
node_widget_name: 'image',
|
||||
inputNodeName: 'LoadImage',
|
||||
inputNode_output_name: 'IMAGE'
|
||||
}
|
||||
]
|
||||
|
||||
const smart_connect_config_output = [
|
||||
{
|
||||
node_type: 'LoadImage',
|
||||
node_output_name: 'IMAGE',
|
||||
outputNodeName: 'ClipInterrogator',
|
||||
outputNode_input_name: 'image'
|
||||
},
|
||||
{
|
||||
node_type: 'VAEDecode',
|
||||
node_output_name: 'IMAGE',
|
||||
outputNodeName: 'PromptImage',
|
||||
outputNode_input_name: 'images'
|
||||
},
|
||||
{
|
||||
node_type: 'VAEDecode',
|
||||
node_output_name: 'IMAGE',
|
||||
outputNodeName: 'PreviewImage',
|
||||
outputNode_input_name: 'images'
|
||||
},
|
||||
{
|
||||
node_type: 'VAEDecode',
|
||||
node_output_name: 'IMAGE',
|
||||
outputNodeName: 'SaveImage',
|
||||
outputNode_input_name: 'images'
|
||||
},
|
||||
{
|
||||
node_type: 'Moondream',
|
||||
node_output_name: 'STRING',
|
||||
outputNodeName: 'ShowTextForGPT',
|
||||
outputNode_input_name: 'text'
|
||||
}
|
||||
]
|
||||
|
||||
// import {
|
||||
// convertToInput,
|
||||
// getConfig,
|
||||
// isConvertableWidget
|
||||
// } from '../../../extensions/core/widgetInputs.js'
|
||||
|
||||
const CONVERTED_TYPE = 'converted-widget'
|
||||
const GET_CONFIG = Symbol()
|
||||
|
||||
function getConfig (widgetName) {
|
||||
const { nodeData } = this.constructor
|
||||
return (
|
||||
nodeData?.input?.required[widgetName] ??
|
||||
nodeData?.input?.optional?.[widgetName]
|
||||
)
|
||||
}
|
||||
|
||||
function hideWidget (node, widget, suffix = '') {
|
||||
widget.origType = widget.type
|
||||
widget.origComputeSize = widget.computeSize
|
||||
widget.origSerializeValue = widget.serializeValue
|
||||
widget.computeSize = () => [0, -4] // -4 is due to the gap litegraph adds between widgets automatically
|
||||
widget.type = CONVERTED_TYPE + suffix
|
||||
widget.serializeValue = () => {
|
||||
// Prevent serializing the widget if we have no input linked
|
||||
if (!node.inputs) {
|
||||
return undefined
|
||||
}
|
||||
let node_input = node.inputs.find(i => i.widget?.name === widget.name)
|
||||
|
||||
if (!node_input || !node_input.link) {
|
||||
return undefined
|
||||
}
|
||||
return widget.origSerializeValue
|
||||
? widget.origSerializeValue()
|
||||
: widget.value
|
||||
}
|
||||
|
||||
// Hide any linked widgets, e.g. seed+seedControl
|
||||
if (widget.linkedWidgets) {
|
||||
for (const w of widget.linkedWidgets) {
|
||||
hideWidget(node, w, ':' + widget.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertToInput (node, widget, config) {
|
||||
hideWidget(node, widget)
|
||||
|
||||
const type = config[0]
|
||||
|
||||
// Add input and store widget config for creating on primitive node
|
||||
const sz = node.size
|
||||
node.addInput(widget.name, type, {
|
||||
widget: { name: widget.name, [GET_CONFIG]: () => config }
|
||||
})
|
||||
|
||||
for (const widget of node.widgets) {
|
||||
widget.last_y += LiteGraph.NODE_SLOT_HEIGHT
|
||||
}
|
||||
|
||||
// Restore original size but grow if needed
|
||||
node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])])
|
||||
}
|
||||
|
||||
export function smart_init () {
|
||||
LGraphCanvas.prototype._createNodeForInput = function (
|
||||
node,
|
||||
widget,
|
||||
inputNodeName,
|
||||
inputNode_slot
|
||||
) {
|
||||
// console.log(node.pos)
|
||||
|
||||
// var widget = node.widgets.filter(w => w.name === node_widget_name)[0]
|
||||
if (widget) {
|
||||
// 如果有存在的,没有连线输出的,自动连,不新建
|
||||
let input_node = null
|
||||
|
||||
Array.from(app.graph.findNodesByType(inputNodeName), n => {
|
||||
var links = n.outputs.filter(o => o.name === inputNode_slot)[0].links
|
||||
// console.log(links)
|
||||
if (!links || links?.length === 0) input_node = n
|
||||
})
|
||||
// 新建
|
||||
if (!input_node) {
|
||||
input_node = LiteGraph.createNode(inputNodeName)
|
||||
input_node.pos = [node.pos[0] - node.size[0] - 24, node.pos[1] - 48]
|
||||
app.canvas.graph.add(input_node, false)
|
||||
} else {
|
||||
input_node.pos = [node.pos[0] - node.size[0] - 24, node.pos[1] - 48]
|
||||
}
|
||||
|
||||
const config = getConfig.call(node, widget.name) ?? [
|
||||
widget.type,
|
||||
widget.options || {}
|
||||
]
|
||||
let node_slotType = config[0]
|
||||
// 如果input没有,则创建
|
||||
if (!node.inputs?.filter(inp => inp.name === widget.name)[0]||!node.inputs)
|
||||
convertToInput(node, widget, config)
|
||||
input_node.connectByType(inputNode_slot, node, node_slotType)
|
||||
}
|
||||
}
|
||||
|
||||
LGraphCanvas.prototype._createNodeForOutput = function (
|
||||
node,
|
||||
widget,
|
||||
outputNodeName,
|
||||
outputNode_slot
|
||||
) {
|
||||
if (widget) {
|
||||
let output_node
|
||||
Array.from(app.graph.findNodesByType(outputNodeName), n => {
|
||||
var links = n.inputs.filter(o => o.name === outputNode_slot)[0].links
|
||||
// console.log(links)
|
||||
if (!links || links?.length === 0) output_node = n
|
||||
})
|
||||
console.log('output_node', output_node, widget.name)
|
||||
|
||||
if (!output_node) {
|
||||
// 新建
|
||||
output_node = LiteGraph.createNode(outputNodeName)
|
||||
output_node.pos = [node.pos[0] + node.size[0] + 24, node.pos[1] - 48]
|
||||
app.canvas.graph.add(output_node, false)
|
||||
} else {
|
||||
output_node.pos = [node.pos[0] + node.size[0] + 24, node.pos[1] - 48]
|
||||
}
|
||||
|
||||
const config = getConfig.call(node, widget.name) ?? [
|
||||
widget.type,
|
||||
widget.options || {}
|
||||
]
|
||||
let node_slotType = config[0]
|
||||
console.log(node_slotType, output_node, outputNode_slot)
|
||||
let type = output_node.inputs.filter(
|
||||
inp => inp.name == outputNode_slot
|
||||
)[0].type
|
||||
node.connectByType(node_slotType, output_node, type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addSmartMenu (options, node) {
|
||||
let sopts = []
|
||||
|
||||
for (const sc of smart_connect_config_input) {
|
||||
// 有智能推荐,则出现
|
||||
if (node.type === sc.node_type) {
|
||||
// console.log('smart',node)
|
||||
// 则出现 randomPrompt
|
||||
// CLIPTextEncode 的widget ,name== 'text'
|
||||
let node_widget_name = sc.node_widget_name
|
||||
let widget = node.widgets.filter(w => w.name === node_widget_name)[0]
|
||||
if (!widget) {
|
||||
// 控件没有,则查找inputs
|
||||
widget = node.inputs.filter(w => w.name === node_widget_name)[0]
|
||||
}
|
||||
|
||||
let isLinkNull = true
|
||||
// 如果input里已经有,但是link为空
|
||||
if (node.inputs?.filter(inp => inp.name === node_widget_name)[0]) {
|
||||
isLinkNull =
|
||||
node.inputs.filter(inp => inp.name === node_widget_name)[0].link ===
|
||||
null
|
||||
}
|
||||
|
||||
if (widget && isLinkNull) {
|
||||
sopts.push({
|
||||
content: sc.inputNodeName.split('_')[0] + '➡️',
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype._createNodeForInput(
|
||||
node, //当前node
|
||||
widget, //当前node里需要自动连线的widget
|
||||
sc.inputNodeName, //作为input的node type
|
||||
sc.inputNode_output_name // 作为input的node的outputs的name. the input slot type of the target node
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const sc of smart_connect_config_output) {
|
||||
if (node.type === sc.node_type) {
|
||||
let node_output_name = sc.node_output_name
|
||||
const widget = node.outputs.filter(w => w.name === node_output_name)[0]
|
||||
|
||||
let isLinkNull = true
|
||||
// 如果output里 link为空
|
||||
if (node.outputs?.filter(inp => inp.name === node_output_name)[0]) {
|
||||
isLinkNull =
|
||||
node.outputs.filter(inp => inp.name === node_output_name)[0].links
|
||||
?.length === 0
|
||||
if (!node.outputs.filter(inp => inp.name === node_output_name)[0].links)
|
||||
isLinkNull = true
|
||||
}
|
||||
|
||||
if (widget && isLinkNull) {
|
||||
sopts.push({
|
||||
content: '➡️' + sc.outputNodeName.split('_')[0],
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype._createNodeForOutput(
|
||||
node, //当前node
|
||||
widget, //当前node里需要自动连线的widget
|
||||
sc.outputNodeName, //作为input的node type
|
||||
sc.outputNode_input_name // 作为input的node的outputs的name. the input slot type of the target node
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sopts.length > 0) options = [...sopts, null, ...options]
|
||||
|
||||
return options
|
||||
}
|
||||
+143
-11
@@ -1,7 +1,4 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
import { closeIcon } from './svg_icons.js'
|
||||
|
||||
import {
|
||||
@@ -9,6 +6,67 @@ import {
|
||||
GroupNodeHandler
|
||||
} from '../../../extensions/core/groupNode.js'
|
||||
|
||||
import { smart_init, addSmartMenu } from './smart_connect.js'
|
||||
|
||||
function copyNodeValues (src, dest) {
|
||||
// title
|
||||
dest.title = src.title
|
||||
|
||||
// copy input connections
|
||||
|
||||
for (let i in src.inputs) {
|
||||
let input = src.inputs[i]
|
||||
if (input.link) {
|
||||
let link = app.graph.links[input.link]
|
||||
let src_node = app.graph.getNodeById(link.origin_id)
|
||||
if (dest.inputs.filter(inp => inp.name === input.name).length === 0) {
|
||||
// 没有,name换了
|
||||
let dInp = dest.inputs.filter(inp => inp.type === input.type)
|
||||
if (dInp.length === 1) {
|
||||
src_node.connect(link.origin_slot, dest.id, dInp[0].name)
|
||||
}
|
||||
} else {
|
||||
src_node.connect(link.origin_slot, dest.id, input.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy output connections
|
||||
let output_links = {}
|
||||
for (let i in src.outputs) {
|
||||
let output = src.outputs[i]
|
||||
if (output.links) {
|
||||
let links = []
|
||||
for (let j in output.links) {
|
||||
links.push(app.graph.links[output.links[j]])
|
||||
}
|
||||
output_links[output.name] = links
|
||||
}
|
||||
}
|
||||
|
||||
for (let i in dest.outputs) {
|
||||
let links = output_links[dest.outputs[i].name]
|
||||
if (links) {
|
||||
for (let j in links) {
|
||||
let link = links[j]
|
||||
let target_node = app.graph.getNodeById(link.target_id)
|
||||
dest.connect(parseInt(i), target_node, link.target_slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy widgets
|
||||
for (const w of src.widgets) {
|
||||
for (const d of dest.widgets) {
|
||||
if (w.name === d.name) {
|
||||
d.value = w.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.graph.afterChange()
|
||||
}
|
||||
|
||||
function deepEqual (obj1, obj2) {
|
||||
if (typeof obj1 !== typeof obj2) {
|
||||
return false
|
||||
@@ -575,22 +633,71 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
LGraphCanvas.prototype.fixTheNode = function (node) {
|
||||
let new_node = LiteGraph.createNode(node.comfyClass)
|
||||
new_node.pos = [node.pos[0], node.pos[1]]
|
||||
app.canvas.graph.add(new_node, false)
|
||||
copyNodeValues(node, new_node)
|
||||
app.canvas.graph.remove(node)
|
||||
}
|
||||
|
||||
smart_init()
|
||||
|
||||
const getNodeMenuOptions = LGraphCanvas.prototype.getNodeMenuOptions // store the existing method
|
||||
LGraphCanvas.prototype.getNodeMenuOptions = function (node) {
|
||||
// replace it
|
||||
const options = getNodeMenuOptions.apply(this, arguments) // start by calling the stored one
|
||||
node.setDirtyCanvas(true, true) // force a redraw of (foreground, background)
|
||||
console.log('getNodeMenuOptions', node.type == 'CLIPTextEncode')
|
||||
|
||||
return [
|
||||
let opts = [
|
||||
{
|
||||
content: 'Help ♾️Mixlab', // with a name
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype.helpAboutNode(node)
|
||||
} // and the callback
|
||||
},
|
||||
null,
|
||||
...options
|
||||
] // and return the options
|
||||
{
|
||||
content: 'Fix node v2', // with a name
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype.fixTheNode(node)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
opts = addSmartMenu(opts,node)
|
||||
|
||||
// if (node.type == 'CLIPTextEncode') {
|
||||
// // 则出现 randomPrompt
|
||||
// // CLIPTextEncode 的widget ,name== 'text'
|
||||
// let node_widget_name = 'text'
|
||||
// const widget = node.widgets.filter(w => w.name === node_widget_name)[0]
|
||||
|
||||
// let mixlab_nodes_smart_connect= [{node_type:'CLIPTextEncode',
|
||||
// node_widget_name:'text',
|
||||
// inputNodeName:'RandomPrompt',
|
||||
// inputNode_output_type:'STRING'}]
|
||||
|
||||
// if (widget) {
|
||||
// opts = [
|
||||
// {
|
||||
// content: 'RandomPrompt',
|
||||
// callback: () => {
|
||||
// LGraphCanvas.prototype._createNodeForInput(
|
||||
// node, //当前node
|
||||
// widget,//当前node里需要自动连线的widget
|
||||
// 'RandomPrompt',//作为input的node type
|
||||
// 'STRING'// 作为input的node的outputs的type. the input slot type of the target node
|
||||
// )
|
||||
// }
|
||||
// },
|
||||
// null,
|
||||
// ...opts
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
|
||||
return [...opts, null, ...options] // and return the options
|
||||
}
|
||||
|
||||
const getGroupMenuOptions = LGraphCanvas.prototype.getGroupMenuOptions // store the existing method
|
||||
@@ -697,6 +804,17 @@ app.registerExtension({
|
||||
})
|
||||
} // and the callback
|
||||
},
|
||||
{
|
||||
content: `Remove Group&Nodes ♾️Mixlab`, // with a name
|
||||
callback: async (value, opts, e, menu, group) => {
|
||||
// console.log(group)
|
||||
let nodes = group._nodes
|
||||
for (const node of nodes) {
|
||||
app.graph.remove(node)
|
||||
}
|
||||
app.graph.remove(group)
|
||||
} // and the callback
|
||||
},
|
||||
null,
|
||||
...options
|
||||
] // and return the options
|
||||
@@ -722,7 +840,7 @@ app.registerExtension({
|
||||
|
||||
const apps = await get_my_app()
|
||||
|
||||
let apps_map = { '0': [] }
|
||||
let apps_map = { 0: [] }
|
||||
|
||||
for (const app of apps) {
|
||||
if (app.category) {
|
||||
@@ -735,7 +853,7 @@ app.registerExtension({
|
||||
|
||||
let apps_opts = []
|
||||
for (const category in apps_map) {
|
||||
console.log('category',typeof(category))
|
||||
console.log('category', typeof category)
|
||||
if (category === '0') {
|
||||
apps_opts.push(
|
||||
...Array.from(apps_map[category], a => {
|
||||
@@ -764,7 +882,7 @@ app.registerExtension({
|
||||
} else {
|
||||
// 二级
|
||||
apps_opts.push({
|
||||
content: '🚀 '+category,
|
||||
content: '🚀 ' + category,
|
||||
has_submenu: true,
|
||||
disabled: false,
|
||||
submenu: {
|
||||
@@ -1047,7 +1165,7 @@ app.registerExtension({
|
||||
has_submenu: true,
|
||||
disabled: false,
|
||||
submenu: {
|
||||
options:apps_opts
|
||||
options: apps_opts
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1055,5 +1173,19 @@ app.registerExtension({
|
||||
return options
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// console.log(
|
||||
// '#ui init',
|
||||
// app.graph._nodes[app.graph._nodes.length - 1].id,
|
||||
// node.id
|
||||
// )
|
||||
try {
|
||||
// 用来居中显示节点
|
||||
if ((app.graph._nodes[app.graph._nodes.length - 1].id, node.id)) {
|
||||
app.canvas.centerOnNode(node)
|
||||
app.canvas.setZoom(0.45)
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
{
|
||||
"last_node_id": 69,
|
||||
"last_link_id": 72,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 37,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
6705,
|
||||
-216
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 33
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
34
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
6693,
|
||||
61
|
||||
],
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 25,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
6689,
|
||||
306
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"type": "EmbeddingPrompt",
|
||||
"pos": [
|
||||
6104,
|
||||
23
|
||||
],
|
||||
"size": {
|
||||
"0": 399.6408996582031,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
25
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmbeddingPrompt"
|
||||
},
|
||||
"widgets_values": [
|
||||
"negative-embed-verybadimagenegative_v1.3",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
7551,
|
||||
-184
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 70
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 69
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
71
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
7187,
|
||||
-145
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 34
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
70
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
408562451564429,
|
||||
"fixed",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"type": "PromptImage",
|
||||
"pos": [
|
||||
7853,
|
||||
-238
|
||||
],
|
||||
"size": [
|
||||
465.6378949342379,
|
||||
760.4568424013569
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 71
|
||||
},
|
||||
{
|
||||
"name": "prompts",
|
||||
"type": "STRING",
|
||||
"link": 72,
|
||||
"widget": {
|
||||
"name": "prompts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PromptImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
"disable",
|
||||
{
|
||||
"_images": [
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_0_00027_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_1_00028_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_2_00029_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_3_00030_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_4_00031_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_5_00032_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
]
|
||||
],
|
||||
"prompts": [
|
||||
"512-inpainting-ema.safetensors",
|
||||
"SSD-1B.safetensors",
|
||||
"awportrait_v12.safetensors",
|
||||
"cardosAnime_v20.safetensors",
|
||||
"deliberate_v2.safetensors",
|
||||
"gameIconInstitute_v40.safetensors"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
6105,
|
||||
-139
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "ckpt_name",
|
||||
"type": [
|
||||
"512-inpainting-ema.safetensors",
|
||||
"SSD-1B.safetensors",
|
||||
"awportrait_v12.safetensors",
|
||||
"cardosAnime_v20.safetensors",
|
||||
"deliberate_v2.safetensors",
|
||||
"gameIconInstitute_v40.safetensors",
|
||||
"illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors",
|
||||
"sd_xl_turbo_1.0_fp16.safetensors",
|
||||
"svd.safetensors"
|
||||
],
|
||||
"link": 64,
|
||||
"widget": {
|
||||
"name": "ckpt_name"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
6,
|
||||
33
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
69
|
||||
],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"type": "CkptNames_",
|
||||
"pos": [
|
||||
7860,
|
||||
-494
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "ckpt_names",
|
||||
"type": "*",
|
||||
"links": [
|
||||
64,
|
||||
72
|
||||
],
|
||||
"shape": 6,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CkptNames_"
|
||||
},
|
||||
"widgets_values": [
|
||||
"512-inpainting-ema.safetensors\nSSD-1B.safetensors\nawportrait_v12.safetensors\ncardosAnime_v20.safetensors\ndeliberate_v2.safetensors\ngameIconInstitute_v40.safetensors"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
3,
|
||||
5,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
6,
|
||||
2,
|
||||
1,
|
||||
5,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
25,
|
||||
27,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
33,
|
||||
2,
|
||||
1,
|
||||
37,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
34,
|
||||
37,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
64,
|
||||
56,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
[
|
||||
"512-inpainting-ema.safetensors",
|
||||
"SSD-1B.safetensors",
|
||||
"awportrait_v12.safetensors",
|
||||
"cardosAnime_v20.safetensors",
|
||||
"deliberate_v2.safetensors",
|
||||
"gameIconInstitute_v40.safetensors",
|
||||
"illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors",
|
||||
"sd_xl_turbo_1.0_fp16.safetensors",
|
||||
"svd.safetensors"
|
||||
]
|
||||
],
|
||||
[
|
||||
69,
|
||||
2,
|
||||
2,
|
||||
67,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
70,
|
||||
1,
|
||||
0,
|
||||
67,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
71,
|
||||
67,
|
||||
0,
|
||||
61,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
72,
|
||||
56,
|
||||
0,
|
||||
61,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
Reference in New Issue
Block a user