Add Krea2 pose variant selector node
This commit is contained in:
@@ -17,6 +17,10 @@ geometry summary, cue phrases, avoid phrases, references, and a known generator
|
|||||||
hook. Code should read it through `krea2_pose_variant_catalog.py` instead of
|
hook. Code should read it through `krea2_pose_variant_catalog.py` instead of
|
||||||
parsing the JSON directly.
|
parsing the JSON directly.
|
||||||
|
|
||||||
|
In ComfyUI, use the `SxCP Krea2 Pose Variant` node when you want a workflow to
|
||||||
|
select one catalog variant and emit a compatible `hardcore_position_config` for
|
||||||
|
the existing Position Pool / Action Filter / Insta-OF chain.
|
||||||
|
|
||||||
## Inventory
|
## Inventory
|
||||||
|
|
||||||
| Family | Pose images | Control images | First sample |
|
| Family | Pose images | Control images | First sample |
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
from . import krea2_pose_variant_catalog
|
||||||
from .hardcore_position_config import (
|
from .hardcore_position_config import (
|
||||||
build_hardcore_action_filter_json,
|
build_hardcore_action_filter_json,
|
||||||
build_hardcore_position_pool_json,
|
build_hardcore_position_pool_json,
|
||||||
@@ -11,6 +12,7 @@ try:
|
|||||||
hardcore_position_key_choices,
|
hardcore_position_key_choices,
|
||||||
)
|
)
|
||||||
except ImportError: # Allows local smoke tests from the repository root.
|
except ImportError: # Allows local smoke tests from the repository root.
|
||||||
|
import krea2_pose_variant_catalog
|
||||||
from hardcore_position_config import (
|
from hardcore_position_config import (
|
||||||
build_hardcore_action_filter_json,
|
build_hardcore_action_filter_json,
|
||||||
build_hardcore_position_pool_json,
|
build_hardcore_position_pool_json,
|
||||||
@@ -30,6 +32,18 @@ def _choice_input_key(prefix, choice):
|
|||||||
return f"{prefix}_{key}"
|
return f"{prefix}_{key}"
|
||||||
|
|
||||||
|
|
||||||
|
def _variant_family(value):
|
||||||
|
family = str(value or "any")
|
||||||
|
if family == "penetration":
|
||||||
|
family = "penetrative"
|
||||||
|
return family if family in hardcore_position_family_choices() else "any"
|
||||||
|
|
||||||
|
|
||||||
|
def _variant_positions(variant):
|
||||||
|
valid = set(hardcore_position_key_choices())
|
||||||
|
return [str(key) for key in variant.get("position_keys", []) if str(key) in valid]
|
||||||
|
|
||||||
|
|
||||||
class SxCPHardcorePositionPool:
|
class SxCPHardcorePositionPool:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
@@ -66,6 +80,66 @@ class SxCPHardcorePositionPool:
|
|||||||
return config, json.loads(config).get("summary", "")
|
return config, json.loads(config).get("summary", "")
|
||||||
|
|
||||||
|
|
||||||
|
class SxCPKrea2PoseVariant:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
keys = krea2_pose_variant_catalog.variant_keys()
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"variant_key": (keys or ["missing_catalog_variant"], {"default": keys[0] if keys else "missing_catalog_variant"}),
|
||||||
|
"combine_mode": (["replace", "add"], {"default": "replace"}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = (SXCP_HARDCORE_POSITION_CONFIG, "STRING", "STRING", "STRING", "STRING", "STRING")
|
||||||
|
RETURN_NAMES = (
|
||||||
|
"hardcore_position_config",
|
||||||
|
"variant_key",
|
||||||
|
"prompt_cues",
|
||||||
|
"avoid_cues",
|
||||||
|
"summary",
|
||||||
|
"variant_json",
|
||||||
|
)
|
||||||
|
FUNCTION = "build"
|
||||||
|
CATEGORY = "prompt_builder"
|
||||||
|
|
||||||
|
def build(self, variant_key, combine_mode="replace", hardcore_position_config=""):
|
||||||
|
variant = krea2_pose_variant_catalog.get_variant(variant_key)
|
||||||
|
if not variant:
|
||||||
|
empty = {
|
||||||
|
"key": str(variant_key or ""),
|
||||||
|
"status": "missing",
|
||||||
|
"summary": "missing Krea2 pose variant",
|
||||||
|
}
|
||||||
|
return hardcore_position_config or "", str(variant_key or ""), "", "", empty["summary"], json.dumps(empty, sort_keys=True)
|
||||||
|
|
||||||
|
positions = _variant_positions(variant)
|
||||||
|
family = _variant_family(variant.get("action_family") or variant.get("family"))
|
||||||
|
config = build_hardcore_position_pool_json(
|
||||||
|
hardcore_position_config=hardcore_position_config or "",
|
||||||
|
combine_mode=combine_mode,
|
||||||
|
family=family,
|
||||||
|
selected_positions=positions,
|
||||||
|
)
|
||||||
|
prompt_cues = "; ".join(str(cue) for cue in variant.get("prompt_cues", []) if str(cue).strip())
|
||||||
|
avoid_cues = "; ".join(str(cue) for cue in variant.get("avoid_cues", []) if str(cue).strip())
|
||||||
|
summary = (
|
||||||
|
f"variant={variant.get('key')}; status={variant.get('status')}; "
|
||||||
|
f"family={family}; positions={','.join(positions) or 'none'}"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
config,
|
||||||
|
str(variant.get("key") or variant_key),
|
||||||
|
prompt_cues,
|
||||||
|
avoid_cues,
|
||||||
|
summary,
|
||||||
|
json.dumps(variant, ensure_ascii=True, sort_keys=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SxCPHardcoreActionFilter:
|
class SxCPHardcoreActionFilter:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
@@ -128,9 +202,11 @@ class SxCPHardcoreActionFilter:
|
|||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
"SxCPHardcorePositionPool": SxCPHardcorePositionPool,
|
"SxCPHardcorePositionPool": SxCPHardcorePositionPool,
|
||||||
"SxCPHardcoreActionFilter": SxCPHardcoreActionFilter,
|
"SxCPHardcoreActionFilter": SxCPHardcoreActionFilter,
|
||||||
|
"SxCPKrea2PoseVariant": SxCPKrea2PoseVariant,
|
||||||
}
|
}
|
||||||
|
|
||||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||||
"SxCPHardcorePositionPool": "SxCP Hardcore Position Pool",
|
"SxCPHardcorePositionPool": "SxCP Hardcore Position Pool",
|
||||||
"SxCPHardcoreActionFilter": "SxCP Hardcore Action Filter",
|
"SxCPHardcoreActionFilter": "SxCP Hardcore Action Filter",
|
||||||
|
"SxCPKrea2PoseVariant": "SxCP Krea2 Pose Variant",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -322,6 +322,11 @@ NODE_INPUT_TOOLTIPS = {
|
|||||||
"combine_mode": "replace discards incoming position choices; add merges these choices with the incoming config.",
|
"combine_mode": "replace discards incoming position choices; add merges these choices with the incoming config.",
|
||||||
"hardcore_position_config": "Optional incoming config. Usually connect previous Position Pool here only when chaining pools.",
|
"hardcore_position_config": "Optional incoming config. Usually connect previous Position Pool here only when chaining pools.",
|
||||||
},
|
},
|
||||||
|
"SxCPKrea2PoseVariant": {
|
||||||
|
"variant_key": "Atlas-calibrated Krea2 POV pose variant. Proven variants have fixed-seed evidence in the eval log.",
|
||||||
|
"combine_mode": "replace discards incoming position choices; add merges this variant with the incoming position config.",
|
||||||
|
"hardcore_position_config": "Optional incoming hardcore position config. Connect this when layering a variant on an existing pool.",
|
||||||
|
},
|
||||||
"SxCPHardcoreActionFilter": {
|
"SxCPHardcoreActionFilter": {
|
||||||
"focus": "keep_pool preserves/broadens the incoming pool; *_only modes force one action family.",
|
"focus": "keep_pool preserves/broadens the incoming pool; *_only modes force one action family.",
|
||||||
"allow_toys": "Allow toy/strap-on wording in hardcore actions.",
|
"allow_toys": "Allow toy/strap-on wording in hardcore actions.",
|
||||||
|
|||||||
@@ -8869,6 +8869,7 @@ def smoke_node_hardcore_position_registration() -> None:
|
|||||||
required_nodes = [
|
required_nodes = [
|
||||||
"SxCPHardcorePositionPool",
|
"SxCPHardcorePositionPool",
|
||||||
"SxCPHardcoreActionFilter",
|
"SxCPHardcoreActionFilter",
|
||||||
|
"SxCPKrea2PoseVariant",
|
||||||
]
|
]
|
||||||
for node_name in required_nodes:
|
for node_name in required_nodes:
|
||||||
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
|
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
|
||||||
@@ -8911,6 +8912,25 @@ def smoke_node_hardcore_position_registration() -> None:
|
|||||||
_expect(parsed_filter.get("allow_outercourse") is True, "Hardcore Action Filter should allow outercourse")
|
_expect(parsed_filter.get("allow_outercourse") is True, "Hardcore Action Filter should allow outercourse")
|
||||||
_expect("blocked=" in filter_summary, "Hardcore Action Filter summary lost blocked-gate details")
|
_expect("blocked=" in filter_summary, "Hardcore Action Filter summary lost blocked-gate details")
|
||||||
|
|
||||||
|
variant_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2PoseVariant"]
|
||||||
|
variant_inputs = variant_node.INPUT_TYPES().get("required") or {}
|
||||||
|
_expect("variant_key" in variant_inputs, "Krea2 Pose Variant lost variant selector")
|
||||||
|
_expect("tooltip" in variant_inputs["variant_key"][1], "Krea2 Pose Variant tooltip injection missing")
|
||||||
|
_expect("pov_boobjob_upright_cleavage" in variant_inputs["variant_key"][0], "Krea2 Pose Variant lost boobjob option")
|
||||||
|
variant_config, variant_key, prompt_cues, avoid_cues, variant_summary, variant_json = variant_node().build(
|
||||||
|
"pov_boobjob_upright_cleavage",
|
||||||
|
"replace",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
parsed_variant_config = json.loads(variant_config)
|
||||||
|
parsed_variant = json.loads(variant_json)
|
||||||
|
_expect(variant_key == "pov_boobjob_upright_cleavage", "Krea2 Pose Variant returned wrong key")
|
||||||
|
_expect(parsed_variant_config.get("positions") == ["boobjob"], "Krea2 Pose Variant did not map to boobjob position config")
|
||||||
|
_expect(parsed_variant.get("status") == "proven", "Krea2 Pose Variant lost status metadata")
|
||||||
|
_expect("pressed-together breasts" in prompt_cues, "Krea2 Pose Variant lost prompt cues output")
|
||||||
|
_expect("torso bent forward" in avoid_cues, "Krea2 Pose Variant lost avoid cues output")
|
||||||
|
_expect("variant=pov_boobjob_upright_cleavage" in variant_summary, "Krea2 Pose Variant summary lost key")
|
||||||
|
|
||||||
|
|
||||||
def smoke_node_formatter_registration() -> None:
|
def smoke_node_formatter_registration() -> None:
|
||||||
required_nodes = [
|
required_nodes = [
|
||||||
|
|||||||
Reference in New Issue
Block a user