Add clothing seed axis vocabulary

This commit is contained in:
2026-07-01 16:13:51 +02:00
parent c0985cddbe
commit 1d23ce32aa
5 changed files with 72 additions and 5 deletions
+5
View File
@@ -65,6 +65,7 @@ class SxCPSeedControl:
"category",
"subcategory",
"content",
"clothing",
"person",
"scene",
"pose",
@@ -117,6 +118,8 @@ class SxCPSeedControl:
subcategory_seed,
content_seed_mode,
content_seed,
clothing_seed_mode,
clothing_seed,
person_seed_mode,
person_seed,
scene_seed_mode,
@@ -134,6 +137,7 @@ class SxCPSeedControl:
category_seed=category_seed,
subcategory_seed=subcategory_seed,
content_seed=content_seed,
clothing_seed=clothing_seed,
person_seed=person_seed,
scene_seed=scene_seed,
pose_seed=pose_seed,
@@ -143,6 +147,7 @@ class SxCPSeedControl:
category_seed_mode=category_seed_mode,
subcategory_seed_mode=subcategory_seed_mode,
content_seed_mode=content_seed_mode,
clothing_seed_mode=clothing_seed_mode,
person_seed_mode=person_seed_mode,
scene_seed_mode=scene_seed_mode,
pose_seed_mode=pose_seed_mode,
+3 -1
View File
@@ -257,6 +257,8 @@ NODE_INPUT_TOOLTIPS = {
"category_seed_mode": "auto/follow_main follows the main seed; fixed uses category_seed; random rerolls this axis at queue time while the field value stays unchanged.",
"subcategory_seed_mode": "Controls which subcategory is selected. Change this to switch oral vs penetration when both are allowed.",
"content_seed_mode": "Controls item/outfit content for non-pose categories.",
"clothing_seed_mode": "Controls clothing/outfit selection separately from content item selection.",
"clothing_seed": "Seed used when clothing_seed_mode is fixed or auto with a non-negative value.",
"person_seed_mode": "Controls generated character appearance unless a slot seed overrides it.",
"scene_seed_mode": "Controls location/scene selection.",
"pose_seed_mode": "Controls pose/item selection for pose categories, including hardcore positions.",
@@ -266,7 +268,7 @@ NODE_INPUT_TOOLTIPS = {
},
"SxCPSeedLocker": {
"base_seed": "Master seed for the locked result. Use the same value as the generator seed for simplest reproduction.",
"reroll_axis": "Choose the one axis to change while the rest stays locked. Use pose for sexual pose, scene for location, person for appearance.",
"reroll_axis": "Choose the one axis to change while the rest stays locked. Use clothing for outfit-only rerolls, pose for sexual pose, scene for location, person for appearance.",
"reroll_seed": "Seed for the selected axis only. Leave -1 to derive a stable reroll from base_seed.",
},
"SxCPCastBias": {
+4
View File
@@ -875,6 +875,7 @@ def build_seed_config_json(
category_seed: int = -1,
subcategory_seed: int = -1,
content_seed: int = -1,
clothing_seed: int = -1,
person_seed: int = -1,
scene_seed: int = -1,
pose_seed: int = -1,
@@ -884,6 +885,7 @@ def build_seed_config_json(
category_seed_mode: str = "auto",
subcategory_seed_mode: str = "auto",
content_seed_mode: str = "auto",
clothing_seed_mode: str = "auto",
person_seed_mode: str = "auto",
scene_seed_mode: str = "auto",
pose_seed_mode: str = "auto",
@@ -895,6 +897,7 @@ def build_seed_config_json(
category_seed=category_seed,
subcategory_seed=subcategory_seed,
content_seed=content_seed,
clothing_seed=clothing_seed,
person_seed=person_seed,
scene_seed=scene_seed,
pose_seed=pose_seed,
@@ -904,6 +907,7 @@ def build_seed_config_json(
category_seed_mode=category_seed_mode,
subcategory_seed_mode=subcategory_seed_mode,
content_seed_mode=content_seed_mode,
clothing_seed_mode=clothing_seed_mode,
person_seed_mode=person_seed_mode,
scene_seed_mode=scene_seed_mode,
pose_seed_mode=pose_seed_mode,
+12 -1
View File
@@ -9,6 +9,7 @@ SEED_AXIS_SALTS = {
"category": 31,
"subcategory": 37,
"content": 41,
"clothing": 41,
"person": 43,
"scene": 47,
"pose": 53,
@@ -20,7 +21,8 @@ SEED_AXIS_SALTS = {
SEED_AXIS_ALIASES = {
"category": ("category_seed", "category"),
"subcategory": ("subcategory_seed", "subcategory"),
"content": ("content_seed", "item_seed", "outfit_seed", "sexual_pose_seed", "content"),
"content": ("content_seed", "item_seed", "sexual_pose_seed", "content"),
"clothing": ("clothing_seed", "outfit_seed", "wardrobe_seed", "content_seed", "content"),
"person": ("person_seed", "appearance_seed", "cast_seed", "person"),
"scene": ("scene_seed", "scene"),
"pose": ("pose_seed", "sexual_pose_seed", "pose"),
@@ -33,6 +35,7 @@ SEED_LOCK_AXES = (
"category",
"subcategory",
"content",
"clothing",
"person",
"scene",
"pose",
@@ -46,6 +49,7 @@ SEED_REROLL_GROUPS = {
"category": ("category",),
"subcategory": ("subcategory",),
"content": ("content",),
"clothing": ("clothing",),
"person": ("person",),
"scene": ("scene",),
"pose": ("pose", "role"),
@@ -53,6 +57,8 @@ SEED_REROLL_GROUPS = {
"expression": ("expression",),
"composition": ("composition",),
"content_pose": ("content", "pose", "role"),
"content_clothing": ("content", "clothing"),
"clothing_pose": ("clothing", "pose", "role"),
"scene_pose": ("scene", "pose", "role"),
}
SEED_REROLL_AXIS_CHOICES = list(SEED_REROLL_GROUPS.keys())
@@ -87,6 +93,8 @@ def normalize_reroll_axis(value: Any) -> str:
normalized = _normal_key(value)
aliases = {
"contentpose": "content_pose",
"contentclothing": "content_clothing",
"clothingpose": "clothing_pose",
"scenepose": "scene_pose",
}
normalized = aliases.get(normalized, normalized)
@@ -101,6 +109,7 @@ def build_seed_config_json(
category_seed: int = -1,
subcategory_seed: int = -1,
content_seed: int = -1,
clothing_seed: int = -1,
person_seed: int = -1,
scene_seed: int = -1,
pose_seed: int = -1,
@@ -110,6 +119,7 @@ def build_seed_config_json(
category_seed_mode: str = "auto",
subcategory_seed_mode: str = "auto",
content_seed_mode: str = "auto",
clothing_seed_mode: str = "auto",
person_seed_mode: str = "auto",
scene_seed_mode: str = "auto",
pose_seed_mode: str = "auto",
@@ -134,6 +144,7 @@ def build_seed_config_json(
"category_seed": axis_seed(category_seed, category_seed_mode),
"subcategory_seed": axis_seed(subcategory_seed, subcategory_seed_mode),
"content_seed": axis_seed(content_seed, content_seed_mode),
"clothing_seed": axis_seed(clothing_seed, clothing_seed_mode),
"person_seed": axis_seed(person_seed, person_seed_mode),
"scene_seed": axis_seed(scene_seed, scene_seed_mode),
"pose_seed": axis_seed(pose_seed, pose_seed_mode),
+48 -3
View File
@@ -13902,6 +13902,8 @@ def smoke_node_utility_registration() -> None:
seed_control = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSeedControl"]
seed_inputs = seed_control.INPUT_TYPES().get("required") or {}
_expect("category_seed_mode" in seed_inputs, "Seed Control lost category seed mode input")
_expect("clothing_seed_mode" in seed_inputs, "Seed Control lost clothing seed mode input")
_expect("clothing_seed" in seed_inputs, "Seed Control lost clothing seed input")
_expect("tooltip" in seed_inputs["category_seed_mode"][1], "Seed Control tooltip injection missing")
_expect(seed_control.RETURN_NAMES == ("seed_config", "summary"), "Seed Control lost visible summary output")
category_seed_tooltip = node_tooltips._tooltip_for_input("SxCPSeedControl", "category_seed_mode")
@@ -13910,6 +13912,8 @@ def smoke_node_utility_registration() -> None:
and "field value stays unchanged" in category_seed_tooltip,
"Node tooltip policy lost Seed Control override",
)
clothing_seed_tooltip = node_tooltips._tooltip_for_input("SxCPSeedControl", "clothing_seed_mode")
_expect("clothing/outfit" in clothing_seed_tooltip, "Node tooltip policy lost Seed Control clothing override")
_expect(
"Autoscaling switch input" in node_tooltips._tooltip_for_input("SxCPIndexSwitch", "input_12"),
"Node tooltip policy lost autoscaling input fallback",
@@ -13921,6 +13925,8 @@ def smoke_node_utility_registration() -> None:
-1,
"random",
-1,
"fixed",
222,
"auto",
123,
"auto",
@@ -13938,6 +13944,7 @@ def smoke_node_utility_registration() -> None:
_expect(parsed_seed_control.get("category_seed") == 0, "Seed Control fixed mode did not clamp negative seed")
_expect(parsed_seed_control.get("subcategory_seed") == -1, "Seed Control follow_main mode should emit -1")
_expect(int(parsed_seed_control.get("content_seed", -1)) >= 0, "Seed Control random mode did not emit resolved seed")
_expect(parsed_seed_control.get("clothing_seed") == 222, "Seed Control fixed clothing seed changed")
_expect(parsed_seed_control.get("person_seed") == 123, "Seed Control auto mode did not preserve explicit value")
_expect(parsed_seed_control.get("pose_seed") == 777, "Seed Control fixed mode did not preserve positive seed")
_expect(parsed_seed_control.get("role_seed") == -1, "Seed Control follow_main role did not emit -1")
@@ -14098,6 +14105,11 @@ def smoke_seed_config_policy() -> None:
_expect(pb.normalize_seed_mode("follow main") == "follow_main", "seed mode normalizer should accept spaced labels")
_expect(pb.normalize_seed_mode("FOLLOW-MAIN") == "follow_main", "seed mode normalizer should accept hyphenated labels")
_expect(pb.normalize_reroll_axis("content pose") == "content_pose", "reroll axis normalizer should accept spaced labels")
reroll_choices = pb.seed_reroll_axis_choices()
for expected_axis in ("clothing", "content_clothing", "clothing_pose"):
_expect(expected_axis in reroll_choices, f"seed reroll axis choices missing {expected_axis}")
_expect(pb.normalize_reroll_axis("clothing pose") == "clothing_pose", "reroll axis normalizer should accept clothing pose")
_expect(pb.normalize_reroll_axis("content clothing") == "content_clothing", "reroll axis normalizer should accept content clothing")
fixed_config = json.loads(
pb.build_seed_config_json(
@@ -14116,9 +14128,21 @@ def smoke_seed_config_policy() -> None:
_expect(fixed_config["pose_seed"] == -1, "follow_main seed mode should emit unlocked axis")
_expect(fixed_config["role_seed"] == 789, "auto seed mode should preserve numeric seed")
parsed = pb._parse_seed_config({"item_seed": "44", "pose_seed": "55", "bad": "nope"})
_expect(parsed == {"item_seed": 44, "pose_seed": 55}, "seed parser should keep integer-like values only")
parsed = pb._parse_seed_config({"item_seed": "44", "pose_seed": "55", "outfit_seed": "66", "bad": "nope"})
_expect(
parsed == {"item_seed": 44, "pose_seed": 55, "outfit_seed": 66},
"seed parser should keep integer-like values only",
)
_expect(pb._configured_axis_seed(parsed, "content") == 44, "content axis should honor item_seed alias")
_expect(pb._configured_axis_seed(parsed, "clothing") == 66, "clothing axis should honor outfit_seed alias")
_expect(
pb._configured_axis_seed({"content_seed": 77}, "clothing") == 77,
"clothing axis should keep content_seed as a legacy fallback",
)
_expect(
pb._configured_axis_seed({"content_seed": 77, "clothing_seed": 88}, "clothing") == 88,
"clothing_seed should override legacy content_seed fallback",
)
_expect(pb._configured_axis_seed(parsed, "role") == 55, "role axis should honor pose seed alias")
_expect(
seed_config.configured_seed_from_axes({"camera_seed": "88", "content_seed": "99"}, ("composition", "content")) == 88,
@@ -14133,9 +14157,30 @@ def smoke_seed_config_policy() -> None:
_expect(locked["content_seed"] == 999, "content_pose reroll should alter content seed")
_expect(locked["pose_seed"] == 999 and locked["role_seed"] == 999, "content_pose reroll should alter pose and role seeds")
_expect(locked["scene_seed"] == 100, "content_pose reroll should leave scene locked")
axis_trace = seed_config.axis_seed_trace({"content_seed": 44}, 99, 3, axes=("content", "scene"))
clothing_locked = json.loads(pb.build_seed_lock_config_json(base_seed=100, reroll_axis="clothing", reroll_seed=777))
_expect(clothing_locked["clothing_seed"] == 777, "clothing reroll should alter clothing seed")
_expect(clothing_locked["content_seed"] == 100, "clothing reroll should leave content locked")
_expect(clothing_locked["pose_seed"] == 100 and clothing_locked["role_seed"] == 100, "clothing reroll should leave pose and role locked")
content_clothing_locked = json.loads(
pb.build_seed_lock_config_json(base_seed=100, reroll_axis="content_clothing", reroll_seed=778)
)
_expect(content_clothing_locked["content_seed"] == 778, "content_clothing reroll should alter content seed")
_expect(content_clothing_locked["clothing_seed"] == 778, "content_clothing reroll should alter clothing seed")
_expect(content_clothing_locked["pose_seed"] == 100, "content_clothing reroll should leave pose locked")
clothing_pose_locked = json.loads(pb.build_seed_lock_config_json(base_seed=100, reroll_axis="clothing_pose", reroll_seed=779))
_expect(clothing_pose_locked["clothing_seed"] == 779, "clothing_pose reroll should alter clothing seed")
_expect(clothing_pose_locked["pose_seed"] == 779 and clothing_pose_locked["role_seed"] == 779, "clothing_pose reroll should alter pose and role seeds")
_expect(clothing_pose_locked["content_seed"] == 100, "clothing_pose reroll should leave content locked")
content_pose_locked = json.loads(pb.build_seed_lock_config_json(base_seed=100, reroll_axis="content_pose", reroll_seed=780))
_expect(content_pose_locked["clothing_seed"] == 100, "content_pose reroll should not alter clothing seed")
axis_trace = seed_config.axis_seed_trace({"content_seed": 44, "clothing_seed": 66}, 99, 3, axes=("content", "clothing", "scene"))
_expect(axis_trace["content"]["source"] == "configured", "Seed axis trace lost configured source")
_expect(axis_trace["content"]["seed"] == 44, "Seed axis trace lost configured seed")
_expect(axis_trace["clothing"]["source"] == "configured", "Seed axis trace lost clothing configured source")
_expect(axis_trace["clothing"]["seed"] == 66, "Seed axis trace lost configured clothing seed")
_expect(axis_trace["scene"]["source"] == "main", "Seed axis trace lost main source")
_expect(axis_trace["scene"]["seed"] == 99, "Seed axis trace lost main seed")
_expect(