Move character slot label policy
This commit is contained in:
@@ -2,6 +2,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
try:
|
||||||
|
from . import character_config as character_policy
|
||||||
|
except ImportError: # Allows local smoke tests with top-level imports.
|
||||||
|
import character_config as character_policy
|
||||||
|
|
||||||
|
|
||||||
Choose = Callable[[Any, list[tuple[str, str, str]]], tuple[str, str, str]]
|
Choose = Callable[[Any, list[tuple[str, str, str]]], tuple[str, str, str]]
|
||||||
|
|
||||||
@@ -37,6 +42,30 @@ def cast_summary_phrase(women_count: int, men_count: int) -> str:
|
|||||||
return f"{women_count} {women_label}, {men_count} {men_label}, {person_count} total adults"
|
return f"{women_count} {women_label}, {men_count} {men_label}, {person_count} total adults"
|
||||||
|
|
||||||
|
|
||||||
|
def explicit_character_slot_label(slot: dict[str, Any]) -> str:
|
||||||
|
label = str(slot.get("label") or "").strip().upper()
|
||||||
|
if label in character_policy.CHARACTER_LABEL_CHOICES and label != "AUTO_CHAIN":
|
||||||
|
return label
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def character_slot_label_map(slots: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
||||||
|
label_map: dict[str, dict[str, Any]] = {}
|
||||||
|
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
for subject_type, prefix in (("woman", "Woman"), ("man", "Man")):
|
||||||
|
subject_slots = [slot for slot in slots if slot.get("subject_type") == subject_type]
|
||||||
|
auto_slots = [slot for slot in subject_slots if not explicit_character_slot_label(slot)]
|
||||||
|
for index, slot in enumerate(reversed(auto_slots)):
|
||||||
|
if index >= len(letters):
|
||||||
|
break
|
||||||
|
label_map[f"{prefix} {letters[index]}"] = slot
|
||||||
|
for slot in subject_slots:
|
||||||
|
explicit = explicit_character_slot_label(slot)
|
||||||
|
if explicit:
|
||||||
|
label_map[f"{prefix} {explicit}"] = slot
|
||||||
|
return label_map
|
||||||
|
|
||||||
|
|
||||||
def configured_cast_context(women_count: int, men_count: int) -> dict[str, str]:
|
def configured_cast_context(women_count: int, men_count: int) -> dict[str, str]:
|
||||||
women_count = max(0, int(women_count))
|
women_count = max(0, int(women_count))
|
||||||
men_count = max(0, int(men_count))
|
men_count = max(0, int(men_count))
|
||||||
|
|||||||
@@ -130,9 +130,10 @@ Already isolated:
|
|||||||
parsers live in `category_cast_config.py`; `prompt_builder.py` keeps public
|
parsers live in `category_cast_config.py`; `prompt_builder.py` keeps public
|
||||||
delegate wrappers for existing nodes and tests.
|
delegate wrappers for existing nodes and tests.
|
||||||
- generation-time cast count phrases, configured-cast context metadata,
|
- generation-time cast count phrases, configured-cast context metadata,
|
||||||
scene-kind labels, cast-summary wording, and couple count normalization live
|
character-slot label assignment, scene-kind labels, cast-summary wording, and
|
||||||
in `cast_context.py`; `prompt_builder.py` keeps delegate wrappers where
|
couple count normalization live in `cast_context.py`; `prompt_builder.py`
|
||||||
existing generation paths still call the old helper names.
|
keeps delegate wrappers where existing generation paths still call the old
|
||||||
|
helper names.
|
||||||
- ethnicity/filter choices, advanced filter JSON, ethnicity-list JSON, filter
|
- ethnicity/filter choices, advanced filter JSON, ethnicity-list JSON, filter
|
||||||
parsing, and ethnicity normalization live in `filter_config.py`; character
|
parsing, and ethnicity normalization live in `filter_config.py`; character
|
||||||
routes and builder filters use `prompt_builder.py` delegate wrappers.
|
routes and builder filters use `prompt_builder.py` delegate wrappers.
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ Core helper ownership:
|
|||||||
| `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. |
|
| `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. |
|
||||||
| `category_template_metadata.py` | Object-style item-template metadata extraction, action/position family normalization, position-key normalization, key merging, and audit validation errors. |
|
| `category_template_metadata.py` | Object-style item-template metadata extraction, action/position family normalization, position-key normalization, key merging, and audit validation errors. |
|
||||||
| `category_cast_config.py` | Category preset and cast preset schemas, category/cast config JSON builders, choice lists, and config parsers used by route nodes. |
|
| `category_cast_config.py` | Category preset and cast preset schemas, category/cast config JSON builders, choice lists, and config parsers used by route nodes. |
|
||||||
| `cast_context.py` | Generation-time cast count phrases, configured-cast context metadata, cast-summary wording, scene-kind labels, and couple count normalization. |
|
| `cast_context.py` | Generation-time cast count phrases, configured-cast context metadata, character-slot label assignment, cast-summary wording, scene-kind labels, and couple count normalization. |
|
||||||
| `camera_config.py` | Camera option schema, direct/orbit/Qwen camera JSON builders, camera config parsing, plain camera directive text, and camera caption labels. |
|
| `camera_config.py` | Camera option schema, direct/orbit/Qwen camera JSON builders, camera config parsing, plain camera directive text, and camera caption labels. |
|
||||||
| `character_config.py` | Character choice lists, descriptor detail/presence/slot-seed normalization, characteristic-list JSON builders/parsers, eye labels, hair config builders/parsers, and hair phrase helpers. |
|
| `character_config.py` | Character choice lists, descriptor detail/presence/slot-seed normalization, characteristic-list JSON builders/parsers, eye labels, hair config builders/parsers, and hair phrase helpers. |
|
||||||
| `character_profile.py` | Character manual-detail config, profile name/path policy, profile JSON normalization, descriptor assembly, save/load/rename/delete operations, fallback profile loading, and context override application. |
|
| `character_profile.py` | Character manual-detail config, profile name/path policy, profile JSON normalization, descriptor assembly, save/load/rename/delete operations, fallback profile loading, and context override application. |
|
||||||
@@ -390,7 +390,8 @@ Important behavior:
|
|||||||
Edit targets:
|
Edit targets:
|
||||||
|
|
||||||
- Appearance field generation: `_context_from_character_slot`,
|
- Appearance field generation: `_context_from_character_slot`,
|
||||||
`_character_context_for_label`; pair cast descriptor entry assembly:
|
`_character_context_for_label`; character-slot label assignment:
|
||||||
|
`cast_context.character_slot_label_map`; pair cast descriptor entry assembly:
|
||||||
`pair_cast.cast_descriptor_entries`.
|
`pair_cast.cast_descriptor_entries`.
|
||||||
- Profile save/load: `SxCPCharacterProfileSave`,
|
- Profile save/load: `SxCPCharacterProfileSave`,
|
||||||
`SxCPCharacterProfileLoad`, profile policy in `character_profile.py`, and
|
`SxCPCharacterProfileLoad`, profile policy in `character_profile.py`, and
|
||||||
|
|||||||
+2
-18
@@ -2432,27 +2432,11 @@ def build_character_slot_json(
|
|||||||
|
|
||||||
|
|
||||||
def _slot_explicit_label(slot: dict[str, Any]) -> str:
|
def _slot_explicit_label(slot: dict[str, Any]) -> str:
|
||||||
label = str(slot.get("label") or "").strip().upper()
|
return cast_context_policy.explicit_character_slot_label(slot)
|
||||||
if label in CHARACTER_LABEL_CHOICES and label != "AUTO_CHAIN":
|
|
||||||
return label
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def _character_slot_label_map(slots: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
def _character_slot_label_map(slots: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
||||||
label_map: dict[str, dict[str, Any]] = {}
|
return cast_context_policy.character_slot_label_map(slots)
|
||||||
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
for subject_type, prefix in (("woman", "Woman"), ("man", "Man")):
|
|
||||||
subject_slots = [slot for slot in slots if slot.get("subject_type") == subject_type]
|
|
||||||
auto_slots = [slot for slot in subject_slots if not _slot_explicit_label(slot)]
|
|
||||||
for index, slot in enumerate(reversed(auto_slots)):
|
|
||||||
if index >= len(letters):
|
|
||||||
break
|
|
||||||
label_map[f"{prefix} {letters[index]}"] = slot
|
|
||||||
for slot in subject_slots:
|
|
||||||
explicit = _slot_explicit_label(slot)
|
|
||||||
if explicit:
|
|
||||||
label_map[f"{prefix} {explicit}"] = slot
|
|
||||||
return label_map
|
|
||||||
|
|
||||||
|
|
||||||
def _pov_character_labels(
|
def _pov_character_labels(
|
||||||
|
|||||||
@@ -682,6 +682,25 @@ def smoke_category_cast_config_policy() -> None:
|
|||||||
== ("two women", "two women", "close affectionate couple pose", 2, 0),
|
== ("two women", "two women", "close affectionate couple pose", 2, 0),
|
||||||
"Couple type count override for two women changed",
|
"Couple type count override for two women changed",
|
||||||
)
|
)
|
||||||
|
label_slots = [
|
||||||
|
{"subject_type": "woman", "label": "auto_chain", "name": "older auto"},
|
||||||
|
{"subject_type": "woman", "label": "auto_chain", "name": "newer auto"},
|
||||||
|
{"subject_type": "man", "label": "B", "name": "explicit man"},
|
||||||
|
]
|
||||||
|
label_map = cast_context.character_slot_label_map(label_slots)
|
||||||
|
_expect(
|
||||||
|
label_map.get("Woman A", {}).get("name") == "newer auto"
|
||||||
|
and label_map.get("Woman B", {}).get("name") == "older auto",
|
||||||
|
"Character slot auto-chain label order changed",
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
label_map.get("Man B", {}).get("name") == "explicit man",
|
||||||
|
"Character slot explicit label mapping changed",
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
pb._character_slot_label_map(label_slots) == label_map,
|
||||||
|
"Prompt builder character slot label map should delegate to cast_context",
|
||||||
|
)
|
||||||
|
|
||||||
category_config = json.loads(pb.build_category_config_json("hardcore_pose", "Foreplay and teasing"))
|
category_config = json.loads(pb.build_category_config_json("hardcore_pose", "Foreplay and teasing"))
|
||||||
_expect(category_config.get("category") == "Hardcore sexual poses", "Category config lost hardcore category mapping")
|
_expect(category_config.get("category") == "Hardcore sexual poses", "Category config lost hardcore category mapping")
|
||||||
|
|||||||
Reference in New Issue
Block a user