Add new OutfitBuilder node
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
from ..config import NODE_CATEGORY
|
||||
from ..utils import clean_prompt, override
|
||||
from ..utils_loader import clean_val
|
||||
|
||||
SOURCE_MODES = ["both", "outfit", "character"]
|
||||
|
||||
|
||||
def _merge_source(mode, character_val, outfit_val, join=", "):
|
||||
"""
|
||||
mode: "both" | "outfit" | "character"
|
||||
"""
|
||||
cv = clean_val(character_val)
|
||||
ov = clean_val(outfit_val)
|
||||
|
||||
if mode == "character":
|
||||
return cv
|
||||
if mode == "outfit":
|
||||
return ov
|
||||
|
||||
if cv and ov:
|
||||
return clean_prompt(cv + join + ov)
|
||||
|
||||
return cv or ov
|
||||
|
||||
|
||||
class OutfitBuilder:
|
||||
CATEGORY = NODE_CATEGORY
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"preset_data": ("STRING", {"default": "{}"}),
|
||||
"hat_override": ("STRING", {"default": ""}),
|
||||
"hat_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"facewear_override": ("STRING", {"default": ""}),
|
||||
"facewear_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"facewear_source": (SOURCE_MODES,),
|
||||
"neckwear_override": ("STRING", {"default": ""}),
|
||||
"neckwear_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"neckwear_source": (SOURCE_MODES,),
|
||||
"coat_override": ("STRING", {"default": ""}),
|
||||
"coat_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"midlayer_override": ("STRING", {"default": ""}),
|
||||
"midlayer_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"top_override": ("STRING", {"default": ""}),
|
||||
"top_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"armwear_override": ("STRING", {"default": ""}),
|
||||
"armwear_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"armwear_source": (SOURCE_MODES,),
|
||||
"accessories_override": ("STRING", {"default": ""}),
|
||||
"accessories_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"bottom_override": ("STRING", {"default": ""}),
|
||||
"bottom_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"legwear_override": ("STRING", {"default": ""}),
|
||||
"legwear_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"footwear_override": ("STRING", {"default": ""}),
|
||||
"footwear_override_mode": ("BOOLEAN", {"default": False}),
|
||||
"use_bypass": ("BOOLEAN", {"default": False}),
|
||||
"bypass_options": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"bypass_selected": ("STRING", {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"hat": ("STRING", {"forceInput": True}),
|
||||
"facewear": ("STRING", {"forceInput": True}),
|
||||
"neckwear": ("STRING", {"forceInput": True}),
|
||||
"coat": ("STRING", {"forceInput": True}),
|
||||
"midlayer": ("STRING", {"forceInput": True}),
|
||||
"top": ("STRING", {"forceInput": True}),
|
||||
"armwear": ("STRING", {"forceInput": True}),
|
||||
"accessories": ("STRING", {"forceInput": True}),
|
||||
"bottom": ("STRING", {"forceInput": True}),
|
||||
"legwear": ("STRING", {"forceInput": True}),
|
||||
"footwear": ("STRING", {"forceInput": True}),
|
||||
"facewear_modifiers": ("STRING", {"forceInput": True}),
|
||||
"neckwear_modifiers": ("STRING", {"forceInput": True}),
|
||||
"armwear_modifiers": ("STRING", {"forceInput": True}),
|
||||
"top_modifiers": ("STRING", {"forceInput": True}),
|
||||
"bottom_modifiers": ("STRING", {"forceInput": True}),
|
||||
"outfit_modifiers": ("STRING", {"forceInput": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("outfit",)
|
||||
FUNCTION = "build"
|
||||
|
||||
def build(
|
||||
self,
|
||||
preset_data,
|
||||
hat_override,
|
||||
hat_override_mode,
|
||||
facewear_override,
|
||||
facewear_override_mode,
|
||||
facewear_source,
|
||||
neckwear_override,
|
||||
neckwear_override_mode,
|
||||
neckwear_source,
|
||||
coat_override,
|
||||
coat_override_mode,
|
||||
midlayer_override,
|
||||
midlayer_override_mode,
|
||||
top_override,
|
||||
top_override_mode,
|
||||
armwear_override,
|
||||
armwear_override_mode,
|
||||
armwear_source,
|
||||
accessories_override,
|
||||
accessories_override_mode,
|
||||
bottom_override,
|
||||
bottom_override_mode,
|
||||
legwear_override,
|
||||
legwear_override_mode,
|
||||
footwear_override,
|
||||
footwear_override_mode,
|
||||
use_bypass,
|
||||
bypass_options,
|
||||
bypass_selected,
|
||||
# Optional
|
||||
hat="",
|
||||
facewear="",
|
||||
neckwear="",
|
||||
coat="",
|
||||
midlayer="",
|
||||
top="",
|
||||
armwear="",
|
||||
accessories="",
|
||||
bottom="",
|
||||
legwear="",
|
||||
footwear="",
|
||||
facewear_modifiers="",
|
||||
neckwear_modifiers="",
|
||||
armwear_modifiers="",
|
||||
top_modifiers="",
|
||||
bottom_modifiers="",
|
||||
outfit_modifiers="",
|
||||
):
|
||||
if use_bypass:
|
||||
return (clean_val(bypass_selected),)
|
||||
|
||||
hat_out = override(hat_override, hat_override_mode, clean_val(hat))
|
||||
|
||||
facewear_base = _merge_source(facewear_source, facewear_modifiers, facewear)
|
||||
facewear_out = override(
|
||||
facewear_override, facewear_override_mode, facewear_base
|
||||
)
|
||||
|
||||
neckwear_base = _merge_source(neckwear_source, neckwear_modifiers, neckwear)
|
||||
neckwear_out = override(
|
||||
neckwear_override, neckwear_override_mode, neckwear_base
|
||||
)
|
||||
|
||||
coat_out = override(coat_override, coat_override_mode, clean_val(coat))
|
||||
|
||||
midlayer_out = override(
|
||||
midlayer_override, midlayer_override_mode, clean_val(midlayer)
|
||||
)
|
||||
|
||||
top_out = override(top_override, top_override_mode, clean_val(top))
|
||||
|
||||
armwear_base = _merge_source(armwear_source, armwear_modifiers, armwear)
|
||||
armwear_out = override(armwear_override, armwear_override_mode, armwear_base)
|
||||
|
||||
accessories_out = override(
|
||||
accessories_override, accessories_override_mode, clean_val(accessories)
|
||||
)
|
||||
|
||||
bottom_out = override(bottom_override, bottom_override_mode, clean_val(bottom))
|
||||
|
||||
legwear_out = override(
|
||||
legwear_override, legwear_override_mode, clean_val(legwear)
|
||||
)
|
||||
|
||||
footwear_out = override(
|
||||
footwear_override, footwear_override_mode, clean_val(footwear)
|
||||
)
|
||||
|
||||
# --- Output assembly ---
|
||||
groups = []
|
||||
|
||||
if v := clean_val(hat_out):
|
||||
groups.append(f"({v})")
|
||||
|
||||
facewear_neckwear = [p for p in [facewear_out, neckwear_out] if p]
|
||||
if facewear_neckwear:
|
||||
groups.append(f"({', '.join(facewear_neckwear)})")
|
||||
|
||||
if v := clean_val(coat_out):
|
||||
groups.append(f"({v})")
|
||||
|
||||
if v := clean_val(midlayer_out):
|
||||
groups.append(f"({v})")
|
||||
|
||||
top_group = [p for p in [top_out, clean_val(top_modifiers)] if p]
|
||||
if top_group:
|
||||
groups.append(f"({', '.join(top_group)})")
|
||||
|
||||
if v := clean_val(armwear_out):
|
||||
groups.append(f"({v})")
|
||||
|
||||
if v := clean_val(accessories_out):
|
||||
groups.append(f"({v})")
|
||||
|
||||
bottom_group = [p for p in [bottom_out, clean_val(bottom_modifiers)] if p]
|
||||
if bottom_group:
|
||||
groups.append(f"({', '.join(bottom_group)})")
|
||||
|
||||
if v := clean_val(legwear_out):
|
||||
groups.append(f"({v})")
|
||||
|
||||
if v := clean_val(footwear_out):
|
||||
groups.append(f"({v})")
|
||||
|
||||
if v := clean_val(outfit_modifiers):
|
||||
groups.append(f"({v})")
|
||||
|
||||
outfit_out = clean_prompt(", ".join(groups))
|
||||
|
||||
return (outfit_out,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"OutfitBuilder": OutfitBuilder,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"OutfitBuilder": "Outfit Builder",
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import { COLORS, DISABLED_OPACITY } from "./constants.js";
|
||||
import { attachInlineSelector } from "./inline_selector.js";
|
||||
import { createPresetManager } from "./preset_manager.js";
|
||||
import {
|
||||
drawGroupBorder,
|
||||
findWidget,
|
||||
hideWidget,
|
||||
hookWidget,
|
||||
registerNode,
|
||||
waitForWidgets,
|
||||
} from "./utils.js";
|
||||
import { replaceWithInlineTextToggle } from "./widgets/inline_text_toggle_widget.js";
|
||||
|
||||
// --- Constants ---
|
||||
|
||||
const NODE_NAME = "OutfitBuilder";
|
||||
|
||||
const OVERRIDE_FIELDS = [
|
||||
"hat_override",
|
||||
"facewear_override",
|
||||
"neckwear_override",
|
||||
"coat_override",
|
||||
"midlayer_override",
|
||||
"top_override",
|
||||
"armwear_override",
|
||||
"accessories_override",
|
||||
"bottom_override",
|
||||
"legwear_override",
|
||||
"footwear_override",
|
||||
];
|
||||
|
||||
const SOURCE_FIELDS = ["facewear_source", "neckwear_source", "armwear_source"];
|
||||
|
||||
const BOOL_FIELDS = ["use_bypass", ...OVERRIDE_FIELDS.map((f) => `${f}_mode`)];
|
||||
|
||||
const setInlineSelectorDisabled = (selectorInstance, disabled) => {
|
||||
if (!selectorInstance?.editor) return;
|
||||
selectorInstance.editor.style.opacity = disabled
|
||||
? `${DISABLED_OPACITY}`
|
||||
: "1";
|
||||
selectorInstance.editor.style.pointerEvents = disabled ? "none" : "auto";
|
||||
};
|
||||
|
||||
const applyQuickOverrideVisibility = (
|
||||
node,
|
||||
toggleWidgets,
|
||||
quickSelectorInstance,
|
||||
) => {
|
||||
const useQuick = findWidget(node, "use_bypass")?.value ?? false;
|
||||
|
||||
for (const field of OVERRIDE_FIELDS) {
|
||||
const helper = toggleWidgets[field];
|
||||
if (helper) helper.disabled = useQuick;
|
||||
}
|
||||
for (const field of SOURCE_FIELDS) {
|
||||
const w = findWidget(node, field);
|
||||
if (w) w.disabled = useQuick;
|
||||
}
|
||||
|
||||
setInlineSelectorDisabled(quickSelectorInstance, !useQuick);
|
||||
|
||||
if (node.graph) node.graph.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
// --- Capture / Recall ---
|
||||
|
||||
const captureState = (node) => {
|
||||
const state = {};
|
||||
for (const field of BOOL_FIELDS) {
|
||||
const w = findWidget(node, field);
|
||||
if (w) state[field] = w.value ?? false;
|
||||
}
|
||||
for (const field of OVERRIDE_FIELDS) {
|
||||
const w = findWidget(node, field);
|
||||
if (w) state[field] = w.value ?? "";
|
||||
}
|
||||
for (const field of SOURCE_FIELDS) {
|
||||
const w = findWidget(node, field);
|
||||
if (w) state[field] = w.value ?? "outfit";
|
||||
}
|
||||
const optionsW = findWidget(node, "bypass_options");
|
||||
const selectedW = findWidget(node, "bypass_selected");
|
||||
if (optionsW) state.bypass_options = optionsW.value ?? "";
|
||||
if (selectedW) state.bypass_selected = selectedW.value ?? "";
|
||||
return state;
|
||||
};
|
||||
|
||||
const recallState = (node, state) => {
|
||||
const fields = [
|
||||
...BOOL_FIELDS,
|
||||
...OVERRIDE_FIELDS,
|
||||
...SOURCE_FIELDS,
|
||||
"bypass_options",
|
||||
"bypass_selected",
|
||||
];
|
||||
for (const field of fields) {
|
||||
if (!(field in state)) continue;
|
||||
const w = findWidget(node, field);
|
||||
if (!w) continue;
|
||||
w.value = state[field];
|
||||
w.callback?.(state[field]);
|
||||
}
|
||||
if (node.graph) node.graph.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
// --- Attach ---
|
||||
|
||||
const attachOutfitBuilder = (node) => {
|
||||
const toggleWidgets = {};
|
||||
for (const field of OVERRIDE_FIELDS) {
|
||||
const textWidget = findWidget(node, field);
|
||||
const modeWidget = findWidget(node, `${field}_mode`);
|
||||
if (!textWidget || !modeWidget) continue;
|
||||
toggleWidgets[field] = replaceWithInlineTextToggle(
|
||||
node,
|
||||
textWidget,
|
||||
modeWidget,
|
||||
);
|
||||
}
|
||||
|
||||
const quickOptions = findWidget(node, "bypass_options");
|
||||
const quickSelected = findWidget(node, "bypass_selected");
|
||||
let quickSelectorInstance = null;
|
||||
if (quickOptions && quickSelected) {
|
||||
hideWidget(quickSelected);
|
||||
attachInlineSelector(node, quickOptions, quickSelected, {
|
||||
placeholder: "quick override",
|
||||
});
|
||||
}
|
||||
|
||||
const useQuickWidget = findWidget(node, "use_bypass");
|
||||
if (useQuickWidget) {
|
||||
hookWidget(useQuickWidget, () =>
|
||||
applyQuickOverrideVisibility(
|
||||
node,
|
||||
toggleWidgets,
|
||||
quickSelectorInstance,
|
||||
),
|
||||
);
|
||||
}
|
||||
applyQuickOverrideVisibility(node, toggleWidgets, quickSelectorInstance);
|
||||
|
||||
const groups = [
|
||||
{ from: "hat_override", to: "hat_override", color: COLORS.blue },
|
||||
{ from: "facewear_override", to: "facewear_source" },
|
||||
{ from: "neckwear_override", to: "neckwear_source" },
|
||||
{ from: "top_override", to: "top_override", color: COLORS.blue },
|
||||
{ from: "armwear_override", to: "armwear_source" },
|
||||
{ from: "bottom_override", to: "bottom_override", color: COLORS.blue },
|
||||
{
|
||||
from: "footwear_override",
|
||||
to: "footwear_override",
|
||||
color: COLORS.blue,
|
||||
},
|
||||
];
|
||||
const original = node.onDrawForeground;
|
||||
node.onDrawForeground = function (ctx) {
|
||||
if (original) original.call(this, ctx);
|
||||
|
||||
for (const group of groups) {
|
||||
drawGroupBorder(
|
||||
ctx,
|
||||
node,
|
||||
group.from,
|
||||
group.to,
|
||||
group.color ?? COLORS.highlight,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// --- Preset manager ---
|
||||
const { presetRow, exportImportRow } = createPresetManager(node, {
|
||||
nodeLabel: NODE_NAME,
|
||||
onCapture: () => captureState(node),
|
||||
onRecall: (state) => {
|
||||
recallState(node, state);
|
||||
applyQuickOverrideVisibility(
|
||||
node,
|
||||
toggleWidgets,
|
||||
quickSelectorInstance,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
node.widgets.push(presetRow);
|
||||
node.widgets.push(exportImportRow);
|
||||
};
|
||||
|
||||
// --- Registration ---
|
||||
|
||||
registerNode(NODE_NAME, (node) => waitForWidgets(node, attachOutfitBuilder));
|
||||
Reference in New Issue
Block a user