Improve kneeling oral prompt geometry
This commit is contained in:
@@ -829,6 +829,33 @@ def _is_outercourse_text(*parts: Any) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_oral_text(*parts: Any) -> bool:
|
||||||
|
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
|
||||||
|
return any(
|
||||||
|
term in text
|
||||||
|
for term in (
|
||||||
|
"oral",
|
||||||
|
"fellatio",
|
||||||
|
"blowjob",
|
||||||
|
"deepthroat",
|
||||||
|
"penis sucking",
|
||||||
|
"penis in her mouth",
|
||||||
|
"penis in mouth",
|
||||||
|
"takes the man's penis",
|
||||||
|
"takes his penis",
|
||||||
|
"mouth at penis level",
|
||||||
|
"mouth on his penis",
|
||||||
|
"lips wrapped",
|
||||||
|
"cunnilingus",
|
||||||
|
"pussy licking",
|
||||||
|
"mouth on her pussy",
|
||||||
|
"mouth pressed to her pussy",
|
||||||
|
"face-sitting",
|
||||||
|
"sixty-nine",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
@@ -1444,6 +1471,56 @@ def _dedupe_outercourse_detail(detail: str, role_graph: str, hard_item: str = ""
|
|||||||
return _join_detail_clauses(clauses)
|
return _join_detail_clauses(clauses)
|
||||||
|
|
||||||
|
|
||||||
|
def _dedupe_oral_detail(detail: str, role_graph: str, hard_item: str = "", axis_values: Any = None) -> str:
|
||||||
|
detail = _clean(detail)
|
||||||
|
if not detail:
|
||||||
|
return ""
|
||||||
|
context = _position_context_text(role_graph, hard_item, "", axis_values)
|
||||||
|
woman_gives = any(
|
||||||
|
term in context
|
||||||
|
for term in (
|
||||||
|
"takes the man's penis",
|
||||||
|
"takes his penis",
|
||||||
|
"penis in her mouth",
|
||||||
|
"mouth at penis level",
|
||||||
|
"mouth on his penis",
|
||||||
|
"fellatio",
|
||||||
|
"blowjob",
|
||||||
|
"deepthroat",
|
||||||
|
"penis sucking",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
clauses: list[str] = []
|
||||||
|
for clause in _detail_clauses(detail):
|
||||||
|
lower = clause.lower()
|
||||||
|
if any(
|
||||||
|
term in lower
|
||||||
|
for term in (
|
||||||
|
"kneeling oral position",
|
||||||
|
"standing oral position",
|
||||||
|
"edge-of-bed oral position",
|
||||||
|
"side-lying oral position",
|
||||||
|
"chair oral position",
|
||||||
|
"reclining cunnilingus position",
|
||||||
|
"face-sitting position",
|
||||||
|
"sixty-nine position",
|
||||||
|
"fellatio with penis in mouth",
|
||||||
|
"deepthroat blowjob",
|
||||||
|
"penis sucking with visible saliva",
|
||||||
|
"cunnilingus with tongue on pussy",
|
||||||
|
"oral sex with tongue and fingers",
|
||||||
|
"oral contact with mouth on the visible genitals",
|
||||||
|
"bodies stacked close together",
|
||||||
|
"body angle keeps the penis and face readable",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
if woman_gives and lower == "wet shine on genitals":
|
||||||
|
clause = "saliva dripping on the penis"
|
||||||
|
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(" ,;")]
|
||||||
|
|
||||||
@@ -1768,6 +1845,7 @@ def _hardcore_action_sentence(
|
|||||||
detail = _hardcore_item_detail(hard_item)
|
detail = _hardcore_item_detail(hard_item)
|
||||||
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))
|
||||||
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",
|
||||||
@@ -1782,6 +1860,10 @@ def _hardcore_action_sentence(
|
|||||||
anchor = ""
|
anchor = ""
|
||||||
detail = _dedupe_outercourse_detail(detail, role_graph, hard_item, axis_values)
|
detail = _dedupe_outercourse_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_oral and role_graph:
|
||||||
|
anchor = ""
|
||||||
|
detail = _dedupe_oral_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)):
|
||||||
|
|||||||
+120
-1
@@ -999,6 +999,8 @@ def _oral_acts_for_position(values: list[Any], position: str) -> list[Any]:
|
|||||||
return filtered(lambda text: "sixty-nine" in text)
|
return filtered(lambda text: "sixty-nine" in text)
|
||||||
if "face-sitting" in position_text:
|
if "face-sitting" in position_text:
|
||||||
return filtered(lambda text: "face-sitting" in text or any(term in text for term in cunnilingus_terms))
|
return filtered(lambda text: "face-sitting" in text or any(term in text for term in cunnilingus_terms))
|
||||||
|
if "kneeling oral" in position_text:
|
||||||
|
return filtered(lambda text: any(term in text for term in penis_terms))
|
||||||
if "straddled oral" in position_text or "reclining cunnilingus" in position_text:
|
if "straddled oral" in position_text or "reclining cunnilingus" in position_text:
|
||||||
return filtered(lambda text: "sixty-nine" not in text and not any(term in text for term in penis_terms))
|
return filtered(lambda text: "sixty-nine" not in text and not any(term in text for term in penis_terms))
|
||||||
if "spread-leg oral" in position_text:
|
if "spread-leg oral" in position_text:
|
||||||
@@ -1008,6 +1010,62 @@ def _oral_acts_for_position(values: list[Any], position: str) -> list[Any]:
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def _oral_axis_values_for_context(values: list[Any], position: str, oral_act: str, axis_name: str) -> list[Any]:
|
||||||
|
axis_name = str(axis_name or "").lower()
|
||||||
|
if axis_name not in {"body_contact", "hand_detail", "mouth_detail", "saliva_detail", "climax_hint", "visibility"}:
|
||||||
|
return values
|
||||||
|
position_text = str(position or "").lower()
|
||||||
|
act_text = str(oral_act or "").lower()
|
||||||
|
woman_gives = any(
|
||||||
|
term in act_text
|
||||||
|
for term in ("fellatio", "blowjob", "deepthroat", "penis sucking", "penis in mouth")
|
||||||
|
)
|
||||||
|
man_gives = any(
|
||||||
|
term in act_text
|
||||||
|
for term in ("cunnilingus", "pussy licking", "tongue on pussy")
|
||||||
|
)
|
||||||
|
if not (woman_gives or man_gives):
|
||||||
|
return values
|
||||||
|
|
||||||
|
def value_text(value: Any) -> str:
|
||||||
|
return _entry_text(value).lower()
|
||||||
|
|
||||||
|
def filtered(terms: tuple[str, ...], excluded_terms: tuple[str, ...] = ()) -> list[Any]:
|
||||||
|
matches = [
|
||||||
|
value
|
||||||
|
for value in values
|
||||||
|
if any(term in value_text(value) for term in terms)
|
||||||
|
and not any(term in value_text(value) for term in excluded_terms)
|
||||||
|
]
|
||||||
|
return matches or values
|
||||||
|
|
||||||
|
if woman_gives:
|
||||||
|
by_axis = {
|
||||||
|
"body_contact": ("hips pushed", "fingers tangled", "bodies stacked", "hands on thighs"),
|
||||||
|
"hand_detail": ("hips", "penis", "head", "hair"),
|
||||||
|
"mouth_detail": ("lips", "mouth", "deep mouth", "saliva"),
|
||||||
|
"saliva_detail": ("saliva", "wet lips", "slick wet mouth", "drool", "mouth"),
|
||||||
|
"climax_hint": ("mouth", "lips", "tongue", "breasts", "belly", "sexual fluids"),
|
||||||
|
"visibility": ("mouth", "penis", "oral"),
|
||||||
|
}
|
||||||
|
excluded = {
|
||||||
|
"body_contact": ("legs held open", "spread legs", "ass lifted", "chest pressed to thighs"),
|
||||||
|
"hand_detail": ("spreading thighs", "sheets", "cupping breasts", "pressing into thighs", "holding the ass"),
|
||||||
|
}
|
||||||
|
return filtered(by_axis.get(axis_name, ("mouth", "penis")), excluded.get(axis_name, ()))
|
||||||
|
if man_gives and ("kneeling oral" in position_text or "standing oral" in position_text):
|
||||||
|
by_axis = {
|
||||||
|
"body_contact": ("legs held open", "one body kneeling", "chest pressed", "ass lifted", "hands on thighs"),
|
||||||
|
"hand_detail": ("thigh", "hips", "head", "ass"),
|
||||||
|
"mouth_detail": ("tongue", "wet lips", "deep mouth", "genitals"),
|
||||||
|
"saliva_detail": ("saliva", "wet lips", "tongue", "drool"),
|
||||||
|
"climax_hint": ("sexual fluids", "orgasmic tension"),
|
||||||
|
"visibility": ("mouth", "pussy", "oral", "genital"),
|
||||||
|
}
|
||||||
|
return filtered(by_axis.get(axis_name, ("mouth", "pussy", "tongue")), ("penis", "breasts"))
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
def _outercourse_acts_for_position(values: list[Any], position: str) -> list[Any]:
|
def _outercourse_acts_for_position(values: list[Any], position: str) -> list[Any]:
|
||||||
position_text = str(position or "").lower()
|
position_text = str(position or "").lower()
|
||||||
if not position_text:
|
if not position_text:
|
||||||
@@ -1136,6 +1194,13 @@ def _compose_item(
|
|||||||
values = _compatible_entries(axes[name], women_count, men_count)
|
values = _compatible_entries(axes[name], women_count, men_count)
|
||||||
if subcategory_slug == "oral_sex" and name == "oral_act":
|
if subcategory_slug == "oral_sex" and name == "oral_act":
|
||||||
values = _oral_acts_for_position(values, axis_values.get("position", ""))
|
values = _oral_acts_for_position(values, axis_values.get("position", ""))
|
||||||
|
elif subcategory_slug == "oral_sex":
|
||||||
|
values = _oral_axis_values_for_context(
|
||||||
|
values,
|
||||||
|
axis_values.get("position", ""),
|
||||||
|
axis_values.get("oral_act", ""),
|
||||||
|
name,
|
||||||
|
)
|
||||||
if subcategory_slug == "outercourse_sex" and name == "outer_act":
|
if subcategory_slug == "outercourse_sex" and name == "outer_act":
|
||||||
values = _outercourse_acts_for_position(values, axis_values.get("position", ""))
|
values = _outercourse_acts_for_position(values, axis_values.get("position", ""))
|
||||||
if subcategory_slug == "outercourse_sex":
|
if subcategory_slug == "outercourse_sex":
|
||||||
@@ -3634,9 +3699,60 @@ def _sanitize_character_expression_text_for_action(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
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()]
|
clauses = [clause.strip() for clause in text.split(";") if clause.strip()]
|
||||||
if woman_active_outercourse:
|
if woman_active_outercourse:
|
||||||
clauses = [clause for clause in clauses if not re.match(r"^Man [A-Z] has\b", clause)]
|
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)
|
return "; ".join(clauses)
|
||||||
|
|
||||||
|
|
||||||
@@ -5334,7 +5450,10 @@ def _role_graph(
|
|||||||
if "kneeling oral" in position_text or ("kneeling oral" in text and not position_text):
|
if "kneeling oral" in position_text or ("kneeling oral" in text and not position_text):
|
||||||
if man_gives and not woman_gives:
|
if man_gives and not woman_gives:
|
||||||
return f"{woman} kneels with thighs parted and hips angled forward while {man} kneels in front of her with his mouth on her pussy."
|
return f"{woman} kneels with thighs parted and hips angled forward while {man} kneels in front of her with his mouth on her pussy."
|
||||||
return f"{woman} kneels in front of {man}'s hips with her mouth at penis level while {man} stands close above her."
|
return (
|
||||||
|
f"{woman} kneels in front of {man}'s penis while {man} stands over her; "
|
||||||
|
f"{woman} takes {man}'s penis in her mouth with saliva dripping on the penis as {man} looks down toward her."
|
||||||
|
)
|
||||||
if man_gives and not woman_gives:
|
if man_gives and not woman_gives:
|
||||||
return f"{woman} lies on her back with thighs open while {man} kneels between her legs with his mouth pressed to her pussy."
|
return f"{woman} lies on her back with thighs open while {man} kneels between her legs with his mouth pressed to her pussy."
|
||||||
return f"{woman} kneels in front of {man}'s hips and takes his penis in her mouth while {man} keeps his hips aligned with her face."
|
return f"{woman} kneels in front of {man}'s hips and takes his penis in her mouth while {man} keeps his hips aligned with her face."
|
||||||
|
|||||||
Reference in New Issue
Block a user