Expose seed control summary

This commit is contained in:
2026-06-27 12:59:51 +02:00
parent 6ff3b0cbd5
commit 58f74e44e5
4 changed files with 88 additions and 31 deletions
+10 -8
View File
@@ -331,11 +331,11 @@ prompt result. Manual fields and explicitly fixed per-axis or character-slot
seeds still override the global seed for those parts.
`SxCP Seed Control` outputs `seed_config`, which can be connected to the prompt
builder's optional `seed_config` input. When an axis is set to `random`, the
visible seed value is materialized before the workflow queues, and that exact
value is used for the queued prompt. The mode returns to `random` after queueing
so the next run can reroll. Use `Lock Random Seeds Now` on the node when you want
to convert the current random axes into fixed reusable seeds.
builder's optional `seed_config` input. It also outputs a `summary` string with
the resolved value for every axis. When an axis is set to `random`, the widget
can stay at `-1`, but the emitted `seed_config` and `summary` contain the
concrete seed used for that queued prompt. Use `Lock Random Seeds Now` on the
node when you want to convert the current random axes into fixed reusable seeds.
`SxCP Seed Locker` is the fast version for iteration. Set `base_seed` to a seed
you like, choose one `reroll_axis`, and connect its `seed_config`. All other
@@ -866,10 +866,12 @@ axis has its own mode plus seed value:
- `follow_main`: always follows the final generator's main `seed` input and
ignores the entered axis seed.
- `fixed`: always uses the entered axis seed.
- `random`: generates a fresh visible axis seed when the workflow queues.
- `random`: generates a fresh resolved axis seed when the workflow queues.
The `Lock Random Seeds Now` button turns every current `random` axis into a
visible concrete seed and switches those axes to `fixed`.
The `summary` output lists the resolved value for every axis, including random
axes whose visible widget value remains `-1`. The `Lock Random Seeds Now` button
turns every current `random` axis into a visible concrete seed and switches
those axes to `fixed`.
For exact prompt reproduction, `SxCP Global Seed` is the shortest path:
+4 -1
View File
@@ -192,7 +192,10 @@ Seed routing is centralized in `seed_config.py` around `SEED_AXIS_SALTS`,
`SxCP Global Seed`, `SxCP Seed Control`, and `SxCP Seed Locker` all feed
`seed_config`. Values below zero mean the row's main seed still drives that
axis. Fixed axis seeds allow changing only one road, for example changing
`pose`/`role` while keeping person, scene, and category stable.
`pose`/`role` while keeping person, scene, and category stable. `SxCP Seed
Control` keeps `seed_config` as its first output and also emits a `summary`
showing resolved per-axis values, including random-mode seeds whose widget value
may still be `-1`.
## Seed Playbook
+39 -22
View File
@@ -76,8 +76,8 @@ class SxCPSeedControl:
required[f"{axis}_seed"] = ("INT", seed_spec)
return {"required": required}
RETURN_TYPES = (SXCP_SEED_CONFIG,)
RETURN_NAMES = ("seed_config",)
RETURN_TYPES = (SXCP_SEED_CONFIG, "STRING")
RETURN_NAMES = ("seed_config", "summary")
FUNCTION = "build"
CATEGORY = "prompt_builder"
@@ -88,6 +88,21 @@ class SxCPSeedControl:
return random.random()
return tuple(args), tuple(sorted(kwargs.items()))
@classmethod
def _summary(cls, config_json):
try:
config = json.loads(config_json)
except (TypeError, ValueError, json.JSONDecodeError):
return "invalid seed config"
parts = []
for axis in cls.SEED_AXES:
try:
value = int(config.get(f"{axis}_seed", -1))
except (TypeError, ValueError):
value = -1
parts.append(f"{axis}={'follow_main' if value < 0 else value}")
return "resolved seeds: " + "; ".join(parts)
def build(
self,
category_seed_mode,
@@ -109,27 +124,29 @@ class SxCPSeedControl:
composition_seed_mode,
composition_seed,
):
config = build_seed_config_json(
category_seed=category_seed,
subcategory_seed=subcategory_seed,
content_seed=content_seed,
person_seed=person_seed,
scene_seed=scene_seed,
pose_seed=pose_seed,
role_seed=role_seed,
expression_seed=expression_seed,
composition_seed=composition_seed,
category_seed_mode=category_seed_mode,
subcategory_seed_mode=subcategory_seed_mode,
content_seed_mode=content_seed_mode,
person_seed_mode=person_seed_mode,
scene_seed_mode=scene_seed_mode,
pose_seed_mode=pose_seed_mode,
role_seed_mode=role_seed_mode,
expression_seed_mode=expression_seed_mode,
composition_seed_mode=composition_seed_mode,
)
return (
build_seed_config_json(
category_seed=category_seed,
subcategory_seed=subcategory_seed,
content_seed=content_seed,
person_seed=person_seed,
scene_seed=scene_seed,
pose_seed=pose_seed,
role_seed=role_seed,
expression_seed=expression_seed,
composition_seed=composition_seed,
category_seed_mode=category_seed_mode,
subcategory_seed_mode=subcategory_seed_mode,
content_seed_mode=content_seed_mode,
person_seed_mode=person_seed_mode,
scene_seed_mode=scene_seed_mode,
pose_seed_mode=pose_seed_mode,
role_seed_mode=role_seed_mode,
expression_seed_mode=expression_seed_mode,
composition_seed_mode=composition_seed_mode,
),
config,
self._summary(config),
)
+35
View File
@@ -5225,6 +5225,7 @@ def smoke_node_utility_registration() -> None:
seed_inputs = seed_control.INPUT_TYPES().get("required") or {}
_expect("category_seed_mode" in seed_inputs, "Seed Control lost category seed mode 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")
_expect(
node_tooltips._tooltip_for_input("SxCPSeedControl", "category_seed_mode")
== "auto/follow_main follows the main seed; fixed uses category_seed; random rerolls this axis each queue.",
@@ -5234,6 +5235,40 @@ def smoke_node_utility_registration() -> None:
"Autoscaling switch input" in node_tooltips._tooltip_for_input("SxCPIndexSwitch", "input_12"),
"Node tooltip policy lost autoscaling input fallback",
)
seed_control_config, seed_control_summary = seed_control().build(
"fixed",
-1,
"follow_main",
-1,
"random",
-1,
"auto",
123,
"auto",
-1,
"fixed",
777,
"follow_main",
888,
"auto",
-1,
"fixed",
999,
)
parsed_seed_control = json.loads(seed_control_config)
_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("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")
_expect(parsed_seed_control.get("composition_seed") == 999, "Seed Control fixed composition seed changed")
_expect("category=0" in seed_control_summary, "Seed Control summary lost fixed resolved seed")
_expect("subcategory=follow_main" in seed_control_summary, "Seed Control summary lost follow_main marker")
_expect(
f"content={parsed_seed_control['content_seed']}" in seed_control_summary,
"Seed Control summary lost random resolved seed value",
)
seed, seed_config, summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPGlobalSeed"]().build(12345)
parsed_seed = json.loads(seed_config)