Extract row location policy
This commit is contained in:
@@ -146,8 +146,9 @@ Already isolated:
|
|||||||
`prompt_builder.py` keeps public delegate wrappers.
|
`prompt_builder.py` keeps public delegate wrappers.
|
||||||
- location/composition config presets, themed location packs, custom
|
- location/composition config presets, themed location packs, custom
|
||||||
location/composition entry parsing, merge behavior, and config parsing live
|
location/composition entry parsing, merge behavior, and config parsing live
|
||||||
in `location_config.py`; `prompt_builder.py` still applies selected configs
|
in `location_config.py`; built-in row location/composition config
|
||||||
to rows.
|
application, source metadata, and prompt/caption rewrites live in
|
||||||
|
`row_location.py`.
|
||||||
- hardcore position/action-filter choices, selected-position normalization,
|
- hardcore position/action-filter choices, selected-position normalization,
|
||||||
config JSON builders/parsers, focus-policy toggles, subcategory allow-list
|
config JSON builders/parsers, focus-policy toggles, subcategory allow-list
|
||||||
policy, position-key detection, category filtering, and item-template/axis
|
policy, position-key detection, category filtering, and item-template/axis
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ Core helper ownership:
|
|||||||
| `generation_profile_config.py` | Generation profile presets, profile option overrides, trigger policy, expression/pose/clothing config normalization, and profile config parsing. |
|
| `generation_profile_config.py` | Generation profile presets, profile option overrides, trigger policy, expression/pose/clothing config normalization, and profile config parsing. |
|
||||||
| `seed_config.py` | Seed axis salts/aliases, seed mode choices, global/axis lock JSON builders, seed config parsing, row seed math, and deterministic axis RNG construction. |
|
| `seed_config.py` | Seed axis salts/aliases, seed mode choices, global/axis lock JSON builders, seed config parsing, row seed math, and deterministic axis RNG construction. |
|
||||||
| `location_config.py` | Location/composition preset schemas, themed location packs, custom location/composition parsing, pool merge behavior, and location/composition config parsing. |
|
| `location_config.py` | Location/composition preset schemas, themed location packs, custom location/composition parsing, pool merge behavior, and location/composition config parsing. |
|
||||||
|
| `row_location.py` | Built-in row location/composition config application, deterministic scene/composition choice, source metadata, and legacy prompt/caption rewrites. |
|
||||||
| `hardcore_position_config.py` | Hardcore position/action-filter choices, selected-position normalization, config JSON builders/parsers, focus-policy toggles, subcategory allow-list policy, position-key detection, and category/template/axis filtering. |
|
| `hardcore_position_config.py` | Hardcore position/action-filter choices, selected-position normalization, config JSON builders/parsers, focus-policy toggles, subcategory allow-list policy, position-key detection, and category/template/axis filtering. |
|
||||||
| `pair_options.py` | Insta/OF option schema/defaults, softcore category/outfit/pose pools, partner outfit pools, clothing-continuity labels, negatives, and hardcore cast count policy. |
|
| `pair_options.py` | Insta/OF option schema/defaults, softcore category/outfit/pose pools, partner outfit pools, clothing-continuity labels, negatives, and hardcore cast count policy. |
|
||||||
| `pair_rows.py` | Insta/OF soft/hard row creation, softcore expression override resolution, Woman A slot context application, soft outfit/pose overrides, and POV row fields. |
|
| `pair_rows.py` | Insta/OF soft/hard row creation, softcore expression override resolution, Woman A slot context application, soft outfit/pose overrides, and POV row fields. |
|
||||||
@@ -132,8 +133,8 @@ These recipes identify the intended road before editing prompt text.
|
|||||||
| Same woman, same room, softcore and hardcore outputs | `Character Slot/Profile` -> `Insta/OF Options` -> `Insta/OF Prompt Pair` | `continuity=same_creator_same_room`; set `softcore_cast` as needed; use pair metadata into formatter | `build_insta_of_pair`, `softcore_row`, `hardcore_row`, pair metadata fields |
|
| Same woman, same room, softcore and hardcore outputs | `Character Slot/Profile` -> `Insta/OF Options` -> `Insta/OF Prompt Pair` | `continuity=same_creator_same_room`; set `softcore_cast` as needed; use pair metadata into formatter | `build_insta_of_pair`, `softcore_row`, `hardcore_row`, pair metadata fields |
|
||||||
| Same cast in softcore and hardcore | Character slot chain -> `Insta/OF Options` | `softcore_cast=same_as_hardcore`; configure partner slots/outfits if needed | `_insta_of_partner_styling`, character slot clothing, pair Krea branch |
|
| Same cast in softcore and hardcore | Character slot chain -> `Insta/OF Options` | `softcore_cast=same_as_hardcore`; configure partner slots/outfits if needed | `_insta_of_partner_styling`, character slot clothing, pair Krea branch |
|
||||||
| Change only outfit/clothing | Character clothing or category content route | Keep `person_seed`, `scene_seed`, `pose_seed`; change `content_seed`; slot `softcore_outfit` overrides Insta/OF outfit | `SxCP Character Clothing`, `pair_options.py`, category item templates |
|
| Change only outfit/clothing | Character clothing or category content route | Keep `person_seed`, `scene_seed`, `pose_seed`; change `content_seed`; slot `softcore_outfit` overrides Insta/OF outfit | `SxCP Character Clothing`, `pair_options.py`, category item templates |
|
||||||
| Force a custom location | `SxCP Location Pool` or `SxCP Location Theme` -> builder/pair | `combine_mode=replace` to force; `add` to mix with category scenes | `_scene_pool`, `_apply_location_config_to_legacy_row`, camera scene adapter |
|
| Force a custom location | `SxCP Location Pool` or `SxCP Location Theme` -> builder/pair | `combine_mode=replace` to force; `add` to mix with category scenes | `_scene_pool`, `row_location.apply_location_config_to_legacy_row`, camera scene adapter |
|
||||||
| Force a custom frame/composition | `SxCP Composition Pool` or `SxCP Location Theme` -> builder/pair | `combine_mode=replace` to force; `add` to mix | `_composition_pool`, `_apply_composition_config_to_legacy_row`, Krea composition phrase |
|
| Force a custom frame/composition | `SxCP Composition Pool` or `SxCP Location Theme` -> builder/pair | `combine_mode=replace` to force; `add` to mix | `_composition_pool`, `row_location.apply_composition_config_to_legacy_row`, Krea composition phrase |
|
||||||
| Use Qwen/orbit camera geometry | Qwen/orbit node -> camera_config -> builder/pair | For pair, use `softcore_camera_config` and/or `hardcore_camera_config`; set mode from config in options | `_camera_config_with_mode`, `_camera_directive`, `_camera_scene_directive_for_context` |
|
| Use Qwen/orbit camera geometry | Qwen/orbit node -> camera_config -> builder/pair | For pair, use `softcore_camera_config` and/or `hardcore_camera_config`; set mode from config in options | `_camera_config_with_mode`, `_camera_directive`, `_camera_scene_directive_for_context` |
|
||||||
| Use Krea2 for only hard prompt from a pair | Pair `metadata_json` -> Krea2 Formatter | `target=hardcore`, `input_hint=metadata_json` or auto with metadata connected | `_insta_pair_to_krea`, hard row fields |
|
| Use Krea2 for only hard prompt from a pair | Pair `metadata_json` -> Krea2 Formatter | `target=hardcore`, `input_hint=metadata_json` or auto with metadata connected | `_insta_pair_to_krea`, hard row fields |
|
||||||
| Convert builder output to SDXL tags | Builder/pair metadata -> SDXL Formatter | Use metadata input; set `target`; select style and quality preset | `_row_core_tags`, `_soft_tags`, `_hard_tags` |
|
| Convert builder output to SDXL tags | Builder/pair metadata -> SDXL Formatter | Use metadata input; set `target`; select style and quality preset | `_row_core_tags`, `_soft_tags`, `_hard_tags` |
|
||||||
|
|||||||
+4
-98
@@ -41,6 +41,7 @@ try:
|
|||||||
from . import pair_options
|
from . import pair_options
|
||||||
from . import row_normalization as row_policy
|
from . import row_normalization as row_policy
|
||||||
from . import row_camera as row_camera_policy
|
from . import row_camera as row_camera_policy
|
||||||
|
from . import row_location as row_location_policy
|
||||||
from . import seed_config as seed_policy
|
from . import seed_config as seed_policy
|
||||||
from .hardcore_text_cleanup import (
|
from .hardcore_text_cleanup import (
|
||||||
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
||||||
@@ -82,6 +83,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
|||||||
import pair_options
|
import pair_options
|
||||||
import row_normalization as row_policy
|
import row_normalization as row_policy
|
||||||
import row_camera as row_camera_policy
|
import row_camera as row_camera_policy
|
||||||
|
import row_location as row_location_policy
|
||||||
import seed_config as seed_policy
|
import seed_config as seed_policy
|
||||||
from hardcore_text_cleanup import (
|
from hardcore_text_cleanup import (
|
||||||
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
||||||
@@ -3122,102 +3124,6 @@ def _scene_pool(
|
|||||||
return scene_entries or fallback
|
return scene_entries or fallback
|
||||||
|
|
||||||
|
|
||||||
def _legacy_scene_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_SCENES)
|
|
||||||
return list(g.SCENES)
|
|
||||||
|
|
||||||
|
|
||||||
def _legacy_scene_text_for_slug(slug: str) -> str:
|
|
||||||
for entry in list(g.SCENES) + list(g.GROUP_SCENES):
|
|
||||||
entry_slug, entry_text = _pair_from(entry)
|
|
||||||
if entry_slug == slug:
|
|
||||||
return entry_text
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def _apply_location_config_to_legacy_row(
|
|
||||||
row: dict[str, Any],
|
|
||||||
location_config: dict[str, Any],
|
|
||||||
seed_config: dict[str, int],
|
|
||||||
seed: int,
|
|
||||||
row_number: int,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
if not _location_config_active(location_config):
|
|
||||||
return row
|
|
||||||
location_entries = _list_from(location_config.get("scene_entries"))
|
|
||||||
if location_config.get("apply_mode") == "add":
|
|
||||||
choices = _legacy_scene_entries_for_row(row)
|
|
||||||
_unique_extend(choices, location_entries)
|
|
||||||
else:
|
|
||||||
choices = location_entries
|
|
||||||
scene_rng = _axis_rng(seed_config, "scene", seed, row_number)
|
|
||||||
scene_slug, scene_text = _choose_pair(scene_rng, choices)
|
|
||||||
old_slug = str(row.get("scene") or "")
|
|
||||||
old_text = _legacy_scene_text_for_slug(old_slug)
|
|
||||||
row["source_scene"] = old_slug
|
|
||||||
row["source_scene_text"] = old_text
|
|
||||||
row["scene"] = scene_slug
|
|
||||||
row["scene_text"] = scene_text
|
|
||||||
row["location_config"] = location_config
|
|
||||||
if old_text:
|
|
||||||
row["prompt"] = str(row.get("prompt") or "").replace(f"Scene: {old_text}.", f"Scene: {scene_text}.")
|
|
||||||
row["caption"] = str(row.get("caption") or "").replace(f", {old_text},", f", {scene_text},")
|
|
||||||
else:
|
|
||||||
row["prompt"] = re.sub(
|
|
||||||
r"Scene:\s*.*?\.\s*Pose:",
|
|
||||||
f"Scene: {scene_text}. Pose:",
|
|
||||||
str(row.get("prompt") or ""),
|
|
||||||
count=1,
|
|
||||||
)
|
|
||||||
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 _expression_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any) -> list[Any]:
|
def _expression_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any) -> list[Any]:
|
||||||
return _configured_pool(
|
return _configured_pool(
|
||||||
category,
|
category,
|
||||||
@@ -3919,14 +3825,14 @@ def build_prompt(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if row.get("source") == "built_in_generator":
|
if row.get("source") == "built_in_generator":
|
||||||
row = _apply_location_config_to_legacy_row(
|
row = row_location_policy.apply_location_config_to_legacy_row(
|
||||||
row,
|
row,
|
||||||
parsed_location_config,
|
parsed_location_config,
|
||||||
parsed_seed_config,
|
parsed_seed_config,
|
||||||
seed,
|
seed,
|
||||||
row_number,
|
row_number,
|
||||||
)
|
)
|
||||||
row = _apply_composition_config_to_legacy_row(
|
row = row_location_policy.apply_composition_config_to_legacy_row(
|
||||||
row,
|
row,
|
||||||
parsed_composition_config,
|
parsed_composition_config,
|
||||||
parsed_seed_config,
|
parsed_seed_config,
|
||||||
|
|||||||
+199
@@ -0,0 +1,199 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
try:
|
||||||
|
from . import generate_prompt_batches as g
|
||||||
|
from . import location_config as location_policy
|
||||||
|
from . import row_camera
|
||||||
|
from . import seed_config as seed_policy
|
||||||
|
except ImportError: # Allows local smoke tests with top-level imports.
|
||||||
|
import generate_prompt_batches as g
|
||||||
|
import location_config as location_policy
|
||||||
|
import row_camera
|
||||||
|
import seed_config as seed_policy
|
||||||
|
|
||||||
|
|
||||||
|
def _list_from(value: Any) -> list[Any]:
|
||||||
|
if value is None:
|
||||||
|
return []
|
||||||
|
if isinstance(value, list):
|
||||||
|
return value
|
||||||
|
return [value]
|
||||||
|
|
||||||
|
|
||||||
|
def _unique_extend(target: list[Any], additions: list[Any]) -> None:
|
||||||
|
seen = set()
|
||||||
|
for item in target:
|
||||||
|
try:
|
||||||
|
seen.add(json.dumps(item, sort_keys=True))
|
||||||
|
except TypeError:
|
||||||
|
seen.add(repr(item))
|
||||||
|
for item in additions:
|
||||||
|
try:
|
||||||
|
marker = json.dumps(item, sort_keys=True)
|
||||||
|
except TypeError:
|
||||||
|
marker = repr(item)
|
||||||
|
if marker not in seen:
|
||||||
|
target.append(item)
|
||||||
|
seen.add(marker)
|
||||||
|
|
||||||
|
|
||||||
|
def _pair_from(value: Any) -> tuple[str, str]:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
text = str(
|
||||||
|
value.get("prompt")
|
||||||
|
or value.get("description")
|
||||||
|
or value.get("text")
|
||||||
|
or value.get("name")
|
||||||
|
or ""
|
||||||
|
).strip()
|
||||||
|
slug = str(value.get("slug") or g.slugify(str(value.get("name") or text)) or "custom").strip()
|
||||||
|
if not text:
|
||||||
|
raise ValueError(f"Pair extension is missing prompt text: {value!r}")
|
||||||
|
return slug, text
|
||||||
|
if isinstance(value, (list, tuple)) and len(value) == 2:
|
||||||
|
return str(value[0]), str(value[1])
|
||||||
|
text = str(value).strip()
|
||||||
|
if not text:
|
||||||
|
raise ValueError("Pair extension cannot be empty")
|
||||||
|
return g.slugify(text) or "custom", text
|
||||||
|
|
||||||
|
|
||||||
|
def _weighted_choice(rng: random.Random, items: list[Any]) -> Any:
|
||||||
|
if not items:
|
||||||
|
raise ValueError("Cannot choose from an empty list")
|
||||||
|
weights: list[float] = []
|
||||||
|
for item in items:
|
||||||
|
weight = item.get("weight", 1.0) if isinstance(item, dict) else 1.0
|
||||||
|
try:
|
||||||
|
weights.append(max(0.0, float(weight)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
weights.append(1.0)
|
||||||
|
total = sum(weights)
|
||||||
|
if total <= 0:
|
||||||
|
return items[rng.randrange(len(items))]
|
||||||
|
pick = rng.random() * total
|
||||||
|
running = 0.0
|
||||||
|
for item, weight in zip(items, weights):
|
||||||
|
running += weight
|
||||||
|
if pick <= running:
|
||||||
|
return item
|
||||||
|
return items[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def _choose_pair(rng: random.Random, items: list[Any]) -> tuple[str, str]:
|
||||||
|
return _pair_from(_weighted_choice(rng, items))
|
||||||
|
|
||||||
|
|
||||||
|
def _choose_text(rng: random.Random, items: list[Any]) -> str:
|
||||||
|
item = _weighted_choice(rng, items)
|
||||||
|
if isinstance(item, dict):
|
||||||
|
return str(
|
||||||
|
item.get("template")
|
||||||
|
or item.get("prompt")
|
||||||
|
or item.get("text")
|
||||||
|
or item.get("description")
|
||||||
|
or item.get("name")
|
||||||
|
or ""
|
||||||
|
).strip()
|
||||||
|
return str(item).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def legacy_scene_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_SCENES)
|
||||||
|
return list(g.SCENES)
|
||||||
|
|
||||||
|
|
||||||
|
def legacy_scene_text_for_slug(slug: str) -> str:
|
||||||
|
for entry in list(g.SCENES) + list(g.GROUP_SCENES):
|
||||||
|
entry_slug, entry_text = _pair_from(entry)
|
||||||
|
if entry_slug == slug:
|
||||||
|
return entry_text
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def apply_location_config_to_legacy_row(
|
||||||
|
row: dict[str, Any],
|
||||||
|
location_config: dict[str, Any],
|
||||||
|
seed_config: dict[str, int],
|
||||||
|
seed: int,
|
||||||
|
row_number: int,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
if not location_policy.location_config_active(location_config):
|
||||||
|
return row
|
||||||
|
location_entries = _list_from(location_config.get("scene_entries"))
|
||||||
|
if location_config.get("apply_mode") == "add":
|
||||||
|
choices = legacy_scene_entries_for_row(row)
|
||||||
|
_unique_extend(choices, location_entries)
|
||||||
|
else:
|
||||||
|
choices = location_entries
|
||||||
|
scene_rng = seed_policy.axis_rng(seed_config, "scene", seed, row_number)
|
||||||
|
scene_slug, scene_text = _choose_pair(scene_rng, choices)
|
||||||
|
old_slug = str(row.get("scene") or "")
|
||||||
|
old_text = legacy_scene_text_for_slug(old_slug)
|
||||||
|
row["source_scene"] = old_slug
|
||||||
|
row["source_scene_text"] = old_text
|
||||||
|
row["scene"] = scene_slug
|
||||||
|
row["scene_text"] = scene_text
|
||||||
|
row["location_config"] = location_config
|
||||||
|
if old_text:
|
||||||
|
row["prompt"] = str(row.get("prompt") or "").replace(f"Scene: {old_text}.", f"Scene: {scene_text}.")
|
||||||
|
row["caption"] = str(row.get("caption") or "").replace(f", {old_text},", f", {scene_text},")
|
||||||
|
else:
|
||||||
|
row["prompt"] = re.sub(
|
||||||
|
r"Scene:\s*.*?\.\s*Pose:",
|
||||||
|
f"Scene: {scene_text}. Pose:",
|
||||||
|
str(row.get("prompt") or ""),
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
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 location_policy.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 = seed_policy.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: {row_camera.composition_prompt(new_composition)}."
|
||||||
|
row["source_composition"] = old_composition
|
||||||
|
row["composition"] = new_composition
|
||||||
|
row["composition_prompt"] = row_camera.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
|
||||||
@@ -45,6 +45,7 @@ import prompt_builder as pb # noqa: E402
|
|||||||
import row_normalization # noqa: E402
|
import row_normalization # noqa: E402
|
||||||
import route_metadata # noqa: E402
|
import route_metadata # noqa: E402
|
||||||
import row_camera # noqa: E402
|
import row_camera # noqa: E402
|
||||||
|
import row_location # noqa: E402
|
||||||
import server_routes # noqa: E402
|
import server_routes # noqa: E402
|
||||||
import sdxl_formatter # noqa: E402
|
import sdxl_formatter # noqa: E402
|
||||||
import sdxl_presets # noqa: E402
|
import sdxl_presets # noqa: E402
|
||||||
@@ -603,6 +604,51 @@ def smoke_location_config_policy() -> None:
|
|||||||
_expect(json.loads(themed_composition).get("composition_entries"), "Themed location did not output compositions")
|
_expect(json.loads(themed_composition).get("composition_entries"), "Themed location did not output compositions")
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_row_location_policy() -> None:
|
||||||
|
location = json.loads(
|
||||||
|
location_config.build_location_pool_json(
|
||||||
|
combine_mode="replace",
|
||||||
|
custom_locations="archive_corner: hidden archive corner with repeated shelves and warm table lamps",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
composition = json.loads(
|
||||||
|
location_config.build_composition_pool_json(
|
||||||
|
combine_mode="replace",
|
||||||
|
custom_compositions="long archive aisle composition",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
row = {
|
||||||
|
"source": "built_in_generator",
|
||||||
|
"primary_subject": "adult woman",
|
||||||
|
"scene": "unknown_old_scene",
|
||||||
|
"composition": "old frame",
|
||||||
|
"prompt": "A generated adult prompt. Scene: old room. Pose: standing. Composition: vertical old frame. Avoid: low quality.",
|
||||||
|
"caption": "sxcppnl7, generated adult prompt, old room, old frame, illustration",
|
||||||
|
}
|
||||||
|
updated = row_location.apply_location_config_to_legacy_row(dict(row), location, {}, 123, 1)
|
||||||
|
updated = row_location.apply_composition_config_to_legacy_row(updated, composition, {}, 123, 1)
|
||||||
|
_expect(updated.get("scene") == "archive_corner", "Row location policy did not select forced custom scene slug")
|
||||||
|
_expect(
|
||||||
|
updated.get("scene_text") == "hidden archive corner with repeated shelves and warm table lamps",
|
||||||
|
"Row location policy did not apply forced custom scene text",
|
||||||
|
)
|
||||||
|
_expect(updated.get("source_scene") == "unknown_old_scene", "Row location policy lost source scene slug")
|
||||||
|
_expect(
|
||||||
|
"Scene: hidden archive corner with repeated shelves and warm table lamps. Pose:" in updated.get("prompt", ""),
|
||||||
|
"Row location policy did not rewrite prompt scene",
|
||||||
|
)
|
||||||
|
_expect(updated.get("composition") == "long archive aisle composition", "Row location policy did not apply forced composition")
|
||||||
|
_expect(
|
||||||
|
updated.get("composition_prompt") == "vertical long archive aisle composition",
|
||||||
|
"Row location policy did not compute composition prompt",
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
"Composition: vertical long archive aisle composition." in updated.get("prompt", ""),
|
||||||
|
"Row location policy did not rewrite prompt composition",
|
||||||
|
)
|
||||||
|
_expect(", long archive aisle composition," in updated.get("caption", ""), "Row location policy did not rewrite caption composition")
|
||||||
|
|
||||||
|
|
||||||
def smoke_category_cast_config_policy() -> None:
|
def smoke_category_cast_config_policy() -> None:
|
||||||
_expect(pb.CATEGORY_PRESETS is category_cast_config.CATEGORY_PRESETS, "Prompt builder category presets are not delegated")
|
_expect(pb.CATEGORY_PRESETS is category_cast_config.CATEGORY_PRESETS, "Prompt builder category presets are not delegated")
|
||||||
_expect(pb.CAST_PRESETS is category_cast_config.CAST_PRESETS, "Prompt builder cast presets are not delegated")
|
_expect(pb.CAST_PRESETS is category_cast_config.CAST_PRESETS, "Prompt builder cast presets are not delegated")
|
||||||
@@ -3475,6 +3521,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("row_camera_policy", smoke_row_camera_policy),
|
("row_camera_policy", smoke_row_camera_policy),
|
||||||
("config_route_location_theme", smoke_config_route_location_theme),
|
("config_route_location_theme", smoke_config_route_location_theme),
|
||||||
("location_config_policy", smoke_location_config_policy),
|
("location_config_policy", smoke_location_config_policy),
|
||||||
|
("row_location_policy", smoke_row_location_policy),
|
||||||
("category_cast_config_policy", smoke_category_cast_config_policy),
|
("category_cast_config_policy", smoke_category_cast_config_policy),
|
||||||
("generation_profile_config_policy", smoke_generation_profile_config_policy),
|
("generation_profile_config_policy", smoke_generation_profile_config_policy),
|
||||||
("filter_config_policy", smoke_filter_config_policy),
|
("filter_config_policy", smoke_filter_config_policy),
|
||||||
|
|||||||
Reference in New Issue
Block a user