From 9f84e5638173049e6269fed62ef0f7450d4d6730 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Thu, 25 Jun 2026 11:42:47 +0200 Subject: [PATCH] Clarify partially removed clothing for penetration --- prompt_builder.py | 102 +++++++++++++++++++++++++++++++++++++++++++++- sdxl_formatter.py | 2 +- 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/prompt_builder.py b/prompt_builder.py index afc1047..cd7a361 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -6342,7 +6342,98 @@ def _insta_of_softcore_pose(rng: random.Random, level: str) -> str: return g.choose(rng, pool) -def _insta_of_hardcore_clothing_state(mode: str, softcore_outfit: str) -> str: +PENETRATION_LOWER_ACCESS_TERMS = ( + "penetrat", + "thrust", + "vaginal", + "anal", + "rear-entry", + "rear entry", + "front-and-back", + "front and back", + "double", + "doggy", + "missionary", + "cowgirl", + "straddles", + "hips aligned", + "penis into", + "penis inside", + "penis entering", + "toy aligned", + "second penetration point", +) + +LOWER_BODY_CLOTHING_TERMS = ( + "panty", + "panties", + "brief", + "briefs", + "thong", + "bottom", + "bottoms", + "bodysuit", + "teddy", + "dress", + "skirt", + "shorts", + "jeans", + "trousers", + "pants", + "bikini", + "towel", + "sheet", + "blanket", +) + + +def _hardcore_row_needs_lower_access(row: dict[str, Any]) -> bool: + axis_values = row.get("item_axis_values") + axis_text = " ".join(str(value) for value in axis_values.values()) if isinstance(axis_values, dict) else "" + text = " ".join( + str(part or "") + for part in ( + row.get("source_role_graph"), + row.get("role_graph"), + row.get("item"), + row.get("source_composition"), + row.get("composition"), + axis_text, + ) + ).lower() + return any(term in text for term in PENETRATION_LOWER_ACCESS_TERMS) + + +def _outfit_without_lower_body_blockers(outfit: str) -> str: + text = str(outfit or "").strip() + if not text: + return "" + text = re.sub(r"\blingerie set\b", "lingerie top details", text, flags=re.IGNORECASE) + text = re.sub(r"\bbrief set\b", "bra set", text, flags=re.IGNORECASE) + text = re.sub(r"\bbodysuit with\b", "upper bodysuit detail with", text, flags=re.IGNORECASE) + fragments = re.split(r"\s*,\s*|\s+\band\b\s+|\s+\bwith\b\s+|\s+\bunder\b\s+|\s+\bover\b\s+", text) + kept = [] + for fragment in fragments: + fragment = fragment.strip(" ,.;") + if not fragment: + continue + lower = fragment.lower() + if any(term in lower for term in LOWER_BODY_CLOTHING_TERMS): + continue + kept.append(fragment) + if not kept: + return "" + deduped = [] + seen = set() + for fragment in kept: + key = re.sub(r"\W+", " ", fragment.lower()).strip() + if key and key not in seen: + deduped.append(fragment) + seen.add(key) + return ", ".join(deduped) + + +def _insta_of_hardcore_clothing_state(mode: str, softcore_outfit: str, needs_lower_access: bool = False) -> str: mode = mode if mode in INSTA_OF_HARDCORE_CLOTHING_CONTINUITY else "none" outfit = str(softcore_outfit or "").strip() if mode == "none" or not outfit: @@ -6352,6 +6443,14 @@ def _insta_of_hardcore_clothing_state(mode: str, softcore_outfit: str) -> str: return f"Body exposure: {base}." if mode == "implied_nude": return f"Body exposure: {base}." + if mode == "partially_removed" and needs_lower_access: + detail = _outfit_without_lower_body_blockers(outfit) + base = ( + "Woman A's lower body is clear for penetration; any lower garment is pulled aside or removed below the hips" + ) + if detail: + return f"Clothing state: {base}; visible remaining styling: {detail}." + return f"Clothing state: {base}." return f"Clothing state: {base}; teaser outfit detail: {outfit}." @@ -6637,6 +6736,7 @@ def build_insta_of_pair( fallback_hard_clothing_state = "" if has_primary_hardcore_clothing else _insta_of_hardcore_clothing_state( options["hardcore_clothing_continuity"], soft_row["item"], + needs_lower_access=_hardcore_row_needs_lower_access(hard_row), ) hard_clothing_parts = [part for part in (fallback_hard_clothing_state, *character_hardcore_clothing_entries) if part] hard_clothing_state = " ".join(hard_clothing_parts) diff --git a/sdxl_formatter.py b/sdxl_formatter.py index 100fb7d..1193b00 100644 --- a/sdxl_formatter.py +++ b/sdxl_formatter.py @@ -145,7 +145,7 @@ def _split_tag_text(text: Any) -> list[str]: text = re.sub(r"\bWoman [A-Z]\b", "woman", text) text = re.sub(r"\bMan [A-Z]\b", "man", text) text = re.sub( - r"\b(?:Clothing state|Visual clothing state|teaser outfit detail|softcore visual reference|Sexual scene|Role graph):\s*", + r"\b(?:Clothing state|Visual clothing state|visible remaining styling|teaser outfit detail|softcore visual reference|Sexual scene|Role graph):\s*", "", text, flags=re.IGNORECASE,