Files
ComfyUI-Ethanfel-Prompt-Bui…/node_character.py
T

810 lines
29 KiB
Python

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",
}