Files
Moser9815andClaude Opus 5 8273fc52eb Add stage capture: ship every decoded image to the Viewer as it is produced
Gives the monitor a clickable history of where a generation has been, at real decoded
quality rather than the 1/8-scale latent previews live_preview streams.

A node collecting image_1..image_N cannot do this. ComfyUI executes a node only once ALL
its inputs resolve, so a hub node emits nothing until the final stage finishes - exactly
when the information stops being useful. Same barrier documented in save_lineage.py, and
the reason RMSaveLineage cannot serve this purpose either.

So this patches the funnel instead: comfy.sd.VAE.decode and .decode_tiled. Every decoded
image passes through one of the two, including images from custom nodes calling
vae.decode directly, so existing workflows gain stage history with nothing wired. Accepted
cost: it captures every decode, including ones inside detailers and upscalers, and stages
cannot be hand-named.

Unlike live previews this queues rather than single-slots - a preview frame is disposable
because a newer one is moments away, but every stage is a distinct event and losing one
leaves a hole in the history. Bounded at 12 pending, dropping oldest.

Capture is guarded at the wrapper as well as inside _capture. A test that threw from
_capture proved the safety property was only incidental: the wrapper had no guard of its
own, so one future edit moving a line outside _capture would have broken every generation
in every workflow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_012uM7Ru1JYn48n6c5Rzwd99
2026-08-24 18:04:56 -04:00

119 lines
5.5 KiB
Python

