mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-03-07 22:30:00 +00:00
Compare commits
1 Commits
feat/api-n
...
accumulate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8bce44549 |
@@ -418,11 +418,12 @@ async def execute(server, dynprompt, caches, current_item, extra_data, executed,
|
||||
inputs = dynprompt.get_node(unique_id)['inputs']
|
||||
class_type = dynprompt.get_node(unique_id)['class_type']
|
||||
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
|
||||
merge = inputs.get('accumulate') is True
|
||||
cached = caches.outputs.get(unique_id)
|
||||
if cached is not None:
|
||||
if server.client_id is not None:
|
||||
cached_ui = cached.ui or {}
|
||||
server.send_sync("executed", { "node": unique_id, "display_node": display_node_id, "output": cached_ui.get("output",None), "prompt_id": prompt_id }, server.client_id)
|
||||
server.send_sync("executed", { "node": unique_id, "display_node": display_node_id, "output": cached_ui.get("output",None), "prompt_id": prompt_id, "merge": merge }, server.client_id)
|
||||
if cached.ui is not None:
|
||||
ui_outputs[unique_id] = cached.ui
|
||||
get_progress_state().finish_progress(unique_id)
|
||||
@@ -549,7 +550,7 @@ async def execute(server, dynprompt, caches, current_item, extra_data, executed,
|
||||
"output": output_ui
|
||||
}
|
||||
if server.client_id is not None:
|
||||
server.send_sync("executed", { "node": unique_id, "display_node": display_node_id, "output": output_ui, "prompt_id": prompt_id }, server.client_id)
|
||||
server.send_sync("executed", { "node": unique_id, "display_node": display_node_id, "output": output_ui, "prompt_id": prompt_id, "merge": merge }, server.client_id)
|
||||
if has_subgraph:
|
||||
cached_outputs = []
|
||||
new_node_ids = []
|
||||
|
||||
8
nodes.py
8
nodes.py
@@ -1640,6 +1640,9 @@ class SaveImage:
|
||||
"images": ("IMAGE", {"tooltip": "The images to save."}),
|
||||
"filename_prefix": ("STRING", {"default": "ComfyUI", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes."})
|
||||
},
|
||||
"optional": {
|
||||
"accumulate": ("BOOLEAN", {"default": False, "tooltip": "When enabled, outputs accumulate into a growing gallery across queue runs instead of being replaced.", "advanced": True}),
|
||||
},
|
||||
"hidden": {
|
||||
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"
|
||||
},
|
||||
@@ -1655,7 +1658,7 @@ class SaveImage:
|
||||
DESCRIPTION = "Saves the input images to your ComfyUI output directory."
|
||||
SEARCH_ALIASES = ["save", "save image", "export image", "output image", "write image", "download"]
|
||||
|
||||
def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
|
||||
def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None, accumulate=False):
|
||||
filename_prefix += self.prefix_append
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
|
||||
results = list()
|
||||
@@ -1696,6 +1699,9 @@ class PreviewImage(SaveImage):
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"images": ("IMAGE", ), },
|
||||
"optional": {
|
||||
"accumulate": ("BOOLEAN", {"default": False, "tooltip": "When enabled, outputs accumulate into a growing gallery across queue runs instead of being replaced.", "advanced": True}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
|
||||
89
tests-unit/execution_test/test_accumulate_merge.py
Normal file
89
tests-unit/execution_test/test_accumulate_merge.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
Unit tests for the accumulate toggle on SaveImage and PreviewImage nodes.
|
||||
|
||||
Tests that the accumulate input is correctly defined and that the merge flag
|
||||
derivation logic in execution.py works for all input shapes.
|
||||
"""
|
||||
import inspect
|
||||
|
||||
import pytest
|
||||
|
||||
import nodes
|
||||
|
||||
|
||||
class TestSaveImageAccumulateInput:
|
||||
"""Test SaveImage node definition includes accumulate input."""
|
||||
|
||||
def test_accumulate_in_optional_inputs(self):
|
||||
input_types = nodes.SaveImage.INPUT_TYPES()
|
||||
assert "optional" in input_types
|
||||
assert "accumulate" in input_types["optional"]
|
||||
|
||||
def test_accumulate_is_boolean_type(self):
|
||||
input_types = nodes.SaveImage.INPUT_TYPES()
|
||||
accumulate_def = input_types["optional"]["accumulate"]
|
||||
assert accumulate_def[0] == "BOOLEAN"
|
||||
|
||||
def test_accumulate_defaults_to_false(self):
|
||||
input_types = nodes.SaveImage.INPUT_TYPES()
|
||||
accumulate_def = input_types["optional"]["accumulate"]
|
||||
assert accumulate_def[1]["default"] is False
|
||||
|
||||
def test_accumulate_is_advanced(self):
|
||||
input_types = nodes.SaveImage.INPUT_TYPES()
|
||||
accumulate_def = input_types["optional"]["accumulate"]
|
||||
assert accumulate_def[1].get("advanced") is True
|
||||
|
||||
def test_save_images_accepts_accumulate_parameter(self):
|
||||
sig = inspect.signature(nodes.SaveImage.save_images)
|
||||
assert "accumulate" in sig.parameters
|
||||
assert sig.parameters["accumulate"].default is False
|
||||
|
||||
|
||||
class TestPreviewImageAccumulateInput:
|
||||
"""Test PreviewImage node definition includes accumulate input."""
|
||||
|
||||
def test_accumulate_in_optional_inputs(self):
|
||||
input_types = nodes.PreviewImage.INPUT_TYPES()
|
||||
assert "optional" in input_types
|
||||
assert "accumulate" in input_types["optional"]
|
||||
|
||||
def test_accumulate_is_boolean_type(self):
|
||||
input_types = nodes.PreviewImage.INPUT_TYPES()
|
||||
accumulate_def = input_types["optional"]["accumulate"]
|
||||
assert accumulate_def[0] == "BOOLEAN"
|
||||
|
||||
def test_accumulate_defaults_to_false(self):
|
||||
input_types = nodes.PreviewImage.INPUT_TYPES()
|
||||
accumulate_def = input_types["optional"]["accumulate"]
|
||||
assert accumulate_def[1]["default"] is False
|
||||
|
||||
|
||||
class TestAccumulateMergeFlagDerivation:
|
||||
"""Test the merge flag logic used in execution.py.
|
||||
|
||||
In execution.py, the merge flag is derived as:
|
||||
merge = inputs.get('accumulate') is True
|
||||
|
||||
This must return True only for literal True, not for truthy values
|
||||
like lists (which represent node links in the prompt).
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"inputs,expected",
|
||||
[
|
||||
({"accumulate": True}, True),
|
||||
({"accumulate": False}, False),
|
||||
({}, False),
|
||||
({"accumulate": None}, False),
|
||||
# Node link: accumulate connected to another node's output
|
||||
({"accumulate": ["other_node_id", 0]}, False),
|
||||
# String "true" should not match
|
||||
({"accumulate": "true"}, False),
|
||||
# Integer 1 should not match
|
||||
({"accumulate": 1}, False),
|
||||
],
|
||||
)
|
||||
def test_merge_flag(self, inputs, expected):
|
||||
merge = inputs.get("accumulate") is True
|
||||
assert merge is expected
|
||||
Reference in New Issue
Block a user