From 29ca3ba369eb37e32b527f11a97dee895b554f74 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 27 Jun 2026 18:24:37 +0200 Subject: [PATCH] Clarify modular node tooltips --- node_tooltips.py | 77 +++++++++++++++++++++++++++++++++++-------- tools/prompt_smoke.py | 17 ++++++++-- 2 files changed, 77 insertions(+), 17 deletions(-) diff --git a/node_tooltips.py b/node_tooltips.py index 65579b3..57a294c 100644 --- a/node_tooltips.py +++ b/node_tooltips.py @@ -16,14 +16,14 @@ COMMON_INPUT_TOOLTIPS = { "generation_profile": "General style/intensity profile from SxCP Generation Profile.", "filter_config": "Ethnicity/body filter config. Ethnicity List can feed this too.", "ethnicity_list": "Optional ethnicity pool. When connected, it overrides the slot or generator ethnicity picker.", - "seed_config": "Per-axis seed config. Connect Global Seed, Seed Locker, or Seed Control here.", - "camera_config": "Camera config used by the prompt formatter when camera mode is from_camera_config.", + "seed_config": "Per-axis seed config. Use Global Seed for full reproducibility, Seed Locker to reroll one axis, or Seed Control for manual axis seeds.", + "camera_config": "Camera config consumed only by nodes/options set to from_camera_config.", "location_config": "Location config from SxCP Location Pool. It can replace or add to the category scene pool.", "composition_config": "Composition config from SxCP Composition Pool or Location Theme. It can replace or add framing options.", "softcore_camera_config": "Camera config used only for the softcore Insta/OF prompt. Falls back to camera_config if empty.", "hardcore_camera_config": "Camera config used only for the hardcore Insta/OF prompt. Falls back to camera_config if empty.", "character_profile": "Saved or loaded single-character profile. Character slots override this for configured casts.", - "character_cast": "Chain character slots here. The node closest to the final generator becomes the next auto_chain label.", + "character_cast": "Chain slot output into the next slot, then into the generator. The first enabled woman/man in chain order becomes A, then B, and so on.", "character_slot": "Single slot payload for saving/loading profiles or debugging one character.", "hardcore_position_config": "Hardcore action/position config. Chain Position Pool into Action Filter, then into the generator.", "custom_locations": "One custom location per line. Use plain text, or slug: location text.", @@ -46,8 +46,8 @@ COMMON_INPUT_TOOLTIPS = { "megapixels": "Approximate megapixel count for the selected bucket.", "enabled": "Enable this node's effect while keeping it wired in the graph.", "combine_mode": "replace starts a new pool/config; add merges selected values into the incoming config.", - "manual": "Manual character details config. Non-empty manual fields override generated slot details.", - "characteristics": "Chainable character characteristic pool such as age/body/eyes/clothing.", + "manual": "Manual character details config from Manual Details. Non-empty fields override generated slot details.", + "characteristics": "Chainable character pool for controlled randomness such as age, body, eyes, and clothing.", "hair_config": "Chainable hair pool. Combine length, color, and style nodes before the character slot.", "summary": "Human-readable description of the config produced by this node.", "status": "Operation result or warning text.", @@ -61,7 +61,7 @@ COMMON_INPUT_TOOLTIPS = { "source": "Where the save node reads character data from.", "subject_type": "Character type for this slot or saved profile.", "label": "Character label. auto_chain assigns the next Woman/Man label based on incoming cast order.", - "slot_seed": "Per-character seed. Use -1 to follow the generator person seed.", + "slot_seed": "Per-character seed for age/body/hair/eyes random picks. Use -1 to derive from the generator person seed.", "age": "Age choice for this slot. Use Age Range node for a custom random age pool.", "manual_age": "Exact age phrase override, for example '32-year-old adult'.", "ethnicity": "Ethnicity choice for this slot. A connected Ethnicity List overrides this picker.", @@ -83,9 +83,9 @@ COMMON_INPUT_TOOLTIPS = { "expression_intensity_mode": "For Generation Profile, choose profile_default, random, or fixed value from expression_intensity.", "softcore_expression_intensity": "Optional expression intensity override for this character in softcore prompts. -1 inherits.", "hardcore_expression_intensity": "Optional expression intensity override for this character in hardcore prompts. -1 inherits.", - "presence_mode": "Controls whether the character is visible, implied POV, or otherwise present.", - "softcore_outfit": "Manual softcore outfit text for this character.", - "hardcore_clothing": "Manual hardcore clothing/body exposure text for this character.", + "presence_mode": "Controls whether the character is visible or acts as the male POV participant.", + "softcore_outfit": "Manual softcore outfit text for this character. Prefer Character Clothing for reusable outfit pools.", + "hardcore_clothing": "Manual hardcore exposure text for this character. Use explicit nude states when you do not want clothing words repeated.", "custom_softcore_outfits": "One custom softcore outfit per line. Used when softcore_source is custom.", "custom_hardcore_clothing": "One custom hardcore clothing/body exposure state per line.", "condition": "Loop condition. When false, the loop stops and passes current values through.", @@ -93,8 +93,8 @@ COMMON_INPUT_TOOLTIPS = { "skip": "Number of leading loop indexes to skip. skip=1 starts generation at index 2.", "collection": "Existing accumulated value or batch.", "value": "Value to append, store, or pass through.", - "store_key": "Accumulator memory key. Same key shares stored entries across executions.", - "store_key_input": "Connect SxCP Accumulator store_key here so preview/delete/save uses the same accumulator and graph dependency.", + "store_key": "Accumulator memory key. Leave blank for node-local storage, or use the same text to share one store across nodes.", + "store_key_input": "Connect SxCP Accumulator store_key output here so preview/delete/save targets the same store and keeps a graph dependency.", "action": "Accumulator operation: append, replace, clear, read, or append a variant.", "max_items": "Maximum stored entries kept in this accumulator.", "image_batch_mode": "How image entries are batched when dimensions differ.", @@ -109,7 +109,7 @@ COMMON_INPUT_TOOLTIPS = { "delete_action": "Optional execution-time delete operation. JS buttons can delete interactively without setting this.", "delete_entry_id": "Entry id to delete when delete_action is delete_entry_id.", "delete_index": "1-based entry index to delete when delete_action is delete_index. 0 disables it.", - "save_batch": "When enabled, save all current accumulator images once finished is true.", + "save_batch": "When enabled, save all current accumulator images during node execution once finished is true.", "finished": "Gate for saving. Outside a loop, leave true; inside a loop, wire a final-iteration signal.", "save_path": "Folder to save the accumulator batch. Relative paths are inside ComfyUI output; absolute paths are used directly.", "filename_prefix": "Filename prefix for saved accumulator images.", @@ -166,8 +166,11 @@ COMMON_INPUT_TOOLTIPS = { } NODE_INPUT_TOOLTIPS = { + "SxCPGlobalSeed": { + "global_seed": "Master reproducibility seed. Connect seed to generator seed and seed_config to seed_config so random choices can be recreated exactly.", + }, "SxCPSeedControl": { - "category_seed_mode": "auto/follow_main follows the main seed; fixed uses category_seed; random rerolls this axis each queue.", + "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.", "person_seed_mode": "Controls generated character appearance unless a slot seed overrides it.", @@ -178,7 +181,9 @@ NODE_INPUT_TOOLTIPS = { "composition_seed_mode": "Controls framing/composition text.", }, "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_seed": "Seed for the selected axis only. Leave -1 to derive a stable reroll from base_seed.", }, "SxCPCastBias": { "seed": "Fixed cast-bias seed. Use -1 for a fresh cast each queue, or connect Global Seed/Seed Locker through seed_config.", @@ -205,6 +210,7 @@ NODE_INPUT_TOOLTIPS = { "phone_visibility": "Use phone_hidden or suppress_phone_visibility when you do not want 'phone hidden' text in prompts.", }, "SxCPCameraOrbitControl": { + "enabled": "When false, outputs an empty camera config so downstream nodes fall back to their own camera settings.", "horizontal_angle": "Orbit angle in degrees. 0=front, 90=right side, 180=back, 270=left side.", "vertical_angle": "Camera elevation. Negative looks up, positive looks down.", "zoom": "Maps to distance/framing when framing is from_zoom.", @@ -215,6 +221,7 @@ NODE_INPUT_TOOLTIPS = { "qwen_prompt": "Camera prompt from Qwen MultiAngle, for example ' front-right quarter view eye-level shot medium shot'.", "camera_info": "Optional structured camera_info from Qwen MultiAngle. Used before qwen_prompt when prefer_camera_info is true.", "prefer_camera_info": "Use structured camera_info values when available instead of parsing the text prompt.", + "phone_visibility": "Leave auto when using Qwen/Orbit camera prompts unless you explicitly want phone visibility text.", "suppress_phone_visibility": "Avoid adding phone visibility text unless you explicitly set a phone option.", }, "SxCPHardcorePositionPool": { @@ -242,7 +249,7 @@ NODE_INPUT_TOOLTIPS = { "hardcore_level": "Controls how explicit the hardcore prompt style is.", "platform_style": "Instagram/OnlyFans styling bias for the dual prompt pair.", "continuity": "Whether the softcore and hardcore prompts share the room/creator setup.", - "hardcore_clothing_continuity": "How clothing carries from softcore to hardcore. explicit_nude omits clothing references.", + "hardcore_clothing_continuity": "How clothing carries from softcore to hardcore. explicit_nude avoids outfit references so clothing tokens do not fight nudity.", "softcore_camera_mode": "Camera mode for the softcore prompt, or from_camera_config.", "hardcore_camera_mode": "Camera mode for the hardcore prompt. same_as_softcore reuses the softcore setting.", "camera_detail": "Global camera verbosity for the pair unless a camera config overrides it.", @@ -256,6 +263,34 @@ NODE_INPUT_TOOLTIPS = { "SxCPPromptBuilderFromConfigs": { "seed": "Main seed. Connect Seed Config for per-axis control.", }, + "SxCPCharacterSlot": { + "subject_type": "Choose whether this slot creates a woman or man. Man is required for POV presence.", + "label": "auto_chain uses the next free label for that subject type based on incoming cast order.", + "character_cast": "Optional incoming cast from the previous slot. Output this node's character_cast to the next slot or final generator.", + "presence_mode": "POV only has an effect for men; it makes the man implied by camera/body cues instead of fully described.", + "characteristics": "Optional controlled-random pool from age/body/eye/clothing nodes. Connected pools override the matching random choices.", + "hair_config": "Optional controlled-random hair pool. Chain Hair Length, Hair Color, and Hair Style before this slot.", + }, + "SxCPWomanSlot": { + "label": "auto_chain uses the next free Woman label based on incoming cast order.", + "character_cast": "Optional incoming cast from the previous slot. Output this node's character_cast to the next slot or final generator.", + "figure_bias": "Broad woman body bias. Body Pool or manual body wording can narrow the actual phrase.", + "characteristics": "Optional controlled-random pool from age/body/eye/clothing nodes. Connected pools override the matching random choices.", + "hair_config": "Optional controlled-random hair pool. Chain Hair Length, Hair Color, and Hair Style before this slot.", + }, + "SxCPManSlot": { + "label": "auto_chain uses the next free Man label based on incoming cast order.", + "character_cast": "Optional incoming cast from the previous slot. Output this node's character_cast to the next slot or final generator.", + "presence_mode": "visible describes the man normally; pov makes him the first-person viewer and suppresses most man descriptor text.", + "descriptor_detail": "compact or minimal usually works better for non-primary men; full makes the man as detailed as the creator.", + "characteristics": "Optional controlled-random pool from age/body/eye/clothing nodes. Connected pools override the matching random choices.", + "hair_config": "Optional controlled-random hair pool. Chain Hair Length, Hair Color, and Hair Style before this slot.", + }, + "SxCPCharacterClothing": { + "softcore_source": "Built-in softcore outfit pool. custom reads custom_softcore_outfits; no_change leaves the current pool untouched.", + "hardcore_state": "Hardcore exposure pool. explicit_nude avoids outfit references; partially_removed can intentionally keep clothing words.", + "characteristics": "Incoming characteristic pool to extend or replace with clothing choices.", + }, "SxCPCharacterProfileSave": { "profile_name": "Profile filename stem. Saving requires save_now=true.", "metadata_json": "Use generator metadata to save the currently generated character without regenerating it.", @@ -297,6 +332,20 @@ NODE_INPUT_TOOLTIPS = { "SxCPAccumulator": { "image_batch_mode": "same_size_only keeps incompatible sizes separate; resize_to_first forces one image batch.", }, + "SxCPAccumulatorPreview": { + "store_key": "Use the same key as the Accumulator store. Prefer wiring store_key_input from Accumulator to avoid mismatches.", + "view_mode": "grid shows all entries together; carousel keeps one large image visible for review.", + "zoom_level": "Visual preview scale only. It does not resize stored images or saved files.", + "delete_action": "Execution-time delete/clear. The preview JS buttons can also delete without changing this widget.", + "save_batch": "Saves all current accumulator images when the workflow executes and finished is true.", + "clear_after_save": "Clear the in-memory accumulator only after a successful save.", + }, + "SxCPIndexSwitch": { + "mode": "pick_input selects input_N as value; route_output sends route_value to output_N.", + "index": "Selected input/output index. With one_based, index 1 maps to input_1/output_1.", + "missing_behavior": "Controls missing indexes: fallback uses fallback, clamp/wrap select another slot, none returns empty.", + "route_value": "Value sent to the selected output_N when mode is route_output.", + }, } diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index da97ed3..25a12f0 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -7408,15 +7408,19 @@ def smoke_node_utility_registration() -> None: 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_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry") + global_seed_inputs = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPGlobalSeed"].INPUT_TYPES().get("required") or {} + global_seed_tooltip = global_seed_inputs.get("global_seed", (None, {}))[1].get("tooltip", "") + _expect("Connect seed to generator seed" in global_seed_tooltip, "Global Seed tooltip should explain generator wiring") 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("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") _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.", + "random rerolls this axis at queue time" in category_seed_tooltip + and "field value stays unchanged" in category_seed_tooltip, "Node tooltip policy lost Seed Control override", ) _expect( @@ -7928,9 +7932,16 @@ def smoke_node_character_registration() -> None: _expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry") woman_slot_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPWomanSlot"] - woman_slot_inputs = woman_slot_node.INPUT_TYPES().get("required") or {} + woman_slot_input_types = woman_slot_node.INPUT_TYPES() + woman_slot_inputs = woman_slot_input_types.get("required") or {} _expect("slot_seed" in woman_slot_inputs, "Woman Slot lost slot_seed input") _expect("tooltip" in woman_slot_inputs["slot_seed"][1], "Woman Slot tooltip injection missing") + woman_slot_optional = woman_slot_input_types.get("optional") or {} + cast_tooltip = woman_slot_optional.get("character_cast", (None, {}))[1].get("tooltip", "") + _expect("incoming cast from the previous slot" in cast_tooltip, "Woman Slot character_cast tooltip should explain chain order") + man_slot_required = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPManSlot"].INPUT_TYPES().get("required") or {} + presence_tooltip = man_slot_required.get("presence_mode", (None, {}))[1].get("tooltip", "") + _expect("first-person viewer" in presence_tooltip, "Man Slot presence tooltip should explain POV behavior") hair_config, hair_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPHairColor"]().build( "replace_axis",