random.seed() on the shared module polluted process-wide state; a per-call random.Random(seed) keeps same-seed output identical while isolating other nodes. Seed max now matches the standard ComfyUI 0xffffffffffffffff range on both APIs. Bump to 1.3.1.
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);
|
|
}
|
|
},
|
|
});
|