From cad2f4b4e492f49d0146d37fa73153d8a3fb52aa Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Wed, 24 Jun 2026 15:30:48 +0200 Subject: [PATCH] Add gender-specific character slot nodes --- README.md | 31 +++++++---- __init__.py | 132 ++++++++++++++++++++++++++++++++++++++++++++++ prompt_builder.py | 44 ++++++++++++++++ 3 files changed, 196 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 668d0bc..0132b33 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ The node is registered as: - `prompt_builder / SxCP Generation Profile` - `prompt_builder / SxCP Advanced Filters` - `prompt_builder / SxCP Prompt Builder From Configs` +- `prompt_builder / SxCP Woman Slot` +- `prompt_builder / SxCP Man Slot` - `prompt_builder / SxCP Character Slot` - `prompt_builder / SxCP Character Profile Save` - `prompt_builder / SxCP Character Profile Load` @@ -53,7 +55,7 @@ The practical compact workflow is: `Category Preset` + `Cast Control` + `Generation Profile` + optional `Advanced Filters`, `Seed Locker` or `Seed Control`, `Camera Control`, -`Character Slot`, and `Character Profile` +`Woman Slot` / `Man Slot`, and `Character Profile` into `Prompt Builder From Configs`. An importable default workflow is included at @@ -70,11 +72,18 @@ as one long chain: ## Character Profiles -`SxCP Character Slot` is the scalable per-participant control node. Each slot -defines one woman or man with optional overrides for age, ethnicity, figure, -body/body phrase, skin, hair, and eyes. Leave any field on `random` or blank to -let the generator fill that part from the normal pools; set exact values only -where you want control. +`SxCP Woman Slot` and `SxCP Man Slot` are the scalable per-participant control +nodes. `Cast Control` still decides how many women and men are generated; slot +nodes decide who those people are. Each slot defines one participant with +optional overrides for age, ethnicity, body/body phrase, skin, hair, and eyes. +Leave any field on `random` or blank to let the generator fill that part from +the normal pools; set exact values only where you want control. + +Use `Woman Slot` for women because it exposes woman-focused body choices and a +`figure_bias` selector. Use `Man Slot` for men because it exposes man-focused +body choices and omits figure bias. The older generic `SxCP Character Slot` +remains available for compatibility and manual mixed use, but the gendered +slots are the cleaner default. Slots are chainable through the `character_cast` input/output. In automatic label mode, the slot closest to the final generator becomes `A` for its gender, @@ -225,11 +234,11 @@ and location. The generated positive prompts are still standalone: each output lists the relevant cast descriptors directly and does not depend on the image model carrying context from another prompt. -For per-character control, chain `SxCP Character Slot` nodes into the pair -node's `character_cast` input. The nearest woman slot controls the shared -primary creator (`Woman A`) in both softcore and hardcore outputs; additional -woman/man slots fill partner descriptors before random fallback descriptors are -used. +For per-character control, chain `SxCP Woman Slot` and `SxCP Man Slot` nodes +into the pair node's `character_cast` input. The nearest woman slot controls +the shared primary creator (`Woman A`) in both softcore and hardcore outputs; +additional woman/man slots fill partner descriptors before random fallback +descriptors are used. It outputs: diff --git a/__init__.py b/__init__.py index 6168987..06ff2de 100644 --- a/__init__.py +++ b/__init__.py @@ -34,7 +34,9 @@ try: character_ethnicity_choices, character_figure_choices, character_label_choices, + character_man_body_choices, character_profile_choices, + character_woman_body_choices, ethnicity_choices, generation_profile_choices, load_character_profile_json, @@ -74,7 +76,9 @@ except ImportError: character_ethnicity_choices, character_figure_choices, character_label_choices, + character_man_body_choices, character_profile_choices, + character_woman_body_choices, ethnicity_choices, generation_profile_choices, load_character_profile_json, @@ -606,6 +610,130 @@ class SxCPCharacterSlot: return result["character_cast"], result["character_slot"], result["summary"], result["status"] +class SxCPWomanSlot: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "enabled": ("BOOLEAN", {"default": True}), + "label": (character_label_choices(), {"default": "auto_chain"}), + "age": (character_age_choices(), {"default": "random"}), + "manual_age": ("STRING", {"default": ""}), + "ethnicity": (character_ethnicity_choices(), {"default": "random"}), + "figure_bias": (character_figure_choices(), {"default": "random"}), + "body": (character_woman_body_choices(), {"default": "random"}), + "manual_body": ("STRING", {"default": ""}), + "body_phrase": ("STRING", {"default": ""}), + "skin": ("STRING", {"default": ""}), + "hair": ("STRING", {"default": ""}), + "eyes": ("STRING", {"default": ""}), + }, + "optional": { + "character_cast": ("STRING", {"default": "", "multiline": True}), + }, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING") + RETURN_NAMES = ("character_cast", "character_slot", "summary", "status") + FUNCTION = "build" + CATEGORY = "prompt_builder" + + def build( + self, + enabled, + label, + age, + manual_age, + ethnicity, + figure_bias, + body, + manual_body, + body_phrase, + skin, + hair, + eyes, + character_cast="", + ): + result = build_character_slot_json( + subject_type="woman", + label=label, + age=age, + manual_age=manual_age, + ethnicity=ethnicity, + figure=figure_bias, + body=body, + manual_body=manual_body, + body_phrase=body_phrase, + skin=skin, + hair=hair, + eyes=eyes, + enabled=enabled, + character_cast=character_cast or "", + ) + return result["character_cast"], result["character_slot"], result["summary"], result["status"] + + +class SxCPManSlot: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "enabled": ("BOOLEAN", {"default": True}), + "label": (character_label_choices(), {"default": "auto_chain"}), + "age": (character_age_choices(), {"default": "random"}), + "manual_age": ("STRING", {"default": ""}), + "ethnicity": (character_ethnicity_choices(), {"default": "random"}), + "body": (character_man_body_choices(), {"default": "random"}), + "manual_body": ("STRING", {"default": ""}), + "body_phrase": ("STRING", {"default": ""}), + "skin": ("STRING", {"default": ""}), + "hair": ("STRING", {"default": ""}), + "eyes": ("STRING", {"default": ""}), + }, + "optional": { + "character_cast": ("STRING", {"default": "", "multiline": True}), + }, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING") + RETURN_NAMES = ("character_cast", "character_slot", "summary", "status") + FUNCTION = "build" + CATEGORY = "prompt_builder" + + def build( + self, + enabled, + label, + age, + manual_age, + ethnicity, + body, + manual_body, + body_phrase, + skin, + hair, + eyes, + character_cast="", + ): + result = build_character_slot_json( + subject_type="man", + label=label, + age=age, + manual_age=manual_age, + ethnicity=ethnicity, + figure="random", + body=body, + manual_body=manual_body, + body_phrase=body_phrase, + skin=skin, + hair=hair, + eyes=eyes, + enabled=enabled, + character_cast=character_cast or "", + ) + return result["character_cast"], result["character_slot"], result["summary"], result["status"] + + class SxCPCharacterProfileSave: @classmethod def INPUT_TYPES(cls): @@ -985,6 +1113,8 @@ NODE_CLASS_MAPPINGS = { "SxCPGenerationProfile": SxCPGenerationProfile, "SxCPAdvancedFilters": SxCPAdvancedFilters, "SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs, + "SxCPWomanSlot": SxCPWomanSlot, + "SxCPManSlot": SxCPManSlot, "SxCPCharacterSlot": SxCPCharacterSlot, "SxCPCharacterProfileSave": SxCPCharacterProfileSave, "SxCPCharacterProfileLoad": SxCPCharacterProfileLoad, @@ -1004,6 +1134,8 @@ NODE_DISPLAY_NAME_MAPPINGS = { "SxCPGenerationProfile": "SxCP Generation Profile", "SxCPAdvancedFilters": "SxCP Advanced Filters", "SxCPPromptBuilderFromConfigs": "SxCP Prompt Builder From Configs", + "SxCPWomanSlot": "SxCP Woman Slot", + "SxCPManSlot": "SxCP Man Slot", "SxCPCharacterSlot": "SxCP Character Slot", "SxCPCharacterProfileSave": "SxCP Character Profile Save", "SxCPCharacterProfileLoad": "SxCP Character Profile Load", diff --git a/prompt_builder.py b/prompt_builder.py index a676f89..1130a9e 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -137,6 +137,42 @@ CHARACTER_BODY_CHOICES = [ "broad", "muscular", ] +CHARACTER_WOMAN_BODY_CHOICES = [ + "random", + "manual", + "slim", + "petite adult", + "toned", + "athletic", + "average", + "curvy", + "soft curvy", + "curvy athletic", + "hourglass", + "slim busty", + "busty", + "busty curvy", + "voluptuous", + "plus-size", + "heavyset", + "fat", +] +CHARACTER_MAN_BODY_CHOICES = [ + "random", + "manual", + "slim", + "lean", + "lean athletic", + "toned", + "average", + "athletic", + "muscular", + "broad", + "broad-shouldered", + "stocky", + "heavyset", + "fat", +] CHARACTER_RANDOM_TOKENS = {"", "random", "auto", "global", "from_global", "default"} CAMERA_DETAIL_CHOICES = ["off", "compact", "full"] @@ -1296,6 +1332,14 @@ def character_body_choices() -> list[str]: return list(CHARACTER_BODY_CHOICES) +def character_woman_body_choices() -> list[str]: + return list(CHARACTER_WOMAN_BODY_CHOICES) + + +def character_man_body_choices() -> list[str]: + return list(CHARACTER_MAN_BODY_CHOICES) + + def character_ethnicity_choices() -> list[str]: return ["random"] + list(ETHNICITY_FILTER_CHOICES)