Extract builder nodes
This commit is contained in:
+12
-219
@@ -404,6 +404,10 @@ try:
|
||||
NODE_CLASS_MAPPINGS as CAMERA_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as CAMERA_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from .node_builder import (
|
||||
NODE_CLASS_MAPPINGS as BUILDER_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as BUILDER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from .node_character import (
|
||||
NODE_CLASS_MAPPINGS as CHARACTER_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as CHARACTER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
@@ -433,12 +437,7 @@ try:
|
||||
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from .prompt_builder import (
|
||||
build_prompt,
|
||||
build_prompt_from_configs,
|
||||
category_choices,
|
||||
ethnicity_choices,
|
||||
save_character_profile_payload,
|
||||
subcategory_choices,
|
||||
)
|
||||
except ImportError:
|
||||
from loop_nodes import (
|
||||
@@ -453,6 +452,10 @@ except ImportError:
|
||||
NODE_CLASS_MAPPINGS as CAMERA_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as CAMERA_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from node_builder import (
|
||||
NODE_CLASS_MAPPINGS as BUILDER_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as BUILDER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from node_character import (
|
||||
NODE_CLASS_MAPPINGS as CHARACTER_NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as CHARACTER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
@@ -482,12 +485,7 @@ except ImportError:
|
||||
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
||||
)
|
||||
from prompt_builder import (
|
||||
build_prompt,
|
||||
build_prompt_from_configs,
|
||||
category_choices,
|
||||
ethnicity_choices,
|
||||
save_character_profile_payload,
|
||||
subcategory_choices,
|
||||
)
|
||||
|
||||
|
||||
@@ -565,206 +563,8 @@ if PromptServer is not None and web is not None:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
|
||||
class SxCPPromptBuilder:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"category": (category_choices(), {"default": "auto_weighted"}),
|
||||
"subcategory": (subcategory_choices(), {"default": "random"}),
|
||||
"row_number": ("INT", {"default": 1, "min": 1, "max": 1000000, "step": 1}),
|
||||
"start_index": ("INT", {"default": 41, "min": 1, "max": 1000000, "step": 1}),
|
||||
"seed": ("INT", {"default": 20260614, "min": 0, "max": 0xFFFFFFFF, "step": 1}),
|
||||
"clothing": (["random", "full", "minimal"], {"default": "random"}),
|
||||
"ethnicity": (ethnicity_choices(), {"default": "any"}),
|
||||
"poses": (["random", "standard", "evocative"], {"default": "random"}),
|
||||
"expression_enabled": ("BOOLEAN", {"default": True}),
|
||||
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
||||
"backside_bias": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"figure": (["random", "curvy", "balanced", "bombshell"], {"default": "random"}),
|
||||
"women_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
||||
"men_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
||||
"minimal_clothing_ratio": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
||||
"standard_pose_ratio": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
||||
"trigger": ("STRING", {"default": "sxcpinup_coloredpencil"}),
|
||||
"prepend_trigger_to_prompt": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
"optional": {
|
||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||
"seed_config": (SXCP_SEED_CONFIG,),
|
||||
"camera_config": (SXCP_CAMERA_CONFIG,),
|
||||
"location_config": (SXCP_LOCATION_CONFIG,),
|
||||
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
||||
"character_profile": (SXCP_CHARACTER_PROFILE,),
|
||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
||||
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
||||
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
|
||||
RETURN_NAMES = ("prompt", "negative_prompt", "caption", "metadata_json", "category", "subcategory")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
category,
|
||||
subcategory,
|
||||
row_number,
|
||||
start_index,
|
||||
seed,
|
||||
clothing,
|
||||
ethnicity,
|
||||
poses,
|
||||
expression_enabled,
|
||||
expression_intensity,
|
||||
backside_bias,
|
||||
figure,
|
||||
women_count,
|
||||
men_count,
|
||||
minimal_clothing_ratio,
|
||||
standard_pose_ratio,
|
||||
trigger,
|
||||
prepend_trigger_to_prompt,
|
||||
seed_config="",
|
||||
camera_config="",
|
||||
location_config="",
|
||||
composition_config="",
|
||||
character_profile="",
|
||||
character_cast="",
|
||||
hardcore_position_config="",
|
||||
extra_positive="",
|
||||
extra_negative="",
|
||||
no_plus_women=False,
|
||||
no_black=False,
|
||||
ethnicity_list="",
|
||||
):
|
||||
row = build_prompt(
|
||||
category=category,
|
||||
subcategory=subcategory,
|
||||
row_number=row_number,
|
||||
start_index=start_index,
|
||||
seed=seed,
|
||||
clothing=clothing,
|
||||
ethnicity=ethnicity_list or ethnicity,
|
||||
poses=poses,
|
||||
expression_enabled=expression_enabled,
|
||||
expression_intensity=expression_intensity,
|
||||
backside_bias=backside_bias,
|
||||
figure=figure,
|
||||
no_plus_women=no_plus_women,
|
||||
no_black=no_black,
|
||||
women_count=women_count,
|
||||
men_count=men_count,
|
||||
minimal_clothing_ratio=minimal_clothing_ratio,
|
||||
standard_pose_ratio=standard_pose_ratio,
|
||||
trigger=trigger,
|
||||
prepend_trigger_to_prompt=prepend_trigger_to_prompt,
|
||||
extra_positive=extra_positive or "",
|
||||
extra_negative=extra_negative or "",
|
||||
seed_config=seed_config or "",
|
||||
camera_config=camera_config or "",
|
||||
location_config=location_config or "",
|
||||
composition_config=composition_config or "",
|
||||
character_profile=character_profile or "",
|
||||
character_cast=character_cast or "",
|
||||
hardcore_position_config=hardcore_position_config or "",
|
||||
)
|
||||
return (
|
||||
row["prompt"],
|
||||
row["negative_prompt"],
|
||||
row["caption"],
|
||||
json.dumps(row, ensure_ascii=True, sort_keys=True),
|
||||
row.get("main_category", category),
|
||||
row.get("subcategory", subcategory),
|
||||
)
|
||||
|
||||
|
||||
class SxCPPromptBuilderFromConfigs:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"row_number": ("INT", {"default": 1, "min": 1, "max": 1000000, "step": 1}),
|
||||
"start_index": ("INT", {"default": 41, "min": 1, "max": 1000000, "step": 1}),
|
||||
"seed": ("INT", {"default": 20260614, "min": 0, "max": 0xFFFFFFFF, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"category_config": (SXCP_CATEGORY_CONFIG,),
|
||||
"cast_config": (SXCP_CAST_CONFIG,),
|
||||
"generation_profile": (SXCP_GENERATION_PROFILE,),
|
||||
"filter_config": (SXCP_FILTER_CONFIG,),
|
||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||
"seed_config": (SXCP_SEED_CONFIG,),
|
||||
"camera_config": (SXCP_CAMERA_CONFIG,),
|
||||
"location_config": (SXCP_LOCATION_CONFIG,),
|
||||
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
||||
"character_profile": (SXCP_CHARACTER_PROFILE,),
|
||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
||||
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
||||
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
|
||||
RETURN_NAMES = ("prompt", "negative_prompt", "caption", "metadata_json", "category", "subcategory")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
row_number,
|
||||
start_index,
|
||||
seed,
|
||||
category_config="",
|
||||
cast_config="",
|
||||
generation_profile="",
|
||||
filter_config="",
|
||||
ethnicity_list="",
|
||||
seed_config="",
|
||||
camera_config="",
|
||||
location_config="",
|
||||
composition_config="",
|
||||
character_profile="",
|
||||
character_cast="",
|
||||
hardcore_position_config="",
|
||||
extra_positive="",
|
||||
extra_negative="",
|
||||
):
|
||||
row = build_prompt_from_configs(
|
||||
row_number=row_number,
|
||||
start_index=start_index,
|
||||
seed=seed,
|
||||
category_config=category_config or "",
|
||||
cast_config=cast_config or "",
|
||||
generation_profile=generation_profile or "",
|
||||
filter_config=ethnicity_list or filter_config or "",
|
||||
seed_config=seed_config or "",
|
||||
camera_config=camera_config or "",
|
||||
location_config=location_config or "",
|
||||
composition_config=composition_config or "",
|
||||
character_profile=character_profile or "",
|
||||
character_cast=character_cast or "",
|
||||
hardcore_position_config=hardcore_position_config or "",
|
||||
extra_positive=extra_positive or "",
|
||||
extra_negative=extra_negative or "",
|
||||
)
|
||||
return (
|
||||
row["prompt"],
|
||||
row["negative_prompt"],
|
||||
row["caption"],
|
||||
json.dumps(row, ensure_ascii=True, sort_keys=True),
|
||||
row.get("main_category", ""),
|
||||
row.get("subcategory", ""),
|
||||
)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SxCPPromptBuilder": SxCPPromptBuilder,
|
||||
}
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_CLASS_MAPPINGS.update(BUILDER_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(SEED_RESOLUTION_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(CAMERA_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(CHARACTER_NODE_CLASS_MAPPINGS)
|
||||
@@ -773,15 +573,11 @@ NODE_CLASS_MAPPINGS.update(FORMATTER_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(INSTA_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(ROUTE_CONFIG_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(PROFILE_FILTER_NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update({
|
||||
"SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs,
|
||||
})
|
||||
NODE_CLASS_MAPPINGS.update(LOOP_NODE_CLASS_MAPPINGS)
|
||||
_install_input_tooltips(NODE_CLASS_MAPPINGS)
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SxCPPromptBuilder": "SxCP Prompt Builder",
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(BUILDER_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(CAMERA_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(CHARACTER_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
@@ -790,9 +586,6 @@ NODE_DISPLAY_NAME_MAPPINGS.update(FORMATTER_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(INSTA_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(ROUTE_CONFIG_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(PROFILE_FILTER_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update({
|
||||
"SxCPPromptBuilderFromConfigs": "SxCP Prompt Builder From Configs",
|
||||
})
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(LOOP_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
@@ -272,10 +272,10 @@ Improve later:
|
||||
|
||||
### Node / UI Path
|
||||
|
||||
Owner: `__init__.py`, `node_seed_resolution.py`, `node_camera.py`,
|
||||
`node_character.py`, `node_hardcore_position.py`, `node_formatter.py`,
|
||||
`node_insta.py`, `node_route_config.py`, `node_profile_filter.py`,
|
||||
`loop_nodes.py`, `web/*.js`.
|
||||
Owner: `__init__.py`, `node_builder.py`, `node_seed_resolution.py`,
|
||||
`node_camera.py`, `node_character.py`, `node_hardcore_position.py`,
|
||||
`node_formatter.py`, `node_insta.py`, `node_route_config.py`,
|
||||
`node_profile_filter.py`, `loop_nodes.py`, `web/*.js`.
|
||||
|
||||
Keep here:
|
||||
|
||||
@@ -283,6 +283,7 @@ Keep here:
|
||||
- widget behavior;
|
||||
- button actions;
|
||||
- dynamic input slots.
|
||||
- direct and config-driven builder node declarations in `node_builder.py`.
|
||||
- seed and resolution utility node declarations in `node_seed_resolution.py`.
|
||||
- camera utility node declarations in `node_camera.py`.
|
||||
- character pool, slot, and profile node declarations in `node_character.py`.
|
||||
@@ -296,6 +297,8 @@ Keep here:
|
||||
|
||||
Already isolated:
|
||||
|
||||
- direct and config-driven prompt builder nodes live in `node_builder.py`, with
|
||||
registration maps imported by `__init__.py`.
|
||||
- seed/global-seed/seed-locker and SDXL/Krea2 resolution utility nodes live in
|
||||
`node_seed_resolution.py`, with registration maps imported by `__init__.py`.
|
||||
- camera/orbit/Qwen translator utility nodes live in `node_camera.py`, with
|
||||
|
||||
@@ -25,10 +25,10 @@ When a result is wrong, first identify which layer owns the bad text:
|
||||
- Raw builder prompt acceptable, SDXL tags wrong: edit `sdxl_formatter.py`.
|
||||
- Natural caption/training caption wrong: edit `caption_naturalizer.py`.
|
||||
- UI/preview/loop behavior wrong: edit `__init__.py`, node family modules such
|
||||
as `node_seed_resolution.py`, `node_camera.py`, `node_character.py`,
|
||||
`node_hardcore_position.py`, `node_formatter.py`, `node_insta.py`,
|
||||
`node_route_config.py`, or `node_profile_filter.py`, `loop_nodes.py`, or
|
||||
`web/*.js`.
|
||||
as `node_builder.py`, `node_seed_resolution.py`, `node_camera.py`,
|
||||
`node_character.py`, `node_hardcore_position.py`, `node_formatter.py`,
|
||||
`node_insta.py`, `node_route_config.py`, or `node_profile_filter.py`,
|
||||
`loop_nodes.py`, or `web/*.js`.
|
||||
|
||||
## High-Level Routes
|
||||
|
||||
@@ -694,6 +694,7 @@ These do not own prompt pool wording, but they affect execution and review:
|
||||
| Index switch | `loop_nodes.py`, `web/index_switch_slots.js` | Multi-input to selected output, and selected input to multi-output routing. |
|
||||
| Accumulator | `loop_nodes.py`, `web/accumulator_preview.js` | Stores generated values/images during workflow execution and previews/reorders/deletes them. |
|
||||
| Persistent text preview | `loop_nodes.py`, `web/preview_any_text.js` | Stores any value as text and keeps it after workflow reload. |
|
||||
| Builder node wrappers | `node_builder.py`, imported by `__init__.py` | Direct prompt builder and config-driven prompt builder ComfyUI declarations. |
|
||||
| Seed and resolution utility nodes | `node_seed_resolution.py`, imported by `__init__.py` | Global/per-axis seed configs plus SDXL/Krea width/height helpers. |
|
||||
| Camera utility nodes | `node_camera.py`, imported by `__init__.py` | Direct camera config, orbit-to-camera config, and Qwen MultiAngle camera translation. |
|
||||
| Character utility nodes | `node_character.py`, imported by `__init__.py` | Hair, age/body/eyes/clothing pools, manual details, character slots, and profile save/load nodes. |
|
||||
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
try:
|
||||
from .prompt_builder import (
|
||||
build_prompt,
|
||||
build_prompt_from_configs,
|
||||
category_choices,
|
||||
ethnicity_choices,
|
||||
subcategory_choices,
|
||||
)
|
||||
except ImportError: # Allows local smoke tests from the repository root.
|
||||
from prompt_builder import (
|
||||
build_prompt,
|
||||
build_prompt_from_configs,
|
||||
category_choices,
|
||||
ethnicity_choices,
|
||||
subcategory_choices,
|
||||
)
|
||||
|
||||
|
||||
SXCP_ETHNICITY_LIST = "SXCP_ETHNICITY_LIST"
|
||||
SXCP_FILTER_CONFIG = "SXCP_FILTER_CONFIG"
|
||||
SXCP_SEED_CONFIG = "SXCP_SEED_CONFIG"
|
||||
SXCP_CAMERA_CONFIG = "SXCP_CAMERA_CONFIG"
|
||||
SXCP_LOCATION_CONFIG = "SXCP_LOCATION_CONFIG"
|
||||
SXCP_COMPOSITION_CONFIG = "SXCP_COMPOSITION_CONFIG"
|
||||
SXCP_CATEGORY_CONFIG = "SXCP_CATEGORY_CONFIG"
|
||||
SXCP_CAST_CONFIG = "SXCP_CAST_CONFIG"
|
||||
SXCP_GENERATION_PROFILE = "SXCP_GENERATION_PROFILE"
|
||||
SXCP_HARDCORE_POSITION_CONFIG = "SXCP_HARDCORE_POSITION_CONFIG"
|
||||
SXCP_CHARACTER_CAST = "SXCP_CHARACTER_CAST"
|
||||
SXCP_CHARACTER_PROFILE = "SXCP_CHARACTER_PROFILE"
|
||||
|
||||
|
||||
class SxCPPromptBuilder:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"category": (category_choices(), {"default": "auto_weighted"}),
|
||||
"subcategory": (subcategory_choices(), {"default": "random"}),
|
||||
"row_number": ("INT", {"default": 1, "min": 1, "max": 1000000, "step": 1}),
|
||||
"start_index": ("INT", {"default": 41, "min": 1, "max": 1000000, "step": 1}),
|
||||
"seed": ("INT", {"default": 20260614, "min": 0, "max": 0xFFFFFFFF, "step": 1}),
|
||||
"clothing": (["random", "full", "minimal"], {"default": "random"}),
|
||||
"ethnicity": (ethnicity_choices(), {"default": "any"}),
|
||||
"poses": (["random", "standard", "evocative"], {"default": "random"}),
|
||||
"expression_enabled": ("BOOLEAN", {"default": True}),
|
||||
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
||||
"backside_bias": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"figure": (["random", "curvy", "balanced", "bombshell"], {"default": "random"}),
|
||||
"women_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
||||
"men_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
||||
"minimal_clothing_ratio": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
||||
"standard_pose_ratio": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
||||
"trigger": ("STRING", {"default": "sxcpinup_coloredpencil"}),
|
||||
"prepend_trigger_to_prompt": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
"optional": {
|
||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||
"seed_config": (SXCP_SEED_CONFIG,),
|
||||
"camera_config": (SXCP_CAMERA_CONFIG,),
|
||||
"location_config": (SXCP_LOCATION_CONFIG,),
|
||||
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
||||
"character_profile": (SXCP_CHARACTER_PROFILE,),
|
||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
||||
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
||||
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
|
||||
RETURN_NAMES = ("prompt", "negative_prompt", "caption", "metadata_json", "category", "subcategory")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
category,
|
||||
subcategory,
|
||||
row_number,
|
||||
start_index,
|
||||
seed,
|
||||
clothing,
|
||||
ethnicity,
|
||||
poses,
|
||||
expression_enabled,
|
||||
expression_intensity,
|
||||
backside_bias,
|
||||
figure,
|
||||
women_count,
|
||||
men_count,
|
||||
minimal_clothing_ratio,
|
||||
standard_pose_ratio,
|
||||
trigger,
|
||||
prepend_trigger_to_prompt,
|
||||
seed_config="",
|
||||
camera_config="",
|
||||
location_config="",
|
||||
composition_config="",
|
||||
character_profile="",
|
||||
character_cast="",
|
||||
hardcore_position_config="",
|
||||
extra_positive="",
|
||||
extra_negative="",
|
||||
no_plus_women=False,
|
||||
no_black=False,
|
||||
ethnicity_list="",
|
||||
):
|
||||
row = build_prompt(
|
||||
category=category,
|
||||
subcategory=subcategory,
|
||||
row_number=row_number,
|
||||
start_index=start_index,
|
||||
seed=seed,
|
||||
clothing=clothing,
|
||||
ethnicity=ethnicity_list or ethnicity,
|
||||
poses=poses,
|
||||
expression_enabled=expression_enabled,
|
||||
expression_intensity=expression_intensity,
|
||||
backside_bias=backside_bias,
|
||||
figure=figure,
|
||||
no_plus_women=no_plus_women,
|
||||
no_black=no_black,
|
||||
women_count=women_count,
|
||||
men_count=men_count,
|
||||
minimal_clothing_ratio=minimal_clothing_ratio,
|
||||
standard_pose_ratio=standard_pose_ratio,
|
||||
trigger=trigger,
|
||||
prepend_trigger_to_prompt=prepend_trigger_to_prompt,
|
||||
extra_positive=extra_positive or "",
|
||||
extra_negative=extra_negative or "",
|
||||
seed_config=seed_config or "",
|
||||
camera_config=camera_config or "",
|
||||
location_config=location_config or "",
|
||||
composition_config=composition_config or "",
|
||||
character_profile=character_profile or "",
|
||||
character_cast=character_cast or "",
|
||||
hardcore_position_config=hardcore_position_config or "",
|
||||
)
|
||||
return (
|
||||
row["prompt"],
|
||||
row["negative_prompt"],
|
||||
row["caption"],
|
||||
json.dumps(row, ensure_ascii=True, sort_keys=True),
|
||||
row.get("main_category", category),
|
||||
row.get("subcategory", subcategory),
|
||||
)
|
||||
|
||||
|
||||
class SxCPPromptBuilderFromConfigs:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"row_number": ("INT", {"default": 1, "min": 1, "max": 1000000, "step": 1}),
|
||||
"start_index": ("INT", {"default": 41, "min": 1, "max": 1000000, "step": 1}),
|
||||
"seed": ("INT", {"default": 20260614, "min": 0, "max": 0xFFFFFFFF, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"category_config": (SXCP_CATEGORY_CONFIG,),
|
||||
"cast_config": (SXCP_CAST_CONFIG,),
|
||||
"generation_profile": (SXCP_GENERATION_PROFILE,),
|
||||
"filter_config": (SXCP_FILTER_CONFIG,),
|
||||
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
||||
"seed_config": (SXCP_SEED_CONFIG,),
|
||||
"camera_config": (SXCP_CAMERA_CONFIG,),
|
||||
"location_config": (SXCP_LOCATION_CONFIG,),
|
||||
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
||||
"character_profile": (SXCP_CHARACTER_PROFILE,),
|
||||
"character_cast": (SXCP_CHARACTER_CAST,),
|
||||
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
||||
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
||||
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
|
||||
RETURN_NAMES = ("prompt", "negative_prompt", "caption", "metadata_json", "category", "subcategory")
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "prompt_builder"
|
||||
|
||||
def build(
|
||||
self,
|
||||
row_number,
|
||||
start_index,
|
||||
seed,
|
||||
category_config="",
|
||||
cast_config="",
|
||||
generation_profile="",
|
||||
filter_config="",
|
||||
ethnicity_list="",
|
||||
seed_config="",
|
||||
camera_config="",
|
||||
location_config="",
|
||||
composition_config="",
|
||||
character_profile="",
|
||||
character_cast="",
|
||||
hardcore_position_config="",
|
||||
extra_positive="",
|
||||
extra_negative="",
|
||||
):
|
||||
row = build_prompt_from_configs(
|
||||
row_number=row_number,
|
||||
start_index=start_index,
|
||||
seed=seed,
|
||||
category_config=category_config or "",
|
||||
cast_config=cast_config or "",
|
||||
generation_profile=generation_profile or "",
|
||||
filter_config=ethnicity_list or filter_config or "",
|
||||
seed_config=seed_config or "",
|
||||
camera_config=camera_config or "",
|
||||
location_config=location_config or "",
|
||||
composition_config=composition_config or "",
|
||||
character_profile=character_profile or "",
|
||||
character_cast=character_cast or "",
|
||||
hardcore_position_config=hardcore_position_config or "",
|
||||
extra_positive=extra_positive or "",
|
||||
extra_negative=extra_negative or "",
|
||||
)
|
||||
return (
|
||||
row["prompt"],
|
||||
row["negative_prompt"],
|
||||
row["caption"],
|
||||
json.dumps(row, ensure_ascii=True, sort_keys=True),
|
||||
row.get("main_category", ""),
|
||||
row.get("subcategory", ""),
|
||||
)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SxCPPromptBuilder": SxCPPromptBuilder,
|
||||
"SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SxCPPromptBuilder": "SxCP Prompt Builder",
|
||||
"SxCPPromptBuilderFromConfigs": "SxCP Prompt Builder From Configs",
|
||||
}
|
||||
+39
-10
@@ -48,10 +48,12 @@ def _assignment_dict(path: Path, name: str) -> dict[str, Any]:
|
||||
|
||||
def _class_return_names(path: Path) -> dict[str, tuple[str, ...]]:
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||
result: dict[str, tuple[str, ...]] = {}
|
||||
classes: dict[str, tuple[list[str], tuple[str, ...]]] = {}
|
||||
for node in tree.body:
|
||||
if not isinstance(node, ast.ClassDef) or not node.name.startswith("SxCP"):
|
||||
if not isinstance(node, ast.ClassDef):
|
||||
continue
|
||||
bases = [base.id for base in node.bases if isinstance(base, ast.Name)]
|
||||
return_names: tuple[str, ...] = ()
|
||||
for item in node.body:
|
||||
if not isinstance(item, ast.Assign):
|
||||
continue
|
||||
@@ -59,7 +61,29 @@ def _class_return_names(path: Path) -> dict[str, tuple[str, ...]]:
|
||||
continue
|
||||
value = _literal_or_none(item.value)
|
||||
if isinstance(value, tuple) and all(isinstance(part, str) for part in value):
|
||||
result[node.name] = value
|
||||
return_names = value
|
||||
classes[node.name] = (bases, return_names)
|
||||
|
||||
def resolve(class_name: str, seen: set[str] | None = None) -> tuple[str, ...]:
|
||||
seen = seen or set()
|
||||
if class_name in seen:
|
||||
return ()
|
||||
seen.add(class_name)
|
||||
bases, return_names = classes.get(class_name, ([], ()))
|
||||
if return_names:
|
||||
return return_names
|
||||
for base_name in bases:
|
||||
inherited = resolve(base_name, seen)
|
||||
if inherited:
|
||||
return inherited
|
||||
return ()
|
||||
|
||||
result: dict[str, tuple[str, ...]] = {}
|
||||
for class_name in classes:
|
||||
if class_name.startswith("SxCP"):
|
||||
return_names = resolve(class_name)
|
||||
if return_names:
|
||||
result[class_name] = return_names
|
||||
return result
|
||||
|
||||
|
||||
@@ -97,6 +121,12 @@ def _category_json_paths() -> list[Path]:
|
||||
return sorted((ROOT / "categories").glob("*.json"))
|
||||
|
||||
|
||||
def _node_python_paths() -> list[Path]:
|
||||
paths = [ROOT / "__init__.py", ROOT / "loop_nodes.py"]
|
||||
paths.extend(sorted(ROOT.glob("node_*.py")))
|
||||
return [path for path in paths if path.exists()]
|
||||
|
||||
|
||||
def _load_category_json(path: Path) -> dict[str, Any]:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
return data if isinstance(data, dict) else {}
|
||||
@@ -246,14 +276,13 @@ def print_table(headers: tuple[str, ...], rows: list[tuple[Any, ...]]) -> None:
|
||||
|
||||
|
||||
def main() -> int:
|
||||
init_path = ROOT / "__init__.py"
|
||||
loop_path = ROOT / "loop_nodes.py"
|
||||
category_paths = _category_json_paths()
|
||||
display = _assignment_dict(init_path, "NODE_DISPLAY_NAME_MAPPINGS")
|
||||
loop_display = _assignment_dict(loop_path, "LOOP_NODE_DISPLAY_NAME_MAPPINGS")
|
||||
display.update(loop_display)
|
||||
returns = _class_return_names(init_path)
|
||||
returns.update(_class_return_names(loop_path))
|
||||
display: dict[str, Any] = {}
|
||||
returns: dict[str, tuple[str, ...]] = {}
|
||||
for path in _node_python_paths():
|
||||
display.update(_assignment_dict(path, "NODE_DISPLAY_NAME_MAPPINGS"))
|
||||
display.update(_assignment_dict(path, "LOOP_NODE_DISPLAY_NAME_MAPPINGS"))
|
||||
returns.update(_class_return_names(path))
|
||||
|
||||
print("# Node Display Map")
|
||||
node_rows = []
|
||||
|
||||
@@ -2169,6 +2169,63 @@ def smoke_node_insta_registration() -> None:
|
||||
_expect(pair.get("options", {}).get("hardcore_cast") == "couple", "Insta/OF Prompt Pair lost options metadata")
|
||||
|
||||
|
||||
def smoke_node_builder_registration() -> None:
|
||||
required_nodes = [
|
||||
"SxCPPromptBuilder",
|
||||
"SxCPPromptBuilderFromConfigs",
|
||||
]
|
||||
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")
|
||||
|
||||
builder_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPPromptBuilder"]
|
||||
builder_inputs = builder_node.INPUT_TYPES().get("required") or {}
|
||||
_expect("category" in builder_inputs, "Prompt Builder lost category input")
|
||||
_expect("tooltip" in builder_inputs["category"][1], "Prompt Builder tooltip injection missing")
|
||||
direct_output = builder_node().build(
|
||||
"woman",
|
||||
"random",
|
||||
1,
|
||||
41,
|
||||
123,
|
||||
"full",
|
||||
"any",
|
||||
"standard",
|
||||
True,
|
||||
0.5,
|
||||
0.0,
|
||||
"random",
|
||||
1,
|
||||
0,
|
||||
-1,
|
||||
-1,
|
||||
Trigger,
|
||||
True,
|
||||
)
|
||||
direct_row = json.loads(direct_output[3])
|
||||
_expect_row_base(direct_row, "node_builder.direct_row")
|
||||
_expect(direct_output[0] == direct_row.get("prompt"), "Prompt Builder prompt output drifted from metadata")
|
||||
_expect(direct_output[4] == direct_row.get("main_category"), "Prompt Builder category output drifted from metadata")
|
||||
_expect_trigger_once("node_builder.direct_prompt", direct_output[0], Trigger)
|
||||
|
||||
config_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPPromptBuilderFromConfigs"]
|
||||
config_inputs = config_node.INPUT_TYPES()
|
||||
_expect("category_config" in (config_inputs.get("optional") or {}), "Prompt Builder From Configs lost category_config input")
|
||||
config_output = config_node().build(
|
||||
1,
|
||||
41,
|
||||
123,
|
||||
category_config=pb.build_category_config_json("woman", "random"),
|
||||
cast_config=pb.build_cast_config_json("solo_woman", 1, 0),
|
||||
generation_profile=pb.build_generation_profile_json(profile="balanced"),
|
||||
)
|
||||
config_row = json.loads(config_output[3])
|
||||
_expect_row_base(config_row, "node_builder.config_row")
|
||||
_expect(config_output[0] == config_row.get("prompt"), "Prompt Builder From Configs prompt output drifted from metadata")
|
||||
_expect(config_output[4] == config_row.get("main_category"), "Prompt Builder From Configs category output drifted from metadata")
|
||||
_expect_text("node_builder.config_caption", config_output[2], 20)
|
||||
|
||||
|
||||
def smoke_node_profile_filter_registration() -> None:
|
||||
required_nodes = [
|
||||
"SxCPGenerationProfile",
|
||||
@@ -2280,6 +2337,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
||||
("node_hardcore_position_registration", smoke_node_hardcore_position_registration),
|
||||
("node_formatter_registration", smoke_node_formatter_registration),
|
||||
("node_insta_registration", smoke_node_insta_registration),
|
||||
("node_builder_registration", smoke_node_builder_registration),
|
||||
("node_profile_filter_registration", smoke_node_profile_filter_registration),
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user