Use item axis values in SDXL tags

This commit is contained in:
2026-06-27 18:03:39 +02:00
parent 727aea6307
commit a94cb9f8f1
4 changed files with 85 additions and 2 deletions
+2 -2
View File
@@ -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. | | `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_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_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_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_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`. | | `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 | | 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 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` | | 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` | | Text fallback | `source_text`, preserve-trigger setting, shared field-label stripping, prompt labels such as `Characters:` | `_fallback_text_to_sdxl` |
+38
View File
@@ -102,6 +102,43 @@ def formatter_hint_tags(*rows: dict[str, Any]) -> list[str]:
return tags 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: def combine_tags(*parts: Any) -> str:
tags: list[str] = [] tags: list[str] = []
seen: set[str] = set() seen: set[str] = set()
@@ -261,6 +298,7 @@ def tag_route_dependencies() -> sdxl_tag_routes.SDXLTagRouteDependencies:
character_tags_from_descriptor=character_tags_from_descriptor, character_tags_from_descriptor=character_tags_from_descriptor,
metadata_family_tags=metadata_family_tags, metadata_family_tags=metadata_family_tags,
formatter_hint_tags=formatter_hint_tags, formatter_hint_tags=formatter_hint_tags,
axis_value_tags=axis_value_tags,
camera_tags=camera_tags, camera_tags=camera_tags,
explicit_tags=explicit_tags, explicit_tags=explicit_tags,
softcore_pair_tags=softcore_pair_tags, softcore_pair_tags=softcore_pair_tags,
+5
View File
@@ -39,6 +39,7 @@ class SDXLTagRouteDependencies:
character_tags_from_descriptor: Callable[[Any], list[str]] character_tags_from_descriptor: Callable[[Any], list[str]]
metadata_family_tags: Callable[[dict[str, Any]], list[str]] metadata_family_tags: Callable[[dict[str, Any]], list[str]]
formatter_hint_tags: Callable[..., list[str]] formatter_hint_tags: Callable[..., list[str]]
axis_value_tags: Callable[[dict[str, Any]], list[str]]
camera_tags: Callable[..., list[str]] camera_tags: Callable[..., list[str]]
explicit_tags: Callable[[str, float], list[str]] explicit_tags: Callable[[str, float], list[str]]
softcore_pair_tags: Callable[[dict[str, Any], dict[str, Any]], 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) deps.add_one(tags, seen, tag)
for tag in deps.formatter_hint_tags(row): for tag in deps.formatter_hint_tags(row):
deps.add(tags, seen, tag) 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( item = deps.row_value(row, "item", ("Sexual scene", "Sexual pose", "Erotic outfit", "Clothing")) or deps.clean(
row.get("custom_item") row.get("custom_item")
@@ -205,6 +208,8 @@ def hard_tags_result(request: SDXLPairTagRequest, deps: SDXLTagRouteDependencies
deps.add_one(tags, seen, tag) deps.add_one(tags, seen, tag)
for tag in deps.formatter_hint_tags(row, root): for tag in deps.formatter_hint_tags(row, root):
deps.add(tags, seen, tag) 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_scene = deps.clean(row.get("scene_text"))
hard_item = deps.clean(row.get("item")) hard_item = deps.clean(row.get("item"))
+40
View File
@@ -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_tags(row) == sdxl_tag_policy.metadata_family_tags(row),
"SDXL formatter metadata-family helper should delegate to sdxl_tag_policy", "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( _expect(
sdxl_formatter._camera_tags(row) == sdxl_tag_policy.camera_tags(row), sdxl_formatter._camera_tags(row) == sdxl_tag_policy.camera_tags(row),
"SDXL formatter camera helper should delegate to sdxl_tag_policy", "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.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.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.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.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.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") _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", "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") _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( stale_character_route_row = _fixture_hardcore_row(
prompt="Characters: stale prompt subject, stale body, stale skin, stale hair, stale eyes.", prompt="Characters: stale prompt subject, stale body, stale skin, stale hair, stale eyes.",
cast_descriptor_text="", cast_descriptor_text="",
@@ -4688,6 +4722,12 @@ def smoke_sdxl_tag_routes() -> None:
).as_text() ).as_text()
for required in ("(naked:1.29)", "pussy", "penis", "penetration"): 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}") _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( formatted = sdxl_formatter.format_sdxl_prompt(
"", "",
metadata_json=_json(pair), metadata_json=_json(pair),