Add chainable character characteristics nodes

This commit is contained in:
2026-06-25 00:50:28 +02:00
parent 13a1a7c962
commit 91d5049774
2 changed files with 495 additions and 13 deletions
+218
View File
@@ -11,6 +11,7 @@ except Exception:
PromptServer = None
SXCP_HAIR_CONFIG = "SXCP_HAIR_CONFIG"
SXCP_CHARACTERISTICS = "SXCP_CHARACTERISTICS"
SXCP_CHARACTER_MANUAL = "SXCP_CHARACTER_MANUAL"
SXCP_ETHNICITY_LIST = "SXCP_ETHNICITY_LIST"
SXCP_FILTER_CONFIG = "SXCP_FILTER_CONFIG"
@@ -35,6 +36,7 @@ try:
build_character_slot_json,
build_character_manual_config_json,
build_character_profile_json,
build_characteristics_config_json,
build_ethnicity_list_json,
build_filter_config_json,
build_generation_profile_json,
@@ -63,6 +65,7 @@ try:
character_body_choices,
character_descriptor_detail_choices,
character_ethnicity_choices,
character_eye_color_choices,
character_figure_choices,
character_hair_color_choices,
character_hair_length_choices,
@@ -71,6 +74,10 @@ try:
character_man_body_choices,
character_presence_choices,
character_profile_choices,
character_hardcore_clothing_state_choices,
character_hardcore_clothing_values,
character_softcore_outfit_source_choices,
character_softcore_outfit_values,
character_woman_body_choices,
ethnicity_choices,
generation_profile_choices,
@@ -93,6 +100,7 @@ except ImportError:
build_character_slot_json,
build_character_manual_config_json,
build_character_profile_json,
build_characteristics_config_json,
build_ethnicity_list_json,
build_filter_config_json,
build_generation_profile_json,
@@ -121,6 +129,7 @@ except ImportError:
character_body_choices,
character_descriptor_detail_choices,
character_ethnicity_choices,
character_eye_color_choices,
character_figure_choices,
character_hair_color_choices,
character_hair_length_choices,
@@ -129,6 +138,10 @@ except ImportError:
character_man_body_choices,
character_presence_choices,
character_profile_choices,
character_hardcore_clothing_state_choices,
character_hardcore_clothing_values,
character_softcore_outfit_source_choices,
character_softcore_outfit_values,
character_woman_body_choices,
ethnicity_choices,
generation_profile_choices,
@@ -880,6 +893,190 @@ class SxCPHairStyle(_SxCPHairAxisNode):
AXIS = "style"
def _choice_input_key(prefix, choice):
key = "".join(char if char.isalnum() else "_" for char in str(choice).lower()).strip("_")
while "__" in key:
key = key.replace("__", "_")
return f"{prefix}_{key}"
class SxCPCharacterAgeRange:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"combine_mode": (["replace_axis", "add_to_axis"], {"default": "replace_axis"}),
"min_age": ("INT", {"default": 21, "min": 21, "max": 85, "step": 1}),
"max_age": ("INT", {"default": 35, "min": 21, "max": 85, "step": 1}),
},
"optional": {
"characteristics": (SXCP_CHARACTERISTICS,),
},
}
RETURN_TYPES = (SXCP_CHARACTERISTICS, "STRING")
RETURN_NAMES = ("characteristics", "summary")
FUNCTION = "build"
CATEGORY = "prompt_builder"
def build(self, combine_mode, min_age, max_age, characteristics=""):
start = max(21, min(85, int(min_age)))
end = max(21, min(85, int(max_age)))
if end < start:
start, end = end, start
ages = [f"{age}-year-old adult" for age in range(start, end + 1)]
config = build_characteristics_config_json(
characteristics=characteristics or "",
axis="ages",
selected_values=ages,
combine_mode=combine_mode,
)
return config, json.loads(config).get("summary", "")
class _SxCPBodyPoolNode:
SUBJECT = "character"
@classmethod
def _choices(cls):
if cls.SUBJECT == "woman":
return [choice for choice in character_woman_body_choices() if choice not in ("random", "manual")]
if cls.SUBJECT == "man":
return [choice for choice in character_man_body_choices() if choice not in ("random", "manual")]
return [choice for choice in character_body_choices() if choice not in ("random", "manual")]
@classmethod
def INPUT_TYPES(cls):
required = {
"combine_mode": (["replace_axis", "add_to_axis"], {"default": "replace_axis"}),
}
for choice in cls._choices():
required[_choice_input_key("include", choice)] = ("BOOLEAN", {"default": False})
return {
"required": required,
"optional": {
"characteristics": (SXCP_CHARACTERISTICS,),
},
}
RETURN_TYPES = (SXCP_CHARACTERISTICS, "STRING")
RETURN_NAMES = ("characteristics", "summary")
FUNCTION = "build"
CATEGORY = "prompt_builder"
def build(self, combine_mode="replace_axis", characteristics="", **kwargs):
selected = [
choice
for choice in self._choices()
if bool(kwargs.get(_choice_input_key("include", choice), False))
]
config = build_characteristics_config_json(
characteristics=characteristics or "",
axis="bodies",
selected_values=selected,
combine_mode=combine_mode,
)
return config, json.loads(config).get("summary", "")
class SxCPCharacterBodyPool(_SxCPBodyPoolNode):
SUBJECT = "character"
class SxCPWomanBodyPool(_SxCPBodyPoolNode):
SUBJECT = "woman"
class SxCPManBodyPool(_SxCPBodyPoolNode):
SUBJECT = "man"
class SxCPEyeColorPool:
@classmethod
def INPUT_TYPES(cls):
required = {
"combine_mode": (["replace_axis", "add_to_axis"], {"default": "replace_axis"}),
}
for choice in character_eye_color_choices():
if choice != "random":
required[_choice_input_key("include", choice)] = ("BOOLEAN", {"default": False})
return {
"required": required,
"optional": {
"characteristics": (SXCP_CHARACTERISTICS,),
},
}
RETURN_TYPES = (SXCP_CHARACTERISTICS, "STRING")
RETURN_NAMES = ("characteristics", "summary")
FUNCTION = "build"
CATEGORY = "prompt_builder"
def build(self, combine_mode="replace_axis", characteristics="", **kwargs):
selected = [
choice
for choice in character_eye_color_choices()
if choice != "random" and bool(kwargs.get(_choice_input_key("include", choice), False))
]
config = build_characteristics_config_json(
characteristics=characteristics or "",
axis="eyes",
selected_values=selected,
combine_mode=combine_mode,
)
return config, json.loads(config).get("summary", "")
class SxCPCharacterClothing:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"combine_mode": (["replace_axis", "add_to_axis"], {"default": "replace_axis"}),
"softcore_source": (character_softcore_outfit_source_choices(), {"default": "no_change"}),
"hardcore_state": (character_hardcore_clothing_state_choices(), {"default": "no_change"}),
"custom_softcore_outfits": ("STRING", {"default": "", "multiline": True}),
"custom_hardcore_clothing": ("STRING", {"default": "", "multiline": True}),
},
"optional": {
"characteristics": (SXCP_CHARACTERISTICS,),
},
}
RETURN_TYPES = (SXCP_CHARACTERISTICS, "STRING")
RETURN_NAMES = ("characteristics", "summary")
FUNCTION = "build"
CATEGORY = "prompt_builder"
def build(
self,
combine_mode,
softcore_source,
hardcore_state,
custom_softcore_outfits,
custom_hardcore_clothing,
characteristics="",
):
config = characteristics or ""
if softcore_source != "no_change":
config = build_characteristics_config_json(
characteristics=config,
axis="softcore_outfits",
selected_values=character_softcore_outfit_values(softcore_source, custom_softcore_outfits),
combine_mode=combine_mode,
)
if hardcore_state != "no_change":
config = build_characteristics_config_json(
characteristics=config,
axis="hardcore_clothing",
selected_values=character_hardcore_clothing_values(hardcore_state, custom_hardcore_clothing),
combine_mode=combine_mode,
)
if not config:
config = build_characteristics_config_json(axis="", selected_values=[])
return config, json.loads(config).get("summary", "")
class SxCPCharacterManualDetails:
@classmethod
def INPUT_TYPES(cls):
@@ -1028,6 +1225,7 @@ class SxCPCharacterSlot:
"optional": {
"manual": (SXCP_CHARACTER_MANUAL,),
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
"characteristics": (SXCP_CHARACTERISTICS,),
"hair_config": (SXCP_HAIR_CONFIG,),
"character_cast": (SXCP_CHARACTER_CAST,),
},
@@ -1056,6 +1254,7 @@ class SxCPCharacterSlot:
hardcore_expression_intensity=-1.0,
character_cast="",
ethnicity_list="",
characteristics="",
hair_config="",
manual="",
):
@@ -1072,6 +1271,7 @@ class SxCPCharacterSlot:
body_phrase="",
skin="",
hair="",
characteristics=characteristics,
hair_config=hair_config,
eyes="",
descriptor_detail=descriptor_detail,
@@ -1109,6 +1309,7 @@ class SxCPWomanSlot:
"optional": {
"manual": (SXCP_CHARACTER_MANUAL,),
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
"characteristics": (SXCP_CHARACTERISTICS,),
"hair_config": (SXCP_HAIR_CONFIG,),
"character_cast": (SXCP_CHARACTER_CAST,),
},
@@ -1135,6 +1336,7 @@ class SxCPWomanSlot:
hardcore_expression_intensity=-1.0,
character_cast="",
ethnicity_list="",
characteristics="",
hair_config="",
manual="",
):
@@ -1151,6 +1353,7 @@ class SxCPWomanSlot:
body_phrase="",
skin="",
hair="",
characteristics=characteristics,
hair_config=hair_config,
eyes="",
descriptor_detail=descriptor_detail,
@@ -1187,6 +1390,7 @@ class SxCPManSlot:
"optional": {
"manual": (SXCP_CHARACTER_MANUAL,),
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
"characteristics": (SXCP_CHARACTERISTICS,),
"hair_config": (SXCP_HAIR_CONFIG,),
"character_cast": (SXCP_CHARACTER_CAST,),
},
@@ -1213,6 +1417,7 @@ class SxCPManSlot:
hardcore_expression_intensity=-1.0,
character_cast="",
ethnicity_list="",
characteristics="",
hair_config="",
manual="",
):
@@ -1229,6 +1434,7 @@ class SxCPManSlot:
body_phrase="",
skin="",
hair="",
characteristics=characteristics,
hair_config=hair_config,
eyes="",
descriptor_detail=descriptor_detail,
@@ -1702,6 +1908,12 @@ NODE_CLASS_MAPPINGS = {
"SxCPHairLength": SxCPHairLength,
"SxCPHairColor": SxCPHairColor,
"SxCPHairStyle": SxCPHairStyle,
"SxCPCharacterAgeRange": SxCPCharacterAgeRange,
"SxCPCharacterBodyPool": SxCPCharacterBodyPool,
"SxCPWomanBodyPool": SxCPWomanBodyPool,
"SxCPManBodyPool": SxCPManBodyPool,
"SxCPEyeColorPool": SxCPEyeColorPool,
"SxCPCharacterClothing": SxCPCharacterClothing,
"SxCPCharacterManualDetails": SxCPCharacterManualDetails,
"SxCPAdvancedFilters": SxCPAdvancedFilters,
"SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs,
@@ -1732,6 +1944,12 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"SxCPHairLength": "SxCP Hair Length",
"SxCPHairColor": "SxCP Hair Color",
"SxCPHairStyle": "SxCP Hair Style/Cut",
"SxCPCharacterAgeRange": "SxCP Character Age Range",
"SxCPCharacterBodyPool": "SxCP Character Body Pool",
"SxCPWomanBodyPool": "SxCP Woman Body Pool",
"SxCPManBodyPool": "SxCP Man Body Pool",
"SxCPEyeColorPool": "SxCP Eye Color Pool",
"SxCPCharacterClothing": "SxCP Character Clothing",
"SxCPCharacterManualDetails": "SxCP Character Manual Details",
"SxCPAdvancedFilters": "SxCP Advanced Filters",
"SxCPPromptBuilderFromConfigs": "SxCP Prompt Builder From Configs",