[Partner Nodes] feat: add new nodes for Tripo3D P1 model

Signed-off-by: bigcat88 <bigcat88@icloud.com>
This commit is contained in:
bigcat88 2026-05-28 11:58:46 +03:00
parent 4af9a47227
commit 45ebdb2efa
No known key found for this signature in database
GPG Key ID: 1F0BF0EC3CF22721
2 changed files with 633 additions and 157 deletions

View File

@ -1,25 +1,25 @@
from enum import Enum from enum import Enum
from typing import Optional, Any from typing import 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_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"
v1_4_20240625 = 'v1.4-20240625' v1_4_20240625 = "v1.4-20240625"
class TripoGeometryQuality(str, Enum): class TripoGeometryQuality(str, Enum):
standard = 'standard' standard = "standard"
detailed = 'detailed' detailed = "detailed"
class TripoTextureQuality(str, Enum): class TripoTextureQuality(str, Enum):
standard = 'standard' standard = "standard"
detailed = 'detailed' detailed = "detailed"
class TripoStyle(str, Enum): class TripoStyle(str, Enum):
@ -33,6 +33,7 @@ class TripoStyle(str, Enum):
ANCIENT_BRONZE = "ancient_bronze" ANCIENT_BRONZE = "ancient_bronze"
NONE = "None" NONE = "None"
class TripoTaskType(str, Enum): class TripoTaskType(str, Enum):
TEXT_TO_MODEL = "text_to_model" TEXT_TO_MODEL = "text_to_model"
IMAGE_TO_MODEL = "image_to_model" IMAGE_TO_MODEL = "image_to_model"
@ -45,26 +46,27 @@ class TripoTaskType(str, Enum):
STYLIZE_MODEL = "stylize_model" STYLIZE_MODEL = "stylize_model"
CONVERT_MODEL = "convert_model" CONVERT_MODEL = "convert_model"
class TripoTextureAlignment(str, Enum): class TripoTextureAlignment(str, Enum):
ORIGINAL_IMAGE = "original_image" ORIGINAL_IMAGE = "original_image"
GEOMETRY = "geometry" GEOMETRY = "geometry"
class TripoOrientation(str, Enum): class TripoOrientation(str, Enum):
ALIGN_IMAGE = "align_image" ALIGN_IMAGE = "align_image"
DEFAULT = "default" DEFAULT = "default"
class TripoOutFormat(str, Enum): class TripoOutFormat(str, Enum):
GLB = "glb" GLB = "glb"
FBX = "fbx" FBX = "fbx"
class TripoTopology(str, Enum):
BIP = "bip"
QUAD = "quad"
class TripoSpec(str, Enum): class TripoSpec(str, Enum):
MIXAMO = "mixamo" MIXAMO = "mixamo"
TRIPO = "tripo" TRIPO = "tripo"
class TripoAnimation(str, Enum): class TripoAnimation(str, Enum):
IDLE = "preset:idle" IDLE = "preset:idle"
WALK = "preset:walk" WALK = "preset:walk"
@ -83,11 +85,6 @@ class TripoAnimation(str, Enum):
SERPENTINE_MARCH = "preset:serpentine:march" SERPENTINE_MARCH = "preset:serpentine:march"
AQUATIC_MARCH = "preset:aquatic:march" AQUATIC_MARCH = "preset:aquatic:march"
class TripoStylizeStyle(str, Enum):
LEGO = "lego"
VOXEL = "voxel"
VORONOI = "voronoi"
MINECRAFT = "minecraft"
class TripoConvertFormat(str, Enum): class TripoConvertFormat(str, Enum):
GLTF = "GLTF" GLTF = "GLTF"
@ -97,6 +94,7 @@ class TripoConvertFormat(str, Enum):
STL = "STL" STL = "STL"
_3MF = "3MF" _3MF = "3MF"
class TripoTextureFormat(str, Enum): class TripoTextureFormat(str, Enum):
BMP = "BMP" BMP = "BMP"
DPX = "DPX" DPX = "DPX"
@ -108,6 +106,7 @@ class TripoTextureFormat(str, Enum):
TIFF = "TIFF" TIFF = "TIFF"
WEBP = "WEBP" WEBP = "WEBP"
class TripoTaskStatus(str, Enum): class TripoTaskStatus(str, Enum):
QUEUED = "queued" QUEUED = "queued"
RUNNING = "running" RUNNING = "running"
@ -118,183 +117,223 @@ class TripoTaskStatus(str, Enum):
BANNED = "banned" BANNED = "banned"
EXPIRED = "expired" EXPIRED = "expired"
class TripoFbxPreset(str, Enum): class TripoFbxPreset(str, Enum):
BLENDER = "blender" BLENDER = "blender"
MIXAMO = "mixamo" MIXAMO = "mixamo"
_3DSMAX = "3dsmax" _3DSMAX = "3dsmax"
class TripoFileTokenReference(BaseModel): class TripoFileTokenReference(BaseModel):
type: Optional[str] = Field(None, description='The type of the reference') type: str | None = Field(None, description="The type of the reference")
file_token: str file_token: str
class TripoUrlReference(BaseModel): class TripoUrlReference(BaseModel):
type: Optional[str] = Field(None, description='The type of the reference') type: str | None = Field(None, description="The type of the reference")
url: str url: str
class TripoObjectStorage(BaseModel): class TripoObjectStorage(BaseModel):
bucket: str bucket: str
key: str key: str
class TripoObjectReference(BaseModel): class TripoObjectReference(BaseModel):
type: str type: str
object: TripoObjectStorage object: TripoObjectStorage
class TripoFileEmptyReference(BaseModel): class TripoFileEmptyReference(BaseModel):
pass pass
class TripoFileReference(RootModel): class TripoFileReference(RootModel):
root: TripoFileTokenReference | TripoUrlReference | TripoObjectReference | TripoFileEmptyReference root: TripoFileTokenReference | TripoUrlReference | TripoObjectReference | TripoFileEmptyReference
class TripoGetStsTokenRequest(BaseModel):
format: str = Field(..., description='The format of the image')
class TripoTextToModelRequest(BaseModel): class TripoTextToModelRequest(BaseModel):
type: TripoTaskType = Field(TripoTaskType.TEXT_TO_MODEL, description='Type of task') type: TripoTaskType = Field(TripoTaskType.TEXT_TO_MODEL, description="Type of task")
prompt: str = Field(..., description='The text prompt describing the model to generate', max_length=1024) prompt: str = Field(..., description="The text prompt describing the model to generate", max_length=1024)
negative_prompt: Optional[str] = Field(None, description='The negative text prompt', max_length=1024) negative_prompt: str | None = Field(None, description="The negative text prompt", max_length=1024)
model_version: Optional[TripoModelVersion] = TripoModelVersion.v2_5_20250123 model_version: TripoModelVersion | None = TripoModelVersion.v2_5_20250123
face_limit: Optional[int] = Field(None, description='The number of faces to limit the generation to') face_limit: int | None = Field(None, description="The number of faces to limit the generation to")
texture: Optional[bool] = Field(True, description='Whether to apply texture to the generated model') texture: bool | None = Field(True, description="Whether to apply texture to the generated model")
pbr: Optional[bool] = Field(True, description='Whether to apply PBR to the generated model') pbr: bool | None = Field(True, description="Whether to apply PBR to the generated model")
image_seed: Optional[int] = Field(None, description='The seed for the text') image_seed: int | None = Field(None, description="The seed for the text")
model_seed: Optional[int] = Field(None, description='The seed for the model') model_seed: int | None = Field(None, description="The seed for the model")
texture_seed: Optional[int] = Field(None, description='The seed for the texture') texture_seed: int | None = Field(None, description="The seed for the texture")
texture_quality: Optional[TripoTextureQuality] = TripoTextureQuality.standard texture_quality: TripoTextureQuality | None = TripoTextureQuality.standard
geometry_quality: Optional[TripoGeometryQuality] = TripoGeometryQuality.standard geometry_quality: TripoGeometryQuality | None = TripoGeometryQuality.standard
style: Optional[TripoStyle] = None style: TripoStyle | None = None
auto_size: Optional[bool] = Field(False, description='Whether to auto-size the model') auto_size: bool | None = Field(False, description="Whether to auto-size the model")
quad: Optional[bool] = Field(False, description='Whether to apply quad to the generated model') quad: bool | None = Field(False, description="Whether to apply quad to the generated model")
class TripoImageToModelRequest(BaseModel): class TripoImageToModelRequest(BaseModel):
type: TripoTaskType = Field(TripoTaskType.IMAGE_TO_MODEL, description='Type of task') type: TripoTaskType = Field(TripoTaskType.IMAGE_TO_MODEL, description="Type of task")
file: TripoFileReference = Field(..., description='The file reference to convert to a model') file: TripoFileReference = Field(..., description="The file reference to convert to a model")
model_version: Optional[TripoModelVersion] = Field(None, description='The model version to use for generation') model_version: TripoModelVersion | None = Field(None, description="The model version to use for generation")
face_limit: Optional[int] = Field(None, description='The number of faces to limit the generation to') face_limit: int | None = Field(None, description="The number of faces to limit the generation to")
texture: Optional[bool] = Field(True, description='Whether to apply texture to the generated model') texture: bool | None = Field(True, description="Whether to apply texture to the generated model")
pbr: Optional[bool] = Field(True, description='Whether to apply PBR to the generated model') pbr: bool | None = Field(True, description="Whether to apply PBR to the generated model")
model_seed: Optional[int] = Field(None, description='The seed for the model') model_seed: int | None = Field(None, description="The seed for the model")
texture_seed: Optional[int] = Field(None, description='The seed for the texture') texture_seed: int | None = Field(None, description="The seed for the texture")
texture_quality: Optional[TripoTextureQuality] = TripoTextureQuality.standard texture_quality: TripoTextureQuality | None = TripoTextureQuality.standard
geometry_quality: Optional[TripoGeometryQuality] = TripoGeometryQuality.standard geometry_quality: TripoGeometryQuality | None = TripoGeometryQuality.standard
texture_alignment: Optional[TripoTextureAlignment] = Field(TripoTextureAlignment.ORIGINAL_IMAGE, description='The texture alignment method') texture_alignment: TripoTextureAlignment | None = Field(
style: Optional[TripoStyle] = Field(None, description='The style to apply to the generated model') TripoTextureAlignment.ORIGINAL_IMAGE, description="The texture alignment method"
auto_size: Optional[bool] = Field(False, description='Whether to auto-size the model') )
orientation: Optional[TripoOrientation] = TripoOrientation.DEFAULT style: TripoStyle | None = Field(None, description="The style to apply to the generated model")
quad: Optional[bool] = Field(False, description='Whether to apply quad to the generated model') auto_size: bool | None = Field(False, description="Whether to auto-size the model")
orientation: TripoOrientation | None = TripoOrientation.DEFAULT
quad: bool | None = Field(False, description="Whether to apply quad to the generated model")
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: TripoModelVersion | None = Field(None, description="The model version to use for generation")
orthographic_projection: Optional[bool] = Field(False, description='Whether to use orthographic projection') orthographic_projection: bool | None = 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: int | None = Field(None, description="The number of faces to limit the generation to")
texture: Optional[bool] = Field(True, description='Whether to apply texture to the generated model') texture: bool | None = Field(True, description="Whether to apply texture to the generated model")
pbr: Optional[bool] = Field(True, description='Whether to apply PBR to the generated model') pbr: bool | None = Field(True, description="Whether to apply PBR to the generated model")
model_seed: Optional[int] = Field(None, description='The seed for the model') model_seed: int | None = Field(None, description="The seed for the model")
texture_seed: Optional[int] = Field(None, description='The seed for the texture') texture_seed: int | None = Field(None, description="The seed for the texture")
texture_quality: Optional[TripoTextureQuality] = TripoTextureQuality.standard texture_quality: TripoTextureQuality | None = TripoTextureQuality.standard
geometry_quality: Optional[TripoGeometryQuality] = TripoGeometryQuality.standard geometry_quality: TripoGeometryQuality | None = TripoGeometryQuality.standard
texture_alignment: Optional[TripoTextureAlignment] = TripoTextureAlignment.ORIGINAL_IMAGE texture_alignment: TripoTextureAlignment | None = TripoTextureAlignment.ORIGINAL_IMAGE
auto_size: Optional[bool] = Field(False, description='Whether to auto-size the model') auto_size: bool | None = Field(False, description="Whether to auto-size the model")
orientation: Optional[TripoOrientation] = Field(TripoOrientation.DEFAULT, description='The orientation for the model') orientation: TripoOrientation | None = Field(TripoOrientation.DEFAULT, description="The orientation for the model")
quad: Optional[bool] = Field(False, description='Whether to apply quad to the generated model') quad: bool | None = Field(False, description="Whether to apply quad to the generated model")
class TripoTextureModelRequest(BaseModel): class TripoTextureModelRequest(BaseModel):
type: TripoTaskType = Field(TripoTaskType.TEXTURE_MODEL, description='Type of task') type: TripoTaskType = Field(TripoTaskType.TEXTURE_MODEL, description="Type of task")
original_model_task_id: str = Field(..., description='The task ID of the original model') original_model_task_id: str = Field(..., description="The task ID of the original model")
texture: Optional[bool] = Field(True, description='Whether to apply texture to the model') texture: bool | None = Field(True, description="Whether to apply texture to the model")
pbr: Optional[bool] = Field(True, description='Whether to apply PBR to the model') pbr: bool | None = Field(True, description="Whether to apply PBR to the model")
model_seed: Optional[int] = Field(None, description='The seed for the model') model_seed: int | None = Field(None, description="The seed for the model")
texture_seed: Optional[int] = Field(None, description='The seed for the texture') texture_seed: int | None = Field(None, description="The seed for the texture")
texture_quality: Optional[TripoTextureQuality] = Field(None, description='The quality of the texture') texture_quality: TripoTextureQuality | None = Field(None, description="The quality of the texture")
texture_alignment: Optional[TripoTextureAlignment] = Field(TripoTextureAlignment.ORIGINAL_IMAGE, description='The texture alignment method') texture_alignment: TripoTextureAlignment | None = Field(
TripoTextureAlignment.ORIGINAL_IMAGE, description="The texture alignment method"
)
class TripoRefineModelRequest(BaseModel): class TripoRefineModelRequest(BaseModel):
type: TripoTaskType = Field(TripoTaskType.REFINE_MODEL, description='Type of task') type: TripoTaskType = Field(TripoTaskType.REFINE_MODEL, description="Type of task")
draft_model_task_id: str = Field(..., description='The task ID of the draft model') draft_model_task_id: str = Field(..., description="The task ID of the draft model")
class TripoAnimatePrerigcheckRequest(BaseModel):
type: TripoTaskType = Field(TripoTaskType.ANIMATE_PRERIGCHECK, description='Type of task')
original_model_task_id: str = Field(..., description='The task ID of the original model')
class TripoAnimateRigRequest(BaseModel): class TripoAnimateRigRequest(BaseModel):
type: TripoTaskType = Field(TripoTaskType.ANIMATE_RIG, description='Type of task') type: TripoTaskType = Field(TripoTaskType.ANIMATE_RIG, description="Type of task")
original_model_task_id: str = Field(..., description='The task ID of the original model') original_model_task_id: str = Field(..., description="The task ID of the original model")
out_format: Optional[TripoOutFormat] = Field(TripoOutFormat.GLB, description='The output format') out_format: TripoOutFormat | None = Field(TripoOutFormat.GLB, description="The output format")
spec: Optional[TripoSpec] = Field(TripoSpec.TRIPO, description='The specification for rigging') spec: TripoSpec | None = Field(TripoSpec.TRIPO, description="The specification for rigging")
class TripoAnimateRetargetRequest(BaseModel): class TripoAnimateRetargetRequest(BaseModel):
type: TripoTaskType = Field(TripoTaskType.ANIMATE_RETARGET, description='Type of task') type: TripoTaskType = Field(TripoTaskType.ANIMATE_RETARGET, description="Type of task")
original_model_task_id: str = Field(..., description='The task ID of the original model') original_model_task_id: str = Field(..., description="The task ID of the original model")
animation: TripoAnimation = Field(..., description='The animation to apply') animation: TripoAnimation = Field(..., description="The animation to apply")
out_format: Optional[TripoOutFormat] = Field(TripoOutFormat.GLB, description='The output format') out_format: TripoOutFormat | None = Field(TripoOutFormat.GLB, description="The output format")
bake_animation: Optional[bool] = Field(True, description='Whether to bake the animation') bake_animation: bool | None = Field(True, description="Whether to bake the animation")
class TripoStylizeModelRequest(BaseModel):
type: TripoTaskType = Field(TripoTaskType.STYLIZE_MODEL, description='Type of task')
style: TripoStylizeStyle = Field(..., description='The style to apply to the model')
original_model_task_id: str = Field(..., description='The task ID of the original model')
block_size: Optional[int] = Field(80, description='The block size for stylization')
class TripoConvertModelRequest(BaseModel): class TripoConvertModelRequest(BaseModel):
type: TripoTaskType = Field(TripoTaskType.CONVERT_MODEL, description='Type of task') type: TripoTaskType = Field(TripoTaskType.CONVERT_MODEL, description="Type of task")
format: TripoConvertFormat = Field(..., description='The format to convert to') format: TripoConvertFormat = Field(..., description="The format to convert to")
original_model_task_id: str = Field(..., description='The task ID of the original model') original_model_task_id: str = Field(..., description="The task ID of the original model")
quad: Optional[bool] = Field(None, description='Whether to apply quad to the model') quad: bool | None = Field(None, description="Whether to apply quad to the model")
force_symmetry: Optional[bool] = Field(None, description='Whether to force symmetry') force_symmetry: bool | None = Field(None, description="Whether to force symmetry")
face_limit: Optional[int] = Field(None, description='The number of faces to limit the conversion to') face_limit: int | None = Field(None, description="The number of faces to limit the conversion to")
flatten_bottom: Optional[bool] = Field(None, description='Whether to flatten the bottom of the model') flatten_bottom: bool | None = Field(None, description="Whether to flatten the bottom of the model")
flatten_bottom_threshold: Optional[float] = Field(None, description='The threshold for flattening the bottom') flatten_bottom_threshold: float | None = Field(None, description="The threshold for flattening the bottom")
texture_size: Optional[int] = Field(None, description='The size of the texture') texture_size: int | None = Field(None, description="The size of the texture")
texture_format: Optional[TripoTextureFormat] = Field(TripoTextureFormat.JPEG, description='The format of the texture') texture_format: TripoTextureFormat | None = Field(TripoTextureFormat.JPEG, description="The format of the texture")
pivot_to_center_bottom: Optional[bool] = Field(None, description='Whether to pivot to the center bottom') pivot_to_center_bottom: bool | None = Field(None, description="Whether to pivot to the center bottom")
scale_factor: Optional[float] = Field(None, description='The scale factor for the model') scale_factor: float | None = Field(None, description="The scale factor for the model")
with_animation: Optional[bool] = Field(None, description='Whether to include animations') with_animation: bool | None = Field(None, description="Whether to include animations")
pack_uv: Optional[bool] = Field(None, description='Whether to pack the UVs') pack_uv: bool | None = Field(None, description="Whether to pack the UVs")
bake: Optional[bool] = Field(None, description='Whether to bake the model') bake: bool | None = 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: list[str] | None = 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: TripoFbxPreset | None = 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: bool | None = Field(None, description="Whether to export the vertex colors")
export_orientation: Optional[TripoOrientation] = Field(None, description='The orientation for the export') export_orientation: TripoOrientation | None = Field(None, description="The orientation for the export")
animate_in_place: Optional[bool] = Field(None, description='Whether to animate in place') animate_in_place: bool | None = Field(None, description="Whether to animate in place")
class TripoP1CommonRequest(BaseModel):
"""Fields supported by Tripo P1 across all input types."""
model_version: str = Field("P1-20260311")
model_seed: int | None = Field(None, description="Random seed for geometry generation")
face_limit: int | None = Field(None, ge=48, le=20000, description="Target face count (48-20000)")
texture: bool | None = Field(None, description="Enable texturing; pbr=True forces this true")
pbr: bool | None = Field(None, description="Enable PBR maps; when true, texture is also enabled")
texture_seed: int | None = Field(None, description="Random seed for texture generation")
texture_quality: str | None = Field(None, description='"standard" or "detailed"')
auto_size: bool | None = Field(None, description="Scale to real-world meters")
compress: str | None = Field(None, description='Only "geometry" is supported')
export_uv: bool | None = Field(None, description="Perform UV unwrapping during generation")
class TripoP1TextToModelRequest(TripoP1CommonRequest):
type: str = "text_to_model"
prompt: str = Field(..., max_length=1024)
negative_prompt: str | None = Field(None, max_length=255)
image_seed: int | None = None
class TripoP1ImageToModelRequest(TripoP1CommonRequest):
type: str = "image_to_model"
file: TripoFileReference
enable_image_autofix: bool | None = None
texture_alignment: str | None = Field(None, description='"original_image" or "geometry"')
orientation: str | None = Field(None, description='"default" or "align_image"; needs texture=true')
class TripoP1MultiviewToModelRequest(TripoP1CommonRequest):
"""P1 multiview generation.
Tripo requires `files` to be exactly four entries in [front, left, back, right] order with `{}`
(TripoFileEmptyReference) for omitted slots; front is required and at least two images total must be provided.
"""
type: str = "multiview_to_model"
files: list[TripoFileReference]
texture_alignment: str | None = None
orientation: str | None = None
class TripoTaskOutput(BaseModel): class TripoTaskOutput(BaseModel):
model: Optional[str] = Field(None, description='URL to the model') model: str | None = Field(None, description="URL to the model")
base_model: Optional[str] = Field(None, description='URL to the base model') base_model: str | None = Field(None, description="URL to the base model")
pbr_model: Optional[str] = Field(None, description='URL to the PBR model') pbr_model: str | None = Field(None, description="URL to the PBR model")
rendered_image: Optional[str] = Field(None, description='URL to the rendered image') rendered_image: str | None = Field(None, description="URL to the rendered image")
riggable: Optional[bool] = Field(None, description='Whether the model is riggable') riggable: bool | None = Field(None, description="Whether the model is riggable")
class TripoTask(BaseModel): 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: str | None = Field(None, description="The type of task")
status: Optional[TripoTaskStatus] = Field(None, description='The status of the task') status: TripoTaskStatus | None = Field(None, description="The status of the task")
input: Optional[dict[str, Any]] = Field(None, description='The input parameters for the task') input: dict[str, Any] | None = Field(None, description="The input parameters for the task")
output: Optional[TripoTaskOutput] = Field(None, description='The output of the task') output: TripoTaskOutput | None = Field(None, description="The output of the task")
progress: Optional[int] = Field(None, description='The progress of the task', ge=0, le=100) progress: int | None = 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: int | None = 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: int | None = Field(None, description="The estimated time left for the task")
queue_position: Optional[int] = Field(None, description='The position in the queue') queue_position: int | None = Field(None, description="The position in the queue")
consumed_credit: int | None = Field(None) 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")
data: TripoTask = Field(..., description='The task data') data: TripoTask = Field(..., description="The task data")
class TripoGeneralResponse(BaseModel):
code: int = Field(0, description='The response code')
data: dict[str, str] = Field(..., description='The task ID data')
class TripoBalanceData(BaseModel):
balance: float = Field(..., description='The account balance')
frozen: float = Field(..., description='The frozen balance')
class TripoBalanceResponse(BaseModel):
code: int = Field(0, description='The response code')
data: TripoBalanceData = Field(..., description='The balance data')
class TripoErrorResponse(BaseModel): class TripoErrorResponse(BaseModel):
code: int = Field(..., description='The error code') code: int = Field(..., description="The error code")
message: str = Field(..., description='The error message') message: str = Field(..., description="The error message")
suggestion: str = Field(..., description='The suggestion for fixing the error') suggestion: str = Field(..., description="The suggestion for fixing the error")

