Add themed location and composition controls
This commit is contained in:
@@ -64,6 +64,13 @@ node. For cleaner workflows, use the split nodes:
|
|||||||
- `SxCP Location Pool` outputs `location_config`. `replace` uses only the
|
- `SxCP Location Pool` outputs `location_config`. `replace` uses only the
|
||||||
selected/custom location pool; `add` keeps the category's own locations and
|
selected/custom location pool; `add` keeps the category's own locations and
|
||||||
adds yours. Custom lines can be plain location text, or `slug: location text`.
|
adds yours. Custom lines can be plain location text, or `slug: location text`.
|
||||||
|
- `SxCP Composition Pool` outputs `composition_config` to control framing
|
||||||
|
separately from location. Use it when category framing mentions unrelated
|
||||||
|
outfit-check details such as shoes, bags, or mirror poses.
|
||||||
|
- `SxCP Location Theme` outputs matched `location_config` and
|
||||||
|
`composition_config`. Themes such as `classical_library`,
|
||||||
|
`semi_public_affair`, `hotel_corridor`, `parking_garage`, and
|
||||||
|
`theater_backstage` keep scene and framing compatible.
|
||||||
- `SxCP Generation Profile` outputs `generation_profile` for common behavior
|
- `SxCP Generation Profile` outputs `generation_profile` for common behavior
|
||||||
presets such as casual-clean, evocative-softcore, hardcore-intense,
|
presets such as casual-clean, evocative-softcore, hardcore-intense,
|
||||||
Krea2-friendly, or Flux-original. Its clothing and pose overrides can be
|
Krea2-friendly, or Flux-original. Its clothing and pose overrides can be
|
||||||
@@ -85,8 +92,8 @@ The practical compact workflow is:
|
|||||||
|
|
||||||
`Category Preset` + `Cast Control` + `Generation Profile` + optional
|
`Category Preset` + `Cast Control` + `Generation Profile` + optional
|
||||||
`Advanced Filters`, `Seed Locker` or `Seed Control`, `Camera Control` or
|
`Advanced Filters`, `Seed Locker` or `Seed Control`, `Camera Control` or
|
||||||
`Camera Orbit Control`, `Location Pool`, `Woman Slot` / `Man Slot`, and
|
`Camera Orbit Control`, `Location Theme` or `Location Pool` + `Composition Pool`,
|
||||||
`Character Profile`
|
`Woman Slot` / `Man Slot`, and `Character Profile`
|
||||||
into `Prompt Builder From Configs`.
|
into `Prompt Builder From Configs`.
|
||||||
|
|
||||||
An importable default workflow is included at
|
An importable default workflow is included at
|
||||||
|
|||||||
+100
@@ -19,6 +19,7 @@ SXCP_FILTER_CONFIG = "SXCP_FILTER_CONFIG"
|
|||||||
SXCP_SEED_CONFIG = "SXCP_SEED_CONFIG"
|
SXCP_SEED_CONFIG = "SXCP_SEED_CONFIG"
|
||||||
SXCP_CAMERA_CONFIG = "SXCP_CAMERA_CONFIG"
|
SXCP_CAMERA_CONFIG = "SXCP_CAMERA_CONFIG"
|
||||||
SXCP_LOCATION_CONFIG = "SXCP_LOCATION_CONFIG"
|
SXCP_LOCATION_CONFIG = "SXCP_LOCATION_CONFIG"
|
||||||
|
SXCP_COMPOSITION_CONFIG = "SXCP_COMPOSITION_CONFIG"
|
||||||
SXCP_CATEGORY_CONFIG = "SXCP_CATEGORY_CONFIG"
|
SXCP_CATEGORY_CONFIG = "SXCP_CATEGORY_CONFIG"
|
||||||
SXCP_CAST_CONFIG = "SXCP_CAST_CONFIG"
|
SXCP_CAST_CONFIG = "SXCP_CAST_CONFIG"
|
||||||
SXCP_GENERATION_PROFILE = "SXCP_GENERATION_PROFILE"
|
SXCP_GENERATION_PROFILE = "SXCP_GENERATION_PROFILE"
|
||||||
@@ -60,6 +61,7 @@ COMMON_INPUT_TOOLTIPS = {
|
|||||||
"seed_config": "Per-axis seed config. Connect Global Seed, Seed Locker, or Seed Control here.",
|
"seed_config": "Per-axis seed config. Connect Global Seed, Seed Locker, or Seed Control here.",
|
||||||
"camera_config": "Camera config used by the prompt formatter when camera mode is from_camera_config.",
|
"camera_config": "Camera config used by the prompt formatter when camera mode is from_camera_config.",
|
||||||
"location_config": "Location config from SxCP Location Pool. It can replace or add to the category scene pool.",
|
"location_config": "Location config from SxCP Location Pool. It can replace or add to the category scene pool.",
|
||||||
|
"composition_config": "Composition config from SxCP Composition Pool or Location Theme. It can replace or add framing options.",
|
||||||
"softcore_camera_config": "Camera config used only for the softcore Insta/OF prompt. Falls back to camera_config if empty.",
|
"softcore_camera_config": "Camera config used only for the softcore Insta/OF prompt. Falls back to camera_config if empty.",
|
||||||
"hardcore_camera_config": "Camera config used only for the hardcore Insta/OF prompt. Falls back to camera_config if empty.",
|
"hardcore_camera_config": "Camera config used only for the hardcore Insta/OF prompt. Falls back to camera_config if empty.",
|
||||||
"character_profile": "Saved or loaded single-character profile. Character slots override this for configured casts.",
|
"character_profile": "Saved or loaded single-character profile. Character slots override this for configured casts.",
|
||||||
@@ -67,6 +69,8 @@ COMMON_INPUT_TOOLTIPS = {
|
|||||||
"character_slot": "Single slot payload for saving/loading profiles or debugging one character.",
|
"character_slot": "Single slot payload for saving/loading profiles or debugging one character.",
|
||||||
"hardcore_position_config": "Hardcore action/position config. Chain Position Pool into Action Filter, then into the generator.",
|
"hardcore_position_config": "Hardcore action/position config. Chain Position Pool into Action Filter, then into the generator.",
|
||||||
"custom_locations": "One custom location per line. Use plain text, or slug: location text.",
|
"custom_locations": "One custom location per line. Use plain text, or slug: location text.",
|
||||||
|
"custom_compositions": "One custom composition/framing phrase per line.",
|
||||||
|
"theme": "Matched location and composition theme, useful when the place needs compatible framing.",
|
||||||
"metadata_json": "Structured metadata from an SxCP generator. Prefer this over raw prompt text for formatters and profile save.",
|
"metadata_json": "Structured metadata from an SxCP generator. Prefer this over raw prompt text for formatters and profile save.",
|
||||||
"source_text": "Raw prompt, caption, or metadata JSON depending on input_hint.",
|
"source_text": "Raw prompt, caption, or metadata JSON depending on input_hint.",
|
||||||
"source_text_input": "Optional linked raw prompt/caption input. When connected, it overrides the source_text widget.",
|
"source_text_input": "Optional linked raw prompt/caption input. When connected, it overrides the source_text widget.",
|
||||||
@@ -409,6 +413,7 @@ try:
|
|||||||
build_character_manual_config_json,
|
build_character_manual_config_json,
|
||||||
build_character_profile_json,
|
build_character_profile_json,
|
||||||
build_characteristics_config_json,
|
build_characteristics_config_json,
|
||||||
|
build_composition_pool_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,
|
||||||
@@ -417,6 +422,7 @@ try:
|
|||||||
build_hardcore_position_pool_json,
|
build_hardcore_position_pool_json,
|
||||||
build_insta_of_options_json,
|
build_insta_of_options_json,
|
||||||
build_location_pool_json,
|
build_location_pool_json,
|
||||||
|
build_thematic_location_json,
|
||||||
build_insta_of_pair,
|
build_insta_of_pair,
|
||||||
build_prompt,
|
build_prompt,
|
||||||
build_prompt_from_configs,
|
build_prompt_from_configs,
|
||||||
@@ -454,6 +460,7 @@ try:
|
|||||||
character_softcore_outfit_source_choices,
|
character_softcore_outfit_source_choices,
|
||||||
character_softcore_outfit_values,
|
character_softcore_outfit_values,
|
||||||
character_woman_body_choices,
|
character_woman_body_choices,
|
||||||
|
composition_pool_preset_choices,
|
||||||
ethnicity_choices,
|
ethnicity_choices,
|
||||||
generation_profile_choices,
|
generation_profile_choices,
|
||||||
hardcore_position_family_choices,
|
hardcore_position_family_choices,
|
||||||
@@ -461,6 +468,7 @@ try:
|
|||||||
hardcore_position_key_choices,
|
hardcore_position_key_choices,
|
||||||
hardcore_detail_density_choices,
|
hardcore_detail_density_choices,
|
||||||
load_character_profile_json,
|
load_character_profile_json,
|
||||||
|
location_theme_choices,
|
||||||
location_pool_preset_choices,
|
location_pool_preset_choices,
|
||||||
save_character_profile_payload,
|
save_character_profile_payload,
|
||||||
seed_mode_choices,
|
seed_mode_choices,
|
||||||
@@ -489,6 +497,7 @@ except ImportError:
|
|||||||
build_character_manual_config_json,
|
build_character_manual_config_json,
|
||||||
build_character_profile_json,
|
build_character_profile_json,
|
||||||
build_characteristics_config_json,
|
build_characteristics_config_json,
|
||||||
|
build_composition_pool_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,
|
||||||
@@ -497,6 +506,7 @@ except ImportError:
|
|||||||
build_hardcore_position_pool_json,
|
build_hardcore_position_pool_json,
|
||||||
build_insta_of_options_json,
|
build_insta_of_options_json,
|
||||||
build_location_pool_json,
|
build_location_pool_json,
|
||||||
|
build_thematic_location_json,
|
||||||
build_insta_of_pair,
|
build_insta_of_pair,
|
||||||
build_prompt,
|
build_prompt,
|
||||||
build_prompt_from_configs,
|
build_prompt_from_configs,
|
||||||
@@ -534,6 +544,7 @@ except ImportError:
|
|||||||
character_softcore_outfit_source_choices,
|
character_softcore_outfit_source_choices,
|
||||||
character_softcore_outfit_values,
|
character_softcore_outfit_values,
|
||||||
character_woman_body_choices,
|
character_woman_body_choices,
|
||||||
|
composition_pool_preset_choices,
|
||||||
ethnicity_choices,
|
ethnicity_choices,
|
||||||
generation_profile_choices,
|
generation_profile_choices,
|
||||||
hardcore_position_family_choices,
|
hardcore_position_family_choices,
|
||||||
@@ -541,6 +552,7 @@ except ImportError:
|
|||||||
hardcore_position_key_choices,
|
hardcore_position_key_choices,
|
||||||
hardcore_detail_density_choices,
|
hardcore_detail_density_choices,
|
||||||
load_character_profile_json,
|
load_character_profile_json,
|
||||||
|
location_theme_choices,
|
||||||
location_pool_preset_choices,
|
location_pool_preset_choices,
|
||||||
save_character_profile_payload,
|
save_character_profile_payload,
|
||||||
seed_mode_choices,
|
seed_mode_choices,
|
||||||
@@ -654,6 +666,7 @@ class SxCPPromptBuilder:
|
|||||||
"seed_config": (SXCP_SEED_CONFIG,),
|
"seed_config": (SXCP_SEED_CONFIG,),
|
||||||
"camera_config": (SXCP_CAMERA_CONFIG,),
|
"camera_config": (SXCP_CAMERA_CONFIG,),
|
||||||
"location_config": (SXCP_LOCATION_CONFIG,),
|
"location_config": (SXCP_LOCATION_CONFIG,),
|
||||||
|
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
||||||
"character_profile": (SXCP_CHARACTER_PROFILE,),
|
"character_profile": (SXCP_CHARACTER_PROFILE,),
|
||||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||||
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
||||||
@@ -690,6 +703,7 @@ class SxCPPromptBuilder:
|
|||||||
seed_config="",
|
seed_config="",
|
||||||
camera_config="",
|
camera_config="",
|
||||||
location_config="",
|
location_config="",
|
||||||
|
composition_config="",
|
||||||
character_profile="",
|
character_profile="",
|
||||||
character_cast="",
|
character_cast="",
|
||||||
hardcore_position_config="",
|
hardcore_position_config="",
|
||||||
@@ -725,6 +739,7 @@ class SxCPPromptBuilder:
|
|||||||
seed_config=seed_config or "",
|
seed_config=seed_config or "",
|
||||||
camera_config=camera_config or "",
|
camera_config=camera_config or "",
|
||||||
location_config=location_config or "",
|
location_config=location_config or "",
|
||||||
|
composition_config=composition_config or "",
|
||||||
character_profile=character_profile or "",
|
character_profile=character_profile or "",
|
||||||
character_cast=character_cast or "",
|
character_cast=character_cast or "",
|
||||||
hardcore_position_config=hardcore_position_config or "",
|
hardcore_position_config=hardcore_position_config or "",
|
||||||
@@ -1204,6 +1219,81 @@ class SxCPLocationPool:
|
|||||||
return config, parsed.get("summary", "")
|
return config, parsed.get("summary", "")
|
||||||
|
|
||||||
|
|
||||||
|
class SxCPCompositionPool:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"enabled": ("BOOLEAN", {"default": True}),
|
||||||
|
"combine_mode": (["replace", "add"], {"default": "replace"}),
|
||||||
|
"preset": (composition_pool_preset_choices(), {"default": "no_outfit_check"}),
|
||||||
|
"custom_compositions": ("STRING", {"default": "", "multiline": True}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = (SXCP_COMPOSITION_CONFIG, "STRING")
|
||||||
|
RETURN_NAMES = ("composition_config", "summary")
|
||||||
|
FUNCTION = "build"
|
||||||
|
CATEGORY = "prompt_builder"
|
||||||
|
|
||||||
|
def build(self, enabled, combine_mode, preset, custom_compositions, composition_config=""):
|
||||||
|
config = build_composition_pool_json(
|
||||||
|
enabled=enabled,
|
||||||
|
combine_mode=combine_mode,
|
||||||
|
preset=preset,
|
||||||
|
custom_compositions=custom_compositions or "",
|
||||||
|
composition_config=composition_config or "",
|
||||||
|
)
|
||||||
|
parsed = json.loads(config)
|
||||||
|
return config, parsed.get("summary", "")
|
||||||
|
|
||||||
|
|
||||||
|
class SxCPLocationTheme:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"enabled": ("BOOLEAN", {"default": True}),
|
||||||
|
"combine_mode": (["replace", "add"], {"default": "replace"}),
|
||||||
|
"theme": (location_theme_choices(), {"default": "semi_public_affair"}),
|
||||||
|
"custom_locations": ("STRING", {"default": "", "multiline": True}),
|
||||||
|
"custom_compositions": ("STRING", {"default": "", "multiline": True}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"location_config": (SXCP_LOCATION_CONFIG,),
|
||||||
|
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = (SXCP_LOCATION_CONFIG, SXCP_COMPOSITION_CONFIG, "STRING")
|
||||||
|
RETURN_NAMES = ("location_config", "composition_config", "summary")
|
||||||
|
FUNCTION = "build"
|
||||||
|
CATEGORY = "prompt_builder"
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
enabled,
|
||||||
|
combine_mode,
|
||||||
|
theme,
|
||||||
|
custom_locations,
|
||||||
|
custom_compositions,
|
||||||
|
location_config="",
|
||||||
|
composition_config="",
|
||||||
|
):
|
||||||
|
return build_thematic_location_json(
|
||||||
|
enabled=enabled,
|
||||||
|
combine_mode=combine_mode,
|
||||||
|
theme=theme,
|
||||||
|
custom_locations=custom_locations or "",
|
||||||
|
custom_compositions=custom_compositions or "",
|
||||||
|
location_config=location_config or "",
|
||||||
|
composition_config=composition_config or "",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SxCPCastControl:
|
class SxCPCastControl:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
@@ -1956,6 +2046,7 @@ class SxCPPromptBuilderFromConfigs:
|
|||||||
"seed_config": (SXCP_SEED_CONFIG,),
|
"seed_config": (SXCP_SEED_CONFIG,),
|
||||||
"camera_config": (SXCP_CAMERA_CONFIG,),
|
"camera_config": (SXCP_CAMERA_CONFIG,),
|
||||||
"location_config": (SXCP_LOCATION_CONFIG,),
|
"location_config": (SXCP_LOCATION_CONFIG,),
|
||||||
|
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
||||||
"character_profile": (SXCP_CHARACTER_PROFILE,),
|
"character_profile": (SXCP_CHARACTER_PROFILE,),
|
||||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||||
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
||||||
@@ -1982,6 +2073,7 @@ class SxCPPromptBuilderFromConfigs:
|
|||||||
seed_config="",
|
seed_config="",
|
||||||
camera_config="",
|
camera_config="",
|
||||||
location_config="",
|
location_config="",
|
||||||
|
composition_config="",
|
||||||
character_profile="",
|
character_profile="",
|
||||||
character_cast="",
|
character_cast="",
|
||||||
hardcore_position_config="",
|
hardcore_position_config="",
|
||||||
@@ -1999,6 +2091,7 @@ class SxCPPromptBuilderFromConfigs:
|
|||||||
seed_config=seed_config or "",
|
seed_config=seed_config or "",
|
||||||
camera_config=camera_config or "",
|
camera_config=camera_config or "",
|
||||||
location_config=location_config or "",
|
location_config=location_config or "",
|
||||||
|
composition_config=composition_config or "",
|
||||||
character_profile=character_profile or "",
|
character_profile=character_profile or "",
|
||||||
character_cast=character_cast or "",
|
character_cast=character_cast or "",
|
||||||
hardcore_position_config=hardcore_position_config or "",
|
hardcore_position_config=hardcore_position_config or "",
|
||||||
@@ -2720,6 +2813,7 @@ class SxCPInstaOFPromptPair:
|
|||||||
"softcore_camera_config": (SXCP_CAMERA_CONFIG,),
|
"softcore_camera_config": (SXCP_CAMERA_CONFIG,),
|
||||||
"hardcore_camera_config": (SXCP_CAMERA_CONFIG,),
|
"hardcore_camera_config": (SXCP_CAMERA_CONFIG,),
|
||||||
"location_config": (SXCP_LOCATION_CONFIG,),
|
"location_config": (SXCP_LOCATION_CONFIG,),
|
||||||
|
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
||||||
"character_profile": (SXCP_CHARACTER_PROFILE,),
|
"character_profile": (SXCP_CHARACTER_PROFILE,),
|
||||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||||
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
||||||
@@ -2759,6 +2853,7 @@ class SxCPInstaOFPromptPair:
|
|||||||
softcore_camera_config="",
|
softcore_camera_config="",
|
||||||
hardcore_camera_config="",
|
hardcore_camera_config="",
|
||||||
location_config="",
|
location_config="",
|
||||||
|
composition_config="",
|
||||||
character_profile="",
|
character_profile="",
|
||||||
character_cast="",
|
character_cast="",
|
||||||
hardcore_position_config="",
|
hardcore_position_config="",
|
||||||
@@ -2784,6 +2879,7 @@ class SxCPInstaOFPromptPair:
|
|||||||
softcore_camera_config=softcore_camera_config or "",
|
softcore_camera_config=softcore_camera_config or "",
|
||||||
hardcore_camera_config=hardcore_camera_config or "",
|
hardcore_camera_config=hardcore_camera_config or "",
|
||||||
location_config=location_config or "",
|
location_config=location_config or "",
|
||||||
|
composition_config=composition_config or "",
|
||||||
character_profile=character_profile or "",
|
character_profile=character_profile or "",
|
||||||
character_cast=character_cast or "",
|
character_cast=character_cast or "",
|
||||||
hardcore_position_config=hardcore_position_config or "",
|
hardcore_position_config=hardcore_position_config or "",
|
||||||
@@ -2813,6 +2909,8 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"SxCPQwenCameraTranslator": SxCPQwenCameraTranslator,
|
"SxCPQwenCameraTranslator": SxCPQwenCameraTranslator,
|
||||||
"SxCPCategoryPreset": SxCPCategoryPreset,
|
"SxCPCategoryPreset": SxCPCategoryPreset,
|
||||||
"SxCPLocationPool": SxCPLocationPool,
|
"SxCPLocationPool": SxCPLocationPool,
|
||||||
|
"SxCPCompositionPool": SxCPCompositionPool,
|
||||||
|
"SxCPLocationTheme": SxCPLocationTheme,
|
||||||
"SxCPCastControl": SxCPCastControl,
|
"SxCPCastControl": SxCPCastControl,
|
||||||
"SxCPCastBias": SxCPCastBias,
|
"SxCPCastBias": SxCPCastBias,
|
||||||
"SxCPGenerationProfile": SxCPGenerationProfile,
|
"SxCPGenerationProfile": SxCPGenerationProfile,
|
||||||
@@ -2856,6 +2954,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
|||||||
"SxCPQwenCameraTranslator": "SxCP Qwen Camera Translator",
|
"SxCPQwenCameraTranslator": "SxCP Qwen Camera Translator",
|
||||||
"SxCPCategoryPreset": "SxCP Category Preset",
|
"SxCPCategoryPreset": "SxCP Category Preset",
|
||||||
"SxCPLocationPool": "SxCP Location Pool",
|
"SxCPLocationPool": "SxCP Location Pool",
|
||||||
|
"SxCPCompositionPool": "SxCP Composition Pool",
|
||||||
|
"SxCPLocationTheme": "SxCP Location Theme",
|
||||||
"SxCPCastControl": "SxCP Cast Control",
|
"SxCPCastControl": "SxCP Cast Control",
|
||||||
"SxCPCastBias": "SxCP Cast Bias",
|
"SxCPCastBias": "SxCP Cast Bias",
|
||||||
"SxCPGenerationProfile": "SxCP Generation Profile",
|
"SxCPGenerationProfile": "SxCP Generation Profile",
|
||||||
|
|||||||
+413
-2
@@ -1348,6 +1348,188 @@ def load_composition_pool_library() -> dict[str, list[Any]]:
|
|||||||
return _load_named_pool_library("composition_pools")
|
return _load_named_pool_library("composition_pools")
|
||||||
|
|
||||||
|
|
||||||
|
COMPOSITION_POOL_PRESETS = {
|
||||||
|
"custom_only": (),
|
||||||
|
"all_json_compositions": ("*",),
|
||||||
|
"casual_all": ("casual_", "streetwear_", "summer_", "cozy_home_", "smart_casual_", "athleisure_"),
|
||||||
|
"creator_softcore": ("softcore_creator_compositions", "boudoir_body_compositions"),
|
||||||
|
"hardcore_all": ("hardcore_",),
|
||||||
|
"hardcore_explicit": ("hardcore_explicit_compositions",),
|
||||||
|
"no_outfit_check": (),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
COMPOSITION_INLINE_PRESETS = {
|
||||||
|
"no_outfit_check": [
|
||||||
|
"environment-led frame with no outfit-check wording",
|
||||||
|
"mid-distance scene composition with the room context readable",
|
||||||
|
"partly occluded candid frame through foreground architecture",
|
||||||
|
"long perspective frame using repeating background structure",
|
||||||
|
"waist-up or three-quarter frame without bag, shoes, or footwear emphasis",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def composition_pool_preset_choices() -> list[str]:
|
||||||
|
pool_choices = [f"pool:{key}" for key in sorted(load_composition_pool_library())]
|
||||||
|
return list(COMPOSITION_POOL_PRESETS) + pool_choices
|
||||||
|
|
||||||
|
|
||||||
|
THEMATIC_LOCATION_PRESETS = {
|
||||||
|
"classical_library": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "classical_large_library", "prompt": "grand classical library hall with towering dark-wood bookshelves, carved columns, rolling ladders, marble floor, warm brass lamps, arched windows, and deep quiet academic atmosphere"},
|
||||||
|
{"slug": "old_world_reading_room", "prompt": "large old-world reading room with floor-to-ceiling bookshelves, heavy wooden tables, green banker lamps, leather chairs, tall arched windows, and warm amber evening light"},
|
||||||
|
{"slug": "hidden_library_stacks", "prompt": "quiet library stacks with endless tall bookshelves, narrow aisles, rolling ladders, brass lamps, and hidden sightlines between shelves"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"narrow aisle frame between towering bookshelves",
|
||||||
|
"over-the-shoulder view through foreground books",
|
||||||
|
"warm lamp-lit reading-table composition",
|
||||||
|
"long vanishing-point frame down repeated library stacks",
|
||||||
|
"partly hidden frame behind carved columns and shelf edges",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"semi_public_affair": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "hotel_corridor_affair", "prompt": "upscale hotel corridor with repeating numbered doors, patterned carpet, brass wall lamps, luggage carts, and a secluded corner near a service alcove"},
|
||||||
|
{"slug": "hotel_service_hall", "prompt": "luxury hotel service corridor with repeating linen carts, beige doors, utility shelves, wall sconces, and a private turn away from the main hallway"},
|
||||||
|
{"slug": "parking_garage_hidden", "prompt": "empty multi-level parking garage with repeating concrete pillars, parked cars, painted floor lines, low fluorescent light, and shadowed blind spots"},
|
||||||
|
{"slug": "office_afterhours_affair", "prompt": "empty corporate office after hours with rows of glass partitions, repeating desks, blinds, copier alcove, muted city light, and no visible coworkers"},
|
||||||
|
{"slug": "library_stacks_secret", "prompt": "classical library stacks with endless tall bookshelves, narrow aisles, rolling ladders, carved columns, warm brass lamps, and hidden sightlines between shelves"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"partly concealed frame from behind a doorway edge",
|
||||||
|
"long corridor vanishing-point composition with repeated doors",
|
||||||
|
"hidden alcove frame with foreground obstruction",
|
||||||
|
"surveillance-like candid angle from across the empty space",
|
||||||
|
"tight frame using pillars, shelves, or walls to block side visibility",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"hotel_corridor": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "upscale_hotel_corridor", "prompt": "upscale hotel corridor with repeating doors, patterned carpet, brass wall lamps, quiet service alcoves, and warm late-night light"},
|
||||||
|
{"slug": "hotel_service_alcove", "prompt": "hotel service alcove with linen carts, beige utility doors, folded towels, soft wall sconces, and a secluded turn off the main corridor"},
|
||||||
|
{"slug": "boutique_hotel_stair_landing", "prompt": "boutique hotel stair landing with repeating railings, framed wall panels, low amber lamps, and a quiet corner between floors"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"long hallway frame with repeated doors receding behind the body",
|
||||||
|
"corner-alcove composition partly hidden by a wall edge",
|
||||||
|
"low corridor angle with patterned carpet leading lines",
|
||||||
|
"over-the-shoulder frame toward a closed hotel-room door",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"parking_garage": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "empty_parking_garage", "prompt": "empty multi-level parking garage with repeating concrete pillars, parked cars, painted bay lines, low fluorescent light, and deep shadowed corners"},
|
||||||
|
{"slug": "underground_garage_corner", "prompt": "underground parking garage corner with numbered pillars, glossy concrete floor, parked cars, and blue-green fluorescent light"},
|
||||||
|
{"slug": "rooftop_parking_deck_night", "prompt": "rooftop parking deck at night with repeated concrete barriers, distant city lights, painted lines, and open wind"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"pillar-framed composition with repeated concrete columns",
|
||||||
|
"low angle across painted parking lines",
|
||||||
|
"hidden corner frame between parked cars",
|
||||||
|
"wide empty garage frame with strong fluorescent perspective",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"theater_backstage": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "old_theater_backstage", "prompt": "old theater backstage with repeated velvet curtains, prop racks, costume rails, bulb mirrors, dark wings, and narrow hidden passages"},
|
||||||
|
{"slug": "cabaret_backstage_wings", "prompt": "cabaret backstage wings with red curtains, costume racks, vanity bulbs, stage ropes, and warm theatrical shadows"},
|
||||||
|
{"slug": "prop_storage_corridor", "prompt": "theater prop storage corridor with stacked trunks, repeated scenery flats, rolling racks, and dim practical lamps"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"frame between layered velvet curtains",
|
||||||
|
"backstage mirror-bulb composition with costume racks behind",
|
||||||
|
"hidden wing angle looking toward the stage light spill",
|
||||||
|
"narrow prop-aisle frame with repeated vertical flats",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"wine_cellar": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "private_wine_cellar", "prompt": "private wine cellar with repeating bottle racks, arched brick walls, narrow aisles, dim amber lamps, and secluded corners between shelves"},
|
||||||
|
{"slug": "restaurant_wine_storage", "prompt": "restaurant wine storage room with stacked bottle shelves, crate rows, stone floor, soft utility light, and hidden service-door access"},
|
||||||
|
{"slug": "arched_cellar_corridor", "prompt": "arched cellar corridor with repeated brick niches, wine racks, low golden lamps, and cool shadowed depth"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"narrow aisle frame between repeated bottle racks",
|
||||||
|
"arched brick corridor composition with warm lamps",
|
||||||
|
"foreground bottle-rack occlusion framing the body",
|
||||||
|
"low cellar angle with shelves receding behind",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"museum_archive": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "museum_archive_room", "prompt": "museum archive room with repeating storage shelves, labeled boxes, rolling ladders, long work tables, soft overhead lights, and hidden aisles"},
|
||||||
|
{"slug": "gallery_storage_backroom", "prompt": "gallery storage backroom with stacked frames, rolling racks, crate labels, clean concrete floor, and muted work lights"},
|
||||||
|
{"slug": "rare_books_archive", "prompt": "rare-books archive with compact shelving, catalog drawers, reading lamps, archival boxes, and narrow private aisles"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"hidden archive-aisle frame between storage shelves",
|
||||||
|
"table-edge composition with labeled boxes in the background",
|
||||||
|
"foreground crate or shelf occlusion",
|
||||||
|
"long compact-shelving perspective with repeated rows",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"laundromat_late_night": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "late_night_laundromat", "prompt": "late-night laundromat with repeating washing machines, chrome reflections, tiled floor, fluorescent lights, empty aisles, and a secluded back corner"},
|
||||||
|
{"slug": "coin_laundry_back_row", "prompt": "coin laundry back row with stacked dryers, plastic folding tables, detergent shelves, buzzing fluorescent light, and no other customers"},
|
||||||
|
{"slug": "laundromat_mirror_windows", "prompt": "quiet laundromat with mirrored machine doors, repeated round windows, tile floor, and cool blue night light through front glass"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"repeating washer-door perspective behind the body",
|
||||||
|
"folding-table edge frame with chrome reflections",
|
||||||
|
"low tiled-floor angle down an empty machine row",
|
||||||
|
"back-corner composition partly hidden by laundry machines",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"train_station_lockers": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "train_station_locker_corridor", "prompt": "quiet train-station locker corridor with repeating metal lockers, tiled walls, vending machines, fluorescent light, and a hidden side alcove"},
|
||||||
|
{"slug": "empty_platform_underpass", "prompt": "empty station underpass with tiled walls, repeated poster frames, stair railings, fluorescent lights, and late-night quiet"},
|
||||||
|
{"slug": "station_service_passage", "prompt": "station service passage with repeating utility doors, metal lockers, warning stripes, and cool overhead light"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"locker-row vanishing-point composition",
|
||||||
|
"side-alcove frame partly blocked by metal lockers",
|
||||||
|
"fluorescent underpass frame with repeated tile lines",
|
||||||
|
"candid angle from behind a vending machine edge",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"nightclub_back_hall": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "nightclub_back_hall", "prompt": "nightclub back hallway with black doors, repeated neon strips, coat-check racks, textured walls, and distant colored dance-floor light"},
|
||||||
|
{"slug": "club_vip_corridor", "prompt": "VIP club corridor with velvet ropes, mirrored wall panels, low red light, repeated booths, and a private bend in the hallway"},
|
||||||
|
{"slug": "music_venue_greenroom_hall", "prompt": "music venue greenroom corridor with stickered doors, cable cases, dim practical lamps, and repeated black curtains"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"neon hallway frame with repeated dark doors",
|
||||||
|
"partly hidden VIP-booth angle",
|
||||||
|
"mirror-panel composition with colored light streaks",
|
||||||
|
"tight backstage corridor frame with curtains at the edges",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"restaurant_private_booth": {
|
||||||
|
"locations": [
|
||||||
|
{"slug": "restaurant_private_booth", "prompt": "dim restaurant private booth with high banquettes, repeating table lamps, dark wood partitions, folded napkins, and secluded sightlines"},
|
||||||
|
{"slug": "empty_bistro_back_corner", "prompt": "empty bistro back corner with tiled floor, small round tables, brass lamps, mirrored walls, and a hidden booth"},
|
||||||
|
{"slug": "afterhours_dining_room", "prompt": "after-hours dining room with stacked chairs, repeated tables, low amber sconces, and a quiet service doorway"},
|
||||||
|
],
|
||||||
|
"compositions": [
|
||||||
|
"booth-partition frame with high seat backs blocking the sides",
|
||||||
|
"table-edge composition with lamps repeating behind",
|
||||||
|
"mirror-wall restaurant angle with dark wood partitions",
|
||||||
|
"after-hours dining-room perspective through empty tables",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def location_theme_choices() -> list[str]:
|
||||||
|
return list(THEMATIC_LOCATION_PRESETS)
|
||||||
|
|
||||||
|
|
||||||
def _extension_targets() -> dict[str, tuple[list[Any], bool]]:
|
def _extension_targets() -> dict[str, tuple[list[Any], bool]]:
|
||||||
return {
|
return {
|
||||||
"women_clothes": (g.WOMEN_CLOTHES, False),
|
"women_clothes": (g.WOMEN_CLOTHES, False),
|
||||||
@@ -1824,6 +2006,156 @@ def _location_config_active(location_config: dict[str, Any]) -> bool:
|
|||||||
return bool(location_config.get("enabled")) and bool(location_config.get("scene_entries"))
|
return bool(location_config.get("enabled")) and bool(location_config.get("scene_entries"))
|
||||||
|
|
||||||
|
|
||||||
|
def _composition_pool_names_for_preset(preset: str) -> list[str]:
|
||||||
|
composition_pools = load_composition_pool_library()
|
||||||
|
preset = str(preset or "custom_only")
|
||||||
|
if preset.startswith("pool:"):
|
||||||
|
pool_name = preset.split(":", 1)[1].strip()
|
||||||
|
return [pool_name] if pool_name in composition_pools else []
|
||||||
|
selectors = COMPOSITION_POOL_PRESETS.get(preset, ())
|
||||||
|
names: list[str] = []
|
||||||
|
for selector in selectors:
|
||||||
|
if selector == "*":
|
||||||
|
_unique_extend(names, sorted(composition_pools))
|
||||||
|
elif selector.endswith("_"):
|
||||||
|
_unique_extend(names, sorted(name for name in composition_pools if name.startswith(selector)))
|
||||||
|
elif selector in composition_pools:
|
||||||
|
_unique_extend(names, [selector])
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
def _custom_composition_entries(custom_compositions: str) -> list[str]:
|
||||||
|
entries: list[str] = []
|
||||||
|
for raw_line in str(custom_compositions or "").splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
entries.append(line)
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def _composition_entries_for_pool_names(pool_names: list[str]) -> list[Any]:
|
||||||
|
composition_pools = load_composition_pool_library()
|
||||||
|
entries: list[Any] = []
|
||||||
|
for pool_name in pool_names:
|
||||||
|
if pool_name not in composition_pools:
|
||||||
|
continue
|
||||||
|
_unique_extend(entries, composition_pools[pool_name])
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def build_composition_pool_json(
|
||||||
|
enabled: bool = True,
|
||||||
|
combine_mode: str = "replace",
|
||||||
|
preset: str = "custom_only",
|
||||||
|
custom_compositions: str = "",
|
||||||
|
composition_config: str | dict[str, Any] | None = "",
|
||||||
|
) -> str:
|
||||||
|
incoming = _parse_composition_config(composition_config)
|
||||||
|
combine_mode = combine_mode if combine_mode in ("replace", "add") else "replace"
|
||||||
|
pool_names = _composition_pool_names_for_preset(preset)
|
||||||
|
entries = _composition_entries_for_pool_names(pool_names)
|
||||||
|
_unique_extend(entries, COMPOSITION_INLINE_PRESETS.get(str(preset or ""), []))
|
||||||
|
_unique_extend(entries, _custom_composition_entries(custom_compositions))
|
||||||
|
|
||||||
|
if combine_mode == "add" and incoming.get("enabled"):
|
||||||
|
apply_mode = str(incoming.get("apply_mode") or "replace")
|
||||||
|
merged_pool_names = _list_from(incoming.get("pool_names"))
|
||||||
|
_unique_extend(merged_pool_names, pool_names)
|
||||||
|
merged_entries = _list_from(incoming.get("composition_entries"))
|
||||||
|
_unique_extend(merged_entries, entries)
|
||||||
|
else:
|
||||||
|
apply_mode = "replace" if combine_mode == "replace" else "add"
|
||||||
|
merged_pool_names = pool_names
|
||||||
|
merged_entries = entries
|
||||||
|
|
||||||
|
active = bool(enabled) and bool(merged_entries)
|
||||||
|
summary = (
|
||||||
|
f"{apply_mode}; pools={len(merged_pool_names)}; compositions={len(merged_entries)}"
|
||||||
|
if active
|
||||||
|
else "disabled or empty"
|
||||||
|
)
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"enabled": active,
|
||||||
|
"apply_mode": apply_mode,
|
||||||
|
"pool_names": merged_pool_names,
|
||||||
|
"composition_entries": merged_entries,
|
||||||
|
"summary": summary,
|
||||||
|
},
|
||||||
|
ensure_ascii=True,
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_composition_config(composition_config: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||||
|
if not composition_config:
|
||||||
|
return {"enabled": False, "apply_mode": "replace", "pool_names": [], "composition_entries": []}
|
||||||
|
if isinstance(composition_config, dict):
|
||||||
|
raw = dict(composition_config)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
raw = json.loads(str(composition_config))
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise ValueError(f"Invalid composition_config JSON: {exc}") from exc
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
raise ValueError("composition_config must be a JSON object")
|
||||||
|
entries = _list_from(raw.get("composition_entries"))
|
||||||
|
if not entries and raw.get("pool_names"):
|
||||||
|
entries = _composition_entries_for_pool_names([str(name) for name in _list_from(raw.get("pool_names"))])
|
||||||
|
return {
|
||||||
|
"enabled": bool(raw.get("enabled")) and bool(entries),
|
||||||
|
"apply_mode": str(raw.get("apply_mode") or "replace") if str(raw.get("apply_mode") or "replace") in ("replace", "add") else "replace",
|
||||||
|
"pool_names": [str(name) for name in _list_from(raw.get("pool_names")) if str(name).strip()],
|
||||||
|
"composition_entries": entries,
|
||||||
|
"summary": str(raw.get("summary") or ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _composition_config_active(composition_config: dict[str, Any]) -> bool:
|
||||||
|
return bool(composition_config.get("enabled")) and bool(composition_config.get("composition_entries"))
|
||||||
|
|
||||||
|
|
||||||
|
def build_thematic_location_json(
|
||||||
|
enabled: bool = True,
|
||||||
|
combine_mode: str = "replace",
|
||||||
|
theme: str = "semi_public_affair",
|
||||||
|
custom_locations: str = "",
|
||||||
|
custom_compositions: str = "",
|
||||||
|
location_config: str | dict[str, Any] | None = "",
|
||||||
|
composition_config: str | dict[str, Any] | None = "",
|
||||||
|
) -> tuple[str, str, str]:
|
||||||
|
theme_data = THEMATIC_LOCATION_PRESETS.get(str(theme or ""), THEMATIC_LOCATION_PRESETS["semi_public_affair"])
|
||||||
|
location_lines = "\n".join(
|
||||||
|
f"{entry['slug']}: {entry['prompt']}"
|
||||||
|
for entry in theme_data.get("locations", [])
|
||||||
|
if isinstance(entry, dict) and entry.get("slug") and entry.get("prompt")
|
||||||
|
)
|
||||||
|
if custom_locations.strip():
|
||||||
|
location_lines = "\n".join(part for part in (location_lines, custom_locations.strip()) if part)
|
||||||
|
composition_lines = "\n".join(str(entry) for entry in theme_data.get("compositions", []) if str(entry).strip())
|
||||||
|
if custom_compositions.strip():
|
||||||
|
composition_lines = "\n".join(part for part in (composition_lines, custom_compositions.strip()) if part)
|
||||||
|
resolved_location_config = build_location_pool_json(
|
||||||
|
enabled=enabled,
|
||||||
|
combine_mode=combine_mode,
|
||||||
|
preset="custom_only",
|
||||||
|
custom_locations=location_lines,
|
||||||
|
location_config=location_config or "",
|
||||||
|
)
|
||||||
|
resolved_composition_config = build_composition_pool_json(
|
||||||
|
enabled=enabled,
|
||||||
|
combine_mode=combine_mode,
|
||||||
|
preset="custom_only",
|
||||||
|
custom_compositions=composition_lines,
|
||||||
|
composition_config=composition_config or "",
|
||||||
|
)
|
||||||
|
location_summary = json.loads(resolved_location_config).get("summary", "")
|
||||||
|
composition_summary = json.loads(resolved_composition_config).get("summary", "")
|
||||||
|
summary = f"{theme}; locations={location_summary}; compositions={composition_summary}"
|
||||||
|
return resolved_location_config, resolved_composition_config, summary
|
||||||
|
|
||||||
|
|
||||||
def _ethnicity_text_from_value(value: Any) -> str:
|
def _ethnicity_text_from_value(value: Any) -> str:
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
return str(value.get("ethnicity") or "").strip()
|
return str(value.get("ethnicity") or "").strip()
|
||||||
@@ -5857,6 +6189,50 @@ def _apply_location_config_to_legacy_row(
|
|||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def _legacy_composition_entries_for_row(row: dict[str, Any]) -> list[Any]:
|
||||||
|
subject = str(row.get("primary_subject") or "").lower()
|
||||||
|
if "group" in subject or "layout" in subject:
|
||||||
|
return list(g.GROUP_COMPOSITIONS)
|
||||||
|
return list(g.COMPOSITIONS)
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_composition_config_to_legacy_row(
|
||||||
|
row: dict[str, Any],
|
||||||
|
composition_config: dict[str, Any],
|
||||||
|
seed_config: dict[str, int],
|
||||||
|
seed: int,
|
||||||
|
row_number: int,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
if not _composition_config_active(composition_config):
|
||||||
|
return row
|
||||||
|
composition_entries = _list_from(composition_config.get("composition_entries"))
|
||||||
|
if composition_config.get("apply_mode") == "add":
|
||||||
|
choices = _legacy_composition_entries_for_row(row)
|
||||||
|
_unique_extend(choices, composition_entries)
|
||||||
|
else:
|
||||||
|
choices = composition_entries
|
||||||
|
composition_rng = _axis_rng(seed_config, "composition", seed, row_number)
|
||||||
|
new_composition = _choose_text(composition_rng, choices)
|
||||||
|
old_composition = str(row.get("composition") or "")
|
||||||
|
old_prompt_fragment = f"Composition: vertical {old_composition}."
|
||||||
|
new_prompt_fragment = f"Composition: {_composition_prompt(new_composition)}."
|
||||||
|
row["source_composition"] = old_composition
|
||||||
|
row["composition"] = new_composition
|
||||||
|
row["composition_prompt"] = _composition_prompt(new_composition)
|
||||||
|
row["composition_config"] = composition_config
|
||||||
|
if old_composition:
|
||||||
|
row["prompt"] = str(row.get("prompt") or "").replace(old_prompt_fragment, new_prompt_fragment)
|
||||||
|
row["caption"] = str(row.get("caption") or "").replace(f", {old_composition},", f", {new_composition},")
|
||||||
|
else:
|
||||||
|
row["prompt"] = re.sub(
|
||||||
|
r"Composition:\s*.*?\.\s*Use",
|
||||||
|
f"{new_prompt_fragment} Use",
|
||||||
|
str(row.get("prompt") or ""),
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
def _sources_with_inheritance(
|
def _sources_with_inheritance(
|
||||||
category: dict[str, Any],
|
category: dict[str, Any],
|
||||||
subcategory: dict[str, Any],
|
subcategory: dict[str, Any],
|
||||||
@@ -6042,7 +6418,17 @@ def _pose_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any,
|
|||||||
return g.EVOCATIVE_ALL if poses == "evocative" else g.POSES
|
return g.EVOCATIVE_ALL if poses == "evocative" else g.POSES
|
||||||
|
|
||||||
|
|
||||||
def _composition_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any, subject_type: str) -> list[Any]:
|
def _composition_pool(
|
||||||
|
category: dict[str, Any],
|
||||||
|
subcategory: dict[str, Any],
|
||||||
|
item: Any,
|
||||||
|
subject_type: str,
|
||||||
|
composition_config: dict[str, Any] | None = None,
|
||||||
|
) -> list[Any]:
|
||||||
|
composition_config = composition_config or {}
|
||||||
|
composition_entries = _list_from(composition_config.get("composition_entries"))
|
||||||
|
if _composition_config_active(composition_config) and composition_config.get("apply_mode") == "replace":
|
||||||
|
return composition_entries
|
||||||
configured = _configured_pool(
|
configured = _configured_pool(
|
||||||
category,
|
category,
|
||||||
subcategory,
|
subcategory,
|
||||||
@@ -6052,6 +6438,9 @@ def _composition_pool(category: dict[str, Any], subcategory: dict[str, Any], ite
|
|||||||
load_composition_pool_library(),
|
load_composition_pool_library(),
|
||||||
"inherit_compositions",
|
"inherit_compositions",
|
||||||
)
|
)
|
||||||
|
if _composition_config_active(composition_config) and composition_config.get("apply_mode") == "add":
|
||||||
|
configured = list(configured or [])
|
||||||
|
_unique_extend(configured, composition_entries)
|
||||||
if configured:
|
if configured:
|
||||||
return configured
|
return configured
|
||||||
if subject_type in ("group", "configured_cast"):
|
if subject_type in ("group", "configured_cast"):
|
||||||
@@ -6083,6 +6472,7 @@ def _build_custom_row(
|
|||||||
expression_phase: str = "",
|
expression_phase: str = "",
|
||||||
hardcore_position_config: str | dict[str, Any] | None = None,
|
hardcore_position_config: str | dict[str, Any] | None = None,
|
||||||
location_config: str | dict[str, Any] | None = None,
|
location_config: str | dict[str, Any] | None = None,
|
||||||
|
composition_config: str | dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
categories = load_category_library()
|
categories = load_category_library()
|
||||||
category_rng = _axis_rng(seed_config, "category", seed, row_number)
|
category_rng = _axis_rng(seed_config, "category", seed, row_number)
|
||||||
@@ -6095,6 +6485,7 @@ def _build_custom_row(
|
|||||||
composition_rng = _axis_rng(seed_config, "composition", seed, row_number)
|
composition_rng = _axis_rng(seed_config, "composition", seed, row_number)
|
||||||
parsed_hardcore_position_config = _parse_hardcore_position_config(hardcore_position_config)
|
parsed_hardcore_position_config = _parse_hardcore_position_config(hardcore_position_config)
|
||||||
parsed_location_config = _parse_location_config(location_config)
|
parsed_location_config = _parse_location_config(location_config)
|
||||||
|
parsed_composition_config = _parse_composition_config(composition_config)
|
||||||
|
|
||||||
requested_women_count = women_count
|
requested_women_count = women_count
|
||||||
requested_men_count = men_count
|
requested_men_count = men_count
|
||||||
@@ -6259,7 +6650,11 @@ def _build_custom_row(
|
|||||||
expression = character_expression_text
|
expression = character_expression_text
|
||||||
source_composition = _choose_text(
|
source_composition = _choose_text(
|
||||||
composition_rng,
|
composition_rng,
|
||||||
_compatible_entries(_composition_pool(category, subcategory, item, subject_type), women_count, men_count),
|
_compatible_entries(
|
||||||
|
_composition_pool(category, subcategory, item, subject_type, parsed_composition_config),
|
||||||
|
women_count,
|
||||||
|
men_count,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if is_pose_category:
|
if is_pose_category:
|
||||||
source_composition = _sanitize_hardcore_environment_anchors(source_composition)
|
source_composition = _sanitize_hardcore_environment_anchors(source_composition)
|
||||||
@@ -6302,6 +6697,7 @@ def _build_custom_row(
|
|||||||
"composition": composition,
|
"composition": composition,
|
||||||
"source_composition": source_composition,
|
"source_composition": source_composition,
|
||||||
"composition_prompt": _composition_prompt(composition),
|
"composition_prompt": _composition_prompt(composition),
|
||||||
|
"composition_config": parsed_composition_config if _composition_config_active(parsed_composition_config) else {},
|
||||||
"role_graph": role_graph,
|
"role_graph": role_graph,
|
||||||
"source_role_graph": source_role_graph,
|
"source_role_graph": source_role_graph,
|
||||||
"pov_character_labels": pov_character_labels,
|
"pov_character_labels": pov_character_labels,
|
||||||
@@ -6441,6 +6837,7 @@ def build_prompt(
|
|||||||
expression_phase: str = "",
|
expression_phase: str = "",
|
||||||
hardcore_position_config: str | dict[str, Any] | None = None,
|
hardcore_position_config: str | dict[str, Any] | None = None,
|
||||||
location_config: str | dict[str, Any] | None = None,
|
location_config: str | dict[str, Any] | None = None,
|
||||||
|
composition_config: str | dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
apply_pool_extensions()
|
apply_pool_extensions()
|
||||||
row_number = max(1, int(row_number))
|
row_number = max(1, int(row_number))
|
||||||
@@ -6452,6 +6849,7 @@ def build_prompt(
|
|||||||
pose_ratio = _ratio_or_none(standard_pose_ratio)
|
pose_ratio = _ratio_or_none(standard_pose_ratio)
|
||||||
parsed_seed_config = _parse_seed_config(seed_config)
|
parsed_seed_config = _parse_seed_config(seed_config)
|
||||||
parsed_location_config = _parse_location_config(location_config)
|
parsed_location_config = _parse_location_config(location_config)
|
||||||
|
parsed_composition_config = _parse_composition_config(composition_config)
|
||||||
content_rng = _axis_rng(parsed_seed_config, "content", seed, row_number)
|
content_rng = _axis_rng(parsed_seed_config, "content", seed, row_number)
|
||||||
pose_axis_rng = _axis_rng(parsed_seed_config, "pose", seed, row_number)
|
pose_axis_rng = _axis_rng(parsed_seed_config, "pose", seed, row_number)
|
||||||
person_rng = _axis_rng(parsed_seed_config, "person", seed, row_number)
|
person_rng = _axis_rng(parsed_seed_config, "person", seed, row_number)
|
||||||
@@ -6525,6 +6923,7 @@ def build_prompt(
|
|||||||
expression_phase,
|
expression_phase,
|
||||||
hardcore_position_config,
|
hardcore_position_config,
|
||||||
parsed_location_config,
|
parsed_location_config,
|
||||||
|
parsed_composition_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
if row.get("source") == "built_in_generator":
|
if row.get("source") == "built_in_generator":
|
||||||
@@ -6535,6 +6934,13 @@ def build_prompt(
|
|||||||
seed,
|
seed,
|
||||||
row_number,
|
row_number,
|
||||||
)
|
)
|
||||||
|
row = _apply_composition_config_to_legacy_row(
|
||||||
|
row,
|
||||||
|
parsed_composition_config,
|
||||||
|
parsed_seed_config,
|
||||||
|
seed,
|
||||||
|
row_number,
|
||||||
|
)
|
||||||
if not expression_enabled:
|
if not expression_enabled:
|
||||||
row = _disable_row_expression(row, "disabled")
|
row = _disable_row_expression(row, "disabled")
|
||||||
if extra_positive.strip():
|
if extra_positive.strip():
|
||||||
@@ -6563,6 +6969,7 @@ def build_prompt_from_configs(
|
|||||||
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
||||||
hardcore_position_config: str | dict[str, Any] | None = "",
|
hardcore_position_config: str | dict[str, Any] | None = "",
|
||||||
location_config: str | dict[str, Any] | None = "",
|
location_config: str | dict[str, Any] | None = "",
|
||||||
|
composition_config: str | dict[str, Any] | None = "",
|
||||||
extra_positive: str = "",
|
extra_positive: str = "",
|
||||||
extra_negative: str = "",
|
extra_negative: str = "",
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -6599,6 +7006,7 @@ def build_prompt_from_configs(
|
|||||||
character_cast=character_cast or "",
|
character_cast=character_cast or "",
|
||||||
hardcore_position_config=hardcore_position_config or "",
|
hardcore_position_config=hardcore_position_config or "",
|
||||||
location_config=location_config or "",
|
location_config=location_config or "",
|
||||||
|
composition_config=composition_config or "",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -7382,6 +7790,7 @@ def build_insta_of_pair(
|
|||||||
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
||||||
hardcore_position_config: str | dict[str, Any] | None = "",
|
hardcore_position_config: str | dict[str, Any] | None = "",
|
||||||
location_config: str | dict[str, Any] | None = "",
|
location_config: str | dict[str, Any] | None = "",
|
||||||
|
composition_config: str | dict[str, Any] | None = "",
|
||||||
extra_positive: str = "",
|
extra_positive: str = "",
|
||||||
extra_negative: str = "",
|
extra_negative: str = "",
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -7460,6 +7869,7 @@ def build_insta_of_pair(
|
|||||||
character_profile="" if primary_slot else character_profile or "",
|
character_profile="" if primary_slot else character_profile or "",
|
||||||
character_cast="",
|
character_cast="",
|
||||||
location_config=location_config or "",
|
location_config=location_config or "",
|
||||||
|
composition_config=composition_config or "",
|
||||||
)
|
)
|
||||||
soft_row["expression_intensity_source"] = soft_expression_intensity_source
|
soft_row["expression_intensity_source"] = soft_expression_intensity_source
|
||||||
if primary_slot_context:
|
if primary_slot_context:
|
||||||
@@ -7518,6 +7928,7 @@ def build_insta_of_pair(
|
|||||||
expression_phase="hardcore",
|
expression_phase="hardcore",
|
||||||
hardcore_position_config=hardcore_position_config or "",
|
hardcore_position_config=hardcore_position_config or "",
|
||||||
location_config=location_config or "",
|
location_config=location_config or "",
|
||||||
|
composition_config=composition_config or "",
|
||||||
)
|
)
|
||||||
hard_row["hardcore_detail_density"] = options["hardcore_detail_density"]
|
hard_row["hardcore_detail_density"] = options["hardcore_detail_density"]
|
||||||
hard_row["pov_character_labels"] = pov_character_labels
|
hard_row["pov_character_labels"] = pov_character_labels
|
||||||
|
|||||||
Reference in New Issue
Block a user