mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-04-12 11:32:37 +08:00
Add has_intermediate_output flag for nodes with interactive UI
Some checks failed
Build package / Build Test (3.13) (push) Has been cancelled
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Build package / Build Test (3.10) (push) Has been cancelled
Build package / Build Test (3.11) (push) Has been cancelled
Build package / Build Test (3.12) (push) Has been cancelled
Build package / Build Test (3.14) (push) Has been cancelled
Some checks failed
Build package / Build Test (3.13) (push) Has been cancelled
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Build package / Build Test (3.10) (push) Has been cancelled
Build package / Build Test (3.11) (push) Has been cancelled
Build package / Build Test (3.12) (push) Has been cancelled
Build package / Build Test (3.14) (push) Has been cancelled
This commit is contained in:
parent
6265a239f3
commit
fbc3b0fed3
@ -1353,6 +1353,7 @@ class NodeInfoV1:
|
|||||||
python_module: Any=None
|
python_module: Any=None
|
||||||
category: str=None
|
category: str=None
|
||||||
output_node: bool=None
|
output_node: bool=None
|
||||||
|
has_intermediate_output: bool=None
|
||||||
deprecated: bool=None
|
deprecated: bool=None
|
||||||
experimental: bool=None
|
experimental: bool=None
|
||||||
dev_only: bool=None
|
dev_only: bool=None
|
||||||
@ -1465,6 +1466,16 @@ class Schema:
|
|||||||
|
|
||||||
Comfy Docs: https://docs.comfy.org/custom-nodes/backend/server_overview#output-node
|
Comfy Docs: https://docs.comfy.org/custom-nodes/backend/server_overview#output-node
|
||||||
"""
|
"""
|
||||||
|
has_intermediate_output: bool=False
|
||||||
|
"""Flags this node as having intermediate output that should persist across page refreshes.
|
||||||
|
|
||||||
|
Nodes with this flag behave like output nodes (their UI results are cached and resent
|
||||||
|
to the frontend) but do NOT automatically get added to the execution list. This means
|
||||||
|
they will only execute if they are on the dependency path of a real output node.
|
||||||
|
|
||||||
|
Use this for nodes with interactive/operable UI regions that produce intermediate outputs
|
||||||
|
(e.g., Image Crop, Painter) rather than final outputs (e.g., Save Image).
|
||||||
|
"""
|
||||||
is_deprecated: bool=False
|
is_deprecated: bool=False
|
||||||
"""Flags a node as deprecated, indicating to users that they should find alternatives to this node."""
|
"""Flags a node as deprecated, indicating to users that they should find alternatives to this node."""
|
||||||
is_experimental: bool=False
|
is_experimental: bool=False
|
||||||
@ -1582,6 +1593,7 @@ class Schema:
|
|||||||
category=self.category,
|
category=self.category,
|
||||||
description=self.description,
|
description=self.description,
|
||||||
output_node=self.is_output_node,
|
output_node=self.is_output_node,
|
||||||
|
has_intermediate_output=self.has_intermediate_output,
|
||||||
deprecated=self.is_deprecated,
|
deprecated=self.is_deprecated,
|
||||||
experimental=self.is_experimental,
|
experimental=self.is_experimental,
|
||||||
dev_only=self.is_dev_only,
|
dev_only=self.is_dev_only,
|
||||||
@ -1873,6 +1885,14 @@ class _ComfyNodeBaseInternal(_ComfyNodeInternal):
|
|||||||
cls.GET_SCHEMA()
|
cls.GET_SCHEMA()
|
||||||
return cls._OUTPUT_NODE
|
return cls._OUTPUT_NODE
|
||||||
|
|
||||||
|
_HAS_INTERMEDIATE_OUTPUT = None
|
||||||
|
@final
|
||||||
|
@classproperty
|
||||||
|
def HAS_INTERMEDIATE_OUTPUT(cls): # noqa
|
||||||
|
if cls._HAS_INTERMEDIATE_OUTPUT is None:
|
||||||
|
cls.GET_SCHEMA()
|
||||||
|
return cls._HAS_INTERMEDIATE_OUTPUT
|
||||||
|
|
||||||
_INPUT_IS_LIST = None
|
_INPUT_IS_LIST = None
|
||||||
@final
|
@final
|
||||||
@classproperty
|
@classproperty
|
||||||
@ -1965,6 +1985,8 @@ class _ComfyNodeBaseInternal(_ComfyNodeInternal):
|
|||||||
cls._API_NODE = schema.is_api_node
|
cls._API_NODE = schema.is_api_node
|
||||||
if cls._OUTPUT_NODE is None:
|
if cls._OUTPUT_NODE is None:
|
||||||
cls._OUTPUT_NODE = schema.is_output_node
|
cls._OUTPUT_NODE = schema.is_output_node
|
||||||
|
if cls._HAS_INTERMEDIATE_OUTPUT is None:
|
||||||
|
cls._HAS_INTERMEDIATE_OUTPUT = schema.has_intermediate_output
|
||||||
if cls._INPUT_IS_LIST is None:
|
if cls._INPUT_IS_LIST is None:
|
||||||
cls._INPUT_IS_LIST = schema.is_input_list
|
cls._INPUT_IS_LIST = schema.is_input_list
|
||||||
if cls._NOT_IDEMPOTENT is None:
|
if cls._NOT_IDEMPOTENT is None:
|
||||||
|
|||||||
@ -118,6 +118,11 @@ class TopologicalSort:
|
|||||||
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
|
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
|
||||||
return get_input_info(class_def, input_name)
|
return get_input_info(class_def, input_name)
|
||||||
|
|
||||||
|
def is_intermediate_output(self, node_id):
|
||||||
|
class_type = self.dynprompt.get_node(node_id)["class_type"]
|
||||||
|
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
|
||||||
|
return hasattr(class_def, 'HAS_INTERMEDIATE_OUTPUT') and class_def.HAS_INTERMEDIATE_OUTPUT == True
|
||||||
|
|
||||||
def make_input_strong_link(self, to_node_id, to_input):
|
def make_input_strong_link(self, to_node_id, to_input):
|
||||||
inputs = self.dynprompt.get_node(to_node_id)["inputs"]
|
inputs = self.dynprompt.get_node(to_node_id)["inputs"]
|
||||||
if to_input not in inputs:
|
if to_input not in inputs:
|
||||||
|
|||||||
@ -762,6 +762,7 @@ class GLSLShader(io.ComfyNode):
|
|||||||
"Apply GLSL ES fragment shaders to images. "
|
"Apply GLSL ES fragment shaders to images. "
|
||||||
"u_resolution (vec2) is always available."
|
"u_resolution (vec2) is always available."
|
||||||
),
|
),
|
||||||
|
has_intermediate_output=True,
|
||||||
inputs=[
|
inputs=[
|
||||||
io.String.Input(
|
io.String.Input(
|
||||||
"fragment_shader",
|
"fragment_shader",
|
||||||
|
|||||||
@ -59,6 +59,7 @@ class ImageCropV2(IO.ComfyNode):
|
|||||||
display_name="Image Crop",
|
display_name="Image Crop",
|
||||||
category="image/transform",
|
category="image/transform",
|
||||||
essentials_category="Image Tools",
|
essentials_category="Image Tools",
|
||||||
|
has_intermediate_output=True,
|
||||||
inputs=[
|
inputs=[
|
||||||
IO.Image.Input("image"),
|
IO.Image.Input("image"),
|
||||||
IO.BoundingBox.Input("crop_region", component="ImageCrop"),
|
IO.BoundingBox.Input("crop_region", component="ImageCrop"),
|
||||||
|
|||||||
@ -30,6 +30,7 @@ class PainterNode(io.ComfyNode):
|
|||||||
node_id="Painter",
|
node_id="Painter",
|
||||||
display_name="Painter",
|
display_name="Painter",
|
||||||
category="image",
|
category="image",
|
||||||
|
has_intermediate_output=True,
|
||||||
inputs=[
|
inputs=[
|
||||||
io.Image.Input(
|
io.Image.Input(
|
||||||
"image",
|
"image",
|
||||||
|
|||||||
24
execution.py
24
execution.py
@ -411,6 +411,14 @@ def format_value(x):
|
|||||||
else:
|
else:
|
||||||
return str(x)
|
return str(x)
|
||||||
|
|
||||||
|
def _send_cached_ui(server, node_id, display_node_id, cached, prompt_id, ui_outputs):
|
||||||
|
if server.client_id is None:
|
||||||
|
return
|
||||||
|
cached_ui = cached.ui or {}
|
||||||
|
server.send_sync("executed", { "node": node_id, "display_node": display_node_id, "output": cached_ui.get("output", None), "prompt_id": prompt_id }, server.client_id)
|
||||||
|
if cached.ui is not None:
|
||||||
|
ui_outputs[node_id] = cached.ui
|
||||||
|
|
||||||
async def execute(server, dynprompt, caches, current_item, extra_data, executed, prompt_id, execution_list, pending_subgraph_results, pending_async_nodes, ui_outputs):
|
async def execute(server, dynprompt, caches, current_item, extra_data, executed, prompt_id, execution_list, pending_subgraph_results, pending_async_nodes, ui_outputs):
|
||||||
unique_id = current_item
|
unique_id = current_item
|
||||||
real_node_id = dynprompt.get_real_node_id(unique_id)
|
real_node_id = dynprompt.get_real_node_id(unique_id)
|
||||||
@ -421,11 +429,7 @@ async def execute(server, dynprompt, caches, current_item, extra_data, executed,
|
|||||||
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
|
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
|
||||||
cached = await caches.outputs.get(unique_id)
|
cached = await caches.outputs.get(unique_id)
|
||||||
if cached is not None:
|
if cached is not None:
|
||||||
if server.client_id is not None:
|
_send_cached_ui(server, unique_id, display_node_id, cached, prompt_id, ui_outputs)
|
||||||
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)
|
|
||||||
if cached.ui is not None:
|
|
||||||
ui_outputs[unique_id] = cached.ui
|
|
||||||
get_progress_state().finish_progress(unique_id)
|
get_progress_state().finish_progress(unique_id)
|
||||||
execution_list.cache_update(unique_id, cached)
|
execution_list.cache_update(unique_id, cached)
|
||||||
return (ExecutionResult.SUCCESS, None, None)
|
return (ExecutionResult.SUCCESS, None, None)
|
||||||
@ -748,6 +752,16 @@ class PromptExecutor:
|
|||||||
for node_id in list(execute_outputs):
|
for node_id in list(execute_outputs):
|
||||||
execution_list.add_node(node_id)
|
execution_list.add_node(node_id)
|
||||||
|
|
||||||
|
# Resend cached UI for intermediate output nodes that are not in the execution list.
|
||||||
|
for node_id in list(prompt.keys()):
|
||||||
|
if node_id in execution_list.pendingNodes:
|
||||||
|
continue
|
||||||
|
if not execution_list.is_intermediate_output(node_id):
|
||||||
|
continue
|
||||||
|
cached = await self.caches.outputs.get(node_id)
|
||||||
|
if cached is not None:
|
||||||
|
_send_cached_ui(self.server, node_id, node_id, cached, prompt_id, ui_node_outputs)
|
||||||
|
|
||||||
while not execution_list.is_empty():
|
while not execution_list.is_empty():
|
||||||
node_id, error, ex = await execution_list.stage_node_execution()
|
node_id, error, ex = await execution_list.stage_node_execution()
|
||||||
if error is not None:
|
if error is not None:
|
||||||
|
|||||||
@ -709,6 +709,8 @@ class PromptServer():
|
|||||||
else:
|
else:
|
||||||
info['output_node'] = False
|
info['output_node'] = False
|
||||||
|
|
||||||
|
info['has_intermediate_output'] = getattr(obj_class, 'HAS_INTERMEDIATE_OUTPUT', False)
|
||||||
|
|
||||||
if hasattr(obj_class, 'CATEGORY'):
|
if hasattr(obj_class, 'CATEGORY'):
|
||||||
info['category'] = obj_class.CATEGORY
|
info['category'] = obj_class.CATEGORY
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user