Extract character utility nodes
This commit is contained in:
+10
-776
@@ -404,6 +404,10 @@ try:
|
||||
NODE_CLASS_MAPPINGS as CAMERA_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as CAMERA_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from .node_character import (
|
||||
NODE_CLASS_MAPPINGS as CHARACTER_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as CHARACTER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from .node_profile_filter import (
|
||||
NODE_CLASS_MAPPINGS as PROFILE_FILTER_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as PROFILE_FILTER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
@@ -417,11 +421,6 @@ try:
|
||||
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from .prompt_builder import (
|
||||
build_character_slot_json,
|
||||
build_character_manual_config_json,
|
||||
build_character_profile_json,
|
||||
build_characteristics_config_json,
|
||||
build_hair_config_json,
|
||||
build_hardcore_action_filter_json,
|
||||
build_hardcore_position_pool_json,
|
||||
build_insta_of_options_json,
|
||||
@@ -431,30 +430,11 @@ try:
|
||||
camera_detail_choices,
|
||||
camera_mode_choices,
|
||||
category_choices,
|
||||
character_age_choices,
|
||||
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,
|
||||
character_hair_style_choices,
|
||||
character_label_choices,
|
||||
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,
|
||||
hardcore_position_family_choices,
|
||||
hardcore_position_focus_choices,
|
||||
hardcore_position_key_choices,
|
||||
hardcore_detail_density_choices,
|
||||
load_character_profile_json,
|
||||
save_character_profile_payload,
|
||||
subcategory_choices,
|
||||
)
|
||||
@@ -474,6 +454,10 @@ except ImportError:
|
||||
NODE_CLASS_MAPPINGS as CAMERA_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as CAMERA_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from node_character import (
|
||||
NODE_CLASS_MAPPINGS as CHARACTER_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as CHARACTER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from node_profile_filter import (
|
||||
NODE_CLASS_MAPPINGS as PROFILE_FILTER_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as PROFILE_FILTER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
@@ -487,11 +471,6 @@ except ImportError:
|
||||
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from prompt_builder import (
|
||||
build_character_slot_json,
|
||||
build_character_manual_config_json,
|
||||
build_character_profile_json,
|
||||
build_characteristics_config_json,
|
||||
build_hair_config_json,
|
||||
build_hardcore_action_filter_json,
|
||||
build_hardcore_position_pool_json,
|
||||
build_insta_of_options_json,
|
||||
@@ -501,30 +480,11 @@ except ImportError:
|
||||
camera_detail_choices,
|
||||
camera_mode_choices,
|
||||
category_choices,
|
||||
character_age_choices,
|
||||
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,
|
||||
character_hair_style_choices,
|
||||
character_label_choices,
|
||||
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,
|
||||
hardcore_position_family_choices,
|
||||
hardcore_position_focus_choices,
|
||||
hardcore_position_key_choices,
|
||||
hardcore_detail_density_choices,
|
||||
load_character_profile_json,
|
||||
save_character_profile_payload,
|
||||
subcategory_choices,
|
||||
)
|
||||
@@ -724,65 +684,6 @@ class SxCPPromptBuilder:
|
||||
)
|
||||
|
||||
|
||||
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": (SXCP_HAIR_CONFIG,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_HAIR_CONFIG, "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"
|
||||
|
||||
|
||||
def _choice_input_key(prefix, choice):
|
||||
key = "".join(char if char.isalnum() else "_" for char in str(choice).lower()).strip("_")
|
||||
while "__" in key:
|
||||
@@ -790,183 +691,6 @@ def _choice_input_key(prefix, choice):
|
||||
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 SxCPHardcorePositionPool:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -1062,60 +786,6 @@ class SxCPHardcoreActionFilter:
|
||||
return config, json.loads(config).get("summary", "")
|
||||
|
||||
|
||||
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": (SXCP_CHARACTER_MANUAL,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_MANUAL, "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):
|
||||
@@ -1196,414 +866,6 @@ class SxCPPromptBuilderFromConfigs:
|
||||
)
|
||||
|
||||
|
||||
class SxCPCharacterSlot:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"enabled": ("BOOLEAN", {"default": True}),
|
||||
"subject_type": (["woman", "man"], {"default": "woman"}),
|
||||
"label": (character_label_choices(), {"default": "auto_chain"}),
|
||||
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
||||
"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": ([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}),
|
||||
},
|
||||
"optional": {
|
||||
"manual": (SXCP_CHARACTER_MANUAL,),
|
||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||
"characteristics": (SXCP_CHARACTERISTICS,),
|
||||
"hair_config": (SXCP_HAIR_CONFIG,),
|
||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_CAST, SXCP_CHARACTER_SLOT, "STRING", "STRING")
|
||||
RETURN_NAMES = ("character_cast", "character_slot", "summary", "status")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
enabled,
|
||||
subject_type,
|
||||
label,
|
||||
slot_seed,
|
||||
age,
|
||||
ethnicity,
|
||||
figure,
|
||||
body,
|
||||
descriptor_detail="auto",
|
||||
expression_enabled=True,
|
||||
expression_intensity=-1.0,
|
||||
presence_mode="visible",
|
||||
softcore_expression_intensity=-1.0,
|
||||
hardcore_expression_intensity=-1.0,
|
||||
character_cast="",
|
||||
ethnicity_list="",
|
||||
characteristics="",
|
||||
hair_config="",
|
||||
manual="",
|
||||
):
|
||||
result = build_character_slot_json(
|
||||
subject_type=subject_type,
|
||||
label=label,
|
||||
slot_seed=slot_seed,
|
||||
age=age,
|
||||
manual=manual,
|
||||
ethnicity=ethnicity_list or ethnicity,
|
||||
figure=figure,
|
||||
body=body,
|
||||
manual_body="",
|
||||
body_phrase="",
|
||||
skin="",
|
||||
hair="",
|
||||
characteristics=characteristics,
|
||||
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="",
|
||||
hardcore_clothing="",
|
||||
enabled=enabled,
|
||||
character_cast=character_cast or "",
|
||||
)
|
||||
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"}),
|
||||
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
||||
"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": ([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}),
|
||||
},
|
||||
"optional": {
|
||||
"manual": (SXCP_CHARACTER_MANUAL,),
|
||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||
"characteristics": (SXCP_CHARACTERISTICS,),
|
||||
"hair_config": (SXCP_HAIR_CONFIG,),
|
||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_CAST, SXCP_CHARACTER_SLOT, "STRING", "STRING")
|
||||
RETURN_NAMES = ("character_cast", "character_slot", "summary", "status")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
enabled,
|
||||
label,
|
||||
slot_seed,
|
||||
age,
|
||||
ethnicity,
|
||||
figure_bias,
|
||||
body,
|
||||
descriptor_detail="auto",
|
||||
expression_enabled=True,
|
||||
expression_intensity=-1.0,
|
||||
softcore_expression_intensity=-1.0,
|
||||
hardcore_expression_intensity=-1.0,
|
||||
character_cast="",
|
||||
ethnicity_list="",
|
||||
characteristics="",
|
||||
hair_config="",
|
||||
manual="",
|
||||
):
|
||||
result = build_character_slot_json(
|
||||
subject_type="woman",
|
||||
label=label,
|
||||
slot_seed=slot_seed,
|
||||
age=age,
|
||||
manual=manual,
|
||||
ethnicity=ethnicity_list or ethnicity,
|
||||
figure=figure_bias,
|
||||
body=body,
|
||||
manual_body="",
|
||||
body_phrase="",
|
||||
skin="",
|
||||
hair="",
|
||||
characteristics=characteristics,
|
||||
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="",
|
||||
hardcore_clothing="",
|
||||
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"}),
|
||||
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
||||
"age": ([choice for choice in character_age_choices() if choice != "manual"], {"default": "random"}),
|
||||
"ethnicity": (character_ethnicity_choices(), {"default": "random"}),
|
||||
"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}),
|
||||
},
|
||||
"optional": {
|
||||
"manual": (SXCP_CHARACTER_MANUAL,),
|
||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||
"characteristics": (SXCP_CHARACTERISTICS,),
|
||||
"hair_config": (SXCP_HAIR_CONFIG,),
|
||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_CAST, SXCP_CHARACTER_SLOT, "STRING", "STRING")
|
||||
RETURN_NAMES = ("character_cast", "character_slot", "summary", "status")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
enabled,
|
||||
label,
|
||||
slot_seed,
|
||||
age,
|
||||
ethnicity,
|
||||
body,
|
||||
descriptor_detail="compact",
|
||||
expression_enabled=True,
|
||||
expression_intensity=-1.0,
|
||||
presence_mode="visible",
|
||||
softcore_expression_intensity=-1.0,
|
||||
hardcore_expression_intensity=-1.0,
|
||||
character_cast="",
|
||||
ethnicity_list="",
|
||||
characteristics="",
|
||||
hair_config="",
|
||||
manual="",
|
||||
):
|
||||
result = build_character_slot_json(
|
||||
subject_type="man",
|
||||
label=label,
|
||||
slot_seed=slot_seed,
|
||||
age=age,
|
||||
manual=manual,
|
||||
ethnicity=ethnicity_list or ethnicity,
|
||||
figure="random",
|
||||
body=body,
|
||||
manual_body="",
|
||||
body_phrase="",
|
||||
skin="",
|
||||
hair="",
|
||||
characteristics=characteristics,
|
||||
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="",
|
||||
hardcore_clothing="",
|
||||
enabled=enabled,
|
||||
character_cast=character_cast or "",
|
||||
)
|
||||
return result["character_cast"], result["character_slot"], result["summary"], result["status"]
|
||||
|
||||
|
||||
class SxCPCharacterProfileSave:
|
||||
OUTPUT_NODE = True
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"profile_name": ("STRING", {"default": "saved_character"}),
|
||||
"source": (["metadata_json", "character_slot", "manual"], {"default": "metadata_json"}),
|
||||
"subject_type": (["woman", "man"], {"default": "woman"}),
|
||||
"age": ("STRING", {"default": ""}),
|
||||
"body": ("STRING", {"default": ""}),
|
||||
"body_phrase": ("STRING", {"default": ""}),
|
||||
"skin": ("STRING", {"default": ""}),
|
||||
"hair": ("STRING", {"default": ""}),
|
||||
"eyes": ("STRING", {"default": ""}),
|
||||
"figure": ("STRING", {"default": ""}),
|
||||
"save_now": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
"optional": {
|
||||
"metadata_json": ("STRING", {"default": "", "multiline": True}),
|
||||
"character_slot": (SXCP_CHARACTER_SLOT,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_PROFILE, "STRING", "STRING", "STRING", "STRING", SXCP_CHARACTER_PROFILE)
|
||||
RETURN_NAMES = ("character_profile", "descriptor", "profile_name", "saved_path", "status", "profile_json")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
profile_name,
|
||||
source,
|
||||
subject_type,
|
||||
age,
|
||||
body,
|
||||
body_phrase,
|
||||
skin,
|
||||
hair,
|
||||
eyes,
|
||||
figure,
|
||||
save_now,
|
||||
metadata_json="",
|
||||
character_slot="",
|
||||
):
|
||||
profile = build_character_profile_json(
|
||||
profile_name=profile_name,
|
||||
source=source,
|
||||
metadata_json=metadata_json or "",
|
||||
character_slot=character_slot or "",
|
||||
subject_type=subject_type,
|
||||
age=age,
|
||||
body=body,
|
||||
body_phrase=body_phrase,
|
||||
skin=skin,
|
||||
hair=hair,
|
||||
eyes=eyes,
|
||||
figure=figure,
|
||||
save_now=save_now,
|
||||
)
|
||||
result = (
|
||||
profile["profile_json"],
|
||||
profile["descriptor"],
|
||||
profile["profile_name"],
|
||||
profile["saved_path"],
|
||||
profile["status"],
|
||||
profile["profile_json"],
|
||||
)
|
||||
return {
|
||||
"ui": {
|
||||
"profile_json": [profile["profile_json"]],
|
||||
"descriptor": [profile["descriptor"]],
|
||||
"profile_name": [profile["profile_name"]],
|
||||
"saved_path": [profile["saved_path"]],
|
||||
"status": [profile["status"]],
|
||||
},
|
||||
"result": result,
|
||||
}
|
||||
|
||||
|
||||
class SxCPCharacterProfileLoad:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"enabled": ("BOOLEAN", {"default": True}),
|
||||
"profile_name": (character_profile_choices(), {"default": "manual"}),
|
||||
"rename_to": ("STRING", {"default": ""}),
|
||||
"delete_now": ("BOOLEAN", {"default": False}),
|
||||
"rename_now": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
"optional": {
|
||||
"manual_profile_name": ("STRING", {"default": ""}),
|
||||
"fallback_profile_json": (SXCP_CHARACTER_PROFILE,),
|
||||
"override_subject_type": (["keep_profile", "woman", "man"], {"default": "keep_profile"}),
|
||||
"override_age": ("STRING", {"default": ""}),
|
||||
"override_body": ("STRING", {"default": ""}),
|
||||
"override_body_phrase": ("STRING", {"default": ""}),
|
||||
"override_skin": ("STRING", {"default": ""}),
|
||||
"override_hair": ("STRING", {"default": ""}),
|
||||
"override_eyes": ("STRING", {"default": ""}),
|
||||
"override_figure": ("STRING", {"default": ""}),
|
||||
"override_descriptor_detail": (["keep_profile"] + character_descriptor_detail_choices(), {"default": "keep_profile"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_PROFILE, "STRING", "STRING", "STRING", "STRING", SXCP_CHARACTER_PROFILE)
|
||||
RETURN_NAMES = ("character_profile", "descriptor", "profile_name", "saved_path", "status", "profile_json")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
enabled,
|
||||
profile_name,
|
||||
rename_to,
|
||||
delete_now,
|
||||
rename_now,
|
||||
manual_profile_name="",
|
||||
fallback_profile_json="",
|
||||
override_subject_type="keep_profile",
|
||||
override_age="",
|
||||
override_body="",
|
||||
override_body_phrase="",
|
||||
override_skin="",
|
||||
override_hair="",
|
||||
override_eyes="",
|
||||
override_figure="",
|
||||
override_descriptor_detail="keep_profile",
|
||||
):
|
||||
chosen_name = manual_profile_name.strip() if profile_name == "manual" and manual_profile_name.strip() else profile_name
|
||||
profile = load_character_profile_json(
|
||||
profile_name=chosen_name,
|
||||
fallback_profile_json=fallback_profile_json or "",
|
||||
enabled=enabled,
|
||||
delete_now=delete_now,
|
||||
rename_now=rename_now,
|
||||
rename_to=rename_to,
|
||||
override_subject_type=override_subject_type,
|
||||
override_age=override_age,
|
||||
override_body=override_body,
|
||||
override_body_phrase=override_body_phrase,
|
||||
override_skin=override_skin,
|
||||
override_hair=override_hair,
|
||||
override_eyes=override_eyes,
|
||||
override_figure=override_figure,
|
||||
override_descriptor_detail=override_descriptor_detail,
|
||||
)
|
||||
return (
|
||||
profile["profile_json"],
|
||||
profile["descriptor"],
|
||||
profile["profile_name"],
|
||||
profile["saved_path"],
|
||||
profile["status"],
|
||||
profile["profile_json"],
|
||||
)
|
||||
|
||||
|
||||
class SxCPCaptionNaturalizer:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -1991,27 +1253,13 @@ NODE_CLASS_MAPPINGS = {
|
||||
}
|
||||
NODE_CLASS_MAPPINGS.update(SEED_RESOLUTION_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(CAMERA_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(CHARACTER_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(ROUTE_CONFIG_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(PROFILE_FILTER_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update({
|
||||
"SxCPHairLength": SxCPHairLength,
|
||||
"SxCPHairColor": SxCPHairColor,
|
||||
"SxCPHairStyle": SxCPHairStyle,
|
||||
"SxCPCharacterAgeRange": SxCPCharacterAgeRange,
|
||||
"SxCPCharacterBodyPool": SxCPCharacterBodyPool,
|
||||
"SxCPWomanBodyPool": SxCPWomanBodyPool,
|
||||
"SxCPManBodyPool": SxCPManBodyPool,
|
||||
"SxCPEyeColorPool": SxCPEyeColorPool,
|
||||
"SxCPCharacterClothing": SxCPCharacterClothing,
|
||||
"SxCPHardcorePositionPool": SxCPHardcorePositionPool,
|
||||
"SxCPHardcoreActionFilter": SxCPHardcoreActionFilter,
|
||||
"SxCPCharacterManualDetails": SxCPCharacterManualDetails,
|
||||
"SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs,
|
||||
"SxCPWomanSlot": SxCPWomanSlot,
|
||||
"SxCPManSlot": SxCPManSlot,
|
||||
"SxCPCharacterSlot": SxCPCharacterSlot,
|
||||
"SxCPCharacterProfileSave": SxCPCharacterProfileSave,
|
||||
"SxCPCharacterProfileLoad": SxCPCharacterProfileLoad,
|
||||
"SxCPCaptionNaturalizer": SxCPCaptionNaturalizer,
|
||||
"SxCPKrea2Formatter": SxCPKrea2Formatter,
|
||||
"SxCPSDXLFormatter": SxCPSDXLFormatter,
|
||||
@@ -2026,27 +1274,13 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(CAMERA_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(CHARACTER_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(ROUTE_CONFIG_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(PROFILE_FILTER_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update({
|
||||
"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",
|
||||
"SxCPHardcorePositionPool": "SxCP Hardcore Position Pool",
|
||||
"SxCPHardcoreActionFilter": "SxCP Hardcore Action Filter",
|
||||
"SxCPCharacterManualDetails": "SxCP Character Manual Details",
|
||||
"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",
|
||||
"SxCPCaptionNaturalizer": "SxCP Caption Naturalizer",
|
||||
"SxCPKrea2Formatter": "SxCP Krea2 Formatter",
|
||||
"SxCPSDXLFormatter": "SxCP SDXL Formatter",
|
||||
|
||||
@@ -273,7 +273,8 @@ Improve later:
|
||||
### Node / UI Path
|
||||
|
||||
Owner: `__init__.py`, `node_seed_resolution.py`, `node_camera.py`,
|
||||
`node_route_config.py`, `node_profile_filter.py`, `loop_nodes.py`, `web/*.js`.
|
||||
`node_character.py`, `node_route_config.py`, `node_profile_filter.py`,
|
||||
`loop_nodes.py`, `web/*.js`.
|
||||
|
||||
Keep here:
|
||||
|
||||
@@ -283,6 +284,7 @@ Keep here:
|
||||
- dynamic input slots.
|
||||
- seed and resolution utility node declarations in `node_seed_resolution.py`.
|
||||
- camera utility node declarations in `node_camera.py`.
|
||||
- character pool, slot, and profile node declarations in `node_character.py`.
|
||||
- route/category/location/composition/cast config node declarations in
|
||||
`node_route_config.py`.
|
||||
- profile/filter/ethnicity-list node declarations in `node_profile_filter.py`.
|
||||
@@ -293,6 +295,9 @@ Already isolated:
|
||||
`node_seed_resolution.py`, with registration maps imported by `__init__.py`.
|
||||
- camera/orbit/Qwen translator utility nodes live in `node_camera.py`, with
|
||||
registration maps imported by `__init__.py`.
|
||||
- hair, age/body/eyes/clothing pools, manual character details, character
|
||||
slots, and profile save/load nodes live in `node_character.py`, with
|
||||
registration maps imported by `__init__.py`.
|
||||
- category preset, location/composition pool, location theme, and cast config
|
||||
utility nodes live in `node_route_config.py`, with registration maps imported
|
||||
by `__init__.py`.
|
||||
|
||||
@@ -25,8 +25,9 @@ When a result is wrong, first identify which layer owns the bad text:
|
||||
- Raw builder prompt acceptable, SDXL tags wrong: edit `sdxl_formatter.py`.
|
||||
- Natural caption/training caption wrong: edit `caption_naturalizer.py`.
|
||||
- UI/preview/loop behavior wrong: edit `__init__.py`, node family modules such
|
||||
as `node_seed_resolution.py`, `node_camera.py`, `node_route_config.py`, or
|
||||
`node_profile_filter.py`, `loop_nodes.py`, or `web/*.js`.
|
||||
as `node_seed_resolution.py`, `node_camera.py`, `node_character.py`,
|
||||
`node_route_config.py`, or `node_profile_filter.py`, `loop_nodes.py`, or
|
||||
`web/*.js`.
|
||||
|
||||
## High-Level Routes
|
||||
|
||||
@@ -694,6 +695,7 @@ These do not own prompt pool wording, but they affect execution and review:
|
||||
| Persistent text preview | `loop_nodes.py`, `web/preview_any_text.js` | Stores any value as text and keeps it after workflow reload. |
|
||||
| Seed and resolution utility nodes | `node_seed_resolution.py`, imported by `__init__.py` | Global/per-axis seed configs plus SDXL/Krea width/height helpers. |
|
||||
| Camera utility nodes | `node_camera.py`, imported by `__init__.py` | Direct camera config, orbit-to-camera config, and Qwen MultiAngle camera translation. |
|
||||
| Character utility nodes | `node_character.py`, imported by `__init__.py` | Hair, age/body/eyes/clothing pools, manual details, character slots, and profile save/load nodes. |
|
||||
| Route config utility nodes | `node_route_config.py`, imported by `__init__.py` | Category preset, location/composition pool, location theme, and cast config helpers. |
|
||||
| Profile/filter utility nodes | `node_profile_filter.py`, imported by `__init__.py` | Generation profile, advanced filter config, and ethnicity list helpers. |
|
||||
|
||||
|
||||
@@ -0,0 +1,809 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
try:
|
||||
from .prompt_builder import (
|
||||
build_character_manual_config_json,
|
||||
build_character_profile_json,
|
||||
build_character_slot_json,
|
||||
build_characteristics_config_json,
|
||||
build_hair_config_json,
|
||||
character_age_choices,
|
||||
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,
|
||||
character_hair_style_choices,
|
||||
character_hardcore_clothing_state_choices,
|
||||
character_hardcore_clothing_values,
|
||||
character_label_choices,
|
||||
character_man_body_choices,
|
||||
character_presence_choices,
|
||||
character_profile_choices,
|
||||
character_softcore_outfit_source_choices,
|
||||
character_softcore_outfit_values,
|
||||
character_woman_body_choices,
|
||||
load_character_profile_json,
|
||||
)
|
||||
except ImportError: # Allows local smoke tests from the repository root.
|
||||
from prompt_builder import (
|
||||
build_character_manual_config_json,
|
||||
build_character_profile_json,
|
||||
build_character_slot_json,
|
||||
build_characteristics_config_json,
|
||||
build_hair_config_json,
|
||||
character_age_choices,
|
||||
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,
|
||||
character_hair_style_choices,
|
||||
character_hardcore_clothing_state_choices,
|
||||
character_hardcore_clothing_values,
|
||||
character_label_choices,
|
||||
character_man_body_choices,
|
||||
character_presence_choices,
|
||||
character_profile_choices,
|
||||
character_softcore_outfit_source_choices,
|
||||
character_softcore_outfit_values,
|
||||
character_woman_body_choices,
|
||||
load_character_profile_json,
|
||||
)
|
||||
|
||||
|
||||
SXCP_HAIR_CONFIG = "SXCP_HAIR_CONFIG"
|
||||
SXCP_CHARACTERISTICS = "SXCP_CHARACTERISTICS"
|
||||
SXCP_CHARACTER_MANUAL = "SXCP_CHARACTER_MANUAL"
|
||||
SXCP_ETHNICITY_LIST = "SXCP_ETHNICITY_LIST"
|
||||
SXCP_CHARACTER_CAST = "SXCP_CHARACTER_CAST"
|
||||
SXCP_CHARACTER_SLOT = "SXCP_CHARACTER_SLOT"
|
||||
SXCP_CHARACTER_PROFILE = "SXCP_CHARACTER_PROFILE"
|
||||
|
||||
|
||||
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": (SXCP_HAIR_CONFIG,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_HAIR_CONFIG, "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"
|
||||
|
||||
|
||||
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):
|
||||
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": (SXCP_CHARACTER_MANUAL,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_MANUAL, "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 SxCPCharacterSlot:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"enabled": ("BOOLEAN", {"default": True}),
|
||||
"subject_type": (["woman", "man"], {"default": "woman"}),
|
||||
"label": (character_label_choices(), {"default": "auto_chain"}),
|
||||
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
||||
"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": ([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}),
|
||||
},
|
||||
"optional": {
|
||||
"manual": (SXCP_CHARACTER_MANUAL,),
|
||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||
"characteristics": (SXCP_CHARACTERISTICS,),
|
||||
"hair_config": (SXCP_HAIR_CONFIG,),
|
||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_CAST, SXCP_CHARACTER_SLOT, "STRING", "STRING")
|
||||
RETURN_NAMES = ("character_cast", "character_slot", "summary", "status")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
enabled,
|
||||
subject_type,
|
||||
label,
|
||||
slot_seed,
|
||||
age,
|
||||
ethnicity,
|
||||
figure,
|
||||
body,
|
||||
descriptor_detail="auto",
|
||||
expression_enabled=True,
|
||||
expression_intensity=-1.0,
|
||||
presence_mode="visible",
|
||||
softcore_expression_intensity=-1.0,
|
||||
hardcore_expression_intensity=-1.0,
|
||||
character_cast="",
|
||||
ethnicity_list="",
|
||||
characteristics="",
|
||||
hair_config="",
|
||||
manual="",
|
||||
):
|
||||
result = build_character_slot_json(
|
||||
subject_type=subject_type,
|
||||
label=label,
|
||||
slot_seed=slot_seed,
|
||||
age=age,
|
||||
manual=manual,
|
||||
ethnicity=ethnicity_list or ethnicity,
|
||||
figure=figure,
|
||||
body=body,
|
||||
manual_body="",
|
||||
body_phrase="",
|
||||
skin="",
|
||||
hair="",
|
||||
characteristics=characteristics,
|
||||
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="",
|
||||
hardcore_clothing="",
|
||||
enabled=enabled,
|
||||
character_cast=character_cast or "",
|
||||
)
|
||||
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"}),
|
||||
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
||||
"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": ([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}),
|
||||
},
|
||||
"optional": {
|
||||
"manual": (SXCP_CHARACTER_MANUAL,),
|
||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||
"characteristics": (SXCP_CHARACTERISTICS,),
|
||||
"hair_config": (SXCP_HAIR_CONFIG,),
|
||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_CAST, SXCP_CHARACTER_SLOT, "STRING", "STRING")
|
||||
RETURN_NAMES = ("character_cast", "character_slot", "summary", "status")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
enabled,
|
||||
label,
|
||||
slot_seed,
|
||||
age,
|
||||
ethnicity,
|
||||
figure_bias,
|
||||
body,
|
||||
descriptor_detail="auto",
|
||||
expression_enabled=True,
|
||||
expression_intensity=-1.0,
|
||||
softcore_expression_intensity=-1.0,
|
||||
hardcore_expression_intensity=-1.0,
|
||||
character_cast="",
|
||||
ethnicity_list="",
|
||||
characteristics="",
|
||||
hair_config="",
|
||||
manual="",
|
||||
):
|
||||
result = build_character_slot_json(
|
||||
subject_type="woman",
|
||||
label=label,
|
||||
slot_seed=slot_seed,
|
||||
age=age,
|
||||
manual=manual,
|
||||
ethnicity=ethnicity_list or ethnicity,
|
||||
figure=figure_bias,
|
||||
body=body,
|
||||
manual_body="",
|
||||
body_phrase="",
|
||||
skin="",
|
||||
hair="",
|
||||
characteristics=characteristics,
|
||||
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="",
|
||||
hardcore_clothing="",
|
||||
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"}),
|
||||
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
||||
"age": ([choice for choice in character_age_choices() if choice != "manual"], {"default": "random"}),
|
||||
"ethnicity": (character_ethnicity_choices(), {"default": "random"}),
|
||||
"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}),
|
||||
},
|
||||
"optional": {
|
||||
"manual": (SXCP_CHARACTER_MANUAL,),
|
||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||
"characteristics": (SXCP_CHARACTERISTICS,),
|
||||
"hair_config": (SXCP_HAIR_CONFIG,),
|
||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_CAST, SXCP_CHARACTER_SLOT, "STRING", "STRING")
|
||||
RETURN_NAMES = ("character_cast", "character_slot", "summary", "status")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
enabled,
|
||||
label,
|
||||
slot_seed,
|
||||
age,
|
||||
ethnicity,
|
||||
body,
|
||||
descriptor_detail="compact",
|
||||
expression_enabled=True,
|
||||
expression_intensity=-1.0,
|
||||
presence_mode="visible",
|
||||
softcore_expression_intensity=-1.0,
|
||||
hardcore_expression_intensity=-1.0,
|
||||
character_cast="",
|
||||
ethnicity_list="",
|
||||
characteristics="",
|
||||
hair_config="",
|
||||
manual="",
|
||||
):
|
||||
result = build_character_slot_json(
|
||||
subject_type="man",
|
||||
label=label,
|
||||
slot_seed=slot_seed,
|
||||
age=age,
|
||||
manual=manual,
|
||||
ethnicity=ethnicity_list or ethnicity,
|
||||
figure="random",
|
||||
body=body,
|
||||
manual_body="",
|
||||
body_phrase="",
|
||||
skin="",
|
||||
hair="",
|
||||
characteristics=characteristics,
|
||||
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="",
|
||||
hardcore_clothing="",
|
||||
enabled=enabled,
|
||||
character_cast=character_cast or "",
|
||||
)
|
||||
return result["character_cast"], result["character_slot"], result["summary"], result["status"]
|
||||
|
||||
|
||||
class SxCPCharacterProfileSave:
|
||||
OUTPUT_NODE = True
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"profile_name": ("STRING", {"default": "saved_character"}),
|
||||
"source": (["metadata_json", "character_slot", "manual"], {"default": "metadata_json"}),
|
||||
"subject_type": (["woman", "man"], {"default": "woman"}),
|
||||
"age": ("STRING", {"default": ""}),
|
||||
"body": ("STRING", {"default": ""}),
|
||||
"body_phrase": ("STRING", {"default": ""}),
|
||||
"skin": ("STRING", {"default": ""}),
|
||||
"hair": ("STRING", {"default": ""}),
|
||||
"eyes": ("STRING", {"default": ""}),
|
||||
"figure": ("STRING", {"default": ""}),
|
||||
"save_now": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
"optional": {
|
||||
"metadata_json": ("STRING", {"default": "", "multiline": True}),
|
||||
"character_slot": (SXCP_CHARACTER_SLOT,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_PROFILE, "STRING", "STRING", "STRING", "STRING", SXCP_CHARACTER_PROFILE)
|
||||
RETURN_NAMES = ("character_profile", "descriptor", "profile_name", "saved_path", "status", "profile_json")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
profile_name,
|
||||
source,
|
||||
subject_type,
|
||||
age,
|
||||
body,
|
||||
body_phrase,
|
||||
skin,
|
||||
hair,
|
||||
eyes,
|
||||
figure,
|
||||
save_now,
|
||||
metadata_json="",
|
||||
character_slot="",
|
||||
):
|
||||
profile = build_character_profile_json(
|
||||
profile_name=profile_name,
|
||||
source=source,
|
||||
metadata_json=metadata_json or "",
|
||||
character_slot=character_slot or "",
|
||||
subject_type=subject_type,
|
||||
age=age,
|
||||
body=body,
|
||||
body_phrase=body_phrase,
|
||||
skin=skin,
|
||||
hair=hair,
|
||||
eyes=eyes,
|
||||
figure=figure,
|
||||
save_now=save_now,
|
||||
)
|
||||
result = (
|
||||
profile["profile_json"],
|
||||
profile["descriptor"],
|
||||
profile["profile_name"],
|
||||
profile["saved_path"],
|
||||
profile["status"],
|
||||
profile["profile_json"],
|
||||
)
|
||||
return {
|
||||
"ui": {
|
||||
"profile_json": [profile["profile_json"]],
|
||||
"descriptor": [profile["descriptor"]],
|
||||
"profile_name": [profile["profile_name"]],
|
||||
"saved_path": [profile["saved_path"]],
|
||||
"status": [profile["status"]],
|
||||
},
|
||||
"result": result,
|
||||
}
|
||||
|
||||
|
||||
class SxCPCharacterProfileLoad:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"enabled": ("BOOLEAN", {"default": True}),
|
||||
"profile_name": (character_profile_choices(), {"default": "manual"}),
|
||||
"rename_to": ("STRING", {"default": ""}),
|
||||
"delete_now": ("BOOLEAN", {"default": False}),
|
||||
"rename_now": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
"optional": {
|
||||
"manual_profile_name": ("STRING", {"default": ""}),
|
||||
"fallback_profile_json": (SXCP_CHARACTER_PROFILE,),
|
||||
"override_subject_type": (["keep_profile", "woman", "man"], {"default": "keep_profile"}),
|
||||
"override_age": ("STRING", {"default": ""}),
|
||||
"override_body": ("STRING", {"default": ""}),
|
||||
"override_body_phrase": ("STRING", {"default": ""}),
|
||||
"override_skin": ("STRING", {"default": ""}),
|
||||
"override_hair": ("STRING", {"default": ""}),
|
||||
"override_eyes": ("STRING", {"default": ""}),
|
||||
"override_figure": ("STRING", {"default": ""}),
|
||||
"override_descriptor_detail": (["keep_profile"] + character_descriptor_detail_choices(), {"default": "keep_profile"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (SXCP_CHARACTER_PROFILE, "STRING", "STRING", "STRING", "STRING", SXCP_CHARACTER_PROFILE)
|
||||
RETURN_NAMES = ("character_profile", "descriptor", "profile_name", "saved_path", "status", "profile_json")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
enabled,
|
||||
profile_name,
|
||||
rename_to,
|
||||
delete_now,
|
||||
rename_now,
|
||||
manual_profile_name="",
|
||||
fallback_profile_json="",
|
||||
override_subject_type="keep_profile",
|
||||
override_age="",
|
||||
override_body="",
|
||||
override_body_phrase="",
|
||||
override_skin="",
|
||||
override_hair="",
|
||||
override_eyes="",
|
||||
override_figure="",
|
||||
override_descriptor_detail="keep_profile",
|
||||
):
|
||||
chosen_name = manual_profile_name.strip() if profile_name == "manual" and manual_profile_name.strip() else profile_name
|
||||
profile = load_character_profile_json(
|
||||
profile_name=chosen_name,
|
||||
fallback_profile_json=fallback_profile_json or "",
|
||||
enabled=enabled,
|
||||
delete_now=delete_now,
|
||||
rename_now=rename_now,
|
||||
rename_to=rename_to,
|
||||
override_subject_type=override_subject_type,
|
||||
override_age=override_age,
|
||||
override_body=override_body,
|
||||
override_body_phrase=override_body_phrase,
|
||||
override_skin=override_skin,
|
||||
override_hair=override_hair,
|
||||
override_eyes=override_eyes,
|
||||
override_figure=override_figure,
|
||||
override_descriptor_detail=override_descriptor_detail,
|
||||
)
|
||||
return (
|
||||
profile["profile_json"],
|
||||
profile["descriptor"],
|
||||
profile["profile_name"],
|
||||
profile["saved_path"],
|
||||
profile["status"],
|
||||
profile["profile_json"],
|
||||
)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SxCPHairLength": SxCPHairLength,
|
||||
"SxCPHairColor": SxCPHairColor,
|
||||
"SxCPHairStyle": SxCPHairStyle,
|
||||
"SxCPCharacterAgeRange": SxCPCharacterAgeRange,
|
||||
"SxCPCharacterBodyPool": SxCPCharacterBodyPool,
|
||||
"SxCPWomanBodyPool": SxCPWomanBodyPool,
|
||||
"SxCPManBodyPool": SxCPManBodyPool,
|
||||
"SxCPEyeColorPool": SxCPEyeColorPool,
|
||||
"SxCPCharacterClothing": SxCPCharacterClothing,
|
||||
"SxCPCharacterManualDetails": SxCPCharacterManualDetails,
|
||||
"SxCPWomanSlot": SxCPWomanSlot,
|
||||
"SxCPManSlot": SxCPManSlot,
|
||||
"SxCPCharacterSlot": SxCPCharacterSlot,
|
||||
"SxCPCharacterProfileSave": SxCPCharacterProfileSave,
|
||||
"SxCPCharacterProfileLoad": SxCPCharacterProfileLoad,
|
||||
}
|
||||
|
||||
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",
|
||||
"SxCPWomanSlot": "SxCP Woman Slot",
|
||||
"SxCPManSlot": "SxCP Man Slot",
|
||||
"SxCPCharacterSlot": "SxCP Character Slot",
|
||||
"SxCPCharacterProfileSave": "SxCP Character Profile Save",
|
||||
"SxCPCharacterProfileLoad": "SxCP Character Profile Load",
|
||||
}
|
||||
@@ -1848,6 +1848,170 @@ def smoke_node_route_config_registration() -> None:
|
||||
_expect("weighted cast:" in bias_a[3], "Cast Bias summary lost weighted cast label")
|
||||
|
||||
|
||||
def smoke_node_character_registration() -> None:
|
||||
required_nodes = [
|
||||
"SxCPHairLength",
|
||||
"SxCPHairColor",
|
||||
"SxCPHairStyle",
|
||||
"SxCPCharacterAgeRange",
|
||||
"SxCPCharacterBodyPool",
|
||||
"SxCPWomanBodyPool",
|
||||
"SxCPManBodyPool",
|
||||
"SxCPEyeColorPool",
|
||||
"SxCPCharacterClothing",
|
||||
"SxCPCharacterManualDetails",
|
||||
"SxCPWomanSlot",
|
||||
"SxCPManSlot",
|
||||
"SxCPCharacterSlot",
|
||||
"SxCPCharacterProfileSave",
|
||||
"SxCPCharacterProfileLoad",
|
||||
]
|
||||
for node_name in required_nodes:
|
||||
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
|
||||
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
|
||||
|
||||
woman_slot_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPWomanSlot"]
|
||||
woman_slot_inputs = woman_slot_node.INPUT_TYPES().get("required") or {}
|
||||
_expect("slot_seed" in woman_slot_inputs, "Woman Slot lost slot_seed input")
|
||||
_expect("tooltip" in woman_slot_inputs["slot_seed"][1], "Woman Slot tooltip injection missing")
|
||||
|
||||
hair_config, hair_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPHairColor"]().build(
|
||||
"replace_axis",
|
||||
"",
|
||||
include_blonde=True,
|
||||
)
|
||||
parsed_hair = json.loads(hair_config)
|
||||
_expect(parsed_hair.get("colors") == ["blonde"], "Hair Color did not output selected blonde pool")
|
||||
_expect("colors=blonde" in hair_summary, "Hair Color summary changed unexpectedly")
|
||||
|
||||
age_config, age_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCharacterAgeRange"]().build(
|
||||
"replace_axis",
|
||||
25,
|
||||
27,
|
||||
)
|
||||
parsed_age = json.loads(age_config)
|
||||
_expect(parsed_age.get("ages") == ["25-year-old adult", "26-year-old adult", "27-year-old adult"], "Age Range output changed")
|
||||
_expect("25-year-old adult" in age_summary, "Age Range summary lost selected ages")
|
||||
|
||||
body_config, _body_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPWomanBodyPool"]().build(
|
||||
"replace_axis",
|
||||
"",
|
||||
include_curvy=True,
|
||||
)
|
||||
_expect(json.loads(body_config).get("bodies") == ["curvy"], "Woman Body Pool did not output selected body")
|
||||
|
||||
eye_config, _eye_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPEyeColorPool"]().build(
|
||||
"replace_axis",
|
||||
"",
|
||||
include_blue=True,
|
||||
)
|
||||
_expect(json.loads(eye_config).get("eyes") == ["blue"], "Eye Color Pool did not output selected eye color")
|
||||
|
||||
clothing_config, clothing_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCharacterClothing"]().build(
|
||||
"replace_axis",
|
||||
"lingerie_tease",
|
||||
"fully_nude",
|
||||
"",
|
||||
"",
|
||||
)
|
||||
parsed_clothing = json.loads(clothing_config)
|
||||
_expect(parsed_clothing.get("softcore_outfits"), "Character Clothing lost softcore outfit pool")
|
||||
_expect(parsed_clothing.get("hardcore_clothing") == ["fully nude"], "Character Clothing lost hardcore clothing state")
|
||||
_expect("soft_outfits=" in clothing_summary, "Character Clothing summary lost outfit count")
|
||||
|
||||
manual_config, manual_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCharacterManualDetails"]().build(
|
||||
"merge_nonempty",
|
||||
"31-year-old adult",
|
||||
"curvy",
|
||||
"custom body phrase",
|
||||
"warm skin",
|
||||
"short blonde hair",
|
||||
"blue eyes",
|
||||
"red dress",
|
||||
"fully nude",
|
||||
)
|
||||
parsed_manual = json.loads(manual_config)
|
||||
_expect(parsed_manual.get("manual_age") == "31-year-old adult", "Manual Details lost manual_age")
|
||||
_expect(parsed_manual.get("softcore_outfit") == "red dress", "Manual Details lost softcore outfit")
|
||||
_expect("manual_age=31-year-old adult" in manual_summary, "Manual Details summary changed unexpectedly")
|
||||
|
||||
cast, slot, slot_summary, slot_status = woman_slot_node().build(
|
||||
True,
|
||||
"A",
|
||||
123,
|
||||
"25-year-old adult",
|
||||
"western_european",
|
||||
"balanced",
|
||||
"curvy",
|
||||
"full",
|
||||
True,
|
||||
0.5,
|
||||
0.4,
|
||||
0.8,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
hair_config,
|
||||
"",
|
||||
)
|
||||
parsed_slot = json.loads(slot)
|
||||
_expect(parsed_slot.get("subject_type") == "woman", "Woman Slot output lost subject type")
|
||||
_expect(parsed_slot.get("slot_seed") == 123, "Woman Slot output lost slot seed")
|
||||
_expect("Woman A" in slot_summary, "Woman Slot summary lost label")
|
||||
_expect("1 slot(s)" in slot_status, "Woman Slot status lost cast count")
|
||||
_expect(json.loads(cast).get("slots"), "Woman Slot did not output chained cast JSON")
|
||||
|
||||
man_cast, man_slot, _man_summary, _man_status = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPManSlot"]().build(
|
||||
True,
|
||||
"A",
|
||||
124,
|
||||
"40-year-old adult",
|
||||
"western_european",
|
||||
"average",
|
||||
"compact",
|
||||
True,
|
||||
0.3,
|
||||
"pov",
|
||||
0.2,
|
||||
0.7,
|
||||
cast,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
)
|
||||
_expect(json.loads(man_slot).get("presence_mode") == "pov", "Man Slot output lost POV presence mode")
|
||||
_expect(len(json.loads(man_cast).get("slots") or []) == 2, "Man Slot did not append to incoming cast")
|
||||
|
||||
save_result = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCharacterProfileSave"]().build(
|
||||
"smoke_profile",
|
||||
"character_slot",
|
||||
"woman",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
False,
|
||||
character_slot=slot,
|
||||
)
|
||||
saved_profile = save_result["result"][0]
|
||||
_expect(save_result["result"][2] == "smoke_profile", "Profile Save lost profile name")
|
||||
_expect(save_result["result"][4] == "not_saved", "Profile Save should not write when save_now is false")
|
||||
loaded_profile = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCharacterProfileLoad"]().build(
|
||||
True,
|
||||
"manual",
|
||||
"",
|
||||
False,
|
||||
False,
|
||||
fallback_profile_json=saved_profile,
|
||||
)
|
||||
_expect(loaded_profile[4] == "fallback", "Profile Load should consume fallback profile JSON")
|
||||
_expect(json.loads(loaded_profile[0]).get("profile_type") == "character", "Profile Load returned wrong profile type")
|
||||
|
||||
|
||||
def smoke_node_profile_filter_registration() -> None:
|
||||
required_nodes = [
|
||||
"SxCPGenerationProfile",
|
||||
@@ -1955,6 +2119,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
||||
("node_utility_registration", smoke_node_utility_registration),
|
||||
("node_camera_registration", smoke_node_camera_registration),
|
||||
("node_route_config_registration", smoke_node_route_config_registration),
|
||||
("node_character_registration", smoke_node_character_registration),
|
||||
("node_profile_filter_registration", smoke_node_profile_filter_registration),
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user