* Added Wildcard Concat node for selecting and concatenating wildcards.
* Refactor functions to resolve wildcard and extra-network mapping folder paths.
This commit is contained in:
@@ -17,6 +17,7 @@ from .ppp_comfyui import (
|
||||
PromptPostProcessorSTNOptionsComfyUINode,
|
||||
PromptPostProcessorCleanupOptionsComfyUINode,
|
||||
PromptPostProcessorSelectVariableComfyUINode,
|
||||
PromptPostProcessorWildcardConcatComfyUINode,
|
||||
)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
@@ -26,6 +27,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ACBPPPSendToNegativeOptions": PromptPostProcessorSTNOptionsComfyUINode,
|
||||
"ACBPPPCleanupOptions": PromptPostProcessorCleanupOptionsComfyUINode,
|
||||
"ACBPPPSelectVariable": PromptPostProcessorSelectVariableComfyUINode,
|
||||
"ACBPPPWildcardConcat": PromptPostProcessorWildcardConcatComfyUINode,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ACBPromptPostProcessor": "ACB Prompt Post Processor",
|
||||
@@ -34,6 +36,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ACBPPPSendToNegativeOptions": "ACB PPP Send-To-Negative Options",
|
||||
"ACBPPPCleanupOptions": "ACB PPP Cleanup Options",
|
||||
"ACBPPPSelectVariable": "ACB PPP Select Variable",
|
||||
"ACBPPPWildcardConcat": "ACB PPP Wildcard Concat",
|
||||
}
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
|
||||
+45
-5
@@ -12,7 +12,11 @@ The model variants now support regular expressions instead of a list of strings
|
||||
|
||||
## ComfyUI
|
||||
|
||||
### ACB Prompt Post Processor node inputs
|
||||
### ACB Prompt Post Processor node
|
||||
|
||||
The main node that processes the prompt.
|
||||
|
||||
Inputs:
|
||||
|
||||
* **model**: Connect here the MODEL or a string with the model class name used by *ComfyUI*. Needed for the model kind system variables.
|
||||
* **modelname**: Name of the model. Needed for the detection of model variants.
|
||||
@@ -31,16 +35,46 @@ The model variants now support regular expressions instead of a list of strings
|
||||
|
||||
The options nodes are optional. If you don't need to change any of the default values then you don't need to use them.
|
||||
|
||||
### ACB Prompt Post Processor node outputs
|
||||
Outputs:
|
||||
|
||||
The outputs are the final positive and negative prompt and a variables dictionary.
|
||||
* **pos_prompt**: the resulting positive prompt
|
||||
* **neg_prompt**: the resulting negative prompt
|
||||
* **variables**: the dictionary of variables set or echoed.
|
||||
|
||||
You can use the "**ACB PPP Select Variable**" node to choose one and output its value. You can use this to send only part of the prompt to, for example, a detailer node. For example:
|
||||
### ACB PPP Select Variable node
|
||||
|
||||
With this prompt: `__quality__, 1girl, ${head:__eyes__, __hair__, __expression__}, __body__, __clothes__, __background__, __style__` then you extract the `head` variable and use `${head}` as prompt for the head/face detailer.
|
||||
Lets you extract the variables used from the output (or just one of them). You can use this to send only part of the prompt to, for example, a detailer node. For example:
|
||||
|
||||
With this prompt: `__quality__, 1girl, ${head:__eyes__, __hair__, __expression__}, __body__, __clothes__, __background__, __style__` then you extract the `head` variable and use it as prompt for the head/face detailer.
|
||||
|
||||
Inputs:
|
||||
|
||||
* **variables**: the variables dictionary from the main node output.
|
||||
* **name**: optional name of a variable.
|
||||
|
||||
Output:
|
||||
|
||||
* **value**: the resulting content, either all the variables (one per line, in "name: value" format) or just the content of the chosen one.
|
||||
|
||||
### ACB PPP Wildcards Concat node
|
||||
|
||||
This node lets you select up to 10 wildcards that will be concatenated with a chosen separator.
|
||||
|
||||
Inputs:
|
||||
|
||||
* **previous_prompt**: An optional text that will be prepended to the wildcards. Lets you chain multiple nodes or other string nodes.
|
||||
* **filter**: a string to filter the identifiers of the wildcards. It matches the start of the identifiers.
|
||||
* **separator**: a separator string.
|
||||
* **wildcard_<n>**: the wildcards to concatenate.
|
||||
|
||||
Output:
|
||||
|
||||
* **prompt**: concatenated result.
|
||||
|
||||
### ACB PPP Wildcard Options node
|
||||
|
||||
Options for wildcard processing, in case you want to change them from the defaults.
|
||||
|
||||
* **folders**: You can enter multiple folders separated by commas. You can leave it empty (the default) and add a `ppp_wildcards` or `wildcards` entry in the **extra_model_paths.yaml** file (recommended).
|
||||
* **definitions**: Wildcards definitions (in yaml or json format). Direct input added to the ones found in the wildcards folders. Allows wildcards to be included in the workflow.
|
||||
* **if_wildcards**: Select what do you want to do with any found wildcards/choices (when process wildcards is off or after the processing).
|
||||
@@ -53,11 +87,15 @@ With this prompt: `__quality__, 1girl, ${head:__eyes__, __hair__, __expression__
|
||||
|
||||
### ACB PPP Send-To-Negative Options node
|
||||
|
||||
Options for sent to negative commands, in case you want to change them from the defaults.
|
||||
|
||||
* **separator**: You can specify the separator used when adding to the negative prompt (by default it's ", ").
|
||||
* **ignore_repeats**: It ignores repeated content to avoid repetitions in the negative prompt.
|
||||
|
||||
### ACB PPP Cleanup Options node
|
||||
|
||||
Options for cleanup processing, in case you want to change them from the defaults.
|
||||
|
||||
* **extra_spaces**: Removes other unnecessary spaces.
|
||||
* **empty_constructs**: Removes attention/scheduling/alternation constructs when they are invalid.
|
||||
* **extra_separators**: Removes unnecessary separators. This applies to the configured separator and regular commas.
|
||||
@@ -75,6 +113,8 @@ Please note that *ComfyUI* does not natively support the `BREAK` and `AND` const
|
||||
|
||||
### ACB PPP ExtraNetwork Mapping Options node
|
||||
|
||||
Options for extranetworks mapping, in case you want to change them from the defaults.
|
||||
|
||||
* **folders**: You can enter multiple folders separated by commas. You can leave it empty (the default) and add a `ppp_extranetworkmappings` entry in the **extra_model_paths.yaml** file (recommended).
|
||||
* **definitions**: Extranetwork Mappings definitions (in yaml format). Direct input added to the ones found in the extranetwork mappings folders. Allows the mappings to be included in the workflow.
|
||||
|
||||
|
||||
+174
-34
@@ -14,6 +14,48 @@ if __name__ == "__main__":
|
||||
raise SystemExit("This script must be run from ComfyUI")
|
||||
|
||||
|
||||
def _resolve_wildcards_folders(override: str = "") -> list[str]:
|
||||
"""Return the resolved list of wildcard folder paths."""
|
||||
folders_str = override
|
||||
if folders_str == "":
|
||||
try:
|
||||
fp1 = folder_paths.get_folder_paths("ppp_wildcards")
|
||||
except Exception: # pylint: disable=W0718
|
||||
fp1 = None
|
||||
try:
|
||||
fp2 = folder_paths.get_folder_paths("wildcards")
|
||||
except Exception: # pylint: disable=W0718
|
||||
fp2 = None
|
||||
folders_str = ",".join(fp1 or fp2 or [])
|
||||
if folders_str == "":
|
||||
folders_str = os.getenv("WILDCARD_DIR", PPPWildcards.DEFAULT_WILDCARDS_FOLDER)
|
||||
return [
|
||||
(f if os.path.isabs(f) else os.path.abspath(os.path.join(folder_paths.models_dir, f)))
|
||||
for f in folders_str.split(",")
|
||||
if f.strip() != ""
|
||||
]
|
||||
|
||||
|
||||
def _resolve_enmappings_folders(override: str = "") -> list[str]:
|
||||
"""Return the resolved list of extra-network mapping folder paths."""
|
||||
folders_str = override
|
||||
if folders_str == "":
|
||||
try:
|
||||
fp3 = folder_paths.get_folder_paths("ppp_extranetworkmappings")
|
||||
except Exception: # pylint: disable=W0718
|
||||
fp3 = None
|
||||
folders_str = ",".join(fp3 or [])
|
||||
if folders_str == "":
|
||||
folders_str = os.getenv(
|
||||
"EXTRANETWORKMAPPINGS_DIR", PPPExtraNetworkMappings.DEFAULT_ENMAPPINGS_FOLDER
|
||||
)
|
||||
return [
|
||||
(f if os.path.isabs(f) else os.path.abspath(os.path.join(folder_paths.models_dir, f)))
|
||||
for f in folders_str.split(",")
|
||||
if f.strip() != ""
|
||||
]
|
||||
|
||||
|
||||
class PromptPostProcessorComfyUINode:
|
||||
"""
|
||||
Node for processing prompts.
|
||||
@@ -220,40 +262,8 @@ class PromptPostProcessorComfyUINode:
|
||||
"model_class": modelclass,
|
||||
"property_base": None,
|
||||
}
|
||||
wc_wildcards_folders = wc_options["wc_wildcards_folders"] if wc_options else ""
|
||||
if wc_wildcards_folders == "":
|
||||
try:
|
||||
fp1 = folder_paths.get_folder_paths("ppp_wildcards")
|
||||
except Exception: # pylint: disable=W0718
|
||||
fp1 = None
|
||||
try:
|
||||
fp2 = folder_paths.get_folder_paths("wildcards")
|
||||
except Exception: # pylint: disable=W0718
|
||||
fp2 = None
|
||||
wc_wildcards_folders = ",".join(fp1 or fp2 or [])
|
||||
if wc_wildcards_folders == "":
|
||||
wc_wildcards_folders = os.getenv("WILDCARD_DIR", PPPWildcards.DEFAULT_WILDCARDS_FOLDER)
|
||||
wildcards_folders = [
|
||||
(f if os.path.isabs(f) else os.path.abspath(os.path.join(folder_paths.models_dir, f)))
|
||||
for f in wc_wildcards_folders.split(",")
|
||||
if f.strip() != ""
|
||||
]
|
||||
en_mappings_folders = en_options["en_mappings_folders"] if en_options else ""
|
||||
if en_mappings_folders == "":
|
||||
try:
|
||||
fp3 = folder_paths.get_folder_paths("ppp_extranetworkmappings")
|
||||
except Exception: # pylint: disable=W0718
|
||||
fp3 = None
|
||||
en_mappings_folders = ",".join(fp3 or [])
|
||||
if en_mappings_folders == "":
|
||||
en_mappings_folders = os.getenv(
|
||||
"EXTRANETWORKMAPPINGS_DIR", PPPExtraNetworkMappings.DEFAULT_ENMAPPINGS_FOLDER
|
||||
)
|
||||
enmappings_folders = [
|
||||
(f if os.path.isabs(f) else os.path.abspath(os.path.join(folder_paths.models_dir, f)))
|
||||
for f in en_mappings_folders.split(",")
|
||||
if f.strip() != ""
|
||||
]
|
||||
wildcards_folders = _resolve_wildcards_folders(wc_options["wc_wildcards_folders"] if wc_options else "")
|
||||
enmappings_folders = _resolve_enmappings_folders(en_options["en_mappings_folders"] if en_options else "")
|
||||
|
||||
options = {
|
||||
"debug_level": debug_level,
|
||||
@@ -743,3 +753,133 @@ class PromptPostProcessorSelectVariableComfyUINode:
|
||||
elif name in variables:
|
||||
value = variables[name]
|
||||
return (value,)
|
||||
|
||||
|
||||
class PromptPostProcessorWildcardConcatComfyUINode:
|
||||
"""
|
||||
Node for selecting and concatenating wildcards as a prompt.
|
||||
"""
|
||||
|
||||
NONE_OPTION = "(none)"
|
||||
_wildcards_obj = None
|
||||
_wildcards_log = None
|
||||
|
||||
@classmethod
|
||||
def _ensure_wildcards(cls):
|
||||
if cls._wildcards_obj is None:
|
||||
lf = PromptPostProcessorLogFactory(SUPPORTED_APPS.comfyui)
|
||||
cls._wildcards_log = lf.log
|
||||
cls._wildcards_obj = PPPWildcards(cls._wildcards_log)
|
||||
cls._wildcards_obj.refresh_wildcards(DEBUG_LEVEL.minimal, _resolve_wildcards_folders())
|
||||
return cls._wildcards_obj
|
||||
|
||||
@classmethod
|
||||
def get_wildcard_keys(cls, filter_prefix=""):
|
||||
wc_obj = cls._ensure_wildcards()
|
||||
keys = sorted(wc_obj.wildcards.keys())
|
||||
if filter_prefix:
|
||||
keys = [k for k in keys if k.startswith(filter_prefix)]
|
||||
return [cls.NONE_OPTION] + keys
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
wc_list = cls.get_wildcard_keys()
|
||||
return {
|
||||
"required": {
|
||||
"filter": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "build",
|
||||
"multiline": False,
|
||||
"dynamicPrompts": False,
|
||||
"tooltip": "Filter wildcards by prefix (changing this refreshes the dropdowns)",
|
||||
},
|
||||
),
|
||||
"separator": (
|
||||
"STRING",
|
||||
{
|
||||
"default": ", ",
|
||||
"multiline": False,
|
||||
"dynamicPrompts": False,
|
||||
"tooltip": "Separator used to concatenate the selected wildcard references",
|
||||
},
|
||||
),
|
||||
"wildcard_1": (wc_list, {"tooltip": "Wildcard 1"}),
|
||||
"wildcard_2": (wc_list, {"tooltip": "Wildcard 2"}),
|
||||
"wildcard_3": (wc_list, {"tooltip": "Wildcard 3"}),
|
||||
"wildcard_4": (wc_list, {"tooltip": "Wildcard 4"}),
|
||||
"wildcard_5": (wc_list, {"tooltip": "Wildcard 5"}),
|
||||
"wildcard_6": (wc_list, {"tooltip": "Wildcard 6"}),
|
||||
"wildcard_7": (wc_list, {"tooltip": "Wildcard 7"}),
|
||||
"wildcard_8": (wc_list, {"tooltip": "Wildcard 8"}),
|
||||
"wildcard_9": (wc_list, {"tooltip": "Wildcard 9"}),
|
||||
"wildcard_10": (wc_list, {"tooltip": "Wildcard 10"}),
|
||||
},
|
||||
"optional": {
|
||||
"previous_prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"forceInput": True,
|
||||
"tooltip": "Connect to the prompt output of another Wildcard Concat node to chain more than 10 wildcards",
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
|
||||
FUNCTION = "process"
|
||||
|
||||
CATEGORY = "ACB"
|
||||
|
||||
def process(
|
||||
self,
|
||||
filter, # noqa: A002 # pylint: disable=redefined-builtin, unused-argument
|
||||
separator,
|
||||
wildcard_1,
|
||||
wildcard_2,
|
||||
wildcard_3,
|
||||
wildcard_4,
|
||||
wildcard_5,
|
||||
wildcard_6,
|
||||
wildcard_7,
|
||||
wildcard_8,
|
||||
wildcard_9,
|
||||
wildcard_10,
|
||||
previous_prompt=None,
|
||||
):
|
||||
parts = [
|
||||
f"__{w}__"
|
||||
for w in [
|
||||
wildcard_1,
|
||||
wildcard_2,
|
||||
wildcard_3,
|
||||
wildcard_4,
|
||||
wildcard_5,
|
||||
wildcard_6,
|
||||
wildcard_7,
|
||||
wildcard_8,
|
||||
wildcard_9,
|
||||
wildcard_10,
|
||||
]
|
||||
if w != self.NONE_OPTION
|
||||
]
|
||||
result = separator.join(parts)
|
||||
if previous_prompt:
|
||||
result = previous_prompt + separator + result if result else previous_prompt
|
||||
return (result,)
|
||||
|
||||
|
||||
try:
|
||||
from server import PromptServer # type: ignore # pylint: disable=import-error
|
||||
from aiohttp import web as _aiohttp_web # type: ignore # pylint: disable=import-error
|
||||
|
||||
@PromptServer.instance.routes.get("/acb_ppp/wildcards")
|
||||
async def _acb_ppp_get_wildcards(request):
|
||||
filter_prefix = request.rel_url.query.get("filter", "")
|
||||
wc_list = PromptPostProcessorWildcardConcatComfyUINode.get_wildcard_keys(filter_prefix)
|
||||
return _aiohttp_web.json_response({"wildcards": wc_list})
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "ACB.PPP.WildcardConcat",
|
||||
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== "ACBPPPWildcardConcat") return;
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const result = onNodeCreated?.apply(this, arguments);
|
||||
|
||||
const self = this;
|
||||
|
||||
const refreshWildcards = async () => {
|
||||
const filterWidget = self.widgets?.find((w) => w.name === "filter");
|
||||
if (!filterWidget) return;
|
||||
|
||||
try {
|
||||
const resp = await api.fetchApi(
|
||||
`/acb_ppp/wildcards?filter=${encodeURIComponent(filterWidget.value ?? "")}`
|
||||
);
|
||||
const data = await resp.json();
|
||||
const wildcards = data.wildcards ?? [];
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const widget = self.widgets?.find((w) => w.name === `wildcard_${i}`);
|
||||
if (widget) {
|
||||
const current = widget.value;
|
||||
widget.options.values = wildcards;
|
||||
widget.value = wildcards.includes(current)
|
||||
? current
|
||||
: wildcards[0] ?? "(none)";
|
||||
}
|
||||
}
|
||||
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
} catch (err) {
|
||||
console.error("[ACB PPP] Failed to refresh wildcard list:", err);
|
||||
}
|
||||
};
|
||||
|
||||
// Hook the filter widget so that committing a new value (Enter / blur)
|
||||
// immediately refreshes all 10 wildcard dropdowns without running the workflow.
|
||||
const filterWidget = this.widgets?.find((w) => w.name === "filter");
|
||||
if (filterWidget) {
|
||||
const origCallback = filterWidget.callback;
|
||||
filterWidget.callback = async function (...args) {
|
||||
if (origCallback) origCallback.apply(this, args);
|
||||
await refreshWildcards();
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user