Add chainable hair and manual character nodes

This commit is contained in:
2026-06-25 00:01:19 +02:00
parent ed6c7b8dc0
commit 01b59df30e
3 changed files with 698 additions and 96 deletions
+176 -78
View File
@@ -19,10 +19,12 @@ try:
build_cast_config_json,
build_category_config_json,
build_character_slot_json,
build_character_manual_config_json,
build_character_profile_json,
build_ethnicity_list_json,
build_filter_config_json,
build_generation_profile_json,
build_hair_config_json,
build_insta_of_options_json,
build_insta_of_pair,
build_prompt,
@@ -48,6 +50,9 @@ try:
character_descriptor_detail_choices,
character_ethnicity_choices,
character_figure_choices,
character_hair_color_choices,
character_hair_length_choices,
character_hair_style_choices,
character_label_choices,
character_man_body_choices,
character_presence_choices,
@@ -72,10 +77,12 @@ except ImportError:
build_cast_config_json,
build_category_config_json,
build_character_slot_json,
build_character_manual_config_json,
build_character_profile_json,
build_ethnicity_list_json,
build_filter_config_json,
build_generation_profile_json,
build_hair_config_json,
build_insta_of_options_json,
build_insta_of_pair,
build_prompt,
@@ -101,6 +108,9 @@ except ImportError:
character_descriptor_detail_choices,
character_ethnicity_choices,
character_figure_choices,
character_hair_color_choices,
character_hair_length_choices,
character_hair_style_choices,
character_label_choices,
character_man_body_choices,
character_presence_choices,
@@ -797,6 +807,119 @@ class SxCPEthnicityList:
return result["ethnicity"], result["filter_config"], result["summary"]
class _SxCPHairAxisNode:
AXIS = "color"
PREFIX = "include"
@classmethod
def _choices(cls):
if cls.AXIS == "color":
return [choice for choice in character_hair_color_choices() if choice != "random"]
if cls.AXIS == "length":
return [choice for choice in character_hair_length_choices() if choice != "random"]
return [choice for choice in character_hair_style_choices() if choice != "random"]
@classmethod
def INPUT_TYPES(cls):
required = {
"combine_mode": (["replace_axis", "add_to_axis"], {"default": "replace_axis"}),
}
for choice in cls._choices():
required[f"{cls.PREFIX}_{choice}"] = ("BOOLEAN", {"default": False})
return {
"required": required,
"optional": {
"hair_config": ("STRING", {"default": "", "multiline": True}),
},
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("hair_config", "summary")
FUNCTION = "build"
CATEGORY = "prompt_builder"
def build(self, combine_mode="replace_axis", hair_config="", **kwargs):
selected = [
choice
for choice in self._choices()
if bool(kwargs.get(f"{self.PREFIX}_{choice}", False))
]
config = build_hair_config_json(
hair_config=hair_config or "",
axis=self.AXIS,
selected_values=selected,
combine_mode=combine_mode,
)
parsed = json.loads(config)
return config, parsed.get("summary", "")
class SxCPHairColor(_SxCPHairAxisNode):
AXIS = "color"
class SxCPHairLength(_SxCPHairAxisNode):
AXIS = "length"
class SxCPHairStyle(_SxCPHairAxisNode):
AXIS = "style"
class SxCPCharacterManualDetails:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"combine_mode": (["merge_nonempty", "replace_all"], {"default": "merge_nonempty"}),
"manual_age": ("STRING", {"default": ""}),
"manual_body": ("STRING", {"default": ""}),
"body_phrase": ("STRING", {"default": ""}),
"skin": ("STRING", {"default": ""}),
"hair": ("STRING", {"default": ""}),
"eyes": ("STRING", {"default": ""}),
"softcore_outfit": ("STRING", {"default": ""}),
"hardcore_clothing": ("STRING", {"default": ""}),
},
"optional": {
"manual": ("STRING", {"default": "", "multiline": True}),
},
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("manual", "summary")
FUNCTION = "build"
CATEGORY = "prompt_builder"
def build(
self,
combine_mode,
manual_age,
manual_body,
body_phrase,
skin,
hair,
eyes,
softcore_outfit,
hardcore_clothing,
manual="",
):
config = build_character_manual_config_json(
manual=manual or "",
combine_mode=combine_mode,
manual_age=manual_age,
manual_body=manual_body,
body_phrase=body_phrase,
skin=skin,
hair=hair,
eyes=eyes,
softcore_outfit=softcore_outfit,
hardcore_clothing=hardcore_clothing,
)
parsed = json.loads(config)
return config, parsed.get("summary", "")
class SxCPPromptBuilderFromConfigs:
@classmethod
def INPUT_TYPES(cls):
@@ -877,27 +1000,21 @@ class SxCPCharacterSlot:
"subject_type": (["woman", "man"], {"default": "woman"}),
"label": (character_label_choices(), {"default": "auto_chain"}),
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
"age": (character_age_choices(), {"default": "random"}),
"manual_age": ("STRING", {"default": ""}),
"age": ([choice for choice in character_age_choices() if choice != "manual"], {"default": "random"}),
"ethnicity": (character_ethnicity_choices(), {"default": "random"}),
"figure": (character_figure_choices(), {"default": "random"}),
"body": (character_body_choices(), {"default": "random"}),
"manual_body": ("STRING", {"default": ""}),
"body_phrase": ("STRING", {"default": ""}),
"skin": ("STRING", {"default": ""}),
"hair": ("STRING", {"default": ""}),
"eyes": ("STRING", {"default": ""}),
"body": ([choice for choice in character_body_choices() if choice != "manual"], {"default": "random"}),
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "auto"}),
"expression_enabled": ("BOOLEAN", {"default": True}),
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"presence_mode": (character_presence_choices(), {"default": "visible"}),
"softcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"hardcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"softcore_outfit": ("STRING", {"default": ""}),
"hardcore_clothing": ("STRING", {"default": ""}),
},
"optional": {
"manual": ("STRING", {"default": "", "multiline": True}),
"ethnicity_list": ("STRING", {"default": "", "multiline": True}),
"hair_config": ("STRING", {"default": "", "multiline": True}),
"character_cast": ("STRING", {"default": "", "multiline": True}),
},
}
@@ -914,48 +1031,43 @@ class SxCPCharacterSlot:
label,
slot_seed,
age,
manual_age,
ethnicity,
figure,
body,
manual_body,
body_phrase,
skin,
hair,
eyes,
descriptor_detail="auto",
expression_enabled=True,
expression_intensity=-1.0,
presence_mode="visible",
softcore_expression_intensity=-1.0,
hardcore_expression_intensity=-1.0,
softcore_outfit="",
hardcore_clothing="",
character_cast="",
ethnicity_list="",
hair_config="",
manual="",
):
result = build_character_slot_json(
subject_type=subject_type,
label=label,
slot_seed=slot_seed,
age=age,
manual_age=manual_age,
manual=manual,
ethnicity=ethnicity_list or ethnicity,
figure=figure,
body=body,
manual_body=manual_body,
body_phrase=body_phrase,
skin=skin,
hair=hair,
eyes=eyes,
manual_body="",
body_phrase="",
skin="",
hair="",
hair_config=hair_config,
eyes="",
descriptor_detail=descriptor_detail,
expression_enabled=expression_enabled,
expression_intensity=expression_intensity,
presence_mode=presence_mode,
softcore_expression_intensity=softcore_expression_intensity,
hardcore_expression_intensity=hardcore_expression_intensity,
softcore_outfit=softcore_outfit,
hardcore_clothing=hardcore_clothing,
softcore_outfit="",
hardcore_clothing="",
enabled=enabled,
character_cast=character_cast or "",
)
@@ -970,26 +1082,20 @@ class SxCPWomanSlot:
"enabled": ("BOOLEAN", {"default": True}),
"label": (character_label_choices(), {"default": "auto_chain"}),
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
"age": (character_age_choices(), {"default": "random"}),
"manual_age": ("STRING", {"default": ""}),
"age": ([choice for choice in character_age_choices() if choice != "manual"], {"default": "random"}),
"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": ""}),
"body": ([choice for choice in character_woman_body_choices() if choice != "manual"], {"default": "random"}),
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "auto"}),
"expression_enabled": ("BOOLEAN", {"default": True}),
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"softcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"hardcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"softcore_outfit": ("STRING", {"default": ""}),
"hardcore_clothing": ("STRING", {"default": ""}),
},
"optional": {
"manual": ("STRING", {"default": "", "multiline": True}),
"ethnicity_list": ("STRING", {"default": "", "multiline": True}),
"hair_config": ("STRING", {"default": "", "multiline": True}),
"character_cast": ("STRING", {"default": "", "multiline": True}),
},
}
@@ -1005,46 +1111,41 @@ class SxCPWomanSlot:
label,
slot_seed,
age,
manual_age,
ethnicity,
figure_bias,
body,
manual_body,
body_phrase,
skin,
hair,
eyes,
descriptor_detail="auto",
expression_enabled=True,
expression_intensity=-1.0,
softcore_expression_intensity=-1.0,
hardcore_expression_intensity=-1.0,
softcore_outfit="",
hardcore_clothing="",
character_cast="",
ethnicity_list="",
hair_config="",
manual="",
):
result = build_character_slot_json(
subject_type="woman",
label=label,
slot_seed=slot_seed,
age=age,
manual_age=manual_age,
manual=manual,
ethnicity=ethnicity_list or ethnicity,
figure=figure_bias,
body=body,
manual_body=manual_body,
body_phrase=body_phrase,
skin=skin,
hair=hair,
eyes=eyes,
manual_body="",
body_phrase="",
skin="",
hair="",
hair_config=hair_config,
eyes="",
descriptor_detail=descriptor_detail,
expression_enabled=expression_enabled,
expression_intensity=expression_intensity,
softcore_expression_intensity=softcore_expression_intensity,
hardcore_expression_intensity=hardcore_expression_intensity,
softcore_outfit=softcore_outfit,
hardcore_clothing=hardcore_clothing,
softcore_outfit="",
hardcore_clothing="",
enabled=enabled,
character_cast=character_cast or "",
)
@@ -1059,26 +1160,20 @@ class SxCPManSlot:
"enabled": ("BOOLEAN", {"default": True}),
"label": (character_label_choices(), {"default": "auto_chain"}),
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
"age": (character_age_choices(), {"default": "random"}),
"manual_age": ("STRING", {"default": ""}),
"age": ([choice for choice in character_age_choices() if choice != "manual"], {"default": "random"}),
"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": ""}),
"body": ([choice for choice in character_man_body_choices() if choice != "manual"], {"default": "random"}),
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "compact"}),
"expression_enabled": ("BOOLEAN", {"default": True}),
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"presence_mode": (character_presence_choices(), {"default": "visible"}),
"softcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"hardcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"softcore_outfit": ("STRING", {"default": ""}),
"hardcore_clothing": ("STRING", {"default": ""}),
},
"optional": {
"manual": ("STRING", {"default": "", "multiline": True}),
"ethnicity_list": ("STRING", {"default": "", "multiline": True}),
"hair_config": ("STRING", {"default": "", "multiline": True}),
"character_cast": ("STRING", {"default": "", "multiline": True}),
},
}
@@ -1094,47 +1189,42 @@ class SxCPManSlot:
label,
slot_seed,
age,
manual_age,
ethnicity,
body,
manual_body,
body_phrase,
skin,
hair,
eyes,
descriptor_detail="compact",
expression_enabled=True,
expression_intensity=-1.0,
presence_mode="visible",
softcore_expression_intensity=-1.0,
hardcore_expression_intensity=-1.0,
softcore_outfit="",
hardcore_clothing="",
character_cast="",
ethnicity_list="",
hair_config="",
manual="",
):
result = build_character_slot_json(
subject_type="man",
label=label,
slot_seed=slot_seed,
age=age,
manual_age=manual_age,
manual=manual,
ethnicity=ethnicity_list or ethnicity,
figure="random",
body=body,
manual_body=manual_body,
body_phrase=body_phrase,
skin=skin,
hair=hair,
eyes=eyes,
manual_body="",
body_phrase="",
skin="",
hair="",
hair_config=hair_config,
eyes="",
descriptor_detail=descriptor_detail,
expression_enabled=expression_enabled,
expression_intensity=expression_intensity,
presence_mode=presence_mode,
softcore_expression_intensity=softcore_expression_intensity,
hardcore_expression_intensity=hardcore_expression_intensity,
softcore_outfit=softcore_outfit,
hardcore_clothing=hardcore_clothing,
softcore_outfit="",
hardcore_clothing="",
enabled=enabled,
character_cast=character_cast or "",
)
@@ -1595,6 +1685,10 @@ NODE_CLASS_MAPPINGS = {
"SxCPCastControl": SxCPCastControl,
"SxCPGenerationProfile": SxCPGenerationProfile,
"SxCPEthnicityList": SxCPEthnicityList,
"SxCPHairLength": SxCPHairLength,
"SxCPHairColor": SxCPHairColor,
"SxCPHairStyle": SxCPHairStyle,
"SxCPCharacterManualDetails": SxCPCharacterManualDetails,
"SxCPAdvancedFilters": SxCPAdvancedFilters,
"SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs,
"SxCPWomanSlot": SxCPWomanSlot,
@@ -1621,6 +1715,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"SxCPCastControl": "SxCP Cast Control",
"SxCPGenerationProfile": "SxCP Generation Profile",
"SxCPEthnicityList": "SxCP Ethnicity List",
"SxCPHairLength": "SxCP Hair Length",
"SxCPHairColor": "SxCP Hair Color",
"SxCPHairStyle": "SxCP Hair Style/Cut",
"SxCPCharacterManualDetails": "SxCP Character Manual Details",
"SxCPAdvancedFilters": "SxCP Advanced Filters",
"SxCPPromptBuilderFromConfigs": "SxCP Prompt Builder From Configs",
"SxCPWomanSlot": "SxCP Woman Slot",