134 lines
6.0 KiB
JavaScript
134 lines
6.0 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
|
|
// ── Per-category labels that are sex-specific ─────────────────────────────────
|
|
// Mirrors the diff between data/male/ and data/female/ JSON files.
|
|
|
|
const MALE_ONLY = {
|
|
hair_style_fixed: ["braids short","buzz cut","crew cut","curly medium","locs long","locs short","messy medium","pompadour","quiff","side part","skin fade","slicked back","taper fade","undercut"],
|
|
hair_length_fixed: [],
|
|
body_type_fixed: ["barrel chested","broad shouldered","compact","dad bod"],
|
|
face_shape_fixed: ["rectangular"],
|
|
nose_shape_fixed: ["aquiline nose","curved nose bridge","roman nose","rounded nose tip"],
|
|
mouth_shape_fixed: [],
|
|
hair_color_fixed: [],
|
|
face_feature_fixed: ["birthmark on jaw","heavy brow","mole on cheek","strong jaw","weathered skin"],
|
|
facial_hair_fixed: ["circle beard","full beard","goatee","heavy stubble","light stubble","moustache","mutton chops","short beard","soul patch","van dyke"],
|
|
makeup_fixed: [],
|
|
clothing_fixed: ["bomber jacket","crew-neck sweater","flannel shirt","henley shirt","parka","polo shirt","three-piece suit","tracksuit","wool overcoat","work shirt"],
|
|
footwear_fixed: [],
|
|
shoulders_fixed: ["muscular"],
|
|
chest_fixed: ["average","barrel","broad","flat","muscular"],
|
|
bust_size_fixed: [],
|
|
bust_shape_fixed: [],
|
|
};
|
|
|
|
const FEMALE_ONLY = {
|
|
hair_style_fixed: ["braid over shoulder","braids","chin length bob","curtain bangs","half up","high ponytail","jaw length bob","layered","locs","loose curls","loose waves","messy","pixie","pulled back loose","space buns","tight curls","twist out"],
|
|
hair_length_fixed: ["chin length","very long"],
|
|
body_type_fixed: ["curvy","full figured","hourglass","pear","petite"],
|
|
face_shape_fixed: ["heart"],
|
|
nose_shape_fixed: ["button nose","upturned nose"],
|
|
mouth_shape_fixed: ["bow shaped lips","heart shaped lips","rosebud lips"],
|
|
hair_color_fixed: ["platinum blonde","strawberry blonde"],
|
|
face_feature_fixed: ["arched brows","beauty mark","birthmark","wide eyes"],
|
|
facial_hair_fixed: [],
|
|
makeup_fixed: ["bold lipstick","bold makeup","glam makeup","minimal makeup","natural makeup","red lipstick","smokey eye","soft makeup","winged eyeliner"],
|
|
clothing_fixed: ["blouse","camisole","cardigan","cocktail dress","jumpsuit","knit sweater","maxi dress","off-shoulder top","peplum top","sundress","wool coat","wrap dress"],
|
|
footwear_fixed: ["ballet flats","high boots","high heels","kitten heels","knee-high boots","mary janes","platform heels","stilettos","strappy heels","wedges"],
|
|
shoulders_fixed: ["soft"],
|
|
chest_fixed: [],
|
|
bust_size_fixed: ["average","extra large","full","large","petite","small"],
|
|
bust_shape_fixed: ["close-set","natural","round","teardrop","wide-set"],
|
|
};
|
|
|
|
const SEX_GATED_WIDGETS = Object.keys(MALE_ONLY);
|
|
|
|
const GATED_NODE_IDS = new Set([
|
|
"RandomPersonNode",
|
|
"RandomPersonFace",
|
|
"RandomPersonHair",
|
|
"RandomPersonBody",
|
|
"RandomPersonStyle",
|
|
]);
|
|
|
|
// ── Apply sex filter to all gated widgets on a node ───────────────────────────
|
|
|
|
function applyFilters(node, sex) {
|
|
for (const widgetName of SEX_GATED_WIDGETS) {
|
|
const w = node.widgets?.find(w => w.name === widgetName);
|
|
if (!w?.options?.values) continue;
|
|
|
|
// Store the full unfiltered list on first encounter
|
|
if (!w._allValues) w._allValues = [...w.options.values];
|
|
|
|
if (sex === "random") {
|
|
// Restore all options
|
|
w.options.values = [...w._allValues];
|
|
} else {
|
|
const excluded = new Set(
|
|
sex === "male" ? (FEMALE_ONLY[widgetName] || [])
|
|
: (MALE_ONLY[widgetName] || [])
|
|
);
|
|
w.options.values = w._allValues.filter(v => v === "(none)" || !excluded.has(v));
|
|
|
|
// If the selected value was just hidden, reset to (none)
|
|
if (excluded.has(w.value)) w.value = "(none)";
|
|
}
|
|
}
|
|
|
|
node.setDirtyCanvas?.(true, true);
|
|
}
|
|
|
|
// ── Extension ─────────────────────────────────────────────────────────────────
|
|
|
|
app.registerExtension({
|
|
name: "RandomPersonNode.SexAwareWidgets",
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
if (!GATED_NODE_IDS.has(nodeData.name)) return;
|
|
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
const result = onNodeCreated?.apply(this, arguments);
|
|
|
|
// Default width
|
|
if (this.size) this.size[0] = 420;
|
|
|
|
// Watch the sex widget
|
|
const sexWidget = this.widgets?.find(w => w.name === "sex");
|
|
if (sexWidget) {
|
|
// Apply immediately for the current value
|
|
applyFilters(this, sexWidget.value);
|
|
|
|
// Intercept changes
|
|
const node = this;
|
|
const origCallback = sexWidget.callback;
|
|
sexWidget.callback = function (value) {
|
|
origCallback?.call(this, value);
|
|
applyFilters(node, value);
|
|
};
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
// Nodes 2.0 width fallbacks
|
|
if (typeof nodeType.prototype.getDefaultSize === "function") {
|
|
nodeType.prototype.getDefaultSize = function () {
|
|
return [420, this.size?.[1] ?? 600];
|
|
};
|
|
} else {
|
|
nodeType.prototype.defaultWidth = 420;
|
|
}
|
|
},
|
|
|
|
// Re-apply after workflow load — widget values are restored before nodeCreated fires
|
|
async nodeCreated(node) {
|
|
if (!GATED_NODE_IDS.has(node.comfyClass)) return;
|
|
const sexWidget = node.widgets?.find(w => w.name === "sex");
|
|
if (sexWidget) {
|
|
setTimeout(() => applyFilters(node, sexWidget.value), 50);
|
|
}
|
|
},
|
|
});
|