Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34882bca10 | ||
|
|
43b94be806 | ||
|
|
0349d81694 | ||
|
|
93254a4c07 | ||
|
|
8485447325 | ||
|
|
717092a3ce | ||
|
|
14a1121860 | ||
|
|
8c1eec2858 | ||
|
|
b6bb4a3055 | ||
|
|
6873492872 | ||
|
|
2d71b3e647 | ||
|
|
e7320ec0c4 | ||
|
|
560be6aee7 | ||
|
|
54614079ca | ||
|
|
b0cd0bcb5b | ||
|
|
e46f8a45d0 | ||
|
|
1616dd6602 | ||
|
|
282eedfea6 | ||
|
|
17b163e234 | ||
|
|
501d97bb5c | ||
|
|
de92038f88 | ||
|
|
530333d72d | ||
|
|
041f49540c | ||
|
|
71c7865d2d | ||
|
|
c7fbf05970 | ||
|
|
2986a01469 | ||
|
|
fa7c5d8b4d | ||
|
|
1d8db7510b | ||
|
|
7ef0612ce7 | ||
|
|
7ff4790493 | ||
|
|
640ef31625 | ||
|
|
e4ac947d96 | ||
|
|
d287e28e5c | ||
|
|
f33c17f762 | ||
|
|
e07b8cc7bf | ||
|
|
6abe07bb79 | ||
|
|
7fbd03bda7 | ||
|
|
9cc2ac02da | ||
|
|
2f2a3035a2 | ||
|
|
5d8f0a3b0a | ||
|
|
8aadd72494 | ||
|
|
fceec754a4 | ||
|
|
419b7c985c | ||
|
|
ce62fc73da | ||
|
|
4f31641da3 | ||
|
|
deec62ab76 | ||
|
|
5c8cdb58c7 | ||
|
|
d820842e39 | ||
|
|
2f78a523b3 | ||
|
|
b2a8666423 | ||
|
|
2c02a471d0 | ||
|
|
ea521e0303 | ||
|
|
9b5daac023 | ||
|
|
0de83f88dc | ||
|
|
342ce8ccad | ||
|
|
b7881d84b1 | ||
|
|
0f5ad38384 | ||
|
|
a3f487c822 | ||
|
|
d0f496adc1 | ||
|
|
665861ff35 | ||
|
|
61568e021c | ||
|
|
a2edc37d89 | ||
|
|
1c4cb43f7b | ||
|
|
66143b0e20 | ||
|
|
f0da5e25c9 | ||
|
|
ebf25b585f | ||
|
|
fb8968d438 | ||
|
|
15cfeedf7b | ||
|
|
50ae13a993 | ||
|
|
aedf917067 | ||
|
|
69ac5e52a0 | ||
|
|
368f7e508d | ||
|
|
44f0676323 | ||
|
|
98273b37f2 | ||
|
|
eff718c13f | ||
|
|
615a2abcfe | ||
|
|
2b4b38ce03 | ||
|
|
dbbd2ffef3 | ||
|
|
b1a875b151 | ||
|
|
6a39ea1188 | ||
|
|
69aac075e8 | ||
|
|
e1dc9250b9 | ||
|
|
8b9c577f55 | ||
|
|
6d8c266b04 | ||
|
|
9292f22862 | ||
|
|
10e9629ca3 | ||
|
|
4f694195a2 | ||
|
|
ff6c0f0e39 | ||
|
|
a6e8783605 | ||
|
|
3e84b8cd77 | ||
|
|
9e70cc0090 | ||
|
|
7dddd2d6e5 | ||
|
|
63a1ca5ec6 | ||
|
|
0104f7f6a9 | ||
|
|
6b1f5cbf69 | ||
|
|
f888e3d75d | ||
|
|
16631d21d9 | ||
|
|
ccb4ba08fc | ||
|
|
0daf114fe8 | ||
|
|
4e9c9c897c | ||
|
|
31fde1ae34 | ||
|
|
aadbb0b389 | ||
|
|
52a8e7faf3 | ||
|
|
3893873085 | ||
|
|
037080ac39 | ||
|
|
4738313b64 | ||
|
|
e842c3bd06 | ||
|
|
fa73da5a00 | ||
|
|
ffe26e8571 | ||
|
|
17e022a7aa |
@@ -7,15 +7,19 @@ on:
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'yolain' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
|
||||
@@ -9,7 +9,6 @@ workflow/**
|
||||
autocomplete/**
|
||||
web_beta/**
|
||||
web_version/dev/**
|
||||
ComfyUI-Easy-Use-Frontend/
|
||||
docs/**
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[submodule "ComfyUI-Easy-Use-Frontend"]
|
||||
path = ComfyUI-Easy-Use-Frontend
|
||||
url = https://github.com/yolain/ComfyUI-Easy-Use-Frontend.git
|
||||
branch = main
|
||||
Submodule
+1
Submodule ComfyUI-Easy-Use-Frontend added at 8b840f2ed5
+34
-1
@@ -52,6 +52,40 @@ git clone https://github.com/yolain/ComfyUI-Easy-Use
|
||||
|
||||
## 📜 更新日志
|
||||
|
||||
**v1.3.2**
|
||||
|
||||
- 改造 `easy imageChooser` 节点以兼容 frontend>=v1.24.2, 解决方案参考自 [Comfyui_LG_Tools](https://github.com/LAOGOU-666/Comfyui_LG_Tools)
|
||||
- 改造 `easy stylesSelector` 节点, 你可在 [other styles files](https://github.com/yolain/EasyUse-Styles-Templates) 下载到 `styles` 文件夹下
|
||||
- 改造 `easy humanSegmentation` 节点
|
||||
- 修复 `easy makeImageForICLora` 节点.
|
||||
- 添加 `easy joycaption3API` 节点
|
||||
- 添加 `easy promptAwait` 节点
|
||||
|
||||
**v1.3.1**
|
||||
|
||||
- 重写 drawNodeWidget 修复组节点预览的问题.
|
||||
- 更新了一些 XYPlot 的功能 by [mekinney](https://github.com/mekinney)
|
||||
- 添加 `easy seedList` 节点 (它对循环节点有用)
|
||||
|
||||
**v1.3.0**
|
||||
|
||||
- 将循环节点设置为最大输入和输出数量为20
|
||||
- 添加 `uniform width` 方式到 `easy makeImageForICLora`
|
||||
- 增加 `wildcardsPromptMatrix` 通配符提示词矩阵,由 [Rosmeowtis](https://github.com/Rosmeowtis) 贡献
|
||||
|
||||
**v1.2.9**
|
||||
|
||||
- 修复 Imagechooser 会导致工作流处理取消
|
||||
- 修复 brushnet tensor(640) 错误
|
||||
- 修复v1.6.0前端之后无法隐藏小部件的bug
|
||||
- 修复图像选择器无法选择图像
|
||||
- 修复ContextMenu Monkey修补以影响自定义脚本(PYSSSS)节点
|
||||
|
||||
**v1.2.8**
|
||||
|
||||
- 修复了一些BUG (😹)
|
||||
- 增加了多语言目录
|
||||
|
||||
**v1.2.7**
|
||||
|
||||
- 优化管理节点组显示
|
||||
@@ -496,7 +530,6 @@ git clone https://github.com/yolain/ComfyUI-Easy-Use
|
||||
**Comfyui-Easy-Use** 是一个 GPL 许可的开源项目。为了项目取得更好、可持续的发展,我希望能够获得更多的支持。 如果我的自定义节点为您的一天增添了价值,请考虑喝杯咖啡来进一步补充能量! 💖感谢您的支持,每一杯咖啡都是我创作的动力!
|
||||
|
||||
- [BiliBili充电](https://space.bilibili.com/1840885116)
|
||||
- [爱发电](https://afdian.com/a/yolain)
|
||||
- [Wechat/Alipay](https://github.com/user-attachments/assets/803469bd-ed6a-4fab-932d-50e5088a2d03)
|
||||
|
||||
感谢您的捐助,我将用这些费用来租用 GPU 或购买其他 GPT 服务,以便更好地调试和完善 ComfyUI-Easy-Use 功能
|
||||
|
||||
@@ -47,10 +47,45 @@ Double-click install.bat to install the required dependencies
|
||||
|
||||
## 📜 Changelog
|
||||
|
||||
**v1.3.2**
|
||||
|
||||
- Revamp `easy imageChooser` node to adapt frontend>=v1.24.2, solution referenced from [Comfyui_LG_Tools](https://github.com/LAOGOU-666/Comfyui_LG_Tools)
|
||||
- Revamp `easy stylesSelector` node, and you can download [other styles files](https://github.com/yolain/EasyUse-Styles-Templates) to the `styles` folder
|
||||
- Revamp `easy humanSegmentation` node
|
||||
- Fix `easy makeImageForICLora` node issue, that occurred when the heights of two images were the same during image stitching on.
|
||||
- Add `easy joycaption3API` node
|
||||
- Add `easy promptAwait` node
|
||||
|
||||
**v1.3.1**
|
||||
|
||||
- Rewrite drawNodeWidget and fix the GroupNode preview issue.
|
||||
- Updated some features of XYPlot by [mekinney](https://github.com/mekinney)
|
||||
- Add `easy seedList` node (It's useful for in loops)
|
||||
|
||||
**v1.3.0**
|
||||
|
||||
- Set loop nodes maximum number of inputs and outputs to 20
|
||||
- Add `uniform width` method to `easy makeImageForICLora`
|
||||
- Add `wildcardsPromptMatrix` Node by [Rosmeowtis](https://github.com/Rosmeowtis)
|
||||
|
||||
**v1.2.9**
|
||||
|
||||
- Fix ImageChooser causes workflow processing to cancel
|
||||
- Fix brushnet tensor(640) error
|
||||
- Fix widgets not hidden after v1.6.0 frontend
|
||||
- Fix image chooser can not select images
|
||||
- Fix contextMenu monkey patching to affect custom scripts (pysssss) nodes
|
||||
|
||||
**v1.2.8**
|
||||
|
||||
- Added the multi-language catalog
|
||||
- Fix CLIP vision model download URLs for IPAdapter and DynamiCrafter
|
||||
- Improve error handling for model downloads with clearer error messages and better handling of download failures
|
||||
|
||||
**v1.2.7**
|
||||
|
||||
- Optimize display of the node maps
|
||||
- Add `ben2` on `easy imageRemBg`
|
||||
- Added `ben2` on `easy imageRemBg`
|
||||
- Using a new way to display the models thumbnails in the loaders (supported diffusion_models、lors、checkpoints)
|
||||
|
||||
**v1.2.6**
|
||||
@@ -479,11 +514,10 @@ If my custom nodes has added value to your day, consider indulging in a coffee t
|
||||
💖You can support me in any of the following ways:
|
||||
|
||||
- [BiliBili](https://space.bilibili.com/1840885116)
|
||||
- [Afdian](https://afdian.com/a/yolain)
|
||||
- [Wechat / Alipay](https://github.com/user-attachments/assets/803469bd-ed6a-4fab-932d-50e5088a2d03)
|
||||
|
||||
## 🌟Stargazers
|
||||
|
||||
My gratitude extends to the generous souls who bestow a star. Your support is much appreciated!
|
||||
|
||||
[](https://github.com/yolain/ComfyUI-Easy-Use/stargazers)
|
||||
[](https://github.com/yolain/ComfyUI-Easy-Use/stargazers)
|
||||
|
||||
+6
-8
@@ -1,10 +1,14 @@
|
||||
__version__ = "1.2.7"
|
||||
__version__ = "1.3.2"
|
||||
|
||||
import yaml
|
||||
import json
|
||||
import os
|
||||
import folder_paths
|
||||
import importlib
|
||||
|
||||
cwd_path = os.path.dirname(os.path.realpath(__file__))
|
||||
comfy_path = folder_paths.base_path
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
@@ -16,9 +20,6 @@ for module_name in nodes_list:
|
||||
NODE_CLASS_MAPPINGS = {**NODE_CLASS_MAPPINGS, **imported_module.NODE_CLASS_MAPPINGS}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {**NODE_DISPLAY_NAME_MAPPINGS, **imported_module.NODE_DISPLAY_NAME_MAPPINGS}
|
||||
|
||||
cwd_path = os.path.dirname(os.path.realpath(__file__))
|
||||
comfy_path = folder_paths.base_path
|
||||
|
||||
#Wildcards
|
||||
from .py.libs.wildcards import read_wildcard_dict
|
||||
wildcards_path = os.path.join(os.path.dirname(__file__), "wildcards")
|
||||
@@ -59,11 +60,8 @@ if not os.path.exists(example_path):
|
||||
with open(example_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
# get comfyui revision
|
||||
from .py.libs.utils import compare_revision
|
||||
|
||||
new_frontend_revision = 2546
|
||||
web_default_version = 'v2' if compare_revision(new_frontend_revision) else 'v1'
|
||||
web_default_version = 'v2'
|
||||
# web directory
|
||||
config_path = os.path.join(cwd_path, "config.yaml")
|
||||
if os.path.isfile(config_path):
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"settingsCategories": {
|
||||
"Hotkeys": "Hotkeys",
|
||||
"Nodes": "Nodes",
|
||||
"NodesMap": "NodesMap",
|
||||
"StylesSelector": "StylesSelector"
|
||||
},
|
||||
"nodeCategories": {
|
||||
"Util": "Util",
|
||||
"Seed": "Seed",
|
||||
"Prompt": "Prompt",
|
||||
"Loaders": "Loaders",
|
||||
"Adapter": "Adapter",
|
||||
"Inpaint": "Inpaint",
|
||||
"PreSampling": "PreSampling",
|
||||
"Sampler": "Sampler",
|
||||
"Fix": "Fix",
|
||||
"Pipe": "Pipe",
|
||||
"XY Inputs": "XY Inputs",
|
||||
"Image": "Image",
|
||||
"Segmentation": "Segmentation",
|
||||
"\uD83D\uDEAB Deprecated": "\uD83D\uDEAB Deprecated",
|
||||
"Type": "Type",
|
||||
"Math": "Math",
|
||||
"Switch": "Switch",
|
||||
"Index Switch": "Index Switch",
|
||||
"While Loop": "While Loop",
|
||||
"For Loop": "For Loop",
|
||||
"LoadImage": "Load Image"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"EasyUse_Hotkeys_AddGroup": {
|
||||
"name": "Enable Shift+g to add the selected nodes to a group",
|
||||
"tooltip": "From v1.2.39, you can use Ctrl+g instead"
|
||||
},
|
||||
"EasyUse_Hotkeys_cleanVRAMUsed": {
|
||||
"name": "Enable Shift+r to unload model and node cache"
|
||||
},
|
||||
"EasyUse_Hotkeys_toggleNodesMap": {
|
||||
"name": "Enable Shift+m to toggle nodes map"
|
||||
},
|
||||
"EasyUse_Hotkeys_AlignSelectedNodes": {
|
||||
"name": "Enable Shift+Up/Down/Left/Right and Shift+Ctrl+Alt+Left/Right to align selected nodes",
|
||||
"tooltip": "Shift+Up/Down/Left/Right can align selected nodes, Shift+Ctrl+Alt+Left/Right can distribute nodes horizontally/vertically"
|
||||
},
|
||||
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
|
||||
"name": "Enable Shift+Ctrl+Left/Right to normalize selected nodes",
|
||||
"tooltip": "Enable Shift+Ctrl+Left to normalize width and Shift+Ctrl+Right to normalize height"
|
||||
},
|
||||
"EasyUse_Hotkeys_NodesTemplate": {
|
||||
"name": "Enable Alt+1~9 to paste node templates into the workflow"
|
||||
},
|
||||
"EasyUse_Hotkeys_JumpNearestNodes": {
|
||||
"name": "Enable Up/Down/Left/Right to jump to the nearest node"
|
||||
},
|
||||
"EasyUse_ContextMenu_SubDirectories": {
|
||||
"name": "Enable automatic nesting of subdirectories in the context menu"
|
||||
},
|
||||
"EasyUse_ContextMenu_ModelsThumbnails": {
|
||||
"name": "Enable model preview thumbnails"
|
||||
},
|
||||
"EasyUse_ContextMenu_NodesSort": {
|
||||
"name": "Enable A~Z sorting of new nodes in the context menu"
|
||||
},
|
||||
"EasyUse_ContextMenu_QuickOptions": {
|
||||
"name": "Use three quick buttons in the context menu",
|
||||
"options": {
|
||||
"At the forefront": "At the forefront",
|
||||
"At the end": "At the end",
|
||||
"Disable": "Disable"
|
||||
}
|
||||
},
|
||||
"EasyUse_Nodes_Runtime": {
|
||||
"name": "Enable node runtime display"
|
||||
},
|
||||
"EasyUse_Nodes_ChainGetSet": {
|
||||
"name": "Enable chaining of get and set points with the parent node"
|
||||
},
|
||||
"EasyUse_NodesMap_Sorting": {
|
||||
"name": "Manage nodes group sorting mode",
|
||||
"tooltip": "Automatically sort by default. If set to manual, groups can be drag and dropped and the order will be saved.",
|
||||
"options": {
|
||||
"Auto sorting": "Auto sorting",
|
||||
"Manual drag&drop sorting": "Manual drag&drop sorting"
|
||||
}
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayNodeID": {
|
||||
"name": "Enable node ID display"
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayGroupOnly": {
|
||||
"name": "Show groups only"
|
||||
},
|
||||
"EasyUse_NodesMap_Enable": {
|
||||
"name": "Enable Group Map",
|
||||
"tooltip": "You need to refresh the page to update successfully"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"settingsCategories": {
|
||||
"Hotkeys": "Raccourcis",
|
||||
"Nodes": "Nœuds",
|
||||
"NodesMap": "Carte des nœuds"
|
||||
},
|
||||
"nodeCategories": {
|
||||
"Util": "Utilitaire",
|
||||
"Seed": "Graine",
|
||||
"Prompt": "Prompt",
|
||||
"Loaders": "Chargeurs",
|
||||
"Adapter": "Adaptateur",
|
||||
"Inpaint": "Retouche",
|
||||
"PreSampling": "Pré-échantillonnage",
|
||||
"Sampler": "Échantillonneur",
|
||||
"Fix": "Correction",
|
||||
"Pipe": "Pipeline",
|
||||
"XY Inputs": "Entrées XY",
|
||||
"Image": "Image",
|
||||
"Segmentation": "Segmentation",
|
||||
"\uD83D\uDEAB Deprecated": "\uD83D\uDEAB Obsolète",
|
||||
"Type": "Type",
|
||||
"Math": "Mathématiques",
|
||||
"Switch": "Interrupteur",
|
||||
"Index Switch": "Interrupteur d'index",
|
||||
"While Loop": "Boucle While",
|
||||
"For Loop": "Boucle For",
|
||||
"LoadImage": "Charger l'image"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"EasyUse_Hotkeys_AddGroup": {
|
||||
"name": "Activer Shift+g pour ajouter les nœuds sélectionnés à un groupe",
|
||||
"tooltip": "Depuis la v1.2.39, vous pouvez utiliser Ctrl+g à la place"
|
||||
},
|
||||
"EasyUse_Hotkeys_cleanVRAMUsed": {
|
||||
"name": "Activer Shift+r pour décharger le cache du modèle et des nœuds"
|
||||
},
|
||||
"EasyUse_Hotkeys_toggleNodesMap": {
|
||||
"name": "Activer Shift+m pour basculer la carte des nœuds"
|
||||
},
|
||||
"EasyUse_Hotkeys_AlignSelectedNodes": {
|
||||
"name": "Activer Shift+Up/Down/Left/Right et Shift+Ctrl+Alt+Left/Right pour aligner les nœuds sélectionnés",
|
||||
"tooltip": "Shift+Up/Down/Left/Right peut aligner les nœuds sélectionnés, Shift+Ctrl+Alt+Left/Right peut les répartir horizontalement/verticalement"
|
||||
},
|
||||
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
|
||||
"name": "Activer Shift+Ctrl+Left/Right pour normaliser les nœuds sélectionnés",
|
||||
"tooltip": "Activer Shift+Ctrl+Left pour normaliser la largeur et Shift+Ctrl+Right pour normaliser la hauteur"
|
||||
},
|
||||
"EasyUse_Hotkeys_NodesTemplate": {
|
||||
"name": "Activer Alt+1~9 pour coller les modèles de nœuds dans le workflow"
|
||||
},
|
||||
"EasyUse_Hotkeys_JumpNearestNodes": {
|
||||
"name": "Activer Up/Down/Left/Right pour passer au nœud le plus proche"
|
||||
},
|
||||
"EasyUse_ContextMenu_SubDirectories": {
|
||||
"name": "Activer l'imbrication automatique des sous-répertoires dans le menu contextuel"
|
||||
},
|
||||
"EasyUse_ContextMenu_ModelsThumbnails": {
|
||||
"name": "Activer les vignettes d'aperçu du modèle"
|
||||
},
|
||||
"EasyUse_ContextMenu_NodesSort": {
|
||||
"name": "Activer le tri A~Z des nouveaux nœuds dans le menu contextuel"
|
||||
},
|
||||
"EasyUse_ContextMenu_QuickOptions": {
|
||||
"name": "Utiliser trois boutons rapides dans le menu contextuel",
|
||||
"options": {
|
||||
"At the forefront": "À l'avant-plan",
|
||||
"At the end": "À la fin",
|
||||
"Disable": "Désactiver"
|
||||
}
|
||||
},
|
||||
"EasyUse_Nodes_Runtime": {
|
||||
"name": "Activer l'affichage du temps d'exécution des nœuds"
|
||||
},
|
||||
"EasyUse_Nodes_ChainGetSet": {
|
||||
"name": "Activer le chaînage des points get et set avec le nœud parent"
|
||||
},
|
||||
"EasyUse_NodesMap_Sorting": {
|
||||
"name": "Gérer le mode de tri des groupes de nœuds",
|
||||
"tooltip": "Tri automatique par défaut. Si défini sur manuel, les groupes peuvent être glissés-déposés et l'ordre sera sauvegardé.",
|
||||
"options": {
|
||||
"Auto sorting": "Tri automatique",
|
||||
"Manual drag&drop sorting": "Tri manuel par glisser-déposer"
|
||||
}
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayNodeID": {
|
||||
"name": "Activer l'affichage de l'ID du nœud"
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayGroupOnly": {
|
||||
"name": "Afficher uniquement les groupes"
|
||||
},
|
||||
"EasyUse_NodesMap_Enable": {
|
||||
"name": "Activer la carte des groupes",
|
||||
"tooltip": "Vous devez actualiser la page pour mettre à jour"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"settingsCategories": {
|
||||
"Hotkeys": "ショートカットキー",
|
||||
"Nodes": "ノード",
|
||||
"NodesMap": "ノードマップ"
|
||||
},
|
||||
"nodeCategories": {
|
||||
"Util": "ユーティリティ",
|
||||
"Seed": "シード",
|
||||
"Prompt": "プロンプト",
|
||||
"Loaders": "ローダー",
|
||||
"Adapter": "アダプター",
|
||||
"Inpaint": "インペイント",
|
||||
"PreSampling": "プリサンプリング",
|
||||
"Sampler": "サンプラー",
|
||||
"Fix": "フィックス",
|
||||
"Pipe": "パイプ",
|
||||
"XY Inputs": "XY入力",
|
||||
"Image": "画像",
|
||||
"Segmentation": "セグメンテーション",
|
||||
"\uD83D\uDEAB Deprecated": "🚫 非推奨",
|
||||
"Type": "タイプ",
|
||||
"Math": "数学",
|
||||
"Switch": "スイッチ",
|
||||
"Index Switch": "インデックススイッチ",
|
||||
"While Loop": "Whileループ",
|
||||
"For Loop": "Forループ",
|
||||
"LoadImage": "画像読み込み"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"EasyUse_Hotkeys_AddGroup": {
|
||||
"name": "Shift+gを使用して選択したノードをグループに追加する",
|
||||
"tooltip": "v1.2.39以降、Ctrl+gが使用できます"
|
||||
},
|
||||
"EasyUse_Hotkeys_cleanVRAMUsed": {
|
||||
"name": "Shift+rを使用してモデルおよびノードキャッシュをアンロードする"
|
||||
},
|
||||
"EasyUse_Hotkeys_toggleNodesMap": {
|
||||
"name": "Shift+mを使用してノードマップを表示/非表示にします"
|
||||
},
|
||||
"EasyUse_Hotkeys_AlignSelectedNodes": {
|
||||
"name": "Shift+上/下/左/右およびShift+Ctrl+Alt+左/右を使用して選択したノードを整列する",
|
||||
"tooltip": "Shift+上/下/左/右で選択したノードを整列し、Shift+Ctrl+Alt+左/右で水平方向/垂直方向に分布させる"
|
||||
},
|
||||
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
|
||||
"name": "Shift+Ctrl+左/右を使用して選択したノードのサイズを正規化する",
|
||||
"tooltip": "Shift+Ctrl+左で幅を、Shift+Ctrl+右で高さを正規化する"
|
||||
},
|
||||
"EasyUse_Hotkeys_NodesTemplate": {
|
||||
"name": "Alt+1~9を使用してワークフローにノードテンプレートを貼り付ける"
|
||||
},
|
||||
"EasyUse_Hotkeys_JumpNearestNodes": {
|
||||
"name": "上/下/左/右を使用して最も近いノードにジャンプする"
|
||||
},
|
||||
"EasyUse_ContextMenu_SubDirectories": {
|
||||
"name": "コンテキストメニューでサブディレクトリを自動でネストする"
|
||||
},
|
||||
"EasyUse_ContextMenu_ModelsThumbnails": {
|
||||
"name": "モデルプレビューサムネイルを有効にする"
|
||||
},
|
||||
"EasyUse_ContextMenu_NodesSort": {
|
||||
"name": "コンテキストメニューで新規ノードをA~Z順に並べ替える"
|
||||
},
|
||||
"EasyUse_ContextMenu_QuickOptions": {
|
||||
"name": "コンテキストメニューで3つのクイックボタンを使用する",
|
||||
"options": {
|
||||
"At the forefront": "最前面に",
|
||||
"At the end": "最後に",
|
||||
"Disable": "無効"
|
||||
}
|
||||
},
|
||||
"EasyUse_Nodes_Runtime": {
|
||||
"name": "ノードの実行時間表示を有効にする"
|
||||
},
|
||||
"EasyUse_Nodes_ChainGetSet": {
|
||||
"name": "親ノードと取得/設定ポイントを連結することを有効にする"
|
||||
},
|
||||
"EasyUse_NodesMap_Sorting": {
|
||||
"name": "ノードグループの並べ替えモードを管理する",
|
||||
"tooltip": "デフォルトで自動的に並べ替えます。マニュアルに設定した場合、グループをドラッグアンドドロップで並べ替え、順序が保存されます。",
|
||||
"options": {
|
||||
"Auto sorting": "自動並べ替え",
|
||||
"Manual drag&drop sorting": "手動ドラッグアンドドロップによる並べ替え"
|
||||
}
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayNodeID": {
|
||||
"name": "ノードIDの表示を有効にする"
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayGroupOnly": {
|
||||
"name": "グループのみ表示する"
|
||||
},
|
||||
"EasyUse_NodesMap_Enable": {
|
||||
"name": "グループマップを有効にする",
|
||||
"tooltip": "ページを更新する必要があります"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"settingsCategories": {
|
||||
"Hotkeys": "단축키",
|
||||
"Nodes": "노드",
|
||||
"NodesMap": "노드 맵"
|
||||
},
|
||||
"nodeCategories": {
|
||||
"Util": "유틸",
|
||||
"Seed": "시드",
|
||||
"Prompt": "프롬프트",
|
||||
"Loaders": "로더",
|
||||
"Adapter": "어댑터",
|
||||
"Inpaint": "인페인트",
|
||||
"PreSampling": "사전 샘플링",
|
||||
"Sampler": "샘플러",
|
||||
"Fix": "픽스",
|
||||
"Pipe": "파이프",
|
||||
"XY Inputs": "XY 입력",
|
||||
"Image": "이미지",
|
||||
"Segmentation": "분할",
|
||||
"\uD83D\uDEAB Deprecated": "\uD83D\uDEAB 사용 중단",
|
||||
"Type": "유형",
|
||||
"Math": "수학",
|
||||
"Switch": "스위치",
|
||||
"Index Switch": "인덱스 스위치",
|
||||
"While Loop": "while 루프",
|
||||
"For Loop": "for 루프",
|
||||
"LoadImage": "이미지 로드"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"EasyUse_Hotkeys_AddGroup": {
|
||||
"name": "Shift+g 를 사용하여 선택된 노드를 그룹에 추가합니다",
|
||||
"tooltip": "v1.2.39부터는 Ctrl+g 를 사용할 수 있습니다"
|
||||
},
|
||||
"EasyUse_Hotkeys_cleanVRAMUsed": {
|
||||
"name": "Shift+r 를 사용하여 모델 및 노드 캐시를 언로드합니다"
|
||||
},
|
||||
"EasyUse_Hotkeys_toggleNodesMap": {
|
||||
"name": "Shift+m 를 사용하여 노드 맵을 전환합니다"
|
||||
},
|
||||
"EasyUse_Hotkeys_AlignSelectedNodes": {
|
||||
"name": "Shift+Up/Down/Left/Right 와 Shift+Ctrl+Alt+Left/Right 를 사용하여 선택된 노드를 정렬합니다",
|
||||
"tooltip": "Shift+Up/Down/Left/Right 는 선택된 노드를 정렬하며, Shift+Ctrl+Alt+Left/Right 는 노드를 수평/수직으로 분배합니다"
|
||||
},
|
||||
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
|
||||
"name": "Shift+Ctrl+Left/Right 를 사용하여 선택된 노드를 정규화합니다",
|
||||
"tooltip": "Shift+Ctrl+Left 는 너비를, Shift+Ctrl+Right 는 높이를 정규화합니다"
|
||||
},
|
||||
"EasyUse_Hotkeys_NodesTemplate": {
|
||||
"name": "Alt+1~9 를 사용하여 워크플로우에 노드 템플릿을 붙여넣습니다"
|
||||
},
|
||||
"EasyUse_Hotkeys_JumpNearestNodes": {
|
||||
"name": "Up/Down/Left/Right 를 사용하여 가장 가까운 노드로 이동합니다"
|
||||
},
|
||||
"EasyUse_ContextMenu_SubDirectories": {
|
||||
"name": "컨텍스트 메뉴에서 자동으로 하위 디렉토리를 중첩합니다"
|
||||
},
|
||||
"EasyUse_ContextMenu_ModelsThumbnails": {
|
||||
"name": "모델 미리보기 썸네일을 활성화합니다"
|
||||
},
|
||||
"EasyUse_ContextMenu_NodesSort": {
|
||||
"name": "컨텍스트 메뉴에서 새로운 노드를 A~Z 순으로 정렬합니다"
|
||||
},
|
||||
"EasyUse_ContextMenu_QuickOptions": {
|
||||
"name": "컨텍스트 메뉴에 3개의 빠른 옵션 버튼을 사용합니다",
|
||||
"options": {
|
||||
"At the forefront": "앞쪽에",
|
||||
"At the end": "뒤쪽에",
|
||||
"Disable": "비활성화"
|
||||
}
|
||||
},
|
||||
"EasyUse_Nodes_Runtime": {
|
||||
"name": "노드 실행 시간 표시를 활성화합니다"
|
||||
},
|
||||
"EasyUse_Nodes_ChainGetSet": {
|
||||
"name": "부모 노드와 연결된 get/ set 포인트 체이닝을 활성화합니다"
|
||||
},
|
||||
"EasyUse_NodesMap_Sorting": {
|
||||
"name": "노드 그룹 정렬 모드를 관리합니다",
|
||||
"tooltip": "기본값은 자동 정렬입니다. 수동으로 설정하면 그룹을 드래그 앤 드롭할 수 있으며 순서가 저장됩니다.",
|
||||
"options": {
|
||||
"Auto sorting": "자동 정렬",
|
||||
"Manual drag&drop sorting": "수동 드래그 앤 드롭 정렬"
|
||||
}
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayNodeID": {
|
||||
"name": "노드 ID 표시를 활성화합니다"
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayGroupOnly": {
|
||||
"name": "그룹만 표시합니다"
|
||||
},
|
||||
"EasyUse_NodesMap_Enable": {
|
||||
"name": "그룹 맵을 활성화합니다",
|
||||
"tooltip": "업데이트를 위해 페이지를 새로고침해야 합니다"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"settingsCategories": {
|
||||
"Hotkeys": "Горячие клавиши",
|
||||
"Nodes": "Узлы",
|
||||
"NodesMap": "Карта узлов"
|
||||
},
|
||||
"nodeCategories": {
|
||||
"Util": "Утилиты",
|
||||
"Seed": "Сид",
|
||||
"Prompt": "Подсказка",
|
||||
"Loaders": "Загрузчики",
|
||||
"Adapter": "Адаптер",
|
||||
"Inpaint": "Ретушь",
|
||||
"PreSampling": "Предвыборка",
|
||||
"Sampler": "Сэмплер",
|
||||
"Fix": "Исправление",
|
||||
"Pipe": "Конвейер",
|
||||
"XY Inputs": "Ввод XY",
|
||||
"Image": "Изображение",
|
||||
"Segmentation": "Сегментация",
|
||||
"\uD83D\uDEAB Deprecated": "\uD83D\uDEAB Устарело",
|
||||
"Type": "Тип",
|
||||
"Math": "Математика",
|
||||
"Switch": "Переключатель",
|
||||
"Index Switch": "Переключатель индексов",
|
||||
"While Loop": "Цикл while",
|
||||
"For Loop": "Цикл for",
|
||||
"LoadImage": "Загрузка изображения"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"EasyUse_Hotkeys_AddGroup": {
|
||||
"name": "Включить Shift+g для добавления выделенных узлов в группу",
|
||||
"tooltip": "Начиная с версии v1.2.39, можно использовать Ctrl+g"
|
||||
},
|
||||
"EasyUse_Hotkeys_cleanVRAMUsed": {
|
||||
"name": "Включить Shift+r для выгрузки модели и кэша узлов"
|
||||
},
|
||||
"EasyUse_Hotkeys_toggleNodesMap": {
|
||||
"name": "Включить Shift+m для переключения карты узлов"
|
||||
},
|
||||
"EasyUse_Hotkeys_AlignSelectedNodes": {
|
||||
"name": "Включить Shift+Стрелки для выравнивания выделенных узлов и Shift+Ctrl+Alt+Стрелки для распределения узлов по горизонтали/вертикали",
|
||||
"tooltip": "Shift+Стрелки выравнивают выделенные узлы, Shift+Ctrl+Alt+Стрелки распределяют узлы по горизонтали/вертикали"
|
||||
},
|
||||
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
|
||||
"name": "Включить Shift+Ctrl+Стрелки для нормализации выделенных узлов",
|
||||
"tooltip": "Включить Shift+Ctrl+Лево для нормализации ширины и Shift+Ctrl+Право для нормализации высоты"
|
||||
},
|
||||
"EasyUse_Hotkeys_NodesTemplate": {
|
||||
"name": "Включить Alt+1~9 для вставки шаблонов узлов в рабочий процесс"
|
||||
},
|
||||
"EasyUse_Hotkeys_JumpNearestNodes": {
|
||||
"name": "Включить Стрелки для перехода к ближайшему узлу"
|
||||
},
|
||||
"EasyUse_ContextMenu_SubDirectories": {
|
||||
"name": "Включить автоматическое вложение подкаталогов в контекстном меню"
|
||||
},
|
||||
"EasyUse_ContextMenu_ModelsThumbnails": {
|
||||
"name": "Включить превью миниатюр моделей"
|
||||
},
|
||||
"EasyUse_ContextMenu_NodesSort": {
|
||||
"name": "Включить A~Z сортировку новых узлов в контекстном меню"
|
||||
},
|
||||
"EasyUse_ContextMenu_QuickOptions": {
|
||||
"name": "Использовать три быстрых кнопки в контекстном меню",
|
||||
"options": {
|
||||
"At the forefront": "В начале",
|
||||
"At the end": "В конце",
|
||||
"Disable": "Отключено"
|
||||
}
|
||||
},
|
||||
"EasyUse_Nodes_Runtime": {
|
||||
"name": "Включить отображение времени выполнения узлов"
|
||||
},
|
||||
"EasyUse_Nodes_ChainGetSet": {
|
||||
"name": "Включить связывание точек получения и установки с родительским узлом"
|
||||
},
|
||||
"EasyUse_NodesMap_Sorting": {
|
||||
"name": "Управление режимом сортировки групп узлов",
|
||||
"tooltip": "По умолчанию автоматическая сортировка. При ручном режиме группы можно перемещать методом перетаскивания, и порядок будет сохранён.",
|
||||
"options": {
|
||||
"Auto sorting": "Автоматическая сортировка",
|
||||
"Manual drag&drop sorting": "Ручная сортировка перетаскиванием"
|
||||
}
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayNodeID": {
|
||||
"name": "Включить отображение ID узлов"
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayGroupOnly": {
|
||||
"name": "Показывать только группы"
|
||||
},
|
||||
"EasyUse_NodesMap_Enable": {
|
||||
"name": "Включить карту групп",
|
||||
"tooltip": "Необходимо обновить страницу для успешного обновления"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
{
|
||||
"settingsCategories": {
|
||||
"Hotkeys": "快捷键",
|
||||
"Nodes": "节点相关",
|
||||
"NodesMap": "管理节点组",
|
||||
"StylesSelector": "样式选择器"
|
||||
},
|
||||
"nodeCategories": {
|
||||
"Util": "工具",
|
||||
"Seed": "随机种",
|
||||
|
||||
+257
-168
@@ -75,6 +75,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy wildcardsMatrix": {
|
||||
"display_name": "通配符提示词矩阵",
|
||||
"inputs": {
|
||||
"Select to add LoRA": {
|
||||
"name": "选择添加Lora"
|
||||
},
|
||||
"Select to add Wildcard": {
|
||||
"name": "选择添加通配符"
|
||||
},
|
||||
"offset": {
|
||||
"name": "偏移量"
|
||||
},
|
||||
"output_limit": {
|
||||
"name": "输出个数限制",
|
||||
"tooltip": "输出n个填充后通配符, -1为输出所有可能性(偏移值失效),默认值为1"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "通配填充词"
|
||||
},
|
||||
"1": {
|
||||
"name": "可能性总数"
|
||||
},
|
||||
"2": {
|
||||
"name": "可能性总数(每通配符)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy prompt": {
|
||||
"display_name": "提示词",
|
||||
"inputs": {
|
||||
@@ -136,6 +165,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy promptAwait": {
|
||||
"display_name": "提示词等待",
|
||||
"inputs": {
|
||||
"now": {
|
||||
"name": "当前"
|
||||
},
|
||||
"prev": {
|
||||
"name": "上一步"
|
||||
},
|
||||
"prompt": {
|
||||
"name": "提示词",
|
||||
"placeholder": "输入提示词或使用语音输入转文字"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "输出"
|
||||
},
|
||||
"1": {
|
||||
"name": "提示词"
|
||||
},
|
||||
"2": {
|
||||
"name": "继续"
|
||||
},
|
||||
"3": {
|
||||
"name": "随机种"
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy promptConcat": {
|
||||
"display_name": "提示词联结",
|
||||
"inputs": {
|
||||
@@ -360,7 +418,7 @@
|
||||
"name": "VAE名称"
|
||||
},
|
||||
"clip_skip": {
|
||||
"name": "Clip停止层"
|
||||
"name": "CLIP停止层"
|
||||
},
|
||||
"lora_name": {
|
||||
"name": "LoRA名称"
|
||||
@@ -412,11 +470,20 @@
|
||||
"1": {
|
||||
"name": "模型"
|
||||
},
|
||||
"2": {
|
||||
"name": "VAE"
|
||||
},
|
||||
"3": {
|
||||
"name": "CLIP"
|
||||
},
|
||||
"4": {
|
||||
"name": "正面条件"
|
||||
},
|
||||
"5": {
|
||||
"name": "负面条件"
|
||||
},
|
||||
"6": {
|
||||
"name": "LATENT"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -436,7 +503,7 @@
|
||||
"name": "VAE"
|
||||
},
|
||||
"clip_skip": {
|
||||
"name": "Clip停止层"
|
||||
"name": "CLIP停止层"
|
||||
},
|
||||
"lora_name": {
|
||||
"name": "LoRA模型"
|
||||
@@ -475,6 +542,9 @@
|
||||
},
|
||||
"1": {
|
||||
"name": "模型"
|
||||
},
|
||||
"2": {
|
||||
"name": "VAE"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -494,7 +564,7 @@
|
||||
"name": "VAE"
|
||||
},
|
||||
"clip_skip": {
|
||||
"name": "Clip停止层"
|
||||
"name": "CLIP停止层"
|
||||
},
|
||||
"lora_name": {
|
||||
"name": "LoRA模型"
|
||||
@@ -1084,6 +1154,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy loraSwitcher": {
|
||||
"display_name": "简易Lora切换器",
|
||||
"inputs": {
|
||||
"optional_lora_stack": {
|
||||
"name": "LoRA堆(可选)"
|
||||
},
|
||||
"toggle": {
|
||||
"name": "开关"
|
||||
},
|
||||
"select": {
|
||||
"name": "选择项"
|
||||
},
|
||||
"num_loras": {
|
||||
"name": "LoRA数量"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "LoRA堆"
|
||||
},
|
||||
"1": {
|
||||
"name": "LoRA名称"
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy controlnetStack": {
|
||||
"display_name": "简易 ControlNet 堆",
|
||||
"inputs": {
|
||||
@@ -1574,7 +1669,7 @@
|
||||
}
|
||||
},
|
||||
"easy latentNoisy": {
|
||||
"display_name": "噪波Latent(Sigma乘积)",
|
||||
"display_name": "噪波LATENT(Sigma乘积)",
|
||||
"inputs": {
|
||||
"pipe": {
|
||||
"name": "节点束"
|
||||
@@ -1583,7 +1678,7 @@
|
||||
"name": "模型(可选)"
|
||||
},
|
||||
"optional_latent": {
|
||||
"name": "Latent(可选)"
|
||||
"name": "LATENT(可选)"
|
||||
},
|
||||
"sample_name": {
|
||||
"name": "采样器"
|
||||
@@ -1618,7 +1713,7 @@
|
||||
"name": "节点束"
|
||||
},
|
||||
"1": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"2": {
|
||||
"name": "伽马值"
|
||||
@@ -1635,7 +1730,7 @@
|
||||
"name": "文字拼接"
|
||||
},
|
||||
"source_latent": {
|
||||
"name": "Latent(源)"
|
||||
"name": "LATENT(源)"
|
||||
},
|
||||
"source_mask": {
|
||||
"name": "遮罩(源)"
|
||||
@@ -1655,7 +1750,7 @@
|
||||
"name": "节点束"
|
||||
},
|
||||
"1": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"2": {
|
||||
"name": "条件"
|
||||
@@ -1672,7 +1767,7 @@
|
||||
"name": "图像转Latent"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"noise": {
|
||||
"name": "噪波"
|
||||
@@ -1695,7 +1790,7 @@
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1715,6 +1810,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy seedList": {
|
||||
"display_name": "随机种列表",
|
||||
"description": "可用于for循环的随机数种子列表,通过与easy forLoopStart节点的索引与easy indexAny节点相连接可实现在循环中使用不同种子值进行采样",
|
||||
"inputs": {
|
||||
"min_num": {
|
||||
"name": "最小值"
|
||||
},
|
||||
"max_num": {
|
||||
"name": "最大值"
|
||||
},
|
||||
"method": {
|
||||
"name": "生成方式"
|
||||
},
|
||||
"total": {
|
||||
"name": "总量"
|
||||
},
|
||||
"seed": {
|
||||
"name": "列表序号"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "随机种"
|
||||
},
|
||||
"1": {
|
||||
"name": "总量"
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy globalSeed": {
|
||||
"display_name": "全局随机种",
|
||||
"inputs": {
|
||||
@@ -1794,7 +1918,7 @@
|
||||
"name": "图像(可选)"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent(可选)"
|
||||
"name": "LATENT(可选)"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
@@ -1849,7 +1973,7 @@
|
||||
"name": "图像(可选)"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent(可选)"
|
||||
"name": "LATENT(可选)"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
@@ -1865,7 +1989,7 @@
|
||||
"name": "节点束"
|
||||
},
|
||||
"optional_latent": {
|
||||
"name": "Latent(可选)"
|
||||
"name": "LATENT(可选)"
|
||||
},
|
||||
"optional_noise_seed": {
|
||||
"name": "随机种(可选)"
|
||||
@@ -1914,7 +2038,7 @@
|
||||
"name": "图像转Latent"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"optional_sampler": {
|
||||
"name": "采样器(可选)"
|
||||
@@ -1993,7 +2117,7 @@
|
||||
"name": "图像(可选)"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent(可选)"
|
||||
"name": "LATENT(可选)"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
@@ -2232,7 +2356,7 @@
|
||||
"name": "图像(可选)"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent(可选)"
|
||||
"name": "LATENT(可选)"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
@@ -2260,7 +2384,7 @@
|
||||
"name": "图像(可选)"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent(可选)"
|
||||
"name": "LATENT(可选)"
|
||||
},
|
||||
"steps": {
|
||||
"name": "步数"
|
||||
@@ -2313,13 +2437,16 @@
|
||||
"name": "负面条件"
|
||||
},
|
||||
"5": {
|
||||
"name": "图像转Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"6": {
|
||||
"name": "Latent"
|
||||
"name": "VAE"
|
||||
},
|
||||
"7": {
|
||||
"name": "随机种"
|
||||
"name": "CLIP"
|
||||
},
|
||||
"8": {
|
||||
"name": "seed"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2594,7 +2721,7 @@
|
||||
"name": "负面条件(可选)"
|
||||
},
|
||||
"optional_latent": {
|
||||
"name": "Latent(可选)"
|
||||
"name": "LATENT(可选)"
|
||||
},
|
||||
"steps": {
|
||||
"name": "步数"
|
||||
@@ -2620,7 +2747,7 @@
|
||||
"name": "节点束"
|
||||
},
|
||||
"1": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2643,7 +2770,7 @@
|
||||
"name": "图像转Latent"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"image": {
|
||||
"name": "图像"
|
||||
@@ -2679,7 +2806,7 @@
|
||||
"name": "图像转Latent"
|
||||
},
|
||||
"5": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"6": {
|
||||
"name": "图像"
|
||||
@@ -2739,17 +2866,17 @@
|
||||
"neg": {
|
||||
"name": "负面条件"
|
||||
},
|
||||
"vae": {
|
||||
"VAE": {
|
||||
"name": "VAE"
|
||||
},
|
||||
"clip": {
|
||||
"CLIP": {
|
||||
"name": "CLIP"
|
||||
},
|
||||
"image_to_latent": {
|
||||
"name": "图像转Latent"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"image": {
|
||||
"name": "图像"
|
||||
@@ -2811,7 +2938,7 @@
|
||||
"name": "图像转Latent"
|
||||
},
|
||||
"9": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"10": {
|
||||
"name": "图像"
|
||||
@@ -3097,34 +3224,34 @@
|
||||
"name": "模型名称10"
|
||||
},
|
||||
"clip_skip_1": {
|
||||
"name": "Clip停止层1"
|
||||
"name": "CLIP停止层1"
|
||||
},
|
||||
"clip_skip_2": {
|
||||
"name": "Clip停止层2"
|
||||
"name": "CLIP停止层2"
|
||||
},
|
||||
"clip_skip_3": {
|
||||
"name": "Clip停止层3"
|
||||
"name": "CLIP停止层3"
|
||||
},
|
||||
"clip_skip_4": {
|
||||
"name": "Clip停止层4"
|
||||
"name": "CLIP停止层4"
|
||||
},
|
||||
"clip_skip_5": {
|
||||
"name": "Clip停止层5"
|
||||
"name": "CLIP停止层5"
|
||||
},
|
||||
"clip_skip_6": {
|
||||
"name": "Clip停止层6"
|
||||
"name": "CLIP停止层6"
|
||||
},
|
||||
"clip_skip_7": {
|
||||
"name": "Clip停止层7"
|
||||
"name": "CLIP停止层7"
|
||||
},
|
||||
"clip_skip_8": {
|
||||
"name": "Clip停止层8"
|
||||
"name": "CLIP停止层8"
|
||||
},
|
||||
"clip_skip_9": {
|
||||
"name": "Clip停止层9"
|
||||
"name": "CLIP停止层9"
|
||||
},
|
||||
"clip_skip_10": {
|
||||
"name": "Clip停止层10"
|
||||
"name": "CLIP停止层10"
|
||||
},
|
||||
"vae_name_1": {
|
||||
"name": "VAE名称1"
|
||||
@@ -3847,7 +3974,7 @@
|
||||
"name": "图像"
|
||||
},
|
||||
"2": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4017,10 +4144,10 @@
|
||||
"model": {
|
||||
"name": "模型"
|
||||
},
|
||||
"clip": {
|
||||
"CLIP": {
|
||||
"name": "CLIP"
|
||||
},
|
||||
"vae": {
|
||||
"VAE": {
|
||||
"name": "VAE"
|
||||
},
|
||||
"positive": {
|
||||
@@ -4168,6 +4295,12 @@
|
||||
},
|
||||
"save_prefix": {
|
||||
"name": "保存前缀"
|
||||
},
|
||||
"add_background": {
|
||||
"name": "添加背景"
|
||||
},
|
||||
"refine_foreground": {
|
||||
"name": "优化前景"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
@@ -4195,6 +4328,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy loraPromptApply":{
|
||||
"display_name": "应用提示词LoRA",
|
||||
"inputs": {
|
||||
"model": {
|
||||
"name": "模型"
|
||||
},
|
||||
"clip": {
|
||||
"name": "CLIP"
|
||||
},
|
||||
"positive":{
|
||||
"name": "正面提示词"
|
||||
},
|
||||
"negative":{
|
||||
"name": "负面提示词"
|
||||
}
|
||||
},
|
||||
"outputs":{
|
||||
"0": {
|
||||
"name": "模型"
|
||||
},
|
||||
"1": {
|
||||
"name": "CLIP"
|
||||
},
|
||||
"2": {
|
||||
"name": "正面提示词"
|
||||
},
|
||||
"3": {
|
||||
"name": "负面提示词"
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy loraStackApply": {
|
||||
"display_name": "应用LoRA堆",
|
||||
"inputs": {
|
||||
@@ -4922,7 +5086,7 @@
|
||||
"name": "模型"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"mode": {
|
||||
"name": "模式"
|
||||
@@ -5511,6 +5675,12 @@
|
||||
},
|
||||
"image_output": {
|
||||
"name": "图像输出"
|
||||
},
|
||||
"add_background": {
|
||||
"name": "添加背景"
|
||||
},
|
||||
"refine_foreground": {
|
||||
"name": "优化前景"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
@@ -5664,6 +5834,9 @@
|
||||
},
|
||||
"pixels": {
|
||||
"name": "限制像素"
|
||||
},
|
||||
"method": {
|
||||
"name": "限制方式"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
@@ -5729,7 +5902,7 @@
|
||||
"name": "模型"
|
||||
},
|
||||
"latent": {
|
||||
"name": "Latent"
|
||||
"name": "LATENT"
|
||||
},
|
||||
"head": {
|
||||
"name": "head"
|
||||
@@ -6025,73 +6198,13 @@
|
||||
"easy whileLoopStart": {
|
||||
"display_name": "While循环-开始",
|
||||
"inputs": {
|
||||
"initial_value0": {
|
||||
"name": "初始值0"
|
||||
},
|
||||
"initial_value1": {
|
||||
"name": "初始值1"
|
||||
},
|
||||
"initial_value2": {
|
||||
"name": "初始值2"
|
||||
},
|
||||
"initial_value3": {
|
||||
"name": "初始值3"
|
||||
},
|
||||
"initial_value4": {
|
||||
"name": "初始值4"
|
||||
},
|
||||
"initial_value5": {
|
||||
"name": "初始值5"
|
||||
},
|
||||
"initial_value6": {
|
||||
"name": "初始值6"
|
||||
},
|
||||
"initial_value7": {
|
||||
"name": "初始值7"
|
||||
},
|
||||
"initial_value8": {
|
||||
"name": "初始值8"
|
||||
},
|
||||
"initial_value9": {
|
||||
"name": "初始值9"
|
||||
},
|
||||
"condition": {
|
||||
"name": "条件"
|
||||
"name": "开始循环"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "开始"
|
||||
},
|
||||
"1": {
|
||||
"name": "值0"
|
||||
},
|
||||
"2": {
|
||||
"name": "值1"
|
||||
},
|
||||
"3": {
|
||||
"name": "值2"
|
||||
},
|
||||
"4": {
|
||||
"name": "值3"
|
||||
},
|
||||
"5": {
|
||||
"name": "值4"
|
||||
},
|
||||
"6": {
|
||||
"name": "值5"
|
||||
},
|
||||
"7": {
|
||||
"name": "值6"
|
||||
},
|
||||
"8": {
|
||||
"name": "值7"
|
||||
},
|
||||
"9": {
|
||||
"name": "值8"
|
||||
},
|
||||
"10": {
|
||||
"name": "值9"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6101,71 +6214,11 @@
|
||||
"flow": {
|
||||
"name": "结束"
|
||||
},
|
||||
"initial_value0": {
|
||||
"name": "初始值0"
|
||||
},
|
||||
"initial_value1": {
|
||||
"name": "初始值1"
|
||||
},
|
||||
"initial_value2": {
|
||||
"name": "初始值2"
|
||||
},
|
||||
"initial_value3": {
|
||||
"name": "初始值3"
|
||||
},
|
||||
"initial_value4": {
|
||||
"name": "初始值4"
|
||||
},
|
||||
"initial_value5": {
|
||||
"name": "初始值5"
|
||||
},
|
||||
"initial_value6": {
|
||||
"name": "初始值6"
|
||||
},
|
||||
"initial_value7": {
|
||||
"name": "初始值7"
|
||||
},
|
||||
"initial_value8": {
|
||||
"name": "初始值8"
|
||||
},
|
||||
"initial_value9": {
|
||||
"name": "初始值9"
|
||||
},
|
||||
"condition": {
|
||||
"name": "条件"
|
||||
"name": "继续循环"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "值0"
|
||||
},
|
||||
"1": {
|
||||
"name": "值1"
|
||||
},
|
||||
"2": {
|
||||
"name": "值2"
|
||||
},
|
||||
"3": {
|
||||
"name": "值3"
|
||||
},
|
||||
"4": {
|
||||
"name": "值4"
|
||||
},
|
||||
"5": {
|
||||
"name": "值5"
|
||||
},
|
||||
"6": {
|
||||
"name": "值6"
|
||||
},
|
||||
"7": {
|
||||
"name": "值7"
|
||||
},
|
||||
"8": {
|
||||
"name": "值8"
|
||||
},
|
||||
"9": {
|
||||
"name": "值9"
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy forLoopStart": {
|
||||
@@ -6306,7 +6359,7 @@
|
||||
"name": "高度"
|
||||
},
|
||||
"scale": {
|
||||
"name": "缩放洗漱"
|
||||
"name": "缩放系数"
|
||||
},
|
||||
"flip_w/h": {
|
||||
"name": "翻转宽高"
|
||||
@@ -6511,6 +6564,11 @@
|
||||
"anything": {
|
||||
"name": "输入任何"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "输出"
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy showTensorShape": {
|
||||
@@ -6618,7 +6676,38 @@
|
||||
"name": "温度"
|
||||
},
|
||||
"max_tokens": {
|
||||
"name": "最大词令牌数"
|
||||
"name": "最大词元数"
|
||||
},
|
||||
"caption_type": {
|
||||
"name": "提示词类型"
|
||||
},
|
||||
"caption_length": {
|
||||
"name": "提示词长度"
|
||||
},
|
||||
"name_input": {
|
||||
"name": "名称输入"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "提示词"
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy joyCaption3API": {
|
||||
"display_name": "JoyCaption3(硅基流动)",
|
||||
"inputs": {
|
||||
"image": {
|
||||
"name": "图像"
|
||||
},
|
||||
"do_sample": {
|
||||
"name": "执行采样"
|
||||
},
|
||||
"temperature": {
|
||||
"name": "温度"
|
||||
},
|
||||
"max_tokens": {
|
||||
"name": "最大词元数"
|
||||
},
|
||||
"caption_type": {
|
||||
"name": "提示词类型"
|
||||
@@ -6644,4 +6733,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"EasyUse_Hotkeys_AddGroup": {
|
||||
"name": "启用 Shift+g 键将选中的节点添加一个组",
|
||||
"tooltip": "从v1.2.39开始,可以使用Ctrl+g代替"
|
||||
},
|
||||
"EasyUse_Hotkeys_cleanVRAMUsed": {
|
||||
"name": "启用 Shift+r 键卸载模型和节点缓存"
|
||||
},
|
||||
"EasyUse_Hotkeys_toggleNodesMap": {
|
||||
"name": "启用 Shift+m 键显隐管理节点组"
|
||||
},
|
||||
"EasyUse_Hotkeys_AlignSelectedNodes": {
|
||||
"name": "启用 Shift+上/下/左/右 和 Shift+Ctrl+Alt+左/右 键对齐选中的节点",
|
||||
"tooltip": "Shift+上/下/左/右 可以对齐选中的节点, Shift+Ctrl+Alt+左/右 可以水平/垂直分布节点"
|
||||
},
|
||||
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
|
||||
"name": "启用 Shift+Ctrl+左/右 键规范化选中的节点",
|
||||
"tooltip": "启用 Shift+Ctrl+左 键规范化宽度和 Shift+Ctrl+右 键规范化高度"
|
||||
},
|
||||
"EasyUse_Hotkeys_NodesTemplate": {
|
||||
"name": "启用 Alt+1~9 从节点模板粘贴到工作流中"
|
||||
},
|
||||
"EasyUse_Hotkeys_JumpNearestNodes": {
|
||||
"name": "启用 上/下/左/右 键跳转到最近的前后节点"
|
||||
},
|
||||
"EasyUse_ContextMenu_SubDirectories": {
|
||||
"name": "启用上下文菜单自动嵌套子目录"
|
||||
},
|
||||
"EasyUse_ContextMenu_ModelsThumbnails": {
|
||||
"name": "启动模型预览图显示"
|
||||
},
|
||||
"EasyUse_ContextMenu_NodesSort": {
|
||||
"name": "启用右键菜单中新建节点A~Z排序"
|
||||
},
|
||||
"EasyUse_ContextMenu_QuickOptions": {
|
||||
"name": "在右键菜单中使用三个快捷按钮",
|
||||
"options": {
|
||||
"At the forefront": "在最前面",
|
||||
"At the end": "在最后面",
|
||||
"Disable": "禁用"
|
||||
}
|
||||
},
|
||||
"EasyUse_Nodes_Runtime": {
|
||||
"name": "启动节点运行时间显示"
|
||||
},
|
||||
"EasyUse_Nodes_ChainGetSet": {
|
||||
"name": "启用将获取点和设置点与父节点链在一起"
|
||||
},
|
||||
"EasyUse_NodesMap_Sorting": {
|
||||
"name": "管理节点组排序模式",
|
||||
"tooltip": "默认自动排序,如果设置为手动,组可以拖放并保存排序结果。",
|
||||
"options": {
|
||||
"Auto sorting": "自动排序",
|
||||
"Manual drag&drop sorting": "手动拖拽排序"
|
||||
}
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayNodeID": {
|
||||
"name": "启用节点ID显示"
|
||||
},
|
||||
"EasyUse_NodesMap_DisplayGroupOnly": {
|
||||
"name": "仅显示组"
|
||||
},
|
||||
"EasyUse_NodesMap_Enable": {
|
||||
"name": "启用管理节点组",
|
||||
"tooltip": "您需要刷新页面以成功更新"
|
||||
},
|
||||
"EasyUse_StylesSelector_DisplayType": {
|
||||
"name": "样式选择器显示类型",
|
||||
"tooltip": "样式选择器显示类型,如果设置为“网格”,则显示为网格,如果设置为“列表”,则显示为列表",
|
||||
"options": {
|
||||
"Gird": "网格",
|
||||
"List": "列表"
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -195,7 +195,7 @@ REMBG_MODELS = {
|
||||
"model_url": "briaai/RMBG-2.0"
|
||||
},
|
||||
"BEN2": {
|
||||
"model_url": "https://huggingface.co/PramaLLC/BEN/resolve/main/BEN_Base.pth"
|
||||
"model_url": "https://huggingface.co/PramaLLC/BEN2/resolve/main/BEN2_Base.pth"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ IPADAPTER_CLIPVISION_MODELS = {
|
||||
"model_url": "https://huggingface.co/openai/clip-vit-large-patch14-336/resolve/main/pytorch_model.bin"
|
||||
},
|
||||
"clip-vit-h-14-laion2B-s32B-b79K":{
|
||||
"model_url": "https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_pytorch_model.safetensors"
|
||||
"model_url": "https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_model.safetensors"
|
||||
},
|
||||
"sigclip_vision_patch14_384":{
|
||||
"model_url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors"
|
||||
@@ -350,7 +350,7 @@ DYNAMICRAFTER_MODELS = {
|
||||
"model_url": "https://huggingface.co/ExponentialML/DynamiCrafterUNet/resolve/main/dynamicrafter_unet_512.safetensors",
|
||||
"vae_url": "https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors",
|
||||
"clip_url": "https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/text_encoder/model.safetensors",
|
||||
"clip_vision_url": "https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_pytorch_model.safetensors",
|
||||
"clip_vision_url": "https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_model.safetensors",
|
||||
},
|
||||
"dynamicrafter_unet_512_interp (2.98GB)": {
|
||||
"model_url": "https://huggingface.co/ExponentialML/DynamiCrafterUNet/resolve/main/dynamicrafter_unet_512_interp.safetensors"
|
||||
@@ -370,6 +370,15 @@ HUMANPARSING_MODELS = {
|
||||
},
|
||||
"human-parts":{
|
||||
"model_url":"https://huggingface.co/Metal3d/deeplabv3p-resnet50-human/resolve/main/deeplabv3p-resnet50-human.onnx",
|
||||
},
|
||||
"segformer_b3_clothes":{
|
||||
"model_name": "sayeed99/segformer_b3_clothes",
|
||||
},
|
||||
"segformer_b3_fashion":{
|
||||
"model_name": "sayeed99/segformer-b3-fashion",
|
||||
},
|
||||
"face_parsing":{
|
||||
"model_name": "jonathandinu/face-parsing"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,4 +404,4 @@ PROMPT_TEMPLATE = {
|
||||
"nsfw": ["nude", "breast", "small breast", "middle breast", "large breast", "nipples", "clothes lift", "pussy juice trail", "pussy juice puddle", "small testicles", "medium testicles", "large testicles", "disembodied penis", "cum on body", "cum inside", "cum outside", "fingering", "handjob", "fellatio", "licking penis", "paizuri", "doggystyle", "cowgirl", "reversed cowgirl", "piledriver", "suspended congress", "full nelson",],
|
||||
}
|
||||
|
||||
NEW_SCHEDULERS = ['align_your_steps', 'gits']
|
||||
NEW_SCHEDULERS = ['align_your_steps', 'gits']
|
||||
|
||||
@@ -56,10 +56,14 @@ class BizyAIRAPI:
|
||||
f"Failed to connect to the server: {e}, if you have no key, "
|
||||
)
|
||||
|
||||
# joycaptionTwo
|
||||
def joyCaption2(self, payload, image):
|
||||
api_key = self.getAPIKey()
|
||||
url = f"{self.base_url}/supernode/joycaption2"
|
||||
# joycaption
|
||||
def joyCaption(self, payload, image, apikey_override=None, API_URL='/supernode/joycaption2'):
|
||||
if apikey_override is not None:
|
||||
api_key = apikey_override
|
||||
else:
|
||||
api_key = self.getAPIKey()
|
||||
url = f"{self.base_url}{API_URL}"
|
||||
print('Sending request to:', url)
|
||||
auth = f"Bearer {api_key}"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
|
||||
+134
-38
@@ -1,52 +1,148 @@
|
||||
from threading import Event
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
from comfy import model_management as mm
|
||||
import time
|
||||
|
||||
class ChooserCancelled(Exception):
|
||||
pass
|
||||
|
||||
class ChooserMessage:
|
||||
stash = {}
|
||||
messages = {}
|
||||
cancelled = False
|
||||
def get_chooser_cache():
|
||||
"""获取选择器缓存"""
|
||||
if not hasattr(PromptServer.instance, '_easyuse_chooser_node'):
|
||||
PromptServer.instance._easyuse_chooser_node = {}
|
||||
return PromptServer.instance._easyuse_chooser_node
|
||||
|
||||
@classmethod
|
||||
def addMessage(cls, id, message):
|
||||
if message == '__cancel__':
|
||||
cls.messages = {}
|
||||
cls.cancelled = True
|
||||
elif message == '__start__':
|
||||
cls.messages = {}
|
||||
cls.stash = {}
|
||||
cls.cancelled = False
|
||||
else:
|
||||
cls.messages[str(id)] = message
|
||||
def cleanup_session_data(node_id):
|
||||
"""清理会话数据"""
|
||||
node_data = get_chooser_cache()
|
||||
if node_id in node_data:
|
||||
session_keys = ["event", "selected", "images", "total_count", "cancelled"]
|
||||
for key in session_keys:
|
||||
if key in node_data[node_id]:
|
||||
del node_data[node_id][key]
|
||||
|
||||
def wait_for_chooser(id, images, mode, period=0.1):
|
||||
try:
|
||||
node_data = get_chooser_cache()
|
||||
|
||||
if mode == "Keep Last Selection":
|
||||
if id in node_data and "last_selection" in node_data[id]:
|
||||
last_selection = node_data[id]["last_selection"]
|
||||
if last_selection and len(last_selection) > 0:
|
||||
valid_indices = [idx for idx in last_selection if 0 <= idx < len(images)]
|
||||
if valid_indices:
|
||||
try:
|
||||
PromptServer.instance.send_sync("easyuse-image-keep-selection", {
|
||||
"id": id,
|
||||
"selected": valid_indices
|
||||
})
|
||||
except Exception as e:
|
||||
pass
|
||||
cleanup_session_data(id)
|
||||
indices_str = ','.join(str(i) for i in valid_indices)
|
||||
return {"result": ([images[idx] for idx in valid_indices],)}
|
||||
|
||||
if id in node_data:
|
||||
del node_data[id]
|
||||
|
||||
event = Event()
|
||||
node_data[id] = {
|
||||
"event": event,
|
||||
"images": images,
|
||||
"selected": None,
|
||||
"total_count": len(images),
|
||||
"cancelled": False,
|
||||
}
|
||||
|
||||
while id in node_data:
|
||||
node_info = node_data[id]
|
||||
if node_info.get("cancelled", False):
|
||||
cleanup_session_data(id)
|
||||
raise ChooserCancelled("Manual selection cancelled")
|
||||
|
||||
if "selected" in node_info and node_info["selected"] is not None:
|
||||
break
|
||||
|
||||
@classmethod
|
||||
def waitForMessage(cls, id, period=0.1, asList=False):
|
||||
sid = str(id)
|
||||
while not (sid in cls.messages) and not ("-1" in cls.messages):
|
||||
if cls.cancelled:
|
||||
cls.cancelled = False
|
||||
raise ChooserCancelled()
|
||||
time.sleep(period)
|
||||
if cls.cancelled:
|
||||
cls.cancelled = False
|
||||
raise ChooserCancelled()
|
||||
message = cls.messages.pop(str(id), None) or cls.messages.pop("-1")
|
||||
try:
|
||||
if asList:
|
||||
return [int(x.strip()) for x in message.split(",")]
|
||||
|
||||
if id in node_data:
|
||||
node_info = node_data[id]
|
||||
selected_indices = node_info.get("selected")
|
||||
|
||||
if selected_indices is not None and len(selected_indices) > 0:
|
||||
valid_indices = [idx for idx in selected_indices if 0 <= idx < len(images)]
|
||||
if valid_indices:
|
||||
selected_images = [images[idx] for idx in valid_indices]
|
||||
|
||||
if id not in node_data:
|
||||
node_data[id] = {}
|
||||
node_data[id]["last_selection"] = valid_indices
|
||||
|
||||
cleanup_session_data(id)
|
||||
indices_str = ','.join(str(i) for i in valid_indices)
|
||||
return {"result": (selected_images, indices_str)}
|
||||
else:
|
||||
cleanup_session_data(id)
|
||||
return {"result": ([images[0]] if len(images) > 0 else [], "0" if len(images) > 0 else "")}
|
||||
else:
|
||||
return int(message.strip())
|
||||
except ValueError:
|
||||
print(
|
||||
f"ERROR IN IMAGE_CHOOSER - failed to parse '${message}' as ${'comma separated list of ints' if asList else 'int'}")
|
||||
return [1] if asList else 1
|
||||
cleanup_session_data(id)
|
||||
return {
|
||||
"result": ([images[0]] if len(images) > 0 else [],)}
|
||||
else:
|
||||
return {"result": ([images[0]] if len(images) > 0 else [],)}
|
||||
|
||||
except ChooserCancelled:
|
||||
raise mm.InterruptProcessingException()
|
||||
except Exception as e:
|
||||
node_data = get_chooser_cache()
|
||||
if id in node_data:
|
||||
cleanup_session_data(id)
|
||||
if 'image_list' in locals() and len(images) > 0:
|
||||
return {"result": ([images[0]])}
|
||||
else:
|
||||
return {"result": ([])}
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post('/easyuse/image_chooser_message')
|
||||
async def make_image_selection(request):
|
||||
post = await request.post()
|
||||
ChooserMessage.addMessage(post.get("id"), post.get("message"))
|
||||
return web.json_response({})
|
||||
async def handle_image_selection(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
node_id = data.get("node_id")
|
||||
selected = data.get("selected", [])
|
||||
action = data.get("action")
|
||||
|
||||
node_data = get_chooser_cache()
|
||||
|
||||
if node_id not in node_data:
|
||||
return web.json_response({"code": -1, "error": "Node data does not exist"})
|
||||
|
||||
try:
|
||||
node_info = node_data[node_id]
|
||||
|
||||
if "total_count" not in node_info:
|
||||
return web.json_response({"code": -1, "error": "The node has been processed"})
|
||||
|
||||
if action == "cancel":
|
||||
node_info["cancelled"] = True
|
||||
node_info["selected"] = []
|
||||
elif action == "select" and isinstance(selected, list):
|
||||
valid_indices = [idx for idx in selected if isinstance(idx, int) and 0 <= idx < node_info["total_count"]]
|
||||
if valid_indices:
|
||||
node_info["selected"] = valid_indices
|
||||
node_info["cancelled"] = False
|
||||
else:
|
||||
return web.json_response({"code": -1, "error": "Invalid Selection Index"})
|
||||
else:
|
||||
return web.json_response({"code": -1, "error": "Invalid operation"})
|
||||
|
||||
node_info["event"].set()
|
||||
return web.json_response({"code": 1})
|
||||
|
||||
except Exception as e:
|
||||
if node_id in node_data and "event" in node_data[node_id]:
|
||||
node_data[node_id]["event"].set()
|
||||
return web.json_response({"code": -1, "message": "Processing Failed"})
|
||||
|
||||
except Exception as e:
|
||||
return web.json_response({"code": -1, "message": "Request Failed"})
|
||||
|
||||
+29
-1
@@ -125,7 +125,35 @@ class ResizeMode(Enum):
|
||||
return 2
|
||||
assert False, "NOTREACHED"
|
||||
|
||||
|
||||
# credit by https://github.com/chflame163/ComfyUI_LayerStyle/blob/main/py/imagefunc.py#L591C1-L617C22
|
||||
def fit_resize_image(image: Image, target_width: int, target_height: int, fit: str, resize_sampler: str,
|
||||
background_color: str = '#000000') -> Image:
|
||||
image = image.convert('RGB')
|
||||
orig_width, orig_height = image.size
|
||||
if image is not None:
|
||||
if fit == 'letterbox':
|
||||
if orig_width / orig_height > target_width / target_height: # 更宽,上下留黑
|
||||
fit_width = target_width
|
||||
fit_height = int(target_width / orig_width * orig_height)
|
||||
else: # 更瘦,左右留黑
|
||||
fit_height = target_height
|
||||
fit_width = int(target_height / orig_height * orig_width)
|
||||
fit_image = image.resize((fit_width, fit_height), resize_sampler)
|
||||
ret_image = Image.new('RGB', size=(target_width, target_height), color=background_color)
|
||||
ret_image.paste(fit_image, box=((target_width - fit_width) // 2, (target_height - fit_height) // 2))
|
||||
elif fit == 'crop':
|
||||
if orig_width / orig_height > target_width / target_height: # 更宽,裁左右
|
||||
fit_width = int(orig_height * target_width / target_height)
|
||||
fit_image = image.crop(
|
||||
((orig_width - fit_width) // 2, 0, (orig_width - fit_width) // 2 + fit_width, orig_height))
|
||||
else: # 更瘦,裁上下
|
||||
fit_height = int(orig_width * target_height / target_width)
|
||||
fit_image = image.crop(
|
||||
(0, (orig_height - fit_height) // 2, orig_width, (orig_height - fit_height) // 2 + fit_height))
|
||||
ret_image = fit_image.resize((target_width, target_height), resize_sampler)
|
||||
else:
|
||||
ret_image = image.resize((target_width, target_height), resize_sampler)
|
||||
return ret_image
|
||||
|
||||
# CLIP反推
|
||||
import comfy.utils
|
||||
|
||||
+5
-1
@@ -351,7 +351,7 @@ class easyLoader:
|
||||
lora_path = None
|
||||
|
||||
if lora_path is not None:
|
||||
log_node_info("Load LORA",f"{lora_name}: {model_strength}, {clip_strength}, LBW={lbw}, A={lbw_a}, B={lbw_b}")
|
||||
log_node_info("Load LORA",f"{lora_name}: model={model_strength:.3f}, clip={clip_strength:.3f}, LBW={lbw}, A={lbw_a}, B={lbw_b}")
|
||||
if lbw:
|
||||
lbw = lora["lbw"]
|
||||
lbw_a = lora["lbw_a"]
|
||||
@@ -432,10 +432,13 @@ class easyLoader:
|
||||
clip_vision = None
|
||||
lora_stack = []
|
||||
|
||||
# Check for model override
|
||||
can_load_lora = True
|
||||
# 判断是否存在 模型或Lora叠加xyplot, 若存在优先缓存第一个模型
|
||||
# Determine whether there is a model or Lora overlapping xyplot, and if there is, prioritize caching the first model.
|
||||
xy_model_id = next((x for x in prompt if str(prompt[x]["class_type"]) in ["easy XYInputs: ModelMergeBlocks",
|
||||
"easy XYInputs: Checkpoint"]), None)
|
||||
# This will find nodes that aren't actively connected to anything, and skip loading lora's for them.
|
||||
xy_lora_id = next((x for x in prompt if str(prompt[x]["class_type"]) == "easy XYInputs: Lora"), None)
|
||||
if xy_lora_id is not None:
|
||||
can_load_lora = False
|
||||
@@ -461,6 +464,7 @@ class easyLoader:
|
||||
|
||||
if optional_lora_stack is not None and can_load_lora:
|
||||
for lora in optional_lora_stack:
|
||||
# This is a subtle bit of code because it uses the model created by the last call, and passes it to the next call.
|
||||
lora = {"lora_name": lora[0], "model": model, "clip": clip, "model_strength": lora[1],
|
||||
"clip_strength": lora[2]}
|
||||
model, clip = self.load_lora(lora)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
import time
|
||||
import json
|
||||
|
||||
class MessageCancelled(Exception):
|
||||
pass
|
||||
|
||||
class Message:
|
||||
stash = {}
|
||||
messages = {}
|
||||
cancelled = False
|
||||
|
||||
@classmethod
|
||||
def addMessage(cls, id, message):
|
||||
if message == '__cancel__':
|
||||
cls.messages = {}
|
||||
cls.cancelled = True
|
||||
elif message == '__start__':
|
||||
cls.messages = {}
|
||||
cls.stash = {}
|
||||
cls.cancelled = False
|
||||
else:
|
||||
cls.messages[str(id)] = message
|
||||
|
||||
@classmethod
|
||||
def waitForMessage(cls, id, period=0.1, asList=False):
|
||||
sid = str(id)
|
||||
while not (sid in cls.messages) and not ("-1" in cls.messages):
|
||||
if cls.cancelled:
|
||||
cls.cancelled = False
|
||||
raise MessageCancelled()
|
||||
time.sleep(period)
|
||||
if cls.cancelled:
|
||||
cls.cancelled = False
|
||||
raise MessageCancelled()
|
||||
message = cls.messages.pop(str(id), None) or cls.messages.pop("-1")
|
||||
try:
|
||||
if asList:
|
||||
return [str(x.strip()) for x in message.split(",")]
|
||||
else:
|
||||
try:
|
||||
return json.loads(message)
|
||||
except ValueError:
|
||||
return message
|
||||
except ValueError:
|
||||
print( f"ERROR IN MESSAGE - failed to parse '${message}' as ${'comma separated list of strings' if asList else 'string'}")
|
||||
return [message] if asList else message
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post('/easyuse/message_callback')
|
||||
async def message_callback(request):
|
||||
post = await request.post()
|
||||
Message.addMessage(post.get("id"), post.get("message"))
|
||||
return web.json_response({})
|
||||
+7
-5
@@ -82,6 +82,7 @@ def compare_revision(num):
|
||||
if not comfy_ui_revision:
|
||||
comfy_ui_revision = get_comfyui_revision()
|
||||
return True if comfy_ui_revision == 'Unknown' or int(comfy_ui_revision) >= num else False
|
||||
|
||||
def find_tags(string: str, sep="/") -> list[str]:
|
||||
"""
|
||||
find tags from string use the sep for split
|
||||
@@ -217,14 +218,15 @@ def get_local_filepath(url, dirname, local_file_name=None):
|
||||
except Exception as e:
|
||||
use_mirror = True
|
||||
url = url.replace('huggingface.co', 'hf-mirror.com')
|
||||
print(f'无法从huggingface下载,正在尝试从 {url} 下载...')
|
||||
PromptServer.instance.send_sync("easyuse-toast", {'content': f'无法连接huggingface,正在尝试从 {url} 下载...', 'duration': 10000})
|
||||
print(f'Unable to download from huggingface, trying mirror: {url}')
|
||||
PromptServer.instance.send_sync("easyuse-toast", {'content': f'Unable to connect to huggingface, trying mirror: {url}', 'duration': 10000})
|
||||
try:
|
||||
download_url_to_file(url, destination)
|
||||
except Exception as err:
|
||||
error_msg = str(err.args[0]) if err.args else str(err)
|
||||
PromptServer.instance.send_sync("easyuse-toast",
|
||||
{'content': f'无法从 {url} 下载模型', 'type':'error'})
|
||||
raise Exception(f'无法从 {url} 下载,错误信息:{str(err.args[0])}')
|
||||
{'content': f'Unable to download model from {url}', 'type':'error'})
|
||||
raise Exception(f'Download failed. Original URL and mirror both failed.\nError: {error_msg}')
|
||||
return destination
|
||||
|
||||
def to_lora_patch_dict(state_dict: dict) -> dict:
|
||||
@@ -277,4 +279,4 @@ def getMetadata(filepath):
|
||||
def cleanGPUUsedForce():
|
||||
gc.collect()
|
||||
mm.unload_all_models()
|
||||
mm.soft_empty_cache()
|
||||
mm.soft_empty_cache()
|
||||
|
||||
+179
-7
@@ -1,9 +1,13 @@
|
||||
import re
|
||||
import random
|
||||
import os
|
||||
import folder_paths
|
||||
import yaml
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
from math import prod
|
||||
|
||||
import yaml
|
||||
|
||||
import folder_paths
|
||||
|
||||
from .log import log_node_info
|
||||
|
||||
easy_wildcard_dict = {}
|
||||
@@ -34,11 +38,11 @@ def read_wildcard_dict(wildcard_path):
|
||||
key = os.path.splitext(rel_path)[0].replace('\\', '/').lower()
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding="ISO-8859-1") as f:
|
||||
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||
lines = f.read().splitlines()
|
||||
easy_wildcard_dict[key] = lines
|
||||
except UnicodeDecodeError:
|
||||
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||
with open(file_path, 'r', encoding="ISO-8859-1") as f:
|
||||
lines = f.read().splitlines()
|
||||
easy_wildcard_dict[key] = lines
|
||||
elif file.endswith('.yaml'):
|
||||
@@ -302,3 +306,171 @@ def process_with_loras(wildcard_opt, model, clip, title="Positive", seed=None, c
|
||||
log_node_info("easy wildcards",f'{title}_decode: {pass1}')
|
||||
|
||||
return model, clip, pass2, pass1, show_wildcard_prompt, pipe_lora_stack
|
||||
|
||||
|
||||
def expand_wildcard(keyword: str) -> tuple[str]:
|
||||
"""传入文件通配符的关键词,从 easy_wildcard_dict 中获取通配符的所有选项。"""
|
||||
global easy_wildcard_dict
|
||||
if keyword in easy_wildcard_dict:
|
||||
return tuple(easy_wildcard_dict[keyword])
|
||||
elif '*' in keyword:
|
||||
subpattern = keyword.replace('*', '.*').replace('+', r"\+")
|
||||
total_pattern = []
|
||||
for k, v in easy_wildcard_dict.items():
|
||||
if re.match(subpattern, k) is not None:
|
||||
total_pattern.extend(v)
|
||||
if total_pattern:
|
||||
return tuple(total_pattern)
|
||||
elif '/' not in keyword:
|
||||
return expand_wildcard(f"*/{keyword}")
|
||||
|
||||
def expand_options(options: str) -> tuple[str]:
|
||||
"""传入去掉 {} 的选项。
|
||||
展开选项通配符,返回该选项中的每一项,这里的每一项都是一个替换项。
|
||||
不会对选项内容进行任何处理,即便存在空格或特殊符号,也会原样返回。"""
|
||||
return tuple(options.split("|"))
|
||||
|
||||
|
||||
def decimal_to_irregular(n, bases):
|
||||
"""
|
||||
将十进制数转换为不规则进制
|
||||
|
||||
:param n: 十进制数
|
||||
:param bases: 各位置的基数列表,从低位到高位
|
||||
:return: 不规则进制表示的列表,从低位到高位
|
||||
"""
|
||||
if n == 0:
|
||||
return [0] * len(bases) if bases else [0]
|
||||
|
||||
digits = []
|
||||
remaining = n
|
||||
|
||||
# 从低位到高位处理
|
||||
for base in bases:
|
||||
digit = remaining % base
|
||||
digits.append(digit)
|
||||
remaining = remaining // base
|
||||
|
||||
return digits
|
||||
|
||||
|
||||
class WildcardProcessor:
|
||||
"""通配符处理器
|
||||
|
||||
通配符格式:
|
||||
+ option : {a|b}
|
||||
+ wildcard: __keyword__ 通配符内容将从 Easy-Use 插件提供的 easy_wildcard_dict 中获取
|
||||
"""
|
||||
|
||||
RE_OPTIONS = re.compile(r"{([^{}]*?)}")
|
||||
RE_WILDCARD = re.compile(r"__([\w\s.\-+/*\\]+?)__")
|
||||
RE_REPLACER = re.compile(r"{([^{}]*?)}|__([\w\s.\-+/*\\]+?)__")
|
||||
|
||||
# 将输入的提示词转化成符合 python str.format 要求格式的模板,并将 option 和 wildcard 按照顺序在模板中留下 {0}, {1} 等占位符
|
||||
template: str
|
||||
# option、wildcard 的替换项列表,按照在模板中出现的顺序排列,相同的替换项列表只保留第一份
|
||||
replacers: dict[int, tuple[str]]
|
||||
# 占位符的编号和替换项列表的索引的映射,占位符编号按照在模板中出现的顺序排列,方便减少替换项的存储占用
|
||||
placeholder_mapping: dict[str, int] # placeholder_id => replacer_id
|
||||
# 各替换项列表的项数,按照在模板中出现的顺序排列,提前计算,方便后续使用
|
||||
placeholder_choices: dict[str, int] # placeholder_id => len(replacer)
|
||||
|
||||
def __init__(self, text: str):
|
||||
self.__make_template(text)
|
||||
self.__total = None
|
||||
|
||||
def random(self, seed=None) -> str:
|
||||
"从所有可能性中随机获取一个"
|
||||
if seed is not None:
|
||||
random.seed(seed)
|
||||
return self.getn(random.randint(0, self.total() - 1))
|
||||
|
||||
def getn(self, n: int) -> str:
|
||||
"从所有可能性中获取第 n 个,以 self.total() 为周期循环"
|
||||
n = n % self.total()
|
||||
indice = decimal_to_irregular(n, self.placeholder_choices.values())
|
||||
replacements = {
|
||||
placeholder_id: self.replacers[self.placeholder_mapping[placeholder_id]][i]
|
||||
for placeholder_id, i in zip(self.placeholder_mapping.keys(), indice)
|
||||
}
|
||||
return self.template.format(**replacements)
|
||||
|
||||
def getmany(self, limit: int, offset: int = 0) -> list[str]:
|
||||
"""返回一组可能性组成的列表,为了避免结果太长导致内存占用超限,使用 limit 限制列表的长度,使用 offset 调整偏移。
|
||||
若 limit 和 offset 的设置导致预期的结果长度超过剩下的实际长度,则会回到开头。
|
||||
"""
|
||||
return [self.getn(n) for n in range(offset, offset + limit)]
|
||||
|
||||
def total(self) -> int:
|
||||
"计算可能性的数目"
|
||||
if self.__total is None:
|
||||
self.__total = prod(self.placeholder_choices.values())
|
||||
return self.__total
|
||||
|
||||
def __make_template(self, text: str):
|
||||
"""将输入的提示词转化成符合 python str.format 要求格式的模板,
|
||||
并将 option 和 wildcard 按照顺序在模板中留下 {r0}, {r1} 等占位符,
|
||||
即使遇到相同的 option 或 wildcard,留下的占位符编号也不同,从而使每项都独立变化。
|
||||
"""
|
||||
self.placeholder_mapping = {}
|
||||
placeholder_id = 0
|
||||
replacer_id = 0
|
||||
replacers_rev = {} # replacers => id
|
||||
blocks = []
|
||||
# 记录所处理过的通配符末尾在文本中的位置,用于拼接完整的模板
|
||||
tail = 0
|
||||
for match in self.RE_REPLACER.finditer(text):
|
||||
# 提取并展开通配符内容
|
||||
m = match.group(0)
|
||||
if m.startswith("{"):
|
||||
choices = expand_options(m[1:-1])
|
||||
elif m.startswith("__"):
|
||||
keyword = m[2:-2].lower()
|
||||
keyword = wildcard_normalize(keyword)
|
||||
choices = expand_wildcard(keyword)
|
||||
else:
|
||||
raise ValueError(f"{m!r} is not a wildcard or option")
|
||||
|
||||
# 记录通配符的替换项列表和ID,相同的通配符只保留第一个
|
||||
if choices not in replacers_rev:
|
||||
replacers_rev[choices] = replacer_id
|
||||
replacer_id += 1
|
||||
|
||||
# 拼接通配符前方文本
|
||||
start, end = match.span()
|
||||
blocks.append(text[tail:start])
|
||||
tail = end
|
||||
# 将通配符替换为占位符,并记录占位符和替换项列表的索引的映射
|
||||
blocks.append(f"{{r{placeholder_id}}}")
|
||||
self.placeholder_mapping[f"r{placeholder_id}"] = replacers_rev[choices]
|
||||
placeholder_id += 1
|
||||
|
||||
if tail < len(text):
|
||||
blocks.append(text[tail:])
|
||||
self.template = "".join(blocks)
|
||||
self.replacers = {v: k for k, v in replacers_rev.items()}
|
||||
self.placeholder_choices = {
|
||||
placeholder_id: len(self.replacers[replacer_id])
|
||||
for placeholder_id, replacer_id in self.placeholder_mapping.items()
|
||||
}
|
||||
|
||||
|
||||
def test_option():
|
||||
text = "{|a|b|c}"
|
||||
answer = ["", "a", "b", "c"]
|
||||
p = WildcardProcessor(text)
|
||||
assert p.total() == len(answer)
|
||||
assert p.getn(0) == answer[0]
|
||||
assert p.getmany(4) == answer
|
||||
assert p.getmany(4, 1) == answer[1:]
|
||||
|
||||
|
||||
def test_same():
|
||||
text = "{a|b},{a|b}"
|
||||
answer = ["a,a", "b,a", "a,b", "b,b"]
|
||||
p = WildcardProcessor(text)
|
||||
assert p.total() == len(answer)
|
||||
assert p.getn(0) == answer[0]
|
||||
assert p.getmany(4) == answer
|
||||
assert p.getmany(4, 1) == answer[1:]
|
||||
|
||||
|
||||
+90
-15
@@ -8,6 +8,7 @@ from .log import log_node_warn
|
||||
from ..modules.layer_diffuse import LayerDiffuse
|
||||
from ..config import RESOURCES_DIR
|
||||
from nodes import CLIPTextEncode
|
||||
import pprint
|
||||
try:
|
||||
from comfy_extras.nodes_flux import FluxGuidance
|
||||
except:
|
||||
@@ -52,7 +53,7 @@ class easyXYPlot():
|
||||
|
||||
plot_image_vars[value_type] = value
|
||||
if value_type in ["seed", "Seeds++ Batch"]:
|
||||
value_label = f"{value}"
|
||||
value_label = f"seed: {value}"
|
||||
else:
|
||||
value_label = f"{value_type}: {value}"
|
||||
|
||||
@@ -63,7 +64,9 @@ class easyXYPlot():
|
||||
arr = value.split(',')
|
||||
model_name = os.path.basename(os.path.splitext(arr[0])[0])
|
||||
trigger_words = ' ' + arr[3] if value_type == 'Lora' and len(arr[3]) > 2 else ''
|
||||
value_label = f"{model_name}{trigger_words}"
|
||||
lora_weight = float(arr[1]) if value_type == 'Lora' and len(arr) > 1 else 0
|
||||
lora_weight_desc = f"({lora_weight:.2f})" if lora_weight > 0 else ''
|
||||
value_label = f"{model_name[:30]}{lora_weight_desc} {trigger_words}"
|
||||
|
||||
if value_type in ["ModelMergeBlocks"]:
|
||||
if ":" in value:
|
||||
@@ -118,24 +121,32 @@ class easyXYPlot():
|
||||
|
||||
def calculate_background_dimensions(self):
|
||||
border_size = int((self.max_width // 8) * 1.5) if self.y_type != "None" or self.x_type != "None" else 0
|
||||
|
||||
bg_width = self.num_cols * (self.max_width + self.grid_spacing) - self.grid_spacing + border_size * (
|
||||
self.y_type != "None")
|
||||
bg_height = self.num_rows * (self.max_height + self.grid_spacing) - self.grid_spacing + border_size * (
|
||||
self.x_type != "None")
|
||||
|
||||
# Add space at the bottom of the image for common informaiton about the image
|
||||
bg_height = bg_height + (border_size*2)
|
||||
# print(f"Grid Size: width = {bg_width} height = {bg_height} border_size = {border_size}")
|
||||
|
||||
x_offset_initial = border_size if self.y_type != "None" else 0
|
||||
y_offset = border_size if self.x_type != "None" else 0
|
||||
|
||||
return bg_width, bg_height, x_offset_initial, y_offset
|
||||
|
||||
|
||||
def adjust_font_size(self, text, initial_font_size, label_width):
|
||||
font = self.get_font(initial_font_size, self.custom_font)
|
||||
text_width = font.getbbox(text)
|
||||
# pprint.pp(f"Initial font size: {initial_font_size}, text: {text}, text_width: {text_width}")
|
||||
if text_width and text_width[2]:
|
||||
text_width = text_width[2]
|
||||
|
||||
scaling_factor = 0.9
|
||||
if text_width > (label_width * scaling_factor):
|
||||
# print(f"Adjusting font size from {initial_font_size} to fit text width {text_width} into label width {label_width} scaling_factor {scaling_factor}")
|
||||
return int(initial_font_size * (label_width / text_width) * scaling_factor)
|
||||
else:
|
||||
return initial_font_size
|
||||
@@ -144,15 +155,22 @@ class easyXYPlot():
|
||||
_, _, width, height = d.textbbox((0, 0), text=text, font=font)
|
||||
return width, height
|
||||
|
||||
def create_label(self, img, text, initial_font_size, is_x_label=True, max_font_size=70, min_font_size=10):
|
||||
label_width = img.width if is_x_label else img.height
|
||||
def create_label(self, img, text, initial_font_size, is_x_label=True, max_font_size=70, min_font_size=10, label_width=0, label_height=0):
|
||||
|
||||
# if the label_width is specified, leave it along. Otherwise do the old logic.
|
||||
if label_width == 0:
|
||||
label_width = img.width if is_x_label else img.height
|
||||
|
||||
text_lines = text.split('\n')
|
||||
longest_line = max(text_lines, key=len)
|
||||
|
||||
# Adjust font size
|
||||
font_size = self.adjust_font_size(text, initial_font_size, label_width)
|
||||
font_size = self.adjust_font_size(longest_line, initial_font_size, label_width)
|
||||
font_size = min(max_font_size, font_size) # Ensure font isn't too large
|
||||
font_size = max(min_font_size, font_size) # Ensure font isn't too small
|
||||
|
||||
label_height = int(font_size * 1.5) if is_x_label else font_size
|
||||
if label_height == 0:
|
||||
label_height = int(font_size * 1.5) if is_x_label else font_size
|
||||
|
||||
label_bg = Image.new('RGBA', (label_width, label_height), color=(255, 255, 255, 0))
|
||||
d = ImageDraw.Draw(label_bg)
|
||||
@@ -166,7 +184,7 @@ class easyXYPlot():
|
||||
text = text + '...'
|
||||
|
||||
# Compute text width and height for multi-line text
|
||||
text_lines = text.split('\n')
|
||||
|
||||
text_widths, text_heights = zip(*[self.textsize(d, line, font=font) for line in text_lines])
|
||||
max_text_width = max(text_widths)
|
||||
total_text_height = sum(text_heights)
|
||||
@@ -195,8 +213,7 @@ class easyXYPlot():
|
||||
clip = clip if clip is not None else plot_image_vars["clip"]
|
||||
steps = plot_image_vars['steps'] if "steps" in plot_image_vars else 1
|
||||
|
||||
sd_version = get_sd_version(plot_image_vars['model'])
|
||||
|
||||
sd_version = get_sd_version(plot_image_vars['model'])
|
||||
# 高级用法
|
||||
if plot_image_vars["x_node_type"] == "advanced" or plot_image_vars["y_node_type"] == "advanced":
|
||||
if self.x_type == "Seeds++ Batch" or self.y_type == "Seeds++ Batch":
|
||||
@@ -347,17 +364,24 @@ class easyXYPlot():
|
||||
|
||||
# Lora
|
||||
if self.x_type == "Lora" or self.y_type == "Lora":
|
||||
# print(f"Lora: {x_value} {y_value}")
|
||||
model = model if model is not None else plot_image_vars["model"]
|
||||
clip = clip if clip is not None else plot_image_vars["clip"]
|
||||
|
||||
xy_values = x_value if self.x_type == "Lora" else y_value
|
||||
lora_name, lora_model_strength, lora_clip_strength, _ = xy_values.split(",")
|
||||
lora_stack = [{"lora_name": lora_name, "model": model, "clip" :clip, "model_strength": float(lora_model_strength), "clip_strength": float(lora_clip_strength)}]
|
||||
|
||||
# print(f"new_lora_stack: {new_lora_stack}")
|
||||
|
||||
|
||||
if 'lora_stack' in plot_image_vars:
|
||||
lora_stack = lora_stack + plot_image_vars['lora_stack']
|
||||
|
||||
|
||||
if lora_stack is not None and lora_stack != []:
|
||||
for lora in lora_stack:
|
||||
# Each generation of the model, must use the reference to previously created model / clip objects.
|
||||
lora['model'] = model
|
||||
lora['clip'] = clip
|
||||
model, clip = self.easyCache.load_lora(lora)
|
||||
|
||||
# 提示词
|
||||
@@ -464,6 +488,7 @@ class easyXYPlot():
|
||||
plot_image_vars['negative_weight_interpretation'], w_max=1.0,
|
||||
apply_to_pooled="enable", a1111_prompt_style=a1111_prompt_style, steps=steps)
|
||||
|
||||
|
||||
model = model if model is not None else plot_image_vars["model"]
|
||||
vae = vae if vae is not None else plot_image_vars["vae"]
|
||||
positive = positive if positive is not None else plot_image_vars["positive_cond"]
|
||||
@@ -582,11 +607,10 @@ class easyXYPlot():
|
||||
|
||||
return self.latents_plot
|
||||
|
||||
def plot_images_and_labels(self):
|
||||
# Calculate the background dimensions
|
||||
def plot_images_and_labels(self, plot_image_vars):
|
||||
|
||||
bg_width, bg_height, x_offset_initial, y_offset = self.calculate_background_dimensions()
|
||||
|
||||
# Create the white background image
|
||||
background = Image.new('RGBA', (int(bg_width), int(bg_height)), color=(255, 255, 255, 255))
|
||||
|
||||
output_image = []
|
||||
@@ -618,4 +642,55 @@ class easyXYPlot():
|
||||
|
||||
y_offset += img.height + self.grid_spacing
|
||||
|
||||
return (self.sampler.pil2tensor(background), output_image)
|
||||
# lookup used models in the image
|
||||
common_label = ""
|
||||
# Update to add a function to do the heavy lifting. Parameters are plot_image_vars name, label to use, names of the axis,
|
||||
|
||||
# pprint.pp(plot_image_vars)
|
||||
|
||||
# We don't process LORAs here because there can be multiple of them.
|
||||
labels = [
|
||||
{"id": "ckpt_name", "id_desc": "ckpt", "axis_type" : "Checkpoint"},
|
||||
{"id": "vae_name", "id_desc": '', "axis_type" : "vae_name"},
|
||||
{"id": "sampler_name", "id_desc": "sampler", "axis_type" : "Sampler"},
|
||||
{"id": "scheduler", "id_desc": '', "axis_type" : "Scheduler"},
|
||||
{"id": "steps", "id_desc": '', "axis_type" : "Steps"},
|
||||
{"id": "Flux Guidance", "id_desc": 'guidance', "axis_type" : "Flux Guidance"},
|
||||
{"id": "seed", "id_desc": '', "axis_type" : "Seeds++ Batch"}
|
||||
]
|
||||
|
||||
for item in labels:
|
||||
# Only add the label if it's not one of the axis
|
||||
# print(f"Checking item: {item['id']} axis_type {item['axis_type']} x_type: {self.x_type} y_type: {self.y_type}")
|
||||
if self.x_type != item['axis_type'] and self.y_type != item['axis_type']:
|
||||
common_label += self.add_common_label(item['id'], plot_image_vars, item['id_desc'])
|
||||
common_label += f"\n"
|
||||
|
||||
if plot_image_vars['lora_stack'] is not None and plot_image_vars['lora_stack'] != []:
|
||||
# print(f"lora_stack: {plot_image_vars['lora_stack']}")
|
||||
for lora in plot_image_vars['lora_stack']:
|
||||
|
||||
lora_name = lora['lora_name']
|
||||
lora_weight = lora['model_strength']
|
||||
if lora_name is not None and len(lora_name) > 0 and lora_weight > 0:
|
||||
common_label += f"LORA: {lora_name} weight: {lora_weight:.2f} \n"
|
||||
|
||||
common_label = common_label.strip()
|
||||
|
||||
if len(common_label) > 0:
|
||||
label_height = background.height - y_offset
|
||||
label_bg = self.create_label(background, common_label, int(48 * background.width / 512), label_width=background.width, label_height=label_height)
|
||||
label_x = (background.width - label_bg.width) // 2
|
||||
label_y = y_offset
|
||||
# print(f"Adding common label: {common_label} x = {label_x} y = {label_y}")
|
||||
background.alpha_composite(label_bg, (label_x, label_y))
|
||||
|
||||
return (self.sampler.pil2tensor(background), output_image)
|
||||
|
||||
def add_common_label(self, tag, plot_image_vars, description = ''):
|
||||
label = ''
|
||||
if description == '': description = tag
|
||||
if tag in plot_image_vars and plot_image_vars[tag] is not None and plot_image_vars[tag] != 'None':
|
||||
label += f"{description}: {plot_image_vars[tag]} "
|
||||
# print(f"add_common_label: {tag} description: {description} label: {label}" )
|
||||
return label
|
||||
|
||||
+445
-82
@@ -1,13 +1,36 @@
|
||||
import math
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.utils.checkpoint as checkpoint
|
||||
from einops import rearrange
|
||||
from PIL import Image, ImageFilter, ImageOps
|
||||
from timm.layers import DropPath, to_2tuple, trunc_normal_
|
||||
import torch.utils.checkpoint as checkpoint
|
||||
import numpy as np
|
||||
from timm.models.layers import DropPath, to_2tuple, trunc_normal_
|
||||
from PIL import Image, ImageOps
|
||||
from torchvision import transforms
|
||||
import numpy as np
|
||||
import random
|
||||
import cv2
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import tempfile
|
||||
|
||||
|
||||
def set_random_seed(seed):
|
||||
random.seed(seed)
|
||||
np.random.seed(seed)
|
||||
torch.manual_seed(seed)
|
||||
torch.cuda.manual_seed(seed)
|
||||
torch.cuda.manual_seed_all(seed)
|
||||
torch.backends.cudnn.deterministic = True
|
||||
torch.backends.cudnn.benchmark = False
|
||||
|
||||
|
||||
set_random_seed(9)
|
||||
|
||||
torch.set_float32_matmul_precision('highest')
|
||||
|
||||
|
||||
class Mlp(nn.Module):
|
||||
""" Multilayer perceptron."""
|
||||
@@ -247,6 +270,7 @@ class PatchMerging(nn.Module):
|
||||
dim (int): Number of input channels.
|
||||
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
|
||||
"""
|
||||
|
||||
def __init__(self, dim, norm_layer=nn.LayerNorm):
|
||||
super().__init__()
|
||||
self.dim = dim
|
||||
@@ -495,7 +519,8 @@ class SwinTransformer(nn.Module):
|
||||
patch_size = to_2tuple(patch_size)
|
||||
patches_resolution = [pretrain_img_size[0] // patch_size[0], pretrain_img_size[1] // patch_size[1]]
|
||||
|
||||
self.absolute_pos_embed = nn.Parameter(torch.zeros(1, embed_dim, patches_resolution[0], patches_resolution[1]))
|
||||
self.absolute_pos_embed = nn.Parameter(
|
||||
torch.zeros(1, embed_dim, patches_resolution[0], patches_resolution[1]))
|
||||
trunc_normal_(self.absolute_pos_embed, std=.02)
|
||||
|
||||
self.pos_drop = nn.Dropout(p=drop_rate)
|
||||
@@ -550,7 +575,6 @@ class SwinTransformer(nn.Module):
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
x = self.patch_embed(x)
|
||||
@@ -559,18 +583,16 @@ class SwinTransformer(nn.Module):
|
||||
if self.ape:
|
||||
# interpolate the position embedding to the corresponding size
|
||||
absolute_pos_embed = F.interpolate(self.absolute_pos_embed, size=(Wh, Ww), mode='bicubic')
|
||||
x = (x + absolute_pos_embed) # B Wh*Ww C
|
||||
x = (x + absolute_pos_embed) # B Wh*Ww C
|
||||
|
||||
outs = [x.contiguous()]
|
||||
x = x.flatten(2).transpose(1, 2)
|
||||
x = self.pos_drop(x)
|
||||
|
||||
|
||||
for i in range(self.num_layers):
|
||||
layer = self.layers[i]
|
||||
x_out, H, W, x, Wh, Ww = layer(x, Wh, Ww)
|
||||
|
||||
|
||||
if i in self.out_indices:
|
||||
norm_layer = getattr(self, f'norm{i}')
|
||||
x_out = norm_layer(x_out)
|
||||
@@ -578,16 +600,9 @@ class SwinTransformer(nn.Module):
|
||||
out = x_out.view(-1, H, W, self.num_features[i]).permute(0, 3, 1, 2).contiguous()
|
||||
outs.append(out)
|
||||
|
||||
|
||||
|
||||
return tuple(outs)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_activation_fn(activation):
|
||||
"""Return an activation function given a string"""
|
||||
if activation == "gelu":
|
||||
@@ -622,6 +637,43 @@ def patches2image(x):
|
||||
"""(hg wg b) c h w -> b c (hg h) (wg w)"""
|
||||
x = rearrange(x, '(hg wg b) c h w -> b c (hg h) (wg w)', hg=2, wg=2)
|
||||
return x
|
||||
|
||||
|
||||
class PositionEmbeddingSine:
|
||||
def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None):
|
||||
super().__init__()
|
||||
self.num_pos_feats = num_pos_feats
|
||||
self.temperature = temperature
|
||||
self.normalize = normalize
|
||||
if scale is not None and normalize is False:
|
||||
raise ValueError("normalize should be True if scale is passed")
|
||||
if scale is None:
|
||||
scale = 2 * math.pi
|
||||
self.scale = scale
|
||||
self.dim_t = torch.arange(0, self.num_pos_feats, dtype=torch.float32)
|
||||
|
||||
def __call__(self, b, h, w):
|
||||
device = self.dim_t.device
|
||||
mask = torch.zeros([b, h, w], dtype=torch.bool, device=device)
|
||||
assert mask is not None
|
||||
not_mask = ~mask
|
||||
y_embed = not_mask.cumsum(dim=1, dtype=torch.float32)
|
||||
x_embed = not_mask.cumsum(dim=2, dtype=torch.float32)
|
||||
if self.normalize:
|
||||
eps = 1e-6
|
||||
y_embed = (y_embed - 0.5) / (y_embed[:, -1:, :] + eps) * self.scale
|
||||
x_embed = (x_embed - 0.5) / (x_embed[:, :, -1:] + eps) * self.scale
|
||||
|
||||
dim_t = self.temperature ** (2 * (self.dim_t.to(device) // 2) / self.num_pos_feats)
|
||||
pos_x = x_embed[:, :, :, None] / dim_t
|
||||
pos_y = y_embed[:, :, :, None] / dim_t
|
||||
|
||||
pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)
|
||||
pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3)
|
||||
|
||||
return torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
|
||||
|
||||
|
||||
class PositionEmbeddingSine:
|
||||
def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None):
|
||||
super().__init__()
|
||||
@@ -688,13 +740,15 @@ class MCLM(nn.Module):
|
||||
l: 4,c,h,w
|
||||
g: 1,c,h,w
|
||||
"""
|
||||
self.p_poses = []
|
||||
self.g_pos = None
|
||||
b, c, h, w = l.size()
|
||||
# 4,c,h,w -> 1,c,2h,2w
|
||||
concated_locs = rearrange(l, '(hg wg b) c h w -> b c (hg h) (wg w)', hg=2, wg=2)
|
||||
|
||||
pools = []
|
||||
for pool_ratio in self.pool_ratios:
|
||||
# b,c,h,w
|
||||
# b,c,h,w
|
||||
tgt_hw = (round(h / pool_ratio), round(w / pool_ratio))
|
||||
pool = F.adaptive_avg_pool2d(concated_locs, tgt_hw)
|
||||
pools.append(rearrange(pool, 'b c h w -> (h w) b c'))
|
||||
@@ -712,11 +766,9 @@ class MCLM(nn.Module):
|
||||
self.p_poses = self.p_poses.to(device)
|
||||
self.g_pos = self.g_pos.to(device)
|
||||
|
||||
|
||||
# attention between glb (q) & multisensory concated-locs (k,v)
|
||||
g_hw_b_c = rearrange(g, 'b c h w -> (h w) b c')
|
||||
|
||||
|
||||
g_hw_b_c = g_hw_b_c + self.dropout1(self.attention[0](g_hw_b_c + self.g_pos, pools + self.p_poses, pools)[0])
|
||||
g_hw_b_c = self.norm1(g_hw_b_c)
|
||||
g_hw_b_c = g_hw_b_c + self.dropout2(self.linear2(self.dropout(self.activation(self.linear1(g_hw_b_c)).clone())))
|
||||
@@ -740,13 +792,6 @@ class MCLM(nn.Module):
|
||||
return rearrange(l, "(h w) b c -> b c h w", h=h, w=w) ## (5,c,h*w)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class MCRM(nn.Module):
|
||||
def __init__(self, d_model, num_heads, pool_ratios=[4, 8, 16], h=None):
|
||||
super(MCRM, self).__init__()
|
||||
@@ -852,91 +897,390 @@ class BEN_Base(nn.Module):
|
||||
if isinstance(m, nn.GELU) or isinstance(m, nn.Dropout):
|
||||
m.inplace = True
|
||||
|
||||
@torch.inference_mode()
|
||||
@torch.autocast(device_type="cuda", dtype=torch.float16)
|
||||
def forward(self, x):
|
||||
device = x.device
|
||||
shallow = self.shallow(x)
|
||||
glb = rescale_to(x, scale_factor=0.5, interpolation='bilinear')
|
||||
loc = image2patches(x)
|
||||
input = torch.cat((loc, glb), dim=0)
|
||||
feature = self.backbone(input)
|
||||
e5 = self.output5(feature[4]) # (5,128,16,16)
|
||||
e4 = self.output4(feature[3]) # (5,128,32,32)
|
||||
e3 = self.output3(feature[2]) # (5,128,64,64)
|
||||
e2 = self.output2(feature[1]) # (5,128,128,128)
|
||||
e1 = self.output1(feature[0]) # (5,128,128,128)
|
||||
loc_e5, glb_e5 = e5.split([4, 1], dim=0)
|
||||
e5 = self.multifieldcrossatt(loc_e5, glb_e5) # (4,128,16,16)
|
||||
real_batch = x.size(0)
|
||||
|
||||
e4, tokenattmap4 = self.dec_blk4(e4 + resize_as(e5, e4))
|
||||
e4 = self.conv4(e4)
|
||||
e3, tokenattmap3 = self.dec_blk3(e3 + resize_as(e4, e3))
|
||||
e3 = self.conv3(e3)
|
||||
e2, tokenattmap2 = self.dec_blk2(e2 + resize_as(e3, e2))
|
||||
e2 = self.conv2(e2)
|
||||
e1, tokenattmap1 = self.dec_blk1(e1 + resize_as(e2, e1))
|
||||
e1 = self.conv1(e1)
|
||||
loc_e1, glb_e1 = e1.split([4, 1], dim=0)
|
||||
output1_cat = patches2image(loc_e1) # (1,128,256,256)
|
||||
output1_cat = output1_cat + resize_as(glb_e1, output1_cat)
|
||||
final_output = self.insmask_head(output1_cat) # (1,128,256,256)
|
||||
final_output = final_output + resize_as(shallow, final_output)
|
||||
final_output = self.upsample1(rescale_to(final_output))
|
||||
final_output = rescale_to(final_output + resize_as(shallow, final_output))
|
||||
final_output = self.upsample2(final_output)
|
||||
final_output = self.output(final_output)
|
||||
shallow_batch = self.shallow(x)
|
||||
glb_batch = rescale_to(x, scale_factor=0.5, interpolation='bilinear')
|
||||
|
||||
return final_output.sigmoid()
|
||||
final_input = None
|
||||
for i in range(real_batch):
|
||||
start = i * 4
|
||||
end = (i + 1) * 4
|
||||
loc_batch = image2patches(x[i, :, :, :].unsqueeze(dim=0))
|
||||
input_ = torch.cat((loc_batch, glb_batch[i, :, :, :].unsqueeze(dim=0)), dim=0)
|
||||
|
||||
@torch.no_grad()
|
||||
def inference(self,image):
|
||||
image, h, w,original_image = rgb_loader_refiner(image)
|
||||
if final_input == None:
|
||||
final_input = input_
|
||||
else:
|
||||
final_input = torch.cat((final_input, input_), dim=0)
|
||||
|
||||
img_tensor = img_transform(image).unsqueeze(0).to(next(self.parameters()).device)
|
||||
features = self.backbone(final_input)
|
||||
outputs = []
|
||||
|
||||
res = self.forward(img_tensor)
|
||||
for i in range(real_batch):
|
||||
start = i * 5
|
||||
end = (i + 1) * 5
|
||||
|
||||
pred_array = postprocess_image(res, im_size=[w, h])
|
||||
f4 = features[4][start:end, :, :, :] # shape: [5, C, H, W]
|
||||
f3 = features[3][start:end, :, :, :]
|
||||
f2 = features[2][start:end, :, :, :]
|
||||
f1 = features[1][start:end, :, :, :]
|
||||
f0 = features[0][start:end, :, :, :]
|
||||
e5 = self.output5(f4)
|
||||
e4 = self.output4(f3)
|
||||
e3 = self.output3(f2)
|
||||
e2 = self.output2(f1)
|
||||
e1 = self.output1(f0)
|
||||
loc_e5, glb_e5 = e5.split([4, 1], dim=0)
|
||||
e5 = self.multifieldcrossatt(loc_e5, glb_e5) # (4,128,16,16)
|
||||
|
||||
mask_image = Image.fromarray(pred_array, mode='L')
|
||||
e4, tokenattmap4 = self.dec_blk4(e4 + resize_as(e5, e4))
|
||||
e4 = self.conv4(e4)
|
||||
e3, tokenattmap3 = self.dec_blk3(e3 + resize_as(e4, e3))
|
||||
e3 = self.conv3(e3)
|
||||
e2, tokenattmap2 = self.dec_blk2(e2 + resize_as(e3, e2))
|
||||
e2 = self.conv2(e2)
|
||||
e1, tokenattmap1 = self.dec_blk1(e1 + resize_as(e2, e1))
|
||||
e1 = self.conv1(e1)
|
||||
|
||||
blurred_mask = mask_image.filter(ImageFilter.GaussianBlur(radius=1))
|
||||
loc_e1, glb_e1 = e1.split([4, 1], dim=0)
|
||||
|
||||
original_image_rgba = original_image.convert("RGBA")
|
||||
output1_cat = patches2image(loc_e1) # (1,128,256,256)
|
||||
|
||||
foreground = original_image_rgba.copy()
|
||||
# add glb feat in
|
||||
output1_cat = output1_cat + resize_as(glb_e1, output1_cat)
|
||||
# merge
|
||||
final_output = self.insmask_head(output1_cat) # (1,128,256,256)
|
||||
# shallow feature merge
|
||||
shallow = shallow_batch[i, :, :, :].unsqueeze(dim=0)
|
||||
final_output = final_output + resize_as(shallow, final_output)
|
||||
final_output = self.upsample1(rescale_to(final_output))
|
||||
final_output = rescale_to(final_output + resize_as(shallow, final_output))
|
||||
final_output = self.upsample2(final_output)
|
||||
final_output = self.output(final_output)
|
||||
mask = final_output.sigmoid()
|
||||
outputs.append(mask)
|
||||
|
||||
foreground.putalpha(blurred_mask)
|
||||
return torch.cat(outputs, dim=0)
|
||||
|
||||
return blurred_mask, foreground
|
||||
|
||||
def loadcheckpoints(self,model_path):
|
||||
def loadcheckpoints(self, model_path):
|
||||
model_dict = torch.load(model_path, map_location="cpu", weights_only=True)
|
||||
self.load_state_dict(model_dict['model_state_dict'], strict=True)
|
||||
del model_path
|
||||
|
||||
def inference(self, image, refine_foreground=False):
|
||||
|
||||
set_random_seed(9)
|
||||
# image = ImageOps.exif_transpose(image)
|
||||
if isinstance(image, Image.Image):
|
||||
image, h, w, original_image = rgb_loader_refiner(image)
|
||||
if torch.cuda.is_available():
|
||||
|
||||
img_tensor = img_transform(image).unsqueeze(0).to(next(self.parameters()).device)
|
||||
else:
|
||||
img_tensor = img_transform32(image).unsqueeze(0).to(next(self.parameters()).device)
|
||||
|
||||
with torch.no_grad():
|
||||
res = self.forward(img_tensor)
|
||||
|
||||
# Show Results
|
||||
if refine_foreground == True:
|
||||
|
||||
pred_pil = transforms.ToPILImage()(res.squeeze())
|
||||
image_masked = refine_foreground_process(original_image, pred_pil)
|
||||
mask = pred_pil.resize(original_image.size)
|
||||
image_masked.putalpha(mask)
|
||||
return mask, image_masked
|
||||
|
||||
else:
|
||||
alpha = postprocess_image(res, im_size=[w, h])
|
||||
pred_pil = transforms.ToPILImage()(alpha)
|
||||
mask = pred_pil.resize(original_image.size)
|
||||
original_image.putalpha(mask)
|
||||
# mask = Image.fromarray(alpha)
|
||||
|
||||
return mask, original_image
|
||||
|
||||
def segment_video(self, video_path, output_path="./", fps=0, refine_foreground=False, batch=1,
|
||||
print_frames_processed=True, webm=False, rgb_value=(0, 255, 0)):
|
||||
|
||||
"""
|
||||
Segments the given video to extract the foreground (with alpha) from each frame
|
||||
and saves the result as either a WebM video (with alpha channel) or MP4 (with a
|
||||
color background).
|
||||
Args:
|
||||
video_path (str):
|
||||
Path to the input video file.
|
||||
output_path (str, optional):
|
||||
Directory (or full path) where the output video and/or files will be saved.
|
||||
Defaults to "./".
|
||||
fps (int, optional):
|
||||
The frames per second (FPS) to use for the output video. If 0 (default), the
|
||||
original FPS of the input video is used. Otherwise, overrides it.
|
||||
refine_foreground (bool, optional):
|
||||
Whether to run an additional “refine foreground” process on each frame.
|
||||
Defaults to False.
|
||||
batch (int, optional):
|
||||
Number of frames to process at once (inference batch size). Large batch sizes
|
||||
may require more GPU memory. Defaults to 1.
|
||||
print_frames_processed (bool, optional):
|
||||
If True (default), prints progress (how many frames have been processed) to
|
||||
the console.
|
||||
webm (bool, optional):
|
||||
If True (default), exports a WebM video with alpha channel (VP9 / yuva420p).
|
||||
If False, exports an MP4 video composited over a solid color background.
|
||||
rgb_value (tuple, optional):
|
||||
The RGB background color (e.g., green screen) used to composite frames when
|
||||
saving to MP4. Defaults to (0, 255, 0).
|
||||
Returns:
|
||||
None. Writes the output video(s) to disk in the specified format.
|
||||
"""
|
||||
|
||||
cap = cv2.VideoCapture(video_path)
|
||||
if not cap.isOpened():
|
||||
raise IOError(f"Cannot open video: {video_path}")
|
||||
|
||||
original_fps = cap.get(cv2.CAP_PROP_FPS)
|
||||
original_fps = 30 if original_fps == 0 else original_fps
|
||||
fps = original_fps if fps == 0 else fps
|
||||
|
||||
ret, first_frame = cap.read()
|
||||
if not ret:
|
||||
raise ValueError("No frames found in the video.")
|
||||
height, width = first_frame.shape[:2]
|
||||
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
||||
|
||||
foregrounds = []
|
||||
frame_idx = 0
|
||||
processed_count = 0
|
||||
batch_frames = []
|
||||
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
|
||||
while True:
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
if batch_frames:
|
||||
batch_results = self.inference(batch_frames, refine_foreground)
|
||||
if isinstance(batch_results, Image.Image):
|
||||
foregrounds.append(batch_results)
|
||||
else:
|
||||
foregrounds.extend(batch_results)
|
||||
if print_frames_processed:
|
||||
print(f"Processed frames {frame_idx - len(batch_frames) + 1} to {frame_idx} of {total_frames}")
|
||||
break
|
||||
|
||||
# Process every frame instead of using intervals
|
||||
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
pil_frame = Image.fromarray(frame_rgb)
|
||||
batch_frames.append(pil_frame)
|
||||
|
||||
if len(batch_frames) == batch:
|
||||
batch_results = self.inference(batch_frames, refine_foreground)
|
||||
if isinstance(batch_results, Image.Image):
|
||||
foregrounds.append(batch_results)
|
||||
else:
|
||||
foregrounds.extend(batch_results)
|
||||
if print_frames_processed:
|
||||
print(f"Processed frames {frame_idx - batch + 1} to {frame_idx} of {total_frames}")
|
||||
batch_frames = []
|
||||
processed_count += batch
|
||||
|
||||
frame_idx += 1
|
||||
|
||||
if webm:
|
||||
alpha_webm_path = os.path.join(output_path, "foreground.webm")
|
||||
pil_images_to_webm_alpha(foregrounds, alpha_webm_path, fps=original_fps)
|
||||
|
||||
else:
|
||||
cap.release()
|
||||
fg_output = os.path.join(output_path, 'foreground.mp4')
|
||||
|
||||
pil_images_to_mp4(foregrounds, fg_output, fps=original_fps, rgb_value=rgb_value)
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
try:
|
||||
fg_audio_output = os.path.join(output_path, 'foreground_output_with_audio.mp4')
|
||||
add_audio_to_video(fg_output, video_path, fg_audio_output)
|
||||
except Exception as e:
|
||||
print("No audio found in the original video")
|
||||
print(e)
|
||||
|
||||
|
||||
def rgb_loader_refiner(original_image):
|
||||
h, w = original_image.size
|
||||
|
||||
def rgb_loader_refiner( original_image):
|
||||
h, w = original_image.size
|
||||
# # Apply EXIF orientation
|
||||
image = ImageOps.exif_transpose(original_image)
|
||||
# Convert to RGB if necessary
|
||||
if image.mode != 'RGB':
|
||||
image = image.convert('RGB')
|
||||
image = original_image
|
||||
# Convert to RGB if necessary
|
||||
if image.mode != 'RGB':
|
||||
image = image.convert('RGB')
|
||||
|
||||
# Resize the image
|
||||
image = image.resize((1024, 1024), resample=Image.LANCZOS)
|
||||
# Resize the image
|
||||
image = image.resize((1024, 1024), resample=Image.LANCZOS)
|
||||
|
||||
return image.convert('RGB'), h, w, original_image
|
||||
|
||||
return image.convert('RGB'), h, w,original_image
|
||||
|
||||
# Define the image transformation
|
||||
img_transform = transforms.Compose([
|
||||
transforms.ToTensor(),
|
||||
transforms.ConvertImageDtype(torch.float16),
|
||||
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
||||
])
|
||||
|
||||
img_transform32 = transforms.Compose([
|
||||
transforms.ToTensor(),
|
||||
transforms.ConvertImageDtype(torch.float32),
|
||||
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
||||
])
|
||||
|
||||
|
||||
def pil_images_to_mp4(images, output_path, fps=24, rgb_value=(0, 255, 0)):
|
||||
"""
|
||||
Converts an array of PIL images to an MP4 video.
|
||||
|
||||
Args:
|
||||
images: List of PIL images
|
||||
output_path: Path to save the MP4 file
|
||||
fps: Frames per second (default: 24)
|
||||
rgb_value: Background RGB color tuple (default: green (0, 255, 0))
|
||||
"""
|
||||
if not images:
|
||||
raise ValueError("No images provided to convert to MP4.")
|
||||
|
||||
width, height = images[0].size
|
||||
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
||||
video_writer = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
|
||||
|
||||
for image in images:
|
||||
# If image has alpha channel, composite onto the specified background color
|
||||
if image.mode == 'RGBA':
|
||||
# Create background image with specified RGB color
|
||||
background = Image.new('RGB', image.size, rgb_value)
|
||||
background = background.convert('RGBA')
|
||||
# Composite the image onto the background
|
||||
image = Image.alpha_composite(background, image)
|
||||
image = image.convert('RGB')
|
||||
else:
|
||||
# Ensure RGB format for non-alpha images
|
||||
image = image.convert('RGB')
|
||||
|
||||
# Convert to OpenCV format and write
|
||||
open_cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
||||
video_writer.write(open_cv_image)
|
||||
|
||||
video_writer.release()
|
||||
|
||||
|
||||
def pil_images_to_webm_alpha(images, output_path, fps=30):
|
||||
"""
|
||||
Converts a list of PIL RGBA images to a VP9 .webm video with alpha channel.
|
||||
NOTE: Not all players will display alpha in WebM.
|
||||
Browsers like Chrome/Firefox typically do support VP9 alpha.
|
||||
"""
|
||||
if not images:
|
||||
raise ValueError("No images provided for WebM with alpha.")
|
||||
|
||||
# Ensure output directory exists
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Save frames as PNG (with alpha)
|
||||
for idx, img in enumerate(images):
|
||||
if img.mode != "RGBA":
|
||||
img = img.convert("RGBA")
|
||||
out_path = os.path.join(tmpdir, f"{idx:06d}.png")
|
||||
img.save(out_path, "PNG")
|
||||
|
||||
# Construct ffmpeg command
|
||||
# -c:v libvpx-vp9 => VP9 encoder
|
||||
# -pix_fmt yuva420p => alpha-enabled pixel format
|
||||
# -auto-alt-ref 0 => helps preserve alpha frames (libvpx quirk)
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg", "-y",
|
||||
"-framerate", str(fps),
|
||||
"-i", os.path.join(tmpdir, "%06d.png"),
|
||||
"-c:v", "libvpx-vp9",
|
||||
"-pix_fmt", "yuva420p",
|
||||
"-auto-alt-ref", "0",
|
||||
output_path
|
||||
]
|
||||
|
||||
subprocess.run(ffmpeg_cmd, check=True)
|
||||
|
||||
print(f"WebM with alpha saved to {output_path}")
|
||||
|
||||
|
||||
def add_audio_to_video(video_without_audio_path, original_video_path, output_path):
|
||||
"""
|
||||
Check if the original video has an audio stream. If yes, add it. If not, skip.
|
||||
"""
|
||||
# 1) Probe original video for audio streams
|
||||
probe_command = [
|
||||
'ffprobe', '-v', 'error',
|
||||
'-select_streams', 'a:0',
|
||||
'-show_entries', 'stream=index',
|
||||
'-of', 'csv=p=0',
|
||||
original_video_path
|
||||
]
|
||||
result = subprocess.run(probe_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
# result.stdout is empty if no audio stream found
|
||||
if not result.stdout.strip():
|
||||
print("No audio track found in original video, skipping audio addition.")
|
||||
return
|
||||
|
||||
print("Audio track detected; proceeding to mux audio.")
|
||||
# 2) If audio found, run ffmpeg to add it
|
||||
command = [
|
||||
'ffmpeg', '-y',
|
||||
'-i', video_without_audio_path,
|
||||
'-i', original_video_path,
|
||||
'-c', 'copy',
|
||||
'-map', '0:v:0',
|
||||
'-map', '1:a:0', # we know there's an audio track now
|
||||
output_path
|
||||
]
|
||||
subprocess.run(command, check=True)
|
||||
print(f"Audio added successfully => {output_path}")
|
||||
|
||||
|
||||
### Thanks to the source: https://huggingface.co/ZhengPeng7/BiRefNet/blob/main/handler.py
|
||||
def refine_foreground_process(image, mask, r=90):
|
||||
if mask.size != image.size:
|
||||
mask = mask.resize(image.size)
|
||||
image = np.array(image) / 255.0
|
||||
mask = np.array(mask) / 255.0
|
||||
estimated_foreground = FB_blur_fusion_foreground_estimator_2(image, mask, r=r)
|
||||
image_masked = Image.fromarray((estimated_foreground * 255.0).astype(np.uint8))
|
||||
return image_masked
|
||||
|
||||
|
||||
def FB_blur_fusion_foreground_estimator_2(image, alpha, r=90):
|
||||
# Thanks to the source: https://github.com/Photoroom/fast-foreground-estimation
|
||||
alpha = alpha[:, :, None]
|
||||
F, blur_B = FB_blur_fusion_foreground_estimator(image, image, image, alpha, r)
|
||||
return FB_blur_fusion_foreground_estimator(image, F, blur_B, alpha, r=6)[0]
|
||||
|
||||
|
||||
def FB_blur_fusion_foreground_estimator(image, F, B, alpha, r=90):
|
||||
if isinstance(image, Image.Image):
|
||||
image = np.array(image) / 255.0
|
||||
blurred_alpha = cv2.blur(alpha, (r, r))[:, :, None]
|
||||
|
||||
blurred_FA = cv2.blur(F * alpha, (r, r))
|
||||
blurred_F = blurred_FA / (blurred_alpha + 1e-5)
|
||||
|
||||
blurred_B1A = cv2.blur(B * (1 - alpha), (r, r))
|
||||
blurred_B = blurred_B1A / ((1 - blurred_alpha) + 1e-5)
|
||||
F = blurred_F + alpha * \
|
||||
(image - alpha * blurred_F - (1 - alpha) * blurred_B)
|
||||
F = np.clip(F, 0, 1)
|
||||
return F, blurred_B
|
||||
|
||||
|
||||
def postprocess_image(result: torch.Tensor, im_size: list) -> np.ndarray:
|
||||
result = torch.squeeze(F.interpolate(result, size=im_size, mode='bilinear'), 0)
|
||||
ma = torch.max(result)
|
||||
@@ -944,4 +1288,23 @@ def postprocess_image(result: torch.Tensor, im_size: list) -> np.ndarray:
|
||||
result = (result - mi) / (ma - mi)
|
||||
im_array = (result * 255).permute(1, 2, 0).cpu().data.numpy().astype(np.uint8)
|
||||
im_array = np.squeeze(im_array)
|
||||
return im_array
|
||||
return im_array
|
||||
|
||||
|
||||
def rgb_loader_refiner(original_image):
|
||||
h, w = original_image.size
|
||||
# # Apply EXIF orientation
|
||||
|
||||
image = ImageOps.exif_transpose(original_image)
|
||||
|
||||
if original_image.mode != 'RGB':
|
||||
original_image = original_image.convert('RGB')
|
||||
|
||||
image = original_image
|
||||
# Convert to RGB if necessary
|
||||
|
||||
# Resize the image
|
||||
image = image.resize((1024, 1024), resample=Image.LANCZOS)
|
||||
|
||||
return image, h, w, original_image
|
||||
|
||||
|
||||
@@ -5,13 +5,21 @@ import os
|
||||
import types
|
||||
|
||||
import torch
|
||||
from accelerate import init_empty_weights, load_checkpoint_and_dispatch
|
||||
try:
|
||||
from accelerate import init_empty_weights, load_checkpoint_and_dispatch
|
||||
except:
|
||||
init_empty_weights, load_checkpoint_and_dispatch = None, None
|
||||
|
||||
import comfy
|
||||
|
||||
from .model import BrushNetModel, PowerPaintModel
|
||||
from .model_patch import add_model_patch_option, patch_model_function_wrapper
|
||||
from .powerpaint_utils import TokenizerWrapper, add_tokens
|
||||
try:
|
||||
from .model import BrushNetModel, PowerPaintModel
|
||||
from .model_patch import add_model_patch_option, patch_model_function_wrapper
|
||||
from .powerpaint_utils import TokenizerWrapper, add_tokens
|
||||
except:
|
||||
BrushNetModel, PowerPaintModel = None, None
|
||||
add_model_patch_option, patch_model_function_wrapper = None, None
|
||||
TokenizerWrapper, add_tokens = None, None
|
||||
|
||||
cwd_path = os.path.dirname(os.path.realpath(__file__))
|
||||
brushnet_config_file = os.path.join(cwd_path, 'config', 'brushnet.json')
|
||||
@@ -272,11 +280,11 @@ class BrushNet:
|
||||
|
||||
# unload vae
|
||||
del vae
|
||||
for loaded_model in comfy.model_management.current_loaded_models:
|
||||
if type(loaded_model.model.model) in ModelsToUnload:
|
||||
comfy.model_management.current_loaded_models.remove(loaded_model)
|
||||
loaded_model.model_unload()
|
||||
del loaded_model
|
||||
# for loaded_model in comfy.model_management.current_loaded_models:
|
||||
# if type(loaded_model.model.model) in ModelsToUnload:
|
||||
# comfy.model_management.current_loaded_models.remove(loaded_model)
|
||||
# loaded_model.model_unload()
|
||||
# del loaded_model
|
||||
|
||||
# prepare embeddings
|
||||
prompt_embeds = positive[0][0].to(dtype=torch_dtype).to(brushnet['brushnet'].device)
|
||||
@@ -449,11 +457,11 @@ class BrushNet:
|
||||
# unload vae and CLIPs
|
||||
del vae
|
||||
del clip
|
||||
for loaded_model in comfy.model_management.current_loaded_models:
|
||||
if type(loaded_model.model.model) in ModelsToUnload:
|
||||
comfy.model_management.current_loaded_models.remove(loaded_model)
|
||||
loaded_model.model_unload()
|
||||
del loaded_model
|
||||
# for loaded_model in comfy.model_management.current_loaded_models:
|
||||
# if type(loaded_model.model.model) in ModelsToUnload:
|
||||
# comfy.model_management.current_loaded_models.remove(loaded_model)
|
||||
# loaded_model.model_unload()
|
||||
# del loaded_model
|
||||
|
||||
# apply patch to model
|
||||
|
||||
@@ -663,8 +671,16 @@ def add_brushnet_patch(model, brushnet, torch_dtype, conditioning_latents,
|
||||
|
||||
is_SDXL = isinstance(model.model.model_config, comfy.supported_models.SDXL)
|
||||
|
||||
if model.model.model_config.custom_operations is None:
|
||||
fp8 = model.model.model_config.optimizations.get("fp8", model.model.model_config.scaled_fp8 is not None)
|
||||
operations = comfy.ops.pick_operations(model.model.model_config.unet_config.get("dtype", None), model.model.manual_cast_dtype,
|
||||
fp8_optimizations=fp8, scaled_fp8=model.model.model_config.scaled_fp8)
|
||||
else:
|
||||
# such as gguf
|
||||
operations = model.model.model_config.custom_operations
|
||||
|
||||
if is_SDXL:
|
||||
input_blocks = [[0, comfy.ops.manual_cast.Conv2d],
|
||||
input_blocks = [[0, operations.Conv2d],
|
||||
[1, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock],
|
||||
[2, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock],
|
||||
[3, comfy.ldm.modules.diffusionmodules.openaimodel.Downsample],
|
||||
@@ -686,7 +702,7 @@ def add_brushnet_patch(model, brushnet, torch_dtype, conditioning_latents,
|
||||
[7, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock],
|
||||
[8, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock]]
|
||||
else:
|
||||
input_blocks = [[0, comfy.ops.manual_cast.Conv2d],
|
||||
input_blocks = [[0, operations.Conv2d],
|
||||
[1, comfy.ldm.modules.attention.SpatialTransformer],
|
||||
[2, comfy.ldm.modules.attention.SpatialTransformer],
|
||||
[3, comfy.ldm.modules.diffusionmodules.openaimodel.Downsample],
|
||||
|
||||
@@ -9,7 +9,10 @@ from enum import Enum
|
||||
from comfy.utils import load_torch_file
|
||||
from comfy.conds import CONDRegular
|
||||
from comfy_extras.nodes_compositing import JoinImageWithAlpha
|
||||
from .model import ModelPatcher, TransparentVAEDecoder, calculate_weight_adjust_channel
|
||||
try:
|
||||
from .model import ModelPatcher, TransparentVAEDecoder, calculate_weight_adjust_channel
|
||||
except:
|
||||
ModelPatcher, TransparentVAEDecoder, calculate_weight_adjust_channel = None, None, None
|
||||
from .attension_sharing import AttentionSharingPatcher
|
||||
from ...config import LAYER_DIFFUSION, LAYER_DIFFUSION_DIR, LAYER_DIFFUSION_VAE
|
||||
from ...libs.utils import to_lora_patch_dict, get_local_filepath, get_sd_version
|
||||
@@ -51,7 +54,7 @@ class LayerDiffuse:
|
||||
|
||||
return (write_c_concat(cond), write_c_concat(uncond))
|
||||
|
||||
def apply_layer_diffusion(self, model: ModelPatcher, method, weight, samples, blend_samples, positive, negative, image=None, additional_cond=(None, None, None)):
|
||||
def apply_layer_diffusion(self, model, method, weight, samples, blend_samples, positive, negative, image=None, additional_cond=(None, None, None)):
|
||||
control_img: Optional[torch.TensorType] = None
|
||||
sd_version = get_sd_version(model)
|
||||
model_url = LAYER_DIFFUSION[method.value][sd_version]["model_url"]
|
||||
|
||||
+30
-1
@@ -9,12 +9,39 @@ from ..config import *
|
||||
|
||||
from ..libs.log import log_node_info, log_node_warn
|
||||
from ..libs.utils import get_local_filepath, get_sd_version
|
||||
from ..libs.wildcards import process_with_loras
|
||||
from ..libs.controlnet import easyControlnet
|
||||
from ..libs.conditioning import prompt_to_cond
|
||||
from ..libs import cache as backend_cache
|
||||
|
||||
from .. import easyCache
|
||||
|
||||
class applyLoraPrompt:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"model": ("MODEL",),
|
||||
"clip": ("CLIP",),
|
||||
"positive": ("STRING", {"default": "", "forceInput": True}),
|
||||
},
|
||||
"optional": {
|
||||
"negative": ("STRING", {"default": "", "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP", "STRING", "STRING")
|
||||
RETURN_NAMES = ("model", "clip", "positive", "negative")
|
||||
CATEGORY = "EasyUse/Adapter"
|
||||
FUNCTION = "apply"
|
||||
|
||||
def apply(self, model, clip, positive, negative=None):
|
||||
model, clip, positive, _, _, _ = process_with_loras(positive, model, clip, 'Positive', easyCache=easyCache)
|
||||
if negative is not None:
|
||||
model, clip, negative, _, _, _ = process_with_loras(negative, model, clip, 'Negative', easyCache=easyCache)
|
||||
|
||||
return (model, clip, positive, negative if negative is not None else "")
|
||||
|
||||
class applyLoraStack:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -40,7 +67,7 @@ class applyLoraStack:
|
||||
lora = {"lora_name": lora[0], "model": model, "clip": optional_clip, "model_strength": lora[1],
|
||||
"clip_strength": lora[2]}
|
||||
model, clip = easyCache.load_lora(lora, model, optional_clip, use_cache=False)
|
||||
return (model, clip)
|
||||
return (model, optional_clip if clip is None else clip)
|
||||
|
||||
class applyControlnetStack:
|
||||
@classmethod
|
||||
@@ -1284,6 +1311,7 @@ class applyPulIDADV(applyPulID):
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"easy loraPromptApply": applyLoraPrompt,
|
||||
"easy loraStackApply": applyLoraStack,
|
||||
"easy controlnetStackApply": applyControlnetStack,
|
||||
"easy ipadapterApply": ipadapterApply,
|
||||
@@ -1303,6 +1331,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy loraPromptApply": "Easy Apply LoraPrompt",
|
||||
"easy loraStackApply": "Easy Apply LoraStack",
|
||||
"easy controlnetStackApply": "Easy Apply CnetStack",
|
||||
"easy ipadapterApply": "Easy Apply IPAdapter",
|
||||
|
||||
+13
-35
@@ -3,37 +3,8 @@ from ..libs.api.fluxai import fluxaiAPI
|
||||
from ..libs.api.bizyair import bizyairAPI, encode_data
|
||||
from nodes import NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS
|
||||
|
||||
class fluxPromptGenAPI:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"describe": ("STRING", {"default": "", "placeholder": "Describe your image idea (you can use any language)", "multiline": True}),
|
||||
},
|
||||
"optional": {
|
||||
"cookie_override": ("STRING", {"default": "", "forceInput": True}),
|
||||
},
|
||||
"hidden": {
|
||||
"prompt": "PROMPT",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
|
||||
FUNCTION = "generate"
|
||||
OUTPUT_NODE = False
|
||||
|
||||
CATEGORY = "EasyUse/API"
|
||||
|
||||
def generate(self, describe, cookie_override=None, prompt=None, unique_id=None, extra_pnginfo=None):
|
||||
prompt = fluxaiAPI.promptGenerate(describe, cookie_override)
|
||||
return (prompt,)
|
||||
|
||||
class joyCaption2API:
|
||||
API_URL = f"/supernode/joycaption2"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -101,18 +72,21 @@ class joyCaption2API:
|
||||
"multiline": True,
|
||||
},
|
||||
),
|
||||
},
|
||||
"optional":{
|
||||
"apikey_override": ("STRING", {"default": "", "forceInput": True, "tooltip":"Override the API key in the local config"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("caption",)
|
||||
|
||||
FUNCTION = "joycaption2"
|
||||
FUNCTION = "joycaption"
|
||||
OUTPUT_NODE = False
|
||||
|
||||
CATEGORY = "EasyUse/API"
|
||||
|
||||
def joycaption2(
|
||||
def joycaption(
|
||||
self,
|
||||
image,
|
||||
do_sample,
|
||||
@@ -123,6 +97,7 @@ class joyCaption2API:
|
||||
extra_options,
|
||||
name_input,
|
||||
custom_prompt,
|
||||
apikey_override=None
|
||||
):
|
||||
pbar = comfy.utils.ProgressBar(100)
|
||||
pbar.update_absolute(10)
|
||||
@@ -145,17 +120,20 @@ class joyCaption2API:
|
||||
}
|
||||
|
||||
pbar.update_absolute(30)
|
||||
caption = bizyairAPI.joyCaption2(payload, image)
|
||||
caption = bizyairAPI.joyCaption(payload, image, apikey_override, API_URL=self.API_URL)
|
||||
|
||||
pbar.update_absolute(100)
|
||||
return (caption,)
|
||||
|
||||
class joyCaption3API(joyCaption2API):
|
||||
API_URL = f"/supernode/joycaption3"
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"easy fluxPromptGenAPI": fluxPromptGenAPI,
|
||||
"easy joyCaption2API": joyCaption2API,
|
||||
"easy joyCaption3API": joyCaption3API,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy fluxPromptGenAPI": "Prompt Gen (FluxAI)",
|
||||
"easy joyCaption2API": "JoyCaption2 (BizyAIR)",
|
||||
"easy joyCaption3API": "JoyCaption3 (BizyAIR)",
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import numpy as np
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
import folder_paths
|
||||
import comfy
|
||||
import comfy.model_management
|
||||
from PIL import Image
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
from nodes import ConditioningSetMask, RepeatLatentBatch
|
||||
from comfy_extras.nodes_mask import LatentCompositeMasked
|
||||
|
||||
+201
-116
@@ -4,19 +4,19 @@ import torch
|
||||
import numpy as np
|
||||
import comfy.utils
|
||||
import comfy.model_management
|
||||
import shutil
|
||||
from comfy_extras.nodes_compositing import JoinImageWithAlpha
|
||||
from server import PromptServer
|
||||
from nodes import MAX_RESOLUTION, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageOps
|
||||
import torch.nn.functional as F
|
||||
from torchvision.transforms import Resize, CenterCrop, GaussianBlur
|
||||
from torchvision.transforms import Resize, CenterCrop, GaussianBlur, ToPILImage
|
||||
from torchvision.transforms.functional import to_pil_image
|
||||
from ..libs.log import log_node_info
|
||||
from ..libs.utils import AlwaysEqualProxy, ByPassTypeTuple
|
||||
from ..libs.cache import cache, update_cache, remove_cache
|
||||
from ..libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds, RGB2RGBA, image2mask, empty_image
|
||||
from ..libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds, RGB2RGBA, image2mask, empty_image, fit_resize_image
|
||||
from ..libs.colorfix import adain_color_fix, wavelet_color_fix
|
||||
from ..libs.chooser import ChooserMessage, ChooserCancelled
|
||||
from ..config import REMBG_DIR, REMBG_MODELS, HUMANPARSING_MODELS, MEDIAPIPE_MODELS, MEDIAPIPE_DIR
|
||||
|
||||
any_type = AlwaysEqualProxy("*")
|
||||
@@ -485,8 +485,7 @@ class imageSaveSimple:
|
||||
|
||||
def save(self, images, filename_prefix="ComfyUI", only_preview=False, prompt=None, extra_pnginfo=None):
|
||||
if only_preview:
|
||||
PreviewImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
|
||||
return ()
|
||||
return PreviewImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
|
||||
else:
|
||||
return SaveImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
|
||||
|
||||
@@ -803,7 +802,20 @@ class imageConcat:
|
||||
elif image2 is None:
|
||||
return (image1,)
|
||||
if match_image_size:
|
||||
image2 = torch.nn.functional.interpolate(image2, size=(image1.shape[2], image1.shape[3]), mode="bilinear")
|
||||
# Convert tensor to PIL for proper aspect ratio resizing
|
||||
pil_image2 = tensor2pil(image2)
|
||||
if direction in ['right', 'left']:
|
||||
aspect_ratio = pil_image2.width / pil_image2.height
|
||||
new_height = image1.shape[1]
|
||||
new_width = int(aspect_ratio * new_height)
|
||||
pil_image2 = fit_resize_image(pil_image2, new_width, new_height, 'fill', Image.LANCZOS, '#000000')
|
||||
else: # 'up' or 'down'
|
||||
aspect_ratio = pil_image2.height / pil_image2.width
|
||||
new_width = image1.shape[2]
|
||||
new_height = int(aspect_ratio * new_width)
|
||||
pil_image2 = fit_resize_image(pil_image2, new_width, new_height, 'fill', Image.LANCZOS, '#000000')
|
||||
image2 = pil2tensor(pil_image2)
|
||||
|
||||
if direction == 'right':
|
||||
row = torch.cat((image1, image2), dim=2)
|
||||
elif direction == 'down':
|
||||
@@ -828,7 +840,8 @@ class imageRemBg:
|
||||
},
|
||||
"optional":{
|
||||
"torchscript_jit": ("BOOLEAN", {"default": False}),
|
||||
"add_background": (["none", "white", "black"], {"default": "none"})
|
||||
"add_background": (["none", "white", "black"], {"default": "none"}),
|
||||
"refine_foreground": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
@@ -841,7 +854,7 @@ class imageRemBg:
|
||||
CATEGORY = "EasyUse/Image"
|
||||
|
||||
|
||||
def remove(self, rem_mode, images, image_output, save_prefix, torchscript_jit=False, add_background='none',prompt=None, extra_pnginfo=None):
|
||||
def remove(self, rem_mode, images, image_output, save_prefix, torchscript_jit=False, add_background='none', refine_foreground=False, prompt=None, extra_pnginfo=None):
|
||||
new_images = list()
|
||||
masks = list()
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
@@ -941,7 +954,7 @@ class imageRemBg:
|
||||
if input_image.mode != 'RGBA':
|
||||
input_image = input_image.convert("RGBA")
|
||||
|
||||
mask, new_im = model.inference(input_image)
|
||||
mask, new_im = model.inference(input_image, refine_foreground)
|
||||
|
||||
new_im_tensor = pil2tensor(new_im)
|
||||
mask_tensor = pil2tensor(mask)
|
||||
@@ -992,6 +1005,7 @@ class imageRemBg:
|
||||
"result": (new_images, masks)}
|
||||
|
||||
# 图像选择器
|
||||
from ..libs.chooser import wait_for_chooser
|
||||
class imageChooser(PreviewImage):
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
@@ -1028,50 +1042,30 @@ class imageChooser(PreviewImage):
|
||||
def chooser(self, prompt=None, my_unique_id=None, extra_pnginfo=None, **kwargs):
|
||||
id = my_unique_id[0]
|
||||
id = id.split('.')[len(id.split('.')) - 1] if "." in id else id
|
||||
if id not in ChooserMessage.stash:
|
||||
ChooserMessage.stash[id] = {}
|
||||
my_stash = ChooserMessage.stash[id]
|
||||
|
||||
# enable stashing. If images is None, we are operating in read-from-stash mode
|
||||
if 'images' in kwargs:
|
||||
my_stash['images'] = kwargs['images']
|
||||
else:
|
||||
kwargs['images'] = my_stash.get('images', None)
|
||||
|
||||
if (kwargs['images'] is None):
|
||||
return (None, None, None, "")
|
||||
return (None,)
|
||||
|
||||
images_in = torch.cat(kwargs.pop('images'))
|
||||
self.batch = images_in.shape[0]
|
||||
for x in kwargs: kwargs[x] = kwargs[x][0]
|
||||
result = self.save_images(images=images_in, prompt=prompt)
|
||||
|
||||
images = result['ui']['images']
|
||||
PromptServer.instance.send_sync("easyuse-image-choose", {"id": id, "urls": images})
|
||||
try:
|
||||
pnginfo = extra_pnginfo[0]
|
||||
except:
|
||||
pnginfo = None
|
||||
result = self.save_images(images=images_in, prompt=prompt, extra_pnginfo=pnginfo)
|
||||
if "ui" in result and "images" in result['ui']:
|
||||
images = result["ui"]["images"]
|
||||
else:
|
||||
images = []
|
||||
try:
|
||||
PromptServer.instance.send_sync("easyuse-image-choose", {"id": id, "urls": images})
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# 获取上次选择
|
||||
mode = kwargs.pop('mode', 'Always Pause')
|
||||
last_choosen = None
|
||||
if mode == 'Keep Last Selection':
|
||||
if not extra_pnginfo:
|
||||
print("Error: extra_pnginfo is empty")
|
||||
elif (not isinstance(extra_pnginfo[0], dict) or "workflow" not in extra_pnginfo[0]):
|
||||
print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key")
|
||||
else:
|
||||
workflow = extra_pnginfo[0]["workflow"]
|
||||
node = next((x for x in workflow["nodes"] if str(x["id"]) == id), None)
|
||||
if node:
|
||||
last_choosen = node['properties']['values']
|
||||
|
||||
# wait for selection
|
||||
try:
|
||||
selections = ChooserMessage.waitForMessage(id, asList=True) if last_choosen is None or len(last_choosen)<1 else last_choosen
|
||||
choosen = [x for x in selections if x >= 0] if len(selections)>1 else [0]
|
||||
except ChooserCancelled:
|
||||
raise comfy.model_management.InterruptProcessingException()
|
||||
|
||||
return {"ui": {"images": images},
|
||||
"result": (self.tensor_bundle(images_in, choosen),)}
|
||||
return wait_for_chooser(id, images_in, mode)
|
||||
|
||||
class imageColorMatch(PreviewImage):
|
||||
@classmethod
|
||||
@@ -1259,13 +1253,22 @@ class humanSegmentation:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
|
||||
return {
|
||||
"required":{
|
||||
"image": ("IMAGE",),
|
||||
"method": (["selfie_multiclass_256x256", "human_parsing_lip", "human_parts (deeplabv3p)"],),
|
||||
"method": (["selfie_multiclass_256x256", "human_parsing_lip", "human_parts (deeplabv3p)", "segformer_b3_clothes", "segformer_b3_fashion", "face_parsing"],),
|
||||
"confidence": ("FLOAT", {"default": 0.4, "min": 0.05, "max": 0.95, "step": 0.01},),
|
||||
"crop_multi": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.001},),
|
||||
"mask_components":(
|
||||
"EASY_COMBO",{
|
||||
"options": [{'label':'Background','value':0}],
|
||||
"multi_select": {
|
||||
"placeholder": "select mask components",
|
||||
"chip": True,
|
||||
"max_selected_labels": 4,
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
"hidden": {
|
||||
"prompt": "PROMPT",
|
||||
@@ -1291,12 +1294,7 @@ class humanSegmentation:
|
||||
numpy_image = cv2.cvtColor(numpy_image, cv2.COLOR_BGR2RGB)
|
||||
return mp.Image(image_format=image_format, data=numpy_image)
|
||||
|
||||
def parsing(self, image, confidence, method, crop_multi, prompt=None, my_unique_id=None):
|
||||
mask_components = []
|
||||
if my_unique_id in prompt:
|
||||
if prompt[my_unique_id]["inputs"]['mask_components']:
|
||||
mask_components = prompt[my_unique_id]["inputs"]['mask_components'].split(',')
|
||||
mask_components = list(map(int, mask_components))
|
||||
def parsing(self, image, confidence, method, crop_multi, mask_components, prompt=None, my_unique_id=None):
|
||||
if method == 'selfie_multiclass_256x256':
|
||||
try:
|
||||
import mediapipe as mp
|
||||
@@ -1424,6 +1422,107 @@ class humanSegmentation:
|
||||
output_image = torch.cat(ret_images, dim=0)
|
||||
mask = torch.cat(ret_masks, dim=0)
|
||||
|
||||
elif method in ["segformer_b3_clothes", "segformer_b3_fashion", "face_parsing"]:
|
||||
from transformers import SegformerImageProcessor, AutoModelForSemanticSegmentation
|
||||
|
||||
# 分割
|
||||
def get_segmentation_from_model(tensor_image, model, processor):
|
||||
cloth = tensor2pil(tensor_image)
|
||||
inputs = processor(images=cloth, return_tensors="pt")
|
||||
outputs = model(**inputs)
|
||||
logits = outputs.logits.cpu()
|
||||
upsampled_logits = F.interpolate(logits, size=cloth.size[::-1], mode="bilinear",
|
||||
align_corners=False)
|
||||
pred_seg = upsampled_logits.argmax(dim=1)[0].numpy()
|
||||
return pred_seg, cloth
|
||||
|
||||
|
||||
if method in cache:
|
||||
_, (processor, model) = cache[method][1]
|
||||
else:
|
||||
model_folder_path = os.path.join(folder_paths.models_dir, method)
|
||||
if os.path.exists(model_folder_path):
|
||||
print(f"Start to load existing model...")
|
||||
else:
|
||||
from huggingface_hub import snapshot_download
|
||||
PromptServer.instance.send_sync("easyuse-toast", {"content": f"Model not found locally. Downloading {method}...", "type": 'loading', "duration": 10000})
|
||||
print(f"Model not found locally. Downloading {method}...")
|
||||
model_path_cache = os.path.join(folder_paths.models_dir, "cache-"+method)
|
||||
snapshot_download(
|
||||
repo_id=HUMANPARSING_MODELS[method]['model_name'],
|
||||
local_dir=model_path_cache,
|
||||
local_dir_use_symlinks=False,
|
||||
resume_download=True
|
||||
)
|
||||
shutil.move(model_path_cache, model_folder_path)
|
||||
print(f"Model downloaded to {model_folder_path}...")
|
||||
try:
|
||||
model_folder_path = os.path.normpath(folder_paths.folder_names_and_paths[method][0][0])
|
||||
except:
|
||||
pass
|
||||
|
||||
processor = SegformerImageProcessor.from_pretrained(model_folder_path)
|
||||
model = AutoModelForSemanticSegmentation.from_pretrained(model_folder_path)
|
||||
update_cache(method, 'human_segmentation', (False, (processor, model)))
|
||||
|
||||
ret_images = []
|
||||
ret_masks = []
|
||||
|
||||
if method == "face_parsing":
|
||||
import matplotlib
|
||||
import torchvision.transforms as T
|
||||
transform = ToPILImage()
|
||||
colormap = matplotlib.colormaps['viridis']
|
||||
device = model.device
|
||||
results = []
|
||||
images = []
|
||||
for img in image:
|
||||
size = img.shape[:2]
|
||||
inputs = processor(images=transform(img.permute(2, 0, 1)), return_tensors="pt")
|
||||
inputs = {k: v.to(device) for k, v in inputs.items()}
|
||||
outputs = model(**inputs)
|
||||
logits = outputs.logits
|
||||
upsampled_logits = F.interpolate(
|
||||
logits,
|
||||
size=size,
|
||||
mode="bilinear",
|
||||
align_corners=False)
|
||||
|
||||
pred_seg = upsampled_logits.argmax(dim=1)[0]
|
||||
pred_seg_np = pred_seg.cpu().detach().numpy().astype(np.uint8)
|
||||
results.append(torch.tensor(pred_seg_np))
|
||||
|
||||
results_out = torch.stack(results, dim=0)
|
||||
for img, result_item in zip(image, results_out):
|
||||
mask = torch.zeros(result_item.shape, dtype=torch.uint8)
|
||||
for i in mask_components:
|
||||
mask = mask | torch.where(result_item == i, 1, 0)
|
||||
|
||||
# 将mask转换为numpy数组,并确保数据类型正确
|
||||
mask_np = (mask * 255).numpy().astype(np.uint8)
|
||||
_mask = Image.fromarray(mask_np)
|
||||
|
||||
# 处理图像输出
|
||||
ret_image = RGB2RGBA(tensor2pil(img).convert('RGB'), _mask.convert('L'))
|
||||
ret_images.append(pil2tensor(ret_image))
|
||||
ret_masks.append(image2mask(_mask))
|
||||
|
||||
else:
|
||||
for img in image:
|
||||
pred_seg, cloth = get_segmentation_from_model(img, model, processor)
|
||||
i = torch.unsqueeze(img, 0)
|
||||
i = pil2tensor(tensor2pil(i).convert('RGB'))
|
||||
|
||||
mask = np.isin(pred_seg, mask_components).astype(np.uint8)
|
||||
_mask = Image.fromarray(mask * 255)
|
||||
|
||||
ret_image = RGB2RGBA(tensor2pil(img).convert('RGB'), _mask.convert('L'))
|
||||
ret_images.append(pil2tensor(ret_image))
|
||||
ret_masks.append(image2mask(_mask))
|
||||
|
||||
output_image = torch.cat(ret_images, dim=0)
|
||||
mask = torch.cat(ret_masks, dim=0)
|
||||
|
||||
# use crop
|
||||
bbox = [[0, 0, 0, 0]]
|
||||
if crop_multi > 0.0:
|
||||
@@ -1745,7 +1844,7 @@ class imageToBase64:
|
||||
pil_image = tensor2pil(image)
|
||||
|
||||
buffered = BytesIO()
|
||||
pil_image.save(buffered, format="JPEG")
|
||||
pil_image.save(buffered, format="PNG")
|
||||
image_bytes = buffered.getvalue()
|
||||
|
||||
base64_str = base64.b64encode(image_bytes).decode("utf-8")
|
||||
@@ -1874,47 +1973,6 @@ class loadImagesForLoop:
|
||||
"result": tuple(["stub", index, image, mask, name] + outputs),
|
||||
"expand": graph.finalize(),
|
||||
}
|
||||
# 姿势编辑器
|
||||
# class poseEditor:
|
||||
# @classmethod
|
||||
# def INPUT_TYPES(self):
|
||||
# temp_dir = folder_paths.get_temp_directory()
|
||||
#
|
||||
# if not os.path.isdir(temp_dir):
|
||||
# os.makedirs(temp_dir)
|
||||
#
|
||||
# temp_dir = folder_paths.get_temp_directory()
|
||||
#
|
||||
# return {"required":
|
||||
# {"image": (sorted(os.listdir(temp_dir)),)},
|
||||
# }
|
||||
#
|
||||
# RETURN_TYPES = ("IMAGE",)
|
||||
# FUNCTION = "output_pose"
|
||||
#
|
||||
# CATEGORY = "EasyUse/🚫 Deprecated"
|
||||
#
|
||||
# def output_pose(self, image):
|
||||
# image_path = os.path.join(folder_paths.get_temp_directory(), image)
|
||||
# # print(f"Create: {image_path}")
|
||||
#
|
||||
# i = Image.open(image_path)
|
||||
# image = i.convert("RGB")
|
||||
# image = np.array(image).astype(np.float32) / 255.0
|
||||
# image = torch.from_numpy(image)[None,]
|
||||
#
|
||||
# return (image,)
|
||||
#
|
||||
# @classmethod
|
||||
# def IS_CHANGED(self, image):
|
||||
# image_path = os.path.join(
|
||||
# folder_paths.get_temp_directory(), image)
|
||||
# # print(f'Change: {image_path}')
|
||||
#
|
||||
# m = hashlib.sha256()
|
||||
# with open(image_path, 'rb') as f:
|
||||
# m.update(f.read())
|
||||
# return m.digest().hex()
|
||||
|
||||
class makeImageForICRepaint:
|
||||
@classmethod
|
||||
@@ -1924,6 +1982,7 @@ class makeImageForICRepaint:
|
||||
"image_1": ("IMAGE",),
|
||||
"direction": (["top-bottom", "left-right"], {"default": "left-right"}),
|
||||
"pixels": ("INT", {"default": 0, "max": MAX_RESOLUTION, "min": 0, "step": 8, "tooltip": "The pixel of the output image is not set when it is 0"}),
|
||||
"method": (["uniform height", "uniform width", "auto"],{"default": "auto"}),
|
||||
},
|
||||
"optional": {
|
||||
"image_2": ("IMAGE",),
|
||||
@@ -1950,26 +2009,52 @@ class makeImageForICRepaint:
|
||||
b = torch.full([batch_size, height, width, 1], ((color) & 0xFF) / 0xFF)
|
||||
return torch.cat((r, g, b), dim=-1)
|
||||
|
||||
def make(self, image_1, direction, pixels=0, image_2=None, mask_1=None, mask_2=None):
|
||||
def resize_image_and_mask(self, image, mask, w, h ,fit='fill'):
|
||||
ret_images = []
|
||||
ret_masks = []
|
||||
_mask = Image.new('L', size=(w, h), color='black')
|
||||
_image = Image.new('RGB', size=(w, h), color='black')
|
||||
if image is not None and len(image) > 0:
|
||||
for i in image:
|
||||
_image = tensor2pil(i).convert('RGB')
|
||||
_image = fit_resize_image(_image, w, h, fit, Image.LANCZOS, '#000000')
|
||||
ret_images.append(pil2tensor(_image))
|
||||
if mask is not None and len(mask) > 0:
|
||||
for m in mask:
|
||||
_mask = tensor2pil(m).convert('L')
|
||||
_mask = fit_resize_image(_mask, w, h, fit, Image.LANCZOS).convert('L')
|
||||
ret_masks.append(image2mask(_mask))
|
||||
|
||||
if len(ret_images) > 0 and len(ret_masks) > 0:
|
||||
return (torch.cat(ret_images, dim=0), torch.cat(ret_masks, dim=0),)
|
||||
elif len(ret_images) > 0 and len(ret_masks) == 0:
|
||||
return (torch.cat(ret_images, dim=0), None,)
|
||||
elif len(ret_images) == 0 and len(ret_masks) > 0:
|
||||
return (None, torch.cat(ret_masks, dim=0),)
|
||||
else:
|
||||
return (None, None)
|
||||
|
||||
def make(self, image_1, direction, pixels, method, image_2=None, mask_1=None, mask_2=None):
|
||||
if image_2 is None:
|
||||
image_2 = self.emptyImage(image_1.shape[2], image_1.shape[1])
|
||||
mask_2 = torch.full((1, image_1.shape[1], image_1.shape[2]), 1, dtype=torch.float32, device="cpu")
|
||||
|
||||
elif image_2 is not None and mask_2 is None:
|
||||
raise ValueError("mask_2 is required when image_2 is provided")
|
||||
mask_2 = torch.full((1, image_2.shape[1], image_2.shape[2]), 1, dtype=torch.float32, device="cpu")
|
||||
|
||||
if pixels > 0:
|
||||
_, img2_h, img2_w, _ = image_2.shape
|
||||
h = pixels if direction == 'left-right' else int(img2_h * (pixels / img2_w))
|
||||
w = pixels if direction == 'top-bottom' else int(img2_w * (pixels / img2_h))
|
||||
if method == "uniform height":
|
||||
h = pixels
|
||||
w = int(img2_w * (pixels / img2_h))
|
||||
elif method == "uniform width":
|
||||
w = pixels
|
||||
h = int(img2_h * (pixels / img2_w))
|
||||
else:
|
||||
h = pixels if direction == 'left-right' else int(img2_h * (pixels / img2_w))
|
||||
w = pixels if direction == 'top-bottom' else int(img2_w * (pixels / img2_h))
|
||||
|
||||
image_2 = image_2.movedim(-1, 1)
|
||||
image_2 = comfy.utils.common_upscale(image_2, w, h, 'bicubic', 'disabled')
|
||||
image_2 = image_2.movedim(1, -1)
|
||||
|
||||
orig_image_2 = tensor2pil(image_2)
|
||||
orig_mask_2 = tensor2pil(mask_2).convert('L')
|
||||
orig_mask_2 = orig_mask_2.resize(orig_image_2.size)
|
||||
mask_2 = pil2tensor(orig_mask_2)
|
||||
image_2, mask_2 = self.resize_image_and_mask(image_2, mask_2, w, h)
|
||||
|
||||
_, img1_h, img1_w, _ = image_1.shape
|
||||
_, img2_h, img2_w, _ = image_2.shape
|
||||
@@ -1979,16 +2064,16 @@ class makeImageForICRepaint:
|
||||
# resize
|
||||
if img1_h != img2_h and img1_w != img2_w:
|
||||
width, height = img2_w, img2_h
|
||||
if direction == 'left-right' and img1_h != img2_h:
|
||||
scale_factor = img2_h / img1_h
|
||||
width = round(img1_w * scale_factor)
|
||||
elif direction == 'top-bottom' and img1_w != img2_w:
|
||||
scale_factor = img2_w / img1_w
|
||||
height = round(img1_h * scale_factor)
|
||||
|
||||
image_1 = image_1.movedim(-1, 1)
|
||||
image_1 = comfy.utils.common_upscale(image_1, width, height, 'bicubic', 'disabled')
|
||||
image_1 = image_1.movedim(1, -1)
|
||||
fit = 'crop'
|
||||
if method != 'uniform width':
|
||||
if direction == 'left-right' and img1_h != img2_h:
|
||||
scale_factor = img2_h / img1_h
|
||||
width = round(img1_w * scale_factor)
|
||||
elif direction == 'top-bottom' and img1_w != img2_w:
|
||||
scale_factor = img2_w / img1_w
|
||||
height = round(img1_h * scale_factor)
|
||||
fit = 'fill'
|
||||
image_1, mask_1 = self.resize_image_and_mask(image_1, mask_1, width, height, fit)
|
||||
|
||||
if mask_1 is None:
|
||||
mask_1 = torch.full((1, image_1.shape[1], image_1.shape[2]), 0, dtype=torch.float32, device="cpu")
|
||||
|
||||
+60
-4
@@ -8,7 +8,7 @@ from nodes import MAX_RESOLUTION, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS
|
||||
|
||||
from ..libs.log import log_node_info, log_node_error, log_node_warn
|
||||
from ..libs.wildcards import process_with_loras
|
||||
from ..libs.utils import find_wildcards_seed, is_linked_styles_selector, get_sd_version
|
||||
from ..libs.utils import find_wildcards_seed, is_linked_styles_selector, get_sd_version, AlwaysEqualProxy
|
||||
from ..libs.sampler import easySampler
|
||||
from ..libs.controlnet import easyControlnet, union_controlnet_types
|
||||
from ..libs.conditioning import prompt_to_cond
|
||||
@@ -19,6 +19,7 @@ from ..config import *
|
||||
|
||||
from .. import easyCache, sampler
|
||||
|
||||
any_type = AlwaysEqualProxy("*")
|
||||
# 简易加载器完整
|
||||
resolution_strings = [f"{width} x {height} (custom)" if width == 'width' and height == 'height' else f"{width} x {height}" for width, height in BASE_RESOLUTIONS]
|
||||
class fullLoader:
|
||||
@@ -28,7 +29,7 @@ class fullLoader:
|
||||
a1111_prompt_style_default = False
|
||||
|
||||
return {"required": {
|
||||
"ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
|
||||
"ckpt_name": (folder_paths.get_filename_list("checkpoints") + ['None'],),
|
||||
"config_name": (["Default", ] + folder_paths.get_filename_list("configs"), {"default": "Default"}),
|
||||
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
|
||||
"clip_skip": ("INT", {"default": -2, "min": -24, "max": 0, "step": 1}),
|
||||
@@ -71,6 +72,9 @@ class fullLoader:
|
||||
my_unique_id=None
|
||||
):
|
||||
|
||||
if ckpt_name == 'None' and model_override is None:
|
||||
raise Exception("Please select a checkpoint or provide a model override.")
|
||||
|
||||
# Clean models from loaded_objects
|
||||
easyCache.update_loaded_objects(prompt)
|
||||
|
||||
@@ -923,7 +927,7 @@ class fluxLoader(fullLoader):
|
||||
loras = ["None"] + folder_paths.get_filename_list("loras")
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (checkpoints,),
|
||||
"ckpt_name": (checkpoints + ['None'],),
|
||||
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
|
||||
"lora_name": (loras,),
|
||||
"lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
@@ -1143,6 +1147,56 @@ class mochiLoader(fullLoader):
|
||||
my_unique_id=my_unique_id
|
||||
)
|
||||
# lora
|
||||
class loraSwitcher:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
max_lora_num = 50
|
||||
inputs = {
|
||||
"required": {
|
||||
"toggle": ("BOOLEAN", {"label_on": "on", "label_off": "off"}),
|
||||
"select": ("INT", {"default": 1, "min": 1, "max": max_lora_num}),
|
||||
"num_loras": ("INT", {"default": 1, "min": 1, "max": max_lora_num}),
|
||||
"lora_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
|
||||
},
|
||||
"optional": {
|
||||
"optional_lora_stack": ("LORA_STACK",),
|
||||
},
|
||||
}
|
||||
|
||||
for i in range(1, max_lora_num + 1):
|
||||
inputs["optional"][f"lora_{i}_name"] = (
|
||||
["None"] + folder_paths.get_filename_list("loras"), {"default": "None"})
|
||||
|
||||
return inputs
|
||||
|
||||
RETURN_TYPES = ("LORA_STACK", any_type)
|
||||
RETURN_NAMES = ("lora_stack", "lora_name")
|
||||
FUNCTION = "stack"
|
||||
|
||||
CATEGORY = "EasyUse/Loaders"
|
||||
|
||||
def stack(self, toggle, select,num_loras, lora_strength, optional_lora_stack=None, **kwargs):
|
||||
if (toggle in [False, None, "False"]) or not kwargs:
|
||||
return (None,'')
|
||||
|
||||
loras = []
|
||||
|
||||
# Import Stack values
|
||||
if optional_lora_stack is not None:
|
||||
loras.extend([l for l in optional_lora_stack if l[0] != "None"])
|
||||
|
||||
# Import Lora values
|
||||
lora_name = kwargs.get(f"lora_{select}_name")
|
||||
|
||||
if not lora_name or lora_name == "None":
|
||||
return (None,'')
|
||||
|
||||
loras.append((lora_name, lora_strength, lora_strength))
|
||||
|
||||
name = os.path.splitext(os.path.basename(str(lora_name)))[0]
|
||||
return (loras, name)
|
||||
|
||||
|
||||
class loraStack:
|
||||
def __init__(self):
|
||||
pass
|
||||
@@ -1152,7 +1206,7 @@ class loraStack:
|
||||
max_lora_num = 10
|
||||
inputs = {
|
||||
"required": {
|
||||
"toggle": ("BOOLEAN", {"label_on": "enabled", "label_off": "disabled"}),
|
||||
"toggle": ("BOOLEAN", {"label_on": "on", "label_off": "off"}),
|
||||
"mode": (["simple", "advanced"],),
|
||||
"num_loras": ("INT", {"default": 1, "min": 1, "max": max_lora_num}),
|
||||
},
|
||||
@@ -1479,6 +1533,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy hunyuanDiTLoader": hunyuanDiTLoader,
|
||||
"easy pixArtLoader": pixArtLoader,
|
||||
"easy mochiLoader": mochiLoader,
|
||||
"easy loraSwitcher": loraSwitcher,
|
||||
"easy loraStack": loraStack,
|
||||
"easy controlnetStack": controlnetStack,
|
||||
"easy controlnetLoader": controlnetSimple,
|
||||
@@ -1500,6 +1555,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy hunyuanDiTLoader": "EasyLoader (HunyuanDiT)",
|
||||
"easy pixArtLoader": "EasyLoader (PixArt)",
|
||||
"easy mochiLoader": "EasyLoader (Mochi)",
|
||||
"easy loraSwitcher": "EasyLoraSwitcher",
|
||||
"easy loraStack": "EasyLoraStack",
|
||||
"easy controlnetStack": "EasyControlnetStack",
|
||||
"easy controlnetLoader": "EasyControlnet",
|
||||
|
||||
+24
-20
@@ -18,7 +18,7 @@ import comfy.utils
|
||||
import folder_paths
|
||||
|
||||
DEFAULT_FLOW_NUM = 2
|
||||
MAX_FLOW_NUM = 10
|
||||
MAX_FLOW_NUM = 20
|
||||
lazy_options = {"lazy": True} if compare_revision(2543) else {}
|
||||
|
||||
any_type = AlwaysEqualProxy("*")
|
||||
@@ -166,7 +166,7 @@ class Float:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {"value": ("FLOAT", {"default": 0, "step": 0.01, "min": -999999, "max": 999999, })},
|
||||
"required": {"value": ("FLOAT", {"default": 0, "step": 0.01, "min":-0xffffffffffffffff, "max": 0xffffffffffffffff, })},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
@@ -175,7 +175,7 @@ class Float:
|
||||
CATEGORY = "EasyUse/Logic/Type"
|
||||
|
||||
def execute(self, value):
|
||||
return (value,)
|
||||
return (round(value, 3),)
|
||||
|
||||
|
||||
# 浮点数范围
|
||||
@@ -239,9 +239,9 @@ class RangeFloat:
|
||||
error_if_mismatched_list_args(locals())
|
||||
getcontext().prec = 12
|
||||
|
||||
start = [Decimal(s) for s in start]
|
||||
stop = [Decimal(s) for s in stop]
|
||||
step = [Decimal(s) for s in step]
|
||||
start = [round(Decimal(s),2) for s in start]
|
||||
stop = [round(Decimal(s),2) for s in stop]
|
||||
step = [round(Decimal(s),2) for s in step]
|
||||
|
||||
ranges = []
|
||||
range_sizes = []
|
||||
@@ -573,17 +573,17 @@ class mathFloatOperation:
|
||||
|
||||
def float_math_operation(self, a, b, operation):
|
||||
if operation == "add":
|
||||
return (a + b,)
|
||||
return (round(a + b,3),)
|
||||
elif operation == "subtract":
|
||||
return (a - b,)
|
||||
return (round(a - b,3),)
|
||||
elif operation == "multiply":
|
||||
return (a * b,)
|
||||
return (round(a * b,3),)
|
||||
elif operation == "divide":
|
||||
return (a / b,)
|
||||
return (round(a / b,3),)
|
||||
elif operation == "modulo":
|
||||
return (a % b,)
|
||||
return (round(a % b,3),)
|
||||
elif operation == "power":
|
||||
return (a ** b,)
|
||||
return (round(a ** b,3),)
|
||||
|
||||
|
||||
class mathStringOperation:
|
||||
@@ -1369,10 +1369,12 @@ class showAnything:
|
||||
if "anything" in kwargs:
|
||||
for val in kwargs['anything']:
|
||||
try:
|
||||
if type(val) is str:
|
||||
if isinstance(val, str):
|
||||
values.append(val)
|
||||
elif type(val) is list:
|
||||
elif isinstance(val, list):
|
||||
values = val
|
||||
elif isinstance(val, (int, float, bool)):
|
||||
values.append(str(val))
|
||||
else:
|
||||
val = json.dumps(val)
|
||||
values.append(str(val))
|
||||
@@ -1381,9 +1383,9 @@ class showAnything:
|
||||
pass
|
||||
|
||||
if not extra_pnginfo:
|
||||
print("Error: extra_pnginfo is empty")
|
||||
pass
|
||||
elif (not isinstance(extra_pnginfo[0], dict) or "workflow" not in extra_pnginfo[0]):
|
||||
print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key")
|
||||
pass
|
||||
else:
|
||||
workflow = extra_pnginfo[0]["workflow"]
|
||||
node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id[0]), None)
|
||||
@@ -1607,8 +1609,10 @@ class saveText:
|
||||
if not os.path.exists(output_file_path):
|
||||
os.makedirs(output_file_path)
|
||||
|
||||
if not overwrite:
|
||||
pass
|
||||
if overwrite:
|
||||
file_mode = "w"
|
||||
else:
|
||||
file_mode = "a"
|
||||
|
||||
log_node_info("Save Text", f"Saving to {filepath}")
|
||||
|
||||
@@ -1617,13 +1621,13 @@ class saveText:
|
||||
for i in text.split("\n"):
|
||||
text_list.append(i.strip())
|
||||
|
||||
with open(filepath, "w", newline="", encoding='utf-8') as csv_file:
|
||||
with open(filepath, file_mode, newline="", encoding='utf-8') as csv_file:
|
||||
csv_writer = csv.writer(csv_file)
|
||||
# Write each line as a separate row in the CSV file
|
||||
for line in text_list:
|
||||
csv_writer.writerow([line])
|
||||
else:
|
||||
with open(filepath, "w", newline="", encoding='utf-8') as text_file:
|
||||
with open(filepath, file_mode, newline="", encoding='utf-8') as text_file:
|
||||
for line in text:
|
||||
text_file.write(line)
|
||||
|
||||
|
||||
+124
-22
@@ -1,12 +1,15 @@
|
||||
import os
|
||||
import json
|
||||
import folder_paths
|
||||
import os
|
||||
from urllib.request import urlopen
|
||||
from ..libs.log import log_node_info
|
||||
from ..libs.wildcards import get_wildcard_list, process
|
||||
from ..libs.utils import AlwaysEqualProxy
|
||||
from ..config import RESOURCES_DIR, FOOOCUS_STYLES_DIR, MAX_SEED_NUM, PROMPT_TEMPLATE
|
||||
|
||||
import folder_paths
|
||||
|
||||
from .. import easyCache
|
||||
from ..config import FOOOCUS_STYLES_DIR, MAX_SEED_NUM, PROMPT_TEMPLATE, RESOURCES_DIR
|
||||
from ..libs.log import log_node_info
|
||||
from ..libs.utils import AlwaysEqualProxy
|
||||
from ..libs.wildcards import WildcardProcessor, get_wildcard_list, process
|
||||
|
||||
|
||||
# 正面提示词
|
||||
class positivePrompt:
|
||||
@@ -40,7 +43,7 @@ class wildcardsPrompt:
|
||||
def INPUT_TYPES(s):
|
||||
wildcard_list = get_wildcard_list()
|
||||
return {"required": {
|
||||
"text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support Lora Block Weight and wildcard)"}),
|
||||
"text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support wildcard)"}),
|
||||
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),),
|
||||
"Select to add Wildcard": (["Select the Wildcard to add to the text"] + wildcard_list,),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
|
||||
@@ -56,9 +59,6 @@ class wildcardsPrompt:
|
||||
|
||||
CATEGORY = "EasyUse/Prompt"
|
||||
|
||||
def translate(self, text):
|
||||
return text
|
||||
|
||||
def main(self, *args, **kwargs):
|
||||
prompt = kwargs["prompt"] if "prompt" in kwargs else None
|
||||
seed = kwargs["seed"]
|
||||
@@ -73,16 +73,58 @@ class wildcardsPrompt:
|
||||
_text = []
|
||||
text = text.split("\n")
|
||||
for t in text:
|
||||
t = self.translate(t)
|
||||
_text.append(t)
|
||||
populated_text.append(process(t, seed))
|
||||
text = _text
|
||||
else:
|
||||
text = self.translate(text)
|
||||
populated_text = [process(text, seed)]
|
||||
text = [text]
|
||||
return {"ui": {"value": [seed]}, "result": (text, populated_text)}
|
||||
|
||||
# 通配符提示词矩阵,会按顺序返回包含通配符的提示词所生成的所有可能
|
||||
class wildcardsPromptMatrix:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
wildcard_list = get_wildcard_list()
|
||||
return {"required": {
|
||||
"text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support Lora Block Weight and wildcard)"}),
|
||||
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),),
|
||||
"Select to add Wildcard": (["Select the Wildcard to add to the text"] + wildcard_list,),
|
||||
"offset": ("INT", {"default": 0, "min": 0, "step": 1, "control_after_generate": True}),
|
||||
},
|
||||
"optional":{
|
||||
"output_limit": ("INT", {"default": 1, "min": -1, "step": 1, "tooltip": "Output All Probilities"})
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "INT", "INT")
|
||||
RETURN_NAMES = ("populated_text", "total", "factors")
|
||||
OUTPUT_IS_LIST = (True, False, True)
|
||||
FUNCTION = "main"
|
||||
|
||||
CATEGORY = "EasyUse/Prompt"
|
||||
|
||||
def main(self, *args, **kwargs):
|
||||
prompt = kwargs["prompt"] if "prompt" in kwargs else None
|
||||
offset = kwargs["offset"]
|
||||
output_limit = kwargs.get("output_limit", 1)
|
||||
# Clean loaded_objects
|
||||
if prompt:
|
||||
easyCache.update_loaded_objects(prompt)
|
||||
|
||||
text = kwargs['text']
|
||||
p = WildcardProcessor(text)
|
||||
total = p.total()
|
||||
limit = total if output_limit > total or output_limit == -1 else output_limit
|
||||
offset = 0 if output_limit == -1 else offset
|
||||
populated_text = p.getmany(limit, offset) if output_limit != 1 else [p.getn(offset)]
|
||||
return {"ui": {"value": [offset]}, "result": (populated_text, p.total(), list(p.placeholder_choices.values()))}
|
||||
|
||||
# 负面提示词
|
||||
class negativePrompt:
|
||||
|
||||
@@ -115,7 +157,9 @@ class stylesPromptSelector:
|
||||
for file_name in os.listdir(styles_dir):
|
||||
file = os.path.join(styles_dir, file_name)
|
||||
if os.path.isfile(file) and file_name.endswith(".json"):
|
||||
styles.append(file_name.split(".")[0])
|
||||
if file_name != "fooocus_styles.json":
|
||||
styles.append(file_name.split(".")[0])
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"styles": (styles, {"default": "fooocus_styles"}),
|
||||
@@ -123,6 +167,7 @@ class stylesPromptSelector:
|
||||
"optional": {
|
||||
"positive": ("STRING", {"forceInput": True}),
|
||||
"negative": ("STRING", {"forceInput": True}),
|
||||
"select_styles": ("EASY_PROMPT_STYLES", {}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
@@ -133,11 +178,12 @@ class stylesPromptSelector:
|
||||
CATEGORY = 'EasyUse/Prompt'
|
||||
FUNCTION = 'run'
|
||||
|
||||
def run(self, styles, positive='', negative='', prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
def run(self, styles, positive='', negative='', select_styles=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
values = []
|
||||
all_styles = {}
|
||||
positive_prompt, negative_prompt = '', negative
|
||||
if styles == "fooocus_styles":
|
||||
fooocus_custom_dir = os.path.join(FOOOCUS_STYLES_DIR, 'fooocus_styles.json')
|
||||
if styles == "fooocus_styles" and not os.path.exists(fooocus_custom_dir):
|
||||
file = os.path.join(RESOURCES_DIR, styles + '.json')
|
||||
else:
|
||||
file = os.path.join(FOOOCUS_STYLES_DIR, styles + '.json')
|
||||
@@ -146,9 +192,14 @@ class stylesPromptSelector:
|
||||
f.close()
|
||||
for d in data:
|
||||
all_styles[d['name']] = d
|
||||
if my_unique_id in prompt:
|
||||
if prompt[my_unique_id]["inputs"]['select_styles']:
|
||||
values = prompt[my_unique_id]["inputs"]['select_styles'].split(',')
|
||||
# if my_unique_id in prompt:
|
||||
# if prompt[my_unique_id]["inputs"]['select_styles']:
|
||||
# values = prompt[my_unique_id]["inputs"]['select_styles'].split(',')
|
||||
|
||||
if isinstance(select_styles, str):
|
||||
values = select_styles.split(',')
|
||||
else:
|
||||
values = select_styles if select_styles else []
|
||||
|
||||
has_prompt = False
|
||||
if len(values) == 0:
|
||||
@@ -159,13 +210,15 @@ class stylesPromptSelector:
|
||||
if "{prompt}" in all_styles[val]['prompt'] and has_prompt == False:
|
||||
positive_prompt = all_styles[val]['prompt'].replace('{prompt}', positive)
|
||||
has_prompt = True
|
||||
else:
|
||||
elif "{prompt}" in all_styles[val]['prompt']:
|
||||
positive_prompt += ', ' + all_styles[val]['prompt'].replace(', {prompt}', '').replace('{prompt}', '')
|
||||
else:
|
||||
positive_prompt = all_styles[val]['prompt'] if positive_prompt == '' else positive_prompt + ', ' + all_styles[val]['prompt']
|
||||
if 'negative_prompt' in all_styles[val]:
|
||||
negative_prompt += ', ' + all_styles[val]['negative_prompt'] if negative_prompt else all_styles[val]['negative_prompt']
|
||||
|
||||
if has_prompt == False and positive:
|
||||
positive_prompt = positive + ', '
|
||||
positive_prompt = positive + positive_prompt + ', '
|
||||
|
||||
return (positive_prompt, negative_prompt)
|
||||
|
||||
@@ -266,11 +319,56 @@ class promptLine:
|
||||
|
||||
return (rows, rows)
|
||||
|
||||
import comfy.utils
|
||||
from server import PromptServer
|
||||
from ..libs.messages import MessageCancelled, Message
|
||||
any_type = AlwaysEqualProxy("*")
|
||||
class promptAwait:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"now": (any_type,),
|
||||
"prompt": ("STRING", {"multiline": True, "default": "", "placeholder":"Enter a prompt or use voice to enter to text"}),
|
||||
"toolbar":("EASY_PROMPT_AWAIT_BAR",),
|
||||
},
|
||||
"optional":{
|
||||
"prev": (any_type,),
|
||||
},
|
||||
"hidden": {"workflow_prompt": "PROMPT", "my_unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type, "STRING", "BOOLEAN", "INT")
|
||||
RETURN_NAMES = ("output", "prompt", "continue", "seed")
|
||||
FUNCTION = "await_select"
|
||||
CATEGORY = "EasyUse/Prompt"
|
||||
|
||||
def await_select(self, now, prompt, toolbar, prev=None, workflow_prompt=None, my_unique_id=None, extra_pnginfo=None, **kwargs):
|
||||
id = my_unique_id
|
||||
id = id.split('.')[len(id.split('.')) - 1] if "." in id else id
|
||||
if ":" in id:
|
||||
id = id.split(":")[0]
|
||||
pbar = comfy.utils.ProgressBar(100)
|
||||
pbar.update_absolute(30)
|
||||
PromptServer.instance.send_sync('easyuse_prompt_await', {"id": id})
|
||||
try:
|
||||
res = Message.waitForMessage(id, asList=False)
|
||||
if res is None or res == "-1":
|
||||
result = (now, prompt, False, 0)
|
||||
else:
|
||||
input = now if res['select'] == 'now' or prev is None else prev
|
||||
result = (input, res['prompt'], False if res['result'] == -1 else True, res['seed'] if res['unlock'] else res['last_seed'])
|
||||
pbar.update_absolute(100)
|
||||
return result
|
||||
except MessageCancelled:
|
||||
pbar.update_absolute(100)
|
||||
raise comfy.model_management.InterruptProcessingException()
|
||||
|
||||
class promptConcat:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {
|
||||
},
|
||||
return {"required": {},
|
||||
"optional": {
|
||||
"prompt1": ("STRING", {"multiline": False, "default": "", "forceInput": True}),
|
||||
"prompt2": ("STRING", {"multiline": False, "default": "", "forceInput": True}),
|
||||
@@ -518,9 +616,11 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy positive": positivePrompt,
|
||||
"easy negative": negativePrompt,
|
||||
"easy wildcards": wildcardsPrompt,
|
||||
"easy wildcardsMatrix": wildcardsPromptMatrix,
|
||||
"easy prompt": prompt,
|
||||
"easy promptList": promptList,
|
||||
"easy promptLine": promptLine,
|
||||
"easy promptAwait": promptAwait,
|
||||
"easy promptConcat": promptConcat,
|
||||
"easy promptReplace": promptReplace,
|
||||
"easy stylesSelector": stylesPromptSelector,
|
||||
@@ -531,9 +631,11 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy positive": "Positive",
|
||||
"easy negative": "Negative",
|
||||
"easy wildcards": "Wildcards",
|
||||
"easy wildcardsMatrix": "Wildcards Matrix",
|
||||
"easy prompt": "Prompt",
|
||||
"easy promptList": "PromptList",
|
||||
"easy promptLine": "PromptLine",
|
||||
"easy promptAwait": "PromptAwait",
|
||||
"easy promptConcat": "PromptConcat",
|
||||
"easy promptReplace": "PromptReplace",
|
||||
"easy stylesSelector": "Styles Selector",
|
||||
|
||||
+10
-33
@@ -15,7 +15,6 @@ from ..libs.log import log_node_warn
|
||||
from ..libs.utils import easySave, get_local_filepath, get_sd_version
|
||||
from ..libs.sampler import alignYourStepsScheduler, gitsScheduler
|
||||
from ..libs.xyplot import easyXYPlot
|
||||
from ..libs.chooser import ChooserMessage, ChooserCancelled
|
||||
|
||||
from .. import easyCache, sampler
|
||||
|
||||
@@ -30,7 +29,7 @@ class samplerFull:
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS+NEW_SCHEDULERS,),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],),
|
||||
"image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],),
|
||||
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
|
||||
"save_prefix": ("STRING", {"default": "ComfyUI"}),
|
||||
},
|
||||
@@ -389,40 +388,18 @@ class samplerFull:
|
||||
|
||||
"loader_settings": {
|
||||
**pipe["loader_settings"],
|
||||
"steps": steps,
|
||||
"cfg": cfg,
|
||||
"sampler_name": sampler_name,
|
||||
"scheduler": scheduler,
|
||||
"denoise": denoise,
|
||||
"add_noise": add_noise,
|
||||
"spent_time": spent_time
|
||||
}
|
||||
}
|
||||
|
||||
del pipe
|
||||
|
||||
if image_output == 'Preview&Choose':
|
||||
if my_unique_id not in ChooserMessage.stash:
|
||||
ChooserMessage.stash[my_unique_id] = {}
|
||||
my_stash = ChooserMessage.stash[my_unique_id]
|
||||
|
||||
PromptServer.instance.send_sync("easyuse-image-choose", {"id": my_unique_id, "urls": results})
|
||||
# wait for selection
|
||||
try:
|
||||
selections = ChooserMessage.waitForMessage(my_unique_id, asList=True)
|
||||
samples = samp_samples['samples']
|
||||
samples = [samples[x] for x in selections if x >= 0] if len(selections) > 1 else [samples[0]]
|
||||
new_images = [new_images[x] for x in selections if x >= 0] if len(selections) > 1 else [new_images[0]]
|
||||
samp_images = [samp_images[x] for x in selections if x >= 0] if len(selections) > 1 else [samp_images[0]]
|
||||
new_images = torch.stack(new_images, dim=0)
|
||||
samp_images = torch.stack(samp_images, dim=0)
|
||||
samples = torch.stack(samples, dim=0)
|
||||
samp_samples = {"samples": samples}
|
||||
new_pipe['samples'] = samp_samples
|
||||
new_pipe['loader_settings']['batch_size'] = len(new_images)
|
||||
except ChooserCancelled:
|
||||
raise comfy.model_management.InterruptProcessingException()
|
||||
|
||||
new_pipe['images'] = new_images
|
||||
new_pipe['samp_images'] = samp_images
|
||||
|
||||
return {"ui": {"images": results},
|
||||
"result": sampler.get_output(new_pipe,)}
|
||||
|
||||
if image_output in ("Hide", "Hide&Save", "None"):
|
||||
return {"ui":{}, "result":sampler.get_output(new_pipe,)}
|
||||
|
||||
@@ -507,7 +484,7 @@ class samplerFull:
|
||||
|
||||
samp_samples = {"samples": latents_plot}
|
||||
|
||||
images, image_list = sampleXYplot.plot_images_and_labels()
|
||||
images, image_list = sampleXYplot.plot_images_and_labels(plot_image_vars)
|
||||
|
||||
# Generate output_images
|
||||
output_images = torch.stack([tensor.squeeze() for tensor in image_list])
|
||||
@@ -588,7 +565,7 @@ class samplerSimple(samplerFull):
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required":
|
||||
{"pipe": ("PIPE_LINE",),
|
||||
"image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "Preview"}),
|
||||
"image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "Preview"}),
|
||||
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
|
||||
"save_prefix": ("STRING", {"default": "ComfyUI"}),
|
||||
},
|
||||
@@ -620,7 +597,7 @@ class samplerSimpleCustom(samplerFull):
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required":
|
||||
{"pipe": ("PIPE_LINE",),
|
||||
"image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "None"}),
|
||||
"image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "None"}),
|
||||
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
|
||||
"save_prefix": ("STRING", {"default": "ComfyUI"}),
|
||||
},
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from ..config import MAX_SEED_NUM
|
||||
import hashlib
|
||||
import random
|
||||
|
||||
class easySeed:
|
||||
@classmethod
|
||||
@@ -19,6 +21,53 @@ class easySeed:
|
||||
def doit(self, seed=0, prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
return seed,
|
||||
|
||||
class seedList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"min_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
|
||||
"max_num": ("INT", {"default": MAX_SEED_NUM, "min": 0 }),
|
||||
"method": (["random", "increment", "decrement"], {"default": "random"}),
|
||||
"total": ("INT", {"default": 1, "min": 1, "max": 100000}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM,}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT")
|
||||
RETURN_NAMES = ("seed", "total")
|
||||
FUNCTION = "doit"
|
||||
DESCRIPTION = "Random number seed that can be used in a for loop, by connecting index and easy indexAny node to realize different seed values in the loop."
|
||||
|
||||
CATEGORY = "EasyUse/Seed"
|
||||
|
||||
def doit(self, min_num, max_num, method, total, seed=0, prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
random.seed(seed)
|
||||
|
||||
seed_list = []
|
||||
if min_num > max_num:
|
||||
min_num, max_num = max_num, min_num
|
||||
for i in range(total):
|
||||
if method == 'random':
|
||||
s = random.randint(min_num, max_num)
|
||||
elif method == 'increment':
|
||||
s = min_num + i
|
||||
if s > max_num:
|
||||
s = max_num
|
||||
elif method == 'decrement':
|
||||
s = max_num - i
|
||||
if s < min_num:
|
||||
s = min_num
|
||||
seed_list.append(s)
|
||||
return seed_list, total
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(s, seed, **kwargs):
|
||||
m = hashlib.sha256()
|
||||
m.update(seed)
|
||||
return m.digest().hex()
|
||||
|
||||
# 全局随机种
|
||||
class globalSeed:
|
||||
@classmethod
|
||||
@@ -46,10 +95,12 @@ class globalSeed:
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"easy seed": easySeed,
|
||||
"easy seedList": seedList,
|
||||
"easy globalSeed": globalSeed,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy seed": "EasySeed",
|
||||
"easy seedList": "EasySeedList",
|
||||
"easy globalSeed": "EasyGlobalSeed",
|
||||
}
|
||||
+20
-1
@@ -106,6 +106,23 @@ class setControlName:
|
||||
|
||||
def set_name(self, controlnet_name):
|
||||
return (controlnet_name,)
|
||||
|
||||
class setLoraName:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {
|
||||
"lora_name": (folder_paths.get_filename_list("loras"),),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (AlwaysEqualProxy('*'),)
|
||||
RETURN_NAMES = ("lora_name",)
|
||||
FUNCTION = "set_name"
|
||||
CATEGORY = "EasyUse/Util"
|
||||
|
||||
def set_name(self, lora_name):
|
||||
return (lora_name,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
@@ -113,6 +130,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy sliderControl": sliderControl,
|
||||
"easy ckptNames": setCkptName,
|
||||
"easy controlnetNames": setControlName,
|
||||
"easy loraNames": setLoraName,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -120,4 +138,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy sliderControl": "Easy Slider Control",
|
||||
"easy ckptNames": "Ckpt Names",
|
||||
"easy controlnetNames": "ControlNet Names",
|
||||
}
|
||||
"easy loraNames": "Lora Names",
|
||||
}
|
||||
|
||||
+38
-32
@@ -12,6 +12,14 @@ from .libs.utils import getMetadata, cleanGPUUsedForce, get_local_filepath
|
||||
from .libs.cache import remove_cache
|
||||
from .libs.translate import has_chinese, zh_to_en
|
||||
|
||||
@PromptServer.instance.routes.get('/easyuse/version')
|
||||
def get_version(request):
|
||||
try:
|
||||
from .. import __version__
|
||||
return web.json_response({"version": __version__})
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return web.Response(status=500)
|
||||
|
||||
@PromptServer.instance.routes.post("/easyuse/cleangpu")
|
||||
def cleanGPU(request):
|
||||
@@ -70,13 +78,14 @@ async def parse_csv(request):
|
||||
@PromptServer.instance.routes.get("/easyuse/prompt/styles")
|
||||
async def getStylesList(request):
|
||||
if "name" in request.rel_url.query:
|
||||
name = request.rel_url.query["name"]
|
||||
if name == 'fooocus_styles':
|
||||
file = os.path.join(RESOURCES_DIR, name+'.json')
|
||||
cn_file = os.path.join(RESOURCES_DIR, name + '_cn.json')
|
||||
style_name = request.rel_url.query["name"]
|
||||
fooocus_custom_dir = os.path.join(FOOOCUS_STYLES_DIR, 'fooocus_styles.json')
|
||||
if style_name == 'fooocus_styles' and not os.path.exists(fooocus_custom_dir):
|
||||
file = os.path.join(RESOURCES_DIR, style_name+'.json')
|
||||
cn_file = os.path.join(RESOURCES_DIR, style_name + '_cn.json')
|
||||
else:
|
||||
file = os.path.join(FOOOCUS_STYLES_DIR, name+'.json')
|
||||
cn_file = os.path.join(FOOOCUS_STYLES_DIR, name + '_cn.json')
|
||||
file = os.path.join(FOOOCUS_STYLES_DIR, style_name+'.json')
|
||||
cn_file = os.path.join(FOOOCUS_STYLES_DIR, style_name + '_cn.json')
|
||||
cn_data = None
|
||||
if os.path.isfile(cn_file):
|
||||
f = open(cn_file, 'r', encoding='utf-8')
|
||||
@@ -95,13 +104,25 @@ async def getStylesList(request):
|
||||
key = ' '.join(
|
||||
word.upper() if word.lower() in ['mre', 'sai', '3d'] else word.capitalize() for word in
|
||||
words)
|
||||
img_name = '_'.join(words).lower()
|
||||
if "name_cn" in d:
|
||||
nd['name_cn'] = d['name_cn']
|
||||
elif cn_data:
|
||||
nd['name_cn'] = cn_data[key] if key in cn_data else key
|
||||
nd["name"] = d['name']
|
||||
nd['imgName'] = img_name
|
||||
if "thumbnail" in d:
|
||||
thumbnail = d['thumbnail']
|
||||
if isinstance(d['thumbnail'], str):
|
||||
nd['thumbnail'] = thumbnail if "http" in thumbnail else f'/easyuse/prompt/styles/image?path={thumbnail}'
|
||||
elif isinstance(d['thumbnail'], list):
|
||||
nd['thumbnail'] = [thumb if "http" in thumb else f'/easyuse/prompt/styles/image?path={thumb}' for thumb in thumbnail]
|
||||
else:
|
||||
nd['thumbnail'] = f'/easyuse/prompt/styles/image?name={name}&styles_name={style_name}'
|
||||
if "thumbnail_variant" in d:
|
||||
nd['thumbnailVariant'] = d['thumbnail_variant']
|
||||
if "media_type" in d:
|
||||
nd['mediaType'] = d['media_type']
|
||||
if "media_subtype" in d:
|
||||
nd['mediaSubtype'] = d['media_subtype']
|
||||
if "prompt" in d:
|
||||
nd['prompt'] = d['prompt']
|
||||
if "negative_prompt" in d:
|
||||
@@ -114,7 +135,15 @@ async def getStylesList(request):
|
||||
@PromptServer.instance.routes.get("/easyuse/prompt/styles/image")
|
||||
async def getStylesImage(request):
|
||||
styles_name = request.rel_url.query["styles_name"] if "styles_name" in request.rel_url.query else None
|
||||
if "name" in request.rel_url.query:
|
||||
if "path" in request.rel_url.query:
|
||||
path = request.rel_url.query["path"]
|
||||
file = os.path.join(FOOOCUS_STYLES_DIR, 'samples', path)
|
||||
parent_file = os.path.join(FOOOCUS_STYLES_DIR, path)
|
||||
if os.path.isfile(file):
|
||||
return web.FileResponse(file)
|
||||
elif os.path.isfile(parent_file):
|
||||
return web.FileResponse(parent_file)
|
||||
elif "name" in request.rel_url.query:
|
||||
name = request.rel_url.query["name"]
|
||||
if os.path.exists(os.path.join(FOOOCUS_STYLES_DIR, 'samples')):
|
||||
file = os.path.join(FOOOCUS_STYLES_DIR, 'samples', name + '.jpg')
|
||||
@@ -138,29 +167,6 @@ async def getModelsList(request):
|
||||
else:
|
||||
return web.Response(status=400)
|
||||
|
||||
# get models thumbnails
|
||||
@PromptServer.instance.routes.get("/easyuse/models/thumbnail")
|
||||
async def getModelsThumbnail(request):
|
||||
limit = 500
|
||||
if "limit" in request.rel_url.query:
|
||||
limit = request.rel_url.query.get("limit")
|
||||
limit = int(limit)
|
||||
checkpoints = folder_paths.get_filename_list("checkpoints_thumb")
|
||||
loras = folder_paths.get_filename_list("loras_thumb")
|
||||
checkpoints_full = []
|
||||
loras_full = []
|
||||
if len(checkpoints) + len(loras) >= limit:
|
||||
return web.Response(status=400)
|
||||
for index, i in enumerate(checkpoints):
|
||||
full_path = folder_paths.get_full_path('checkpoints_thumb', str(i))
|
||||
if full_path:
|
||||
checkpoints_full.append(full_path)
|
||||
for index, i in enumerate(loras):
|
||||
full_path = folder_paths.get_full_path('loras_thumb', str(i))
|
||||
if full_path:
|
||||
loras_full.append(full_path)
|
||||
return web.json_response(checkpoints_full + loras_full)
|
||||
|
||||
@PromptServer.instance.routes.post("/easyuse/metadata/notes/{name}")
|
||||
async def save_notes(request):
|
||||
name = request.match_info["name"]
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-easy-use"
|
||||
description = "To enhance the usability of ComfyUI, optimizations and integrations have been implemented for several commonly used nodes."
|
||||
version = "1.2.7"
|
||||
version = "1.3.2"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["diffusers", "accelerate", "clip_interrogator>=0.6.0", "sentencepiece", "lark", "onnxruntime", "spandrel", "opencv-python", "matplotlib", "peft"]
|
||||
|
||||
|
||||
+1923
-1373
File diff suppressed because it is too large
Load Diff
@@ -1,279 +0,0 @@
|
||||
{
|
||||
"Fooocus V2": "Fooocus V2扩展词",
|
||||
"Default (Slightly Cinematic)": "默认(轻微的电影感)",
|
||||
"Fooocus Enhance": "Fooocus-优化增强",
|
||||
"Fooocus Cinematic": "Fooocus-电影感",
|
||||
"Fooocus Sharp": "Fooocus-锐化",
|
||||
"Fooocus Masterpiece": "Fooocus-杰作",
|
||||
"Fooocus Photograph": "Fooocus-照片",
|
||||
"Fooocus Negative": "Fooocus-反向提示词",
|
||||
"SAI 3D Model": "SAI-3D模型",
|
||||
"SAI Analog Film": "SAI-模拟电影",
|
||||
"SAI Anime": "SAI-动漫",
|
||||
"SAI Cinematic": "SAI-电影片段",
|
||||
"SAI Comic Book": "SAI-漫画",
|
||||
"SAI Craft Clay": "SAI-工艺粘土",
|
||||
"SAI Digital Art": "SAI-数字艺术",
|
||||
"SAI Enhance": "SAI-增强",
|
||||
"SAI Fantasy Art": "SAI-奇幻艺术",
|
||||
"SAI Isometric": "SAI-等距风格",
|
||||
"SAI Line Art": "SAI-线条艺术",
|
||||
"SAI Lowpoly": "SAI-低多边形",
|
||||
"SAI Neonpunk": "SAI-霓虹朋克",
|
||||
"SAI Origami": "SAI-折纸",
|
||||
"SAI Photographic": "SAI-摄影",
|
||||
"SAI Pixel Art": "SAI-像素艺术",
|
||||
"SAI Texture": "SAI-纹理",
|
||||
"MRE Cinematic Dynamic": "MRE-史诗电影",
|
||||
"MRE Spontaneous Picture": "MRE-自然的抓拍照片",
|
||||
"MRE Artistic Vision": "MRE-艺术视觉",
|
||||
"MRE Dark Dream": "MRE-黑暗梦境",
|
||||
"MRE Gloomy Art": "MRE-阴郁艺术",
|
||||
"MRE Bad Dream": "MRE-噩梦",
|
||||
"MRE Underground": "MRE-阴森地下",
|
||||
"MRE Surreal Painting": "MRE-超现实主义绘画",
|
||||
"MRE Dynamic Illustration": "MRE-动态插画",
|
||||
"MRE Undead Art": "MRE-遗忘艺术家作品",
|
||||
"MRE Elemental Art": "MRE-元素艺术",
|
||||
"MRE Space Art": "MRE-空间艺术",
|
||||
"MRE Ancient Illustration": "MRE-古代插图",
|
||||
"MRE Brave Art": "MRE-勇敢艺术",
|
||||
"MRE Heroic Fantasy": "MRE-英雄幻想",
|
||||
"MRE Dark Cyberpunk": "MRE-黑暗赛博朋克",
|
||||
"MRE Lyrical Geometry": "MRE-抒情几何抽象画",
|
||||
"MRE Sumi E Symbolic": "MRE-墨绘长笔画",
|
||||
"MRE Sumi E Detailed": "MRE-精细墨绘画",
|
||||
"MRE Manga": "MRE-日本漫画",
|
||||
"MRE Anime": "MRE-日本动画片",
|
||||
"MRE Comic": "MRE-成人漫画书插画",
|
||||
"Ads Advertising": "广告-广告",
|
||||
"Ads Automotive": "广告-汽车",
|
||||
"Ads Corporate": "广告-企业品牌",
|
||||
"Ads Fashion Editorial": "广告-时尚编辑",
|
||||
"Ads Food Photography": "广告-食品摄影",
|
||||
"Ads Gourmet Food Photography": "广告-顶级美食摄影",
|
||||
"Ads Luxury": "广告-奢侈品",
|
||||
"Ads Real Estate": "广告-房地产",
|
||||
"Ads Retail": "广告-零售",
|
||||
"Artstyle Abstract": "艺术风格-抽象",
|
||||
"Artstyle Abstract Expressionism": "艺术风格-抽象表现主义",
|
||||
"Artstyle Art Deco": "艺术风格-装饰艺术",
|
||||
"Artstyle Art Nouveau": "艺术风格-新艺术",
|
||||
"Artstyle Constructivist": "艺术风格-构造主义",
|
||||
"Artstyle Cubist": "艺术风格-立体主义",
|
||||
"Artstyle Expressionist": "艺术风格-表现主义",
|
||||
"Artstyle Graffiti": "艺术风格-涂鸦",
|
||||
"Artstyle Hyperrealism": "艺术风格-超写实主义",
|
||||
"Artstyle Impressionist": "艺术风格-印象派",
|
||||
"Artstyle Pointillism": "艺术风格-点彩派",
|
||||
"Artstyle Pop Art": "艺术风格-波普艺术",
|
||||
"Artstyle Psychedelic": "艺术风格-迷幻",
|
||||
"Artstyle Renaissance": "艺术风格-文艺复兴",
|
||||
"Artstyle Steampunk": "艺术风格-蒸汽朋克",
|
||||
"Artstyle Surrealist": "艺术风格-超现实主义",
|
||||
"Artstyle Typography": "艺术风格-字体设计",
|
||||
"Artstyle Watercolor": "艺术风格-水彩",
|
||||
"Futuristic Biomechanical": "未来主义-生物机械",
|
||||
"Futuristic Biomechanical Cyberpunk": "未来主义-生物机械-赛博朋克",
|
||||
"Futuristic Cybernetic": "未来主义-人机融合",
|
||||
"Futuristic Cybernetic Robot": "未来主义-人机融合-机器人",
|
||||
"Futuristic Cyberpunk Cityscape": "未来主义-赛博朋克城市",
|
||||
"Futuristic Futuristic": "未来主义-未来主义",
|
||||
"Futuristic Retro Cyberpunk": "未来主义-复古赛博朋克",
|
||||
"Futuristic Retro Futurism": "未来主义-复古未来主义",
|
||||
"Futuristic Sci Fi": "未来主义-科幻",
|
||||
"Futuristic Vaporwave": "未来主义-蒸汽波",
|
||||
"Game Bubble Bobble": "游戏-泡泡龙",
|
||||
"Game Cyberpunk Game": "游戏-赛博朋克游戏",
|
||||
"Game Fighting Game": "游戏-格斗游戏",
|
||||
"Game Gta": "游戏-侠盗猎车手",
|
||||
"Game Mario": "游戏-马里奥",
|
||||
"Game Minecraft": "游戏-我的世界",
|
||||
"Game Pokemon": "游戏-宝可梦",
|
||||
"Game Retro Arcade": "游戏-复古街机",
|
||||
"Game Retro Game": "游戏-复古游戏",
|
||||
"Game Rpg Fantasy Game": "游戏-角色扮演幻想游戏",
|
||||
"Game Strategy Game": "游戏-策略游戏",
|
||||
"Game Streetfighter": "游戏-街头霸王",
|
||||
"Game Zelda": "游戏-塞尔达传说",
|
||||
"Misc Architectural": "其他-建筑",
|
||||
"Misc Disco": "其他-迪斯科",
|
||||
"Misc Dreamscape": "其他-梦境",
|
||||
"Misc Dystopian": "其他-反乌托邦",
|
||||
"Misc Fairy Tale": "其他-童话故事",
|
||||
"Misc Gothic": "其他-哥特风",
|
||||
"Misc Grunge": "其他-垮掉的",
|
||||
"Misc Horror": "其他-恐怖",
|
||||
"Misc Kawaii": "其他-可爱",
|
||||
"Misc Lovecraftian": "其他-洛夫克拉夫特",
|
||||
"Misc Macabre": "其他-恐怖",
|
||||
"Misc Manga": "其他-漫画",
|
||||
"Misc Metropolis": "其他-大都市",
|
||||
"Misc Minimalist": "其他-极简主义",
|
||||
"Misc Monochrome": "其他-单色",
|
||||
"Misc Nautical": "其他-航海",
|
||||
"Misc Space": "其他-太空",
|
||||
"Misc Stained Glass": "其他-彩色玻璃",
|
||||
"Misc Techwear Fashion": "其他-科技时尚",
|
||||
"Misc Tribal": "其他-部落",
|
||||
"Misc Zentangle": "其他-禅绕画",
|
||||
"Papercraft Collage": "手工艺-拼贴",
|
||||
"Papercraft Flat Papercut": "手工艺-平面剪纸",
|
||||
"Papercraft Kirigami": "手工艺-切纸",
|
||||
"Papercraft Paper Mache": "手工艺-纸浆塑造",
|
||||
"Papercraft Paper Quilling": "手工艺-纸艺卷轴",
|
||||
"Papercraft Papercut Collage": "手工艺-剪纸拼贴",
|
||||
"Papercraft Papercut Shadow Box": "手工艺-剪纸影箱",
|
||||
"Papercraft Stacked Papercut": "手工艺-层叠剪纸",
|
||||
"Papercraft Thick Layered Papercut": "手工艺-厚层剪纸",
|
||||
"Photo Alien": "摄影-外星人",
|
||||
"Photo Film Noir": "摄影-黑色电影",
|
||||
"Photo Glamour": "摄影-魅力",
|
||||
"Photo Hdr": "摄影-高动态范围",
|
||||
"Photo Iphone Photographic": "摄影-苹果手机摄影",
|
||||
"Photo Long Exposure": "摄影-长曝光",
|
||||
"Photo Neon Noir": "摄影-霓虹黑色",
|
||||
"Photo Silhouette": "摄影-轮廓",
|
||||
"Photo Tilt Shift": "摄影-移轴",
|
||||
"Cinematic Diva": "电影女主角",
|
||||
"Abstract Expressionism": "抽象表现主义",
|
||||
"Academia": "学术",
|
||||
"Action Figure": "动作人偶",
|
||||
"Adorable 3D Character": "可爱的3D角色",
|
||||
"Adorable Kawaii": "可爱的卡哇伊",
|
||||
"Art Deco": "装饰艺术",
|
||||
"Art Nouveau": "新艺术,美丽艺术",
|
||||
"Astral Aura": "星体光环",
|
||||
"Avant Garde": "前卫",
|
||||
"Baroque": "巴洛克",
|
||||
"Bauhaus Style Poster": "包豪斯风格海报",
|
||||
"Blueprint Schematic Drawing": "蓝图示意图",
|
||||
"Caricature": "漫画",
|
||||
"Cel Shaded Art": "卡通渲染",
|
||||
"Character Design Sheet": "角色设计表",
|
||||
"Classicism Art": "古典主义艺术",
|
||||
"Color Field Painting": "色彩领域绘画",
|
||||
"Colored Pencil Art": "彩色铅笔艺术",
|
||||
"Conceptual Art": "概念艺术",
|
||||
"Constructivism": "建构主义",
|
||||
"Cubism": "立体主义",
|
||||
"Dadaism": "达达主义",
|
||||
"Dark Fantasy": "黑暗奇幻",
|
||||
"Dark Moody Atmosphere": "黑暗忧郁气氛",
|
||||
"Dmt Art Style": "迷幻艺术风格",
|
||||
"Doodle Art": "涂鸦艺术",
|
||||
"Double Exposure": "双重曝光",
|
||||
"Dripping Paint Splatter Art": "滴漆飞溅艺术",
|
||||
"Expressionism": "表现主义",
|
||||
"Faded Polaroid Photo": "褪色的宝丽来照片",
|
||||
"Fauvism": "野兽派",
|
||||
"Flat 2d Art": "平面 2D 艺术",
|
||||
"Fortnite Art Style": "堡垒之夜艺术风格",
|
||||
"Futurism": "未来派",
|
||||
"Glitchcore": "故障核心",
|
||||
"Glo Fi": "光明高保真",
|
||||
"Googie Art Style": "古吉艺术风格",
|
||||
"Graffiti Art": "涂鸦艺术",
|
||||
"Harlem Renaissance Art": "哈莱姆文艺复兴艺术",
|
||||
"High Fashion": "高级时装",
|
||||
"Idyllic": "田园诗般",
|
||||
"Impressionism": "印象派",
|
||||
"Infographic Drawing": "信息图表绘图",
|
||||
"Ink Dripping Drawing": "滴墨绘画",
|
||||
"Japanese Ink Drawing": "日式水墨画",
|
||||
"Knolling Photography": "规律摆放摄影",
|
||||
"Light Cheery Atmosphere": "轻松愉快的气氛",
|
||||
"Logo Design": "标志设计",
|
||||
"Luxurious Elegance": "奢华优雅",
|
||||
"Macro Photography": "微距摄影",
|
||||
"Mandola Art": "曼陀罗艺术",
|
||||
"Marker Drawing": "马克笔绘图",
|
||||
"Medievalism": "中世纪主义",
|
||||
"Minimalism": "极简主义",
|
||||
"Neo Baroque": "新巴洛克",
|
||||
"Neo Byzantine": "新拜占庭",
|
||||
"Neo Futurism": "新未来派",
|
||||
"Neo Impressionism": "新印象派",
|
||||
"Neo Rococo": "新洛可可",
|
||||
"Neoclassicism": "新古典主义",
|
||||
"Op Art": "欧普艺术",
|
||||
"Ornate And Intricate": "华丽而复杂",
|
||||
"Pencil Sketch Drawing": "铅笔素描",
|
||||
"Pop Art 2": "流行艺术2",
|
||||
"Rococo": "洛可可",
|
||||
"Silhouette Art": "剪影艺术",
|
||||
"Simple Vector Art": "简单矢量艺术",
|
||||
"Sketchup": "草图",
|
||||
"Steampunk 2": "赛博朋克2",
|
||||
"Surrealism": "超现实主义",
|
||||
"Suprematism": "至上主义",
|
||||
"Terragen": "地表风景",
|
||||
"Tranquil Relaxing Atmosphere": "宁静轻松的氛围",
|
||||
"Sticker Designs": "贴纸设计",
|
||||
"Vibrant Rim Light": "生动的边缘光",
|
||||
"Volumetric Lighting": "体积照明",
|
||||
"Watercolor 2": "水彩2",
|
||||
"Whimsical And Playful": "异想天开、俏皮",
|
||||
"Mk Chromolithography": "MK 色彩版画",
|
||||
"Mk Cross Processing Print": "MK 交叉过程打印",
|
||||
"Mk Dufaycolor Photograph": "MK 杜法色彩照片",
|
||||
"Mk Herbarium": "MK 植物标本馆",
|
||||
"Mk Punk Collage": "MK 朋克拼贴画",
|
||||
"Mk Mosaic": "MK 镶嵌图",
|
||||
"Mk Van Gogh": "MK 梵高",
|
||||
"Mk Coloring Book": "MK 色彩书",
|
||||
"Mk Singer Sargent": "MK 辛格 · 萨尔生特",
|
||||
"Mk Pollock": "MK 波洛克",
|
||||
"Mk Basquiat": "MK 巴斯奎特",
|
||||
"Mk Andy Warhol": "MK 安迪 · 沃霍尔",
|
||||
"Mk Halftone Print": "MK 半色版画",
|
||||
"Mk Gond Painting": "MK 贡德绘画",
|
||||
"Mk Albumen Print": "MK 白蛋清印刷",
|
||||
"Mk Aquatint Print": "MK 水蚀刻印刷",
|
||||
"Mk Anthotype Print": "MK 花纹版画",
|
||||
"Mk Inuit Carving": "MK 因纽特雕塑",
|
||||
"Mk Bromoil Print": "MK 溴油印刷",
|
||||
"Mk Calotype Print": "MK 卡洛雅图印刷",
|
||||
"Mk Color Sketchnote": "MK色彩素描笔记",
|
||||
"Mk Cibulak Porcelain": "MK 西布拉瓷器",
|
||||
"Mk Alcohol Ink Art": "MK 酒精水彩艺术",
|
||||
"Mk One Line Art": "MK 一线画",
|
||||
"Mk Blacklight Paint": "MK 黑光油漆",
|
||||
"Mk Carnival Glass": "MK 嘉年华玻璃",
|
||||
"Mk Cyanotype Print": "MK 青色版画",
|
||||
"Mk Cross Stitching": "MK 交叉针织",
|
||||
"Mk Encaustic Paint": "MK 蜡漆",
|
||||
"Mk Embroidery": "MK 刺绣",
|
||||
"Mk Gyotaku": "MK 鱼拓版画",
|
||||
"Mk Luminogram": "MK 光感影像",
|
||||
"Mk Lite Brite Art": "MK 彩色灯泡艺术",
|
||||
"Mk Mokume Gane": "MK 木金工艺",
|
||||
"Pebble Art": "MK 鹅卵石艺术",
|
||||
"Mk Palekh": "MK 帕列赫",
|
||||
"Mk Suminagashi": "MK 澄洗画",
|
||||
"Mk Scrimshaw": "MK 丝线绣",
|
||||
"Mk Shibori": "MK 湿布雕版印刷",
|
||||
"Mk Vitreous Enamel": "MK 玻璃珐琅",
|
||||
"Mk Ukiyo E": "MK 浮世绘",
|
||||
"Mk Vintage Airline Poster": "MK 古董航空公司海报",
|
||||
"Mk Vintage Travel Poster": "MK 古董旅行海报",
|
||||
"Mk Bauhaus Style": "Mk 包豪斯风格",
|
||||
"Mk Afrofuturism": "Mk 非洲未来主义",
|
||||
"Mk Atompunk": "Mk 原子朋克",
|
||||
"Mk Constructivism": "Mk 构成派",
|
||||
"Mk Chicano Art": "Mk 西班牙裔美国艺术",
|
||||
"Mk De Stijl": "Mk 去风格派",
|
||||
"Mk Dayak Art": "Mk 达雅克艺术",
|
||||
"Mk Fayum Portrait": "Mk 法尤姆肖像画",
|
||||
"Mk Illuminated Manuscript": "Mk 彩绘手稿",
|
||||
"Mk Kalighat Painting": "Mk 卡利加特绘画",
|
||||
"Mk Madhubani Painting": "Mk 马杜班尼绘画",
|
||||
"Mk Pictorialism": "Mk 描绘主义",
|
||||
"Mk Pichwai Painting": "Mk 皮奇瓦伊绘画",
|
||||
"Mk Patachitra Painting": "Mk 帕塔基特拉绘画",
|
||||
"Mk Samoan Art Inspired": "Mk 萨莫亚艺术启发的",
|
||||
"Mk Tlingit Art": "Mk 特林吉特艺术",
|
||||
"Mk Adnate Style": "Mk 阿达内特风格",
|
||||
"Mk Ron English Style": "Mk 罗恩英国风格",
|
||||
"Mk Shepard Fairey Style": "Mk 舒帕德 · 费尔利风格"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# 开发人员使用(请勿运行)
|
||||
# 将 https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation 的翻译文件转换格式以适配 ComfyUI核心 locales
|
||||
# 将 https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation 的翻译文件转换格式以适配 ComfyUI locales
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
@@ -12,22 +12,22 @@ const ipadapterNodes = ["easy ipadapterApply", "easy ipadapterApplyADV" ,"easy i
|
||||
const pipeNodes = ['easy pipeIn','easy pipeOut', 'easy pipeEdit']
|
||||
const xyNodes = ['easy XYPlot', 'easy XYPlotAdvanced']
|
||||
const extraNodes = ['easy setNode']
|
||||
const modelNormalNodes = [...["Reroute"],...['RescaleCFG','LoraLoaderModelOnly','LoraLoader','FreeU','FreeU_v2'],...ipadapterNodes,...extraNodes]
|
||||
const modelNormalNodes = [...['RescaleCFG','LoraLoaderModelOnly','LoraLoader','FreeU','FreeU_v2'],...ipadapterNodes,...extraNodes]
|
||||
const suggestions = {
|
||||
// prompt
|
||||
"easy seed":{
|
||||
"from":{
|
||||
"INT": [...["Reroute"],...preSamplingNodes,...['easy fullkSampler']]
|
||||
"INT": [...preSamplingNodes,...['easy fullkSampler']]
|
||||
}
|
||||
},
|
||||
"easy positive":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute"],...propmts]
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy negative":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute"],...propmts]
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy wildcards":{
|
||||
@@ -53,214 +53,225 @@ const suggestions = {
|
||||
// sd相关
|
||||
"easy fullLoader": {
|
||||
"from":{
|
||||
"PIPE_LINE": [...["Reroute"],...preSamplingNodes,...['easy fullkSampler'],...pipeNodes,...extraNodes],
|
||||
"PIPE_LINE": [...preSamplingNodes,...['easy fullkSampler'],...pipeNodes,...extraNodes],
|
||||
"MODEL":modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...["Reroute"],...propmts]
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy a1111Loader": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...["Reroute"],...propmts]
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy comfyLoader": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...["Reroute"],...propmts]
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy svdLoader":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes],
|
||||
"PIPE_LINE": [ ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...["Reroute"],...propmts]
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy zero123Loader":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes],
|
||||
"PIPE_LINE": [ ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...["Reroute"],...propmts]
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy sv3dLoader":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes],
|
||||
"PIPE_LINE": [ ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...["Reroute"],...propmts]
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy preSampling": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
},
|
||||
},
|
||||
"easy preSamplingAdvanced": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingDynamicCFG": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingCustom": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingLayerDiffusion": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute", "easy kSamplerLayerDiffusion"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
"PIPE_LINE": [...["easy kSamplerLayerDiffusion"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingNoiseIn": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
// ksampler
|
||||
"easy fullkSampler": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...pipeNodes.reverse(), ...['easy preDetailerFix', 'easy preMaskDetailerFix'], ...preSamplingNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...pipeNodes.reverse(), ...['easy preDetailerFix', 'easy preMaskDetailerFix'], ...preSamplingNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy kSampler": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...pipeNodes.reverse(), ...['easy preDetailerFix', 'easy preMaskDetailerFix', 'easy hiresFix'], ...preSamplingNodes, ...extraNodes],
|
||||
"PIPE_LINE": [ ...pipeNodes.reverse(), ...['easy preDetailerFix', 'easy preMaskDetailerFix', 'easy hiresFix'], ...preSamplingNodes, ...extraNodes],
|
||||
}
|
||||
},
|
||||
// cn
|
||||
"easy controlnetLoader": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy controlnetLoaderADV":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
// instant
|
||||
"easy instantIDApply": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"COMBO": [...["Reroute", "easy promptLine"]]
|
||||
"COMBO": [...["easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy instantIDApplyADV":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"COMBO": [...["Reroute", "easy promptLine"]]
|
||||
"COMBO": [...["easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy ipadapterApply":{
|
||||
"to":{
|
||||
"COMBO": [...["Reroute", "easy promptLine"]]
|
||||
"COMBO": [...["easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy ipadapterApplyADV":{
|
||||
"to":{
|
||||
"STRING": [...["Reroute", "easy sliderControl"], ...propmts],
|
||||
"COMBO": [...["Reroute", "easy promptLine"]]
|
||||
"STRING": [...["easy sliderControl"], ...propmts],
|
||||
"COMBO": [...["easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy ipadapterStyleComposition":{
|
||||
"to":{
|
||||
"COMBO": [...["Reroute", "easy promptLine"]]
|
||||
"COMBO": [...["easy promptLine"]]
|
||||
}
|
||||
},
|
||||
// fix
|
||||
"easy preDetailerFix":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute", "easy detailerFix"], ...pipeNodes, ...extraNodes]
|
||||
"PIPE_LINE": [...["easy detailerFix"], ...pipeNodes, ...extraNodes]
|
||||
},
|
||||
"to":{
|
||||
"PIPE_LINE": [...["Reroute", "easy ultralyticsDetectorPipe", "easy samLoaderPipe", "easy kSampler", "easy fullkSampler"]]
|
||||
"PIPE_LINE": [...["easy ultralyticsDetectorPipe", "easy samLoaderPipe", "easy kSampler", "easy fullkSampler"]]
|
||||
}
|
||||
},
|
||||
"easy preMaskDetailerFix":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute", "easy detailerFix"], ...pipeNodes, ...extraNodes]
|
||||
"PIPE_LINE": [...["easy detailerFix"], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy samLoaderPipe": {
|
||||
"from":{
|
||||
"PIPE_LINE": [...["Reroute", "easy preDetailerFix"], ...pipeNodes, ...extraNodes]
|
||||
"PIPE_LINE": [...["easy preDetailerFix"], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy ultralyticsDetectorPipe": {
|
||||
"from":{
|
||||
"PIPE_LINE": [...["Reroute", "easy preDetailerFix"], ...pipeNodes, ...extraNodes]
|
||||
"PIPE_LINE": [...["easy preDetailerFix"], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
// cascade相关
|
||||
"easy cascadeLoader":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...["easy fullCascadeKSampler", 'easy preSamplingCascade'], ...controlNetNodes, ...pipeNodes, ...extraNodes],
|
||||
"PIPE_LINE": [ ...["easy fullCascadeKSampler", 'easy preSamplingCascade'], ...controlNetNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes.filter(cate => !ipadapterNodes.includes(cate))
|
||||
}
|
||||
},
|
||||
"easy fullCascadeKSampler":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...["easy preSampling", "easy preSamplingAdvanced"], ...pipeNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...["easy preSampling", "easy preSamplingAdvanced"], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingCascade":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...["easy cascadeKSampler",], ...pipeNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...["easy cascadeKSampler",], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy cascadeKSampler": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...["easy preSampling", "easy preSamplingAdvanced"], ...pipeNodes, ...extraNodes]
|
||||
"PIPE_LINE": [ ...["easy preSampling", "easy preSamplingAdvanced"], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class NullGraphError extends Error {
|
||||
constructor(message="Attempted to access LGraph reference that was null or undefined.", cause) {
|
||||
super(message, {cause})
|
||||
this.name = "NullGraphError"
|
||||
}
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "comfy.easyuse.suggestions",
|
||||
async setup(app) {
|
||||
async setup() {
|
||||
const createDefaultNodeForSlot = LGraphCanvas.prototype.createDefaultNodeForSlot;
|
||||
LGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection
|
||||
var optPass = optPass || {};
|
||||
var opts = Object.assign({ nodeFrom: null // input
|
||||
,slotFrom: null // input
|
||||
,nodeTo: null // output
|
||||
,slotTo: null // output
|
||||
,position: [] // pass the event coords
|
||||
,nodeType: null // choose a nodetype to add, AUTO to set at first good
|
||||
,posAdd:[0,0] // adjust x,y
|
||||
,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h
|
||||
}
|
||||
,optPass
|
||||
);
|
||||
var that = this;
|
||||
const opts = Object.assign({ nodeFrom: null // input
|
||||
,slotFrom: null // input
|
||||
,nodeTo: null // output
|
||||
,slotTo: null // output
|
||||
,position: [] // pass the event coords
|
||||
,nodeType: null // choose a nodetype to add, AUTO to set at first good
|
||||
,posAdd:[0,0] // adjust x,y
|
||||
,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h
|
||||
}
|
||||
, optPass || {}
|
||||
);
|
||||
const { afterRerouteId } = opts
|
||||
const that = this;
|
||||
|
||||
var isFrom = opts.nodeFrom && opts.slotFrom!==null;
|
||||
var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null;
|
||||
const isFrom = opts.nodeFrom && opts.slotFrom!==null;
|
||||
const isTo = !isFrom && opts.nodeTo && opts.slotTo!==null;
|
||||
const node = isFrom ? opts.nodeFrom : opts.nodeTo
|
||||
// Not an Easy Use node, skip showConnectionMenu hijack
|
||||
if(!node || !Object.keys(suggestions).includes(node.type)){
|
||||
return createDefaultNodeForSlot.call(this, optPass)
|
||||
}
|
||||
|
||||
if (!isFrom && !isTo){
|
||||
console.warn("No data passed to createDefaultNodeForSlot "+opts.nodeFrom+" "+opts.slotFrom+" "+opts.nodeTo+" "+opts.slotTo);
|
||||
@@ -271,24 +282,24 @@ app.registerExtension({
|
||||
return false;
|
||||
}
|
||||
|
||||
var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;
|
||||
var slotX = isFrom ? opts.slotFrom : opts.slotTo;
|
||||
var nodeType = nodeX.type
|
||||
const nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;
|
||||
const nodeType = nodeX.type
|
||||
let slotX = isFrom ? opts.slotFrom : opts.slotTo;
|
||||
|
||||
var iSlotConn = false;
|
||||
let iSlotConn = false;
|
||||
switch (typeof slotX){
|
||||
case "string":
|
||||
iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);
|
||||
slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
|
||||
break;
|
||||
break;
|
||||
case "object":
|
||||
// ok slotX
|
||||
iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);
|
||||
break;
|
||||
break;
|
||||
case "number":
|
||||
iSlotConn = slotX;
|
||||
slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
|
||||
break;
|
||||
break;
|
||||
case "undefined":
|
||||
default:
|
||||
// bad ?
|
||||
@@ -324,8 +335,7 @@ app.registerExtension({
|
||||
for(var typeX in slotTypesDefault[fromSlotType]){
|
||||
if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO"){
|
||||
nodeNewType = slotTypesDefault[fromSlotType][typeX];
|
||||
// console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType);
|
||||
break; // --------
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
@@ -379,9 +389,7 @@ app.registerExtension({
|
||||
// add the node
|
||||
that.graph.add(newNode);
|
||||
newNode.pos = [ opts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0)
|
||||
,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/
|
||||
|
||||
//that.graph.afterChange();
|
||||
,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/
|
||||
|
||||
// connect the two!
|
||||
if (isFrom){
|
||||
@@ -390,11 +398,6 @@ app.registerExtension({
|
||||
opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType );
|
||||
}
|
||||
|
||||
// if connecting in between
|
||||
if (isFrom && isTo){
|
||||
// TODO
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}else{
|
||||
@@ -405,43 +408,54 @@ app.registerExtension({
|
||||
return false;
|
||||
}
|
||||
|
||||
let showConnectionMenu = LGraphCanvas.prototype.showConnectionMenu
|
||||
LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection
|
||||
var optPass = optPass || {};
|
||||
var opts = Object.assign({ nodeFrom: null // input
|
||||
,slotFrom: null // input
|
||||
,nodeTo: null // output
|
||||
,slotTo: null // output
|
||||
,e: null
|
||||
}
|
||||
,optPass
|
||||
);
|
||||
var that = this;
|
||||
const opts = Object.assign({
|
||||
nodeFrom: null, // input
|
||||
slotFrom: null, // input
|
||||
nodeTo: null, // output
|
||||
slotTo: null, // output
|
||||
e: undefined,
|
||||
allow_searchbox: this.allow_searchbox,
|
||||
showSearchBox: this.showSearchBox,
|
||||
}
|
||||
,optPass || {}
|
||||
);
|
||||
const that = this;
|
||||
const { graph } = this
|
||||
const { afterRerouteId } = opts
|
||||
const isFrom = opts.nodeFrom && opts.slotFrom;
|
||||
const isTo = !isFrom && opts.nodeTo && opts.slotTo;
|
||||
const node = isFrom ? opts.nodeFrom : opts.nodeTo
|
||||
|
||||
var isFrom = opts.nodeFrom && opts.slotFrom;
|
||||
var isTo = !isFrom && opts.nodeTo && opts.slotTo;
|
||||
// Not an Easy Use node, skip showConnectionMenu hijack
|
||||
if(!node || !Object.keys(suggestions).includes(node.type)){
|
||||
return showConnectionMenu.call(this, optPass)
|
||||
}
|
||||
|
||||
if (!isFrom && !isTo){
|
||||
console.warn("No data passed to showConnectionMenu");
|
||||
return false;
|
||||
}
|
||||
|
||||
var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;
|
||||
var slotX = isFrom ? opts.slotFrom : opts.slotTo;
|
||||
const nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;
|
||||
if (!nodeX) throw new TypeError("nodeX was null when creating default node for slot.")
|
||||
let slotX = isFrom ? opts.slotFrom : opts.slotTo;
|
||||
|
||||
var iSlotConn = false;
|
||||
let iSlotConn = false;
|
||||
switch (typeof slotX){
|
||||
case "string":
|
||||
iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);
|
||||
slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
|
||||
break;
|
||||
break;
|
||||
case "object":
|
||||
// ok slotX
|
||||
iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);
|
||||
break;
|
||||
break;
|
||||
case "number":
|
||||
iSlotConn = slotX;
|
||||
slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
// bad ?
|
||||
//iSlotConn = 0;
|
||||
@@ -449,9 +463,8 @@ app.registerExtension({
|
||||
return false;
|
||||
}
|
||||
|
||||
var options = ["Add Node",null];
|
||||
|
||||
if (that.allow_searchbox){
|
||||
const options = ["Add Node", "Add Reroute", null]
|
||||
if (opts.allow_searchbox){
|
||||
options.push("Search");
|
||||
options.push(null);
|
||||
}
|
||||
@@ -479,8 +492,12 @@ app.registerExtension({
|
||||
// build menu
|
||||
var menu = new LiteGraph.ContextMenu(options, {
|
||||
event: opts.e,
|
||||
title: (slotX && slotX.name!="" ? (slotX.name + (fromSlotType?" | ":"")) : "")+(slotX && fromSlotType ? fromSlotType : ""),
|
||||
callback: inner_clicked
|
||||
extra: slotX,
|
||||
title:
|
||||
(slotX && slotX.name != ""
|
||||
? slotX.name + (fromSlotType ? " | " : "")
|
||||
: "") + (slotX && fromSlotType ? fromSlotType : ""),
|
||||
callback: inner_clicked,
|
||||
});
|
||||
|
||||
// callback
|
||||
@@ -488,32 +505,45 @@ app.registerExtension({
|
||||
//console.log("Process showConnectionMenu selection");
|
||||
switch (v) {
|
||||
case "Add Node":
|
||||
LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){
|
||||
if (isFrom){
|
||||
opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType );
|
||||
}else{
|
||||
opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType );
|
||||
LGraphCanvas.onMenuAdd(null, null, e, menu, function (node) {
|
||||
if (!node) return
|
||||
|
||||
if (isFrom) {
|
||||
opts.nodeFrom?.connectByType(iSlotConn, node, fromSlotType, { afterRerouteId })
|
||||
} else {
|
||||
opts.nodeTo?.connectByTypeOutput(iSlotConn, node, fromSlotType, { afterRerouteId })
|
||||
}
|
||||
});
|
||||
})
|
||||
break;
|
||||
case "Add Reroute":
|
||||
const node = isFrom ? opts.nodeFrom : opts.nodeTo
|
||||
const slot = options.extra
|
||||
if (!graph) throw new NullGraphError()
|
||||
if (!node) throw new TypeError("Cannot add reroute: node was null")
|
||||
if (!slot) throw new TypeError("Cannot add reroute: slot was null")
|
||||
if (!opts.e) throw new TypeError("Cannot add reroute: CanvasPointerEvent was null")
|
||||
|
||||
const reroute = node.connectFloatingReroute([opts.e.canvasX, opts.e.canvasY], slot, afterRerouteId)
|
||||
if (!reroute) throw new Error("Failed to create reroute")
|
||||
|
||||
that.dirty_canvas = true
|
||||
that.dirty_bgcanvas = true
|
||||
break
|
||||
case "Search":
|
||||
if(isFrom){
|
||||
that.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType});
|
||||
opts.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType});
|
||||
}else{
|
||||
that.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType});
|
||||
opts.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// check for defaults nodes for this slottype
|
||||
var nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY]
|
||||
,nodeType: v
|
||||
}));
|
||||
if (nodeCreated){
|
||||
// new node created
|
||||
//console.log("node "+v+" created")
|
||||
}else{
|
||||
// failed or v is not in defaults
|
||||
const customProps = {
|
||||
position: [opts.e?.canvasX ?? 0, opts.e?.canvasY ?? 0],
|
||||
nodeType: v,
|
||||
afterRerouteId,
|
||||
}
|
||||
// check for defaults nodes for this slottype
|
||||
that.createDefaultNodeForSlot(Object.assign(opts, customProps))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user