Extract row text field resolution

This commit is contained in:
2026-06-27 10:18:26 +02:00
parent a5b648eb98
commit 09eaafc8f6
5 changed files with 78 additions and 17 deletions
@@ -195,6 +195,10 @@ Already isolated:
filtering, expression-disabled handling, per-character expression promotion, filtering, expression-disabled handling, per-character expression promotion,
POV composition adaptation, and pose-category environment sanitizing live in POV composition adaptation, and pose-category environment sanitizing live in
`row_prompt_axes.py`; `prompt_builder.py` keeps a public delegate wrapper. `row_prompt_axes.py`; `prompt_builder.py` keeps a public delegate wrapper.
- row prompt/caption text-field resolution, prompt/caption template selection,
safe formatting, configured-cast descriptor insertion, and POV directive
insertion live in `row_rendering.py`; `prompt_builder.py` keeps public
delegate wrappers.
- row expression text cleanup, expression route resolution, expression - row expression text cleanup, expression route resolution, expression
intensity weighting, character-slot/cast expression override resolution, and intensity weighting, character-slot/cast expression override resolution, and
per-character expression picking plus action-aware character-expression per-character expression picking plus action-aware character-expression
+1 -1
View File
@@ -72,7 +72,7 @@ Core helper ownership:
| `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. |
| `row_item.py` | Row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse axis compatibility filters. | | `row_item.py` | Row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse axis compatibility filters. |
| `row_category_route.py` | Row category/subcategory/item route resolution, hardcore position-category filtering, cast-count adjustment, pose-vs-content seed-axis choice, item metadata collection, and pose-category item sanitizing. | | `row_category_route.py` | Row category/subcategory/item route resolution, hardcore position-category filtering, cast-count adjustment, pose-vs-content seed-axis choice, item metadata collection, and pose-category item sanitizing. |
| `row_rendering.py` | Row prompt/caption template selection, safe formatting, default prompt templates, configured-cast descriptor insertion, and POV directive insertion. | | `row_rendering.py` | Row prompt/caption text-field resolution, template selection, safe formatting, default prompt templates, configured-cast descriptor insertion, and POV directive insertion. |
| `row_assembly.py` | Final custom-row dictionary assembly behind `CustomRowAssemblyRequest`, render-context metadata population, prompt/caption rendering delegation, row-base indexing, cast/profile/slot metadata copying, and disabled-expression cleanup. | | `row_assembly.py` | Final custom-row dictionary assembly behind `CustomRowAssemblyRequest`, render-context metadata population, prompt/caption rendering delegation, row-base indexing, cast/profile/slot metadata copying, and disabled-expression cleanup. |
| `row_route_metadata.py` | Row action/position route metadata resolution, template metadata precedence, inferred position-key merging, and source action-family fallback. | | `row_route_metadata.py` | Row action/position route metadata resolution, template metadata precedence, inferred position-key merging, and source action-family fallback. |
| `row_generation.py` | Built-in legacy row generation, auto-weighted/auto-full selection, row mode randomization, ratio clamps, and expression-intensity randomization. | | `row_generation.py` | Built-in legacy row generation, auto-weighted/auto-full selection, row mode randomization, ratio clamps, and expression-intensity randomization. |
+13 -16
View File
@@ -812,6 +812,14 @@ def _format(template: str, context: dict[str, Any]) -> str:
return row_rendering_policy.format_template(template, context) return row_rendering_policy.format_template(template, context)
def _row_text_fields(
category: dict[str, Any],
subcategory: dict[str, Any],
item: Any,
) -> row_rendering_policy.RowTextFields:
return row_rendering_policy.resolve_row_text_fields(category, subcategory, item)
def _clean_prompt_punctuation(text: str) -> str: def _clean_prompt_punctuation(text: str) -> str:
return row_expression_policy.clean_prompt_punctuation(text) return row_expression_policy.clean_prompt_punctuation(text)
@@ -2269,18 +2277,7 @@ def _build_custom_row(
position_key = str(action_route.get("position_key") or "") position_key = str(action_route.get("position_key") or "")
action_family = str(action_route.get("action_family") or "") action_family = str(action_route.get("action_family") or "")
negative_prompt = str(_merged_field(category, subcategory, item, "negative_prompt", g.NEGATIVE_PROMPT)) text_fields = _row_text_fields(category, subcategory, item)
positive_suffix = str(_merged_field(category, subcategory, item, "positive_suffix", GENERIC_POSITIVE_SUFFIX))
style = str(
_merged_field(
category,
subcategory,
item,
"style",
"sexy but tasteful adult pin-up coloured-pencil comic illustration",
)
)
item_label = str(_merged_field(category, subcategory, item, "item_label", category["name"]))
assembly_request = row_assembly_policy.CustomRowAssemblyRequest( assembly_request = row_assembly_policy.CustomRowAssemblyRequest(
row_number=row_number, row_number=row_number,
@@ -2295,10 +2292,10 @@ def _build_custom_row(
item_axis_values=item_axis_values, item_axis_values=item_axis_values,
item_template_metadata=item_template_metadata, item_template_metadata=item_template_metadata,
formatter_hints=item_formatter_hints, formatter_hints=item_formatter_hints,
item_label=item_label, item_label=text_fields.item_label,
style=style, style=text_fields.style,
positive_suffix=positive_suffix, positive_suffix=text_fields.positive_suffix,
negative_prompt=negative_prompt, negative_prompt=text_fields.negative_prompt,
scene_slug=scene_slug, scene_slug=scene_slug,
scene=scene, scene=scene,
pose=pose, pose=pose,
+29
View File
@@ -1,11 +1,16 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from string import Formatter from string import Formatter
from typing import Any from typing import Any
try: try:
from . import category_library as category_policy
from . import generate_prompt_batches as g
from . import row_camera as row_camera_policy from . import row_camera as row_camera_policy
except ImportError: # Allows local smoke tests from the repository root. except ImportError: # Allows local smoke tests from the repository root.
import category_library as category_policy
import generate_prompt_batches as g
import row_camera as row_camera_policy import row_camera as row_camera_policy
@@ -14,6 +19,17 @@ GENERIC_POSITIVE_SUFFIX = (
"pastel skin tones, muted blues and pinks, warm sensual lighting, and tactile textured paper." "pastel skin tones, muted blues and pinks, warm sensual lighting, and tactile textured paper."
) )
DEFAULT_STYLE = "sexy but tasteful adult pin-up coloured-pencil comic illustration"
@dataclass(frozen=True)
class RowTextFields:
negative_prompt: str
positive_suffix: str
style: str
item_label: str
SINGLE_TEMPLATE = ( SINGLE_TEMPLATE = (
"A {subject}: {style}, {age}, {body_phrase}, {skin}, {hair}, {eyes}. " "A {subject}: {style}, {age}, {body_phrase}, {skin}, {hair}, {eyes}. "
"{item_label}: {item}. Scene: {scene}. Pose: {pose}. Facial expression: {expression}. " "{item_label}: {item}. Scene: {scene}. Pose: {pose}. Facial expression: {expression}. "
@@ -56,6 +72,19 @@ def format_template(template: str, context: dict[str, Any]) -> str:
return template.format_map(safe_context) return template.format_map(safe_context)
def resolve_row_text_fields(category: dict[str, Any], subcategory: dict[str, Any], item: Any) -> RowTextFields:
return RowTextFields(
negative_prompt=str(
category_policy.merged_field(category, subcategory, item, "negative_prompt", g.NEGATIVE_PROMPT)
),
positive_suffix=str(
category_policy.merged_field(category, subcategory, item, "positive_suffix", GENERIC_POSITIVE_SUFFIX)
),
style=str(category_policy.merged_field(category, subcategory, item, "style", DEFAULT_STYLE)),
item_label=str(category_policy.merged_field(category, subcategory, item, "item_label", category["name"])),
)
def default_prompt_template(subject_type: str) -> str: def default_prompt_template(subject_type: str) -> str:
if subject_type in ("woman", "man"): if subject_type in ("woman", "man"):
return SINGLE_TEMPLATE return SINGLE_TEMPLATE
+31
View File
@@ -1714,6 +1714,37 @@ def smoke_row_rendering_policy() -> None:
row_rendering.prompt_template_for({}, {}, {}, "group") == row_rendering.GROUP_TEMPLATE, row_rendering.prompt_template_for({}, {}, {}, "group") == row_rendering.GROUP_TEMPLATE,
"Row rendering default group template changed", "Row rendering default group template changed",
) )
category_text = {
"name": "Category Label",
"negative_prompt": "category negative",
"positive_suffix": "category suffix",
"style": "category style",
"item_label": "Category Item",
}
subcategory_text = {
"negative_prompt": "subcategory negative",
"positive_suffix": "subcategory suffix",
"style": "subcategory style",
}
item_text = {
"negative_prompt": "item negative",
"style": "item style",
}
_expect(
pb._row_text_fields(category_text, subcategory_text, item_text)
== row_rendering.resolve_row_text_fields(category_text, subcategory_text, item_text),
"Prompt builder row text field wrapper should delegate to row_rendering",
)
text_fields = row_rendering.resolve_row_text_fields(category_text, subcategory_text, item_text)
_expect(text_fields.negative_prompt == "item negative", "Row text fields did not prefer item negative prompt")
_expect(text_fields.positive_suffix == "subcategory suffix", "Row text fields did not prefer subcategory suffix")
_expect(text_fields.style == "item style", "Row text fields did not prefer item style")
_expect(text_fields.item_label == "Category Item", "Row text fields did not fall back to category item label")
default_text_fields = row_rendering.resolve_row_text_fields({"name": "Default Category"}, {}, {})
_expect(default_text_fields.negative_prompt, "Row text fields lost default negative prompt")
_expect(default_text_fields.positive_suffix == row_rendering.GENERIC_POSITIVE_SUFFIX, "Row text fields lost suffix default")
_expect(default_text_fields.style == row_rendering.DEFAULT_STYLE, "Row text fields lost style default")
_expect(default_text_fields.item_label == "Default Category", "Row text fields lost category-name label default")
context = { context = {
"trigger": Trigger, "trigger": Trigger,