View File

@ -11,6 +11,9 @@ from comfy_api_nodes.apis.tripo import (
TripoModelVersion, TripoModelVersion,
TripoMultiviewToModelRequest, TripoMultiviewToModelRequest,
TripoOrientation, TripoOrientation,
TripoP1ImageToModelRequest,
TripoP1MultiviewToModelRequest,
TripoP1TextToModelRequest,
TripoRefineModelRequest, TripoRefineModelRequest,
TripoStyle, TripoStyle,
TripoTaskResponse, TripoTaskResponse,
@ -93,10 +96,22 @@ class TripoTextToModelNode(IO.ComfyNode):
IO.Int.Input("image_seed", default=42, optional=True, advanced=True), IO.Int.Input("image_seed", default=42, optional=True, advanced=True),
IO.Int.Input("model_seed", default=42, optional=True, advanced=True), IO.Int.Input("model_seed", default=42, optional=True, advanced=True),
IO.Int.Input("texture_seed", default=42, optional=True, advanced=True), IO.Int.Input("texture_seed", default=42, optional=True, advanced=True),
IO.Combo.Input("texture_quality", default="standard", options=["standard", "detailed"], optional=True, advanced=True), IO.Combo.Input(
"texture_quality",
default="standard",
options=["standard", "detailed"],
optional=True,
advanced=True,
),
IO.Int.Input("face_limit", default=-1, min=-1, max=2000000, optional=True, advanced=True), IO.Int.Input("face_limit", default=-1, min=-1, max=2000000, optional=True, advanced=True),
IO.Boolean.Input("quad", default=False, optional=True, advanced=True), IO.Boolean.Input("quad", default=False, optional=True, advanced=True),
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=[
IO.String.Output(display_name="model_file"), # for backward compatibility only IO.String.Output(display_name="model_file"), # for backward compatibility only
@ -209,16 +224,36 @@ class TripoImageToModelNode(IO.ComfyNode):
IO.Boolean.Input("pbr", default=True, optional=True), IO.Boolean.Input("pbr", default=True, optional=True),
IO.Int.Input("model_seed", default=42, optional=True, advanced=True), IO.Int.Input("model_seed", default=42, optional=True, advanced=True),
IO.Combo.Input( IO.Combo.Input(
"orientation", options=TripoOrientation, default=TripoOrientation.DEFAULT, optional=True, advanced=True "orientation",
options=TripoOrientation,
default=TripoOrientation.DEFAULT,
optional=True,
advanced=True,
), ),
IO.Int.Input("texture_seed", default=42, optional=True, advanced=True), IO.Int.Input("texture_seed", default=42, optional=True, advanced=True),
IO.Combo.Input("texture_quality", default="standard", options=["standard", "detailed"], optional=True, advanced=True),
IO.Combo.Input( IO.Combo.Input(
"texture_alignment", default="original_image", options=["original_image", "geometry"], optional=True, advanced=True "texture_quality",
default="standard",
options=["standard", "detailed"],
optional=True,
advanced=True,
),
IO.Combo.Input(
"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),
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=[
IO.String.Output(display_name="model_file"), # for backward compatibility only IO.String.Output(display_name="model_file"), # for backward compatibility only
@ -346,13 +381,35 @@ class TripoMultiviewToModelNode(IO.ComfyNode):
IO.Boolean.Input("pbr", default=True, optional=True), IO.Boolean.Input("pbr", default=True, optional=True),
IO.Int.Input("model_seed", default=42, optional=True, advanced=True), IO.Int.Input("model_seed", default=42, optional=True, advanced=True),
IO.Int.Input("texture_seed", default=42, optional=True, advanced=True), IO.Int.Input("texture_seed", default=42, optional=True, advanced=True),
IO.Combo.Input("texture_quality", default="standard", options=["standard", "detailed"], optional=True, advanced=True),
IO.Combo.Input( IO.Combo.Input(
"texture_alignment", default="original_image", options=["original_image", "geometry"], optional=True, advanced=True "texture_quality",
default="standard",
options=["standard", "detailed"],
optional=True,
advanced=True,
),
IO.Combo.Input(
"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, tooltip="This parameter is deprecated and does nothing."), IO.Boolean.Input(
IO.Combo.Input("geometry_quality", default="standard", options=["standard", "detailed"], optional=True, advanced=True), "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,
),
], ],
outputs=[ outputs=[
IO.String.Output(display_name="model_file"), # for backward compatibility only IO.String.Output(display_name="model_file"), # for backward compatibility only
@ -467,9 +524,19 @@ class TripoTextureNode(IO.ComfyNode):
IO.Boolean.Input("texture", default=True, optional=True), IO.Boolean.Input("texture", default=True, optional=True),
IO.Boolean.Input("pbr", default=True, optional=True), IO.Boolean.Input("pbr", default=True, optional=True),
IO.Int.Input("texture_seed", default=42, optional=True, advanced=True), IO.Int.Input("texture_seed", default=42, optional=True, advanced=True),
IO.Combo.Input("texture_quality", default="standard", options=["standard", "detailed"], optional=True, advanced=True),
IO.Combo.Input( IO.Combo.Input(
"texture_alignment", default="original_image", options=["original_image", "geometry"], optional=True, advanced=True "texture_quality",
default="standard",
options=["standard", "detailed"],
optional=True,
advanced=True,
),
IO.Combo.Input(
"texture_alignment",
default="original_image",
options=["original_image", "geometry"],
optional=True,
advanced=True,
), ),
], ],
outputs=[ outputs=[
@ -626,7 +693,7 @@ class TripoRetargetNode(IO.ComfyNode):
"preset:hexapod:walk", "preset:hexapod:walk",
"preset:octopod:walk", "preset:octopod:walk",
"preset:serpentine:march", "preset:serpentine:march",
"preset:aquatic:march" "preset:aquatic:march",
], ],
), ),
], ],
@ -817,7 +884,7 @@ class TripoConversionNode(IO.ComfyNode):
# Parse part_names from comma-separated string to list # Parse part_names from comma-separated string to list
part_names_list = None part_names_list = None
if part_names and part_names.strip(): if part_names and part_names.strip():
part_names_list = [name.strip() for name in part_names.split(',') if name.strip()] part_names_list = [name.strip() for name in part_names.split(",") if name.strip()]
response = await sync_op( response = await sync_op(
cls, cls,
@ -848,6 +915,373 @@ class TripoConversionNode(IO.ComfyNode):
return await poll_until_finished(cls, response, average_duration=30) return await poll_until_finished(cls, response, average_duration=30)
def _p1_price_expr(*, geometry_credits: int, textured_credits: int, detailed_credits: int) -> str:
return (
"("
" $mode := widgets.output_mode;"
' $detailed := $lookup(widgets, "output_mode.texture_quality") = "detailed";'
f' $credits := $mode = "geometry only" ? {geometry_credits} : ($detailed ? {detailed_credits} : {textured_credits});'
' {"type":"usd","usd": $credits * 0.01, "format": {"approximate": true}}'
")"
)
def _p1_textured_inputs(*, include_image_alignment: bool) -> list:
"""Inputs shown inside the 'Textured' branch of the P1 output_mode DynamicCombo."""
inputs: list = [
IO.Boolean.Input("pbr", default=True, tooltip="Include PBR maps. When on, base texture is forced on too."),
IO.Combo.Input("texture_quality", options=["standard", "detailed"], default="standard"),
]
if include_image_alignment:
inputs.extend(
[
IO.Combo.Input(
"texture_alignment",
options=["original_image", "geometry"],
default="original_image",
tooltip="Prioritize visual fidelity to the source image, or alignment to the mesh geometry.",
),
IO.Combo.Input(
"orientation",
options=["default", "align_image"],
default="default",
tooltip="Rotate the output to match the source image. Only applies when textured.",
),
]
)
inputs.append(IO.Int.Input("texture_seed", default=42, advanced=True))
return inputs
def _build_p1_output_mode(*, include_image_alignment: bool) -> IO.DynamicCombo.Input:
return IO.DynamicCombo.Input(
"output_mode",
options=[
IO.DynamicCombo.Option("Geometry only", []),
IO.DynamicCombo.Option("Textured", _p1_textured_inputs(include_image_alignment=include_image_alignment)),
],
tooltip='"Geometry only" returns an untextured mesh. "Textured" adds color/PBR maps.',
)
def _resolve_p1_texture_fields(output_mode: dict) -> dict:
"""Translate the output_mode DynamicCombo payload into P1 request fields.
pbr=true forces texture=true server-side, but we send both explicitly so the
intent is visible in the request body and logs.
"""
mode = output_mode["output_mode"]
if mode == "Geometry only":
return {"texture": False, "pbr": False}
out = {
"texture": True,
"pbr": bool(output_mode.get("pbr", True)),
"texture_quality": output_mode.get("texture_quality", "standard"),
"texture_seed": output_mode.get("texture_seed"),
}
if "texture_alignment" in output_mode:
out["texture_alignment"] = output_mode["texture_alignment"]
if "orientation" in output_mode:
out["orientation"] = output_mode["orientation"]
return out
def _p1_common_inputs() -> list:
"""Inputs shared by all P1 nodes (placed after output_mode)."""
return [
IO.Int.Input(
"face_limit",
default=-1,
min=-1,
max=20000,
optional=True,
advanced=True,
tooltip="Target face count, 48-20000. -1 lets Tripo pick adaptively.",
),
IO.Int.Input("model_seed", default=42, optional=True, advanced=True),
IO.Boolean.Input(
"auto_size",
default=False,
optional=True,
advanced=True,
tooltip="Scale the output to approximate real-world meters.",
),
IO.Boolean.Input(
"export_uv",
default=True,
optional=True,
advanced=True,
tooltip="UV unwrap during generation. Turn off for faster geometry-only runs.",
),
IO.Boolean.Input(
"compress_geometry",
default=False,
optional=True,
advanced=True,
tooltip="Apply geometry-based compression. Decompress before editing.",
),
]
def _build_p1_request_kwargs(
*,
output_mode: dict,
face_limit: int,
model_seed: int,
auto_size: bool,
export_uv: bool,
compress_geometry: bool,
) -> dict:
"""Common P1 request fields shared by all three node types."""
kwargs: dict = {
"model_seed": model_seed,
"face_limit": face_limit if face_limit != -1 else None,
"auto_size": auto_size,
"export_uv": export_uv,
"compress": "geometry" if compress_geometry else None,
}
kwargs.update(_resolve_p1_texture_fields(output_mode))
return kwargs
class TripoP1TextToModelNode(IO.ComfyNode):
@classmethod
def define_schema(cls):
return IO.Schema(
node_id="TripoP1TextToModelNode",
display_name="Tripo P1: Text to Model",
category="3d/partner/Tripo",
description="Tripo P1 text-to-3D. Optimized for low-poly, game-ready meshes with stable topology.",
inputs=[
IO.String.Input("prompt", multiline=True, tooltip="Up to 1024 characters."),
IO.String.Input("negative_prompt", multiline=True, optional=True, tooltip="Up to 255 characters."),
_build_p1_output_mode(include_image_alignment=False),
IO.Int.Input("image_seed", default=42, optional=True, advanced=True),
*_p1_common_inputs(),
],
outputs=[
IO.String.Output(display_name="model_file"), # for backward compatibility only
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
IO.File3DGLB.Output(display_name="GLB"),
],
hidden=[
IO.Hidden.auth_token_comfy_org,
IO.Hidden.api_key_comfy_org,
IO.Hidden.unique_id,
],
is_api_node=True,
price_badge=IO.PriceBadge(
depends_on=IO.PriceBadgeDepends(widgets=["output_mode", "output_mode.texture_quality"]),
expr=_p1_price_expr(geometry_credits=30, textured_credits=40, detailed_credits=50),
),
)
@classmethod
async def execute(
cls,
prompt: str,
output_mode: dict,
negative_prompt: str | None = None,
image_seed: int | None = None,
face_limit: int = -1,
model_seed: int | None = None,
auto_size: bool = False,
export_uv: bool = True,
compress_geometry: bool = False,
) -> IO.NodeOutput:
if not prompt:
raise RuntimeError("Prompt is required")
common = _build_p1_request_kwargs(
output_mode=output_mode,
face_limit=face_limit,
model_seed=model_seed,
auto_size=auto_size,
export_uv=export_uv,
compress_geometry=compress_geometry,
)
request = TripoP1TextToModelRequest(
prompt=prompt,
negative_prompt=negative_prompt or None,
image_seed=image_seed,
**common,
)
response = await sync_op(
cls,
endpoint=ApiEndpoint(path="/proxy/tripo/v2/openapi/task", method="POST"),
response_model=TripoTaskResponse,
data=request,
)
return await poll_until_finished(cls, response, average_duration=60)
class TripoP1ImageToModelNode(IO.ComfyNode):
@classmethod
def define_schema(cls):
return IO.Schema(
node_id="TripoP1ImageToModelNode",
display_name="Tripo P1: Image to Model",
category="3d/partner/Tripo",
description="Tripo P1 image-to-3D. Optimized for low-poly, game-ready meshes.",
inputs=[
IO.Image.Input("image"),
_build_p1_output_mode(include_image_alignment=True),
IO.Boolean.Input(
"enable_image_autofix",
default=False,
optional=True,
advanced=True,
tooltip="Pre-process the input image for better generation quality.",
),
*_p1_common_inputs(),
],
outputs=[
IO.String.Output(display_name="model_file"), # for backward compatibility only
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
IO.File3DGLB.Output(display_name="GLB"),
],
hidden=[
IO.Hidden.auth_token_comfy_org,
IO.Hidden.api_key_comfy_org,
IO.Hidden.unique_id,
],
is_api_node=True,
price_badge=IO.PriceBadge(
depends_on=IO.PriceBadgeDepends(widgets=["output_mode", "output_mode.texture_quality"]),
expr=_p1_price_expr(geometry_credits=40, textured_credits=50, detailed_credits=60),
),
)
@classmethod
async def execute(
cls,
image: Input.Image,
output_mode: dict,
enable_image_autofix: bool = False,
face_limit: int = -1,
model_seed: int | None = None,
auto_size: bool = False,
export_uv: bool = True,
compress_geometry: bool = False,
) -> IO.NodeOutput:
if image is None:
raise RuntimeError("Image is required")
tripo_file = TripoFileReference(
root=TripoUrlReference(
url=(await upload_images_to_comfyapi(cls, image, max_images=1))[0],
type="jpeg",
)
)
common = _build_p1_request_kwargs(
output_mode=output_mode,
face_limit=face_limit,
model_seed=model_seed,
auto_size=auto_size,
export_uv=export_uv,
compress_geometry=compress_geometry,
)
request = TripoP1ImageToModelRequest(
file=tripo_file,
enable_image_autofix=enable_image_autofix,
**common,
)
response = await sync_op(
cls,
endpoint=ApiEndpoint(path="/proxy/tripo/v2/openapi/task", method="POST"),
response_model=TripoTaskResponse,
data=request,
)
return await poll_until_finished(cls, response, average_duration=60)
class TripoP1MultiviewToModelNode(IO.ComfyNode):
@classmethod
def define_schema(cls):
return IO.Schema(
node_id="TripoP1MultiviewToModelNode",
display_name="Tripo P1: Multiview to Model",
category="3d/partner/Tripo",
description="Tripo P1 multiview-to-3D from 2-4 reference images in [front, left, back, right] order. "
"Front is required; any combination of the other three may be omitted.",
inputs=[
IO.Image.Input("image", tooltip="Front view (0°). Required."),
IO.Image.Input(
"image_left",
optional=True,
tooltip="Left view (90°), i.e. the subject's left side.",
),
IO.Image.Input("image_back", optional=True, tooltip="Back view (180°)."),
IO.Image.Input(
"image_right",
optional=True,
tooltip="Right view (270°), i.e. the subject's right side.",
),
_build_p1_output_mode(include_image_alignment=True),
*_p1_common_inputs(),
],
outputs=[
IO.String.Output(display_name="model_file"), # for backward compatibility only
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
IO.File3DGLB.Output(display_name="GLB"),
],
hidden=[
IO.Hidden.auth_token_comfy_org,
IO.Hidden.api_key_comfy_org,
IO.Hidden.unique_id,
],
is_api_node=True,
price_badge=IO.PriceBadge(
depends_on=IO.PriceBadgeDepends(widgets=["output_mode", "output_mode.texture_quality"]),
expr=_p1_price_expr(geometry_credits=40, textured_credits=50, detailed_credits=60),
),
)
@classmethod
async def execute(
cls,
image: Input.Image,
output_mode: dict,
image_left: Input.Image | None = None,
image_back: Input.Image | None = None,
image_right: Input.Image | None = None,
face_limit: int = -1,
model_seed: int | None = None,
auto_size: bool = False,
export_uv: bool = True,
compress_geometry: bool = False,
) -> IO.NodeOutput:
views = [image, image_left, image_back, image_right]
if sum(1 for v in views if v is not None) < 2:
raise RuntimeError("Tripo P1 multiview requires at least 2 images (front plus one of left/back/right).")
files: list[TripoFileReference] = []
for view in views:
if view is None:
files.append(TripoFileReference(root=TripoFileEmptyReference()))
continue
url = (await upload_images_to_comfyapi(cls, view, max_images=1))[0]
files.append(TripoFileReference(root=TripoUrlReference(url=url, type="jpeg")))
common = _build_p1_request_kwargs(
output_mode=output_mode,
face_limit=face_limit,
model_seed=model_seed,
auto_size=auto_size,
export_uv=export_uv,
compress_geometry=compress_geometry,
)
request = TripoP1MultiviewToModelRequest(files=files, **common)
response = await sync_op(
cls,
endpoint=ApiEndpoint(path="/proxy/tripo/v2/openapi/task", method="POST"),
response_model=TripoTaskResponse,
data=request,
)
return await poll_until_finished(cls, response, average_duration=80)
class TripoExtension(ComfyExtension): class TripoExtension(ComfyExtension):
@override @override
async def get_node_list(self) -> list[type[IO.ComfyNode]]: async def get_node_list(self) -> list[type[IO.ComfyNode]]:
@ -855,6 +1289,9 @@ class TripoExtension(ComfyExtension):
TripoTextToModelNode, TripoTextToModelNode,
TripoImageToModelNode, TripoImageToModelNode,
TripoMultiviewToModelNode, TripoMultiviewToModelNode,
TripoP1TextToModelNode,
TripoP1ImageToModelNode,
TripoP1MultiviewToModelNode,
TripoTextureNode, TripoTextureNode,
TripoRefineNode, TripoRefineNode,
TripoRigNode, TripoRigNode,