Extract camera utility nodes
This commit is contained in:
+10
-204
@@ -393,7 +393,6 @@ def _install_input_tooltips(node_classes: dict[str, type]) -> None:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from .loop_nodes import (
|
from .loop_nodes import (
|
||||||
ANY_TYPE,
|
|
||||||
LOOP_NODE_CLASS_MAPPINGS,
|
LOOP_NODE_CLASS_MAPPINGS,
|
||||||
LOOP_NODE_DISPLAY_NAME_MAPPINGS,
|
LOOP_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
accumulator_delete_entries,
|
accumulator_delete_entries,
|
||||||
@@ -401,14 +400,15 @@ try:
|
|||||||
accumulator_move_entry,
|
accumulator_move_entry,
|
||||||
accumulator_save_entries,
|
accumulator_save_entries,
|
||||||
)
|
)
|
||||||
|
from .node_camera import (
|
||||||
|
NODE_CLASS_MAPPINGS as CAMERA_NODE_CLASS_MAPPINGS,
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS as CAMERA_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
|
)
|
||||||
from .node_seed_resolution import (
|
from .node_seed_resolution import (
|
||||||
NODE_CLASS_MAPPINGS as SEED_RESOLUTION_NODE_CLASS_MAPPINGS,
|
NODE_CLASS_MAPPINGS as SEED_RESOLUTION_NODE_CLASS_MAPPINGS,
|
||||||
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
)
|
)
|
||||||
from .prompt_builder import (
|
from .prompt_builder import (
|
||||||
build_camera_config_json,
|
|
||||||
build_camera_orbit_config_json,
|
|
||||||
build_qwen_camera_config_json,
|
|
||||||
build_cast_config_json,
|
build_cast_config_json,
|
||||||
build_category_config_json,
|
build_category_config_json,
|
||||||
build_character_slot_json,
|
build_character_slot_json,
|
||||||
@@ -428,17 +428,8 @@ try:
|
|||||||
build_insta_of_pair,
|
build_insta_of_pair,
|
||||||
build_prompt,
|
build_prompt,
|
||||||
build_prompt_from_configs,
|
build_prompt_from_configs,
|
||||||
camera_angle_choices,
|
|
||||||
camera_detail_choices,
|
camera_detail_choices,
|
||||||
camera_distance_choices,
|
|
||||||
camera_lens_choices,
|
|
||||||
camera_mode_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,
|
cast_preset_choices,
|
||||||
category_preset_choices,
|
category_preset_choices,
|
||||||
category_choices,
|
category_choices,
|
||||||
@@ -478,7 +469,6 @@ try:
|
|||||||
from .sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices
|
from .sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from loop_nodes import (
|
from loop_nodes import (
|
||||||
ANY_TYPE,
|
|
||||||
LOOP_NODE_CLASS_MAPPINGS,
|
LOOP_NODE_CLASS_MAPPINGS,
|
||||||
LOOP_NODE_DISPLAY_NAME_MAPPINGS,
|
LOOP_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
accumulator_delete_entries,
|
accumulator_delete_entries,
|
||||||
@@ -486,14 +476,15 @@ except ImportError:
|
|||||||
accumulator_move_entry,
|
accumulator_move_entry,
|
||||||
accumulator_save_entries,
|
accumulator_save_entries,
|
||||||
)
|
)
|
||||||
|
from node_camera import (
|
||||||
|
NODE_CLASS_MAPPINGS as CAMERA_NODE_CLASS_MAPPINGS,
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS as CAMERA_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
|
)
|
||||||
from node_seed_resolution import (
|
from node_seed_resolution import (
|
||||||
NODE_CLASS_MAPPINGS as SEED_RESOLUTION_NODE_CLASS_MAPPINGS,
|
NODE_CLASS_MAPPINGS as SEED_RESOLUTION_NODE_CLASS_MAPPINGS,
|
||||||
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
NODE_DISPLAY_NAME_MAPPINGS as SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS,
|
||||||
)
|
)
|
||||||
from prompt_builder import (
|
from prompt_builder import (
|
||||||
build_camera_config_json,
|
|
||||||
build_camera_orbit_config_json,
|
|
||||||
build_qwen_camera_config_json,
|
|
||||||
build_cast_config_json,
|
build_cast_config_json,
|
||||||
build_category_config_json,
|
build_category_config_json,
|
||||||
build_character_slot_json,
|
build_character_slot_json,
|
||||||
@@ -513,17 +504,8 @@ except ImportError:
|
|||||||
build_insta_of_pair,
|
build_insta_of_pair,
|
||||||
build_prompt,
|
build_prompt,
|
||||||
build_prompt_from_configs,
|
build_prompt_from_configs,
|
||||||
camera_angle_choices,
|
|
||||||
camera_detail_choices,
|
camera_detail_choices,
|
||||||
camera_distance_choices,
|
|
||||||
camera_lens_choices,
|
|
||||||
camera_mode_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,
|
cast_preset_choices,
|
||||||
category_preset_choices,
|
category_preset_choices,
|
||||||
category_choices,
|
category_choices,
|
||||||
@@ -754,178 +736,6 @@ class SxCPPromptBuilder:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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:
|
class SxCPCategoryPreset:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
@@ -2671,10 +2481,8 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"SxCPPromptBuilder": SxCPPromptBuilder,
|
"SxCPPromptBuilder": SxCPPromptBuilder,
|
||||||
}
|
}
|
||||||
NODE_CLASS_MAPPINGS.update(SEED_RESOLUTION_NODE_CLASS_MAPPINGS)
|
NODE_CLASS_MAPPINGS.update(SEED_RESOLUTION_NODE_CLASS_MAPPINGS)
|
||||||
|
NODE_CLASS_MAPPINGS.update(CAMERA_NODE_CLASS_MAPPINGS)
|
||||||
NODE_CLASS_MAPPINGS.update({
|
NODE_CLASS_MAPPINGS.update({
|
||||||
"SxCPCameraControl": SxCPCameraControl,
|
|
||||||
"SxCPCameraOrbitControl": SxCPCameraOrbitControl,
|
|
||||||
"SxCPQwenCameraTranslator": SxCPQwenCameraTranslator,
|
|
||||||
"SxCPCategoryPreset": SxCPCategoryPreset,
|
"SxCPCategoryPreset": SxCPCategoryPreset,
|
||||||
"SxCPLocationPool": SxCPLocationPool,
|
"SxCPLocationPool": SxCPLocationPool,
|
||||||
"SxCPCompositionPool": SxCPCompositionPool,
|
"SxCPCompositionPool": SxCPCompositionPool,
|
||||||
@@ -2715,10 +2523,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
|||||||
"SxCPPromptBuilder": "SxCP Prompt Builder",
|
"SxCPPromptBuilder": "SxCP Prompt Builder",
|
||||||
}
|
}
|
||||||
NODE_DISPLAY_NAME_MAPPINGS.update(SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS)
|
NODE_DISPLAY_NAME_MAPPINGS.update(SEED_RESOLUTION_NODE_DISPLAY_NAME_MAPPINGS)
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS.update(CAMERA_NODE_DISPLAY_NAME_MAPPINGS)
|
||||||
NODE_DISPLAY_NAME_MAPPINGS.update({
|
NODE_DISPLAY_NAME_MAPPINGS.update({
|
||||||
"SxCPCameraControl": "SxCP Camera Control",
|
|
||||||
"SxCPCameraOrbitControl": "SxCP Camera Orbit Control",
|
|
||||||
"SxCPQwenCameraTranslator": "SxCP Qwen Camera Translator",
|
|
||||||
"SxCPCategoryPreset": "SxCP Category Preset",
|
"SxCPCategoryPreset": "SxCP Category Preset",
|
||||||
"SxCPLocationPool": "SxCP Location Pool",
|
"SxCPLocationPool": "SxCP Location Pool",
|
||||||
"SxCPCompositionPool": "SxCP Composition Pool",
|
"SxCPCompositionPool": "SxCP Composition Pool",
|
||||||
|
|||||||
@@ -272,7 +272,8 @@ Improve later:
|
|||||||
|
|
||||||
### Node / UI Path
|
### Node / UI Path
|
||||||
|
|
||||||
Owner: `__init__.py`, `node_seed_resolution.py`, `loop_nodes.py`, `web/*.js`.
|
Owner: `__init__.py`, `node_seed_resolution.py`, `node_camera.py`,
|
||||||
|
`loop_nodes.py`, `web/*.js`.
|
||||||
|
|
||||||
Keep here:
|
Keep here:
|
||||||
|
|
||||||
@@ -281,11 +282,14 @@ Keep here:
|
|||||||
- button actions;
|
- button actions;
|
||||||
- dynamic input slots.
|
- dynamic input slots.
|
||||||
- seed and resolution utility node declarations in `node_seed_resolution.py`.
|
- seed and resolution utility node declarations in `node_seed_resolution.py`.
|
||||||
|
- camera utility node declarations in `node_camera.py`.
|
||||||
|
|
||||||
Already isolated:
|
Already isolated:
|
||||||
|
|
||||||
- seed/global-seed/seed-locker and SDXL/Krea2 resolution utility nodes live in
|
- seed/global-seed/seed-locker and SDXL/Krea2 resolution utility nodes live in
|
||||||
`node_seed_resolution.py`, with registration maps imported by `__init__.py`.
|
`node_seed_resolution.py`, with registration maps imported by `__init__.py`.
|
||||||
|
- camera/orbit/Qwen translator utility nodes live in `node_camera.py`, with
|
||||||
|
registration maps imported by `__init__.py`.
|
||||||
|
|
||||||
Improve later:
|
Improve later:
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ When a result is wrong, first identify which layer owns the bad text:
|
|||||||
- Raw builder prompt acceptable, SDXL tags wrong: edit `sdxl_formatter.py`.
|
- Raw builder prompt acceptable, SDXL tags wrong: edit `sdxl_formatter.py`.
|
||||||
- Natural caption/training caption wrong: edit `caption_naturalizer.py`.
|
- Natural caption/training caption wrong: edit `caption_naturalizer.py`.
|
||||||
- UI/preview/loop behavior wrong: edit `__init__.py`, node family modules such
|
- UI/preview/loop behavior wrong: edit `__init__.py`, node family modules such
|
||||||
as `node_seed_resolution.py`, `loop_nodes.py`, or `web/*.js`.
|
as `node_seed_resolution.py` or `node_camera.py`, `loop_nodes.py`, or
|
||||||
|
`web/*.js`.
|
||||||
|
|
||||||
## High-Level Routes
|
## High-Level Routes
|
||||||
|
|
||||||
@@ -692,6 +693,7 @@ These do not own prompt pool wording, but they affect execution and review:
|
|||||||
| Accumulator | `loop_nodes.py`, `web/accumulator_preview.js` | Stores generated values/images during workflow execution and previews/reorders/deletes them. |
|
| Accumulator | `loop_nodes.py`, `web/accumulator_preview.js` | Stores generated values/images during workflow execution and previews/reorders/deletes them. |
|
||||||
| Persistent text preview | `loop_nodes.py`, `web/preview_any_text.js` | Stores any value as text and keeps it after workflow reload. |
|
| Persistent text preview | `loop_nodes.py`, `web/preview_any_text.js` | Stores any value as text and keeps it after workflow reload. |
|
||||||
| Seed and resolution utility nodes | `node_seed_resolution.py`, imported by `__init__.py` | Global/per-axis seed configs plus SDXL/Krea width/height helpers. |
|
| Seed and resolution utility nodes | `node_seed_resolution.py`, imported by `__init__.py` | Global/per-axis seed configs plus SDXL/Krea width/height helpers. |
|
||||||
|
| Camera utility nodes | `node_camera.py`, imported by `__init__.py` | Direct camera config, orbit-to-camera config, and Qwen MultiAngle camera translation. |
|
||||||
|
|
||||||
## Drift Audit Helper
|
## Drift Audit Helper
|
||||||
|
|
||||||
|
|||||||
+228
@@ -0,0 +1,228 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .loop_nodes import ANY_TYPE
|
||||||
|
from .prompt_builder import (
|
||||||
|
build_camera_config_json,
|
||||||
|
build_camera_orbit_config_json,
|
||||||
|
build_qwen_camera_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,
|
||||||
|
)
|
||||||
|
except ImportError: # Allows local smoke tests from the repository root.
|
||||||
|
from loop_nodes import ANY_TYPE
|
||||||
|
from prompt_builder import (
|
||||||
|
build_camera_config_json,
|
||||||
|
build_camera_orbit_config_json,
|
||||||
|
build_qwen_camera_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,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SXCP_CAMERA_CONFIG = "SXCP_CAMERA_CONFIG"
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"SxCPCameraControl": SxCPCameraControl,
|
||||||
|
"SxCPCameraOrbitControl": SxCPCameraOrbitControl,
|
||||||
|
"SxCPQwenCameraTranslator": SxCPQwenCameraTranslator,
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||||
|
"SxCPCameraControl": "SxCP Camera Control",
|
||||||
|
"SxCPCameraOrbitControl": "SxCP Camera Orbit Control",
|
||||||
|
"SxCPQwenCameraTranslator": "SxCP Qwen Camera Translator",
|
||||||
|
}
|
||||||
@@ -1706,6 +1706,78 @@ def smoke_node_utility_registration() -> None:
|
|||||||
_expect(krea_config.get("width") == krea_width and krea_config.get("height") == krea_height, "Krea2 config_json dimensions mismatch")
|
_expect(krea_config.get("width") == krea_width and krea_config.get("height") == krea_height, "Krea2 config_json dimensions mismatch")
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_node_camera_registration() -> None:
|
||||||
|
required_nodes = [
|
||||||
|
"SxCPCameraControl",
|
||||||
|
"SxCPCameraOrbitControl",
|
||||||
|
"SxCPQwenCameraTranslator",
|
||||||
|
]
|
||||||
|
for node_name in required_nodes:
|
||||||
|
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
|
||||||
|
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
|
||||||
|
|
||||||
|
camera_control = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCameraControl"]
|
||||||
|
camera_inputs = camera_control.INPUT_TYPES().get("required") or {}
|
||||||
|
_expect("camera_mode" in camera_inputs, "Camera Control lost camera_mode input")
|
||||||
|
_expect("tooltip" in camera_inputs["camera_mode"][1], "Camera Control tooltip injection missing")
|
||||||
|
camera_config = camera_control().build(
|
||||||
|
"handheld_selfie",
|
||||||
|
"three_quarter_body",
|
||||||
|
"high_angle",
|
||||||
|
"smartphone_wide",
|
||||||
|
"arm_length",
|
||||||
|
"vertical_story",
|
||||||
|
"phone_visible",
|
||||||
|
"locked",
|
||||||
|
"compact",
|
||||||
|
)[0]
|
||||||
|
parsed_camera = json.loads(camera_config)
|
||||||
|
_expect(parsed_camera.get("camera_mode") == "handheld_selfie", "Camera Control lost camera_mode")
|
||||||
|
_expect(parsed_camera.get("phone_visibility") == "phone_visible", "Camera Control lost phone visibility")
|
||||||
|
|
||||||
|
orbit_config, orbit_prompt, orbit_info = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCameraOrbitControl"]().build(
|
||||||
|
True,
|
||||||
|
"standard",
|
||||||
|
45,
|
||||||
|
0,
|
||||||
|
5.5,
|
||||||
|
"from_zoom",
|
||||||
|
"auto",
|
||||||
|
"auto",
|
||||||
|
"auto",
|
||||||
|
"auto",
|
||||||
|
"soft_hint",
|
||||||
|
"compact",
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
parsed_orbit = json.loads(orbit_config)
|
||||||
|
_expect(parsed_orbit.get("camera_source") == "orbit", "Orbit camera lost source metadata")
|
||||||
|
_expect("front-right quarter view" in orbit_prompt, "Orbit camera prompt lost direction")
|
||||||
|
_expect(json.loads(orbit_info).get("orbit_azimuth") == 45, "Orbit info JSON lost azimuth")
|
||||||
|
|
||||||
|
qwen_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPQwenCameraTranslator"]
|
||||||
|
qwen_inputs = qwen_node.INPUT_TYPES()
|
||||||
|
_expect("camera_info" in (qwen_inputs.get("optional") or {}), "Qwen translator lost camera_info optional input")
|
||||||
|
qwen_config, qwen_prompt, qwen_info = qwen_node().build(
|
||||||
|
"<sks> front-right quarter view eye-level shot medium shot",
|
||||||
|
True,
|
||||||
|
"standard",
|
||||||
|
"auto",
|
||||||
|
"auto",
|
||||||
|
"auto",
|
||||||
|
"auto",
|
||||||
|
"soft_hint",
|
||||||
|
"compact",
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
parsed_qwen = json.loads(qwen_config)
|
||||||
|
_expect(parsed_qwen.get("camera_source") == "qwen_multiangle_prompt", "Qwen translator lost source metadata")
|
||||||
|
_expect(parsed_qwen.get("phone_visibility") == "auto", "Qwen translator should suppress phone visibility by default")
|
||||||
|
_expect("front-right quarter view" in qwen_prompt, "Qwen camera prompt lost direction")
|
||||||
|
_expect(json.loads(qwen_info).get("qwen_prompt", "").startswith("<sks>"), "Qwen info JSON lost original prompt")
|
||||||
|
|
||||||
|
|
||||||
SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
||||||
("builtin_single_woman", smoke_builtin_single),
|
("builtin_single_woman", smoke_builtin_single),
|
||||||
("camera_scene_single", smoke_camera_scene_single),
|
("camera_scene_single", smoke_camera_scene_single),
|
||||||
@@ -1730,6 +1802,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("expression_disabled", smoke_no_expression_fallback),
|
("expression_disabled", smoke_no_expression_fallback),
|
||||||
("formatter_metadata_fixtures", smoke_formatter_metadata_fixtures),
|
("formatter_metadata_fixtures", smoke_formatter_metadata_fixtures),
|
||||||
("node_utility_registration", smoke_node_utility_registration),
|
("node_utility_registration", smoke_node_utility_registration),
|
||||||
|
("node_camera_registration", smoke_node_camera_registration),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user