Add chainable character characteristics nodes
This commit is contained in:
+218
@@ -11,6 +11,7 @@ except Exception:
|
|||||||
PromptServer = None
|
PromptServer = None
|
||||||
|
|
||||||
SXCP_HAIR_CONFIG = "SXCP_HAIR_CONFIG"
|
SXCP_HAIR_CONFIG = "SXCP_HAIR_CONFIG"
|
||||||
|
SXCP_CHARACTERISTICS = "SXCP_CHARACTERISTICS"
|
||||||
SXCP_CHARACTER_MANUAL = "SXCP_CHARACTER_MANUAL"
|
SXCP_CHARACTER_MANUAL = "SXCP_CHARACTER_MANUAL"
|
||||||
SXCP_ETHNICITY_LIST = "SXCP_ETHNICITY_LIST"
|
SXCP_ETHNICITY_LIST = "SXCP_ETHNICITY_LIST"
|
||||||
SXCP_FILTER_CONFIG = "SXCP_FILTER_CONFIG"
|
SXCP_FILTER_CONFIG = "SXCP_FILTER_CONFIG"
|
||||||
@@ -35,6 +36,7 @@ try:
|
|||||||
build_character_slot_json,
|
build_character_slot_json,
|
||||||
build_character_manual_config_json,
|
build_character_manual_config_json,
|
||||||
build_character_profile_json,
|
build_character_profile_json,
|
||||||
|
build_characteristics_config_json,
|
||||||
build_ethnicity_list_json,
|
build_ethnicity_list_json,
|
||||||
build_filter_config_json,
|
build_filter_config_json,
|
||||||
build_generation_profile_json,
|
build_generation_profile_json,
|
||||||
@@ -63,6 +65,7 @@ try:
|
|||||||
character_body_choices,
|
character_body_choices,
|
||||||
character_descriptor_detail_choices,
|
character_descriptor_detail_choices,
|
||||||
character_ethnicity_choices,
|
character_ethnicity_choices,
|
||||||
|
character_eye_color_choices,
|
||||||
character_figure_choices,
|
character_figure_choices,
|
||||||
character_hair_color_choices,
|
character_hair_color_choices,
|
||||||
character_hair_length_choices,
|
character_hair_length_choices,
|
||||||
@@ -71,6 +74,10 @@ try:
|
|||||||
character_man_body_choices,
|
character_man_body_choices,
|
||||||
character_presence_choices,
|
character_presence_choices,
|
||||||
character_profile_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,
|
character_woman_body_choices,
|
||||||
ethnicity_choices,
|
ethnicity_choices,
|
||||||
generation_profile_choices,
|
generation_profile_choices,
|
||||||
@@ -93,6 +100,7 @@ except ImportError:
|
|||||||
build_character_slot_json,
|
build_character_slot_json,
|
||||||
build_character_manual_config_json,
|
build_character_manual_config_json,
|
||||||
build_character_profile_json,
|
build_character_profile_json,
|
||||||
|
build_characteristics_config_json,
|
||||||
build_ethnicity_list_json,
|
build_ethnicity_list_json,
|
||||||
build_filter_config_json,
|
build_filter_config_json,
|
||||||
build_generation_profile_json,
|
build_generation_profile_json,
|
||||||
@@ -121,6 +129,7 @@ except ImportError:
|
|||||||
character_body_choices,
|
character_body_choices,
|
||||||
character_descriptor_detail_choices,
|
character_descriptor_detail_choices,
|
||||||
character_ethnicity_choices,
|
character_ethnicity_choices,
|
||||||
|
character_eye_color_choices,
|
||||||
character_figure_choices,
|
character_figure_choices,
|
||||||
character_hair_color_choices,
|
character_hair_color_choices,
|
||||||
character_hair_length_choices,
|
character_hair_length_choices,
|
||||||
@@ -129,6 +138,10 @@ except ImportError:
|
|||||||
character_man_body_choices,
|
character_man_body_choices,
|
||||||
character_presence_choices,
|
character_presence_choices,
|
||||||
character_profile_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,
|
character_woman_body_choices,
|
||||||
ethnicity_choices,
|
ethnicity_choices,
|
||||||
generation_profile_choices,
|
generation_profile_choices,
|
||||||
@@ -880,6 +893,190 @@ class SxCPHairStyle(_SxCPHairAxisNode):
|
|||||||
AXIS = "style"
|
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:
|
class SxCPCharacterManualDetails:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
@@ -1028,6 +1225,7 @@ class SxCPCharacterSlot:
|
|||||||
"optional": {
|
"optional": {
|
||||||
"manual": (SXCP_CHARACTER_MANUAL,),
|
"manual": (SXCP_CHARACTER_MANUAL,),
|
||||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||||
|
"characteristics": (SXCP_CHARACTERISTICS,),
|
||||||
"hair_config": (SXCP_HAIR_CONFIG,),
|
"hair_config": (SXCP_HAIR_CONFIG,),
|
||||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||||
},
|
},
|
||||||
@@ -1056,6 +1254,7 @@ class SxCPCharacterSlot:
|
|||||||
hardcore_expression_intensity=-1.0,
|
hardcore_expression_intensity=-1.0,
|
||||||
character_cast="",
|
character_cast="",
|
||||||
ethnicity_list="",
|
ethnicity_list="",
|
||||||
|
characteristics="",
|
||||||
hair_config="",
|
hair_config="",
|
||||||
manual="",
|
manual="",
|
||||||
):
|
):
|
||||||
@@ -1072,6 +1271,7 @@ class SxCPCharacterSlot:
|
|||||||
body_phrase="",
|
body_phrase="",
|
||||||
skin="",
|
skin="",
|
||||||
hair="",
|
hair="",
|
||||||
|
characteristics=characteristics,
|
||||||
hair_config=hair_config,
|
hair_config=hair_config,
|
||||||
eyes="",
|
eyes="",
|
||||||
descriptor_detail=descriptor_detail,
|
descriptor_detail=descriptor_detail,
|
||||||
@@ -1109,6 +1309,7 @@ class SxCPWomanSlot:
|
|||||||
"optional": {
|
"optional": {
|
||||||
"manual": (SXCP_CHARACTER_MANUAL,),
|
"manual": (SXCP_CHARACTER_MANUAL,),
|
||||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||||
|
"characteristics": (SXCP_CHARACTERISTICS,),
|
||||||
"hair_config": (SXCP_HAIR_CONFIG,),
|
"hair_config": (SXCP_HAIR_CONFIG,),
|
||||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||||
},
|
},
|
||||||
@@ -1135,6 +1336,7 @@ class SxCPWomanSlot:
|
|||||||
hardcore_expression_intensity=-1.0,
|
hardcore_expression_intensity=-1.0,
|
||||||
character_cast="",
|
character_cast="",
|
||||||
ethnicity_list="",
|
ethnicity_list="",
|
||||||
|
characteristics="",
|
||||||
hair_config="",
|
hair_config="",
|
||||||
manual="",
|
manual="",
|
||||||
):
|
):
|
||||||
@@ -1151,6 +1353,7 @@ class SxCPWomanSlot:
|
|||||||
body_phrase="",
|
body_phrase="",
|
||||||
skin="",
|
skin="",
|
||||||
hair="",
|
hair="",
|
||||||
|
characteristics=characteristics,
|
||||||
hair_config=hair_config,
|
hair_config=hair_config,
|
||||||
eyes="",
|
eyes="",
|
||||||
descriptor_detail=descriptor_detail,
|
descriptor_detail=descriptor_detail,
|
||||||
@@ -1187,6 +1390,7 @@ class SxCPManSlot:
|
|||||||
"optional": {
|
"optional": {
|
||||||
"manual": (SXCP_CHARACTER_MANUAL,),
|
"manual": (SXCP_CHARACTER_MANUAL,),
|
||||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||||
|
"characteristics": (SXCP_CHARACTERISTICS,),
|
||||||
"hair_config": (SXCP_HAIR_CONFIG,),
|
"hair_config": (SXCP_HAIR_CONFIG,),
|
||||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||||
},
|
},
|
||||||
@@ -1213,6 +1417,7 @@ class SxCPManSlot:
|
|||||||
hardcore_expression_intensity=-1.0,
|
hardcore_expression_intensity=-1.0,
|
||||||
character_cast="",
|
character_cast="",
|
||||||
ethnicity_list="",
|
ethnicity_list="",
|
||||||
|
characteristics="",
|
||||||
hair_config="",
|
hair_config="",
|
||||||
manual="",
|
manual="",
|
||||||
):
|
):
|
||||||
@@ -1229,6 +1434,7 @@ class SxCPManSlot:
|
|||||||
body_phrase="",
|
body_phrase="",
|
||||||
skin="",
|
skin="",
|
||||||
hair="",
|
hair="",
|
||||||
|
characteristics=characteristics,
|
||||||
hair_config=hair_config,
|
hair_config=hair_config,
|
||||||
eyes="",
|
eyes="",
|
||||||
descriptor_detail=descriptor_detail,
|
descriptor_detail=descriptor_detail,
|
||||||
@@ -1702,6 +1908,12 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"SxCPHairLength": SxCPHairLength,
|
"SxCPHairLength": SxCPHairLength,
|
||||||
"SxCPHairColor": SxCPHairColor,
|
"SxCPHairColor": SxCPHairColor,
|
||||||
"SxCPHairStyle": SxCPHairStyle,
|
"SxCPHairStyle": SxCPHairStyle,
|
||||||
|
"SxCPCharacterAgeRange": SxCPCharacterAgeRange,
|
||||||
|
"SxCPCharacterBodyPool": SxCPCharacterBodyPool,
|
||||||
|
"SxCPWomanBodyPool": SxCPWomanBodyPool,
|
||||||
|
"SxCPManBodyPool": SxCPManBodyPool,
|
||||||
|
"SxCPEyeColorPool": SxCPEyeColorPool,
|
||||||
|
"SxCPCharacterClothing": SxCPCharacterClothing,
|
||||||
"SxCPCharacterManualDetails": SxCPCharacterManualDetails,
|
"SxCPCharacterManualDetails": SxCPCharacterManualDetails,
|
||||||
"SxCPAdvancedFilters": SxCPAdvancedFilters,
|
"SxCPAdvancedFilters": SxCPAdvancedFilters,
|
||||||
"SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs,
|
"SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs,
|
||||||
@@ -1732,6 +1944,12 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
|||||||
"SxCPHairLength": "SxCP Hair Length",
|
"SxCPHairLength": "SxCP Hair Length",
|
||||||
"SxCPHairColor": "SxCP Hair Color",
|
"SxCPHairColor": "SxCP Hair Color",
|
||||||
"SxCPHairStyle": "SxCP Hair Style/Cut",
|
"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",
|
"SxCPCharacterManualDetails": "SxCP Character Manual Details",
|
||||||
"SxCPAdvancedFilters": "SxCP Advanced Filters",
|
"SxCPAdvancedFilters": "SxCP Advanced Filters",
|
||||||
"SxCPPromptBuilderFromConfigs": "SxCP Prompt Builder From Configs",
|
"SxCPPromptBuilderFromConfigs": "SxCP Prompt Builder From Configs",
|
||||||
|
|||||||
+277
-13
@@ -271,6 +271,27 @@ CHARACTER_HAIR_STYLE_CHOICES = [
|
|||||||
"wet_hair",
|
"wet_hair",
|
||||||
"slicked_back",
|
"slicked_back",
|
||||||
]
|
]
|
||||||
|
CHARACTER_EYE_COLOR_CHOICES = [
|
||||||
|
"random",
|
||||||
|
"blue",
|
||||||
|
"pale_blue",
|
||||||
|
"ice_blue",
|
||||||
|
"blue_gray",
|
||||||
|
"green",
|
||||||
|
"emerald_green",
|
||||||
|
"hazel",
|
||||||
|
"light_hazel",
|
||||||
|
"green_hazel",
|
||||||
|
"amber",
|
||||||
|
"amber_brown",
|
||||||
|
"honey_brown",
|
||||||
|
"brown",
|
||||||
|
"deep_brown",
|
||||||
|
"dark_brown",
|
||||||
|
"dark",
|
||||||
|
"gray",
|
||||||
|
"gray_brown",
|
||||||
|
]
|
||||||
|
|
||||||
CAMERA_DETAIL_CHOICES = ["off", "compact", "full"]
|
CAMERA_DETAIL_CHOICES = ["off", "compact", "full"]
|
||||||
HARDCORE_DETAIL_DENSITY_CHOICES = ["compact", "balanced", "dense"]
|
HARDCORE_DETAIL_DENSITY_CHOICES = ["compact", "balanced", "dense"]
|
||||||
@@ -1761,6 +1782,10 @@ def character_hair_style_choices() -> list[str]:
|
|||||||
return list(CHARACTER_HAIR_STYLE_CHOICES)
|
return list(CHARACTER_HAIR_STYLE_CHOICES)
|
||||||
|
|
||||||
|
|
||||||
|
def character_eye_color_choices() -> list[str]:
|
||||||
|
return list(CHARACTER_EYE_COLOR_CHOICES)
|
||||||
|
|
||||||
|
|
||||||
def character_ethnicity_choices() -> list[str]:
|
def character_ethnicity_choices() -> list[str]:
|
||||||
return ["random"] + list(ETHNICITY_FILTER_CHOICES)
|
return ["random"] + list(ETHNICITY_FILTER_CHOICES)
|
||||||
|
|
||||||
@@ -1777,6 +1802,31 @@ def hardcore_detail_density_choices() -> list[str]:
|
|||||||
return list(HARDCORE_DETAIL_DENSITY_CHOICES)
|
return list(HARDCORE_DETAIL_DENSITY_CHOICES)
|
||||||
|
|
||||||
|
|
||||||
|
def character_softcore_outfit_source_choices() -> list[str]:
|
||||||
|
return [
|
||||||
|
"no_change",
|
||||||
|
"social_tease",
|
||||||
|
"lingerie_tease",
|
||||||
|
"implied_nude",
|
||||||
|
"explicit_tease",
|
||||||
|
"explicit_nude",
|
||||||
|
"partner_woman",
|
||||||
|
"partner_man",
|
||||||
|
"custom",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def character_hardcore_clothing_state_choices() -> list[str]:
|
||||||
|
return [
|
||||||
|
"no_change",
|
||||||
|
"fully_nude",
|
||||||
|
"partly_exposed",
|
||||||
|
"same_outfit",
|
||||||
|
"partially_removed",
|
||||||
|
"custom",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def camera_orbit_framing_choices() -> list[str]:
|
def camera_orbit_framing_choices() -> list[str]:
|
||||||
return list(CAMERA_ORBIT_FRAMING_CHOICES)
|
return list(CAMERA_ORBIT_FRAMING_CHOICES)
|
||||||
|
|
||||||
@@ -2565,6 +2615,157 @@ def _slot_value(value: Any) -> str:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
CHARACTER_CHARACTERISTIC_AXES = {
|
||||||
|
"ages": CHARACTER_AGE_CHOICES,
|
||||||
|
"bodies": list(dict.fromkeys([*CHARACTER_BODY_CHOICES, *CHARACTER_WOMAN_BODY_CHOICES, *CHARACTER_MAN_BODY_CHOICES])),
|
||||||
|
"eyes": CHARACTER_EYE_COLOR_CHOICES,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _empty_characteristics_config() -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"config_type": "characteristics",
|
||||||
|
"ages": [],
|
||||||
|
"bodies": [],
|
||||||
|
"eyes": [],
|
||||||
|
"softcore_outfits": [],
|
||||||
|
"hardcore_clothing": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_characteristic_choice(value: Any, choices: list[str] | tuple[str, ...]) -> str:
|
||||||
|
text = str(value or "").strip()
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
normalized = re.sub(r"[^a-z0-9]+", "_", text.lower()).strip("_")
|
||||||
|
for choice in choices:
|
||||||
|
if normalized == re.sub(r"[^a-z0-9]+", "_", str(choice).lower()).strip("_"):
|
||||||
|
return str(choice)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_characteristic_values(
|
||||||
|
values: Any,
|
||||||
|
choices: list[str] | tuple[str, ...] | None = None,
|
||||||
|
*,
|
||||||
|
allow_free_text: bool = False,
|
||||||
|
) -> list[str]:
|
||||||
|
if isinstance(values, str):
|
||||||
|
raw_values = [part.strip() for part in re.split(r"[\n;]+", values) if part.strip()]
|
||||||
|
if len(raw_values) == 1 and "," in raw_values[0] and not allow_free_text:
|
||||||
|
raw_values = [part.strip() for part in raw_values[0].split(",") if part.strip()]
|
||||||
|
elif isinstance(values, (list, tuple, set)):
|
||||||
|
raw_values = list(values)
|
||||||
|
else:
|
||||||
|
raw_values = []
|
||||||
|
normalized: list[str] = []
|
||||||
|
for raw_value in raw_values:
|
||||||
|
value = str(raw_value or "").strip() if choices is None else _normalize_characteristic_choice(raw_value, choices)
|
||||||
|
if not value or value in ("random", "manual"):
|
||||||
|
continue
|
||||||
|
if value not in normalized:
|
||||||
|
normalized.append(value)
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_characteristics_config(value: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||||
|
if not value:
|
||||||
|
return _empty_characteristics_config()
|
||||||
|
if isinstance(value, dict):
|
||||||
|
raw = value
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
raw = json.loads(str(value))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return _empty_characteristics_config()
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
return _empty_characteristics_config()
|
||||||
|
return {
|
||||||
|
"config_type": "characteristics",
|
||||||
|
"ages": _normalize_characteristic_values(raw.get("ages"), CHARACTER_AGE_CHOICES),
|
||||||
|
"bodies": _normalize_characteristic_values(raw.get("bodies"), CHARACTER_CHARACTERISTIC_AXES["bodies"]),
|
||||||
|
"eyes": _normalize_characteristic_values(raw.get("eyes"), CHARACTER_EYE_COLOR_CHOICES),
|
||||||
|
"softcore_outfits": _normalize_characteristic_values(raw.get("softcore_outfits"), None, allow_free_text=True),
|
||||||
|
"hardcore_clothing": _normalize_characteristic_values(raw.get("hardcore_clothing"), None, allow_free_text=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _characteristics_summary(config: dict[str, Any]) -> str:
|
||||||
|
parts = []
|
||||||
|
for key, label in (
|
||||||
|
("ages", "ages"),
|
||||||
|
("bodies", "bodies"),
|
||||||
|
("eyes", "eyes"),
|
||||||
|
("softcore_outfits", "soft_outfits"),
|
||||||
|
("hardcore_clothing", "hard_clothing"),
|
||||||
|
):
|
||||||
|
values = config.get(key) or []
|
||||||
|
if not values:
|
||||||
|
continue
|
||||||
|
if key in ("softcore_outfits", "hardcore_clothing"):
|
||||||
|
parts.append(f"{label}={len(values)}")
|
||||||
|
else:
|
||||||
|
parts.append(f"{label}={','.join(values)}")
|
||||||
|
return "; ".join(parts) if parts else "characteristics unrestricted"
|
||||||
|
|
||||||
|
|
||||||
|
def build_characteristics_config_json(
|
||||||
|
characteristics: str | dict[str, Any] | None = "",
|
||||||
|
axis: str = "ages",
|
||||||
|
selected_values: list[str] | tuple[str, ...] | str | None = None,
|
||||||
|
combine_mode: str = "replace_axis",
|
||||||
|
) -> str:
|
||||||
|
config = _parse_characteristics_config(characteristics)
|
||||||
|
axis_key = str(axis or "").strip().lower()
|
||||||
|
if axis_key not in config:
|
||||||
|
config["summary"] = _characteristics_summary(config)
|
||||||
|
return json.dumps(config, ensure_ascii=True, sort_keys=True)
|
||||||
|
choices = CHARACTER_CHARACTERISTIC_AXES.get(axis_key)
|
||||||
|
values = _normalize_characteristic_values(
|
||||||
|
selected_values,
|
||||||
|
choices,
|
||||||
|
allow_free_text=choices is None,
|
||||||
|
)
|
||||||
|
if combine_mode == "add_to_axis":
|
||||||
|
existing = list(config.get(axis_key) or [])
|
||||||
|
for value in values:
|
||||||
|
if value not in existing:
|
||||||
|
existing.append(value)
|
||||||
|
config[axis_key] = existing
|
||||||
|
else:
|
||||||
|
config[axis_key] = values
|
||||||
|
config["summary"] = _characteristics_summary(config)
|
||||||
|
return json.dumps(config, ensure_ascii=True, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _characteristic_choice(config: dict[str, Any], key: str, rng: random.Random) -> str:
|
||||||
|
values = config.get(key) or []
|
||||||
|
return g.choose(rng, values) if values else ""
|
||||||
|
|
||||||
|
|
||||||
|
def _eye_phrase_from_key(key: str) -> str:
|
||||||
|
return {
|
||||||
|
"blue": "blue eyes",
|
||||||
|
"pale_blue": "pale blue eyes",
|
||||||
|
"ice_blue": "ice blue eyes",
|
||||||
|
"blue_gray": "blue-gray eyes",
|
||||||
|
"green": "green eyes",
|
||||||
|
"emerald_green": "emerald green eyes",
|
||||||
|
"hazel": "hazel eyes",
|
||||||
|
"light_hazel": "light hazel eyes",
|
||||||
|
"green_hazel": "green-hazel eyes",
|
||||||
|
"amber": "amber eyes",
|
||||||
|
"amber_brown": "amber-brown eyes",
|
||||||
|
"honey_brown": "honey-brown eyes",
|
||||||
|
"brown": "brown eyes",
|
||||||
|
"deep_brown": "deep brown eyes",
|
||||||
|
"dark_brown": "dark brown eyes",
|
||||||
|
"dark": "dark eyes",
|
||||||
|
"gray": "gray eyes",
|
||||||
|
"gray_brown": "gray-brown eyes",
|
||||||
|
}.get(key, "")
|
||||||
|
|
||||||
|
|
||||||
def _normalize_descriptor_detail(value: Any) -> str:
|
def _normalize_descriptor_detail(value: Any) -> str:
|
||||||
text = str(value or "auto").strip()
|
text = str(value or "auto").strip()
|
||||||
return text if text in CHARACTER_DESCRIPTOR_DETAIL_CHOICES else "auto"
|
return text if text in CHARACTER_DESCRIPTOR_DETAIL_CHOICES else "auto"
|
||||||
@@ -3143,6 +3344,11 @@ def _normalize_character_slot(slot: dict[str, Any]) -> dict[str, Any]:
|
|||||||
"skin": manual_fallback("skin"),
|
"skin": manual_fallback("skin"),
|
||||||
"hair": manual_fallback("hair"),
|
"hair": manual_fallback("hair"),
|
||||||
"manual": manual_config,
|
"manual": manual_config,
|
||||||
|
"characteristics": (
|
||||||
|
slot.get("characteristics")
|
||||||
|
if isinstance(slot.get("characteristics"), dict)
|
||||||
|
else _slot_value(slot.get("characteristics") or slot.get("characteristics_config"))
|
||||||
|
),
|
||||||
"hair_config": (
|
"hair_config": (
|
||||||
slot.get("hair_config")
|
slot.get("hair_config")
|
||||||
if isinstance(slot.get("hair_config"), dict)
|
if isinstance(slot.get("hair_config"), dict)
|
||||||
@@ -3227,6 +3433,10 @@ def _character_slot_summary(slot: dict[str, Any]) -> str:
|
|||||||
parts.append(f"soft_outfit={slot['softcore_outfit']}")
|
parts.append(f"soft_outfit={slot['softcore_outfit']}")
|
||||||
if slot.get("hardcore_clothing"):
|
if slot.get("hardcore_clothing"):
|
||||||
parts.append(f"hard_clothing={slot['hardcore_clothing']}")
|
parts.append(f"hard_clothing={slot['hardcore_clothing']}")
|
||||||
|
characteristics = _parse_characteristics_config(slot.get("characteristics"))
|
||||||
|
characteristics_summary = _characteristics_summary(characteristics)
|
||||||
|
if characteristics_summary != "characteristics unrestricted":
|
||||||
|
parts.append(f"characteristics={characteristics_summary}")
|
||||||
hair_config = _parse_hair_config(slot.get("hair_config"))
|
hair_config = _parse_hair_config(slot.get("hair_config"))
|
||||||
hair_config_summary = _hair_config_summary(hair_config)
|
hair_config_summary = _hair_config_summary(hair_config)
|
||||||
if hair_config_summary != "hair unrestricted":
|
if hair_config_summary != "hair unrestricted":
|
||||||
@@ -3256,6 +3466,7 @@ def build_character_slot_json(
|
|||||||
body_phrase: str = "",
|
body_phrase: str = "",
|
||||||
skin: str = "",
|
skin: str = "",
|
||||||
hair: str = "",
|
hair: str = "",
|
||||||
|
characteristics: str | dict[str, Any] | None = "",
|
||||||
hair_config: str | dict[str, Any] | None = "",
|
hair_config: str | dict[str, Any] | None = "",
|
||||||
hair_color: str = "random",
|
hair_color: str = "random",
|
||||||
hair_length: str = "random",
|
hair_length: str = "random",
|
||||||
@@ -3288,6 +3499,7 @@ def build_character_slot_json(
|
|||||||
"body_phrase": body_phrase,
|
"body_phrase": body_phrase,
|
||||||
"skin": skin,
|
"skin": skin,
|
||||||
"hair": hair,
|
"hair": hair,
|
||||||
|
"characteristics": characteristics,
|
||||||
"hair_config": hair_config,
|
"hair_config": hair_config,
|
||||||
"hair_color": hair_color,
|
"hair_color": hair_color,
|
||||||
"hair_length": hair_length,
|
"hair_length": hair_length,
|
||||||
@@ -3423,12 +3635,26 @@ def _body_exposure_scene_text(scene: Any) -> str:
|
|||||||
return _clean_prompt_punctuation(text)
|
return _clean_prompt_punctuation(text)
|
||||||
|
|
||||||
|
|
||||||
def _slot_softcore_outfit(slot: dict[str, Any] | None) -> str:
|
def _slot_softcore_outfit(slot: dict[str, Any] | None, rng: random.Random | None = None) -> str:
|
||||||
return _slot_value(slot.get("softcore_outfit")) if slot else ""
|
if not slot:
|
||||||
|
return ""
|
||||||
|
outfit = _slot_value(slot.get("softcore_outfit"))
|
||||||
|
if outfit:
|
||||||
|
return outfit
|
||||||
|
if rng is None:
|
||||||
|
return ""
|
||||||
|
return _characteristic_choice(_parse_characteristics_config(slot.get("characteristics")), "softcore_outfits", rng)
|
||||||
|
|
||||||
|
|
||||||
def _slot_hardcore_clothing(slot: dict[str, Any] | None) -> str:
|
def _slot_hardcore_clothing(slot: dict[str, Any] | None, rng: random.Random | None = None) -> str:
|
||||||
return _slot_value(slot.get("hardcore_clothing")) if slot else ""
|
if not slot:
|
||||||
|
return ""
|
||||||
|
clothing = _slot_value(slot.get("hardcore_clothing"))
|
||||||
|
if clothing:
|
||||||
|
return clothing
|
||||||
|
if rng is None:
|
||||||
|
return ""
|
||||||
|
return _characteristic_choice(_parse_characteristics_config(slot.get("characteristics")), "hardcore_clothing", rng)
|
||||||
|
|
||||||
|
|
||||||
def _softcore_outfit_sentence(label: str, outfit: str) -> str:
|
def _softcore_outfit_sentence(label: str, outfit: str) -> str:
|
||||||
@@ -3460,6 +3686,7 @@ def _character_hardcore_clothing_entries(
|
|||||||
women_count: int,
|
women_count: int,
|
||||||
men_count: int,
|
men_count: int,
|
||||||
pov_labels: list[str] | None = None,
|
pov_labels: list[str] | None = None,
|
||||||
|
rng: random.Random | None = None,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
pov_set = set(pov_labels or [])
|
pov_set = set(pov_labels or [])
|
||||||
labels = [
|
labels = [
|
||||||
@@ -3470,7 +3697,7 @@ def _character_hardcore_clothing_entries(
|
|||||||
for label in labels:
|
for label in labels:
|
||||||
if label in pov_set:
|
if label in pov_set:
|
||||||
continue
|
continue
|
||||||
clothing = _slot_hardcore_clothing(label_map.get(label))
|
clothing = _slot_hardcore_clothing(label_map.get(label), rng)
|
||||||
sentence = _hardcore_clothing_sentence(label, clothing)
|
sentence = _hardcore_clothing_sentence(label, clothing)
|
||||||
if sentence:
|
if sentence:
|
||||||
entries.append(sentence)
|
entries.append(sentence)
|
||||||
@@ -3502,8 +3729,11 @@ def _context_from_character_slot(
|
|||||||
effective_no_black,
|
effective_no_black,
|
||||||
)
|
)
|
||||||
|
|
||||||
age = _slot_value(slot.get("age"))
|
characteristics = _parse_characteristics_config(slot.get("characteristics"))
|
||||||
|
age = _slot_value(slot.get("age")) or _characteristic_choice(characteristics, "ages", appearance_rng)
|
||||||
body_phrase = _slot_value(slot.get("body_phrase"))
|
body_phrase = _slot_value(slot.get("body_phrase"))
|
||||||
|
if not slot_body:
|
||||||
|
slot_body = _characteristic_choice(characteristics, "bodies", appearance_rng)
|
||||||
if age:
|
if age:
|
||||||
context["age"] = age
|
context["age"] = age
|
||||||
if slot_body:
|
if slot_body:
|
||||||
@@ -3514,10 +3744,14 @@ def _context_from_character_slot(
|
|||||||
context["body_phrase"] = f"{slot_body} figure"
|
context["body_phrase"] = f"{slot_body} figure"
|
||||||
if body_phrase:
|
if body_phrase:
|
||||||
context["body_phrase"] = body_phrase
|
context["body_phrase"] = body_phrase
|
||||||
for key in ("skin", "eyes"):
|
skin_value = _slot_value(slot.get("skin"))
|
||||||
value = _slot_value(slot.get(key))
|
if skin_value:
|
||||||
if value:
|
context["skin"] = skin_value
|
||||||
context[key] = value
|
eyes_value = _slot_value(slot.get("eyes"))
|
||||||
|
if not eyes_value:
|
||||||
|
eyes_value = _eye_phrase_from_key(_characteristic_choice(characteristics, "eyes", appearance_rng))
|
||||||
|
if eyes_value:
|
||||||
|
context["eyes"] = eyes_value
|
||||||
hair_value = _slot_value(slot.get("hair"))
|
hair_value = _slot_value(slot.get("hair"))
|
||||||
if hair_value:
|
if hair_value:
|
||||||
context["hair"] = hair_value
|
context["hair"] = hair_value
|
||||||
@@ -5255,6 +5489,34 @@ INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def character_softcore_outfit_values(source: str, custom_outfits: str = "") -> list[str]:
|
||||||
|
source = str(source or "no_change").strip()
|
||||||
|
if source in INSTA_OF_SOFTCORE_OUTFITS:
|
||||||
|
return list(INSTA_OF_SOFTCORE_OUTFITS[source])
|
||||||
|
if source == "partner_woman":
|
||||||
|
return list(INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS)
|
||||||
|
if source == "partner_man":
|
||||||
|
return list(INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS)
|
||||||
|
if source == "custom":
|
||||||
|
return _normalize_characteristic_values(custom_outfits, None, allow_free_text=True)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def character_hardcore_clothing_values(state: str, custom_clothing: str = "") -> list[str]:
|
||||||
|
state = str(state or "no_change").strip()
|
||||||
|
if state == "fully_nude":
|
||||||
|
return ["fully nude"]
|
||||||
|
if state == "partly_exposed":
|
||||||
|
return ["partly nude, body exposed"]
|
||||||
|
if state == "same_outfit":
|
||||||
|
return ["keeps the teaser outfit on, with sexual contact clearly visible"]
|
||||||
|
if state == "partially_removed":
|
||||||
|
return ["teaser outfit is pushed aside and partly removed, exposing the sexual contact clearly"]
|
||||||
|
if state == "custom":
|
||||||
|
return _normalize_characteristic_values(custom_clothing, None, allow_free_text=True)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def build_insta_of_options_json(
|
def build_insta_of_options_json(
|
||||||
softcore_cast: str = "solo",
|
softcore_cast: str = "solo",
|
||||||
hardcore_cast: str = "use_counts",
|
hardcore_cast: str = "use_counts",
|
||||||
@@ -5534,7 +5796,7 @@ def _insta_of_partner_styling(
|
|||||||
for index in range(max(0, women_count - 1)):
|
for index in range(max(0, women_count - 1)):
|
||||||
label = chr(ord("B") + index)
|
label = chr(ord("B") + index)
|
||||||
full_label = f"Woman {label}"
|
full_label = f"Woman {label}"
|
||||||
outfit = _slot_softcore_outfit((label_map or {}).get(full_label)) or g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS)
|
outfit = _slot_softcore_outfit((label_map or {}).get(full_label), content_rng) or g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS)
|
||||||
sentence = _softcore_outfit_sentence(full_label, outfit)
|
sentence = _softcore_outfit_sentence(full_label, outfit)
|
||||||
if sentence:
|
if sentence:
|
||||||
outfits.append(sentence)
|
outfits.append(sentence)
|
||||||
@@ -5543,7 +5805,7 @@ def _insta_of_partner_styling(
|
|||||||
full_label = f"Man {label}"
|
full_label = f"Man {label}"
|
||||||
if full_label in pov_set:
|
if full_label in pov_set:
|
||||||
continue
|
continue
|
||||||
outfit = _slot_softcore_outfit((label_map or {}).get(full_label)) or g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS)
|
outfit = _slot_softcore_outfit((label_map or {}).get(full_label), content_rng) or g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS)
|
||||||
sentence = _softcore_outfit_sentence(full_label, outfit)
|
sentence = _softcore_outfit_sentence(full_label, outfit)
|
||||||
if sentence:
|
if sentence:
|
||||||
outfits.append(sentence)
|
outfits.append(sentence)
|
||||||
@@ -5594,6 +5856,7 @@ def build_insta_of_pair(
|
|||||||
softcore_level_key = str(options["softcore_level"])
|
softcore_level_key = str(options["softcore_level"])
|
||||||
soft_category, soft_subcategory = _insta_of_softcore_category(softcore_level_key)
|
soft_category, soft_subcategory = _insta_of_softcore_category(softcore_level_key)
|
||||||
soft_content_rng = _axis_rng(parsed_seed_config, "content", seed, row_number + 311)
|
soft_content_rng = _axis_rng(parsed_seed_config, "content", seed, row_number + 311)
|
||||||
|
hard_content_rng = _axis_rng(parsed_seed_config, "content", seed, row_number + 317)
|
||||||
soft_person_rng = _axis_rng(parsed_seed_config, "person", seed, row_number)
|
soft_person_rng = _axis_rng(parsed_seed_config, "person", seed, row_number)
|
||||||
soft_expression_women_count = hard_women_count if options["softcore_cast"] == "same_as_hardcore" else 1
|
soft_expression_women_count = hard_women_count if options["softcore_cast"] == "same_as_hardcore" else 1
|
||||||
soft_expression_men_count = hard_men_count if options["softcore_cast"] == "same_as_hardcore" else 0
|
soft_expression_men_count = hard_men_count if options["softcore_cast"] == "same_as_hardcore" else 0
|
||||||
@@ -5659,7 +5922,7 @@ def build_insta_of_pair(
|
|||||||
soft_row["character_slot_status"] = "applied:Woman A"
|
soft_row["character_slot_status"] = "applied:Woman A"
|
||||||
if not soft_expression_enabled:
|
if not soft_expression_enabled:
|
||||||
soft_row = _disable_row_expression(soft_row, soft_expression_intensity_source)
|
soft_row = _disable_row_expression(soft_row, soft_expression_intensity_source)
|
||||||
primary_softcore_outfit = _slot_softcore_outfit(primary_slot)
|
primary_softcore_outfit = _slot_softcore_outfit(primary_slot, soft_content_rng)
|
||||||
soft_row["item"] = primary_softcore_outfit or _insta_of_softcore_outfit(soft_content_rng, softcore_level_key)
|
soft_row["item"] = primary_softcore_outfit or _insta_of_softcore_outfit(soft_content_rng, softcore_level_key)
|
||||||
soft_row["pose"] = _insta_of_softcore_pose(soft_content_rng, softcore_level_key)
|
soft_row["pose"] = _insta_of_softcore_pose(soft_content_rng, softcore_level_key)
|
||||||
soft_row["item_label"] = "Insta/OF softcore body exposure" if softcore_level_key == "explicit_nude" else "Insta/OF softcore outfit"
|
soft_row["item_label"] = "Insta/OF softcore body exposure" if softcore_level_key == "explicit_nude" else "Insta/OF softcore outfit"
|
||||||
@@ -5791,6 +6054,7 @@ def build_insta_of_pair(
|
|||||||
hard_women_count,
|
hard_women_count,
|
||||||
hard_men_count,
|
hard_men_count,
|
||||||
pov_character_labels,
|
pov_character_labels,
|
||||||
|
hard_content_rng,
|
||||||
)
|
)
|
||||||
has_primary_hardcore_clothing = any(entry.startswith("Woman A") for entry in character_hardcore_clothing_entries)
|
has_primary_hardcore_clothing = any(entry.startswith("Woman A") for entry in character_hardcore_clothing_entries)
|
||||||
fallback_hard_clothing_state = "" if has_primary_hardcore_clothing else _insta_of_hardcore_clothing_state(
|
fallback_hard_clothing_state = "" if has_primary_hardcore_clothing else _insta_of_hardcore_clothing_state(
|
||||||
|
|||||||
Reference in New Issue
Block a user