From a94cb9f8f1e31ff0ee3ce1aa2a9e3186544fd944 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 27 Jun 2026 18:03:39 +0200 Subject: [PATCH] Use item axis values in SDXL tags --- docs/prompt-pool-routing-map.md | 4 ++-- sdxl_tag_policy.py | 38 +++++++++++++++++++++++++++++++ sdxl_tag_routes.py | 5 +++++ tools/prompt_smoke.py | 40 +++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index 30cde8a..532f4ea 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -140,7 +140,7 @@ Core helper ownership: | `server_routes.py` | Pure payload handlers for profile-save and accumulator server endpoints, used by ComfyUI routes and smoke tests without importing ComfyUI. | | `sdxl_presets.py` | SDXL formatter profiles, style presets, quality presets, default negative prompt, and metadata-family tag hints used by the SDXL formatter and node choice lists. | | `sdxl_format_route.py` | Top-level SDXL dispatch, formatter profile application, target and nude-weight normalization, metadata-vs-text input selection, single-vs-pair branching, final prompt/negative output shape, and fallback routing. | -| `sdxl_tag_policy.py` | SDXL tag splitting, tag-key dedupe, count inference, character descriptor tags, metadata-family/camera/explicit helper tags, and route dependency assembly used by `sdxl_formatter.py` and `sdxl_tag_routes.py`. | +| `sdxl_tag_policy.py` | SDXL tag splitting, tag-key dedupe, count inference, character descriptor tags, item-axis tags, metadata-family/camera/explicit helper tags, and route dependency assembly used by `sdxl_formatter.py` and `sdxl_tag_routes.py`. | | `caption_format_route.py` | Top-level caption dispatch, input-hint and target normalization, caption profile application, metadata-vs-text branching, trigger wrapping, final prose hygiene, and method/output shape. | | `caption_policy.py` | Caption naturalizer policy data and helpers: caption profiles, style tails, item labels, metadata-family caption labels, detail/style-policy normalization, clothing cleanup, and composition cleanup. | | `caption_text_policy.py` | Caption sentence helpers, trigger wrapping, formatter-hint append, row-value fallback wrappers, cast text wrappers, single-caption front parsing, and metadata-route dependency assembly used by `caption_naturalizer.py` and `caption_metadata_routes.py`. | @@ -814,7 +814,7 @@ SDXL field consumption: | Branch | Reads most from | Key functions | | --- | --- | --- | -| Normal metadata | `cast_descriptor_text` or structured age/body/skin/hair/eyes, `action_family`, `position_family`, `position_keys`, item, role graph, clothing/body exposure state, scene, camera config/directive | `sdxl_tag_routes.row_core_tags_result`, `sdxl_tag_policy.normal_character_tags`, `sdxl_tag_policy.metadata_family_tags`, `sdxl_tag_policy.camera_tags` | +| Normal metadata | `cast_descriptor_text` or structured age/body/skin/hair/eyes, `action_family`, `position_family`, `position_keys`, `item_axis_values`, item, role graph, clothing/body exposure state, scene, camera config/directive | `sdxl_tag_routes.row_core_tags_result`, `sdxl_tag_policy.normal_character_tags`, `sdxl_tag_policy.metadata_family_tags`, `sdxl_tag_policy.axis_value_tags`, `sdxl_tag_policy.camera_tags` | | Pair softcore | `softcore_row`, pair partner styling, root soft camera config | `sdxl_tag_routes.soft_tags_result` | | Pair hardcore | `hardcore_row`, `action_family`, `position_family`, `position_keys`, role graph, item, `hardcore_clothing_state`, expression/composition, hard camera fields | `sdxl_tag_routes.hard_tags_result`, `sdxl_tag_policy.metadata_family_tags` | | Text fallback | `source_text`, preserve-trigger setting, shared field-label stripping, prompt labels such as `Characters:` | `_fallback_text_to_sdxl` | diff --git a/sdxl_tag_policy.py b/sdxl_tag_policy.py index 3aa2aa5..a9b890e 100644 --- a/sdxl_tag_policy.py +++ b/sdxl_tag_policy.py @@ -102,6 +102,43 @@ def formatter_hint_tags(*rows: dict[str, Any]) -> list[str]: return tags +def _axis_value_texts(value: Any) -> list[str]: + if isinstance(value, str): + text = clean(value) + return [text] if text and text.lower() not in ("any", "auto", "random", "none") else [] + if isinstance(value, (int, float, bool)) or value is None: + return [] + if isinstance(value, list): + texts: list[str] = [] + for item in value: + texts.extend(_axis_value_texts(item)) + return texts + if isinstance(value, dict): + for preferred in ("text", "prompt", "template", "value", "name"): + preferred_texts = _axis_value_texts(value.get(preferred)) + if preferred_texts: + return preferred_texts + texts: list[str] = [] + for item in value.values(): + texts.extend(_axis_value_texts(item)) + return texts + return [] + + +def axis_value_tags(row: dict[str, Any]) -> list[str]: + if not isinstance(row, dict): + return [] + axis_values = row.get("item_axis_values") + if not isinstance(axis_values, dict): + return [] + tags: list[str] = [] + seen: set[str] = set() + for value in axis_values.values(): + for text in _axis_value_texts(value): + add(tags, seen, text) + return tags + + def combine_tags(*parts: Any) -> str: tags: list[str] = [] seen: set[str] = set() @@ -261,6 +298,7 @@ def tag_route_dependencies() -> sdxl_tag_routes.SDXLTagRouteDependencies: character_tags_from_descriptor=character_tags_from_descriptor, metadata_family_tags=metadata_family_tags, formatter_hint_tags=formatter_hint_tags, + axis_value_tags=axis_value_tags, camera_tags=camera_tags, explicit_tags=explicit_tags, softcore_pair_tags=softcore_pair_tags, diff --git a/sdxl_tag_routes.py b/sdxl_tag_routes.py index afc37f1..2918781 100644 --- a/sdxl_tag_routes.py +++ b/sdxl_tag_routes.py @@ -39,6 +39,7 @@ class SDXLTagRouteDependencies: character_tags_from_descriptor: Callable[[Any], list[str]] metadata_family_tags: Callable[[dict[str, Any]], list[str]] formatter_hint_tags: Callable[..., list[str]] + axis_value_tags: Callable[[dict[str, Any]], list[str]] camera_tags: Callable[..., list[str]] explicit_tags: Callable[[str, float], list[str]] softcore_pair_tags: Callable[[dict[str, Any], dict[str, Any]], list[str]] @@ -109,6 +110,8 @@ def row_core_tags_result(request: SDXLRowTagRequest, deps: SDXLTagRouteDependenc deps.add_one(tags, seen, tag) for tag in deps.formatter_hint_tags(row): deps.add(tags, seen, tag) + for tag in deps.axis_value_tags(row): + deps.add(tags, seen, tag) item = deps.row_value(row, "item", ("Sexual scene", "Sexual pose", "Erotic outfit", "Clothing")) or deps.clean( row.get("custom_item") @@ -205,6 +208,8 @@ def hard_tags_result(request: SDXLPairTagRequest, deps: SDXLTagRouteDependencies deps.add_one(tags, seen, tag) for tag in deps.formatter_hint_tags(row, root): deps.add(tags, seen, tag) + for tag in deps.axis_value_tags(row): + deps.add(tags, seen, tag) hard_scene = deps.clean(row.get("scene_text")) hard_item = deps.clean(row.get("item")) diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 9208c1f..8dff169 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -4485,6 +4485,18 @@ def smoke_sdxl_tag_policy() -> None: sdxl_formatter._metadata_family_tags(row) == sdxl_tag_policy.metadata_family_tags(row), "SDXL formatter metadata-family helper should delegate to sdxl_tag_policy", ) + axis_row = { + "item_axis_values": { + "position": "edge-supported kneeling pose", + "contact_detail": "hands braced on thighs, close body alignment", + "ignored": "random", + } + } + axis_tags = sdxl_tag_policy.axis_value_tags(axis_row) + _expect("edge-supported kneeling pose" in axis_tags, "SDXL axis tags lost selected position axis") + _expect("hands braced on thighs" in axis_tags, "SDXL axis tags lost selected detail axis") + _expect("close body alignment" in axis_tags, "SDXL axis tags lost split detail axis") + _expect("random" not in axis_tags, "SDXL axis tags should ignore random placeholders") _expect( sdxl_formatter._camera_tags(row) == sdxl_tag_policy.camera_tags(row), "SDXL formatter camera helper should delegate to sdxl_tag_policy", @@ -4499,6 +4511,7 @@ def smoke_sdxl_tag_policy() -> None: _expect(deps.tag_key is sdxl_tag_policy.tag_key, "SDXL route deps lost policy tag_key") _expect(deps.normal_character_tags is sdxl_tag_policy.normal_character_tags, "SDXL route deps lost character tag policy") _expect(deps.metadata_family_tags is sdxl_tag_policy.metadata_family_tags, "SDXL route deps lost metadata family policy") + _expect(deps.axis_value_tags is sdxl_tag_policy.axis_value_tags, "SDXL route deps lost axis-value tag policy") _expect(deps.camera_tags is sdxl_tag_policy.camera_tags, "SDXL route deps lost camera tag policy") _expect(deps.explicit_tags is sdxl_tag_policy.explicit_tags, "SDXL route deps lost explicit tag policy") _expect(deps.softcore_pair_tags is sdxl_tag_policy.softcore_pair_tags, "SDXL route deps lost softcore pair tag policy") @@ -4547,6 +4560,27 @@ def smoke_sdxl_tag_routes() -> None: "Typed SDXL row tag route should match legacy wrapper output", ) _expect("sdxl route tag" in typed_row.as_text(), "Typed SDXL row tag route lost route-specific formatter hint") + axis_only_row = _fixture_hardcore_row( + item="generic configured adult action", + pose="configured explicit pose", + role_graph="", + source_role_graph="", + item_axis_values={ + "position": "standing oral position", + "contact_detail": "mouth contact at hip height, hands on hips", + }, + action_family="oral", + position_family="oral", + position_key="standing", + position_keys=["standing"], + ) + axis_only_tags = sdxl_tag_routes.row_core_tags_result( + sdxl_tag_routes.SDXLRowTagRequest(axis_only_row, 1.29), + deps, + ).as_text() + _expect("standing oral position" in axis_only_tags, "SDXL row route lost item axis position tag") + _expect("mouth contact at hip height" in axis_only_tags, "SDXL row route lost item axis contact tag") + _expect("hands on hips" in axis_only_tags, "SDXL row route lost split item axis detail tag") stale_character_route_row = _fixture_hardcore_row( prompt="Characters: stale prompt subject, stale body, stale skin, stale hair, stale eyes.", cast_descriptor_text="", @@ -4688,6 +4722,12 @@ def smoke_sdxl_tag_routes() -> None: ).as_text() for required in ("(naked:1.29)", "pussy", "penis", "penetration"): _expect(required in pair_metadata_tags, f"SDXL pair tags lost structured explicit metadata tag: {required}") + pair_axis_tags = sdxl_tag_routes.hard_tags_result( + sdxl_tag_routes.SDXLPairTagRequest(axis_only_row, pair_metadata_root, 1.29), + deps, + ).as_text() + _expect("standing oral position" in pair_axis_tags, "SDXL pair hard route lost item axis position tag") + _expect("mouth contact at hip height" in pair_axis_tags, "SDXL pair hard route lost item axis contact tag") formatted = sdxl_formatter.format_sdxl_prompt( "", metadata_json=_json(pair),