[Partner Nodes] fix(Tripo): refuse a P2 run whose linked GLB or FBX output would be empty (#16369)

Signed-off-by: bigcat88 <bigcat88@icloud.com>
This commit is contained in:
Alexander Piskun
2026-09-17 11:29:13 +04:00
committed by GitHub
parent d39cdfdb03
commit 387f98aa28
3 changed files with 43 additions and 4 deletions
+17 -3
View File
@@ -55,6 +55,7 @@ from comfy_api_nodes.util import (
tensor_to_bytesio,
upload_3d_model_to_comfyapi,
upload_images_to_comfyapi,
validate_output_unlinked,
validate_string,
)
@@ -2482,6 +2483,16 @@ def p_series_request_fields(model: dict) -> dict:
return fields
def p_series_check_outputs(cls: type[IO.ComfyNode], quad: bool) -> None:
empty_name, filled_name = ("GLB", "FBX") if quad else ("FBX", "GLB")
validate_output_unlinked(
cls,
1 if quad else 2,
f"quad is {'enabled' if quad else 'disabled'}, so this node delivers {filled_name} only "
f"and its {empty_name} output is empty. Connect the {filled_name} output instead",
)
async def p_series_generate(cls: type[IO.ComfyNode], path: str, request: TripoPSeriesRequest) -> IO.NodeOutput:
response = await sync_op(
cls,
@@ -2509,13 +2520,14 @@ class TripoPSeriesTextToModelNode(IO.ComfyNode):
search_aliases=["tripo p2", "quad mesh", "low poly"],
inputs=[p_series_model_input("text")],
outputs=model_outputs(legacy=False),
hidden=hidden_inputs(),
hidden=[*hidden_inputs(), IO.Hidden.dynprompt],
is_api_node=True,
price_badge=p_series_price_badge(),
)
@classmethod
async def execute(cls, model: dict) -> IO.NodeOutput:
p_series_check_outputs(cls, model["quad"])
prompt = model["prompt"].strip()
negative_prompt = (model.get("negative_prompt") or "").strip()
validate_string(prompt, min_length=1, max_length=1024)
@@ -2542,13 +2554,14 @@ class TripoPSeriesImageToModelNode(IO.ComfyNode):
search_aliases=["tripo p2", "quad mesh", "low poly"],
inputs=[p_series_model_input("image")],
outputs=model_outputs(legacy=False),
hidden=hidden_inputs(),
hidden=[*hidden_inputs(), IO.Hidden.dynprompt],
is_api_node=True,
price_badge=p_series_price_badge(),
)
@classmethod
async def execute(cls, model: dict) -> IO.NodeOutput:
p_series_check_outputs(cls, model["quad"])
fields = p_series_request_fields(model)
request = TripoPSeriesImageToModelRequest(
input=(await upload_images_to_comfyapi(cls, model["image"], max_images=1))[0],
@@ -2571,13 +2584,14 @@ class TripoPSeriesMultiviewToModelNode(IO.ComfyNode):
search_aliases=["tripo p2", "quad mesh", "low poly"],
inputs=[p_series_model_input("multiview")],
outputs=model_outputs(legacy=False),
hidden=hidden_inputs(),
hidden=[*hidden_inputs(), IO.Hidden.dynprompt],
is_api_node=True,
price_badge=p_series_price_badge(),
)
@classmethod
async def execute(cls, model: dict) -> IO.NodeOutput:
p_series_check_outputs(cls, model["quad"])
views = {
"front": model["image"],
"left": model.get("image_left"),
+4 -1
View File
@@ -1,4 +1,4 @@
from ._helpers import get_fs_object_size
from ._helpers import get_fs_object_size, get_output_consumers, validate_output_unlinked
from .client import (
ApiEndpoint,
poll_op,
@@ -122,4 +122,7 @@ __all__ = [
"validate_video_frame_count",
# Misc functions
"get_fs_object_size",
# Graph helpers
"get_output_consumers",
"validate_output_unlinked",
]
+22
View File
@@ -15,6 +15,7 @@ from comfy.comfy_api_env import normalize_comfy_api_base
from comfy.deploy_environment import get_deploy_environment
from comfy.model_management import processing_interrupted
from comfy_api.latest import IO
from comfy_execution.graph_utils import is_link
from comfy_execution.utils import get_executing_context
from comfyui_version import __version__ as comfyui_version
@@ -137,6 +138,27 @@ def get_fs_object_size(path_or_object: str | BytesIO) -> int:
return len(path_or_object.getvalue())
def get_output_consumers(node_cls: type[IO.ComfyNode], output_index: int) -> list[str]:
dynprompt = node_cls.hidden.dynprompt
if dynprompt is None:
return []
node_id = str(node_cls.hidden.unique_id)
consumers = []
for consumer_id in dynprompt.all_node_ids():
consumer = dynprompt.get_node(consumer_id)
for value in (consumer.get("inputs") or {}).values():
if is_link(value) and value[0] == node_id and value[1] == output_index:
title = (consumer.get("_meta") or {}).get("title") or consumer.get("class_type")
consumers.append(f"{title} #{dynprompt.get_display_node_id(consumer_id)}")
return sorted(consumers)
def validate_output_unlinked(node_cls: type[IO.ComfyNode], output_index: int, reason: str) -> None:
consumers = get_output_consumers(node_cls, output_index)
if consumers:
raise ValueError(f"{reason} (currently linked: {', '.join(consumers)}).")
def to_aiohttp_url(url: str) -> URL:
"""If `url` appears to be already percent-encoded (contains at least one valid %HH
escape and no malformed '%' sequences) and contains no raw whitespace/control