Add accumulator preview and batch save node

This commit is contained in:
2026-06-25 02:19:07 +02:00
parent 0b1b79445e
commit 71f4f162eb
3 changed files with 583 additions and 15 deletions
+46 -2
View File
@@ -117,6 +117,15 @@ COMMON_INPUT_TOOLTIPS = {
"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.",
"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.",
"clothing": "Built-in clothing density for legacy direct generation. Category/profile nodes can override this.",
"poses": "Built-in pose pool for legacy direct generation.",
"backside_bias": "Legacy bias toward rear/backside poses where that category supports it.",
@@ -317,7 +326,13 @@ def _install_input_tooltips(node_classes: dict[str, type]) -> None:
node_class._sxcp_tooltips_installed = True
try:
from .loop_nodes import ANY_TYPE, LOOP_NODE_CLASS_MAPPINGS, LOOP_NODE_DISPLAY_NAME_MAPPINGS
from .loop_nodes import (
ANY_TYPE,
LOOP_NODE_CLASS_MAPPINGS,
LOOP_NODE_DISPLAY_NAME_MAPPINGS,
accumulator_delete_entries,
accumulator_list_entries,
)
from .prompt_builder import (
build_camera_config_json,
build_camera_orbit_config_json,
@@ -386,7 +401,13 @@ try:
from .caption_naturalizer import naturalize_caption
from .krea_formatter import format_krea2_prompt
except ImportError:
from loop_nodes import ANY_TYPE, LOOP_NODE_CLASS_MAPPINGS, LOOP_NODE_DISPLAY_NAME_MAPPINGS
from loop_nodes import (
ANY_TYPE,
LOOP_NODE_CLASS_MAPPINGS,
LOOP_NODE_DISPLAY_NAME_MAPPINGS,
accumulator_delete_entries,
accumulator_list_entries,
)
from prompt_builder import (
build_camera_config_json,
build_camera_orbit_config_json,
@@ -469,6 +490,29 @@ if PromptServer is not None and web is not None:
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 ""))
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 ""),
entry_id=str(payload.get("entry_id") or ""),
index=int(payload.get("index") or 0),
clear=bool(payload.get("clear")),
)
return web.json_response(result)
except Exception as exc:
return web.json_response({"error": str(exc)}, status=400)
class SxCPPromptBuilder:
@classmethod