Move action expression sanitizer
This commit is contained in:
@@ -179,8 +179,8 @@ Already isolated:
|
||||
wrappers.
|
||||
- row expression text cleanup, expression intensity weighting,
|
||||
character-slot/cast expression override resolution, and per-character
|
||||
expression picking live in `row_expression.py`; `prompt_builder.py` keeps
|
||||
public delegate wrappers.
|
||||
expression picking plus action-aware character-expression sanitizing live in
|
||||
`row_expression.py`; `prompt_builder.py` keeps public delegate wrappers.
|
||||
- hardcore position/action-filter choices, selected-position normalization,
|
||||
config JSON builders/parsers, focus-policy toggles, subcategory allow-list
|
||||
policy, position-key detection, category filtering, and item-template/axis
|
||||
|
||||
@@ -85,7 +85,7 @@ Core helper ownership:
|
||||
| `subject_context.py` | Row subject-context routing for single, couple, configured-cast, group, and layout subjects, combining appearance policy, cast metadata, and generator subject pools. |
|
||||
| `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. |
|
||||
| `row_expression.py` | Row expression cleanup, expression intensity weighting, character-slot/cast expression override resolution, and per-character expression selection. |
|
||||
| `row_expression.py` | Row expression cleanup, expression intensity weighting, character-slot/cast expression override resolution, per-character expression selection, and action-aware character-expression sanitizing. |
|
||||
| `row_pools.py` | Row scene/expression/pose/composition pool routing, category inheritance handling, runtime location/composition pool overrides, and generator fallback pools. |
|
||||
| `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, hardcore cast count policy, and hardcore detail-density directives. |
|
||||
@@ -846,7 +846,7 @@ pair metadata through the core Python APIs, then verifies:
|
||||
| Wrong location | `categories/location_pools.json`, category `scene_pool`, `_scene_pool`. |
|
||||
| Location good but camera/location layout wrong | `_camera_scene_directive_for_context`, coworking adapter functions. |
|
||||
| Repeated desk/anchor in POV foreground | Coworking direction/distance/elevation helpers. |
|
||||
| Wrong expression intensity | Character slot expression settings, `row_expression.py`, expression pools. |
|
||||
| Wrong expression intensity or action-incompatible character expression | Character slot expression settings, `row_expression.py`, expression pools. |
|
||||
| Expression appears when disabled | `row_expression.disable_row_expression`, formatter expression extraction. |
|
||||
| Same hardcore action repeats | Hardcore filter config, `sexual_poses.json` weights, `hardcore_position_config.apply_hardcore_position_config_to_subcategory`. |
|
||||
| Hardcore interaction beat falls back to penetration/oral | `sexual_poses.json` interaction subcategory, `_role_graph`, and `krea_action_context.is_foreplay_text` / `krea_action_positions.hardcore_pose_anchor`. |
|
||||
|
||||
+3
-84
@@ -1454,93 +1454,12 @@ def _sanitize_character_expression_text_for_action(
|
||||
item: Any,
|
||||
axis_values: Any = None,
|
||||
) -> str:
|
||||
text = str(expression_text or "").strip()
|
||||
if not text:
|
||||
return ""
|
||||
context = " ".join(
|
||||
str(part or "").lower()
|
||||
for part in (
|
||||
return row_expression_policy.sanitize_character_expression_text_for_action(
|
||||
expression_text,
|
||||
role_graph,
|
||||
item,
|
||||
*((axis_values or {}).values() if isinstance(axis_values, dict) else ()),
|
||||
axis_values,
|
||||
)
|
||||
)
|
||||
woman_active_outercourse = (
|
||||
re.search(r"\bwoman [a-z]\b", context)
|
||||
and re.search(r"\bman [a-z]\b", context)
|
||||
and any(
|
||||
term in context
|
||||
for term in (
|
||||
"boobjob",
|
||||
"titjob",
|
||||
"breast sex",
|
||||
"breasts tightly",
|
||||
"testicle",
|
||||
"balls-licking",
|
||||
"balls licking",
|
||||
"penis-licking",
|
||||
"penis licking",
|
||||
"handjob",
|
||||
"hand job",
|
||||
"footjob",
|
||||
)
|
||||
)
|
||||
)
|
||||
woman_gives_oral = (
|
||||
re.search(r"\bwoman [a-z]\b", context)
|
||||
and re.search(r"\bman [a-z]\b", context)
|
||||
and any(
|
||||
term in context
|
||||
for term in (
|
||||
"takes man",
|
||||
"penis in her mouth",
|
||||
"mouth at penis level",
|
||||
"fellatio",
|
||||
"blowjob",
|
||||
"deepthroat",
|
||||
"penis sucking",
|
||||
"lips wrapped",
|
||||
)
|
||||
)
|
||||
)
|
||||
man_gives_oral = (
|
||||
re.search(r"\bwoman [a-z]\b", context)
|
||||
and re.search(r"\bman [a-z]\b", context)
|
||||
and any(
|
||||
term in context
|
||||
for term in (
|
||||
"mouth on her pussy",
|
||||
"mouth on woman",
|
||||
"mouth pressed to her pussy",
|
||||
"cunnilingus",
|
||||
"pussy licking",
|
||||
"tongue on pussy",
|
||||
)
|
||||
)
|
||||
)
|
||||
mouth_expression_terms = ("mouth", "oral", "tongue", "lips", "gagging", "saliva")
|
||||
clauses = [clause.strip() for clause in text.split(";") if clause.strip()]
|
||||
if woman_active_outercourse:
|
||||
clauses = [clause for clause in clauses if not re.match(r"^Man [A-Z] has\b", clause)]
|
||||
if woman_gives_oral:
|
||||
clauses = [
|
||||
clause
|
||||
for clause in clauses
|
||||
if not (
|
||||
re.match(r"^Man [A-Z] has\b", clause)
|
||||
and any(term in clause.lower() for term in mouth_expression_terms)
|
||||
)
|
||||
]
|
||||
if man_gives_oral:
|
||||
clauses = [
|
||||
clause
|
||||
for clause in clauses
|
||||
if not (
|
||||
re.match(r"^Woman [A-Z] has\b", clause)
|
||||
and any(term in clause.lower() for term in mouth_expression_terms)
|
||||
)
|
||||
]
|
||||
return "; ".join(clauses)
|
||||
|
||||
|
||||
def _descriptor_detail_for_subject(subject: Any, descriptor_detail: Any) -> str:
|
||||
|
||||
@@ -302,3 +302,98 @@ def character_expression_entries(
|
||||
used.add(choice)
|
||||
expressions.append(f"{label} has {choice}")
|
||||
return expressions
|
||||
|
||||
|
||||
def sanitize_character_expression_text_for_action(
|
||||
expression_text: str,
|
||||
role_graph: Any,
|
||||
item: Any,
|
||||
axis_values: Any = None,
|
||||
) -> str:
|
||||
text = str(expression_text or "").strip()
|
||||
if not text:
|
||||
return ""
|
||||
context = " ".join(
|
||||
str(part or "").lower()
|
||||
for part in (
|
||||
role_graph,
|
||||
item,
|
||||
*((axis_values or {}).values() if isinstance(axis_values, dict) else ()),
|
||||
)
|
||||
)
|
||||
woman_active_outercourse = (
|
||||
re.search(r"\bwoman [a-z]\b", context)
|
||||
and re.search(r"\bman [a-z]\b", context)
|
||||
and any(
|
||||
term in context
|
||||
for term in (
|
||||
"boobjob",
|
||||
"titjob",
|
||||
"breast sex",
|
||||
"breasts tightly",
|
||||
"testicle",
|
||||
"balls-licking",
|
||||
"balls licking",
|
||||
"penis-licking",
|
||||
"penis licking",
|
||||
"handjob",
|
||||
"hand job",
|
||||
"footjob",
|
||||
)
|
||||
)
|
||||
)
|
||||
woman_gives_oral = (
|
||||
re.search(r"\bwoman [a-z]\b", context)
|
||||
and re.search(r"\bman [a-z]\b", context)
|
||||
and any(
|
||||
term in context
|
||||
for term in (
|
||||
"takes man",
|
||||
"penis in her mouth",
|
||||
"mouth at penis level",
|
||||
"fellatio",
|
||||
"blowjob",
|
||||
"deepthroat",
|
||||
"penis sucking",
|
||||
"lips wrapped",
|
||||
)
|
||||
)
|
||||
)
|
||||
man_gives_oral = (
|
||||
re.search(r"\bwoman [a-z]\b", context)
|
||||
and re.search(r"\bman [a-z]\b", context)
|
||||
and any(
|
||||
term in context
|
||||
for term in (
|
||||
"mouth on her pussy",
|
||||
"mouth on woman",
|
||||
"mouth pressed to her pussy",
|
||||
"cunnilingus",
|
||||
"pussy licking",
|
||||
"tongue on pussy",
|
||||
)
|
||||
)
|
||||
)
|
||||
mouth_expression_terms = ("mouth", "oral", "tongue", "lips", "gagging", "saliva")
|
||||
clauses = [clause.strip() for clause in text.split(";") if clause.strip()]
|
||||
if woman_active_outercourse:
|
||||
clauses = [clause for clause in clauses if not re.match(r"^Man [A-Z] has\b", clause)]
|
||||
if woman_gives_oral:
|
||||
clauses = [
|
||||
clause
|
||||
for clause in clauses
|
||||
if not (
|
||||
re.match(r"^Man [A-Z] has\b", clause)
|
||||
and any(term in clause.lower() for term in mouth_expression_terms)
|
||||
)
|
||||
]
|
||||
if man_gives_oral:
|
||||
clauses = [
|
||||
clause
|
||||
for clause in clauses
|
||||
if not (
|
||||
re.match(r"^Woman [A-Z] has\b", clause)
|
||||
and any(term in clause.lower() for term in mouth_expression_terms)
|
||||
)
|
||||
]
|
||||
return "; ".join(clauses)
|
||||
|
||||
@@ -733,6 +733,25 @@ def smoke_row_expression_policy() -> None:
|
||||
== (None, "character_slots:disabled"),
|
||||
"Row expression cast override did not honor all-slot expression disable",
|
||||
)
|
||||
expression_text = "Woman A has steady focus; Man A has parted lips with saliva"
|
||||
context_role = "Woman A performs a handjob while Man A stands close"
|
||||
_expect(
|
||||
pb._sanitize_character_expression_text_for_action(expression_text, context_role, "", {})
|
||||
== row_expression.sanitize_character_expression_text_for_action(expression_text, context_role, "", {})
|
||||
== "Woman A has steady focus",
|
||||
"Row expression action sanitizer did not remove incompatible partner expression",
|
||||
)
|
||||
reverse_context = "Man A has mouth pressed to her pussy while Woman A lies back"
|
||||
_expect(
|
||||
row_expression.sanitize_character_expression_text_for_action(
|
||||
"Woman A has tongue out; Man A has focused mouth contact",
|
||||
reverse_context,
|
||||
"",
|
||||
{},
|
||||
)
|
||||
== "Man A has focused mouth contact",
|
||||
"Row expression action sanitizer did not remove incompatible visible-subject expression",
|
||||
)
|
||||
|
||||
|
||||
def smoke_row_item_policy() -> None:
|
||||
|
||||
Reference in New Issue
Block a user