Improve random prompt controls

This commit is contained in:
2026-06-25 14:24:34 +02:00
parent f2c768458f
commit 46ccbd3eac
3 changed files with 223 additions and 34 deletions
+156 -12
View File
@@ -114,7 +114,8 @@ COMMON_INPUT_TOOLTIPS = {
"eyes": "Manual eye description.",
"descriptor_detail": "How detailed this character's descriptor should be. Men usually work better compact.",
"expression_enabled": "Master expression toggle for this generator or character.",
"expression_intensity": "Expression intensity from 0 to 1. Use -1 on slots to inherit the generator setting.",
"expression_intensity": "Expression intensity from 0 to 1. On the direct builder, -1 randomizes per row; on slots, -1 inherits the generator setting.",
"expression_intensity_mode": "For Generation Profile, choose profile_default, random, or fixed value from expression_intensity.",
"softcore_expression_intensity": "Optional expression intensity override for this character in softcore prompts. -1 inherits.",
"hardcore_expression_intensity": "Optional expression intensity override for this character in hardcore prompts. -1 inherits.",
"presence_mode": "Controls whether the character is visible, implied POV, or otherwise present.",
@@ -151,8 +152,8 @@ COMMON_INPUT_TOOLTIPS = {
"missing_behavior": "What to do when the requested switch input is not connected: use fallback, output none, clamp, or wrap.",
"fallback": "Optional value used by SxCP Index Switch when the requested input is missing and missing_behavior is fallback.",
"route_value": "Value routed to output_N when mode is route_output.",
"clothing": "Built-in clothing density for legacy direct generation. Category/profile nodes can override this.",
"poses": "Built-in pose pool for legacy direct generation.",
"clothing": "Built-in clothing density for legacy direct generation. random picks full/minimal from the seeded row.",
"poses": "Built-in pose pool for legacy direct generation. random picks standard/evocative from the seeded row.",
"backside_bias": "Legacy bias toward rear/backside poses where that category supports it.",
"minimal_clothing_ratio": "Legacy weighted ratio override. -1 keeps the category/profile default.",
"standard_pose_ratio": "Legacy weighted ratio override. -1 keeps the category/profile default.",
@@ -161,6 +162,11 @@ COMMON_INPUT_TOOLTIPS = {
"poses_override": "Override the profile pose setting, or leave profile_default.",
"trigger_policy": "Controls whether the profile prepends the trigger token.",
"cast_mode": "Preset cast shape. Custom counts are used when the preset allows them.",
"women_weights": "Comma-separated count weights. First value maps to women_start_count, second to +1, and so on.",
"men_weights": "Comma-separated count weights. First value maps to men_start_count, second to +1, and so on.",
"women_start_count": "Woman count represented by the first women_weights value.",
"men_start_count": "Man count represented by the first men_weights value.",
"empty_behavior": "What to do if the weighted pick selects zero women and zero men.",
"preset": "Category preset for common workflow lanes.",
"camera_mode": "Camera style preset.",
"shot_size": "How much of the body/frame should be visible.",
@@ -203,6 +209,13 @@ NODE_INPUT_TOOLTIPS = {
"SxCPSeedLocker": {
"reroll_axis": "Choose the one axis to change while the rest stays locked. Use pose for sexual pose, scene for location, person for appearance.",
},
"SxCPCastBias": {
"seed": "Fixed cast-bias seed. Use -1 for a fresh cast each queue, or connect Global Seed/Seed Locker through seed_config.",
"seed_config": "Optional seed config. The category seed controls weighted cast selection.",
"women_weights": "Example with women_start_count=1: 0.6,0.25,0.1 means 60% one woman, 25% two women, 10% three women.",
"men_weights": "Example with men_start_count=0: 0.5,0.35,0.1 means 50% no man, 35% one man, 10% two men.",
"empty_behavior": "Prevents accidental empty casts when both weighted pools pick zero.",
},
"SxCPSDXLBucketSize": {
"orientation": "Bucket orientation filter. any uses the full table; portrait/square/landscape restrict random selection.",
"seed": "Fixed bucket seed. Use -1 for a fresh random bucket each queue, or connect Global Seed for reproducible sizes.",
@@ -597,13 +610,13 @@ class SxCPPromptBuilder:
"row_number": ("INT", {"default": 1, "min": 1, "max": 1000000, "step": 1}),
"start_index": ("INT", {"default": 41, "min": 1, "max": 1000000, "step": 1}),
"seed": ("INT", {"default": 20260614, "min": 0, "max": 0xFFFFFFFF, "step": 1}),
"clothing": (["full", "minimal"], {"default": "full"}),
"clothing": (["random", "full", "minimal"], {"default": "random"}),
"ethnicity": (ethnicity_choices(), {"default": "any"}),
"poses": (["standard", "evocative"], {"default": "standard"}),
"poses": (["random", "standard", "evocative"], {"default": "random"}),
"expression_enabled": ("BOOLEAN", {"default": True}),
"expression_intensity": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"backside_bias": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"figure": (["curvy", "balanced", "bombshell"], {"default": "curvy"}),
"figure": (["random", "curvy", "balanced", "bombshell"], {"default": "random"}),
"women_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
"men_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
"minimal_clothing_ratio": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
@@ -1154,15 +1167,137 @@ class SxCPCastControl:
return config, parsed["women_count"], parsed["men_count"], summary
class SxCPCastBias:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF, "step": 1}),
"row_number": ("INT", {"default": 1, "min": 1, "max": 1000000, "step": 1}),
"women_weights": ("STRING", {"default": "0.60,0.25,0.10,0.05"}),
"women_start_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
"men_weights": ("STRING", {"default": "0.45,0.40,0.10,0.05"}),
"men_start_count": ("INT", {"default": 0, "min": 0, "max": 12, "step": 1}),
"empty_behavior": (["force_one_woman", "force_one_man", "allow_empty"], {"default": "force_one_woman"}),
},
"optional": {
"seed_config": (SXCP_SEED_CONFIG,),
},
}
RETURN_TYPES = (SXCP_CAST_CONFIG, "INT", "INT", "STRING")
RETURN_NAMES = ("cast_config", "women_count", "men_count", "cast_summary")
FUNCTION = "build"
CATEGORY = "prompt_builder"
@staticmethod
def _configured_cast_seed(seed_config):
if not seed_config:
return None
if isinstance(seed_config, dict):
raw = seed_config
else:
try:
raw = json.loads(str(seed_config))
except (TypeError, ValueError, json.JSONDecodeError):
return None
if not isinstance(raw, dict):
return None
for key in ("category_seed", "content_seed", "role_seed", "seed", "global_seed"):
try:
value = int(raw.get(key))
except (TypeError, ValueError):
continue
if value >= 0:
return value
return None
@staticmethod
def _weight_pairs(weights_text, start_count):
pairs = []
start = max(0, min(12, int(start_count)))
parts = str(weights_text or "").replace("\n", ",").split(",")
for offset, raw in enumerate(parts):
count = start + offset
if count > 12:
break
try:
weight = float(raw.strip())
except (TypeError, ValueError):
continue
if weight > 0:
pairs.append((count, weight))
return pairs or [(start, 1.0)]
@staticmethod
def _weighted_count(rng, pairs):
total = sum(weight for _count, weight in pairs)
point = rng.random() * total
upto = 0.0
for count, weight in pairs:
upto += weight
if point <= upto:
return int(count)
return int(pairs[-1][0])
@classmethod
def IS_CHANGED(cls, *args, **kwargs):
seed_value = kwargs.get("seed")
if seed_value is None and args:
seed_value = args[0]
seed_config = kwargs.get("seed_config", "")
if not seed_config and len(args) > 7:
seed_config = args[7]
try:
seed = int(seed_value)
except (TypeError, ValueError):
seed = -1
if seed < 0 and cls._configured_cast_seed(seed_config) is None:
return random.random()
return tuple(args), tuple(sorted(kwargs.items()))
def build(
self,
seed,
row_number,
women_weights,
women_start_count,
men_weights,
men_start_count,
empty_behavior,
seed_config="",
):
configured_seed = self._configured_cast_seed(seed_config)
if configured_seed is None and int(seed) < 0:
rng = random.Random(random.getrandbits(64))
else:
cast_seed = configured_seed if configured_seed is not None else int(seed)
rng = random.Random(f"sxcp_cast_bias:{cast_seed}:{int(row_number)}")
women_pairs = self._weight_pairs(women_weights, women_start_count)
men_pairs = self._weight_pairs(men_weights, men_start_count)
women_count = self._weighted_count(rng, women_pairs)
men_count = self._weighted_count(rng, men_pairs)
if women_count + men_count == 0:
if empty_behavior == "force_one_man":
men_count = 1
elif empty_behavior != "allow_empty":
women_count = 1
config = build_cast_config_json(cast_mode="custom_counts", women_count=women_count, men_count=men_count)
parsed = json.loads(config)
summary = f"weighted cast: {parsed['women_count']} women, {parsed['men_count']} men"
return config, parsed["women_count"], parsed["men_count"], summary
class SxCPGenerationProfile:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"profile": (generation_profile_choices(), {"default": "balanced"}),
"clothing_override": (["profile_default", "full", "minimal"], {"default": "profile_default"}),
"poses_override": (["profile_default", "standard", "evocative"], {"default": "profile_default"}),
"clothing_override": (["profile_default", "random", "full", "minimal"], {"default": "profile_default"}),
"poses_override": (["profile_default", "random", "standard", "evocative"], {"default": "profile_default"}),
"expression_enabled": ("BOOLEAN", {"default": True}),
"expression_intensity_mode": (["profile_default", "random", "fixed"], {"default": "profile_default"}),
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"backside_bias": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"minimal_clothing_ratio": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
@@ -1182,6 +1317,7 @@ class SxCPGenerationProfile:
clothing_override,
poses_override,
expression_enabled,
expression_intensity_mode,
expression_intensity,
backside_bias,
minimal_clothing_ratio,
@@ -1193,6 +1329,7 @@ class SxCPGenerationProfile:
clothing_override=clothing_override,
poses_override=poses_override,
expression_enabled=expression_enabled,
expression_intensity_mode=expression_intensity_mode,
expression_intensity=expression_intensity,
backside_bias=backside_bias,
minimal_clothing_ratio=minimal_clothing_ratio,
@@ -1200,7 +1337,12 @@ class SxCPGenerationProfile:
trigger_policy=trigger_policy,
)
parsed = json.loads(config)
expression_summary = "expression disabled" if not parsed.get("expression_enabled", True) else f"expression {parsed['expression_intensity']}"
if not parsed.get("expression_enabled", True):
expression_summary = "expression disabled"
elif float(parsed.get("expression_intensity", 0.5)) < 0:
expression_summary = "expression random"
else:
expression_summary = f"expression {parsed['expression_intensity']}"
summary = f"{parsed['profile']}: {parsed['clothing']}, {parsed['poses']}, {expression_summary}"
return config, summary
@@ -1220,7 +1362,7 @@ class SxCPAdvancedFilters:
"include_indigenous": ("BOOLEAN", {"default": True}),
"include_mixed": ("BOOLEAN", {"default": True}),
"include_plus_size": ("BOOLEAN", {"default": True}),
"figure": (["curvy", "balanced", "bombshell"], {"default": "curvy"}),
"figure": (["random", "curvy", "balanced", "bombshell"], {"default": "random"}),
}
}
@@ -2499,7 +2641,7 @@ class SxCPInstaOFPromptPair:
"start_index": ("INT", {"default": 41, "min": 1, "max": 1000000, "step": 1}),
"seed": ("INT", {"default": 20260614, "min": 0, "max": 0xFFFFFFFF, "step": 1}),
"ethnicity": (ethnicity_choices(), {"default": "any"}),
"figure": (["curvy", "balanced", "bombshell"], {"default": "curvy"}),
"figure": (["random", "curvy", "balanced", "bombshell"], {"default": "random"}),
"trigger": ("STRING", {"default": "sxcpinup_coloredpencil"}),
"prepend_trigger_to_prompt": ("BOOLEAN", {"default": True}),
},
@@ -2602,6 +2744,7 @@ NODE_CLASS_MAPPINGS = {
"SxCPQwenCameraTranslator": SxCPQwenCameraTranslator,
"SxCPCategoryPreset": SxCPCategoryPreset,
"SxCPCastControl": SxCPCastControl,
"SxCPCastBias": SxCPCastBias,
"SxCPGenerationProfile": SxCPGenerationProfile,
"SxCPEthnicityList": SxCPEthnicityList,
"SxCPHairLength": SxCPHairLength,
@@ -2643,6 +2786,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"SxCPQwenCameraTranslator": "SxCP Qwen Camera Translator",
"SxCPCategoryPreset": "SxCP Category Preset",
"SxCPCastControl": "SxCP Cast Control",
"SxCPCastBias": "SxCP Cast Bias",
"SxCPGenerationProfile": "SxCP Generation Profile",
"SxCPEthnicityList": "SxCP Ethnicity List",
"SxCPHairLength": "SxCP Hair Length",