3250 lines
137 KiB
Python
3250 lines
137 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import math
|
|
import random
|
|
import re
|
|
|
|
try:
|
|
from aiohttp import web
|
|
from server import PromptServer
|
|
except Exception:
|
|
web = None
|
|
PromptServer = None
|
|
|
|
SXCP_HAIR_CONFIG = "SXCP_HAIR_CONFIG"
|
|
SXCP_CHARACTERISTICS = "SXCP_CHARACTERISTICS"
|
|
SXCP_CHARACTER_MANUAL = "SXCP_CHARACTER_MANUAL"
|
|
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_INSTA_OF_OPTIONS = "SXCP_INSTA_OF_OPTIONS"
|
|
SXCP_HARDCORE_POSITION_CONFIG = "SXCP_HARDCORE_POSITION_CONFIG"
|
|
SXCP_CHARACTER_CAST = "SXCP_CHARACTER_CAST"
|
|
SXCP_CHARACTER_SLOT = "SXCP_CHARACTER_SLOT"
|
|
SXCP_CHARACTER_PROFILE = "SXCP_CHARACTER_PROFILE"
|
|
|
|
SDXL_BUCKET_RESOLUTIONS = [
|
|
{"orientation": "portrait", "width": 896, "height": 1792, "aspect": 0.50, "mp": 1.61},
|
|
{"orientation": "portrait", "width": 960, "height": 1664, "aspect": 0.58, "mp": 1.60},
|
|
{"orientation": "portrait", "width": 1024, "height": 1600, "aspect": 0.64, "mp": 1.64},
|
|
{"orientation": "portrait", "width": 1088, "height": 1472, "aspect": 0.74, "mp": 1.60},
|
|
{"orientation": "portrait", "width": 1152, "height": 1408, "aspect": 0.82, "mp": 1.62},
|
|
{"orientation": "portrait", "width": 1216, "height": 1344, "aspect": 0.90, "mp": 1.63},
|
|
{"orientation": "square", "width": 1280, "height": 1280, "aspect": 1.00, "mp": 1.64},
|
|
{"orientation": "landscape", "width": 1344, "height": 1216, "aspect": 1.11, "mp": 1.63},
|
|
{"orientation": "landscape", "width": 1408, "height": 1152, "aspect": 1.22, "mp": 1.62},
|
|
{"orientation": "landscape", "width": 1472, "height": 1088, "aspect": 1.35, "mp": 1.60},
|
|
{"orientation": "landscape", "width": 1536, "height": 1024, "aspect": 1.50, "mp": 1.57},
|
|
]
|
|
|
|
KREA2_API_ASPECT_RATIOS = ["1:1", "4:3", "3:2", "16:9", "2.35:1", "4:5", "2:3", "9:16"]
|
|
KREA2_ASPECT_RATIOS = KREA2_API_ASPECT_RATIOS + ["8:9", "21:9", "9:21", "3:1", "1:3"]
|
|
KREA2_MEGAPIXEL_PRESETS = [
|
|
"1.0MP",
|
|
"1.25MP",
|
|
"1.5MP",
|
|
"1.75MP",
|
|
"2.0MP",
|
|
"2.25MP",
|
|
"2.5MP",
|
|
"2.75MP",
|
|
"3.0MP",
|
|
"3.25MP",
|
|
"3.5MP",
|
|
"3.75MP",
|
|
"4.0MP",
|
|
"max_for_aspect",
|
|
]
|
|
|
|
|
|
COMMON_INPUT_TOOLTIPS = {
|
|
"row_number": "Generation row to use. Changing it advances the deterministic selection without changing the main seed.",
|
|
"start_index": "Metadata/output index offset only. It does not limit category pools or random choices.",
|
|
"seed": "Main seed used when no more specific seed config overrides an axis.",
|
|
"global_seed": "One seed that locks all prompt axes so the same inputs can recreate the same result.",
|
|
"base_seed": "Base seed used by Seed Locker before applying a selected reroll axis.",
|
|
"reroll_seed": "Seed for the selected reroll axis. Use -1 to derive it from the base seed.",
|
|
"category": "Main category source. auto_weighted is legacy random; auto_full mixes legacy random with JSON categories including hardcore.",
|
|
"subcategory": "Specific subcategory, or random to choose within the selected category.",
|
|
"category_config": "Category/subcategory config from SxCP Category Preset.",
|
|
"cast_config": "Cast size config from SxCP Cast Control.",
|
|
"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.",
|
|
"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_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.",
|
|
"custom_compositions": "One custom composition/framing phrase per line.",
|
|
"theme": "Matched location and composition theme, useful when the place needs compatible framing.",
|
|
"metadata_json": "Structured metadata from an SxCP generator. Prefer this over raw prompt text for formatters and profile save.",
|
|
"source_text": "Raw prompt, caption, or metadata JSON depending on input_hint.",
|
|
"source_text_input": "Optional linked raw prompt/caption input. When connected, it overrides the source_text widget.",
|
|
"input_hint": "Tells the node how to interpret source_text. auto tries metadata first.",
|
|
"target": "For dual prompts, choose which side to output as the main Krea prompt.",
|
|
"detail_level": "Controls how much detail the rewriter keeps. concise is shorter, dense keeps more clauses.",
|
|
"style_mode": "How strongly the formatter rewrites visual style terms.",
|
|
"preserve_trigger": "Keep the trigger token in the formatted prompt instead of stripping it.",
|
|
"negative_prompt": "Negative prompt text to pass through or merge with generated negatives.",
|
|
"extra_positive": "Extra positive text appended after the generated prompt.",
|
|
"extra_negative": "Extra negative text appended after the generated negative prompt.",
|
|
"trigger": "Training or style trigger token.",
|
|
"prepend_trigger_to_prompt": "If enabled, put the trigger token at the start of generated prompts.",
|
|
"bucket_index": "0 picks a random bucket. 1+ picks that position inside the selected orientation pool.",
|
|
"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.",
|
|
"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.",
|
|
"profile_name": "Name of the profile to save, load, rename, or delete.",
|
|
"manual_profile_name": "Free-text profile name used when profile_name is set to manual.",
|
|
"fallback_profile_json": "Profile JSON to use when a named profile cannot be loaded.",
|
|
"rename_to": "New profile name used only when rename_now is enabled.",
|
|
"save_now": "Writes the profile to disk only when enabled. Keep off while adjusting fields.",
|
|
"delete_now": "Deletes the selected profile when enabled.",
|
|
"rename_now": "Renames the selected profile when enabled.",
|
|
"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.",
|
|
"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.",
|
|
"figure": "General figure bias for generated body descriptors.",
|
|
"figure_bias": "Woman-slot figure bias. Body pool can give more precise body choices.",
|
|
"women_count": "Number of women in the generated cast when no Insta/OF preset overrides it.",
|
|
"men_count": "Number of men in the generated cast when no Insta/OF preset overrides it.",
|
|
"hardcore_women_count": "Number of women in the hardcore cast when hardcore_cast is use_counts.",
|
|
"hardcore_men_count": "Number of men in the hardcore cast when hardcore_cast is use_counts.",
|
|
"body": "Body choice for this slot. A Body Pool node can replace the random list.",
|
|
"manual_body": "Exact body phrase override.",
|
|
"body_phrase": "Full custom body wording. Use only when the body picker is not specific enough.",
|
|
"skin": "Manual skin/complexion phrase.",
|
|
"hair": "Manual hair phrase. Hair config nodes are better for controlled random choices.",
|
|
"eyes": "Manual eye description.",
|
|
"descriptor_detail": "How detailed this character's descriptor should be. Men usually work better compact.",
|
|
"expression_enabled": "Master expression toggle for this generator or character.",
|
|
"expression_intensity": "Expression intensity from 0 to 1. On the direct builder, -1 randomizes per row; on slots, -1 inherits the generator setting.",
|
|
"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.",
|
|
"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.",
|
|
"total": "Total number of loop iterations.",
|
|
"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.",
|
|
"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.",
|
|
"skip_empty": "Ignore empty inputs instead of adding blank entries.",
|
|
"image": "Image to store in the accumulator.",
|
|
"entry_id": "Stable ID used for replace_by_entry_id or grouping variants.",
|
|
"entry_tag": "Optional suffix added to entry_id.",
|
|
"preview_limit": "Maximum number of accumulator images to show in the preview panel.",
|
|
"view_mode": "Accumulator Preview layout: grid shows many images, carousel shows one large image at a time.",
|
|
"zoom_level": "Accumulator Preview image scale. Higher values make grid thumbnails or carousel image area larger.",
|
|
"carousel_index": "1-based image position shown in carousel mode. The previous/next buttons update this value.",
|
|
"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.",
|
|
"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.",
|
|
"clear_after_save": "Clear the accumulator store after a successful batch save.",
|
|
"preview_text": "Serialized persistent text preview. It is updated after execution and saved with the workflow.",
|
|
"preview_format": "How to convert an arbitrary input to preview text.",
|
|
"max_chars": "Maximum stored preview characters. 0 disables truncation.",
|
|
"mode": "Switch direction: pick_input selects one input to value, route_output sends route_value to one output.",
|
|
"index": "Index used by SxCP Index Switch. For Loop Start outputs one_based indexes by default.",
|
|
"index_base": "one_based means index 1 selects input_1. zero_based means index 0 selects input_1.",
|
|
"missing_behavior": "What to do when the requested switch input is not connected: use fallback, output none, clamp, or wrap.",
|
|
"fallback": "Optional value used by SxCP Index Switch when the requested input is missing and missing_behavior is fallback.",
|
|
"route_value": "Value routed to output_N when mode is route_output.",
|
|
"clothing": "Built-in clothing density for legacy direct generation. random picks full/minimal from the seeded row.",
|
|
"poses": "Built-in pose pool for legacy direct generation. random picks standard/evocative from the seeded row.",
|
|
"backside_bias": "Legacy bias toward rear/backside poses where that category supports it.",
|
|
"minimal_clothing_ratio": "Legacy weighted ratio override. -1 keeps the category/profile default.",
|
|
"standard_pose_ratio": "Legacy weighted ratio override. -1 keeps the category/profile default.",
|
|
"profile": "Generation profile preset for broad style, clothing, pose, and expression defaults.",
|
|
"clothing_override": "Override the profile clothing setting, or leave profile_default.",
|
|
"poses_override": "Override the profile pose setting, or leave profile_default.",
|
|
"trigger_policy": "Controls whether the profile prepends the trigger token.",
|
|
"cast_mode": "Preset cast shape. Custom counts are used when the preset allows them.",
|
|
"women_weights": "Comma-separated count weights. First value maps to women_start_count, second to +1, and so on.",
|
|
"men_weights": "Comma-separated count weights. First value maps to men_start_count, second to +1, and so on.",
|
|
"women_start_count": "Woman count represented by the first women_weights value.",
|
|
"men_start_count": "Man count represented by the first men_weights value.",
|
|
"empty_behavior": "What to do if the weighted pick selects zero women and zero men.",
|
|
"preset": "Category preset for common workflow lanes.",
|
|
"camera_mode": "Camera style preset.",
|
|
"shot_size": "How much of the body/frame should be visible.",
|
|
"angle": "Camera angle relative to the subject.",
|
|
"lens": "Lens wording to include in the prompt.",
|
|
"distance": "Camera distance wording.",
|
|
"orientation": "Horizontal/vertical framing wording.",
|
|
"phone_visibility": "Whether the prompt mentions a visible/hidden phone.",
|
|
"priority": "How strictly the prompt should enforce the camera wording.",
|
|
"camera_detail": "off omits camera text, compact keeps one line, full emits detailed camera wording.",
|
|
"subject_focus": "Optional camera focus phrase, such as face/body/contact emphasis.",
|
|
"strict_excludes": "When enabled, only selected ethnicity groups are used. When off, selections act more like soft includes.",
|
|
"min_age": "Minimum adult age in this custom age pool.",
|
|
"max_age": "Maximum adult age in this custom age pool.",
|
|
"softcore_source": "Softcore outfit source for this character. custom reads custom_softcore_outfits.",
|
|
"hardcore_state": "Hardcore clothing/body exposure state for this character.",
|
|
"softcore_expression_enabled": "Enable expression text in the softcore prompt.",
|
|
"hardcore_expression_enabled": "Enable expression text in the hardcore prompt.",
|
|
"flow": "Loop flow-control socket. Wire from the matching loop start node.",
|
|
"collection_mode": "How the loop end collects per-iteration values.",
|
|
"skip_none": "Do not add empty values to the collection.",
|
|
"collected": "Current accumulated value carried through the loop.",
|
|
"collect_value": "Value captured from the current loop iteration.",
|
|
"a": "First integer/boolean helper input.",
|
|
"b": "Second integer/boolean helper input.",
|
|
}
|
|
|
|
NODE_INPUT_TOOLTIPS = {
|
|
"SxCPSeedControl": {
|
|
"category_seed_mode": "auto/follow_main follows the main seed; fixed uses category_seed; random rerolls this axis each queue.",
|
|
"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.",
|
|
"scene_seed_mode": "Controls location/scene selection.",
|
|
"pose_seed_mode": "Controls pose/item selection for pose categories, including hardcore positions.",
|
|
"role_seed_mode": "Controls role assignment and secondary action details.",
|
|
"expression_seed_mode": "Controls selected expression text.",
|
|
"composition_seed_mode": "Controls framing/composition text.",
|
|
},
|
|
"SxCPSeedLocker": {
|
|
"reroll_axis": "Choose the one axis to change while the rest stays locked. Use pose for sexual pose, scene for location, person for appearance.",
|
|
},
|
|
"SxCPCastBias": {
|
|
"seed": "Fixed cast-bias seed. Use -1 for a fresh cast each queue, or connect Global Seed/Seed Locker through seed_config.",
|
|
"seed_config": "Optional seed config. The category seed controls weighted cast selection.",
|
|
"women_weights": "Example with women_start_count=1: 0.6,0.25,0.1 means 60% one woman, 25% two women, 10% three women.",
|
|
"men_weights": "Example with men_start_count=0: 0.5,0.35,0.1 means 50% no man, 35% one man, 10% two men.",
|
|
"empty_behavior": "Prevents accidental empty casts when both weighted pools pick zero.",
|
|
},
|
|
"SxCPSDXLBucketSize": {
|
|
"orientation": "Bucket orientation filter. any uses the full table; portrait/square/landscape restrict random selection.",
|
|
"seed": "Fixed bucket seed. Use -1 for a fresh random bucket each queue, or connect Global Seed for reproducible sizes.",
|
|
"row_number": "Deterministic row offset for the bucket. With a fixed seed, changing this advances the bucket choice.",
|
|
"bucket_index": "0=random. 1+ selects that bucket position inside the selected orientation pool and ignores seed.",
|
|
"seed_config": "Optional seed config. The composition seed controls bucket choice, so Seed Locker can keep sizes fixed while rerolling pose/person.",
|
|
},
|
|
"SxCPKrea2ResolutionSelector": {
|
|
"megapixels": "Target megapixel preset. If it cannot fit the aspect ratio under the 2K Krea2 Turbo limit, the node clamps to the maximum valid size.",
|
|
"aspect_ratio": "Krea API ratios are listed first; local-only helper ratios like 8:9 are included after them.",
|
|
},
|
|
"SxCPCameraControl": {
|
|
"camera_mode": "Camera style preset. Use from_camera_config in Insta/OF options to consume this.",
|
|
"priority": "locked makes the camera wording strict; soft_hint allows the model more freedom.",
|
|
"camera_detail": "off omits camera text, compact keeps one short line, full emits detailed camera constraints.",
|
|
"phone_visibility": "Use phone_hidden or suppress_phone_visibility when you do not want 'phone hidden' text in prompts.",
|
|
},
|
|
"SxCPCameraOrbitControl": {
|
|
"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.",
|
|
"framing": "How zoom should be translated into shot size/distance wording.",
|
|
"include_degrees": "Include numeric degree wording in addition to human camera direction.",
|
|
},
|
|
"SxCPQwenCameraTranslator": {
|
|
"qwen_prompt": "Camera prompt from Qwen MultiAngle, for example '<sks> 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.",
|
|
"suppress_phone_visibility": "Avoid adding phone visibility text unless you explicitly set a phone option.",
|
|
},
|
|
"SxCPHardcorePositionPool": {
|
|
"family": "Restrict the broad hardcore family. Use any when you want oral and penetration to both be possible.",
|
|
"combine_mode": "replace discards incoming position choices; add merges these choices with the incoming config.",
|
|
"hardcore_position_config": "Optional incoming config. Usually connect previous Position Pool here only when chaining pools.",
|
|
},
|
|
"SxCPHardcoreActionFilter": {
|
|
"focus": "keep_pool preserves/broadens the incoming pool; *_only modes force one action family.",
|
|
"allow_toys": "Allow toy/strap-on wording in hardcore actions.",
|
|
"allow_double": "Allow double-penetration or second-contact wording.",
|
|
"allow_penetration": "Allow vaginal/penetrative sex subcategories.",
|
|
"allow_foreplay": "Allow hardcore teasing/foreplay setup actions such as kissing, caressing, breast/face touching, and undressing.",
|
|
"allow_interaction": "Allow non-act interaction pools such as body worship, clothing transitions, guidance, camera presentation, watching, and aftercare.",
|
|
"allow_manual": "Allow manual stimulation pools such as fingering, clit rubbing, and mutual masturbation.",
|
|
"allow_oral": "Allow oral sex subcategories.",
|
|
"allow_outercourse": "Allow non-penetrative penis-contact acts such as boobjob/titjob, footjob, penis licking, and testicle sucking.",
|
|
"allow_anal": "Allow anal subcategories.",
|
|
"allow_climax": "Allow cumshot/climax aftermath subcategories.",
|
|
},
|
|
"SxCPInstaOFOptions": {
|
|
"softcore_cast": "solo keeps softcore focused on Woman A; same_as_hardcore includes the same cast as the hardcore prompt.",
|
|
"hardcore_cast": "use_counts reads hardcore_women_count/hardcore_men_count; presets set the counts automatically.",
|
|
"softcore_level": "Controls the soft prompt exposure/outfit level.",
|
|
"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.",
|
|
"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.",
|
|
"hardcore_detail_density": "How dense the hardcore action sentence should be in the Krea formatter.",
|
|
},
|
|
"SxCPInstaOFPromptPair": {
|
|
"options_json": "Options from SxCP Insta/OF Options. If empty, defaults are used.",
|
|
"ethnicity": "Fallback ethnicity when no filter/ethnicity list or character slots are connected.",
|
|
"figure": "Fallback figure bias when no character slot overrides it.",
|
|
},
|
|
"SxCPPromptBuilderFromConfigs": {
|
|
"seed": "Main seed. Connect Seed Config for per-axis control.",
|
|
},
|
|
"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.",
|
|
"character_slot": "Use this when saving a configured slot directly.",
|
|
},
|
|
"SxCPCharacterProfileLoad": {
|
|
"enabled": "When false, outputs an empty profile and leaves downstream generation unchanged.",
|
|
"override_age": "Optional loaded-profile override. Empty keeps the profile value.",
|
|
"override_body": "Optional body override. Empty keeps the profile value.",
|
|
"override_descriptor_detail": "Override descriptor verbosity while keeping the rest of the loaded profile.",
|
|
},
|
|
"SxCPKrea2Formatter": {
|
|
"metadata_json": "Best input for Krea2 formatting because it preserves cast, camera, and hardcore action metadata.",
|
|
"preserve_trigger": "Reminder: Krea2 formatting is intended to remove training/style triggers. Leave false unless you intentionally want a raw text trigger preserved.",
|
|
"source_text": "Raw prompt fallback. Known trigger tokens are stripped by default for Krea2.",
|
|
},
|
|
"SxCPSDXLFormatter": {
|
|
"metadata_json": "Best input for SDXL tag formatting because it preserves cast, camera, outfit, and explicit action metadata.",
|
|
"style_preset": "Positive style anchor preset. flat_vector_pony matches the old SDXL tag style.",
|
|
"quality_preset": "Quality/score tag tail for SDXL or Pony-style checkpoints.",
|
|
"custom_style": "Optional replacement for the style preset. Leave empty to use style_preset.",
|
|
"custom_quality": "Optional replacement for the quality preset. Leave empty to use quality_preset.",
|
|
"nude_weight": "Weight used when explicit nude/body exposure tags are inferred.",
|
|
},
|
|
"SxCPCaptionNaturalizer": {
|
|
"metadata_json": "Best input for training captions because it preserves structured generator details.",
|
|
"style_policy": "drop_style_tail removes generation/style boilerplate; keep_style_terms preserves more of it.",
|
|
"include_trigger": "Keep this true for LoRA/training captions so the trigger token is learned.",
|
|
},
|
|
"SxCPForLoopStart": {
|
|
"index": "Output loop index. First generated index is skip + 1.",
|
|
"collected": "Current accumulated value carried through the loop.",
|
|
},
|
|
"SxCPLoopAppend": {
|
|
"mode": "auto_batch tries tensor/latent batching first, then falls back to a list.",
|
|
},
|
|
"SxCPAccumulator": {
|
|
"image_batch_mode": "same_size_only keeps incompatible sizes separate; resize_to_first forces one image batch.",
|
|
},
|
|
}
|
|
|
|
|
|
def _tooltip_for_input(node_name: str, input_name: str) -> str:
|
|
node_tooltips = NODE_INPUT_TOOLTIPS.get(node_name, {})
|
|
if input_name in node_tooltips:
|
|
return node_tooltips[input_name]
|
|
if input_name in COMMON_INPUT_TOOLTIPS:
|
|
return COMMON_INPUT_TOOLTIPS[input_name]
|
|
if input_name.endswith("_seed_mode"):
|
|
axis = input_name[: -len("_seed_mode")]
|
|
return f"How the {axis} seed is resolved: follow the main seed, use the fixed field, or reroll randomly."
|
|
if input_name.endswith("_seed"):
|
|
axis = input_name[: -len("_seed")]
|
|
return f"Fixed {axis} seed value. Used only when the matching seed mode is fixed, or as a fallback for auto modes."
|
|
if input_name.startswith("include_"):
|
|
value = input_name[len("include_") :].replace("_", " ")
|
|
return f"Include {value} in this random pool."
|
|
if input_name.startswith("initial_value"):
|
|
return "Carry value passed into the loop body and returned on the matching output."
|
|
if re.match(r"^input_\d+$", input_name):
|
|
return "Autoscaling switch input. Connect the last visible input to reveal the next one."
|
|
if re.match(r"^output_\d+$", input_name):
|
|
return "Autoscaling routed output. Connect the last visible output to reveal the next one."
|
|
if input_name.startswith("override_"):
|
|
return "Optional loaded-profile override. Leave empty or keep_profile to preserve the profile value."
|
|
return ""
|
|
|
|
|
|
def _copy_input_spec_with_tooltip(input_spec, tooltip: str):
|
|
if not tooltip or not isinstance(input_spec, tuple):
|
|
return input_spec
|
|
if len(input_spec) >= 2 and isinstance(input_spec[1], dict):
|
|
options = dict(input_spec[1])
|
|
options.setdefault("tooltip", tooltip)
|
|
return (input_spec[0], options, *input_spec[2:])
|
|
if len(input_spec) == 1:
|
|
return (input_spec[0], {"tooltip": tooltip})
|
|
return input_spec
|
|
|
|
|
|
def _inject_input_tooltips(input_types: dict, node_name: str) -> dict:
|
|
patched = dict(input_types)
|
|
for group_name in ("required", "optional"):
|
|
group = patched.get(group_name)
|
|
if not isinstance(group, dict):
|
|
continue
|
|
patched_group = {}
|
|
for input_name, input_spec in group.items():
|
|
patched_group[input_name] = _copy_input_spec_with_tooltip(
|
|
input_spec,
|
|
_tooltip_for_input(node_name, input_name),
|
|
)
|
|
patched[group_name] = patched_group
|
|
return patched
|
|
|
|
|
|
def _install_input_tooltips(node_classes: dict[str, type]) -> None:
|
|
for node_name, node_class in node_classes.items():
|
|
original = getattr(node_class, "INPUT_TYPES", None)
|
|
if original is None or getattr(node_class, "_sxcp_tooltips_installed", False):
|
|
continue
|
|
|
|
def input_types(cls, _original=original, _node_name=node_name):
|
|
return _inject_input_tooltips(_original(), _node_name)
|
|
|
|
node_class.INPUT_TYPES = classmethod(input_types)
|
|
node_class._sxcp_tooltips_installed = True
|
|
|
|
try:
|
|
from .loop_nodes import (
|
|
ANY_TYPE,
|
|
LOOP_NODE_CLASS_MAPPINGS,
|
|
LOOP_NODE_DISPLAY_NAME_MAPPINGS,
|
|
accumulator_delete_entries,
|
|
accumulator_list_entries,
|
|
accumulator_move_entry,
|
|
accumulator_save_entries,
|
|
)
|
|
from .prompt_builder import (
|
|
build_camera_config_json,
|
|
build_camera_orbit_config_json,
|
|
build_qwen_camera_config_json,
|
|
build_cast_config_json,
|
|
build_category_config_json,
|
|
build_character_slot_json,
|
|
build_character_manual_config_json,
|
|
build_character_profile_json,
|
|
build_characteristics_config_json,
|
|
build_composition_pool_json,
|
|
build_ethnicity_list_json,
|
|
build_filter_config_json,
|
|
build_generation_profile_json,
|
|
build_hair_config_json,
|
|
build_hardcore_action_filter_json,
|
|
build_hardcore_position_pool_json,
|
|
build_insta_of_options_json,
|
|
build_location_pool_json,
|
|
build_thematic_location_json,
|
|
build_insta_of_pair,
|
|
build_prompt,
|
|
build_prompt_from_configs,
|
|
build_seed_config_json,
|
|
build_seed_lock_config_json,
|
|
camera_angle_choices,
|
|
camera_detail_choices,
|
|
camera_distance_choices,
|
|
camera_lens_choices,
|
|
camera_mode_choices,
|
|
camera_orbit_focus_choices,
|
|
camera_orbit_framing_choices,
|
|
camera_orientation_choices,
|
|
camera_phone_choices,
|
|
camera_priority_choices,
|
|
camera_shot_choices,
|
|
cast_preset_choices,
|
|
category_preset_choices,
|
|
category_choices,
|
|
character_age_choices,
|
|
character_body_choices,
|
|
character_descriptor_detail_choices,
|
|
character_ethnicity_choices,
|
|
character_eye_color_choices,
|
|
character_figure_choices,
|
|
character_hair_color_choices,
|
|
character_hair_length_choices,
|
|
character_hair_style_choices,
|
|
character_label_choices,
|
|
character_man_body_choices,
|
|
character_presence_choices,
|
|
character_profile_choices,
|
|
character_hardcore_clothing_state_choices,
|
|
character_hardcore_clothing_values,
|
|
character_softcore_outfit_source_choices,
|
|
character_softcore_outfit_values,
|
|
character_woman_body_choices,
|
|
composition_pool_preset_choices,
|
|
ethnicity_choices,
|
|
generation_profile_choices,
|
|
hardcore_position_family_choices,
|
|
hardcore_position_focus_choices,
|
|
hardcore_position_key_choices,
|
|
hardcore_detail_density_choices,
|
|
load_character_profile_json,
|
|
location_theme_choices,
|
|
location_pool_preset_choices,
|
|
save_character_profile_payload,
|
|
seed_mode_choices,
|
|
subcategory_choices,
|
|
)
|
|
from .caption_naturalizer import naturalize_caption
|
|
from .krea_formatter import format_krea2_prompt
|
|
from .sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices
|
|
except ImportError:
|
|
from loop_nodes import (
|
|
ANY_TYPE,
|
|
LOOP_NODE_CLASS_MAPPINGS,
|
|
LOOP_NODE_DISPLAY_NAME_MAPPINGS,
|
|
accumulator_delete_entries,
|
|
accumulator_list_entries,
|
|
accumulator_move_entry,
|
|
accumulator_save_entries,
|
|
)
|
|
from prompt_builder import (
|
|
build_camera_config_json,
|
|
build_camera_orbit_config_json,
|
|
build_qwen_camera_config_json,
|
|
build_cast_config_json,
|
|
build_category_config_json,
|
|
build_character_slot_json,
|
|
build_character_manual_config_json,
|
|
build_character_profile_json,
|
|
build_characteristics_config_json,
|
|
build_composition_pool_json,
|
|
build_ethnicity_list_json,
|
|
build_filter_config_json,
|
|
build_generation_profile_json,
|
|
build_hair_config_json,
|
|
build_hardcore_action_filter_json,
|
|
build_hardcore_position_pool_json,
|
|
build_insta_of_options_json,
|
|
build_location_pool_json,
|
|
build_thematic_location_json,
|
|
build_insta_of_pair,
|
|
build_prompt,
|
|
build_prompt_from_configs,
|
|
build_seed_config_json,
|
|
build_seed_lock_config_json,
|
|
camera_angle_choices,
|
|
camera_detail_choices,
|
|
camera_distance_choices,
|
|
camera_lens_choices,
|
|
camera_mode_choices,
|
|
camera_orbit_focus_choices,
|
|
camera_orbit_framing_choices,
|
|
camera_orientation_choices,
|
|
camera_phone_choices,
|
|
camera_priority_choices,
|
|
camera_shot_choices,
|
|
cast_preset_choices,
|
|
category_preset_choices,
|
|
category_choices,
|
|
character_age_choices,
|
|
character_body_choices,
|
|
character_descriptor_detail_choices,
|
|
character_ethnicity_choices,
|
|
character_eye_color_choices,
|
|
character_figure_choices,
|
|
character_hair_color_choices,
|
|
character_hair_length_choices,
|
|
character_hair_style_choices,
|
|
character_label_choices,
|
|
character_man_body_choices,
|
|
character_presence_choices,
|
|
character_profile_choices,
|
|
character_hardcore_clothing_state_choices,
|
|
character_hardcore_clothing_values,
|
|
character_softcore_outfit_source_choices,
|
|
character_softcore_outfit_values,
|
|
character_woman_body_choices,
|
|
composition_pool_preset_choices,
|
|
ethnicity_choices,
|
|
generation_profile_choices,
|
|
hardcore_position_family_choices,
|
|
hardcore_position_focus_choices,
|
|
hardcore_position_key_choices,
|
|
hardcore_detail_density_choices,
|
|
load_character_profile_json,
|
|
location_theme_choices,
|
|
location_pool_preset_choices,
|
|
save_character_profile_payload,
|
|
seed_mode_choices,
|
|
subcategory_choices,
|
|
)
|
|
from caption_naturalizer import naturalize_caption
|
|
from krea_formatter import format_krea2_prompt
|
|
from sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices
|
|
|
|
|
|
if PromptServer is not None and web is not None:
|
|
@PromptServer.instance.routes.post("/sxcp/profile/save_cached")
|
|
async def sxcp_save_cached_profile(request):
|
|
try:
|
|
payload = await request.json()
|
|
result = save_character_profile_payload(
|
|
profile_name=str(payload.get("profile_name") or ""),
|
|
profile_json=payload.get("profile_json") or "",
|
|
)
|
|
return web.json_response(result)
|
|
except Exception as exc:
|
|
return web.json_response({"error": str(exc)}, status=400)
|
|
|
|
@PromptServer.instance.routes.post("/sxcp/accumulator/list")
|
|
async def sxcp_accumulator_list(request):
|
|
try:
|
|
payload = await request.json()
|
|
result = accumulator_list_entries(
|
|
str(payload.get("store_key") or ""),
|
|
preview_limit=int(payload.get("preview_limit") or 0),
|
|
)
|
|
return web.json_response(result)
|
|
except Exception as exc:
|
|
return web.json_response({"error": str(exc)}, status=400)
|
|
|
|
@PromptServer.instance.routes.post("/sxcp/accumulator/delete")
|
|
async def sxcp_accumulator_delete(request):
|
|
try:
|
|
payload = await request.json()
|
|
result = accumulator_delete_entries(
|
|
store_key=str(payload.get("store_key") or ""),
|
|
preview_key=str(payload.get("preview_key") or ""),
|
|
entry_id=str(payload.get("entry_id") or ""),
|
|
index=int(payload.get("index") or 0),
|
|
clear=bool(payload.get("clear")),
|
|
preview_limit=int(payload.get("preview_limit") or 0),
|
|
)
|
|
return web.json_response(result)
|
|
except Exception as exc:
|
|
return web.json_response({"error": str(exc)}, status=400)
|
|
|
|
@PromptServer.instance.routes.post("/sxcp/accumulator/save")
|
|
async def sxcp_accumulator_save(request):
|
|
try:
|
|
payload = await request.json()
|
|
result = accumulator_save_entries(
|
|
store_key=str(payload.get("store_key") or ""),
|
|
save_path=str(payload.get("save_path") or "sxcp_accumulator"),
|
|
filename_prefix=str(payload.get("filename_prefix") or "sxcp_accum"),
|
|
clear_after_save=bool(payload.get("clear_after_save")),
|
|
preview_limit=int(payload.get("preview_limit") or 0),
|
|
)
|
|
return web.json_response(result)
|
|
except Exception as exc:
|
|
return web.json_response({"error": str(exc)}, status=400)
|
|
|
|
@PromptServer.instance.routes.post("/sxcp/accumulator/move")
|
|
async def sxcp_accumulator_move(request):
|
|
try:
|
|
payload = await request.json()
|
|
result = accumulator_move_entry(
|
|
store_key=str(payload.get("store_key") or ""),
|
|
preview_key=str(payload.get("preview_key") or ""),
|
|
entry_id=str(payload.get("entry_id") or ""),
|
|
index=int(payload.get("index") or 0),
|
|
direction=str(payload.get("direction") or "up"),
|
|
target_index=int(payload.get("target_index") or 0),
|
|
preview_limit=int(payload.get("preview_limit") or 0),
|
|
)
|
|
return web.json_response(result)
|
|
except Exception as exc:
|
|
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 SxCPSeedControl:
|
|
SEED_AXES = (
|
|
"category",
|
|
"subcategory",
|
|
"content",
|
|
"person",
|
|
"scene",
|
|
"pose",
|
|
"role",
|
|
"expression",
|
|
"composition",
|
|
)
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
seed_spec = {"default": -1, "min": -1, "max": 0xFFFFFFFF, "step": 1}
|
|
required = {}
|
|
for axis in cls.SEED_AXES:
|
|
required[f"{axis}_seed_mode"] = (seed_mode_choices(), {"default": "auto"})
|
|
required[f"{axis}_seed"] = ("INT", seed_spec)
|
|
return {
|
|
"required": required
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SEED_CONFIG,)
|
|
RETURN_NAMES = ("seed_config",)
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
values = list(args) + list(kwargs.values())
|
|
if "random" in values:
|
|
return random.random()
|
|
return tuple(args), tuple(sorted(kwargs.items()))
|
|
|
|
def build(
|
|
self,
|
|
category_seed_mode,
|
|
category_seed,
|
|
subcategory_seed_mode,
|
|
subcategory_seed,
|
|
content_seed_mode,
|
|
content_seed,
|
|
person_seed_mode,
|
|
person_seed,
|
|
scene_seed_mode,
|
|
scene_seed,
|
|
pose_seed_mode,
|
|
pose_seed,
|
|
role_seed_mode,
|
|
role_seed,
|
|
expression_seed_mode,
|
|
expression_seed,
|
|
composition_seed_mode,
|
|
composition_seed,
|
|
):
|
|
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,
|
|
),
|
|
)
|
|
|
|
|
|
class SxCPGlobalSeed:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
seed_spec = {"default": 20260614, "min": 0, "max": 0xFFFFFFFF, "step": 1}
|
|
return {
|
|
"required": {
|
|
"global_seed": ("INT", seed_spec),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", SXCP_SEED_CONFIG, "STRING")
|
|
RETURN_NAMES = ("seed", "seed_config", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(self, global_seed):
|
|
seed = max(0, min(0xFFFFFFFF, int(global_seed)))
|
|
config = build_seed_lock_config_json(base_seed=seed, reroll_axis="none", reroll_seed=-1)
|
|
return seed, config, f"global seed {seed}; all axes locked"
|
|
|
|
|
|
class SxCPSeedLocker:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
seed_spec = {"default": 20260614, "min": 0, "max": 0xFFFFFFFF, "step": 1}
|
|
reroll_seed_spec = {"default": -1, "min": -1, "max": 0xFFFFFFFF, "step": 1}
|
|
return {
|
|
"required": {
|
|
"base_seed": ("INT", seed_spec),
|
|
"reroll_axis": (
|
|
[
|
|
"none",
|
|
"category",
|
|
"subcategory",
|
|
"content",
|
|
"person",
|
|
"scene",
|
|
"pose",
|
|
"role",
|
|
"expression",
|
|
"composition",
|
|
"content_pose",
|
|
"scene_pose",
|
|
],
|
|
{"default": "none"},
|
|
),
|
|
"reroll_seed": ("INT", reroll_seed_spec),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_SEED_CONFIG, "STRING")
|
|
RETURN_NAMES = ("seed_config", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(self, base_seed, reroll_axis, reroll_seed):
|
|
config = build_seed_lock_config_json(base_seed=base_seed, reroll_axis=reroll_axis, reroll_seed=reroll_seed)
|
|
summary = f"base {base_seed}; reroll {reroll_axis} with {'main seed' if int(reroll_seed) < 0 else reroll_seed}"
|
|
return config, summary
|
|
|
|
|
|
class SxCPSDXLBucketSize:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"orientation": (["any", "portrait", "square", "landscape"], {"default": "any"}),
|
|
"seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF, "step": 1}),
|
|
"row_number": ("INT", {"default": 1, "min": 1, "max": 1000000, "step": 1}),
|
|
"bucket_index": ("INT", {"default": 0, "min": 0, "max": len(SDXL_BUCKET_RESOLUTIONS), "step": 1}),
|
|
},
|
|
"optional": {
|
|
"seed_config": (SXCP_SEED_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", "INT", "STRING", "STRING", "FLOAT", "FLOAT", "INT", "STRING")
|
|
RETURN_NAMES = ("width", "height", "resolution", "orientation", "aspect", "megapixels", "bucket_index", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder/util"
|
|
|
|
@staticmethod
|
|
def _configured_bucket_seed(seed_config):
|
|
if not seed_config:
|
|
return None
|
|
if isinstance(seed_config, dict):
|
|
raw = seed_config
|
|
else:
|
|
try:
|
|
raw = json.loads(str(seed_config))
|
|
except (TypeError, ValueError, json.JSONDecodeError):
|
|
return None
|
|
if not isinstance(raw, dict):
|
|
return None
|
|
for key in ("composition_seed", "content_seed", "seed", "global_seed"):
|
|
try:
|
|
value = int(raw.get(key))
|
|
except (TypeError, ValueError):
|
|
continue
|
|
if value >= 0:
|
|
return value
|
|
return None
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
seed_value = kwargs.get("seed")
|
|
if seed_value is None and len(args) > 1:
|
|
seed_value = args[1]
|
|
bucket_index = kwargs.get("bucket_index")
|
|
if bucket_index is None and len(args) > 3:
|
|
bucket_index = args[3]
|
|
seed_config = kwargs.get("seed_config", "")
|
|
if not seed_config and len(args) > 4:
|
|
seed_config = args[4]
|
|
try:
|
|
seed = int(seed_value)
|
|
except (TypeError, ValueError):
|
|
seed = -1
|
|
try:
|
|
index = int(bucket_index)
|
|
except (TypeError, ValueError):
|
|
index = 0
|
|
if index <= 0 and seed < 0 and cls._configured_bucket_seed(seed_config) is None:
|
|
return random.random()
|
|
return tuple(args), tuple(sorted(kwargs.items()))
|
|
|
|
def build(self, orientation, seed, row_number, bucket_index, seed_config=""):
|
|
orientation = str(orientation or "any").strip().lower()
|
|
pool = [
|
|
(index + 1, bucket)
|
|
for index, bucket in enumerate(SDXL_BUCKET_RESOLUTIONS)
|
|
if orientation == "any" or bucket["orientation"] == orientation
|
|
]
|
|
if not pool:
|
|
pool = list(enumerate(SDXL_BUCKET_RESOLUTIONS, start=1))
|
|
if int(bucket_index) > 0:
|
|
pool_position = max(1, min(len(pool), int(bucket_index))) - 1
|
|
else:
|
|
configured_seed = self._configured_bucket_seed(seed_config)
|
|
if configured_seed is None and int(seed) < 0:
|
|
rng = random.Random(random.getrandbits(64))
|
|
else:
|
|
bucket_seed = configured_seed if configured_seed is not None else int(seed)
|
|
rng = random.Random(f"sdxl_bucket:{bucket_seed}:{int(row_number)}:{orientation}")
|
|
pool_position = rng.randrange(len(pool))
|
|
selected_index, selected = pool[pool_position]
|
|
width = int(selected["width"])
|
|
height = int(selected["height"])
|
|
selected_orientation = str(selected["orientation"])
|
|
aspect = float(selected["aspect"])
|
|
mp = float(selected["mp"])
|
|
resolution = f"{width}x{height}"
|
|
summary = (
|
|
f"{selected_orientation} bucket {pool_position + 1}/{len(pool)} "
|
|
f"(table {selected_index}): {resolution}, aspect {aspect:.2f}, {mp:.2f} MP"
|
|
)
|
|
return width, height, resolution, selected_orientation, aspect, mp, selected_index, summary
|
|
|
|
|
|
class SxCPKrea2ResolutionSelector:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"megapixels": (KREA2_MEGAPIXEL_PRESETS, {"default": "1.0MP"}),
|
|
"aspect_ratio": (KREA2_ASPECT_RATIOS, {"default": "1:1"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", "INT", "STRING", "STRING", "STRING", "STRING", "FLOAT", "FLOAT", "STRING", "STRING", "STRING")
|
|
RETURN_NAMES = (
|
|
"width",
|
|
"height",
|
|
"resolution",
|
|
"aspect_ratio",
|
|
"api_aspect_ratio",
|
|
"api_resolution",
|
|
"megapixels",
|
|
"max_megapixels_for_aspect",
|
|
"orientation",
|
|
"summary",
|
|
"config_json",
|
|
)
|
|
FUNCTION = "select"
|
|
CATEGORY = "prompt_builder/util"
|
|
|
|
@staticmethod
|
|
def _aspect_value(aspect_ratio, custom_aspect_width, custom_aspect_height, rng):
|
|
selected = str(aspect_ratio or "1:1").strip()
|
|
if selected == "random_api":
|
|
selected = rng.choice(KREA2_API_ASPECT_RATIOS)
|
|
if selected == "custom":
|
|
width = max(0.1, float(custom_aspect_width))
|
|
height = max(0.1, float(custom_aspect_height))
|
|
return selected, width / height
|
|
try:
|
|
left, right = selected.split(":", 1)
|
|
return selected, max(0.01, float(left) / float(right))
|
|
except (TypeError, ValueError):
|
|
return "1:1", 1.0
|
|
|
|
@staticmethod
|
|
def _closest_api_aspect(ratio):
|
|
def parse(value):
|
|
left, right = value.split(":", 1)
|
|
return float(left) / float(right)
|
|
|
|
return min(KREA2_API_ASPECT_RATIOS, key=lambda item: abs(math.log(parse(item) / max(0.01, ratio))))
|
|
|
|
@staticmethod
|
|
def _continuous_limit_mp(ratio, max_long_edge, max_megapixels):
|
|
ratio = max(0.01, float(ratio))
|
|
max_long = max(16.0, float(max_long_edge))
|
|
if ratio >= 1.0:
|
|
exact_width = max_long
|
|
exact_height = max_long / ratio
|
|
else:
|
|
exact_width = max_long * ratio
|
|
exact_height = max_long
|
|
exact_mp = (exact_width * exact_height) / 1_000_000.0
|
|
return max(0.01, min(float(max_megapixels), exact_mp))
|
|
|
|
@staticmethod
|
|
def _nearby_multiples(value, multiple):
|
|
scaled = float(value) / float(multiple)
|
|
values = {
|
|
int(math.floor(scaled)) * multiple,
|
|
int(round(scaled)) * multiple,
|
|
int(math.ceil(scaled)) * multiple,
|
|
}
|
|
return {int(v) for v in values if int(v) > 0}
|
|
|
|
@classmethod
|
|
def _candidate_sizes(cls, ratio, max_long_edge, max_megapixels, multiple):
|
|
max_long = max(multiple, int(max_long_edge) // multiple * multiple)
|
|
max_pixels = float(max_megapixels) * 1_000_000.0
|
|
candidates = set()
|
|
for width in range(multiple, max_long + 1, multiple):
|
|
for height in cls._nearby_multiples(float(width) / ratio, multiple):
|
|
candidates.add((width, height))
|
|
for height in range(multiple, max_long + 1, multiple):
|
|
for width in cls._nearby_multiples(float(height) * ratio, multiple):
|
|
candidates.add((width, height))
|
|
valid = []
|
|
for width, height in candidates:
|
|
if width < multiple or height < multiple:
|
|
continue
|
|
if max(width, height) > max_long:
|
|
continue
|
|
if width * height > max_pixels + 1:
|
|
continue
|
|
valid.append((width, height))
|
|
return valid
|
|
|
|
@classmethod
|
|
def _best_size(cls, ratio, target_megapixels, max_long_edge, max_megapixels, multiple):
|
|
candidates = cls._candidate_sizes(ratio, max_long_edge, max_megapixels, multiple)
|
|
if not candidates:
|
|
fallback = max(multiple, int(max_long_edge) // multiple * multiple)
|
|
return fallback, fallback, (fallback * fallback) / 1_000_000.0, 1.0
|
|
target = max((multiple * multiple) / 1_000_000.0, float(target_megapixels))
|
|
best = None
|
|
best_score = None
|
|
for width, height in candidates:
|
|
actual_mp = (width * height) / 1_000_000.0
|
|
actual_ratio = float(width) / float(height)
|
|
ratio_error = abs(math.log(actual_ratio / max(0.01, ratio)))
|
|
mp_error = abs(actual_mp - target) / max(target, 0.01)
|
|
score = ratio_error * 4.0 + mp_error
|
|
if best_score is None or score < best_score:
|
|
best = (width, height, actual_mp, actual_ratio)
|
|
best_score = score
|
|
return best
|
|
|
|
@staticmethod
|
|
def _profile_limits(profile, custom_max_long_edge, custom_max_megapixels):
|
|
profile = str(profile or "turbo_local_2k").strip()
|
|
if profile == "raw_local_1k":
|
|
return 1024, 1.05, "Krea2 RAW local explicit size, up to 1K"
|
|
if profile == "api_hosted_1k":
|
|
return 1024, 1.05, "Krea hosted API fields, 1K only"
|
|
if profile == "custom_limit":
|
|
return max(256, int(custom_max_long_edge)), max(0.10, float(custom_max_megapixels)), "custom explicit size limit"
|
|
return 2048, 4.20, "Krea2 Turbo local explicit size, up to 2K"
|
|
|
|
@staticmethod
|
|
def _preset_megapixels(megapixel_preset):
|
|
value = str(megapixel_preset or "1.0MP").strip()
|
|
if value.endswith("MP"):
|
|
try:
|
|
return float(value[:-2])
|
|
except ValueError:
|
|
return 1.0
|
|
return None
|
|
|
|
def select(self, megapixels, aspect_ratio):
|
|
multiple = 16
|
|
profile = "turbo_local_2k"
|
|
max_long_edge, max_profile_mp, _profile_label = self._profile_limits(profile, 2048, 4.20)
|
|
resolved_aspect, ratio = self._aspect_value(aspect_ratio, 1.0, 1.0, random.Random("krea2_resolution"))
|
|
api_aspect_ratio = resolved_aspect if resolved_aspect in KREA2_API_ASPECT_RATIOS else self._closest_api_aspect(ratio)
|
|
|
|
continuous_max_mp = self._continuous_limit_mp(ratio, max_long_edge, max_profile_mp)
|
|
max_width, max_height, max_actual_mp, max_actual_ratio = self._best_size(
|
|
ratio, continuous_max_mp, max_long_edge, max_profile_mp, multiple
|
|
)
|
|
|
|
preset = str(megapixels or "1.0MP").strip()
|
|
target_mp = self._preset_megapixels(preset)
|
|
if preset == "max_for_aspect":
|
|
target_mp = max_actual_mp
|
|
if target_mp is None:
|
|
target_mp = 1.0
|
|
|
|
clamped = target_mp > max_actual_mp + 0.001
|
|
effective_target_mp = min(float(target_mp), max_actual_mp)
|
|
width, height, actual_mp, actual_ratio = self._best_size(
|
|
ratio, effective_target_mp, max_long_edge, max_profile_mp, multiple
|
|
)
|
|
orientation = "square"
|
|
if width > height:
|
|
orientation = "landscape"
|
|
elif height > width:
|
|
orientation = "portrait"
|
|
|
|
resolution = f"{width}x{height}"
|
|
api_resolution = "1K"
|
|
summary_parts = [
|
|
f"{resolution}",
|
|
f"{actual_mp:.2f} MP",
|
|
f"aspect {resolved_aspect} ({actual_ratio:.3f})",
|
|
f"max for aspect {max_width}x{max_height} / {max_actual_mp:.2f} MP",
|
|
f"Krea2 Turbo 2K",
|
|
f"API equivalent {api_aspect_ratio} {api_resolution}",
|
|
]
|
|
if clamped:
|
|
summary_parts.append(f"target {target_mp:.2f} MP clamped to aspect/profile limit")
|
|
summary = "; ".join(summary_parts)
|
|
|
|
config = {
|
|
"profile": profile,
|
|
"width": width,
|
|
"height": height,
|
|
"resolution": resolution,
|
|
"aspect_ratio": resolved_aspect,
|
|
"aspect_ratio_value": actual_ratio,
|
|
"target_megapixels": round(float(target_mp), 4),
|
|
"megapixels": round(actual_mp, 4),
|
|
"max_width_for_aspect": max_width,
|
|
"max_height_for_aspect": max_height,
|
|
"max_megapixels_for_aspect": round(max_actual_mp, 4),
|
|
"api_aspect_ratio": api_aspect_ratio,
|
|
"api_resolution": api_resolution,
|
|
"orientation": orientation,
|
|
"round_to": multiple,
|
|
"clamped": clamped,
|
|
}
|
|
return (
|
|
width,
|
|
height,
|
|
resolution,
|
|
resolved_aspect,
|
|
api_aspect_ratio,
|
|
api_resolution,
|
|
round(actual_mp, 4),
|
|
round(max_actual_mp, 4),
|
|
orientation,
|
|
summary,
|
|
json.dumps(config, ensure_ascii=True, sort_keys=True),
|
|
)
|
|
|
|
|
|
class SxCPCameraControl:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"camera_mode": (camera_mode_choices(), {"default": "handheld_selfie"}),
|
|
"shot_size": (camera_shot_choices(), {"default": "auto"}),
|
|
"angle": (camera_angle_choices(), {"default": "auto"}),
|
|
"lens": (camera_lens_choices(), {"default": "smartphone_wide"}),
|
|
"distance": (camera_distance_choices(), {"default": "arm_length"}),
|
|
"orientation": (camera_orientation_choices(), {"default": "vertical_story"}),
|
|
"phone_visibility": (camera_phone_choices(), {"default": "phone_visible"}),
|
|
"priority": (camera_priority_choices(), {"default": "locked"}),
|
|
"camera_detail": (camera_detail_choices(), {"default": "compact"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CAMERA_CONFIG,)
|
|
RETURN_NAMES = ("camera_config",)
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
camera_mode,
|
|
shot_size,
|
|
angle,
|
|
lens,
|
|
distance,
|
|
orientation,
|
|
phone_visibility,
|
|
priority,
|
|
camera_detail,
|
|
):
|
|
return (
|
|
build_camera_config_json(
|
|
camera_mode=camera_mode,
|
|
shot_size=shot_size,
|
|
angle=angle,
|
|
lens=lens,
|
|
distance=distance,
|
|
orientation=orientation,
|
|
phone_visibility=phone_visibility,
|
|
priority=priority,
|
|
camera_detail=camera_detail,
|
|
),
|
|
)
|
|
|
|
|
|
class SxCPCameraOrbitControl:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"camera_mode": (camera_mode_choices(), {"default": "standard"}),
|
|
"horizontal_angle": ("INT", {"default": 0, "min": 0, "max": 359, "step": 1}),
|
|
"vertical_angle": ("INT", {"default": 0, "min": -90, "max": 90, "step": 1}),
|
|
"zoom": ("FLOAT", {"default": 5.0, "min": 0.0, "max": 10.0, "step": 0.1}),
|
|
"framing": (camera_orbit_framing_choices(), {"default": "from_zoom"}),
|
|
"subject_focus": (camera_orbit_focus_choices(), {"default": "auto"}),
|
|
"lens": (camera_lens_choices(), {"default": "auto"}),
|
|
"orientation": (camera_orientation_choices(), {"default": "auto"}),
|
|
"phone_visibility": (camera_phone_choices(), {"default": "auto"}),
|
|
"priority": (camera_priority_choices(), {"default": "locked"}),
|
|
"camera_detail": (camera_detail_choices(), {"default": "compact"}),
|
|
"include_degrees": ("BOOLEAN", {"default": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CAMERA_CONFIG, "STRING", "STRING")
|
|
RETURN_NAMES = ("camera_config", "camera_prompt", "camera_info_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
enabled,
|
|
camera_mode,
|
|
horizontal_angle,
|
|
vertical_angle,
|
|
zoom,
|
|
framing,
|
|
subject_focus,
|
|
lens,
|
|
orientation,
|
|
phone_visibility,
|
|
priority,
|
|
camera_detail,
|
|
include_degrees,
|
|
):
|
|
config = build_camera_orbit_config_json(
|
|
enabled=enabled,
|
|
camera_mode=camera_mode,
|
|
horizontal_angle=horizontal_angle,
|
|
vertical_angle=vertical_angle,
|
|
zoom=zoom,
|
|
framing=framing,
|
|
subject_focus=subject_focus,
|
|
lens=lens,
|
|
orientation=orientation,
|
|
phone_visibility=phone_visibility,
|
|
priority=priority,
|
|
camera_detail=camera_detail,
|
|
include_degrees=include_degrees,
|
|
)
|
|
parsed = json.loads(config)
|
|
camera_prompt = parsed.get("custom_camera_prompt", "")
|
|
return config, camera_prompt, json.dumps(parsed, ensure_ascii=True, sort_keys=True)
|
|
|
|
|
|
class SxCPQwenCameraTranslator:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"qwen_prompt": ("STRING", {"default": ""}),
|
|
"prefer_camera_info": ("BOOLEAN", {"default": True}),
|
|
"camera_mode": (camera_mode_choices(), {"default": "standard"}),
|
|
"subject_focus": (camera_orbit_focus_choices(), {"default": "auto"}),
|
|
"lens": (camera_lens_choices(), {"default": "auto"}),
|
|
"orientation": (camera_orientation_choices(), {"default": "auto"}),
|
|
"phone_visibility": (camera_phone_choices(), {"default": "auto"}),
|
|
"priority": (camera_priority_choices(), {"default": "locked"}),
|
|
"camera_detail": (camera_detail_choices(), {"default": "compact"}),
|
|
"include_degrees": ("BOOLEAN", {"default": False}),
|
|
"suppress_phone_visibility": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {
|
|
"camera_info": (ANY_TYPE,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CAMERA_CONFIG, "STRING", "STRING")
|
|
RETURN_NAMES = ("camera_config", "camera_prompt", "camera_info_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
qwen_prompt,
|
|
prefer_camera_info,
|
|
camera_mode,
|
|
subject_focus,
|
|
lens,
|
|
orientation,
|
|
phone_visibility,
|
|
priority,
|
|
camera_detail,
|
|
include_degrees,
|
|
suppress_phone_visibility,
|
|
camera_info=None,
|
|
):
|
|
config = build_qwen_camera_config_json(
|
|
qwen_prompt=qwen_prompt or "",
|
|
camera_info=camera_info,
|
|
prefer_camera_info=prefer_camera_info,
|
|
camera_mode=camera_mode,
|
|
subject_focus=subject_focus,
|
|
lens=lens,
|
|
orientation=orientation,
|
|
phone_visibility=phone_visibility,
|
|
priority=priority,
|
|
camera_detail=camera_detail,
|
|
include_degrees=include_degrees,
|
|
suppress_phone_visibility=suppress_phone_visibility,
|
|
)
|
|
parsed = json.loads(config)
|
|
camera_prompt = parsed.get("custom_camera_prompt", "")
|
|
return config, camera_prompt, json.dumps(parsed, ensure_ascii=True, sort_keys=True)
|
|
|
|
|
|
class SxCPCategoryPreset:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"preset": (category_preset_choices(), {"default": "auto_weighted"}),
|
|
"subcategory": (subcategory_choices(), {"default": "random"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CATEGORY_CONFIG, "STRING", "STRING")
|
|
RETURN_NAMES = ("category_config", "category", "subcategory")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(self, preset, subcategory):
|
|
config = build_category_config_json(preset=preset, subcategory=subcategory)
|
|
parsed = json.loads(config)
|
|
return config, parsed["category"], parsed["subcategory"]
|
|
|
|
|
|
class SxCPLocationPool:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"combine_mode": (["replace", "add"], {"default": "replace"}),
|
|
"preset": (location_pool_preset_choices(), {"default": "custom_only"}),
|
|
"custom_locations": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
"optional": {
|
|
"location_config": (SXCP_LOCATION_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_LOCATION_CONFIG, "STRING")
|
|
RETURN_NAMES = ("location_config", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(self, enabled, combine_mode, preset, custom_locations, location_config=""):
|
|
config = build_location_pool_json(
|
|
enabled=enabled,
|
|
combine_mode=combine_mode,
|
|
preset=preset,
|
|
custom_locations=custom_locations or "",
|
|
location_config=location_config or "",
|
|
)
|
|
parsed = json.loads(config)
|
|
return config, parsed.get("summary", "")
|
|
|
|
|
|
class SxCPCompositionPool:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"combine_mode": (["replace", "add"], {"default": "replace"}),
|
|
"preset": (composition_pool_preset_choices(), {"default": "no_outfit_check"}),
|
|
"custom_compositions": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
"optional": {
|
|
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_COMPOSITION_CONFIG, "STRING")
|
|
RETURN_NAMES = ("composition_config", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(self, enabled, combine_mode, preset, custom_compositions, composition_config=""):
|
|
config = build_composition_pool_json(
|
|
enabled=enabled,
|
|
combine_mode=combine_mode,
|
|
preset=preset,
|
|
custom_compositions=custom_compositions or "",
|
|
composition_config=composition_config or "",
|
|
)
|
|
parsed = json.loads(config)
|
|
return config, parsed.get("summary", "")
|
|
|
|
|
|
class SxCPLocationTheme:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"combine_mode": (["replace", "add"], {"default": "replace"}),
|
|
"theme": (location_theme_choices(), {"default": "semi_public_affair"}),
|
|
"custom_locations": ("STRING", {"default": "", "multiline": True}),
|
|
"custom_compositions": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
"optional": {
|
|
"location_config": (SXCP_LOCATION_CONFIG,),
|
|
"composition_config": (SXCP_COMPOSITION_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_LOCATION_CONFIG, SXCP_COMPOSITION_CONFIG, "STRING")
|
|
RETURN_NAMES = ("location_config", "composition_config", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
enabled,
|
|
combine_mode,
|
|
theme,
|
|
custom_locations,
|
|
custom_compositions,
|
|
location_config="",
|
|
composition_config="",
|
|
):
|
|
return build_thematic_location_json(
|
|
enabled=enabled,
|
|
combine_mode=combine_mode,
|
|
theme=theme,
|
|
custom_locations=custom_locations or "",
|
|
custom_compositions=custom_compositions or "",
|
|
location_config=location_config or "",
|
|
composition_config=composition_config or "",
|
|
)
|
|
|
|
|
|
class SxCPCastControl:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"cast_mode": (cast_preset_choices(), {"default": "mixed_couple"}),
|
|
"women_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
|
"men_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CAST_CONFIG, "INT", "INT", "STRING")
|
|
RETURN_NAMES = ("cast_config", "women_count", "men_count", "cast_summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(self, cast_mode, women_count, men_count):
|
|
config = build_cast_config_json(cast_mode=cast_mode, women_count=women_count, men_count=men_count)
|
|
parsed = json.loads(config)
|
|
summary = f"{parsed['women_count']} women, {parsed['men_count']} men"
|
|
return config, parsed["women_count"], parsed["men_count"], summary
|
|
|
|
|
|
class SxCPCastBias:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF, "step": 1}),
|
|
"row_number": ("INT", {"default": 1, "min": 1, "max": 1000000, "step": 1}),
|
|
"women_weights": ("STRING", {"default": "0.60,0.25,0.10,0.05"}),
|
|
"women_start_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
|
"men_weights": ("STRING", {"default": "0.45,0.40,0.10,0.05"}),
|
|
"men_start_count": ("INT", {"default": 0, "min": 0, "max": 12, "step": 1}),
|
|
"empty_behavior": (["force_one_woman", "force_one_man", "allow_empty"], {"default": "force_one_woman"}),
|
|
},
|
|
"optional": {
|
|
"seed_config": (SXCP_SEED_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CAST_CONFIG, "INT", "INT", "STRING")
|
|
RETURN_NAMES = ("cast_config", "women_count", "men_count", "cast_summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
@staticmethod
|
|
def _configured_cast_seed(seed_config):
|
|
if not seed_config:
|
|
return None
|
|
if isinstance(seed_config, dict):
|
|
raw = seed_config
|
|
else:
|
|
try:
|
|
raw = json.loads(str(seed_config))
|
|
except (TypeError, ValueError, json.JSONDecodeError):
|
|
return None
|
|
if not isinstance(raw, dict):
|
|
return None
|
|
for key in ("category_seed", "content_seed", "role_seed", "seed", "global_seed"):
|
|
try:
|
|
value = int(raw.get(key))
|
|
except (TypeError, ValueError):
|
|
continue
|
|
if value >= 0:
|
|
return value
|
|
return None
|
|
|
|
@staticmethod
|
|
def _weight_pairs(weights_text, start_count):
|
|
pairs = []
|
|
start = max(0, min(12, int(start_count)))
|
|
parts = str(weights_text or "").replace("\n", ",").split(",")
|
|
for offset, raw in enumerate(parts):
|
|
count = start + offset
|
|
if count > 12:
|
|
break
|
|
try:
|
|
weight = float(raw.strip())
|
|
except (TypeError, ValueError):
|
|
continue
|
|
if weight > 0:
|
|
pairs.append((count, weight))
|
|
return pairs or [(start, 1.0)]
|
|
|
|
@staticmethod
|
|
def _weighted_count(rng, pairs):
|
|
total = sum(weight for _count, weight in pairs)
|
|
point = rng.random() * total
|
|
upto = 0.0
|
|
for count, weight in pairs:
|
|
upto += weight
|
|
if point <= upto:
|
|
return int(count)
|
|
return int(pairs[-1][0])
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
seed_value = kwargs.get("seed")
|
|
if seed_value is None and args:
|
|
seed_value = args[0]
|
|
seed_config = kwargs.get("seed_config", "")
|
|
if not seed_config and len(args) > 7:
|
|
seed_config = args[7]
|
|
try:
|
|
seed = int(seed_value)
|
|
except (TypeError, ValueError):
|
|
seed = -1
|
|
if seed < 0 and cls._configured_cast_seed(seed_config) is None:
|
|
return random.random()
|
|
return tuple(args), tuple(sorted(kwargs.items()))
|
|
|
|
def build(
|
|
self,
|
|
seed,
|
|
row_number,
|
|
women_weights,
|
|
women_start_count,
|
|
men_weights,
|
|
men_start_count,
|
|
empty_behavior,
|
|
seed_config="",
|
|
):
|
|
configured_seed = self._configured_cast_seed(seed_config)
|
|
if configured_seed is None and int(seed) < 0:
|
|
rng = random.Random(random.getrandbits(64))
|
|
else:
|
|
cast_seed = configured_seed if configured_seed is not None else int(seed)
|
|
rng = random.Random(f"sxcp_cast_bias:{cast_seed}:{int(row_number)}")
|
|
women_pairs = self._weight_pairs(women_weights, women_start_count)
|
|
men_pairs = self._weight_pairs(men_weights, men_start_count)
|
|
women_count = self._weighted_count(rng, women_pairs)
|
|
men_count = self._weighted_count(rng, men_pairs)
|
|
if women_count + men_count == 0:
|
|
if empty_behavior == "force_one_man":
|
|
men_count = 1
|
|
elif empty_behavior != "allow_empty":
|
|
women_count = 1
|
|
config = build_cast_config_json(cast_mode="custom_counts", women_count=women_count, men_count=men_count)
|
|
parsed = json.loads(config)
|
|
summary = f"weighted cast: {parsed['women_count']} women, {parsed['men_count']} men"
|
|
return config, parsed["women_count"], parsed["men_count"], summary
|
|
|
|
|
|
class SxCPGenerationProfile:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"profile": (generation_profile_choices(), {"default": "balanced"}),
|
|
"clothing_override": (["profile_default", "random", "full", "minimal"], {"default": "profile_default"}),
|
|
"poses_override": (["profile_default", "random", "standard", "evocative"], {"default": "profile_default"}),
|
|
"expression_enabled": ("BOOLEAN", {"default": True}),
|
|
"expression_intensity_mode": (["profile_default", "random", "fixed"], {"default": "profile_default"}),
|
|
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
"backside_bias": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
"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_policy": (["profile_default", "prepend_trigger", "do_not_prepend"], {"default": "profile_default"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_GENERATION_PROFILE, "STRING")
|
|
RETURN_NAMES = ("generation_profile", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
profile,
|
|
clothing_override,
|
|
poses_override,
|
|
expression_enabled,
|
|
expression_intensity_mode,
|
|
expression_intensity,
|
|
backside_bias,
|
|
minimal_clothing_ratio,
|
|
standard_pose_ratio,
|
|
trigger_policy,
|
|
):
|
|
config = build_generation_profile_json(
|
|
profile=profile,
|
|
clothing_override=clothing_override,
|
|
poses_override=poses_override,
|
|
expression_enabled=expression_enabled,
|
|
expression_intensity_mode=expression_intensity_mode,
|
|
expression_intensity=expression_intensity,
|
|
backside_bias=backside_bias,
|
|
minimal_clothing_ratio=minimal_clothing_ratio,
|
|
standard_pose_ratio=standard_pose_ratio,
|
|
trigger_policy=trigger_policy,
|
|
)
|
|
parsed = json.loads(config)
|
|
if not parsed.get("expression_enabled", True):
|
|
expression_summary = "expression disabled"
|
|
elif float(parsed.get("expression_intensity", 0.5)) < 0:
|
|
expression_summary = "expression random"
|
|
else:
|
|
expression_summary = f"expression {parsed['expression_intensity']}"
|
|
summary = f"{parsed['profile']}: {parsed['clothing']}, {parsed['poses']}, {expression_summary}"
|
|
return config, summary
|
|
|
|
|
|
class SxCPAdvancedFilters:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"include_european": ("BOOLEAN", {"default": True}),
|
|
"include_mediterranean_mena": ("BOOLEAN", {"default": True}),
|
|
"include_latina": ("BOOLEAN", {"default": True}),
|
|
"include_east_asian": ("BOOLEAN", {"default": True}),
|
|
"include_southeast_asian": ("BOOLEAN", {"default": True}),
|
|
"include_south_asian": ("BOOLEAN", {"default": True}),
|
|
"include_black_african": ("BOOLEAN", {"default": True}),
|
|
"include_indigenous": ("BOOLEAN", {"default": True}),
|
|
"include_mixed": ("BOOLEAN", {"default": True}),
|
|
"include_plus_size": ("BOOLEAN", {"default": True}),
|
|
"figure": (["random", "curvy", "balanced", "bombshell"], {"default": "random"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_FILTER_CONFIG,)
|
|
RETURN_NAMES = ("filter_config",)
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
include_european,
|
|
include_mediterranean_mena,
|
|
include_latina,
|
|
include_east_asian,
|
|
include_southeast_asian,
|
|
include_south_asian,
|
|
include_black_african,
|
|
include_indigenous,
|
|
include_mixed,
|
|
include_plus_size,
|
|
figure,
|
|
):
|
|
return (
|
|
build_filter_config_json(
|
|
figure=figure,
|
|
include_european=include_european,
|
|
include_mediterranean_mena=include_mediterranean_mena,
|
|
include_latina=include_latina,
|
|
include_east_asian=include_east_asian,
|
|
include_southeast_asian=include_southeast_asian,
|
|
include_south_asian=include_south_asian,
|
|
include_black_african=include_black_african,
|
|
include_indigenous=include_indigenous,
|
|
include_mixed=include_mixed,
|
|
include_plus_size=include_plus_size,
|
|
),
|
|
)
|
|
|
|
|
|
class SxCPEthnicityList:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"include_european": ("BOOLEAN", {"default": False}),
|
|
"include_mediterranean_mena": ("BOOLEAN", {"default": False}),
|
|
"include_latina": ("BOOLEAN", {"default": False}),
|
|
"include_east_asian": ("BOOLEAN", {"default": False}),
|
|
"include_southeast_asian": ("BOOLEAN", {"default": False}),
|
|
"include_south_asian": ("BOOLEAN", {"default": False}),
|
|
"include_black_african": ("BOOLEAN", {"default": False}),
|
|
"include_indigenous": ("BOOLEAN", {"default": False}),
|
|
"include_mixed": ("BOOLEAN", {"default": False}),
|
|
"include_asian": ("BOOLEAN", {"default": False}),
|
|
"include_white_asian": ("BOOLEAN", {"default": False}),
|
|
"include_western_european": ("BOOLEAN", {"default": False}),
|
|
"include_french_european": ("BOOLEAN", {"default": False}),
|
|
"include_germanic_european": ("BOOLEAN", {"default": False}),
|
|
"include_nordic_european": ("BOOLEAN", {"default": False}),
|
|
"include_celtic_european": ("BOOLEAN", {"default": False}),
|
|
"include_slavic_european": ("BOOLEAN", {"default": False}),
|
|
"include_baltic_european": ("BOOLEAN", {"default": False}),
|
|
"include_alpine_european": ("BOOLEAN", {"default": False}),
|
|
"include_balkan_european": ("BOOLEAN", {"default": False}),
|
|
"include_greek_mediterranean": ("BOOLEAN", {"default": False}),
|
|
"include_italian_mediterranean": ("BOOLEAN", {"default": False}),
|
|
"include_iberian_mediterranean": ("BOOLEAN", {"default": False}),
|
|
"strict_excludes": ("BOOLEAN", {"default": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_ETHNICITY_LIST, SXCP_FILTER_CONFIG, "STRING")
|
|
RETURN_NAMES = ("ethnicity_list", "filter_config", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
include_european,
|
|
include_mediterranean_mena,
|
|
include_latina,
|
|
include_east_asian,
|
|
include_southeast_asian,
|
|
include_south_asian,
|
|
include_black_african,
|
|
include_indigenous,
|
|
include_mixed,
|
|
include_asian,
|
|
include_white_asian,
|
|
include_western_european,
|
|
include_french_european,
|
|
include_germanic_european,
|
|
include_nordic_european,
|
|
include_celtic_european,
|
|
include_slavic_european,
|
|
include_baltic_european,
|
|
include_alpine_european,
|
|
include_balkan_european,
|
|
include_greek_mediterranean,
|
|
include_italian_mediterranean,
|
|
include_iberian_mediterranean,
|
|
strict_excludes,
|
|
):
|
|
result = build_ethnicity_list_json(
|
|
include_european=include_european,
|
|
include_mediterranean_mena=include_mediterranean_mena,
|
|
include_latina=include_latina,
|
|
include_east_asian=include_east_asian,
|
|
include_southeast_asian=include_southeast_asian,
|
|
include_south_asian=include_south_asian,
|
|
include_black_african=include_black_african,
|
|
include_indigenous=include_indigenous,
|
|
include_mixed=include_mixed,
|
|
include_asian=include_asian,
|
|
include_white_asian=include_white_asian,
|
|
include_western_european=include_western_european,
|
|
include_french_european=include_french_european,
|
|
include_germanic_european=include_germanic_european,
|
|
include_nordic_european=include_nordic_european,
|
|
include_celtic_european=include_celtic_european,
|
|
include_slavic_european=include_slavic_european,
|
|
include_baltic_european=include_baltic_european,
|
|
include_alpine_european=include_alpine_european,
|
|
include_balkan_european=include_balkan_european,
|
|
include_greek_mediterranean=include_greek_mediterranean,
|
|
include_italian_mediterranean=include_italian_mediterranean,
|
|
include_iberian_mediterranean=include_iberian_mediterranean,
|
|
strict_excludes=strict_excludes,
|
|
)
|
|
return result["ethnicity"], result["filter_config"], result["summary"]
|
|
|
|
|
|
class _SxCPHairAxisNode:
|
|
AXIS = "color"
|
|
PREFIX = "include"
|
|
|
|
@classmethod
|
|
def _choices(cls):
|
|
if cls.AXIS == "color":
|
|
return [choice for choice in character_hair_color_choices() if choice != "random"]
|
|
if cls.AXIS == "length":
|
|
return [choice for choice in character_hair_length_choices() if choice != "random"]
|
|
return [choice for choice in character_hair_style_choices() if choice != "random"]
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
required = {
|
|
"combine_mode": (["replace_axis", "add_to_axis"], {"default": "replace_axis"}),
|
|
}
|
|
for choice in cls._choices():
|
|
required[f"{cls.PREFIX}_{choice}"] = ("BOOLEAN", {"default": False})
|
|
return {
|
|
"required": required,
|
|
"optional": {
|
|
"hair_config": (SXCP_HAIR_CONFIG,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_HAIR_CONFIG, "STRING")
|
|
RETURN_NAMES = ("hair_config", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(self, combine_mode="replace_axis", hair_config="", **kwargs):
|
|
selected = [
|
|
choice
|
|
for choice in self._choices()
|
|
if bool(kwargs.get(f"{self.PREFIX}_{choice}", False))
|
|
]
|
|
config = build_hair_config_json(
|
|
hair_config=hair_config or "",
|
|
axis=self.AXIS,
|
|
selected_values=selected,
|
|
combine_mode=combine_mode,
|
|
)
|
|
parsed = json.loads(config)
|
|
return config, parsed.get("summary", "")
|
|
|
|
|
|
class SxCPHairColor(_SxCPHairAxisNode):
|
|
AXIS = "color"
|
|
|
|
|
|
class SxCPHairLength(_SxCPHairAxisNode):
|
|
AXIS = "length"
|
|
|
|
|
|
class SxCPHairStyle(_SxCPHairAxisNode):
|
|
AXIS = "style"
|
|
|
|
|
|
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 SxCPCharacterAgeRange:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"combine_mode": (["replace_axis", "add_to_axis"], {"default": "replace_axis"}),
|
|
"min_age": ("INT", {"default": 21, "min": 21, "max": 85, "step": 1}),
|
|
"max_age": ("INT", {"default": 35, "min": 21, "max": 85, "step": 1}),
|
|
},
|
|
"optional": {
|
|
"characteristics": (SXCP_CHARACTERISTICS,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CHARACTERISTICS, "STRING")
|
|
RETURN_NAMES = ("characteristics", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(self, combine_mode, min_age, max_age, characteristics=""):
|
|
start = max(21, min(85, int(min_age)))
|
|
end = max(21, min(85, int(max_age)))
|
|
if end < start:
|
|
start, end = end, start
|
|
ages = [f"{age}-year-old adult" for age in range(start, end + 1)]
|
|
config = build_characteristics_config_json(
|
|
characteristics=characteristics or "",
|
|
axis="ages",
|
|
selected_values=ages,
|
|
combine_mode=combine_mode,
|
|
)
|
|
return config, json.loads(config).get("summary", "")
|
|
|
|
|
|
class _SxCPBodyPoolNode:
|
|
SUBJECT = "character"
|
|
|
|
@classmethod
|
|
def _choices(cls):
|
|
if cls.SUBJECT == "woman":
|
|
return [choice for choice in character_woman_body_choices() if choice not in ("random", "manual")]
|
|
if cls.SUBJECT == "man":
|
|
return [choice for choice in character_man_body_choices() if choice not in ("random", "manual")]
|
|
return [choice for choice in character_body_choices() if choice not in ("random", "manual")]
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
required = {
|
|
"combine_mode": (["replace_axis", "add_to_axis"], {"default": "replace_axis"}),
|
|
}
|
|
for choice in cls._choices():
|
|
required[_choice_input_key("include", choice)] = ("BOOLEAN", {"default": False})
|
|
return {
|
|
"required": required,
|
|
"optional": {
|
|
"characteristics": (SXCP_CHARACTERISTICS,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CHARACTERISTICS, "STRING")
|
|
RETURN_NAMES = ("characteristics", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(self, combine_mode="replace_axis", characteristics="", **kwargs):
|
|
selected = [
|
|
choice
|
|
for choice in self._choices()
|
|
if bool(kwargs.get(_choice_input_key("include", choice), False))
|
|
]
|
|
config = build_characteristics_config_json(
|
|
characteristics=characteristics or "",
|
|
axis="bodies",
|
|
selected_values=selected,
|
|
combine_mode=combine_mode,
|
|
)
|
|
return config, json.loads(config).get("summary", "")
|
|
|
|
|
|
class SxCPCharacterBodyPool(_SxCPBodyPoolNode):
|
|
SUBJECT = "character"
|
|
|
|
|
|
class SxCPWomanBodyPool(_SxCPBodyPoolNode):
|
|
SUBJECT = "woman"
|
|
|
|
|
|
class SxCPManBodyPool(_SxCPBodyPoolNode):
|
|
SUBJECT = "man"
|
|
|
|
|
|
class SxCPEyeColorPool:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
required = {
|
|
"combine_mode": (["replace_axis", "add_to_axis"], {"default": "replace_axis"}),
|
|
}
|
|
for choice in character_eye_color_choices():
|
|
if choice != "random":
|
|
required[_choice_input_key("include", choice)] = ("BOOLEAN", {"default": False})
|
|
return {
|
|
"required": required,
|
|
"optional": {
|
|
"characteristics": (SXCP_CHARACTERISTICS,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CHARACTERISTICS, "STRING")
|
|
RETURN_NAMES = ("characteristics", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(self, combine_mode="replace_axis", characteristics="", **kwargs):
|
|
selected = [
|
|
choice
|
|
for choice in character_eye_color_choices()
|
|
if choice != "random" and bool(kwargs.get(_choice_input_key("include", choice), False))
|
|
]
|
|
config = build_characteristics_config_json(
|
|
characteristics=characteristics or "",
|
|
axis="eyes",
|
|
selected_values=selected,
|
|
combine_mode=combine_mode,
|
|
)
|
|
return config, json.loads(config).get("summary", "")
|
|
|
|
|
|
class SxCPCharacterClothing:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"combine_mode": (["replace_axis", "add_to_axis"], {"default": "replace_axis"}),
|
|
"softcore_source": (character_softcore_outfit_source_choices(), {"default": "no_change"}),
|
|
"hardcore_state": (character_hardcore_clothing_state_choices(), {"default": "no_change"}),
|
|
"custom_softcore_outfits": ("STRING", {"default": "", "multiline": True}),
|
|
"custom_hardcore_clothing": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
"optional": {
|
|
"characteristics": (SXCP_CHARACTERISTICS,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CHARACTERISTICS, "STRING")
|
|
RETURN_NAMES = ("characteristics", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
combine_mode,
|
|
softcore_source,
|
|
hardcore_state,
|
|
custom_softcore_outfits,
|
|
custom_hardcore_clothing,
|
|
characteristics="",
|
|
):
|
|
config = characteristics or ""
|
|
if softcore_source != "no_change":
|
|
config = build_characteristics_config_json(
|
|
characteristics=config,
|
|
axis="softcore_outfits",
|
|
selected_values=character_softcore_outfit_values(softcore_source, custom_softcore_outfits),
|
|
combine_mode=combine_mode,
|
|
)
|
|
if hardcore_state != "no_change":
|
|
config = build_characteristics_config_json(
|
|
characteristics=config,
|
|
axis="hardcore_clothing",
|
|
selected_values=character_hardcore_clothing_values(hardcore_state, custom_hardcore_clothing),
|
|
combine_mode=combine_mode,
|
|
)
|
|
if not config:
|
|
config = build_characteristics_config_json(axis="", selected_values=[])
|
|
return config, json.loads(config).get("summary", "")
|
|
|
|
|
|
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 SxCPCharacterManualDetails:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"combine_mode": (["merge_nonempty", "replace_all"], {"default": "merge_nonempty"}),
|
|
"manual_age": ("STRING", {"default": ""}),
|
|
"manual_body": ("STRING", {"default": ""}),
|
|
"body_phrase": ("STRING", {"default": ""}),
|
|
"skin": ("STRING", {"default": ""}),
|
|
"hair": ("STRING", {"default": ""}),
|
|
"eyes": ("STRING", {"default": ""}),
|
|
"softcore_outfit": ("STRING", {"default": ""}),
|
|
"hardcore_clothing": ("STRING", {"default": ""}),
|
|
},
|
|
"optional": {
|
|
"manual": (SXCP_CHARACTER_MANUAL,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CHARACTER_MANUAL, "STRING")
|
|
RETURN_NAMES = ("manual", "summary")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
combine_mode,
|
|
manual_age,
|
|
manual_body,
|
|
body_phrase,
|
|
skin,
|
|
hair,
|
|
eyes,
|
|
softcore_outfit,
|
|
hardcore_clothing,
|
|
manual="",
|
|
):
|
|
config = build_character_manual_config_json(
|
|
manual=manual or "",
|
|
combine_mode=combine_mode,
|
|
manual_age=manual_age,
|
|
manual_body=manual_body,
|
|
body_phrase=body_phrase,
|
|
skin=skin,
|
|
hair=hair,
|
|
eyes=eyes,
|
|
softcore_outfit=softcore_outfit,
|
|
hardcore_clothing=hardcore_clothing,
|
|
)
|
|
parsed = json.loads(config)
|
|
return config, parsed.get("summary", "")
|
|
|
|
|
|
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", ""),
|
|
)
|
|
|
|
|
|
class SxCPCharacterSlot:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"subject_type": (["woman", "man"], {"default": "woman"}),
|
|
"label": (character_label_choices(), {"default": "auto_chain"}),
|
|
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
|
"age": ([choice for choice in character_age_choices() if choice != "manual"], {"default": "random"}),
|
|
"ethnicity": (character_ethnicity_choices(), {"default": "random"}),
|
|
"figure": (character_figure_choices(), {"default": "random"}),
|
|
"body": ([choice for choice in character_body_choices() if choice != "manual"], {"default": "random"}),
|
|
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "auto"}),
|
|
"expression_enabled": ("BOOLEAN", {"default": True}),
|
|
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
"presence_mode": (character_presence_choices(), {"default": "visible"}),
|
|
"softcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
"hardcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
},
|
|
"optional": {
|
|
"manual": (SXCP_CHARACTER_MANUAL,),
|
|
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
|
"characteristics": (SXCP_CHARACTERISTICS,),
|
|
"hair_config": (SXCP_HAIR_CONFIG,),
|
|
"character_cast": (SXCP_CHARACTER_CAST,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CHARACTER_CAST, SXCP_CHARACTER_SLOT, "STRING", "STRING")
|
|
RETURN_NAMES = ("character_cast", "character_slot", "summary", "status")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
enabled,
|
|
subject_type,
|
|
label,
|
|
slot_seed,
|
|
age,
|
|
ethnicity,
|
|
figure,
|
|
body,
|
|
descriptor_detail="auto",
|
|
expression_enabled=True,
|
|
expression_intensity=-1.0,
|
|
presence_mode="visible",
|
|
softcore_expression_intensity=-1.0,
|
|
hardcore_expression_intensity=-1.0,
|
|
character_cast="",
|
|
ethnicity_list="",
|
|
characteristics="",
|
|
hair_config="",
|
|
manual="",
|
|
):
|
|
result = build_character_slot_json(
|
|
subject_type=subject_type,
|
|
label=label,
|
|
slot_seed=slot_seed,
|
|
age=age,
|
|
manual=manual,
|
|
ethnicity=ethnicity_list or ethnicity,
|
|
figure=figure,
|
|
body=body,
|
|
manual_body="",
|
|
body_phrase="",
|
|
skin="",
|
|
hair="",
|
|
characteristics=characteristics,
|
|
hair_config=hair_config,
|
|
eyes="",
|
|
descriptor_detail=descriptor_detail,
|
|
expression_enabled=expression_enabled,
|
|
expression_intensity=expression_intensity,
|
|
presence_mode=presence_mode,
|
|
softcore_expression_intensity=softcore_expression_intensity,
|
|
hardcore_expression_intensity=hardcore_expression_intensity,
|
|
softcore_outfit="",
|
|
hardcore_clothing="",
|
|
enabled=enabled,
|
|
character_cast=character_cast or "",
|
|
)
|
|
return result["character_cast"], result["character_slot"], result["summary"], result["status"]
|
|
|
|
|
|
class SxCPWomanSlot:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"label": (character_label_choices(), {"default": "auto_chain"}),
|
|
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
|
"age": ([choice for choice in character_age_choices() if choice != "manual"], {"default": "random"}),
|
|
"ethnicity": (character_ethnicity_choices(), {"default": "random"}),
|
|
"figure_bias": (character_figure_choices(), {"default": "random"}),
|
|
"body": ([choice for choice in character_woman_body_choices() if choice != "manual"], {"default": "random"}),
|
|
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "auto"}),
|
|
"expression_enabled": ("BOOLEAN", {"default": True}),
|
|
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
"softcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
"hardcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
},
|
|
"optional": {
|
|
"manual": (SXCP_CHARACTER_MANUAL,),
|
|
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
|
"characteristics": (SXCP_CHARACTERISTICS,),
|
|
"hair_config": (SXCP_HAIR_CONFIG,),
|
|
"character_cast": (SXCP_CHARACTER_CAST,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CHARACTER_CAST, SXCP_CHARACTER_SLOT, "STRING", "STRING")
|
|
RETURN_NAMES = ("character_cast", "character_slot", "summary", "status")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
enabled,
|
|
label,
|
|
slot_seed,
|
|
age,
|
|
ethnicity,
|
|
figure_bias,
|
|
body,
|
|
descriptor_detail="auto",
|
|
expression_enabled=True,
|
|
expression_intensity=-1.0,
|
|
softcore_expression_intensity=-1.0,
|
|
hardcore_expression_intensity=-1.0,
|
|
character_cast="",
|
|
ethnicity_list="",
|
|
characteristics="",
|
|
hair_config="",
|
|
manual="",
|
|
):
|
|
result = build_character_slot_json(
|
|
subject_type="woman",
|
|
label=label,
|
|
slot_seed=slot_seed,
|
|
age=age,
|
|
manual=manual,
|
|
ethnicity=ethnicity_list or ethnicity,
|
|
figure=figure_bias,
|
|
body=body,
|
|
manual_body="",
|
|
body_phrase="",
|
|
skin="",
|
|
hair="",
|
|
characteristics=characteristics,
|
|
hair_config=hair_config,
|
|
eyes="",
|
|
descriptor_detail=descriptor_detail,
|
|
expression_enabled=expression_enabled,
|
|
expression_intensity=expression_intensity,
|
|
softcore_expression_intensity=softcore_expression_intensity,
|
|
hardcore_expression_intensity=hardcore_expression_intensity,
|
|
softcore_outfit="",
|
|
hardcore_clothing="",
|
|
enabled=enabled,
|
|
character_cast=character_cast or "",
|
|
)
|
|
return result["character_cast"], result["character_slot"], result["summary"], result["status"]
|
|
|
|
|
|
class SxCPManSlot:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"label": (character_label_choices(), {"default": "auto_chain"}),
|
|
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
|
"age": ([choice for choice in character_age_choices() if choice != "manual"], {"default": "random"}),
|
|
"ethnicity": (character_ethnicity_choices(), {"default": "random"}),
|
|
"body": ([choice for choice in character_man_body_choices() if choice != "manual"], {"default": "random"}),
|
|
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "compact"}),
|
|
"expression_enabled": ("BOOLEAN", {"default": True}),
|
|
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
"presence_mode": (character_presence_choices(), {"default": "visible"}),
|
|
"softcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
"hardcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
},
|
|
"optional": {
|
|
"manual": (SXCP_CHARACTER_MANUAL,),
|
|
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
|
"characteristics": (SXCP_CHARACTERISTICS,),
|
|
"hair_config": (SXCP_HAIR_CONFIG,),
|
|
"character_cast": (SXCP_CHARACTER_CAST,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CHARACTER_CAST, SXCP_CHARACTER_SLOT, "STRING", "STRING")
|
|
RETURN_NAMES = ("character_cast", "character_slot", "summary", "status")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
enabled,
|
|
label,
|
|
slot_seed,
|
|
age,
|
|
ethnicity,
|
|
body,
|
|
descriptor_detail="compact",
|
|
expression_enabled=True,
|
|
expression_intensity=-1.0,
|
|
presence_mode="visible",
|
|
softcore_expression_intensity=-1.0,
|
|
hardcore_expression_intensity=-1.0,
|
|
character_cast="",
|
|
ethnicity_list="",
|
|
characteristics="",
|
|
hair_config="",
|
|
manual="",
|
|
):
|
|
result = build_character_slot_json(
|
|
subject_type="man",
|
|
label=label,
|
|
slot_seed=slot_seed,
|
|
age=age,
|
|
manual=manual,
|
|
ethnicity=ethnicity_list or ethnicity,
|
|
figure="random",
|
|
body=body,
|
|
manual_body="",
|
|
body_phrase="",
|
|
skin="",
|
|
hair="",
|
|
characteristics=characteristics,
|
|
hair_config=hair_config,
|
|
eyes="",
|
|
descriptor_detail=descriptor_detail,
|
|
expression_enabled=expression_enabled,
|
|
expression_intensity=expression_intensity,
|
|
presence_mode=presence_mode,
|
|
softcore_expression_intensity=softcore_expression_intensity,
|
|
hardcore_expression_intensity=hardcore_expression_intensity,
|
|
softcore_outfit="",
|
|
hardcore_clothing="",
|
|
enabled=enabled,
|
|
character_cast=character_cast or "",
|
|
)
|
|
return result["character_cast"], result["character_slot"], result["summary"], result["status"]
|
|
|
|
|
|
class SxCPCharacterProfileSave:
|
|
OUTPUT_NODE = True
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"profile_name": ("STRING", {"default": "saved_character"}),
|
|
"source": (["metadata_json", "character_slot", "manual"], {"default": "metadata_json"}),
|
|
"subject_type": (["woman", "man"], {"default": "woman"}),
|
|
"age": ("STRING", {"default": ""}),
|
|
"body": ("STRING", {"default": ""}),
|
|
"body_phrase": ("STRING", {"default": ""}),
|
|
"skin": ("STRING", {"default": ""}),
|
|
"hair": ("STRING", {"default": ""}),
|
|
"eyes": ("STRING", {"default": ""}),
|
|
"figure": ("STRING", {"default": ""}),
|
|
"save_now": ("BOOLEAN", {"default": False}),
|
|
},
|
|
"optional": {
|
|
"metadata_json": ("STRING", {"default": "", "multiline": True}),
|
|
"character_slot": (SXCP_CHARACTER_SLOT,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CHARACTER_PROFILE, "STRING", "STRING", "STRING", "STRING", SXCP_CHARACTER_PROFILE)
|
|
RETURN_NAMES = ("character_profile", "descriptor", "profile_name", "saved_path", "status", "profile_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
profile_name,
|
|
source,
|
|
subject_type,
|
|
age,
|
|
body,
|
|
body_phrase,
|
|
skin,
|
|
hair,
|
|
eyes,
|
|
figure,
|
|
save_now,
|
|
metadata_json="",
|
|
character_slot="",
|
|
):
|
|
profile = build_character_profile_json(
|
|
profile_name=profile_name,
|
|
source=source,
|
|
metadata_json=metadata_json or "",
|
|
character_slot=character_slot or "",
|
|
subject_type=subject_type,
|
|
age=age,
|
|
body=body,
|
|
body_phrase=body_phrase,
|
|
skin=skin,
|
|
hair=hair,
|
|
eyes=eyes,
|
|
figure=figure,
|
|
save_now=save_now,
|
|
)
|
|
result = (
|
|
profile["profile_json"],
|
|
profile["descriptor"],
|
|
profile["profile_name"],
|
|
profile["saved_path"],
|
|
profile["status"],
|
|
profile["profile_json"],
|
|
)
|
|
return {
|
|
"ui": {
|
|
"profile_json": [profile["profile_json"]],
|
|
"descriptor": [profile["descriptor"]],
|
|
"profile_name": [profile["profile_name"]],
|
|
"saved_path": [profile["saved_path"]],
|
|
"status": [profile["status"]],
|
|
},
|
|
"result": result,
|
|
}
|
|
|
|
|
|
class SxCPCharacterProfileLoad:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"enabled": ("BOOLEAN", {"default": True}),
|
|
"profile_name": (character_profile_choices(), {"default": "manual"}),
|
|
"rename_to": ("STRING", {"default": ""}),
|
|
"delete_now": ("BOOLEAN", {"default": False}),
|
|
"rename_now": ("BOOLEAN", {"default": False}),
|
|
},
|
|
"optional": {
|
|
"manual_profile_name": ("STRING", {"default": ""}),
|
|
"fallback_profile_json": (SXCP_CHARACTER_PROFILE,),
|
|
"override_subject_type": (["keep_profile", "woman", "man"], {"default": "keep_profile"}),
|
|
"override_age": ("STRING", {"default": ""}),
|
|
"override_body": ("STRING", {"default": ""}),
|
|
"override_body_phrase": ("STRING", {"default": ""}),
|
|
"override_skin": ("STRING", {"default": ""}),
|
|
"override_hair": ("STRING", {"default": ""}),
|
|
"override_eyes": ("STRING", {"default": ""}),
|
|
"override_figure": ("STRING", {"default": ""}),
|
|
"override_descriptor_detail": (["keep_profile"] + character_descriptor_detail_choices(), {"default": "keep_profile"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_CHARACTER_PROFILE, "STRING", "STRING", "STRING", "STRING", SXCP_CHARACTER_PROFILE)
|
|
RETURN_NAMES = ("character_profile", "descriptor", "profile_name", "saved_path", "status", "profile_json")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
enabled,
|
|
profile_name,
|
|
rename_to,
|
|
delete_now,
|
|
rename_now,
|
|
manual_profile_name="",
|
|
fallback_profile_json="",
|
|
override_subject_type="keep_profile",
|
|
override_age="",
|
|
override_body="",
|
|
override_body_phrase="",
|
|
override_skin="",
|
|
override_hair="",
|
|
override_eyes="",
|
|
override_figure="",
|
|
override_descriptor_detail="keep_profile",
|
|
):
|
|
chosen_name = manual_profile_name.strip() if profile_name == "manual" and manual_profile_name.strip() else profile_name
|
|
profile = load_character_profile_json(
|
|
profile_name=chosen_name,
|
|
fallback_profile_json=fallback_profile_json or "",
|
|
enabled=enabled,
|
|
delete_now=delete_now,
|
|
rename_now=rename_now,
|
|
rename_to=rename_to,
|
|
override_subject_type=override_subject_type,
|
|
override_age=override_age,
|
|
override_body=override_body,
|
|
override_body_phrase=override_body_phrase,
|
|
override_skin=override_skin,
|
|
override_hair=override_hair,
|
|
override_eyes=override_eyes,
|
|
override_figure=override_figure,
|
|
override_descriptor_detail=override_descriptor_detail,
|
|
)
|
|
return (
|
|
profile["profile_json"],
|
|
profile["descriptor"],
|
|
profile["profile_name"],
|
|
profile["saved_path"],
|
|
profile["status"],
|
|
profile["profile_json"],
|
|
)
|
|
|
|
|
|
class SxCPCaptionNaturalizer:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"source_text": ("STRING", {"default": "", "multiline": True}),
|
|
"input_hint": (["auto", "metadata_json", "caption_or_prompt"], {"default": "auto"}),
|
|
"detail_level": (["balanced", "concise", "dense"], {"default": "balanced"}),
|
|
"style_policy": (["drop_style_tail", "keep_style_terms"], {"default": "drop_style_tail"}),
|
|
"trigger": ("STRING", {"default": "sxcppnl7"}),
|
|
"include_trigger": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {
|
|
"source_text_input": ("STRING", {"forceInput": True}),
|
|
"metadata_json": ("STRING", {"forceInput": True}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING")
|
|
RETURN_NAMES = ("natural_caption", "method")
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
source_text,
|
|
input_hint,
|
|
detail_level,
|
|
style_policy,
|
|
trigger,
|
|
include_trigger,
|
|
source_text_input="",
|
|
metadata_json="",
|
|
):
|
|
active_source_text = source_text_input or source_text or ""
|
|
return naturalize_caption(
|
|
source_text=active_source_text,
|
|
metadata_json=metadata_json or "",
|
|
input_hint=input_hint,
|
|
trigger=trigger,
|
|
include_trigger=include_trigger,
|
|
detail_level=detail_level,
|
|
style_policy=style_policy,
|
|
)
|
|
|
|
|
|
class SxCPKrea2Formatter:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"source_text": ("STRING", {"default": "", "multiline": True}),
|
|
"input_hint": (["auto", "metadata_json", "prompt"], {"default": "auto"}),
|
|
"target": (["auto", "single", "softcore", "hardcore"], {"default": "auto"}),
|
|
"detail_level": (["balanced", "concise", "dense"], {"default": "balanced"}),
|
|
"style_mode": (["preserve", "photographic", "minimal"], {"default": "preserve"}),
|
|
"preserve_trigger": ("BOOLEAN", {"default": False}),
|
|
},
|
|
"optional": {
|
|
"metadata_json": ("STRING", {"default": "", "multiline": True}),
|
|
"negative_prompt": ("STRING", {"default": "", "multiline": True}),
|
|
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
|
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
|
|
RETURN_NAMES = (
|
|
"krea_prompt",
|
|
"negative_prompt",
|
|
"krea_softcore_prompt",
|
|
"krea_hardcore_prompt",
|
|
"softcore_negative_prompt",
|
|
"hardcore_negative_prompt",
|
|
"method",
|
|
)
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
source_text,
|
|
input_hint,
|
|
target,
|
|
detail_level,
|
|
style_mode,
|
|
preserve_trigger,
|
|
metadata_json="",
|
|
negative_prompt="",
|
|
extra_positive="",
|
|
extra_negative="",
|
|
):
|
|
row = format_krea2_prompt(
|
|
source_text=source_text or "",
|
|
metadata_json=metadata_json or "",
|
|
negative_prompt=negative_prompt or "",
|
|
input_hint=input_hint,
|
|
target=target,
|
|
detail_level=detail_level,
|
|
style_mode=style_mode,
|
|
preserve_trigger=preserve_trigger,
|
|
extra_positive=extra_positive or "",
|
|
extra_negative=extra_negative or "",
|
|
)
|
|
return (
|
|
row["krea_prompt"],
|
|
row["negative_prompt"],
|
|
row["krea_softcore_prompt"],
|
|
row["krea_hardcore_prompt"],
|
|
row["softcore_negative_prompt"],
|
|
row["hardcore_negative_prompt"],
|
|
row["method"],
|
|
)
|
|
|
|
|
|
class SxCPSDXLFormatter:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"source_text": ("STRING", {"default": "", "multiline": True}),
|
|
"input_hint": (["auto", "metadata_json", "prompt"], {"default": "auto"}),
|
|
"target": (["auto", "single", "softcore", "hardcore"], {"default": "auto"}),
|
|
"style_preset": (sdxl_style_preset_choices(), {"default": "flat_vector_pony"}),
|
|
"quality_preset": (sdxl_quality_preset_choices(), {"default": "pony_high"}),
|
|
"trigger": ("STRING", {"default": "mythp0rt", "multiline": False}),
|
|
"prepend_trigger_to_prompt": ("BOOLEAN", {"default": True}),
|
|
"preserve_trigger": ("BOOLEAN", {"default": False}),
|
|
"nude_weight": ("FLOAT", {"default": 1.29, "min": 0.1, "max": 3.0, "step": 0.01}),
|
|
},
|
|
"optional": {
|
|
"source_text_input": ("STRING", {"forceInput": True}),
|
|
"metadata_json": ("STRING", {"forceInput": True}),
|
|
"negative_prompt": ("STRING", {"forceInput": True}),
|
|
"custom_style": ("STRING", {"default": "", "multiline": True}),
|
|
"custom_quality": ("STRING", {"default": "", "multiline": True}),
|
|
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
|
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
|
|
RETURN_NAMES = (
|
|
"sdxl_prompt",
|
|
"negative_prompt",
|
|
"sdxl_softcore_prompt",
|
|
"sdxl_hardcore_prompt",
|
|
"softcore_negative_prompt",
|
|
"hardcore_negative_prompt",
|
|
"method",
|
|
)
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
source_text,
|
|
input_hint,
|
|
target,
|
|
style_preset,
|
|
quality_preset,
|
|
trigger,
|
|
prepend_trigger_to_prompt,
|
|
preserve_trigger,
|
|
nude_weight,
|
|
source_text_input="",
|
|
metadata_json="",
|
|
negative_prompt="",
|
|
custom_style="",
|
|
custom_quality="",
|
|
extra_positive="",
|
|
extra_negative="",
|
|
):
|
|
active_source_text = source_text_input or source_text or ""
|
|
row = format_sdxl_prompt(
|
|
source_text=active_source_text,
|
|
metadata_json=metadata_json or "",
|
|
negative_prompt=negative_prompt or "",
|
|
input_hint=input_hint,
|
|
target=target,
|
|
style_preset=style_preset,
|
|
quality_preset=quality_preset,
|
|
trigger=trigger,
|
|
prepend_trigger=prepend_trigger_to_prompt,
|
|
preserve_trigger=preserve_trigger,
|
|
nude_weight=nude_weight,
|
|
custom_style=custom_style or "",
|
|
custom_quality=custom_quality or "",
|
|
extra_positive=extra_positive or "",
|
|
extra_negative=extra_negative or "",
|
|
)
|
|
return (
|
|
row["sdxl_prompt"],
|
|
row["negative_prompt"],
|
|
row["sdxl_softcore_prompt"],
|
|
row["sdxl_hardcore_prompt"],
|
|
row["softcore_negative_prompt"],
|
|
row["hardcore_negative_prompt"],
|
|
row["method"],
|
|
)
|
|
|
|
|
|
class SxCPInstaOFOptions:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"softcore_cast": (["solo", "same_as_hardcore"], {"default": "solo"}),
|
|
"hardcore_cast": (["use_counts", "couple", "threesome", "group"], {"default": "use_counts"}),
|
|
"hardcore_women_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
|
"hardcore_men_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}),
|
|
"softcore_level": (["social_tease", "lingerie_tease", "implied_nude", "explicit_tease", "explicit_nude"], {"default": "lingerie_tease"}),
|
|
"hardcore_level": (["explicit", "hardcore"], {"default": "hardcore"}),
|
|
"softcore_expression_enabled": ("BOOLEAN", {"default": True}),
|
|
"hardcore_expression_enabled": ("BOOLEAN", {"default": True}),
|
|
"softcore_expression_intensity": ("FLOAT", {"default": 0.45, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"hardcore_expression_intensity": ("FLOAT", {"default": 0.85, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"platform_style": (["hybrid", "instagram", "onlyfans"], {"default": "hybrid"}),
|
|
"continuity": (["same_creator_same_room", "same_creator_new_scene"], {"default": "same_creator_same_room"}),
|
|
"hardcore_clothing_continuity": (["none", "same_outfit", "partially_removed", "implied_nude", "explicit_nude"], {"default": "partially_removed"}),
|
|
"softcore_camera_mode": (["from_camera_config"] + camera_mode_choices(), {"default": "handheld_selfie"}),
|
|
"hardcore_camera_mode": (["from_camera_config", "same_as_softcore"] + camera_mode_choices(), {"default": "from_camera_config"}),
|
|
"camera_detail": (["from_camera_config"] + camera_detail_choices(), {"default": "from_camera_config"}),
|
|
"hardcore_detail_density": (hardcore_detail_density_choices(), {"default": "balanced"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (SXCP_INSTA_OF_OPTIONS,)
|
|
RETURN_NAMES = ("options_json",)
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
softcore_cast,
|
|
hardcore_cast,
|
|
hardcore_women_count,
|
|
hardcore_men_count,
|
|
softcore_level,
|
|
hardcore_level,
|
|
softcore_expression_enabled,
|
|
hardcore_expression_enabled,
|
|
softcore_expression_intensity,
|
|
hardcore_expression_intensity,
|
|
platform_style,
|
|
continuity,
|
|
hardcore_clothing_continuity,
|
|
softcore_camera_mode,
|
|
hardcore_camera_mode,
|
|
camera_detail,
|
|
hardcore_detail_density,
|
|
):
|
|
return (
|
|
build_insta_of_options_json(
|
|
softcore_cast=softcore_cast,
|
|
hardcore_cast=hardcore_cast,
|
|
hardcore_women_count=hardcore_women_count,
|
|
hardcore_men_count=hardcore_men_count,
|
|
softcore_level=softcore_level,
|
|
hardcore_level=hardcore_level,
|
|
softcore_expression_enabled=softcore_expression_enabled,
|
|
hardcore_expression_enabled=hardcore_expression_enabled,
|
|
softcore_expression_intensity=softcore_expression_intensity,
|
|
hardcore_expression_intensity=hardcore_expression_intensity,
|
|
platform_style=platform_style,
|
|
continuity=continuity,
|
|
hardcore_clothing_continuity=hardcore_clothing_continuity,
|
|
softcore_camera_mode=softcore_camera_mode,
|
|
hardcore_camera_mode=hardcore_camera_mode,
|
|
camera_detail=camera_detail,
|
|
hardcore_detail_density=hardcore_detail_density,
|
|
),
|
|
)
|
|
|
|
|
|
class SxCPInstaOFPromptPair:
|
|
@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}),
|
|
"ethnicity": (ethnicity_choices(), {"default": "any"}),
|
|
"figure": (["random", "curvy", "balanced", "bombshell"], {"default": "random"}),
|
|
"trigger": ("STRING", {"default": "sxcpinup_coloredpencil"}),
|
|
"prepend_trigger_to_prompt": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {
|
|
"seed_config": (SXCP_SEED_CONFIG,),
|
|
"options_json": (SXCP_INSTA_OF_OPTIONS,),
|
|
"filter_config": (SXCP_FILTER_CONFIG,),
|
|
"ethnicity_list": (SXCP_ETHNICITY_LIST,),
|
|
"camera_config": (SXCP_CAMERA_CONFIG,),
|
|
"softcore_camera_config": (SXCP_CAMERA_CONFIG,),
|
|
"hardcore_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", "STRING", "STRING")
|
|
RETURN_NAMES = (
|
|
"softcore_prompt",
|
|
"hardcore_prompt",
|
|
"softcore_negative_prompt",
|
|
"hardcore_negative_prompt",
|
|
"softcore_caption",
|
|
"hardcore_caption",
|
|
"shared_descriptor",
|
|
"metadata_json",
|
|
)
|
|
FUNCTION = "build"
|
|
CATEGORY = "prompt_builder"
|
|
|
|
def build(
|
|
self,
|
|
row_number,
|
|
start_index,
|
|
seed,
|
|
ethnicity,
|
|
figure,
|
|
trigger,
|
|
prepend_trigger_to_prompt,
|
|
seed_config="",
|
|
options_json="",
|
|
filter_config="",
|
|
ethnicity_list="",
|
|
camera_config="",
|
|
softcore_camera_config="",
|
|
hardcore_camera_config="",
|
|
location_config="",
|
|
composition_config="",
|
|
character_profile="",
|
|
character_cast="",
|
|
hardcore_position_config="",
|
|
extra_positive="",
|
|
extra_negative="",
|
|
no_plus_women=False,
|
|
no_black=False,
|
|
):
|
|
row = build_insta_of_pair(
|
|
row_number=row_number,
|
|
start_index=start_index,
|
|
seed=seed,
|
|
ethnicity=ethnicity,
|
|
figure=figure,
|
|
no_plus_women=no_plus_women,
|
|
no_black=no_black,
|
|
trigger=trigger,
|
|
prepend_trigger_to_prompt=prepend_trigger_to_prompt,
|
|
seed_config=seed_config or "",
|
|
options_json=options_json or "",
|
|
filter_config=ethnicity_list or filter_config or "",
|
|
camera_config=camera_config or "",
|
|
softcore_camera_config=softcore_camera_config or "",
|
|
hardcore_camera_config=hardcore_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["softcore_prompt"],
|
|
row["hardcore_prompt"],
|
|
row["softcore_negative_prompt"],
|
|
row["hardcore_negative_prompt"],
|
|
row["softcore_caption"],
|
|
row["hardcore_caption"],
|
|
row["shared_descriptor"],
|
|
json.dumps(row, ensure_ascii=True, sort_keys=True),
|
|
)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"SxCPPromptBuilder": SxCPPromptBuilder,
|
|
"SxCPGlobalSeed": SxCPGlobalSeed,
|
|
"SxCPSeedControl": SxCPSeedControl,
|
|
"SxCPSeedLocker": SxCPSeedLocker,
|
|
"SxCPSDXLBucketSize": SxCPSDXLBucketSize,
|
|
"SxCPKrea2ResolutionSelector": SxCPKrea2ResolutionSelector,
|
|
"SxCPCameraControl": SxCPCameraControl,
|
|
"SxCPCameraOrbitControl": SxCPCameraOrbitControl,
|
|
"SxCPQwenCameraTranslator": SxCPQwenCameraTranslator,
|
|
"SxCPCategoryPreset": SxCPCategoryPreset,
|
|
"SxCPLocationPool": SxCPLocationPool,
|
|
"SxCPCompositionPool": SxCPCompositionPool,
|
|
"SxCPLocationTheme": SxCPLocationTheme,
|
|
"SxCPCastControl": SxCPCastControl,
|
|
"SxCPCastBias": SxCPCastBias,
|
|
"SxCPGenerationProfile": SxCPGenerationProfile,
|
|
"SxCPEthnicityList": SxCPEthnicityList,
|
|
"SxCPHairLength": SxCPHairLength,
|
|
"SxCPHairColor": SxCPHairColor,
|
|
"SxCPHairStyle": SxCPHairStyle,
|
|
"SxCPCharacterAgeRange": SxCPCharacterAgeRange,
|
|
"SxCPCharacterBodyPool": SxCPCharacterBodyPool,
|
|
"SxCPWomanBodyPool": SxCPWomanBodyPool,
|
|
"SxCPManBodyPool": SxCPManBodyPool,
|
|
"SxCPEyeColorPool": SxCPEyeColorPool,
|
|
"SxCPCharacterClothing": SxCPCharacterClothing,
|
|
"SxCPHardcorePositionPool": SxCPHardcorePositionPool,
|
|
"SxCPHardcoreActionFilter": SxCPHardcoreActionFilter,
|
|
"SxCPCharacterManualDetails": SxCPCharacterManualDetails,
|
|
"SxCPAdvancedFilters": SxCPAdvancedFilters,
|
|
"SxCPPromptBuilderFromConfigs": SxCPPromptBuilderFromConfigs,
|
|
"SxCPWomanSlot": SxCPWomanSlot,
|
|
"SxCPManSlot": SxCPManSlot,
|
|
"SxCPCharacterSlot": SxCPCharacterSlot,
|
|
"SxCPCharacterProfileSave": SxCPCharacterProfileSave,
|
|
"SxCPCharacterProfileLoad": SxCPCharacterProfileLoad,
|
|
"SxCPCaptionNaturalizer": SxCPCaptionNaturalizer,
|
|
"SxCPKrea2Formatter": SxCPKrea2Formatter,
|
|
"SxCPSDXLFormatter": SxCPSDXLFormatter,
|
|
"SxCPInstaOFOptions": SxCPInstaOFOptions,
|
|
"SxCPInstaOFPromptPair": SxCPInstaOFPromptPair,
|
|
}
|
|
NODE_CLASS_MAPPINGS.update(LOOP_NODE_CLASS_MAPPINGS)
|
|
_install_input_tooltips(NODE_CLASS_MAPPINGS)
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"SxCPPromptBuilder": "SxCP Prompt Builder",
|
|
"SxCPGlobalSeed": "SxCP Global Seed",
|
|
"SxCPSeedControl": "SxCP Seed Control",
|
|
"SxCPSeedLocker": "SxCP Seed Locker",
|
|
"SxCPSDXLBucketSize": "SxCP SDXL Bucket Size",
|
|
"SxCPKrea2ResolutionSelector": "SxCP Krea2 Resolution Selector",
|
|
"SxCPCameraControl": "SxCP Camera Control",
|
|
"SxCPCameraOrbitControl": "SxCP Camera Orbit Control",
|
|
"SxCPQwenCameraTranslator": "SxCP Qwen Camera Translator",
|
|
"SxCPCategoryPreset": "SxCP Category Preset",
|
|
"SxCPLocationPool": "SxCP Location Pool",
|
|
"SxCPCompositionPool": "SxCP Composition Pool",
|
|
"SxCPLocationTheme": "SxCP Location Theme",
|
|
"SxCPCastControl": "SxCP Cast Control",
|
|
"SxCPCastBias": "SxCP Cast Bias",
|
|
"SxCPGenerationProfile": "SxCP Generation Profile",
|
|
"SxCPEthnicityList": "SxCP Ethnicity List",
|
|
"SxCPHairLength": "SxCP Hair Length",
|
|
"SxCPHairColor": "SxCP Hair Color",
|
|
"SxCPHairStyle": "SxCP Hair Style/Cut",
|
|
"SxCPCharacterAgeRange": "SxCP Character Age Range",
|
|
"SxCPCharacterBodyPool": "SxCP Character Body Pool",
|
|
"SxCPWomanBodyPool": "SxCP Woman Body Pool",
|
|
"SxCPManBodyPool": "SxCP Man Body Pool",
|
|
"SxCPEyeColorPool": "SxCP Eye Color Pool",
|
|
"SxCPCharacterClothing": "SxCP Character Clothing",
|
|
"SxCPHardcorePositionPool": "SxCP Hardcore Position Pool",
|
|
"SxCPHardcoreActionFilter": "SxCP Hardcore Action Filter",
|
|
"SxCPCharacterManualDetails": "SxCP Character Manual Details",
|
|
"SxCPAdvancedFilters": "SxCP Advanced Filters",
|
|
"SxCPPromptBuilderFromConfigs": "SxCP Prompt Builder From Configs",
|
|
"SxCPWomanSlot": "SxCP Woman Slot",
|
|
"SxCPManSlot": "SxCP Man Slot",
|
|
"SxCPCharacterSlot": "SxCP Character Slot",
|
|
"SxCPCharacterProfileSave": "SxCP Character Profile Save",
|
|
"SxCPCharacterProfileLoad": "SxCP Character Profile Load",
|
|
"SxCPCaptionNaturalizer": "SxCP Caption Naturalizer",
|
|
"SxCPKrea2Formatter": "SxCP Krea2 Formatter",
|
|
"SxCPSDXLFormatter": "SxCP SDXL Formatter",
|
|
"SxCPInstaOFOptions": "SxCP Insta/OF Options",
|
|
"SxCPInstaOFPromptPair": "SxCP Insta/OF Prompt Pair",
|
|
}
|
|
NODE_DISPLAY_NAME_MAPPINGS.update(LOOP_NODE_DISPLAY_NAME_MAPPINGS)
|
|
|
|
WEB_DIRECTORY = "./web"
|
|
|
|
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|