[Partner Nodes] Tripo3D 3.1 model (#13788)
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
Python Linting / Run Pylint (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Waiting to run
Execution Tests / test (macos-latest) (push) Waiting to run
Execution Tests / test (ubuntu-latest) (push) Waiting to run
Execution Tests / test (windows-latest) (push) Waiting to run
Test server launches without errors / test (push) Waiting to run
Unit Tests / test (macos-latest) (push) Waiting to run
Unit Tests / test (ubuntu-latest) (push) Waiting to run
Unit Tests / test (windows-2022) (push) Waiting to run

* feat(api-nodes): add Tripo3D 3.1 model

Signed-off-by: bigcat88 <bigcat88@icloud.com>

* fix: price badges algo

Signed-off-by: bigcat88 <bigcat88@icloud.com>

* [Partner Nodes] deprecate "quad" param for the TripoMultiviewToModel node

Signed-off-by: bigcat88 <bigcat88@icloud.com>

---------

Signed-off-by: bigcat88 <bigcat88@icloud.com>
This commit is contained in:
Alexander Piskun 2026-05-09 07:38:17 +03:00 committed by GitHub
parent 8b08bfdcbe
commit 7bbf1e8169
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 36 additions and 78 deletions

View File

@ -1,10 +1,11 @@
from __future__ import annotations
from enum import Enum from enum import Enum
from typing import Optional, List, Dict, Any, Union from typing import Optional, Any
from pydantic import BaseModel, Field, RootModel from pydantic import BaseModel, Field, RootModel
class TripoModelVersion(str, Enum): class TripoModelVersion(str, Enum):
v3_1_20260211 = 'v3.1-20260211'
v3_0_20250812 = 'v3.0-20250812' v3_0_20250812 = 'v3.0-20250812'
v2_5_20250123 = 'v2.5-20250123' v2_5_20250123 = 'v2.5-20250123'
v2_0_20240919 = 'v2.0-20240919' v2_0_20240919 = 'v2.0-20240919'
@ -142,7 +143,7 @@ class TripoFileEmptyReference(BaseModel):
pass pass
class TripoFileReference(RootModel): class TripoFileReference(RootModel):
root: Union[TripoFileTokenReference, TripoUrlReference, TripoObjectReference, TripoFileEmptyReference] root: TripoFileTokenReference | TripoUrlReference | TripoObjectReference | TripoFileEmptyReference
class TripoGetStsTokenRequest(BaseModel): class TripoGetStsTokenRequest(BaseModel):
format: str = Field(..., description='The format of the image') format: str = Field(..., description='The format of the image')
@ -183,7 +184,7 @@ class TripoImageToModelRequest(BaseModel):
class TripoMultiviewToModelRequest(BaseModel): class TripoMultiviewToModelRequest(BaseModel):
type: TripoTaskType = TripoTaskType.MULTIVIEW_TO_MODEL type: TripoTaskType = TripoTaskType.MULTIVIEW_TO_MODEL
files: List[TripoFileReference] = Field(..., description='The file references to convert to a model') files: list[TripoFileReference] = Field(..., description='The file references to convert to a model')
model_version: Optional[TripoModelVersion] = Field(None, description='The model version to use for generation') model_version: Optional[TripoModelVersion] = Field(None, description='The model version to use for generation')
orthographic_projection: Optional[bool] = Field(False, description='Whether to use orthographic projection') orthographic_projection: Optional[bool] = Field(False, description='Whether to use orthographic projection')
face_limit: Optional[int] = Field(None, description='The number of faces to limit the generation to') face_limit: Optional[int] = Field(None, description='The number of faces to limit the generation to')
@ -251,27 +252,13 @@ class TripoConvertModelRequest(BaseModel):
with_animation: Optional[bool] = Field(None, description='Whether to include animations') with_animation: Optional[bool] = Field(None, description='Whether to include animations')
pack_uv: Optional[bool] = Field(None, description='Whether to pack the UVs') pack_uv: Optional[bool] = Field(None, description='Whether to pack the UVs')
bake: Optional[bool] = Field(None, description='Whether to bake the model') bake: Optional[bool] = Field(None, description='Whether to bake the model')
part_names: Optional[List[str]] = Field(None, description='The names of the parts to include') part_names: Optional[list[str]] = Field(None, description='The names of the parts to include')
fbx_preset: Optional[TripoFbxPreset] = Field(None, description='The preset for the FBX export') fbx_preset: Optional[TripoFbxPreset] = Field(None, description='The preset for the FBX export')
export_vertex_colors: Optional[bool] = Field(None, description='Whether to export the vertex colors') export_vertex_colors: Optional[bool] = Field(None, description='Whether to export the vertex colors')
export_orientation: Optional[TripoOrientation] = Field(None, description='The orientation for the export') export_orientation: Optional[TripoOrientation] = Field(None, description='The orientation for the export')
animate_in_place: Optional[bool] = Field(None, description='Whether to animate in place') animate_in_place: Optional[bool] = Field(None, description='Whether to animate in place')
class TripoTaskRequest(RootModel):
root: Union[
TripoTextToModelRequest,
TripoImageToModelRequest,
TripoMultiviewToModelRequest,
TripoTextureModelRequest,
TripoRefineModelRequest,
TripoAnimatePrerigcheckRequest,
TripoAnimateRigRequest,
TripoAnimateRetargetRequest,
TripoStylizeModelRequest,
TripoConvertModelRequest
]
class TripoTaskOutput(BaseModel): class TripoTaskOutput(BaseModel):
model: Optional[str] = Field(None, description='URL to the model') model: Optional[str] = Field(None, description='URL to the model')
base_model: Optional[str] = Field(None, description='URL to the base model') base_model: Optional[str] = Field(None, description='URL to the base model')
@ -283,12 +270,13 @@ class TripoTask(BaseModel):
task_id: str = Field(..., description='The task ID') task_id: str = Field(..., description='The task ID')
type: Optional[str] = Field(None, description='The type of task') type: Optional[str] = Field(None, description='The type of task')
status: Optional[TripoTaskStatus] = Field(None, description='The status of the task') status: Optional[TripoTaskStatus] = Field(None, description='The status of the task')
input: Optional[Dict[str, Any]] = Field(None, description='The input parameters for the task') input: Optional[dict[str, Any]] = Field(None, description='The input parameters for the task')
output: Optional[TripoTaskOutput] = Field(None, description='The output of the task') output: Optional[TripoTaskOutput] = Field(None, description='The output of the task')
progress: Optional[int] = Field(None, description='The progress of the task', ge=0, le=100) progress: Optional[int] = Field(None, description='The progress of the task', ge=0, le=100)
create_time: Optional[int] = Field(None, description='The creation time of the task') create_time: Optional[int] = Field(None, description='The creation time of the task')
running_left_time: Optional[int] = Field(None, description='The estimated time left for the task') running_left_time: Optional[int] = Field(None, description='The estimated time left for the task')
queue_position: Optional[int] = Field(None, description='The position in the queue') queue_position: Optional[int] = Field(None, description='The position in the queue')
consumed_credit: int | None = Field(None)
class TripoTaskResponse(BaseModel): class TripoTaskResponse(BaseModel):
code: int = Field(0, description='The response code') code: int = Field(0, description='The response code')
@ -296,7 +284,7 @@ class TripoTaskResponse(BaseModel):
class TripoGeneralResponse(BaseModel): class TripoGeneralResponse(BaseModel):
code: int = Field(0, description='The response code') code: int = Field(0, description='The response code')
data: Dict[str, str] = Field(..., description='The task ID data') data: dict[str, str] = Field(..., description='The task ID data')
class TripoBalanceData(BaseModel): class TripoBalanceData(BaseModel):
balance: float = Field(..., description='The account balance') balance: float = Field(..., description='The account balance')

View File

@ -60,6 +60,7 @@ async def poll_until_finished(
], ],
status_extractor=lambda x: x.data.status, status_extractor=lambda x: x.data.status,
progress_extractor=lambda x: x.data.progress, progress_extractor=lambda x: x.data.progress,
price_extractor=lambda x: x.data.consumed_credit * 0.01 if x.data.consumed_credit else None,
estimated_duration=average_duration, estimated_duration=average_duration,
) )
if response_poll.data.status == TripoTaskStatus.SUCCESS: if response_poll.data.status == TripoTaskStatus.SUCCESS:
@ -113,7 +114,6 @@ class TripoTextToModelNode(IO.ComfyNode):
depends_on=IO.PriceBadgeDepends( depends_on=IO.PriceBadgeDepends(
widgets=[ widgets=[
"model_version", "model_version",
"style",
"texture", "texture",
"pbr", "pbr",
"quad", "quad",
@ -124,20 +124,17 @@ class TripoTextToModelNode(IO.ComfyNode):
expr=""" expr="""
( (
$isV14 := $contains(widgets.model_version,"v1.4"); $isV14 := $contains(widgets.model_version,"v1.4");
$style := widgets.style; $isV3OrLater := $contains(widgets.model_version,"v3.");
$hasStyle := ($style != "" and $style != "none");
$withTexture := widgets.texture or widgets.pbr; $withTexture := widgets.texture or widgets.pbr;
$isHdTexture := (widgets.texture_quality = "detailed"); $isHdTexture := (widgets.texture_quality = "detailed");
$isDetailedGeometry := (widgets.geometry_quality = "detailed"); $isDetailedGeometry := (widgets.geometry_quality = "detailed");
$baseCredits := $credits := $isV14 ? 20 : (
$isV14 ? 20 : ($withTexture ? 20 : 10); ($withTexture ? 20 : 10)
$credits :=
$baseCredits
+ ($hasStyle ? 5 : 0)
+ (widgets.quad ? 5 : 0) + (widgets.quad ? 5 : 0)
+ ($isHdTexture ? 10 : 0) + ($isHdTexture ? 10 : 0)
+ ($isDetailedGeometry ? 20 : 0); + (($isDetailedGeometry and $isV3OrLater) ? 20 : 0)
{"type":"usd","usd": $round($credits * 0.01, 2)} );
{"type":"usd","usd": $round($credits * 0.01, 2), "format": {"approximate": true}}
) )
""", """,
), ),
@ -239,7 +236,6 @@ class TripoImageToModelNode(IO.ComfyNode):
depends_on=IO.PriceBadgeDepends( depends_on=IO.PriceBadgeDepends(
widgets=[ widgets=[
"model_version", "model_version",
"style",
"texture", "texture",
"pbr", "pbr",
"quad", "quad",
@ -250,20 +246,17 @@ class TripoImageToModelNode(IO.ComfyNode):
expr=""" expr="""
( (
$isV14 := $contains(widgets.model_version,"v1.4"); $isV14 := $contains(widgets.model_version,"v1.4");
$style := widgets.style; $isV3OrLater := $contains(widgets.model_version,"v3.");
$hasStyle := ($style != "" and $style != "none");
$withTexture := widgets.texture or widgets.pbr; $withTexture := widgets.texture or widgets.pbr;
$isHdTexture := (widgets.texture_quality = "detailed"); $isHdTexture := (widgets.texture_quality = "detailed");
$isDetailedGeometry := (widgets.geometry_quality = "detailed"); $isDetailedGeometry := (widgets.geometry_quality = "detailed");
$baseCredits := $credits := $isV14 ? 30 : (
$isV14 ? 30 : ($withTexture ? 30 : 20); ($withTexture ? 30 : 20)
$credits :=
$baseCredits
+ ($hasStyle ? 5 : 0)
+ (widgets.quad ? 5 : 0) + (widgets.quad ? 5 : 0)
+ ($isHdTexture ? 10 : 0) + ($isHdTexture ? 10 : 0)
+ ($isDetailedGeometry ? 20 : 0); + (($isDetailedGeometry and $isV3OrLater) ? 20 : 0)
{"type":"usd","usd": $round($credits * 0.01, 2)} );
{"type":"usd","usd": $round($credits * 0.01, 2), "format": {"approximate": true}}
) )
""", """,
), ),
@ -358,7 +351,7 @@ class TripoMultiviewToModelNode(IO.ComfyNode):
"texture_alignment", default="original_image", options=["original_image", "geometry"], optional=True, advanced=True "texture_alignment", default="original_image", options=["original_image", "geometry"], optional=True, advanced=True
), ),
IO.Int.Input("face_limit", default=-1, min=-1, max=500000, optional=True, advanced=True), IO.Int.Input("face_limit", default=-1, min=-1, max=500000, optional=True, advanced=True),
IO.Boolean.Input("quad", default=False, optional=True, advanced=True), IO.Boolean.Input("quad", default=False, optional=True, advanced=True, tooltip="This parameter is deprecated and does nothing."),
IO.Combo.Input("geometry_quality", default="standard", options=["standard", "detailed"], optional=True, advanced=True), IO.Combo.Input("geometry_quality", default="standard", options=["standard", "detailed"], optional=True, advanced=True),
], ],
outputs=[ outputs=[
@ -379,7 +372,6 @@ class TripoMultiviewToModelNode(IO.ComfyNode):
"model_version", "model_version",
"texture", "texture",
"pbr", "pbr",
"quad",
"texture_quality", "texture_quality",
"geometry_quality", "geometry_quality",
], ],
@ -387,17 +379,16 @@ class TripoMultiviewToModelNode(IO.ComfyNode):
expr=""" expr="""
( (
$isV14 := $contains(widgets.model_version,"v1.4"); $isV14 := $contains(widgets.model_version,"v1.4");
$isV3OrLater := $contains(widgets.model_version,"v3.");
$withTexture := widgets.texture or widgets.pbr; $withTexture := widgets.texture or widgets.pbr;
$isHdTexture := (widgets.texture_quality = "detailed"); $isHdTexture := (widgets.texture_quality = "detailed");
$isDetailedGeometry := (widgets.geometry_quality = "detailed"); $isDetailedGeometry := (widgets.geometry_quality = "detailed");
$baseCredits := $credits := $isV14 ? 30 : (
$isV14 ? 30 : ($withTexture ? 30 : 20); ($withTexture ? 30 : 20)
$credits :=
$baseCredits
+ (widgets.quad ? 5 : 0)
+ ($isHdTexture ? 10 : 0) + ($isHdTexture ? 10 : 0)
+ ($isDetailedGeometry ? 20 : 0); + (($isDetailedGeometry and $isV3OrLater) ? 20 : 0)
{"type":"usd","usd": $round($credits * 0.01, 2)} );
{"type":"usd","usd": $round($credits * 0.01, 2), "format": {"approximate": true}}
) )
""", """,
), ),
@ -457,7 +448,7 @@ class TripoMultiviewToModelNode(IO.ComfyNode):
geometry_quality=geometry_quality, geometry_quality=geometry_quality,
texture_alignment=texture_alignment, texture_alignment=texture_alignment,
face_limit=face_limit if face_limit != -1 else None, face_limit=face_limit if face_limit != -1 else None,
quad=quad, quad=None,
), ),
) )
return await poll_until_finished(cls, response, average_duration=80) return await poll_until_finished(cls, response, average_duration=80)
@ -498,7 +489,7 @@ class TripoTextureNode(IO.ComfyNode):
expr=""" expr="""
( (
$tq := widgets.texture_quality; $tq := widgets.texture_quality;
{"type":"usd","usd": ($contains($tq,"detailed") ? 0.2 : 0.1)} {"type":"usd","usd": ($contains($tq,"detailed") ? 0.2 : 0.1), "format": {"approximate": true}}
) )
""", """,
), ),
@ -555,7 +546,7 @@ class TripoRefineNode(IO.ComfyNode):
is_api_node=True, is_api_node=True,
is_output_node=True, is_output_node=True,
price_badge=IO.PriceBadge( price_badge=IO.PriceBadge(
expr="""{"type":"usd","usd":0.3}""", expr="""{"type":"usd","usd":0.3, "format": {"approximate": true}}""",
), ),
) )
@ -592,7 +583,7 @@ class TripoRigNode(IO.ComfyNode):
is_api_node=True, is_api_node=True,
is_output_node=True, is_output_node=True,
price_badge=IO.PriceBadge( price_badge=IO.PriceBadge(
expr="""{"type":"usd","usd":0.25}""", expr="""{"type":"usd","usd":0.25, "format": {"approximate": true}}""",
), ),
) )
@ -652,7 +643,7 @@ class TripoRetargetNode(IO.ComfyNode):
is_api_node=True, is_api_node=True,
is_output_node=True, is_output_node=True,
price_badge=IO.PriceBadge( price_badge=IO.PriceBadge(
expr="""{"type":"usd","usd":0.1}""", expr="""{"type":"usd","usd":0.1, "format": {"approximate": true}}""",
), ),
) )
@ -761,19 +752,10 @@ class TripoConversionNode(IO.ComfyNode):
"face_limit", "face_limit",
"texture_size", "texture_size",
"texture_format", "texture_format",
"force_symmetry",
"flatten_bottom", "flatten_bottom",
"flatten_bottom_threshold", "flatten_bottom_threshold",
"pivot_to_center_bottom", "pivot_to_center_bottom",
"scale_factor", "scale_factor",
"with_animation",
"pack_uv",
"bake",
"part_names",
"fbx_preset",
"export_vertex_colors",
"export_orientation",
"animate_in_place",
], ],
), ),
expr=""" expr="""
@ -783,28 +765,16 @@ class TripoConversionNode(IO.ComfyNode):
$flatThresh := (widgets.flatten_bottom_threshold != null) ? widgets.flatten_bottom_threshold : 0; $flatThresh := (widgets.flatten_bottom_threshold != null) ? widgets.flatten_bottom_threshold : 0;
$scale := (widgets.scale_factor != null) ? widgets.scale_factor : 1; $scale := (widgets.scale_factor != null) ? widgets.scale_factor : 1;
$texFmt := (widgets.texture_format != "" ? widgets.texture_format : "jpeg"); $texFmt := (widgets.texture_format != "" ? widgets.texture_format : "jpeg");
$part := widgets.part_names;
$fbx := (widgets.fbx_preset != "" ? widgets.fbx_preset : "blender");
$orient := (widgets.export_orientation != "" ? widgets.export_orientation : "default");
$advanced := $advanced :=
widgets.quad or widgets.quad or
widgets.force_symmetry or
widgets.flatten_bottom or widgets.flatten_bottom or
widgets.pivot_to_center_bottom or widgets.pivot_to_center_bottom or
widgets.with_animation or
widgets.pack_uv or
widgets.bake or
widgets.export_vertex_colors or
widgets.animate_in_place or
($face != -1) or ($face != -1) or
($texSize != 4096) or ($texSize != 4096) or
($flatThresh != 0) or ($flatThresh != 0) or
($scale != 1) or ($scale != 1) or
($texFmt != "jpeg") or ($texFmt != "jpeg");
($part != "") or {"type":"usd","usd": ($advanced ? 0.1 : 0.05), "format": {"approximate": true}}
($fbx != "blender") or
($orient != "default");
{"type":"usd","usd": ($advanced ? 0.1 : 0.05)}
) )
""", """,
), ),