"""
ComfyUI-RMAutomation
A collection of workflow automation nodes for ComfyUI.
"""
from .nodes.power_lora_loader import NODE_CLASS_MAPPINGS as POWER_LORA_MAPPINGS
from .nodes.power_lora_loader import NODE_DISPLAY_NAME_MAPPINGS as POWER_LORA_DISPLAY
from .nodes.styles_full import NODE_CLASS_MAPPINGS as STYLES_FULL_MAPPINGS
from .nodes.styles_full import NODE_DISPLAY_NAME_MAPPINGS as STYLES_FULL_DISPLAY
from .nodes.styles_pipe import NODE_CLASS_MAPPINGS as STYLES_PIPE_MAPPINGS
from .nodes.styles_pipe import NODE_DISPLAY_NAME_MAPPINGS as STYLES_PIPE_DISPLAY
from .nodes.video_combine import NODE_CLASS_MAPPINGS as VIDEO_MAPPINGS
from .nodes.video_combine import NODE_DISPLAY_NAME_MAPPINGS as VIDEO_DISPLAY
from .nodes.image_fallback import NODE_CLASS_MAPPINGS as IMAGE_FALLBACK_MAPPINGS
from .nodes.image_fallback import NODE_DISPLAY_NAME_MAPPINGS as IMAGE_FALLBACK_DISPLAY
from .nodes.mask_gate import NODE_CLASS_MAPPINGS as MASK_GATE_MAPPINGS
from .nodes.mask_gate import NODE_DISPLAY_NAME_MAPPINGS as MASK_GATE_DISPLAY
from .nodes.mask_combine import NODE_CLASS_MAPPINGS as MASK_COMBINE_MAPPINGS
from .nodes.mask_combine import NODE_DISPLAY_NAME_MAPPINGS as MASK_COMBINE_DISPLAY
from .nodes.latent_nodes import NODE_CLASS_MAPPINGS as LATENT_MAPPINGS
from .nodes.latent_nodes import NODE_DISPLAY_NAME_MAPPINGS as LATENT_DISPLAY
from .nodes.text_embed import NODE_CLASS_MAPPINGS as TEXT_EMBED_MAPPINGS
from .nodes.text_embed import NODE_DISPLAY_NAME_MAPPINGS as TEXT_EMBED_DISPLAY
from .nodes.save_image_with_metadata import NODE_CLASS_MAPPINGS as SAVE_IMAGE_MAPPINGS
from .nodes.save_image_with_metadata import NODE_DISPLAY_NAME_MAPPINGS as SAVE_IMAGE_DISPLAY
from .nodes.face_detailer import NODE_CLASS_MAPPINGS as FACE_DETAILER_MAPPINGS
from .nodes.face_detailer import NODE_DISPLAY_NAME_MAPPINGS as FACE_DETAILER_DISPLAY
from .nodes.multi_detect_crop import NODE_CLASS_MAPPINGS as MULTI_DETECT_MAPPINGS
from .nodes.multi_detect_crop import NODE_DISPLAY_NAME_MAPPINGS as MULTI_DETECT_DISPLAY
from .nodes.math_expression import NODE_CLASS_MAPPINGS as MATH_EXPR_MAPPINGS
from .nodes.math_expression import NODE_DISPLAY_NAME_MAPPINGS as MATH_EXPR_DISPLAY
from .nodes.make_image_batch import NODE_CLASS_MAPPINGS as IMAGE_BATCH_MAPPINGS
from .nodes.make_image_batch import NODE_DISPLAY_NAME_MAPPINGS as IMAGE_BATCH_DISPLAY
from .nodes.mask_denoise_remap import NODE_CLASS_MAPPINGS as MASK_DENOISE_REMAP_MAPPINGS
from .nodes.mask_denoise_remap import NODE_DISPLAY_NAME_MAPPINGS as MASK_DENOISE_REMAP_DISPLAY
from .nodes.save_lineage import NODE_CLASS_MAPPINGS as SAVE_LINEAGE_MAPPINGS
from .nodes.save_lineage import NODE_DISPLAY_NAME_MAPPINGS as SAVE_LINEAGE_DISPLAY
from .nodes.load_image_tracked import NODE_CLASS_MAPPINGS as LOAD_TRACKED_MAPPINGS
from .nodes.load_image_tracked import NODE_DISPLAY_NAME_MAPPINGS as LOAD_TRACKED_DISPLAY
from .nodes.add_label import NODE_CLASS_MAPPINGS as ADD_LABEL_MAPPINGS
from .nodes.add_label import NODE_DISPLAY_NAME_MAPPINGS as ADD_LABEL_DISPLAY
from .nodes.image_comparer import NODE_CLASS_MAPPINGS as IMAGE_COMPARER_MAPPINGS
from .nodes.image_comparer import NODE_DISPLAY_NAME_MAPPINGS as IMAGE_COMPARER_DISPLAY
# Content Policy editor routes. Registered at import so the modal has a backend as
# soon as ComfyUI is up; failure here must not take the whole pack down with it.
try:
from .nodes import policy_api
policy_api.register_routes()
except Exception as _err: # pragma: no cover
print("[RMAutomation] content policy routes unavailable: %s" % _err)
# Background extensions that register no nodes. Both talk to the Playground Viewer, and
# both install themselves on import, so each is isolated: a Viewer that isn't running, or
# a missing optional dependency, must not take the node pack down with it.
try:
from .nodes import live_preview # noqa: F401 (installs a sampler hook on import)
except Exception as _err: # pragma: no cover
print("[RMAutomation] live preview unavailable: %s" % _err)
try:
from .nodes import auto_retry # noqa: F401 (starts a websocket listener on import)
except Exception as _err: # pragma: no cover
print("[RMAutomation] auto retry unavailable: %s" % _err)
try:
from .nodes import stage_capture # noqa: F401 (wraps VAE decode on import)
except Exception as _err: # pragma: no cover
print("[RMAutomation] stage capture unavailable: %s" % _err)
# Combine all mappings
NODE_CLASS_MAPPINGS = {
**POWER_LORA_MAPPINGS,
**STYLES_FULL_MAPPINGS,
**STYLES_PIPE_MAPPINGS,
**VIDEO_MAPPINGS,
**IMAGE_FALLBACK_MAPPINGS,
**MASK_GATE_MAPPINGS,
**MASK_COMBINE_MAPPINGS,
**LATENT_MAPPINGS,
**TEXT_EMBED_MAPPINGS,
**SAVE_IMAGE_MAPPINGS,
**FACE_DETAILER_MAPPINGS,
**MULTI_DETECT_MAPPINGS,
**MATH_EXPR_MAPPINGS,
**IMAGE_BATCH_MAPPINGS,
**MASK_DENOISE_REMAP_MAPPINGS,
**SAVE_LINEAGE_MAPPINGS,
**LOAD_TRACKED_MAPPINGS,
**ADD_LABEL_MAPPINGS,
**IMAGE_COMPARER_MAPPINGS,
}
NODE_DISPLAY_NAME_MAPPINGS = {
**POWER_LORA_DISPLAY,
**STYLES_FULL_DISPLAY,
**STYLES_PIPE_DISPLAY,
**VIDEO_DISPLAY,
**IMAGE_FALLBACK_DISPLAY,
**MASK_GATE_DISPLAY,
**MASK_COMBINE_DISPLAY,
**LATENT_DISPLAY,
**TEXT_EMBED_DISPLAY,
**SAVE_IMAGE_DISPLAY,
**FACE_DETAILER_DISPLAY,
**MULTI_DETECT_DISPLAY,
**MATH_EXPR_DISPLAY,
**IMAGE_BATCH_DISPLAY,
**MASK_DENOISE_REMAP_DISPLAY,
**SAVE_LINEAGE_DISPLAY,
**LOAD_TRACKED_DISPLAY,
**ADD_LABEL_DISPLAY,
**IMAGE_COMPARER_DISPLAY,
}
WEB_DIRECTORY = "./js"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]