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.
This commit is contained in:
BRADSEC
2026-07-03 10:50:47 +10:00
parent 56549dfdc9
commit 1ce4dc851f
3 changed files with 18 additions and 16 deletions
+1 -1
View File
@@ -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");
+16 -14
View File
@@ -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),
+1 -1
View File
@@ -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 = []