Clarify vaginal penetration geometry

This commit is contained in:
2026-06-25 18:29:43 +02:00
parent 7dc4333746
commit e916609de8
2 changed files with 127 additions and 11 deletions
+107
View File
@@ -856,6 +856,40 @@ def _is_oral_text(*parts: Any) -> bool:
) )
def _is_vaginal_penetration_text(*parts: Any) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
if not text or _is_outercourse_text(text) or _is_oral_text(text):
return False
if any(term in text for term in ("anal", "double penetration", "double-penetration", "toy-assisted", "strap-on")):
return False
return any(
term in text
for term in (
"vaginal penetration",
"deep vaginal sex",
"explicit penetrative sex",
"penetrative sex",
"penis entering pussy",
"penis thrusts into her pussy",
"penis thrusts into the woman",
"pussy stretched around a penis",
"hardcore vaginal thrusting",
"full-body penetrative sex",
"close-contact vaginal sex",
"missionary position",
"cowgirl position",
"reverse cowgirl position",
"doggy style position",
"standing sex position",
"spooning sex position",
"edge-of-bed position",
"kneeling straddle position",
"lotus sex position",
"bent-over position",
)
)
def _is_toy_assisted_double_text(*parts: Any) -> bool: def _is_toy_assisted_double_text(*parts: Any) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part)) text = " ".join(_clean(part).lower() for part in parts if _clean(part))
if "toy" not in text: if "toy" not in text:
@@ -1521,6 +1555,74 @@ def _dedupe_oral_detail(detail: str, role_graph: str, hard_item: str = "", axis_
return _join_detail_clauses(clauses) return _join_detail_clauses(clauses)
def _dedupe_penetration_detail(detail: str, role_graph: str, hard_item: str = "", axis_values: Any = None) -> str:
detail = _clean(detail)
if not detail:
return ""
role_lower = _clean(role_graph).lower()
detail = re.sub(
r"\b(?:front-facing|side-profile|rear-view|overhead|mirror-reflected|low-angle|close-up|wide full-body)\s+view of\s+"
r"(?:vaginal penetration with visible genital contact|deep vaginal sex|explicit penetrative sex|penetrative sex|"
r"penis entering pussy|pussy stretched around a penis|hardcore vaginal thrusting|full-body penetrative sex|"
r"close-contact vaginal sex)\b,?\s*",
"",
detail,
flags=re.IGNORECASE,
)
act_terms = (
"vaginal penetration with visible genital contact",
"deep vaginal sex",
"explicit penetrative sex",
"penetrative sex",
"penis entering pussy",
"pussy stretched around a penis",
"hardcore vaginal thrusting",
"full-body penetrative sex",
"close-contact vaginal sex",
"missionary position",
"cowgirl position",
"reverse cowgirl position",
"doggy style position",
"standing sex position",
"spooning sex position",
"edge-of-bed position",
"kneeling straddle position",
"lotus sex position",
"bent-over position",
)
clauses: list[str] = []
for clause in _detail_clauses(detail):
lower = clause.lower()
if any(term in lower for term in act_terms):
continue
if lower in (
"tongues visible while kissing",
"deep kissing",
"mouth close to the ear",
"neck kissing",
"explicit genital contact visible",
"genitals clearly visible",
"anatomically clear penetration",
"pussy and penis visible",
"wetness visible between the thighs",
):
continue
if lower in ("legs spread wide", "thighs open toward the viewer") and any(
term in role_lower for term in ("legs spread wide", "thighs open", "open thighs")
):
continue
if lower == "one body pinned under another" and "lies under" in role_lower:
continue
if lower in ("hips locked tightly together", "hips aligned") and "hips" in role_lower:
continue
if lower in ("hands gripping hips", "hands spreading the thighs") and any(
term in role_lower for term in ("hips", "thighs", "legs")
):
continue
clauses.append(clause)
return _join_detail_clauses(clauses)
def _detail_clauses(detail: str) -> list[str]: def _detail_clauses(detail: str) -> list[str]:
return [part.strip(" ,;") for part in re.split(r",\s*(?:and\s+)?", _clean(detail)) if part.strip(" ,;")] return [part.strip(" ,;") for part in re.split(r",\s*(?:and\s+)?", _clean(detail)) if part.strip(" ,;")]
@@ -1846,6 +1948,7 @@ def _hardcore_action_sentence(
anchor = _hardcore_pose_anchor(role_graph, hard_item, composition, axis_values) anchor = _hardcore_pose_anchor(role_graph, hard_item, composition, axis_values)
is_outercourse = _is_outercourse_text(role_graph, hard_item, composition, _axis_values_text(axis_values)) is_outercourse = _is_outercourse_text(role_graph, hard_item, composition, _axis_values_text(axis_values))
is_oral = _is_oral_text(role_graph, hard_item, composition, _axis_values_text(axis_values)) is_oral = _is_oral_text(role_graph, hard_item, composition, _axis_values_text(axis_values))
is_penetrative = _is_vaginal_penetration_text(role_graph, hard_item, composition, _axis_values_text(axis_values))
if _is_toy_assisted_double_text(role_graph, hard_item, composition, _axis_values_text(axis_values)): if _is_toy_assisted_double_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
role_graph = re.sub( role_graph = re.sub(
r"\s+while a toy adds (?:the|a) second penetration point\b", r"\s+while a toy adds (?:the|a) second penetration point\b",
@@ -1864,6 +1967,10 @@ def _hardcore_action_sentence(
anchor = "" anchor = ""
detail = _dedupe_oral_detail(detail, role_graph, hard_item, axis_values) detail = _dedupe_oral_detail(detail, role_graph, hard_item, axis_values)
detail = _limit_detail_for_density(detail, detail_density, False) detail = _limit_detail_for_density(detail, detail_density, False)
elif is_penetrative and role_graph:
anchor = ""
detail = _dedupe_penetration_detail(detail, role_graph, hard_item, axis_values)
detail = _limit_detail_for_density(detail, detail_density, False)
else: else:
detail = _dedupe_hardcore_detail(detail, anchor) if anchor else detail detail = _dedupe_hardcore_detail(detail, anchor) if anchor else detail
if _is_toy_assisted_double_text(role_graph, hard_item, composition, _axis_values_text(axis_values)): if _is_toy_assisted_double_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
+20 -11
View File
@@ -5251,24 +5251,33 @@ def _role_graph(
) )
) )
if "missionary" in text: if "missionary" in text:
return f"{woman} lies on her back with legs open while {man} is above her and {man}'s penis thrusts into her." return (
f"{woman} lies on her back with legs open around {man}'s hips while {man} is above her between her thighs; "
f"{man}'s hips press close and {man}'s penis thrusts into her pussy."
)
if "reverse cowgirl" in text: if "reverse cowgirl" in text:
return f"{woman} straddles {man}'s hips facing away while {man} lies under her and {man}'s penis thrusts into her." return f"{woman} straddles {man}'s hips facing away while {man} lies under her and {man}'s penis thrusts into her pussy."
if "cowgirl" in text or "straddling" in text: if "cowgirl" in text or "straddling" in text:
return f"{woman} straddles {man}'s hips facing him while {man} lies under her and {man}'s penis thrusts into her." return f"{woman} straddles {man}'s hips facing him while {man} lies under her and {man}'s penis thrusts into her pussy."
if "doggy" in text or "rear-entry" in text or "bent-over" in text or "bent over" in text: if "doggy" in text or "rear-entry" in text or "bent-over" in text or "bent over" in text:
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and {man}'s penis thrusts into her." return f"{woman} is on all fours with hips raised while {man} is positioned behind her and {man}'s penis thrusts into her pussy."
if "standing" in text: if "standing" in text:
return f"{woman} stands braced with hips angled back while {man} stands behind her and {man}'s penis thrusts into her." return f"{woman} stands braced with hips angled back while {man} stands behind her and {man}'s penis thrusts into her pussy."
if "spooning" in text or "side-lying" in text: if "spooning" in text or "side-lying" in text:
return f"{woman} lies on her side with thighs parted while {man} presses behind her and {man}'s penis thrusts into her." return f"{woman} lies on her side with thighs parted while {man} presses behind her and {man}'s penis thrusts into her pussy."
if "edge-of-bed" in text or "edge of bed" in text or "bed edge" in text: if "edge-of-bed" in text or "edge of bed" in text or "bed edge" in text or "edge-supported" in text or "raised edge" in text:
return f"{woman} lies at the bed edge with hips near the edge while {man} kneels between her legs and {man}'s penis thrusts into her." return (
f"{woman} lies back at a raised edge with hips at the edge and legs open while {man} kneels between her thighs; "
f"{man}'s hips press close and {man}'s penis thrusts into her pussy."
)
if "kneeling straddle" in text: if "kneeling straddle" in text:
return f"{woman} kneels straddling {man}'s hips while {man} supports her waist and {man}'s penis thrusts into her." return f"{woman} kneels straddling {man}'s hips while {man} supports her waist and {man}'s penis thrusts into her pussy."
if "lotus" in text: if "lotus" in text:
return f"{woman} sits in {man}'s lap facing him with legs around his hips while {man}'s penis thrusts into her." return f"{woman} sits in {man}'s lap facing him with legs around his hips while {man}'s penis thrusts into her pussy."
return f"{woman} lies on her back with thighs open while {man} kneels between her legs and {man}'s penis thrusts into her." return (
f"{woman} lies on her back with legs spread wide and knees bent outward while {man} kneels between her open thighs facing her; "
f"{man}'s hips are pressed between her legs and {man}'s penis thrusts into her pussy."
)
def anal_position_graph(woman: str, man: str) -> str: def anal_position_graph(woman: str, man: str) -> str:
text = " ".join( text = " ".join(