1263 lines
53 KiB
Python
1263 lines
53 KiB
Python
from __future__ import annotations
|
|
|
|
import copy
|
|
import json
|
|
from typing import Any
|
|
|
|
try:
|
|
from .prompt_builder import (
|
|
INSTA_OF_HARDCORE_CLOTHING_CONTINUITY,
|
|
INSTA_OF_HARDCORE_LEVELS,
|
|
INSTA_OF_PLATFORM_STYLES,
|
|
INSTA_OF_SOFT_LEVELS,
|
|
build_camera_config_json,
|
|
build_cast_config_json,
|
|
build_category_config_json,
|
|
build_character_slot_json,
|
|
build_composition_pool_json,
|
|
build_generation_profile_json,
|
|
build_insta_of_options_json,
|
|
build_insta_of_pair,
|
|
build_location_pool_json,
|
|
build_prompt_from_configs,
|
|
camera_angle_choices,
|
|
camera_detail_choices,
|
|
camera_distance_choices,
|
|
camera_lens_choices,
|
|
camera_mode_choices,
|
|
camera_orientation_choices,
|
|
camera_phone_choices,
|
|
camera_priority_choices,
|
|
camera_shot_choices,
|
|
cast_preset_choices,
|
|
category_preset_choices,
|
|
character_age_choices,
|
|
character_body_choices,
|
|
character_descriptor_detail_choices,
|
|
character_ethnicity_choices,
|
|
character_figure_choices,
|
|
character_label_choices,
|
|
character_presence_choices,
|
|
composition_pool_preset_choices,
|
|
generation_profile_choices,
|
|
hardcore_detail_density_choices,
|
|
location_pool_preset_choices,
|
|
subcategory_choices,
|
|
)
|
|
except ImportError: # Allows local smoke tests from the repository root.
|
|
from prompt_builder import (
|
|
INSTA_OF_HARDCORE_CLOTHING_CONTINUITY,
|
|
INSTA_OF_HARDCORE_LEVELS,
|
|
INSTA_OF_PLATFORM_STYLES,
|
|
INSTA_OF_SOFT_LEVELS,
|
|
build_camera_config_json,
|
|
build_cast_config_json,
|
|
build_category_config_json,
|
|
build_character_slot_json,
|
|
build_composition_pool_json,
|
|
build_generation_profile_json,
|
|
build_insta_of_options_json,
|
|
build_insta_of_pair,
|
|
build_location_pool_json,
|
|
build_prompt_from_configs,
|
|
camera_angle_choices,
|
|
camera_detail_choices,
|
|
camera_distance_choices,
|
|
camera_lens_choices,
|
|
camera_mode_choices,
|
|
camera_orientation_choices,
|
|
camera_phone_choices,
|
|
camera_priority_choices,
|
|
camera_shot_choices,
|
|
cast_preset_choices,
|
|
category_preset_choices,
|
|
character_age_choices,
|
|
character_body_choices,
|
|
character_descriptor_detail_choices,
|
|
character_ethnicity_choices,
|
|
character_figure_choices,
|
|
character_label_choices,
|
|
character_presence_choices,
|
|
composition_pool_preset_choices,
|
|
generation_profile_choices,
|
|
hardcore_detail_density_choices,
|
|
location_pool_preset_choices,
|
|
subcategory_choices,
|
|
)
|
|
|
|
|
|
SXCP_SCENE = "SXCP_SCENE"
|
|
SXCP_SEED_CONFIG = "SXCP_SEED_CONFIG"
|
|
SXCP_CAMERA_CONFIG = "SXCP_CAMERA_CONFIG"
|
|
SXCP_LOCATION_CONFIG = "SXCP_LOCATION_CONFIG"
|
|
SXCP_COMPOSITION_CONFIG = "SXCP_COMPOSITION_CONFIG"
|
|
SXCP_CATEGORY_CONFIG = "SXCP_CATEGORY_CONFIG"
|
|
SXCP_CAST_CONFIG = "SXCP_CAST_CONFIG"
|
|
SXCP_GENERATION_PROFILE = "SXCP_GENERATION_PROFILE"
|
|
SXCP_FILTER_CONFIG = "SXCP_FILTER_CONFIG"
|
|
SXCP_ETHNICITY_LIST = "SXCP_ETHNICITY_LIST"
|
|
SXCP_CHARACTER_CAST = "SXCP_CHARACTER_CAST"
|
|
SXCP_CHARACTER_SLOT = "SXCP_CHARACTER_SLOT"
|
|
SXCP_CHARACTER_MANUAL = "SXCP_CHARACTER_MANUAL"
|
|
SXCP_CHARACTERISTICS = "SXCP_CHARACTERISTICS"
|
|
SXCP_HAIR_CONFIG = "SXCP_HAIR_CONFIG"
|
|
SXCP_HARDCORE_POSITION_CONFIG = "SXCP_HARDCORE_POSITION_CONFIG"
|
|
|
|
SCENE_SCHEMA = "sxcp_scene_v2"
|
|
TARGET_FORMATTERS = ["raw", "krea2", "sdxl", "caption"]
|
|
SCENE_KINDS = ["regular", "softcore", "hardcore"]
|
|
CENTRAL_SUBJECT_CHOICES = ["auto", "woman_a", "man_a", "none"]
|
|
POV_PARTICIPANT_CHOICES = ["none", "man_a"]
|
|
SUBJECT_LABEL_CHOICES = ["all"] + [choice for choice in character_label_choices() if choice != "auto_chain"]
|
|
BRANCH_NAMES = ("softcore", "hardcore")
|
|
SOFTCORE_CAMERA_CHOICES = ["from_camera_config"] + camera_mode_choices()
|
|
HARDCORE_CAMERA_CHOICES = ["from_camera_config", "same_as_softcore"] + camera_mode_choices()
|
|
HARDCORE_CLOTHING_CONTINUITY_CHOICES = list(INSTA_OF_HARDCORE_CLOTHING_CONTINUITY)
|
|
|
|
|
|
def _json_dict(value: str | dict[str, Any] | None) -> dict[str, Any]:
|
|
if not value:
|
|
return {}
|
|
if isinstance(value, dict):
|
|
return copy.deepcopy(value)
|
|
try:
|
|
parsed = json.loads(str(value))
|
|
except json.JSONDecodeError:
|
|
return {}
|
|
return copy.deepcopy(parsed) if isinstance(parsed, dict) else {}
|
|
|
|
|
|
def _dump(value: dict[str, Any]) -> str:
|
|
return json.dumps(value, ensure_ascii=True, sort_keys=True)
|
|
|
|
|
|
def _parse_scene(scene: str | dict[str, Any] | None) -> dict[str, Any]:
|
|
parsed = _json_dict(scene)
|
|
if parsed.get("schema") != SCENE_SCHEMA:
|
|
parsed = {}
|
|
parsed.setdefault("schema", SCENE_SCHEMA)
|
|
parsed.setdefault("version", 1)
|
|
parsed.setdefault("row_number", 1)
|
|
parsed.setdefault("start_index", 41)
|
|
parsed.setdefault("seed", 20260614)
|
|
parsed.setdefault("target_formatter", "raw")
|
|
parsed.setdefault("trigger", "sxcpinup_coloredpencil")
|
|
parsed.setdefault("prepend_trigger_to_prompt", True)
|
|
parsed.setdefault("extra_positive", "")
|
|
parsed.setdefault("extra_negative", "")
|
|
parsed.setdefault("configs", {})
|
|
parsed.setdefault("layers", {})
|
|
parsed.setdefault("branches", {})
|
|
parsed.setdefault("history", [])
|
|
return parsed
|
|
|
|
|
|
def _scene_out(scene: dict[str, Any]) -> tuple[str, str, str]:
|
|
summary = _scene_summary(scene)
|
|
return _dump(scene), summary, _dump(scene)
|
|
|
|
|
|
def _scene_summary(scene: dict[str, Any]) -> str:
|
|
layers = ",".join(sorted(scene.get("layers", {}))) or "empty"
|
|
branches = ",".join(sorted(scene.get("branches", {}))) or "none"
|
|
return (
|
|
f"scene v{scene.get('version', 1)}; row={scene.get('row_number')}; "
|
|
f"seed={scene.get('seed')}; layers={layers}; branches={branches}"
|
|
)
|
|
|
|
|
|
def _add_history(scene: dict[str, Any], node: str, summary: str) -> None:
|
|
history = scene.setdefault("history", [])
|
|
if isinstance(history, list):
|
|
history.append({"node": node, "summary": summary})
|
|
|
|
|
|
def _set_config(scene: dict[str, Any], key: str, value: Any) -> None:
|
|
if value not in (None, ""):
|
|
scene.setdefault("configs", {})[key] = value
|
|
|
|
|
|
def _set_layer(scene: dict[str, Any], layer: str, values: dict[str, Any], summary: str) -> None:
|
|
scene.setdefault("layers", {})[layer] = values
|
|
_add_history(scene, layer, summary)
|
|
|
|
|
|
def _branch(scene: dict[str, Any], name: str) -> dict[str, Any]:
|
|
branches = scene.setdefault("branches", {})
|
|
branch = branches.setdefault(name, {})
|
|
branch.setdefault("configs", {})
|
|
branch.setdefault("options", {})
|
|
branch.setdefault("extra_positive", "")
|
|
return branch
|
|
|
|
|
|
def _text_parts(*values: Any) -> list[str]:
|
|
parts: list[str] = []
|
|
for value in values:
|
|
text = str(value or "").strip()
|
|
if text and text not in parts:
|
|
parts.append(text)
|
|
return parts
|
|
|
|
|
|
def _joined_text(*values: Any) -> str:
|
|
return ". ".join(part.rstrip(".") for part in _text_parts(*values))
|
|
|
|
|
|
def _cast_slots(character_cast: Any) -> list[dict[str, Any]]:
|
|
cast = _json_dict(character_cast)
|
|
slots = cast.get("slots")
|
|
if isinstance(slots, list):
|
|
return [dict(slot) for slot in slots if isinstance(slot, dict)]
|
|
if isinstance(character_cast, list):
|
|
return [dict(slot) for slot in character_cast if isinstance(slot, dict)]
|
|
return []
|
|
|
|
|
|
def _slot_matches(slot: dict[str, Any], subject_label: str) -> bool:
|
|
if subject_label == "all":
|
|
return True
|
|
return str(slot.get("label") or "").strip().upper() == subject_label.upper()
|
|
|
|
|
|
def _update_character_cast_wardrobe(
|
|
character_cast: str | dict[str, Any] | list[Any] | None,
|
|
subject_label: str,
|
|
softcore_outfit: str,
|
|
hardcore_clothing: str,
|
|
) -> str:
|
|
slots = _cast_slots(character_cast)
|
|
if not slots:
|
|
return character_cast if isinstance(character_cast, str) else ""
|
|
changed = False
|
|
for slot in slots:
|
|
if not _slot_matches(slot, subject_label):
|
|
continue
|
|
if softcore_outfit:
|
|
slot["softcore_outfit"] = softcore_outfit
|
|
changed = True
|
|
if hardcore_clothing:
|
|
slot["hardcore_clothing"] = hardcore_clothing
|
|
changed = True
|
|
if not changed:
|
|
return character_cast if isinstance(character_cast, str) else _dump({"profile_type": "character_cast", "version": 1, "slots": slots})
|
|
return _dump({"profile_type": "character_cast", "version": 1, "slots": slots})
|
|
|
|
|
|
def _base_config(scene: dict[str, Any], key: str, default: str = "") -> str:
|
|
return str((scene.get("configs") or {}).get(key) or default or "")
|
|
|
|
|
|
def _build_generation_profile_from_scene(scene: dict[str, Any]) -> str:
|
|
existing = _base_config(scene, "generation_profile")
|
|
if existing:
|
|
profile_config = _json_dict(existing)
|
|
if profile_config:
|
|
profile_config["trigger"] = str(scene.get("trigger") or profile_config.get("trigger") or "sxcpinup_coloredpencil")
|
|
profile_config["prepend_trigger_to_prompt"] = bool(scene.get("prepend_trigger_to_prompt", True))
|
|
return _dump(profile_config)
|
|
return existing
|
|
start_profile = str(scene.get("profile") or "balanced")
|
|
wardrobe = scene.get("layers", {}).get("wardrobe", {})
|
|
performance = scene.get("layers", {}).get("performance", {})
|
|
clothing_override = str(wardrobe.get("clothing_override") or "profile_default")
|
|
expression_enabled = bool(performance.get("expression_enabled", True))
|
|
expression_mode = str(performance.get("expression_intensity_mode") or "profile_default")
|
|
expression_intensity = float(performance.get("expression_intensity", -1.0))
|
|
trigger_policy = "prepend_trigger" if scene.get("prepend_trigger_to_prompt", True) else "do_not_prepend"
|
|
profile_json = build_generation_profile_json(
|
|
profile=start_profile,
|
|
clothing_override=clothing_override,
|
|
expression_enabled=expression_enabled,
|
|
expression_intensity_mode=expression_mode,
|
|
expression_intensity=expression_intensity,
|
|
trigger_policy=trigger_policy,
|
|
)
|
|
profile_config = _json_dict(profile_json)
|
|
profile_config["trigger"] = str(scene.get("trigger") or profile_config.get("trigger") or "sxcpinup_coloredpencil")
|
|
profile_config["prepend_trigger_to_prompt"] = bool(scene.get("prepend_trigger_to_prompt", True))
|
|
return _dump(profile_config)
|
|
|
|
|
|
def _scene_extra_positive(scene: dict[str, Any], branch_name: str = "") -> str:
|
|
layers = scene.get("layers", {})
|
|
branch = scene.get("branches", {}).get(branch_name, {}) if branch_name else {}
|
|
parts = _text_parts(
|
|
scene.get("extra_positive"),
|
|
layers.get("set_dressing", {}).get("prompt"),
|
|
layers.get("blocking", {}).get("prompt"),
|
|
layers.get("action", {}).get("prompt"),
|
|
layers.get("performance", {}).get("prompt"),
|
|
layers.get("lighting", {}).get("prompt"),
|
|
branch.get("extra_positive"),
|
|
)
|
|
return ". ".join(part.rstrip(".") for part in parts)
|
|
|
|
|
|
def _compat_configs(scene: dict[str, Any], branch_name: str = "") -> dict[str, Any]:
|
|
configs = scene.get("configs") or {}
|
|
branch = scene.get("branches", {}).get(branch_name, {}) if branch_name else {}
|
|
branch_configs = branch.get("configs") or {}
|
|
category_config = branch_configs.get("category_config") or configs.get("category_config")
|
|
if not category_config:
|
|
category_config = build_category_config_json(
|
|
preset=str(scene.get("category_preset") or "auto_weighted"),
|
|
subcategory=str(scene.get("subcategory") or "random"),
|
|
)
|
|
cast_config = branch_configs.get("cast_config") or configs.get("cast_config")
|
|
if not cast_config:
|
|
cast = scene.get("layers", {}).get("cast", {})
|
|
cast_config = build_cast_config_json(
|
|
cast_mode=str(cast.get("cast_mode") or "mixed_couple"),
|
|
women_count=int(cast.get("women_count", 1)),
|
|
men_count=int(cast.get("men_count", 1)),
|
|
)
|
|
return {
|
|
"category_config": category_config,
|
|
"cast_config": cast_config,
|
|
"generation_profile": branch_configs.get("generation_profile") or _build_generation_profile_from_scene(scene),
|
|
"filter_config": branch_configs.get("filter_config") or configs.get("filter_config") or "",
|
|
"seed_config": branch_configs.get("seed_config") or configs.get("seed_config") or scene.get("seed_config") or "",
|
|
"camera_config": branch_configs.get("camera_config") or configs.get("camera_config") or "",
|
|
"location_config": branch_configs.get("location_config") or configs.get("location_config") or "",
|
|
"composition_config": branch_configs.get("composition_config") or configs.get("composition_config") or "",
|
|
"character_profile": branch_configs.get("character_profile") or configs.get("character_profile") or "",
|
|
"character_cast": branch_configs.get("character_cast") or configs.get("character_cast") or "",
|
|
"hardcore_position_config": branch_configs.get("hardcore_position_config") or configs.get("hardcore_position_config") or "",
|
|
"extra_positive": _scene_extra_positive(scene, branch_name),
|
|
"extra_negative": str(scene.get("extra_negative") or ""),
|
|
}
|
|
|
|
|
|
def _pair_options(soft_scene: dict[str, Any], hard_scene: dict[str, Any]) -> str:
|
|
soft_pair = soft_scene.get("pair", {})
|
|
hard_pair = hard_scene.get("pair", {})
|
|
soft_options = soft_scene.get("branches", {}).get("softcore", {}).get("options", {})
|
|
hard_options = hard_scene.get("branches", {}).get("hardcore", {}).get("options", {})
|
|
return build_insta_of_options_json(
|
|
softcore_cast=str(soft_options.get("softcore_cast") or "solo"),
|
|
hardcore_cast=str(hard_options.get("hardcore_cast") or "use_counts"),
|
|
hardcore_women_count=int(hard_options.get("hardcore_women_count", 1)),
|
|
hardcore_men_count=int(hard_options.get("hardcore_men_count", 1)),
|
|
softcore_level=str(soft_options.get("softcore_level") or "lingerie_tease"),
|
|
hardcore_level=str(hard_options.get("hardcore_level") or "hardcore"),
|
|
platform_style=str(hard_pair.get("platform_style") or soft_pair.get("platform_style") or "hybrid"),
|
|
continuity=str(hard_pair.get("continuity") or soft_pair.get("continuity") or "same_creator_same_room"),
|
|
hardcore_clothing_continuity=str(hard_options.get("hardcore_clothing_continuity") or "explicit_nude"),
|
|
softcore_camera_mode=str(soft_options.get("softcore_camera_mode") or "from_camera_config"),
|
|
hardcore_camera_mode=str(hard_options.get("hardcore_camera_mode") or "from_camera_config"),
|
|
camera_detail=str(hard_options.get("camera_detail") or soft_options.get("camera_detail") or "from_camera_config"),
|
|
softcore_expression_enabled=bool(soft_options.get("softcore_expression_enabled", True)),
|
|
hardcore_expression_enabled=bool(hard_options.get("hardcore_expression_enabled", True)),
|
|
softcore_expression_intensity=float(soft_options.get("softcore_expression_intensity", 0.45)),
|
|
hardcore_expression_intensity=float(hard_options.get("hardcore_expression_intensity", 0.85)),
|
|
hardcore_detail_density=str(hard_options.get("hardcore_detail_density") or "balanced"),
|
|
)
|
|
|
|
|
|
class SxCPSceneStart:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"row_number": ("INT", {"default": 1, "min": 1, "max": 1000000, "step": 1}),
|
|
"start_index": ("INT", {"default": 41, "min": 1, "max": 1000000, "step": 1}),
|
|
"seed": ("INT", {"default": 20260614, "min": 0, "max": 0xFFFFFFFF, "step": 1}),
|
|
"target_formatter": (TARGET_FORMATTERS, {"default": "raw"}),
|
|
"category_preset": (category_preset_choices(), {"default": "auto_weighted"}),
|
|
"subcategory": (subcategory_choices(), {"default": "random"}),
|
|
"profile": (generation_profile_choices(), {"default": "balanced"}),
|
|
"trigger": ("STRING", {"default": "sxcpinup_coloredpencil"}),
|
|
"prepend_trigger_to_prompt": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {
|
|
"seed_config": (SXCP_SEED_CONFIG,),
|
|
"category_config": (SXCP_CATEGORY_CONFIG,),
|
|
"generation_profile": (SXCP_GENERATION_PROFILE,),
|
|
"filter_config": (SXCP_FILTER_CONFIG,),
|
|
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
|
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(
|
|
self,
|
|
row_number,
|
|
start_index,
|
|
seed,
|
|
target_formatter,
|
|
category_preset,
|
|
subcategory,
|
|
profile,
|
|
trigger,
|
|
prepend_trigger_to_prompt,
|
|
seed_config="",
|
|
category_config="",
|
|
generation_profile="",
|
|
filter_config="",
|
|
extra_positive="",
|
|
extra_negative="",
|
|
):
|
|
scene = _parse_scene("")
|
|
scene.update(
|
|
{
|
|
"row_number": int(row_number),
|
|
"start_index": int(start_index),
|
|
"seed": int(seed),
|
|
"target_formatter": target_formatter if target_formatter in TARGET_FORMATTERS else "raw",
|
|
"category_preset": category_preset,
|
|
"subcategory": subcategory,
|
|
"profile": profile,
|
|
"trigger": str(trigger or ""),
|
|
"prepend_trigger_to_prompt": bool(prepend_trigger_to_prompt),
|
|
"extra_positive": extra_positive or "",
|
|
"extra_negative": extra_negative or "",
|
|
}
|
|
)
|
|
_set_config(scene, "seed_config", seed_config)
|
|
_set_config(scene, "category_config", category_config)
|
|
_set_config(scene, "generation_profile", generation_profile)
|
|
_set_config(scene, "filter_config", filter_config)
|
|
_add_history(scene, "scene_start", f"{category_preset}/{subcategory}; {profile}")
|
|
return _scene_out(scene)
|
|
|
|
|
|
class SxCPSceneCast:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"cast_mode": (cast_preset_choices(), {"default": "mixed_couple"}),
|
|
"women_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
|
"men_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
|
"central_subject": (CENTRAL_SUBJECT_CHOICES, {"default": "auto"}),
|
|
"pov_participant": (POV_PARTICIPANT_CHOICES, {"default": "none"}),
|
|
},
|
|
"optional": {
|
|
"cast_config": (SXCP_CAST_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, SXCP_CAST_CONFIG, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "cast_config", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, scene, cast_mode, women_count, men_count, central_subject, pov_participant, cast_config=""):
|
|
parsed = _parse_scene(scene)
|
|
config = cast_config or build_cast_config_json(cast_mode, women_count, men_count)
|
|
_set_config(parsed, "cast_config", config)
|
|
layer = {
|
|
"cast_mode": cast_mode,
|
|
"women_count": int(women_count),
|
|
"men_count": int(men_count),
|
|
"central_subject": central_subject,
|
|
"pov_participant": pov_participant,
|
|
}
|
|
summary = f"{women_count} women, {men_count} men; central={central_subject}; pov={pov_participant}"
|
|
_set_layer(parsed, "cast", layer, summary)
|
|
return _dump(parsed), config, summary, _dump(parsed)
|
|
|
|
|
|
class SxCPSceneCharacter:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"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,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, SXCP_CHARACTER_CAST, SXCP_CHARACTER_SLOT, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "character_cast", "character_slot", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(
|
|
self,
|
|
scene,
|
|
enabled,
|
|
subject_type,
|
|
label,
|
|
slot_seed,
|
|
age,
|
|
ethnicity,
|
|
figure,
|
|
body,
|
|
descriptor_detail,
|
|
expression_enabled,
|
|
expression_intensity,
|
|
presence_mode,
|
|
softcore_expression_intensity,
|
|
hardcore_expression_intensity,
|
|
manual="",
|
|
ethnicity_list="",
|
|
characteristics="",
|
|
hair_config="",
|
|
):
|
|
parsed = _parse_scene(scene)
|
|
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,
|
|
characteristics=characteristics,
|
|
hair_config=hair_config,
|
|
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,
|
|
enabled=enabled,
|
|
character_cast=_base_config(parsed, "character_cast"),
|
|
)
|
|
_set_config(parsed, "character_cast", result["character_cast"])
|
|
summary = result["summary"] if enabled else "character disabled"
|
|
_add_history(parsed, "character", summary)
|
|
return _dump(parsed), result["character_cast"], result["character_slot"], summary, _dump(parsed)
|
|
|
|
|
|
class SxCPSceneWardrobe:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"subject_label": (SUBJECT_LABEL_CHOICES, {"default": "all"}),
|
|
"clothing_override": (["profile_default", "random", "full", "minimal"], {"default": "profile_default"}),
|
|
"softcore_outfit": ("STRING", {"default": "", "multiline": True}),
|
|
"hardcore_clothing": ("STRING", {"default": "", "multiline": True}),
|
|
"wardrobe_prompt": ("STRING", {"default": "", "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, SXCP_CHARACTER_CAST, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "character_cast", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, scene, enabled, subject_label, clothing_override, softcore_outfit, hardcore_clothing, wardrobe_prompt):
|
|
parsed = _parse_scene(scene)
|
|
current_cast = _base_config(parsed, "character_cast")
|
|
if enabled:
|
|
updated_cast = _update_character_cast_wardrobe(current_cast, subject_label, softcore_outfit, hardcore_clothing)
|
|
if updated_cast:
|
|
_set_config(parsed, "character_cast", updated_cast)
|
|
else:
|
|
updated_cast = current_cast
|
|
layer = {
|
|
"enabled": bool(enabled),
|
|
"subject_label": subject_label,
|
|
"clothing_override": clothing_override,
|
|
"softcore_outfit": softcore_outfit or "",
|
|
"hardcore_clothing": hardcore_clothing or "",
|
|
"prompt": wardrobe_prompt or "",
|
|
}
|
|
summary = "disabled" if not enabled else f"{subject_label}; {clothing_override}"
|
|
_set_layer(parsed, "wardrobe", layer, summary)
|
|
return _dump(parsed), updated_cast or "", summary, _dump(parsed)
|
|
|
|
|
|
class SxCPSceneLocation:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"combine_mode": (["replace", "add"], {"default": "replace"}),
|
|
"preset": (location_pool_preset_choices(), {"default": "custom_only"}),
|
|
"custom_location": ("STRING", {"default": "", "multiline": True}),
|
|
"location_note": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
"optional": {
|
|
"location_config": (SXCP_LOCATION_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, SXCP_LOCATION_CONFIG, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "location_config", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, scene, enabled, combine_mode, preset, custom_location, location_note, location_config=""):
|
|
parsed = _parse_scene(scene)
|
|
custom = "\n".join(_text_parts(custom_location, location_note))
|
|
config = build_location_pool_json(
|
|
enabled=enabled,
|
|
combine_mode=combine_mode,
|
|
preset=preset,
|
|
custom_locations=custom,
|
|
location_config=location_config or _base_config(parsed, "location_config"),
|
|
)
|
|
_set_config(parsed, "location_config", config)
|
|
config_summary = _json_dict(config).get("summary", "")
|
|
_set_layer(parsed, "location", {"preset": preset, "custom_location": custom, "summary": config_summary}, config_summary)
|
|
return _dump(parsed), config, config_summary, _dump(parsed)
|
|
|
|
|
|
class SxCPSceneSetDressing:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"foreground_anchors": ("STRING", {"default": "", "multiline": True}),
|
|
"repeated_background": ("STRING", {"default": "", "multiline": True}),
|
|
"props": ("STRING", {"default": "", "multiline": True}),
|
|
"set_prompt": ("STRING", {"default": "", "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, scene, enabled, foreground_anchors, repeated_background, props, set_prompt):
|
|
parsed = _parse_scene(scene)
|
|
prompt = _joined_text(foreground_anchors, repeated_background, props, set_prompt) if enabled else ""
|
|
summary = "set dressing disabled" if not enabled else (prompt[:120] or "set dressing empty")
|
|
_set_layer(
|
|
parsed,
|
|
"set_dressing",
|
|
{
|
|
"enabled": bool(enabled),
|
|
"foreground_anchors": foreground_anchors or "",
|
|
"repeated_background": repeated_background or "",
|
|
"props": props or "",
|
|
"prompt": prompt,
|
|
},
|
|
summary,
|
|
)
|
|
return _scene_out(parsed)
|
|
|
|
|
|
class SxCPSceneBlocking:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"blocking_mode": (["auto", "standing", "sitting", "kneeling", "lying", "bent_over", "custom"], {"default": "auto"}),
|
|
"subject_placement": ("STRING", {"default": "", "multiline": True}),
|
|
"body_relation": ("STRING", {"default": "", "multiline": True}),
|
|
"custom_blocking": ("STRING", {"default": "", "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, scene, enabled, blocking_mode, subject_placement, body_relation, custom_blocking):
|
|
parsed = _parse_scene(scene)
|
|
prompt = _joined_text(subject_placement, body_relation, custom_blocking) if enabled else ""
|
|
summary = "blocking disabled" if not enabled else f"{blocking_mode}: {prompt[:100] or 'auto'}"
|
|
_set_layer(
|
|
parsed,
|
|
"blocking",
|
|
{
|
|
"enabled": bool(enabled),
|
|
"blocking_mode": blocking_mode,
|
|
"subject_placement": subject_placement or "",
|
|
"body_relation": body_relation or "",
|
|
"prompt": prompt,
|
|
},
|
|
summary,
|
|
)
|
|
return _scene_out(parsed)
|
|
|
|
|
|
class SxCPSceneAction:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"scene_kind": (SCENE_KINDS, {"default": "regular"}),
|
|
"category_preset": (["no_change"] + category_preset_choices(), {"default": "no_change"}),
|
|
"action_prompt": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
"optional": {
|
|
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, SXCP_HARDCORE_POSITION_CONFIG, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "hardcore_position_config", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, scene, enabled, scene_kind, category_preset, action_prompt, hardcore_position_config=""):
|
|
parsed = _parse_scene(scene)
|
|
if enabled and category_preset != "no_change":
|
|
_set_config(parsed, "category_config", build_category_config_json(category_preset, "random"))
|
|
elif enabled and scene_kind == "hardcore" and not _base_config(parsed, "category_config"):
|
|
_set_config(parsed, "category_config", build_category_config_json("hardcore_pose", "random"))
|
|
if hardcore_position_config:
|
|
_set_config(parsed, "hardcore_position_config", hardcore_position_config)
|
|
layer = {
|
|
"enabled": bool(enabled),
|
|
"scene_kind": scene_kind,
|
|
"category_preset": category_preset,
|
|
"prompt": action_prompt or "",
|
|
}
|
|
summary = "action disabled" if not enabled else f"{scene_kind}; category={category_preset}"
|
|
_set_layer(parsed, "action", layer, summary)
|
|
return _dump(parsed), hardcore_position_config or _base_config(parsed, "hardcore_position_config"), summary, _dump(parsed)
|
|
|
|
|
|
class SxCPScenePerformance:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"expression_enabled": ("BOOLEAN", {"default": True}),
|
|
"expression_intensity_mode": (["profile_default", "random", "fixed"], {"default": "profile_default"}),
|
|
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
"performance_prompt": ("STRING", {"default": "", "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, scene, expression_enabled, expression_intensity_mode, expression_intensity, performance_prompt):
|
|
parsed = _parse_scene(scene)
|
|
layer = {
|
|
"expression_enabled": bool(expression_enabled),
|
|
"expression_intensity_mode": expression_intensity_mode,
|
|
"expression_intensity": float(expression_intensity),
|
|
"prompt": performance_prompt or "",
|
|
}
|
|
summary = "expression disabled" if not expression_enabled else f"expression {expression_intensity_mode}"
|
|
_set_layer(parsed, "performance", layer, summary)
|
|
return _scene_out(parsed)
|
|
|
|
|
|
class SxCPSceneCamera:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"camera_mode": (camera_mode_choices(), {"default": "standard"}),
|
|
"shot_size": (camera_shot_choices(), {"default": "auto"}),
|
|
"angle": (camera_angle_choices(), {"default": "auto"}),
|
|
"lens": (camera_lens_choices(), {"default": "auto"}),
|
|
"distance": (camera_distance_choices(), {"default": "auto"}),
|
|
"orientation": (camera_orientation_choices(), {"default": "auto"}),
|
|
"phone_visibility": (camera_phone_choices(), {"default": "auto"}),
|
|
"priority": (camera_priority_choices(), {"default": "strong"}),
|
|
"camera_detail": (camera_detail_choices(), {"default": "compact"}),
|
|
"camera_prompt": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
"optional": {
|
|
"camera_config": (SXCP_CAMERA_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, SXCP_CAMERA_CONFIG, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "camera_config", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(
|
|
self,
|
|
scene,
|
|
enabled,
|
|
camera_mode,
|
|
shot_size,
|
|
angle,
|
|
lens,
|
|
distance,
|
|
orientation,
|
|
phone_visibility,
|
|
priority,
|
|
camera_detail,
|
|
camera_prompt,
|
|
camera_config="",
|
|
):
|
|
parsed = _parse_scene(scene)
|
|
config = ""
|
|
if enabled:
|
|
config = camera_config or build_camera_config_json(
|
|
camera_mode=camera_mode,
|
|
shot_size=shot_size,
|
|
angle=angle,
|
|
lens=lens,
|
|
distance=distance,
|
|
orientation=orientation,
|
|
phone_visibility=phone_visibility,
|
|
priority=priority,
|
|
camera_detail=camera_detail,
|
|
)
|
|
_set_config(parsed, "camera_config", config)
|
|
layer = {
|
|
"enabled": bool(enabled),
|
|
"camera_mode": camera_mode,
|
|
"shot_size": shot_size,
|
|
"angle": angle,
|
|
"lens": lens,
|
|
"distance": distance,
|
|
"orientation": orientation,
|
|
"phone_visibility": phone_visibility,
|
|
"priority": priority,
|
|
"camera_detail": camera_detail,
|
|
"prompt": camera_prompt or "",
|
|
}
|
|
summary = "camera disabled" if not enabled else f"{camera_mode}; {shot_size}; {angle}"
|
|
_set_layer(parsed, "camera", layer, summary)
|
|
return _dump(parsed), config, summary, _dump(parsed)
|
|
|
|
|
|
class SxCPSceneComposition:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"combine_mode": (["replace", "add"], {"default": "replace"}),
|
|
"preset": (composition_pool_preset_choices(), {"default": "no_outfit_check"}),
|
|
"custom_composition": ("STRING", {"default": "", "multiline": True}),
|
|
"composition_prompt": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
"optional": {
|
|
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, SXCP_COMPOSITION_CONFIG, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "composition_config", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, scene, enabled, combine_mode, preset, custom_composition, composition_prompt, composition_config=""):
|
|
parsed = _parse_scene(scene)
|
|
custom = "\n".join(_text_parts(custom_composition, composition_prompt))
|
|
config = build_composition_pool_json(
|
|
enabled=enabled,
|
|
combine_mode=combine_mode,
|
|
preset=preset,
|
|
custom_compositions=custom,
|
|
composition_config=composition_config or _base_config(parsed, "composition_config"),
|
|
)
|
|
_set_config(parsed, "composition_config", config)
|
|
config_summary = _json_dict(config).get("summary", "")
|
|
_set_layer(parsed, "composition", {"preset": preset, "custom_composition": custom, "summary": config_summary}, config_summary)
|
|
return _dump(parsed), config, config_summary, _dump(parsed)
|
|
|
|
|
|
class SxCPSceneLighting:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"lighting_source": (["auto", "daylight", "window_light", "practical_lamps", "neon", "studio", "phone_flash", "custom"], {"default": "auto"}),
|
|
"lighting_softness": (["auto", "soft", "balanced", "hard"], {"default": "auto"}),
|
|
"lighting_contrast": (["auto", "low", "medium", "high"], {"default": "auto"}),
|
|
"color_temperature": (["auto", "warm", "neutral", "cool", "mixed"], {"default": "auto"}),
|
|
"custom_lighting": ("STRING", {"default": "", "multiline": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(
|
|
self,
|
|
scene,
|
|
enabled,
|
|
lighting_source,
|
|
lighting_softness,
|
|
lighting_contrast,
|
|
color_temperature,
|
|
custom_lighting,
|
|
):
|
|
parsed = _parse_scene(scene)
|
|
generated = " ".join(
|
|
value.replace("_", " ")
|
|
for value in (lighting_softness, lighting_contrast, color_temperature, lighting_source)
|
|
if value and value != "auto"
|
|
).strip()
|
|
prompt = _joined_text(generated, custom_lighting) if enabled else ""
|
|
summary = "lighting disabled" if not enabled else (prompt or "lighting auto")
|
|
_set_layer(
|
|
parsed,
|
|
"lighting",
|
|
{
|
|
"enabled": bool(enabled),
|
|
"lighting_source": lighting_source,
|
|
"lighting_softness": lighting_softness,
|
|
"lighting_contrast": lighting_contrast,
|
|
"color_temperature": color_temperature,
|
|
"prompt": prompt,
|
|
},
|
|
summary,
|
|
)
|
|
return _scene_out(parsed)
|
|
|
|
|
|
class SxCPSceneBranchPair:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"continuity": (["same_creator_same_room", "same_creator_new_scene"], {"default": "same_creator_same_room"}),
|
|
"platform_style": (list(INSTA_OF_PLATFORM_STYLES), {"default": "hybrid"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, SXCP_SCENE, "STRING", "STRING")
|
|
RETURN_NAMES = ("softcore_scene", "hardcore_scene", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, scene, continuity, platform_style):
|
|
soft_scene = _parse_scene(scene)
|
|
hard_scene = _parse_scene(scene)
|
|
for branch_name, branch_scene in (("softcore", soft_scene), ("hardcore", hard_scene)):
|
|
branch_scene["active_branch"] = branch_name
|
|
branch_scene["pair"] = {"continuity": continuity, "platform_style": platform_style}
|
|
_branch(branch_scene, branch_name)
|
|
_add_history(branch_scene, "branch_pair", f"{branch_name}; {continuity}; {platform_style}")
|
|
summary = f"pair branch; {continuity}; {platform_style}"
|
|
metadata = {"softcore_scene": soft_scene, "hardcore_scene": hard_scene, "summary": summary}
|
|
return _dump(soft_scene), _dump(hard_scene), summary, _dump(metadata)
|
|
|
|
|
|
class SxCPSoftcoreBranchOptions:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"softcore_cast": (["solo", "same_as_hardcore"], {"default": "solo"}),
|
|
"softcore_level": (list(INSTA_OF_SOFT_LEVELS), {"default": "lingerie_tease"}),
|
|
"softcore_expression_enabled": ("BOOLEAN", {"default": True}),
|
|
"softcore_expression_intensity": ("FLOAT", {"default": 0.45, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"softcore_camera_mode": (SOFTCORE_CAMERA_CHOICES, {"default": "from_camera_config"}),
|
|
"camera_detail": (["from_camera_config"] + camera_detail_choices(), {"default": "from_camera_config"}),
|
|
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
"optional": {
|
|
"softcore_camera_config": (SXCP_CAMERA_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(
|
|
self,
|
|
scene,
|
|
softcore_cast,
|
|
softcore_level,
|
|
softcore_expression_enabled,
|
|
softcore_expression_intensity,
|
|
softcore_camera_mode,
|
|
camera_detail,
|
|
extra_positive,
|
|
softcore_camera_config="",
|
|
):
|
|
parsed = _parse_scene(scene)
|
|
branch = _branch(parsed, "softcore")
|
|
branch["options"].update(
|
|
{
|
|
"softcore_cast": softcore_cast,
|
|
"softcore_level": softcore_level,
|
|
"softcore_expression_enabled": bool(softcore_expression_enabled),
|
|
"softcore_expression_intensity": float(softcore_expression_intensity),
|
|
"softcore_camera_mode": softcore_camera_mode,
|
|
"camera_detail": camera_detail,
|
|
}
|
|
)
|
|
branch["extra_positive"] = extra_positive or ""
|
|
if softcore_camera_config:
|
|
branch["configs"]["camera_config"] = softcore_camera_config
|
|
summary = f"softcore {softcore_level}; cast={softcore_cast}; camera={softcore_camera_mode}"
|
|
_add_history(parsed, "softcore_branch_options", summary)
|
|
return _dump(parsed), summary, _dump(parsed)
|
|
|
|
|
|
class SxCPHardcoreBranchOptions:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
"hardcore_cast": (["use_counts", "couple", "threesome", "group"], {"default": "use_counts"}),
|
|
"hardcore_women_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
|
"hardcore_men_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
|
"hardcore_level": (list(INSTA_OF_HARDCORE_LEVELS), {"default": "hardcore"}),
|
|
"hardcore_expression_enabled": ("BOOLEAN", {"default": True}),
|
|
"hardcore_expression_intensity": ("FLOAT", {"default": 0.85, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"hardcore_clothing_continuity": (HARDCORE_CLOTHING_CONTINUITY_CHOICES, {"default": "explicit_nude"}),
|
|
"hardcore_camera_mode": (HARDCORE_CAMERA_CHOICES, {"default": "from_camera_config"}),
|
|
"camera_detail": (["from_camera_config"] + camera_detail_choices(), {"default": "from_camera_config"}),
|
|
"hardcore_detail_density": (hardcore_detail_density_choices(), {"default": "balanced"}),
|
|
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
"optional": {
|
|
"hardcore_camera_config": (SXCP_CAMERA_CONFIG,),
|
|
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SCENE, SXCP_HARDCORE_POSITION_CONFIG, "STRING", "STRING")
|
|
RETURN_NAMES = ("scene", "hardcore_position_config", "summary", "metadata_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(
|
|
self,
|
|
scene,
|
|
hardcore_cast,
|
|
hardcore_women_count,
|
|
hardcore_men_count,
|
|
hardcore_level,
|
|
hardcore_expression_enabled,
|
|
hardcore_expression_intensity,
|
|
hardcore_clothing_continuity,
|
|
hardcore_camera_mode,
|
|
camera_detail,
|
|
hardcore_detail_density,
|
|
extra_positive,
|
|
hardcore_camera_config="",
|
|
hardcore_position_config="",
|
|
):
|
|
parsed = _parse_scene(scene)
|
|
branch = _branch(parsed, "hardcore")
|
|
branch["options"].update(
|
|
{
|
|
"hardcore_cast": hardcore_cast,
|
|
"hardcore_women_count": int(hardcore_women_count),
|
|
"hardcore_men_count": int(hardcore_men_count),
|
|
"hardcore_level": hardcore_level,
|
|
"hardcore_expression_enabled": bool(hardcore_expression_enabled),
|
|
"hardcore_expression_intensity": float(hardcore_expression_intensity),
|
|
"hardcore_clothing_continuity": hardcore_clothing_continuity,
|
|
"hardcore_camera_mode": hardcore_camera_mode,
|
|
"camera_detail": camera_detail,
|
|
"hardcore_detail_density": hardcore_detail_density,
|
|
}
|
|
)
|
|
branch["extra_positive"] = extra_positive or ""
|
|
if hardcore_camera_config:
|
|
branch["configs"]["camera_config"] = hardcore_camera_config
|
|
if hardcore_position_config:
|
|
branch["configs"]["hardcore_position_config"] = hardcore_position_config
|
|
summary = f"hardcore {hardcore_level}; cast={hardcore_cast}; camera={hardcore_camera_mode}"
|
|
_add_history(parsed, "hardcore_branch_options", summary)
|
|
return _dump(parsed), hardcore_position_config or _base_config(parsed, "hardcore_position_config"), summary, _dump(parsed)
|
|
|
|
|
|
class SxCPSceneOutput:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"scene": (SXCP_SCENE,),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", SXCP_SCENE, "STRING", "STRING")
|
|
RETURN_NAMES = ("prompt", "negative_prompt", "caption", "metadata_json", "scene", "category", "subcategory")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, scene):
|
|
parsed = _parse_scene(scene)
|
|
configs = _compat_configs(parsed, str(parsed.get("active_branch") or ""))
|
|
row = build_prompt_from_configs(
|
|
row_number=int(parsed.get("row_number", 1)),
|
|
start_index=int(parsed.get("start_index", 41)),
|
|
seed=int(parsed.get("seed", 20260614)),
|
|
category_config=configs["category_config"],
|
|
cast_config=configs["cast_config"],
|
|
generation_profile=configs["generation_profile"],
|
|
filter_config=configs["filter_config"],
|
|
seed_config=configs["seed_config"],
|
|
camera_config=configs["camera_config"],
|
|
character_profile=configs["character_profile"],
|
|
character_cast=configs["character_cast"],
|
|
hardcore_position_config=configs["hardcore_position_config"],
|
|
location_config=configs["location_config"],
|
|
composition_config=configs["composition_config"],
|
|
extra_positive=configs["extra_positive"],
|
|
extra_negative=configs["extra_negative"],
|
|
)
|
|
row = dict(row)
|
|
row["scene_chain"] = parsed
|
|
metadata = _dump(row)
|
|
return (
|
|
row["prompt"],
|
|
row["negative_prompt"],
|
|
row["caption"],
|
|
metadata,
|
|
_dump(parsed),
|
|
row.get("main_category", ""),
|
|
row.get("subcategory", ""),
|
|
)
|
|
|
|
|
|
class SxCPScenePairOutput:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"softcore_scene": (SXCP_SCENE,),
|
|
"hardcore_scene": (SXCP_SCENE,),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", SXCP_SCENE)
|
|
RETURN_NAMES = (
|
|
"softcore_prompt",
|
|
"hardcore_prompt",
|
|
"softcore_negative_prompt",
|
|
"hardcore_negative_prompt",
|
|
"softcore_caption",
|
|
"hardcore_caption",
|
|
"shared_descriptor",
|
|
"metadata_json",
|
|
"scene_metadata_json",
|
|
)
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/v2_scene"
|
|
|
|
def build(self, softcore_scene, hardcore_scene):
|
|
soft_scene = _parse_scene(softcore_scene)
|
|
hard_scene = _parse_scene(hardcore_scene)
|
|
base_configs = _compat_configs(soft_scene, "softcore")
|
|
hard_configs = _compat_configs(hard_scene, "hardcore")
|
|
options_json = _pair_options(soft_scene, hard_scene)
|
|
row = build_insta_of_pair(
|
|
row_number=int(soft_scene.get("row_number", 1)),
|
|
start_index=int(soft_scene.get("start_index", 41)),
|
|
seed=int(soft_scene.get("seed", 20260614)),
|
|
ethnicity="any",
|
|
figure="random",
|
|
no_plus_women=False,
|
|
no_black=False,
|
|
trigger=str(soft_scene.get("trigger") or "sxcpinup_coloredpencil"),
|
|
prepend_trigger_to_prompt=bool(soft_scene.get("prepend_trigger_to_prompt", True)),
|
|
seed_config=base_configs["seed_config"] or hard_configs["seed_config"],
|
|
options_json=options_json,
|
|
filter_config=base_configs["filter_config"] or hard_configs["filter_config"],
|
|
camera_config=base_configs["camera_config"],
|
|
softcore_camera_config=base_configs["camera_config"],
|
|
hardcore_camera_config=hard_configs["camera_config"],
|
|
character_profile=base_configs["character_profile"] or hard_configs["character_profile"],
|
|
character_cast=base_configs["character_cast"] or hard_configs["character_cast"],
|
|
hardcore_position_config=hard_configs["hardcore_position_config"],
|
|
location_config=base_configs["location_config"] or hard_configs["location_config"],
|
|
composition_config=base_configs["composition_config"] or hard_configs["composition_config"],
|
|
extra_positive=_joined_text(base_configs["extra_positive"], hard_configs["extra_positive"]),
|
|
extra_negative=base_configs["extra_negative"] or hard_configs["extra_negative"],
|
|
)
|
|
row = dict(row)
|
|
row["scene_chain"] = {"softcore": soft_scene, "hardcore": hard_scene}
|
|
metadata = _dump(row)
|
|
return (
|
|
row["softcore_prompt"],
|
|
row["hardcore_prompt"],
|
|
row["softcore_negative_prompt"],
|
|
row["hardcore_negative_prompt"],
|
|
row["softcore_caption"],
|
|
row["hardcore_caption"],
|
|
row["shared_descriptor"],
|
|
metadata,
|
|
_dump({"softcore": soft_scene, "hardcore": hard_scene}),
|
|
)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"SxCPSceneStart": SxCPSceneStart,
|
|
"SxCPSceneCast": SxCPSceneCast,
|
|
"SxCPSceneCharacter": SxCPSceneCharacter,
|
|
"SxCPSceneWardrobe": SxCPSceneWardrobe,
|
|
"SxCPSceneLocation": SxCPSceneLocation,
|
|
"SxCPSceneSetDressing": SxCPSceneSetDressing,
|
|
"SxCPSceneBlocking": SxCPSceneBlocking,
|
|
"SxCPSceneAction": SxCPSceneAction,
|
|
"SxCPScenePerformance": SxCPScenePerformance,
|
|
"SxCPSceneCamera": SxCPSceneCamera,
|
|
"SxCPSceneComposition": SxCPSceneComposition,
|
|
"SxCPSceneLighting": SxCPSceneLighting,
|
|
"SxCPSceneBranchPair": SxCPSceneBranchPair,
|
|
"SxCPSoftcoreBranchOptions": SxCPSoftcoreBranchOptions,
|
|
"SxCPHardcoreBranchOptions": SxCPHardcoreBranchOptions,
|
|
"SxCPSceneOutput": SxCPSceneOutput,
|
|
"SxCPScenePairOutput": SxCPScenePairOutput,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"SxCPSceneStart": "SxCP Scene Start",
|
|
"SxCPSceneCast": "SxCP Scene Cast",
|
|
"SxCPSceneCharacter": "SxCP Scene Character",
|
|
"SxCPSceneWardrobe": "SxCP Scene Wardrobe",
|
|
"SxCPSceneLocation": "SxCP Scene Location",
|
|
"SxCPSceneSetDressing": "SxCP Scene Set Dressing",
|
|
"SxCPSceneBlocking": "SxCP Scene Blocking",
|
|
"SxCPSceneAction": "SxCP Scene Action",
|
|
"SxCPScenePerformance": "SxCP Scene Performance",
|
|
"SxCPSceneCamera": "SxCP Scene Camera",
|
|
"SxCPSceneComposition": "SxCP Scene Composition",
|
|
"SxCPSceneLighting": "SxCP Scene Lighting",
|
|
"SxCPSceneBranchPair": "SxCP Scene Branch Pair",
|
|
"SxCPSoftcoreBranchOptions": "SxCP Softcore Branch Options",
|
|
"SxCPHardcoreBranchOptions": "SxCP Hardcore Branch Options",
|
|
"SxCPSceneOutput": "SxCP Scene Output",
|
|
"SxCPScenePairOutput": "SxCP Scene Pair Output",
|
|
}
|