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. seeds still override the global seed for those parts.
`SxCP Seed Control` outputs `seed_config`, which can be connected to the prompt `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 builder's optional `seed_config` input. It also outputs a `summary` string with
visible seed value is materialized before the workflow queues, and that exact the resolved value for every axis. When an axis is set to `random`, the widget
value is used for the queued prompt. The mode returns to `random` after queueing can stay at `-1`, but the emitted `seed_config` and `summary` contain the
so the next run can reroll. Use `Lock Random Seeds Now` on the node when you want concrete seed used for that queued prompt. Use `Lock Random Seeds Now` on the
to convert the current random axes into fixed reusable seeds. 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 `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 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 - `follow_main`: always follows the final generator's main `seed` input and
ignores the entered axis seed. ignores the entered axis seed.
- `fixed`: always uses 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 The `summary` output lists the resolved value for every axis, including random
visible concrete seed and switches those axes to `fixed`. 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: 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 `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 `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 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 ## Seed Playbook
+39 -22
View File
@@ -76,8 +76,8 @@ class SxCPSeedControl:
required[f"{axis}_seed"] = ("INT", seed_spec) required[f"{axis}_seed"] = ("INT", seed_spec)
return {"required": required} return {"required": required}
RETURN_TYPES = (SXCP_SEED_CONFIG,) RETURN_TYPES = (SXCP_SEED_CONFIG, "STRING")
RETURN_NAMES = ("seed_config",) RETURN_NAMES = ("seed_config", "summary")
FUNCTION = "build" FUNCTION = "build"
CATEGORY = "prompt_builder" CATEGORY = "prompt_builder"
@@ -88,6 +88,21 @@ class SxCPSeedControl:
return random.random() return random.random()
return tuple(args), tuple(sorted(kwargs.items())) 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( def build(
self, self,
category_seed_mode, category_seed_mode,
@@ -109,27 +124,29 @@ class SxCPSeedControl:
composition_seed_mode, composition_seed_mode,
composition_seed, 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 ( return (
build_seed_config_json( config,
category_seed=category_seed, self._summary(config),
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,
),
) )
+35
View File
@@ -5225,6 +5225,7 @@ def smoke_node_utility_registration() -> None:
seed_inputs = seed_control.INPUT_TYPES().get("required") or {} seed_inputs = seed_control.INPUT_TYPES().get("required") or {}
_expect("category_seed_mode" in seed_inputs, "Seed Control lost category seed mode input") _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("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( _expect(
node_tooltips._tooltip_for_input("SxCPSeedControl", "category_seed_mode") 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.", == "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"), "Autoscaling switch input" in node_tooltips._tooltip_for_input("SxCPIndexSwitch", "input_12"),
"Node tooltip policy lost autoscaling input fallback", "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) seed, seed_config, summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPGlobalSeed"]().build(12345)
parsed_seed = json.loads(seed_config) parsed_seed = json.loads(seed_config)