From 1ce4dc851f07131d017fb37cd85d970176c4d2fa Mon Sep 17 00:00:00 2001 From: BRADSEC <7948876+bradsec@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:50:47 +1000 Subject: [PATCH] Use local RNG instance, widen seed to 64-bit, add age_mode tooltip (review R1-R4) 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. --- js/random_person_node.js | 2 +- nodes.py | 30 ++++++++++++++++-------------- pyproject.toml | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/js/random_person_node.js b/js/random_person_node.js index a38060e..9a10f84 100644 --- a/js/random_person_node.js +++ b/js/random_person_node.js @@ -122,7 +122,7 @@ app.registerExtension({ } }, - // Re-apply after workflow load — widget values are restored before nodeCreated fires + // 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"); diff --git a/nodes.py b/nodes.py index 5d31fc6..aecf87a 100644 --- a/nodes.py +++ b/nodes.py @@ -163,6 +163,7 @@ _SEX_TIP = "random, male, or female. male/female filter the sex-specific drop _MODE_TIP = "How this attribute is chosen: random (any), allow_list (subset you list), fixed (the dropdown value), off (omit it)." _ALLOW_TIP = "Comma-separated values used when mode=allow_list. Unknown values are added as literal descriptors." _FIXED_TIP = "Exact value used when mode=fixed. (none) falls back to random." +_AGEMODE_TIP = "How to pick the age: random within range, fixed value, or off (no age in the description)." _AGEMIN_TIP = "Minimum age. 0 = no lower bound (defaults to 21). Cannot go below 21." _AGEMAX_TIP = "Maximum age. 0 = no upper bound (defaults to 90). Minimum is always 21." _AGEFIX_TIP = "Exact age when mode=fixed. 0 = ignored. Values below 21 are clamped to 21." @@ -210,7 +211,7 @@ def _best_match(full_list, token): return candidates[0] -def resolve(mode, full_list, allow_str="", fixed_val=""): +def resolve(mode, full_list, allow_str="", fixed_val="", rng=random): """Pick one item from full_list according to mode. Returns None if off.""" if mode == "off": return None @@ -243,13 +244,13 @@ def resolve(mode, full_list, allow_str="", fixed_val=""): pool = deduped + [{"label": s, "description": s} for s in custom_strings] if pool: - return random.choice(pool) + return rng.choice(pool) print(f"[RandomPersonNode] WARNING: allow_list '{allow_str}' matched nothing - using random.") - return random.choice(full_list) + return rng.choice(full_list) -def resolve_age(mode, age_min, age_max, age_fixed_val, all_ages): +def resolve_age(mode, age_min, age_max, age_fixed_val, all_ages, rng=random): if mode == "off": return None if mode == "fixed" and age_fixed_val > 0: @@ -266,7 +267,7 @@ def resolve_age(mode, age_min, age_max, age_fixed_val, all_ages): if not pool: print(f"[RandomPersonNode] WARNING: no ages in {lo}-{hi} - using full list.") pool = all_ages - return random.choice(pool) + return rng.choice(pool) def d(item, key="description"): @@ -389,10 +390,11 @@ def generate_person(seed, randomize, sex, category_args, RETURN_NAMES. """ if randomize: - seed = random.randint(0, 0xFFFFFFFF) - random.seed(seed) + seed = random.randint(0, 0xFFFFFFFFFFFFFFFF) + # Local RNG: never seed the process-wide random module; other nodes share it. + rng = random.Random(seed) - resolved_sex = sex if sex in ("male", "female") else random.choice(["male", "female"]) + resolved_sex = sex if sex in ("male", "female") else rng.choice(["male", "female"]) def load_list(key): sub = resolved_sex if key in SEX_GATED else "shared" @@ -401,8 +403,8 @@ def generate_person(seed, randomize, sex, category_args, p = {} for key, _, _ in CATEGORY_SPECS: mode, allow, fixed = category_args[key] - p[key] = resolve(mode, load_list(key), allow, fixed) - age = resolve_age(age_mode, age_min, age_max, age_fixed, load_shared("age.json")) + p[key] = resolve(mode, load_list(key), allow, fixed, rng=rng) + age = resolve_age(age_mode, age_min, age_max, age_fixed, load_shared("age.json"), rng=rng) description = build_description( resolved_sex, p["nationality"], age, p["skin_texture"], @@ -534,7 +536,7 @@ def make_v1_node(node_id, display_name, keys, include_age, output_names): @classmethod def INPUT_TYPES(cls): required = { - "seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFF, "tooltip": _SEED_TIP}), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF, "tooltip": _SEED_TIP}), "randomize": ("BOOLEAN", {"default": True, "tooltip": _RAND_TIP}), "sex": (SEX_MODES, {"default": "random", "tooltip": _SEX_TIP}), } @@ -546,7 +548,7 @@ def make_v1_node(node_id, display_name, keys, include_age, output_names): "placeholder": placeholder, "tooltip": _ALLOW_TIP}) required[f"{key}_fixed"] = (fixed_labels, {"default": "(none)", "tooltip": _FIXED_TIP}) if include_age: - required["age_mode"] = (AGE_MODES, {"default": "random"}) + required["age_mode"] = (AGE_MODES, {"default": "random", "tooltip": _AGEMODE_TIP}) required["age_min"] = ("INT", {"default": 0, "min": 0, "max": 90, "tooltip": _AGEMIN_TIP}) required["age_max"] = ("INT", {"default": 0, "min": 0, "max": 90, "tooltip": _AGEMAX_TIP}) required["age_fixed"] = ("INT", {"default": 0, "min": 0, "max": 90, "tooltip": _AGEFIX_TIP}) @@ -600,7 +602,7 @@ try: @classmethod def define_schema(cls) -> io.Schema: inputs = [ - io.Int.Input("seed", default=0, min=0, max=0xFFFFFFFF, tooltip=_SEED_TIP), + io.Int.Input("seed", default=0, min=0, max=0xFFFFFFFFFFFFFFFF, tooltip=_SEED_TIP), io.Boolean.Input("randomize", default=True, tooltip=_RAND_TIP), io.Combo.Input("sex", options=SEX_MODES, default="random", tooltip=_SEX_TIP), ] @@ -615,7 +617,7 @@ try: ] if include_age: inputs += [ - io.Combo.Input("age_mode", options=AGE_MODES, default="random"), + io.Combo.Input("age_mode", options=AGE_MODES, default="random", tooltip=_AGEMODE_TIP), io.Int.Input("age_min", default=0, min=0, max=90, tooltip=_AGEMIN_TIP), io.Int.Input("age_max", default=0, min=0, max=90, tooltip=_AGEMAX_TIP), io.Int.Input("age_fixed", default=0, min=0, max=90, tooltip=_AGEFIX_TIP), diff --git a/pyproject.toml b/pyproject.toml index 909d977..bed2fd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui_randomperson" description = "Generate randomised, structured physical person descriptions for image-generation prompts. Sex-aware traits, per-category random/allow_list/fixed/off modes, age ranges, body details, and individual output pins. Full node plus Identity, Face, Hair, Body, and Style segment nodes under the Random Person menu category." -version = "1.3.0" +version = "1.3.1" license = { file = "LICENSE" } requires-python = ">=3.10" dependencies = []