Extract hardcore position nodes
This commit is contained in:
+10
-116
@@ -408,6 +408,10 @@ try:
|
|||||||
NODE_CLASS_MAPPINGS as CHARACTER_NODE_CLASS_MAPPINGS,
|
NODE_CLASS_MAPPINGS as CHARACTER_NODE_CLASS_MAPPINGS,
|
||||||
NODE_DISPLAY_NAME_MAPPINGS as CHARACTER_NODE_DISPLAY_NAME_MAPPINGS,
|
NODE_DISPLAY_NAME_MAPPINGS as CHARACTER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
)
|
)
|
||||||
|
from .node_hardcore_position import (
|
||||||
|
NODE_CLASS_MAPPINGS as HARDCORE_POSITION_NODE_CLASS_MAPPINGS,
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS as HARDCORE_POSITION_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
|
)
|
||||||
from .node_profile_filter import (
|
from .node_profile_filter import (
|
||||||
NODE_CLASS_MAPPINGS as PROFILE_FILTER_NODE_CLASS_MAPPINGS,
|
NODE_CLASS_MAPPINGS as PROFILE_FILTER_NODE_CLASS_MAPPINGS,
|
||||||
NODE_DISPLAY_NAME_MAPPINGS as PROFILE_FILTER_NODE_DISPLAY_NAME_MAPPINGS,
|
NODE_DISPLAY_NAME_MAPPINGS as PROFILE_FILTER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
@@ -421,8 +425,6 @@ try:
|
|||||||
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
)
|
)
|
||||||
from .prompt_builder import (
|
from .prompt_builder import (
|
||||||
build_hardcore_action_filter_json,
|
|
||||||
build_hardcore_position_pool_json,
|
|
||||||
build_insta_of_options_json,
|
build_insta_of_options_json,
|
||||||
build_insta_of_pair,
|
build_insta_of_pair,
|
||||||
build_prompt,
|
build_prompt,
|
||||||
@@ -431,9 +433,6 @@ try:
|
|||||||
camera_mode_choices,
|
camera_mode_choices,
|
||||||
category_choices,
|
category_choices,
|
||||||
ethnicity_choices,
|
ethnicity_choices,
|
||||||
hardcore_position_family_choices,
|
|
||||||
hardcore_position_focus_choices,
|
|
||||||
hardcore_position_key_choices,
|
|
||||||
hardcore_detail_density_choices,
|
hardcore_detail_density_choices,
|
||||||
save_character_profile_payload,
|
save_character_profile_payload,
|
||||||
subcategory_choices,
|
subcategory_choices,
|
||||||
@@ -458,6 +457,10 @@ except ImportError:
|
|||||||
NODE_CLASS_MAPPINGS as CHARACTER_NODE_CLASS_MAPPINGS,
|
NODE_CLASS_MAPPINGS as CHARACTER_NODE_CLASS_MAPPINGS,
|
||||||
NODE_DISPLAY_NAME_MAPPINGS as CHARACTER_NODE_DISPLAY_NAME_MAPPINGS,
|
NODE_DISPLAY_NAME_MAPPINGS as CHARACTER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
)
|
)
|
||||||
|
from node_hardcore_position import (
|
||||||
|
NODE_CLASS_MAPPINGS as HARDCORE_POSITION_NODE_CLASS_MAPPINGS,
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS as HARDCORE_POSITION_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
|
)
|
||||||
from node_profile_filter import (
|
from node_profile_filter import (
|
||||||
NODE_CLASS_MAPPINGS as PROFILE_FILTER_NODE_CLASS_MAPPINGS,
|
NODE_CLASS_MAPPINGS as PROFILE_FILTER_NODE_CLASS_MAPPINGS,
|
||||||
NODE_DISPLAY_NAME_MAPPINGS as PROFILE_FILTER_NODE_DISPLAY_NAME_MAPPINGS,
|
NODE_DISPLAY_NAME_MAPPINGS as PROFILE_FILTER_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
@@ -471,8 +474,6 @@ except ImportError:
|
|||||||
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
)
|
)
|
||||||
from prompt_builder import (
|
from prompt_builder import (
|
||||||
build_hardcore_action_filter_json,
|
|
||||||
build_hardcore_position_pool_json,
|
|
||||||
build_insta_of_options_json,
|
build_insta_of_options_json,
|
||||||
build_insta_of_pair,
|
build_insta_of_pair,
|
||||||
build_prompt,
|
build_prompt,
|
||||||
@@ -481,9 +482,6 @@ except ImportError:
|
|||||||
camera_mode_choices,
|
camera_mode_choices,
|
||||||
category_choices,
|
category_choices,
|
||||||
ethnicity_choices,
|
ethnicity_choices,
|
||||||
hardcore_position_family_choices,
|
|
||||||
hardcore_position_focus_choices,
|
|
||||||
hardcore_position_key_choices,
|
|
||||||
hardcore_detail_density_choices,
|
hardcore_detail_density_choices,
|
||||||
save_character_profile_payload,
|
save_character_profile_payload,
|
||||||
subcategory_choices,
|
subcategory_choices,
|
||||||
@@ -684,108 +682,6 @@ class SxCPPromptBuilder:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _choice_input_key(prefix, choice):
|
|
||||||
key = "".join(char if char.isalnum() else "_" for char in str(choice).lower()).strip("_")
|
|
||||||
while "__" in key:
|
|
||||||
key = key.replace("__", "_")
|
|
||||||
return f"{prefix}_{key}"
|
|
||||||
|
|
||||||
|
|
||||||
class SxCPHardcorePositionPool:
|
|
||||||
@classmethod
|
|
||||||
def INPUT_TYPES(cls):
|
|
||||||
required = {
|
|
||||||
"combine_mode": (["replace", "add"], {"default": "replace"}),
|
|
||||||
"family": (hardcore_position_family_choices(), {"default": "any"}),
|
|
||||||
}
|
|
||||||
for choice in hardcore_position_key_choices():
|
|
||||||
required[_choice_input_key("include", choice)] = ("BOOLEAN", {"default": False})
|
|
||||||
return {
|
|
||||||
"required": required,
|
|
||||||
"optional": {
|
|
||||||
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
RETURN_TYPES = (SXCP_HARDCORE_POSITION_CONFIG, "STRING")
|
|
||||||
RETURN_NAMES = ("hardcore_position_config", "summary")
|
|
||||||
FUNCTION = "build"
|
|
||||||
CATEGORY = "prompt_builder"
|
|
||||||
|
|
||||||
def build(self, combine_mode="replace", family="any", hardcore_position_config="", **kwargs):
|
|
||||||
selected = [
|
|
||||||
choice
|
|
||||||
for choice in hardcore_position_key_choices()
|
|
||||||
if bool(kwargs.get(_choice_input_key("include", choice), False))
|
|
||||||
]
|
|
||||||
config = build_hardcore_position_pool_json(
|
|
||||||
hardcore_position_config=hardcore_position_config or "",
|
|
||||||
combine_mode=combine_mode,
|
|
||||||
family=family,
|
|
||||||
selected_positions=selected,
|
|
||||||
)
|
|
||||||
return config, json.loads(config).get("summary", "")
|
|
||||||
|
|
||||||
|
|
||||||
class SxCPHardcoreActionFilter:
|
|
||||||
@classmethod
|
|
||||||
def INPUT_TYPES(cls):
|
|
||||||
return {
|
|
||||||
"required": {
|
|
||||||
"focus": (hardcore_position_focus_choices(), {"default": "keep_pool"}),
|
|
||||||
"allow_toys": ("BOOLEAN", {"default": False}),
|
|
||||||
"allow_double": ("BOOLEAN", {"default": False}),
|
|
||||||
"allow_penetration": ("BOOLEAN", {"default": True}),
|
|
||||||
"allow_foreplay": ("BOOLEAN", {"default": True}),
|
|
||||||
"allow_interaction": ("BOOLEAN", {"default": True}),
|
|
||||||
"allow_manual": ("BOOLEAN", {"default": True}),
|
|
||||||
"allow_oral": ("BOOLEAN", {"default": True}),
|
|
||||||
"allow_outercourse": ("BOOLEAN", {"default": True}),
|
|
||||||
"allow_anal": ("BOOLEAN", {"default": True}),
|
|
||||||
"allow_climax": ("BOOLEAN", {"default": True}),
|
|
||||||
},
|
|
||||||
"optional": {
|
|
||||||
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
RETURN_TYPES = (SXCP_HARDCORE_POSITION_CONFIG, "STRING")
|
|
||||||
RETURN_NAMES = ("hardcore_position_config", "summary")
|
|
||||||
FUNCTION = "build"
|
|
||||||
CATEGORY = "prompt_builder"
|
|
||||||
|
|
||||||
def build(
|
|
||||||
self,
|
|
||||||
focus,
|
|
||||||
allow_toys,
|
|
||||||
allow_double,
|
|
||||||
allow_penetration,
|
|
||||||
allow_foreplay,
|
|
||||||
allow_interaction,
|
|
||||||
allow_manual,
|
|
||||||
allow_oral,
|
|
||||||
allow_outercourse,
|
|
||||||
allow_anal,
|
|
||||||
allow_climax,
|
|
||||||
hardcore_position_config="",
|
|
||||||
):
|
|
||||||
config = build_hardcore_action_filter_json(
|
|
||||||
hardcore_position_config=hardcore_position_config or "",
|
|
||||||
focus=focus,
|
|
||||||
allow_toys=allow_toys,
|
|
||||||
allow_double=allow_double,
|
|
||||||
allow_penetration=allow_penetration,
|
|
||||||
allow_foreplay=allow_foreplay,
|
|
||||||
allow_interaction=allow_interaction,
|
|
||||||
allow_manual=allow_manual,
|
|
||||||
allow_oral=allow_oral,
|
|
||||||
allow_outercourse=allow_outercourse,
|
|
||||||
allow_anal=allow_anal,
|
|
||||||
allow_climax=allow_climax,
|
|
||||||
)
|
|
||||||
return config, json.loads(config).get("summary", "")
|
|
||||||
|
|
||||||
|
|
||||||
class SxCPPromptBuilderFromConfigs:
|
class SxCPPromptBuilderFromConfigs:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
@@ -1254,11 +1150,10 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
NODE_CLASS_MAPPINGS.update(SEED_RESOLUTION_NODE_CLASS_MAPPINGS)
|
NODE_CLASS_MAPPINGS.update(SEED_RESOLUTION_NODE_CLASS_MAPPINGS)
|
||||||
NODE_CLASS_MAPPINGS.update(CAMERA_NODE_CLASS_MAPPINGS)
|
NODE_CLASS_MAPPINGS.update(CAMERA_NODE_CLASS_MAPPINGS)
|
||||||
NODE_CLASS_MAPPINGS.update(CHARACTER_NODE_CLASS_MAPPINGS)
|
NODE_CLASS_MAPPINGS.update(CHARACTER_NODE_CLASS_MAPPINGS)
|
||||||
|
NODE_CLASS_MAPPINGS.update(HARDCORE_POSITION_NODE_CLASS_MAPPINGS)
|
||||||
NODE_CLASS_MAPPINGS.update(ROUTE_CONFIG_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(PROFILE_FILTER_NODE_CLASS_MAPPINGS)
|
||||||
NODE_CLASS_MAPPINGS.update({
|
NODE_CLASS_MAPPINGS.update({
|
||||||
"SxCPHardcorePositionPool": SxCPHardcorePositionPool,
|
|
||||||
"SxCPHardcoreActionFilter": SxCPHardcoreActionFilter,
|
|
||||||
"SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs,
|
"SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs,
|
||||||
"SxCPCaptionNaturalizer": SxCPCaptionNaturalizer,
|
"SxCPCaptionNaturalizer": SxCPCaptionNaturalizer,
|
||||||
"SxCPKrea2Formatter": SxCPKrea2Formatter,
|
"SxCPKrea2Formatter": SxCPKrea2Formatter,
|
||||||
@@ -1275,11 +1170,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
|||||||
NODE_DISPLAY_NAME_MAPPINGS.update(SEED_RESOLUTION_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(CAMERA_NODE_DISPLAY_NAME_MAPPINGS)
|
||||||
NODE_DISPLAY_NAME_MAPPINGS.update(CHARACTER_NODE_DISPLAY_NAME_MAPPINGS)
|
NODE_DISPLAY_NAME_MAPPINGS.update(CHARACTER_NODE_DISPLAY_NAME_MAPPINGS)
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS.update(HARDCORE_POSITION_NODE_DISPLAY_NAME_MAPPINGS)
|
||||||
NODE_DISPLAY_NAME_MAPPINGS.update(ROUTE_CONFIG_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(PROFILE_FILTER_NODE_DISPLAY_NAME_MAPPINGS)
|
||||||
NODE_DISPLAY_NAME_MAPPINGS.update({
|
NODE_DISPLAY_NAME_MAPPINGS.update({
|
||||||
"SxCPHardcorePositionPool": "SxCP Hardcore Position Pool",
|
|
||||||
"SxCPHardcoreActionFilter": "SxCP Hardcore Action Filter",
|
|
||||||
"SxCPPromptBuilderFromConfigs": "SxCP Prompt Builder From Configs",
|
"SxCPPromptBuilderFromConfigs": "SxCP Prompt Builder From Configs",
|
||||||
"SxCPCaptionNaturalizer": "SxCP Caption Naturalizer",
|
"SxCPCaptionNaturalizer": "SxCP Caption Naturalizer",
|
||||||
"SxCPKrea2Formatter": "SxCP Krea2 Formatter",
|
"SxCPKrea2Formatter": "SxCP Krea2 Formatter",
|
||||||
|
|||||||
@@ -273,8 +273,8 @@ Improve later:
|
|||||||
### Node / UI Path
|
### Node / UI Path
|
||||||
|
|
||||||
Owner: `__init__.py`, `node_seed_resolution.py`, `node_camera.py`,
|
Owner: `__init__.py`, `node_seed_resolution.py`, `node_camera.py`,
|
||||||
`node_character.py`, `node_route_config.py`, `node_profile_filter.py`,
|
`node_character.py`, `node_hardcore_position.py`, `node_route_config.py`,
|
||||||
`loop_nodes.py`, `web/*.js`.
|
`node_profile_filter.py`, `loop_nodes.py`, `web/*.js`.
|
||||||
|
|
||||||
Keep here:
|
Keep here:
|
||||||
|
|
||||||
@@ -285,6 +285,8 @@ Keep here:
|
|||||||
- seed and resolution utility node declarations in `node_seed_resolution.py`.
|
- seed and resolution utility node declarations in `node_seed_resolution.py`.
|
||||||
- camera utility node declarations in `node_camera.py`.
|
- camera utility node declarations in `node_camera.py`.
|
||||||
- character pool, slot, and profile node declarations in `node_character.py`.
|
- character pool, slot, and profile node declarations in `node_character.py`.
|
||||||
|
- hardcore position pool/filter node declarations in
|
||||||
|
`node_hardcore_position.py`.
|
||||||
- route/category/location/composition/cast config node declarations in
|
- route/category/location/composition/cast config node declarations in
|
||||||
`node_route_config.py`.
|
`node_route_config.py`.
|
||||||
- profile/filter/ethnicity-list node declarations in `node_profile_filter.py`.
|
- profile/filter/ethnicity-list node declarations in `node_profile_filter.py`.
|
||||||
@@ -298,6 +300,9 @@ Already isolated:
|
|||||||
- hair, age/body/eyes/clothing pools, manual character details, character
|
- hair, age/body/eyes/clothing pools, manual character details, character
|
||||||
slots, and profile save/load nodes live in `node_character.py`, with
|
slots, and profile save/load nodes live in `node_character.py`, with
|
||||||
registration maps imported by `__init__.py`.
|
registration maps imported by `__init__.py`.
|
||||||
|
- hardcore position pool and action filter nodes live in
|
||||||
|
`node_hardcore_position.py`, with registration maps imported by
|
||||||
|
`__init__.py`.
|
||||||
- category preset, location/composition pool, location theme, and cast config
|
- category preset, location/composition pool, location theme, and cast config
|
||||||
utility nodes live in `node_route_config.py`, with registration maps imported
|
utility nodes live in `node_route_config.py`, with registration maps imported
|
||||||
by `__init__.py`.
|
by `__init__.py`.
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ When a result is wrong, first identify which layer owns the bad text:
|
|||||||
- Natural caption/training caption wrong: edit `caption_naturalizer.py`.
|
- Natural caption/training caption wrong: edit `caption_naturalizer.py`.
|
||||||
- UI/preview/loop behavior wrong: edit `__init__.py`, node family modules such
|
- UI/preview/loop behavior wrong: edit `__init__.py`, node family modules such
|
||||||
as `node_seed_resolution.py`, `node_camera.py`, `node_character.py`,
|
as `node_seed_resolution.py`, `node_camera.py`, `node_character.py`,
|
||||||
`node_route_config.py`, or `node_profile_filter.py`, `loop_nodes.py`, or
|
`node_hardcore_position.py`, `node_route_config.py`, or
|
||||||
`web/*.js`.
|
`node_profile_filter.py`, `loop_nodes.py`, or `web/*.js`.
|
||||||
|
|
||||||
## High-Level Routes
|
## High-Level Routes
|
||||||
|
|
||||||
@@ -696,6 +696,7 @@ These do not own prompt pool wording, but they affect execution and review:
|
|||||||
| Seed and resolution utility nodes | `node_seed_resolution.py`, imported by `__init__.py` | Global/per-axis seed configs plus SDXL/Krea width/height helpers. |
|
| 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. |
|
| 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. |
|
| 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. |
|
||||||
|
| Hardcore position utility nodes | `node_hardcore_position.py`, imported by `__init__.py` | Position-family pool and action/filter gates for hardcore routes. |
|
||||||
| Route config utility nodes | `node_route_config.py`, imported by `__init__.py` | Category preset, location/composition pool, location theme, and cast config helpers. |
|
| Route config utility nodes | `node_route_config.py`, imported by `__init__.py` | Category preset, location/composition pool, location theme, and cast config helpers. |
|
||||||
| Profile/filter utility nodes | `node_profile_filter.py`, imported by `__init__.py` | Generation profile, advanced filter config, and ethnicity list helpers. |
|
| Profile/filter utility nodes | `node_profile_filter.py`, imported by `__init__.py` | Generation profile, advanced filter config, and ethnicity list helpers. |
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .prompt_builder import (
|
||||||
|
build_hardcore_action_filter_json,
|
||||||
|
build_hardcore_position_pool_json,
|
||||||
|
hardcore_position_family_choices,
|
||||||
|
hardcore_position_focus_choices,
|
||||||
|
hardcore_position_key_choices,
|
||||||
|
)
|
||||||
|
except ImportError: # Allows local smoke tests from the repository root.
|
||||||
|
from prompt_builder import (
|
||||||
|
build_hardcore_action_filter_json,
|
||||||
|
build_hardcore_position_pool_json,
|
||||||
|
hardcore_position_family_choices,
|
||||||
|
hardcore_position_focus_choices,
|
||||||
|
hardcore_position_key_choices,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SXCP_HARDCORE_POSITION_CONFIG = "SXCP_HARDCORE_POSITION_CONFIG"
|
||||||
|
|
||||||
|
|
||||||
|
def _choice_input_key(prefix, choice):
|
||||||
|
key = "".join(char if char.isalnum() else "_" for char in str(choice).lower()).strip("_")
|
||||||
|
while "__" in key:
|
||||||
|
key = key.replace("__", "_")
|
||||||
|
return f"{prefix}_{key}"
|
||||||
|
|
||||||
|
|
||||||
|
class SxCPHardcorePositionPool:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
required = {
|
||||||
|
"combine_mode": (["replace", "add"], {"default": "replace"}),
|
||||||
|
"family": (hardcore_position_family_choices(), {"default": "any"}),
|
||||||
|
}
|
||||||
|
for choice in hardcore_position_key_choices():
|
||||||
|
required[_choice_input_key("include", choice)] = ("BOOLEAN", {"default": False})
|
||||||
|
return {
|
||||||
|
"required": required,
|
||||||
|
"optional": {
|
||||||
|
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = (SXCP_HARDCORE_POSITION_CONFIG, "STRING")
|
||||||
|
RETURN_NAMES = ("hardcore_position_config", "summary")
|
||||||
|
FUNCTION = "build"
|
||||||
|
CATEGORY = "prompt_builder"
|
||||||
|
|
||||||
|
def build(self, combine_mode="replace", family="any", hardcore_position_config="", **kwargs):
|
||||||
|
selected = [
|
||||||
|
choice
|
||||||
|
for choice in hardcore_position_key_choices()
|
||||||
|
if bool(kwargs.get(_choice_input_key("include", choice), False))
|
||||||
|
]
|
||||||
|
config = build_hardcore_position_pool_json(
|
||||||
|
hardcore_position_config=hardcore_position_config or "",
|
||||||
|
combine_mode=combine_mode,
|
||||||
|
family=family,
|
||||||
|
selected_positions=selected,
|
||||||
|
)
|
||||||
|
return config, json.loads(config).get("summary", "")
|
||||||
|
|
||||||
|
|
||||||
|
class SxCPHardcoreActionFilter:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"focus": (hardcore_position_focus_choices(), {"default": "keep_pool"}),
|
||||||
|
"allow_toys": ("BOOLEAN", {"default": False}),
|
||||||
|
"allow_double": ("BOOLEAN", {"default": False}),
|
||||||
|
"allow_penetration": ("BOOLEAN", {"default": True}),
|
||||||
|
"allow_foreplay": ("BOOLEAN", {"default": True}),
|
||||||
|
"allow_interaction": ("BOOLEAN", {"default": True}),
|
||||||
|
"allow_manual": ("BOOLEAN", {"default": True}),
|
||||||
|
"allow_oral": ("BOOLEAN", {"default": True}),
|
||||||
|
"allow_outercourse": ("BOOLEAN", {"default": True}),
|
||||||
|
"allow_anal": ("BOOLEAN", {"default": True}),
|
||||||
|
"allow_climax": ("BOOLEAN", {"default": True}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = (SXCP_HARDCORE_POSITION_CONFIG, "STRING")
|
||||||
|
RETURN_NAMES = ("hardcore_position_config", "summary")
|
||||||
|
FUNCTION = "build"
|
||||||
|
CATEGORY = "prompt_builder"
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
focus,
|
||||||
|
allow_toys,
|
||||||
|
allow_double,
|
||||||
|
allow_penetration,
|
||||||
|
allow_foreplay,
|
||||||
|
allow_interaction,
|
||||||
|
allow_manual,
|
||||||
|
allow_oral,
|
||||||
|
allow_outercourse,
|
||||||
|
allow_anal,
|
||||||
|
allow_climax,
|
||||||
|
hardcore_position_config="",
|
||||||
|
):
|
||||||
|
config = build_hardcore_action_filter_json(
|
||||||
|
hardcore_position_config=hardcore_position_config or "",
|
||||||
|
focus=focus,
|
||||||
|
allow_toys=allow_toys,
|
||||||
|
allow_double=allow_double,
|
||||||
|
allow_penetration=allow_penetration,
|
||||||
|
allow_foreplay=allow_foreplay,
|
||||||
|
allow_interaction=allow_interaction,
|
||||||
|
allow_manual=allow_manual,
|
||||||
|
allow_oral=allow_oral,
|
||||||
|
allow_outercourse=allow_outercourse,
|
||||||
|
allow_anal=allow_anal,
|
||||||
|
allow_climax=allow_climax,
|
||||||
|
)
|
||||||
|
return config, json.loads(config).get("summary", "")
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"SxCPHardcorePositionPool": SxCPHardcorePositionPool,
|
||||||
|
"SxCPHardcoreActionFilter": SxCPHardcoreActionFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||||
|
"SxCPHardcorePositionPool": "SxCP Hardcore Position Pool",
|
||||||
|
"SxCPHardcoreActionFilter": "SxCP Hardcore Action Filter",
|
||||||
|
}
|
||||||
@@ -2012,6 +2012,53 @@ def smoke_node_character_registration() -> None:
|
|||||||
_expect(json.loads(loaded_profile[0]).get("profile_type") == "character", "Profile Load returned wrong profile type")
|
_expect(json.loads(loaded_profile[0]).get("profile_type") == "character", "Profile Load returned wrong profile type")
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_node_hardcore_position_registration() -> None:
|
||||||
|
required_nodes = [
|
||||||
|
"SxCPHardcorePositionPool",
|
||||||
|
"SxCPHardcoreActionFilter",
|
||||||
|
]
|
||||||
|
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")
|
||||||
|
|
||||||
|
position_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPHardcorePositionPool"]
|
||||||
|
position_inputs = position_node.INPUT_TYPES().get("required") or {}
|
||||||
|
_expect("family" in position_inputs, "Hardcore Position Pool lost family input")
|
||||||
|
_expect("tooltip" in position_inputs["family"][1], "Hardcore Position Pool tooltip injection missing")
|
||||||
|
pool_config, pool_summary = position_node().build(
|
||||||
|
"replace",
|
||||||
|
"oral",
|
||||||
|
"",
|
||||||
|
include_boobjob=True,
|
||||||
|
include_handjob=True,
|
||||||
|
)
|
||||||
|
parsed_pool = json.loads(pool_config)
|
||||||
|
_expect(parsed_pool.get("family") == "oral", "Hardcore Position Pool lost selected family")
|
||||||
|
_expect(parsed_pool.get("positions") == ["boobjob", "handjob"], "Hardcore Position Pool lost selected positions")
|
||||||
|
_expect("positions=boobjob,handjob" in pool_summary, "Hardcore Position Pool summary changed unexpectedly")
|
||||||
|
|
||||||
|
filter_config, filter_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPHardcoreActionFilter"]().build(
|
||||||
|
"outercourse_only",
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
True,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
pool_config,
|
||||||
|
)
|
||||||
|
parsed_filter = json.loads(filter_config)
|
||||||
|
_expect(parsed_filter.get("family") == "outercourse", "Hardcore Action Filter did not apply focus family")
|
||||||
|
_expect(parsed_filter.get("positions") == ["boobjob", "handjob"], "Hardcore Action Filter lost incoming positions")
|
||||||
|
_expect(parsed_filter.get("allow_penetration") is False, "Hardcore Action Filter did not block penetration")
|
||||||
|
_expect(parsed_filter.get("allow_outercourse") is True, "Hardcore Action Filter should allow outercourse")
|
||||||
|
_expect("blocked=" in filter_summary, "Hardcore Action Filter summary lost blocked-gate details")
|
||||||
|
|
||||||
|
|
||||||
def smoke_node_profile_filter_registration() -> None:
|
def smoke_node_profile_filter_registration() -> None:
|
||||||
required_nodes = [
|
required_nodes = [
|
||||||
"SxCPGenerationProfile",
|
"SxCPGenerationProfile",
|
||||||
@@ -2120,6 +2167,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("node_camera_registration", smoke_node_camera_registration),
|
("node_camera_registration", smoke_node_camera_registration),
|
||||||
("node_route_config_registration", smoke_node_route_config_registration),
|
("node_route_config_registration", smoke_node_route_config_registration),
|
||||||
("node_character_registration", smoke_node_character_registration),
|
("node_character_registration", smoke_node_character_registration),
|
||||||
|
("node_hardcore_position_registration", smoke_node_hardcore_position_registration),
|
||||||
("node_profile_filter_registration", smoke_node_profile_filter_registration),
|
("node_profile_filter_registration", smoke_node_profile_filter_registration),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user