diff --git a/README.md b/README.md index 6131682..008a6a2 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ The node is registered as: - `prompt_builder / SxCP Accumulator` - `prompt_builder / SxCP Category Preset` - `prompt_builder / SxCP Cast Control` +- `prompt_builder / SxCP Cast Bias` - `prompt_builder / SxCP Generation Profile` - `prompt_builder / SxCP Ethnicity List` - `prompt_builder / SxCP Hair Length` @@ -55,9 +56,15 @@ node. For cleaner workflows, use the split nodes: women casual, men casual, couple casual, provocative erotic, or hardcore pose. - `SxCP Cast Control` outputs `cast_config` plus raw `women_count` and `men_count`, so couple/two-women/two-men/group setups can be reused. +- `SxCP Cast Bias` outputs the same cast sockets, but chooses counts from + weighted lists. With `women_start_count=1`, `women_weights=0.6,0.2` means + 60% one woman and 20% two women; with `men_start_count=0`, + `men_weights=0.5,0.3` means 50% no man and 30% one man. - `SxCP Generation Profile` outputs `generation_profile` for common behavior presets such as casual-clean, evocative-softcore, hardcore-intense, - Krea2-friendly, or Flux-original. + Krea2-friendly, or Flux-original. Its clothing and pose overrides can be + `random`, and `expression_intensity_mode=random` makes expression strength + vary by seeded row. - `SxCP Ethnicity List` outputs a reusable ethnicity filter from checkboxes. It includes broad groups and narrower European/Mediterranean groups such as `french_european`, `germanic_european`, `nordic_european`, @@ -539,22 +546,22 @@ Options: The node keeps the original generator controls: - `category`: `auto_weighted`, `woman`, `man`, `couple`, `group_or_layout`, or a custom JSON category. -- `clothing`: `full` or `minimal`. -- `minimal_clothing_ratio`: `-1` disables mixing; `0.0` to `1.0` mixes minimal/full clothing. +- `clothing`: `random`, `full`, or `minimal`. +- `minimal_clothing_ratio`: `-1` disables ratio mixing; `0.0` to `1.0` mixes minimal/full clothing when `clothing` is fixed. - `ethnicity`: `any`, `european`, `mediterranean_mena`, `latina`, `east_asian`, `southeast_asian`, `south_asian`, `black_african`, `indigenous`, `mixed`, `asian`, or `white_asian`. Combined filter strings such as `latina+south_asian` are also accepted in config JSON. -- `poses`: `standard` or `evocative`. +- `poses`: `random`, `standard`, or `evocative`. - `expression_enabled`: disable facial-expression text entirely for this row. -- `expression_intensity`: `0.0` favors mild, neutral, controlled expressions; +- `expression_intensity`: `-1` randomizes per row; `0.0` favors mild, neutral, controlled expressions; `0.5` favors balanced category expressions; `1.0` strongly favors the most intense expressions available in the selected category. This affects custom JSON categories such as `Provocative erotic clothes` and `Hardcore sexual poses`. -- `standard_pose_ratio`: `-1` disables mixing; `0.0` to `1.0` mixes standard/evocative poses. +- `standard_pose_ratio`: `-1` disables ratio mixing; `0.0` to `1.0` mixes standard/evocative poses when `poses` is fixed. - `backside_bias`: `0.0` to `1.0`, applies to evocative single-subject poses. -- `figure`: `curvy`, `balanced`, `bombshell`. +- `figure`: `random`, `curvy`, `balanced`, `bombshell`. - In split workflows, use `SxCP Advanced Filters` checkboxes instead of negative toggles. Black/African and plus-size are positive include choices there. - Optional `camera_config`: connect `SxCP Camera Control` or @@ -813,7 +820,7 @@ Seed values in `auto` mode: Axes: -- `category_seed`: custom category selection when `custom_random` is used. +- `category_seed`: custom category selection when `custom_random` is used; also drives `SxCP Cast Bias` when its `seed_config` input is connected. - `subcategory_seed`: random subcategory selection. - `content_seed`: generated item content, such as outfit wording. - `person_seed`: appearance/person selection. diff --git a/__init__.py b/__init__.py index 8eaa35d..1dd33c8 100644 --- a/__init__.py +++ b/__init__.py @@ -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", diff --git a/prompt_builder.py b/prompt_builder.py index a950b4d..5036fdc 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -1372,6 +1372,7 @@ def build_generation_profile_json( profile: str = "balanced", clothing_override: str = "profile_default", poses_override: str = "profile_default", + expression_intensity_mode: str = "profile_default", expression_intensity: float = -1.0, backside_bias: float = -1.0, minimal_clothing_ratio: float = -1.0, @@ -1381,12 +1382,14 @@ def build_generation_profile_json( ) -> str: profile = profile if profile in GENERATION_PROFILE_PRESETS else "balanced" config = dict(GENERATION_PROFILE_PRESETS[profile]) - if clothing_override in ("full", "minimal"): + if clothing_override in ("full", "minimal", "random"): config["clothing"] = clothing_override - if poses_override in ("standard", "evocative"): + if poses_override in ("standard", "evocative", "random"): config["poses"] = poses_override config["expression_enabled"] = not _is_false(expression_enabled) - if float(expression_intensity) >= 0: + if expression_intensity_mode == "random": + config["expression_intensity"] = -1.0 + elif expression_intensity_mode == "fixed" and float(expression_intensity) >= 0: config["expression_intensity"] = _clamped_float(expression_intensity, config["expression_intensity"]) if float(backside_bias) >= 0: config["backside_bias"] = _clamped_float(backside_bias, config["backside_bias"]) @@ -1417,10 +1420,14 @@ def _parse_generation_profile(profile_config: str | dict[str, Any] | None) -> di profile = str(raw.get("profile") or "balanced") parsed = dict(GENERATION_PROFILE_PRESETS.get(profile, GENERATION_PROFILE_PRESETS["balanced"])) parsed.update(raw) - parsed["clothing"] = parsed["clothing"] if parsed.get("clothing") in ("full", "minimal") else "full" - parsed["poses"] = parsed["poses"] if parsed.get("poses") in ("standard", "evocative") else "standard" + parsed["clothing"] = parsed["clothing"] if parsed.get("clothing") in ("full", "minimal", "random") else "full" + parsed["poses"] = parsed["poses"] if parsed.get("poses") in ("standard", "evocative", "random") else "standard" parsed["expression_enabled"] = not _is_false(parsed.get("expression_enabled", True)) - parsed["expression_intensity"] = _clamped_float(parsed.get("expression_intensity"), 0.5) + try: + raw_expression_intensity = float(parsed.get("expression_intensity")) + except (TypeError, ValueError): + raw_expression_intensity = 0.5 + parsed["expression_intensity"] = -1.0 if raw_expression_intensity < 0 else _clamped_float(raw_expression_intensity, 0.5) parsed["backside_bias"] = _clamped_float(parsed.get("backside_bias"), 0.0) parsed["minimal_clothing_ratio"] = _clamped_float(parsed.get("minimal_clothing_ratio"), -1.0, -1.0, 1.0) parsed["standard_pose_ratio"] = _clamped_float(parsed.get("standard_pose_ratio"), -1.0, -1.0, 1.0) @@ -1469,7 +1476,7 @@ def build_filter_config_json( { "ethnicity": ethnicity, "ethnicity_includes": selected_ethnicities, - "figure": figure if figure in ("curvy", "balanced", "bombshell") else "curvy", + "figure": figure if figure in ("curvy", "balanced", "bombshell", "random") else "curvy", "include_plus_size": bool(include_plus_size), "include_black_african": bool(include_black_african), "no_plus_women": not bool(include_plus_size) or bool(no_plus_women), @@ -1620,7 +1627,7 @@ def _parse_filter_config(filter_config: str | dict[str, Any] | None) -> dict[str raise ValueError("filter_config must be a JSON object") parsed = {**defaults, **raw} parsed["ethnicity"] = normalize_ethnicity_filter(parsed.get("ethnicity"), "any") - parsed["figure"] = parsed["figure"] if parsed.get("figure") in ("curvy", "balanced", "bombshell") else "curvy" + parsed["figure"] = parsed["figure"] if parsed.get("figure") in ("curvy", "balanced", "bombshell", "random") else "curvy" parsed["include_plus_size"] = bool(parsed.get("include_plus_size")) parsed["include_black_african"] = bool(parsed.get("include_black_african")) parsed["no_plus_women"] = bool(parsed.get("no_plus_women")) @@ -2765,17 +2772,37 @@ def _row_seed(seed: int, row_number: int, salt: int = 0) -> int: def _pick_clothing_mode(rng: random.Random, clothing: str, minimal_ratio: float | None) -> str: + if clothing == "random": + return "minimal" if rng.random() < 0.5 else "full" if minimal_ratio is None: return clothing return "minimal" if rng.random() < minimal_ratio else "full" def _pick_pose_mode(rng: random.Random, poses: str, standard_ratio: float | None) -> str: + if poses == "random": + return "standard" if rng.random() < 0.5 else "evocative" if standard_ratio is None: return poses return "standard" if rng.random() < standard_ratio else "evocative" +def _pick_figure_bias(rng: random.Random, figure: str) -> str: + if figure in ("curvy", "balanced", "bombshell"): + return figure + return g.choose(rng, ["curvy", "balanced", "bombshell"]) + + +def _pick_expression_intensity(rng: random.Random, expression_intensity: Any) -> tuple[float, str]: + try: + value = float(expression_intensity) + except (TypeError, ValueError): + return 0.5, "default" + if value < 0: + return round(rng.random(), 2), "random" + return _clamped_float(value, 0.5), "input" + + def _build_auto_weighted_row( row_number: int, start_index: int, @@ -5424,6 +5451,7 @@ def _build_custom_row( seed_config: dict[str, int], expression_enabled: bool, expression_intensity: float, + expression_intensity_source: str = "input", character_profile: str | dict[str, Any] | None = None, character_cast: str | dict[str, Any] | list[Any] | None = None, expression_phase: str = "", @@ -5512,7 +5540,7 @@ def _build_custom_row( role_graph = _pov_role_graph_prompt(source_role_graph, pov_character_labels) cast_descriptors: list[str] = [] cast_descriptor_text = "" - expression_intensity_source = "input" + expression_intensity_source = expression_intensity_source or "input" expression_disabled = not bool(expression_enabled) if expression_disabled: expression_intensity_source = "disabled" @@ -5774,15 +5802,24 @@ def build_prompt( row_number = max(1, int(row_number)) start_index = max(1, int(start_index)) seed = int(seed) - clothing = clothing if clothing in ("full", "minimal") else "full" ethnicity = normalize_ethnicity_filter(ethnicity, "any") - poses = poses if poses in ("standard", "evocative") else "standard" - figure = figure if figure in ("curvy", "balanced", "bombshell") else "curvy" expression_enabled = not _is_false(expression_enabled) minimal_ratio = _ratio_or_none(minimal_clothing_ratio) pose_ratio = _ratio_or_none(standard_pose_ratio) - expression_intensity = _clamped_float(expression_intensity, 0.5) parsed_seed_config = _parse_seed_config(seed_config) + content_rng = _axis_rng(parsed_seed_config, "content", seed, row_number) + pose_axis_rng = _axis_rng(parsed_seed_config, "pose", seed, row_number) + person_rng = _axis_rng(parsed_seed_config, "person", seed, row_number) + expression_rng = _axis_rng(parsed_seed_config, "expression", seed, row_number) + clothing = clothing if clothing in ("full", "minimal", "random") else "full" + poses = poses if poses in ("standard", "evocative", "random") else "standard" + figure = figure if figure in ("curvy", "balanced", "bombshell", "random") else "curvy" + clothing = _pick_clothing_mode(content_rng, clothing, minimal_ratio) + poses = _pick_pose_mode(pose_axis_rng, poses, pose_ratio) + figure = _pick_figure_bias(person_rng, figure) + minimal_ratio = None + pose_ratio = None + expression_intensity, expression_intensity_source = _pick_expression_intensity(expression_rng, expression_intensity) exact_custom_subcategory = bool(subcategory and subcategory != RANDOM_SUBCATEGORY and " / " in subcategory) @@ -5834,6 +5871,7 @@ def build_prompt( parsed_seed_config, expression_enabled, expression_intensity, + expression_intensity_source, character_profile, character_cast, expression_phase, @@ -5850,7 +5888,7 @@ def build_prompt( row["negative_prompt"] = _combined_negative(row.get("negative_prompt", g.NEGATIVE_PROMPT), extra_negative) row["trigger"] = active_trigger row.setdefault("expression_intensity", expression_intensity) - row.setdefault("expression_intensity_source", "input") + row.setdefault("expression_intensity_source", expression_intensity_source) return row