mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-09-04 10:57:09 +08:00
Add Tripo Nodes. (#189)
Co-authored-by: Robin Huang <robin.j.huang@gmail.com>
This commit is contained in:
parent
af30f287c5
commit
7f340e90e9
@ -1,6 +1,6 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: filtered-openapi.yaml
|
||||
# timestamp: 2025-05-20T01:04:45+00:00
|
||||
# timestamp: 2025-05-19T21:38:55+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -4095,7 +4095,6 @@ class RunwayTextToImageAspectRatioEnum(str, Enum):
|
||||
field_1808_768 = '1808:768'
|
||||
field_2112_912 = '2112:912'
|
||||
|
||||
|
||||
class Model4(str, Enum):
|
||||
gen4_image = 'gen4_image'
|
||||
|
||||
@ -4270,9 +4269,9 @@ class TripoModelStyle(str, Enum):
|
||||
|
||||
|
||||
class TripoModelVersion(str, Enum):
|
||||
v2_5_20250123 = 'v2.5-20250123'
|
||||
v2_0_20240919 = 'v2.0-20240919'
|
||||
v1_4_20240625 = 'v1.4-20240625'
|
||||
V2_5 = 'v2.5-20250123'
|
||||
V2_0 = 'v2.0-20240919'
|
||||
V1_4 = 'v1.4-20240625'
|
||||
|
||||
|
||||
class TripoMultiviewMode(str, Enum):
|
||||
@ -4597,7 +4596,8 @@ class WebSearchToolCall(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
CreateModelResponseProperties = ModelResponseProperties
|
||||
class CreateModelResponseProperties(ModelResponseProperties):
|
||||
pass
|
||||
|
||||
|
||||
class GeminiInlineData(BaseModel):
|
||||
|
||||
@ -142,7 +142,7 @@ class EmptyRequest(BaseModel):
|
||||
|
||||
class UploadRequest(BaseModel):
|
||||
file_name: str = Field(..., description="Filename to upload")
|
||||
content_type: str | None = Field(
|
||||
content_type: Optional[str] = Field(
|
||||
None,
|
||||
description="Mime type of the file. For example: image/png, image/jpeg, video/mp4, etc.",
|
||||
)
|
||||
|
||||
272
comfy_api_nodes/apis/tripo_api.py
Normal file
272
comfy_api_nodes/apis/tripo_api.py
Normal file
@ -0,0 +1,272 @@
|
||||
from __future__ import annotations
|
||||
from comfy_api_nodes.apis import (
|
||||
TripoModelVersion,
|
||||
TripoTextureQuality,
|
||||
)
|
||||
from enum import Enum
|
||||
from typing import Optional, List, Dict, Any, Union
|
||||
|
||||
from pydantic import BaseModel, Field, RootModel
|
||||
|
||||
class TripoStyle(str, Enum):
|
||||
PERSON_TO_CARTOON = "person:person2cartoon"
|
||||
ANIMAL_VENOM = "animal:venom"
|
||||
OBJECT_CLAY = "object:clay"
|
||||
OBJECT_STEAMPUNK = "object:steampunk"
|
||||
OBJECT_CHRISTMAS = "object:christmas"
|
||||
OBJECT_BARBIE = "object:barbie"
|
||||
GOLD = "gold"
|
||||
ANCIENT_BRONZE = "ancient_bronze"
|
||||
NONE = "None"
|
||||
|
||||
class TripoTaskType(str, Enum):
|
||||
TEXT_TO_MODEL = "text_to_model"
|
||||
IMAGE_TO_MODEL = "image_to_model"
|
||||
MULTIVIEW_TO_MODEL = "multiview_to_model"
|
||||
TEXTURE_MODEL = "texture_model"
|
||||
REFINE_MODEL = "refine_model"
|
||||
ANIMATE_PRERIGCHECK = "animate_prerigcheck"
|
||||
ANIMATE_RIG = "animate_rig"
|
||||
ANIMATE_RETARGET = "animate_retarget"
|
||||
STYLIZE_MODEL = "stylize_model"
|
||||
CONVERT_MODEL = "convert_model"
|
||||
|
||||
class TripoTextureAlignment(str, Enum):
|
||||
ORIGINAL_IMAGE = "original_image"
|
||||
GEOMETRY = "geometry"
|
||||
|
||||
class TripoOrientation(str, Enum):
|
||||
ALIGN_IMAGE = "align_image"
|
||||
DEFAULT = "default"
|
||||
|
||||
class TripoOutFormat(str, Enum):
|
||||
GLB = "glb"
|
||||
FBX = "fbx"
|
||||
|
||||
class TripoTopology(str, Enum):
|
||||
BIP = "bip"
|
||||
QUAD = "quad"
|
||||
|
||||
class TripoSpec(str, Enum):
|
||||
MIXAMO = "mixamo"
|
||||
TRIPO = "tripo"
|
||||
|
||||
class TripoAnimation(str, Enum):
|
||||
IDLE = "preset:idle"
|
||||
WALK = "preset:walk"
|
||||
CLIMB = "preset:climb"
|
||||
JUMP = "preset:jump"
|
||||
RUN = "preset:run"
|
||||
SLASH = "preset:slash"
|
||||
SHOOT = "preset:shoot"
|
||||
HURT = "preset:hurt"
|
||||
FALL = "preset:fall"
|
||||
TURN = "preset:turn"
|
||||
|
||||
class TripoStylizeStyle(str, Enum):
|
||||
LEGO = "lego"
|
||||
VOXEL = "voxel"
|
||||
VORONOI = "voronoi"
|
||||
MINECRAFT = "minecraft"
|
||||
|
||||
class TripoConvertFormat(str, Enum):
|
||||
GLTF = "GLTF"
|
||||
USDZ = "USDZ"
|
||||
FBX = "FBX"
|
||||
OBJ = "OBJ"
|
||||
STL = "STL"
|
||||
_3MF = "3MF"
|
||||
|
||||
class TripoTextureFormat(str, Enum):
|
||||
BMP = "BMP"
|
||||
DPX = "DPX"
|
||||
HDR = "HDR"
|
||||
JPEG = "JPEG"
|
||||
OPEN_EXR = "OPEN_EXR"
|
||||
PNG = "PNG"
|
||||
TARGA = "TARGA"
|
||||
TIFF = "TIFF"
|
||||
WEBP = "WEBP"
|
||||
|
||||
class TripoTaskStatus(str, Enum):
|
||||
QUEUED = "queued"
|
||||
RUNNING = "running"
|
||||
SUCCESS = "success"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
UNKNOWN = "unknown"
|
||||
BANNED = "banned"
|
||||
EXPIRED = "expired"
|
||||
|
||||
class TripoFileTokenReference(BaseModel):
|
||||
type: Optional[str] = Field(None, description='The type of the reference')
|
||||
file_token: str
|
||||
|
||||
class TripoUrlReference(BaseModel):
|
||||
type: Optional[str] = Field(None, description='The type of the reference')
|
||||
url: str
|
||||
|
||||
class TripoObjectStorage(BaseModel):
|
||||
bucket: str
|
||||
key: str
|
||||
|
||||
class TripoObjectReference(BaseModel):
|
||||
type: str
|
||||
object: TripoObjectStorage
|
||||
|
||||
class TripoFileReference(RootModel):
|
||||
root: Union[TripoFileTokenReference, TripoUrlReference, TripoObjectReference]
|
||||
|
||||
class TripoGetStsTokenRequest(BaseModel):
|
||||
format: str = Field(..., description='The format of the image')
|
||||
|
||||
class TripoTextToModelRequest(BaseModel):
|
||||
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)
|
||||
negative_prompt: Optional[str] = Field(None, description='The negative text prompt', max_length=1024)
|
||||
model_version: Optional[TripoModelVersion] = TripoModelVersion.V2_5
|
||||
face_limit: Optional[int] = 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')
|
||||
pbr: Optional[bool] = Field(True, description='Whether to apply PBR to the generated model')
|
||||
image_seed: Optional[int] = Field(None, description='The seed for the text')
|
||||
model_seed: Optional[int] = Field(None, description='The seed for the model')
|
||||
texture_seed: Optional[int] = Field(None, description='The seed for the texture')
|
||||
texture_quality: Optional[TripoTextureQuality] = TripoTextureQuality.standard
|
||||
style: Optional[TripoStyle] = None
|
||||
auto_size: Optional[bool] = Field(False, description='Whether to auto-size the model')
|
||||
quad: Optional[bool] = Field(False, description='Whether to apply quad to the generated model')
|
||||
|
||||
class TripoImageToModelRequest(BaseModel):
|
||||
type: TripoTaskType = Field(TripoTaskType.IMAGE_TO_MODEL, description='Type of task')
|
||||
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')
|
||||
face_limit: Optional[int] = 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')
|
||||
pbr: Optional[bool] = Field(True, description='Whether to apply PBR to the generated model')
|
||||
model_seed: Optional[int] = Field(None, description='The seed for the model')
|
||||
texture_seed: Optional[int] = Field(None, description='The seed for the texture')
|
||||
texture_quality: Optional[TripoTextureQuality] = TripoTextureQuality.standard
|
||||
texture_alignment: Optional[TripoTextureAlignment] = Field(TripoTextureAlignment.ORIGINAL_IMAGE, description='The texture alignment method')
|
||||
style: Optional[TripoStyle] = Field(None, description='The style to apply to the generated model')
|
||||
auto_size: Optional[bool] = Field(False, description='Whether to auto-size the model')
|
||||
orientation: Optional[TripoOrientation] = TripoOrientation.DEFAULT
|
||||
quad: Optional[bool] = Field(False, description='Whether to apply quad to the generated model')
|
||||
|
||||
class TripoMultiviewToModelRequest(BaseModel):
|
||||
type: TripoTaskType = TripoTaskType.MULTIVIEW_TO_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')
|
||||
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')
|
||||
texture: Optional[bool] = 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')
|
||||
model_seed: Optional[int] = Field(None, description='The seed for the model')
|
||||
texture_seed: Optional[int] = Field(None, description='The seed for the texture')
|
||||
texture_quality: Optional[TripoTextureQuality] = TripoTextureQuality.standard
|
||||
texture_alignment: Optional[TripoTextureAlignment] = TripoTextureAlignment.ORIGINAL_IMAGE
|
||||
auto_size: Optional[bool] = Field(False, description='Whether to auto-size the model')
|
||||
orientation: Optional[TripoOrientation] = Field(TripoOrientation.DEFAULT, description='The orientation for the model')
|
||||
quad: Optional[bool] = Field(False, description='Whether to apply quad to the generated model')
|
||||
|
||||
class TripoTextureModelRequest(BaseModel):
|
||||
type: TripoTaskType = Field(TripoTaskType.TEXTURE_MODEL, description='Type of task')
|
||||
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')
|
||||
pbr: Optional[bool] = Field(True, description='Whether to apply PBR to the model')
|
||||
model_seed: Optional[int] = Field(None, description='The seed for the model')
|
||||
texture_seed: Optional[int] = Field(None, description='The seed for the texture')
|
||||
texture_quality: Optional[TripoTextureQuality] = Field(None, description='The quality of the texture')
|
||||
texture_alignment: Optional[TripoTextureAlignment] = Field(TripoTextureAlignment.ORIGINAL_IMAGE, description='The texture alignment method')
|
||||
|
||||
class TripoRefineModelRequest(BaseModel):
|
||||
type: TripoTaskType = Field(TripoTaskType.REFINE_MODEL, description='Type of task')
|
||||
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):
|
||||
type: TripoTaskType = Field(TripoTaskType.ANIMATE_RIG, description='Type of task')
|
||||
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')
|
||||
spec: Optional[TripoSpec] = Field(TripoSpec.TRIPO, description='The specification for rigging')
|
||||
|
||||
class TripoAnimateRetargetRequest(BaseModel):
|
||||
type: TripoTaskType = Field(TripoTaskType.ANIMATE_RETARGET, description='Type of task')
|
||||
original_model_task_id: str = Field(..., description='The task ID of the original model')
|
||||
animation: TripoAnimation = Field(..., description='The animation to apply')
|
||||
out_format: Optional[TripoOutFormat] = Field(TripoOutFormat.GLB, description='The output format')
|
||||
bake_animation: Optional[bool] = 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):
|
||||
type: TripoTaskType = Field(TripoTaskType.CONVERT_MODEL, description='Type of task')
|
||||
format: TripoConvertFormat = Field(..., description='The format to convert to')
|
||||
original_model_task_id: str = Field(..., description='The task ID of the original model')
|
||||
quad: Optional[bool] = Field(False, description='Whether to apply quad to the model')
|
||||
force_symmetry: Optional[bool] = Field(False, description='Whether to force symmetry')
|
||||
face_limit: Optional[int] = Field(10000, description='The number of faces to limit the conversion to')
|
||||
flatten_bottom: Optional[bool] = Field(False, description='Whether to flatten the bottom of the model')
|
||||
flatten_bottom_threshold: Optional[float] = Field(0.01, description='The threshold for flattening the bottom')
|
||||
texture_size: Optional[int] = Field(4096, description='The size of the texture')
|
||||
texture_format: Optional[TripoTextureFormat] = Field(TripoTextureFormat.JPEG, description='The format of the texture')
|
||||
pivot_to_center_bottom: Optional[bool] = Field(False, description='Whether to pivot to the center bottom')
|
||||
|
||||
class TripoTaskRequest(RootModel):
|
||||
root: Union[
|
||||
TripoTextToModelRequest,
|
||||
TripoImageToModelRequest,
|
||||
TripoMultiviewToModelRequest,
|
||||
TripoTextureModelRequest,
|
||||
TripoRefineModelRequest,
|
||||
TripoAnimatePrerigcheckRequest,
|
||||
TripoAnimateRigRequest,
|
||||
TripoAnimateRetargetRequest,
|
||||
TripoStylizeModelRequest,
|
||||
TripoConvertModelRequest
|
||||
]
|
||||
|
||||
class TripoTaskOutput(BaseModel):
|
||||
model: Optional[str] = Field(None, description='URL to the model')
|
||||
base_model: Optional[str] = Field(None, description='URL to the base model')
|
||||
pbr_model: Optional[str] = Field(None, description='URL to the PBR model')
|
||||
rendered_image: Optional[str] = Field(None, description='URL to the rendered image')
|
||||
riggable: Optional[bool] = Field(None, description='Whether the model is riggable')
|
||||
|
||||
class TripoTask(BaseModel):
|
||||
task_id: str = Field(..., description='The task ID')
|
||||
type: Optional[str] = Field(None, description='The type of 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')
|
||||
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)
|
||||
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')
|
||||
queue_position: Optional[int] = Field(None, description='The position in the queue')
|
||||
|
||||
class TripoTaskResponse(BaseModel):
|
||||
code: int = Field(0, description='The response code')
|
||||
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):
|
||||
code: int = Field(..., description='The error code')
|
||||
message: str = Field(..., description='The error message')
|
||||
suggestion: str = Field(..., description='The suggestion for fixing the error')
|
||||
567
comfy_api_nodes/nodes_tripo.py
Normal file
567
comfy_api_nodes/nodes_tripo.py
Normal file
@ -0,0 +1,567 @@
|
||||
import os
|
||||
from folder_paths import get_output_directory
|
||||
from comfy_api_nodes.mapper_utils import model_field_to_node_input
|
||||
from comfy.comfy_types.node_typing import IO
|
||||
from comfy_api_nodes.apis import (
|
||||
TripoOrientation,
|
||||
TripoModelVersion,
|
||||
)
|
||||
from comfy_api_nodes.apis.tripo_api import (
|
||||
TripoTaskType,
|
||||
TripoStyle,
|
||||
TripoFileReference,
|
||||
TripoUrlReference,
|
||||
TripoTaskResponse,
|
||||
TripoTaskStatus,
|
||||
TripoTextToModelRequest,
|
||||
TripoImageToModelRequest,
|
||||
TripoMultiviewToModelRequest,
|
||||
TripoTextureModelRequest,
|
||||
TripoRefineModelRequest,
|
||||
TripoAnimateRigRequest,
|
||||
TripoAnimateRetargetRequest,
|
||||
TripoConvertModelRequest,
|
||||
)
|
||||
|
||||
from comfy_api_nodes.apis.client import (
|
||||
ApiEndpoint,
|
||||
HttpMethod,
|
||||
SynchronousOperation,
|
||||
PollingOperation,
|
||||
EmptyRequest,
|
||||
)
|
||||
from comfy_api_nodes.apinode_utils import (
|
||||
upload_images_to_comfyapi,
|
||||
download_url_to_bytesio,
|
||||
)
|
||||
|
||||
|
||||
def upload_image_to_tripo(image, **kwargs):
|
||||
urls = upload_images_to_comfyapi(image, max_images=1, auth_kwargs=kwargs)
|
||||
return TripoFileReference(TripoUrlReference(url=urls[0], type="jpeg"))
|
||||
|
||||
def get_model_url_from_response(response: TripoTaskResponse) -> str:
|
||||
if response.data is not None:
|
||||
for key in ["pbr_model", "model", "base_model"]:
|
||||
if getattr(response.data.output, key, None) is not None:
|
||||
return getattr(response.data.output, key)
|
||||
raise RuntimeError(f"Failed to get model url from response: {response}")
|
||||
|
||||
|
||||
def poll_until_finished(
|
||||
kwargs: dict[str, str],
|
||||
response: TripoTaskResponse,
|
||||
) -> tuple[str, str]:
|
||||
"""Polls the Tripo API endpoint until the task reaches a terminal state, then returns the response."""
|
||||
if response.code != 0:
|
||||
raise RuntimeError(f"Failed to generate mesh: {response.error}")
|
||||
task_id = response.data.task_id
|
||||
response_poll = PollingOperation(
|
||||
poll_endpoint=ApiEndpoint(
|
||||
path=f"/proxy/tripo/v2/openapi/task/{task_id}",
|
||||
method=HttpMethod.GET,
|
||||
request_model=EmptyRequest,
|
||||
response_model=TripoTaskResponse,
|
||||
),
|
||||
completed_statuses=[TripoTaskStatus.SUCCESS],
|
||||
failed_statuses=[
|
||||
TripoTaskStatus.FAILED,
|
||||
TripoTaskStatus.CANCELLED,
|
||||
TripoTaskStatus.UNKNOWN,
|
||||
TripoTaskStatus.BANNED,
|
||||
TripoTaskStatus.EXPIRED,
|
||||
],
|
||||
status_extractor=lambda x: x.data.status,
|
||||
auth_kwargs=kwargs,
|
||||
node_id=kwargs["unique_id"],
|
||||
result_url_extractor=get_model_url_from_response,
|
||||
progress_extractor=lambda x: x.data.progress,
|
||||
).execute()
|
||||
if response_poll.data.status == TripoTaskStatus.SUCCESS:
|
||||
url = get_model_url_from_response(response_poll)
|
||||
bytesio = download_url_to_bytesio(url)
|
||||
# Save the downloaded model file
|
||||
model_file = f"tripo_model_{task_id}.glb"
|
||||
with open(os.path.join(get_output_directory(), model_file), "wb") as f:
|
||||
f.write(bytesio.getvalue())
|
||||
return model_file, task_id
|
||||
raise RuntimeError(f"Failed to generate mesh: {response_poll}")
|
||||
|
||||
class TripoTextToModelNode:
|
||||
"""
|
||||
Generates 3D models synchronously based on a text prompt using Tripo's API.
|
||||
"""
|
||||
AVERAGE_DURATION = 80
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {"multiline": True}),
|
||||
},
|
||||
"optional": {
|
||||
"negative_prompt": ("STRING", {"multiline": True}),
|
||||
"model_version": model_field_to_node_input(IO.COMBO, TripoTextToModelRequest, "model_version", enum_type=TripoModelVersion),
|
||||
"style": model_field_to_node_input(IO.COMBO, TripoTextToModelRequest, "style", enum_type=TripoStyle, default="None"),
|
||||
"texture": ("BOOLEAN", {"default": True}),
|
||||
"pbr": ("BOOLEAN", {"default": True}),
|
||||
"image_seed": ("INT", {"default": 42}),
|
||||
"model_seed": ("INT", {"default": 42}),
|
||||
"texture_seed": ("INT", {"default": 42}),
|
||||
"texture_quality": (["standard", "detailed"], {"default": "standard"}),
|
||||
"face_limit": ("INT", {"min": -1, "max": 500000, "default": -1}),
|
||||
"quad": ("BOOLEAN", {"default": False})
|
||||
},
|
||||
"hidden": {
|
||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "MODEL_TASK_ID",)
|
||||
RETURN_NAMES = ("model_file", "model task_id")
|
||||
FUNCTION = "generate_mesh"
|
||||
CATEGORY = "api node/3d/Tripo"
|
||||
API_NODE = True
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def generate_mesh(self, prompt, negative_prompt=None, model_version=None, style=None, texture=None, pbr=None, image_seed=None, model_seed=None, texture_seed=None, texture_quality=None, face_limit=None, quad=None, **kwargs):
|
||||
style_enum = None if style == "None" else style
|
||||
if not prompt:
|
||||
raise RuntimeError("Prompt is required")
|
||||
response = SynchronousOperation(
|
||||
endpoint=ApiEndpoint(
|
||||
path="/proxy/tripo/v2/openapi/task",
|
||||
method=HttpMethod.POST,
|
||||
request_model=TripoTextToModelRequest,
|
||||
response_model=TripoTaskResponse,
|
||||
),
|
||||
request=TripoTextToModelRequest(
|
||||
type=TripoTaskType.TEXT_TO_MODEL,
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
model_version=model_version,
|
||||
style=style_enum,
|
||||
texture=texture,
|
||||
pbr=pbr,
|
||||
image_seed=image_seed,
|
||||
model_seed=model_seed,
|
||||
texture_seed=texture_seed,
|
||||
texture_quality=texture_quality,
|
||||
face_limit=face_limit,
|
||||
auto_size=True,
|
||||
quad=quad
|
||||
),
|
||||
auth_kwargs=kwargs,
|
||||
).execute()
|
||||
return poll_until_finished(kwargs, response)
|
||||
|
||||
class TripoImageToModelNode:
|
||||
"""
|
||||
Generates 3D models synchronously based on a single image using Tripo's API.
|
||||
"""
|
||||
AVERAGE_DURATION = 80
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
},
|
||||
"optional": {
|
||||
"model_version": model_field_to_node_input(IO.COMBO, TripoImageToModelRequest, "model_version", enum_type=TripoModelVersion),
|
||||
"style": model_field_to_node_input(IO.COMBO, TripoTextToModelRequest, "style", enum_type=TripoStyle, default="None"),
|
||||
"texture": ("BOOLEAN", {"default": True}),
|
||||
"pbr": ("BOOLEAN", {"default": True}),
|
||||
"model_seed": ("INT", {"default": 42}),
|
||||
"orientation": model_field_to_node_input(IO.COMBO, TripoImageToModelRequest, "orientation", enum_type=TripoOrientation),
|
||||
"texture_seed": ("INT", {"default": 42}),
|
||||
"texture_quality": (["standard", "detailed"], {"default": "standard"}),
|
||||
"texture_alignment": (["original_image", "geometry"], {"default": "original_image"}),
|
||||
"face_limit": ("INT", {"min": -1, "max": 500000, "default": -1}),
|
||||
"quad": ("BOOLEAN", {"default": False})
|
||||
},
|
||||
"hidden": {
|
||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "MODEL_TASK_ID",)
|
||||
RETURN_NAMES = ("model_file", "model task_id")
|
||||
FUNCTION = "generate_mesh"
|
||||
CATEGORY = "api node/3d/Tripo"
|
||||
API_NODE = True
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def generate_mesh(self, image, model_version=None, style=None, texture=None, pbr=None, model_seed=None, orientation=None, texture_alignment=None, texture_seed=None, texture_quality=None, face_limit=None, quad=None, **kwargs):
|
||||
style_enum = None if style == "None" else style
|
||||
if image is None:
|
||||
raise RuntimeError("Image is required")
|
||||
tripo_file = upload_image_to_tripo(image, **kwargs)
|
||||
response = SynchronousOperation(
|
||||
endpoint=ApiEndpoint(
|
||||
path="/proxy/tripo/v2/openapi/task",
|
||||
method=HttpMethod.POST,
|
||||
request_model=TripoImageToModelRequest,
|
||||
response_model=TripoTaskResponse,
|
||||
),
|
||||
request=TripoImageToModelRequest(
|
||||
type=TripoTaskType.IMAGE_TO_MODEL,
|
||||
file=tripo_file,
|
||||
model_version=model_version,
|
||||
style=style_enum,
|
||||
texture=texture,
|
||||
pbr=pbr,
|
||||
model_seed=model_seed,
|
||||
orientation=orientation,
|
||||
texture_alignment=texture_alignment,
|
||||
texture_seed=texture_seed,
|
||||
texture_quality=texture_quality,
|
||||
face_limit=face_limit,
|
||||
auto_size=True,
|
||||
quad=quad
|
||||
),
|
||||
auth_kwargs=kwargs,
|
||||
).execute()
|
||||
return poll_until_finished(kwargs, response)
|
||||
|
||||
class TripoMultiviewToModelNode:
|
||||
"""
|
||||
Generates 3D models synchronously based on up to four images (front, left, back, right) using Tripo's API.
|
||||
"""
|
||||
AVERAGE_DURATION = 80
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
},
|
||||
"optional": {
|
||||
"image_left": ("IMAGE",),
|
||||
"image_back": ("IMAGE",),
|
||||
"image_right": ("IMAGE",),
|
||||
"model_version": model_field_to_node_input(IO.COMBO, TripoMultiviewToModelRequest, "model_version", enum_type=TripoModelVersion),
|
||||
"orientation": model_field_to_node_input(IO.COMBO, TripoImageToModelRequest, "orientation", enum_type=TripoOrientation),
|
||||
"texture": ("BOOLEAN", {"default": True}),
|
||||
"pbr": ("BOOLEAN", {"default": True}),
|
||||
"model_seed": ("INT", {"default": 42}),
|
||||
"texture_seed": ("INT", {"default": 42}),
|
||||
"texture_quality": (["standard", "detailed"], {"default": "standard"}),
|
||||
"texture_alignment": (["original_image", "geometry"], {"default": "original_image"}),
|
||||
"face_limit": ("INT", {"min": -1, "max": 500000, "default": -1}),
|
||||
"quad": ("BOOLEAN", {"default": False})
|
||||
},
|
||||
"hidden": {
|
||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "MODEL_TASK_ID",)
|
||||
RETURN_NAMES = ("model_file", "model task_id")
|
||||
FUNCTION = "generate_mesh"
|
||||
CATEGORY = "api node/3d/Tripo"
|
||||
API_NODE = True
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def generate_mesh(self, image, image_left=None, image_back=None, image_right=None, model_version=None, orientation=None, texture=None, pbr=None, model_seed=None, texture_seed=None, texture_quality=None, texture_alignment=None, face_limit=None, quad=None, **kwargs):
|
||||
if image is None:
|
||||
raise RuntimeError("front image for multiview is required")
|
||||
images = []
|
||||
image_dict = {
|
||||
"image": image,
|
||||
"image_left": image_left,
|
||||
"image_back": image_back,
|
||||
"image_right": image_right
|
||||
}
|
||||
for image_name in ["image", "image_left", "image_back", "image_right"]:
|
||||
image_ = image_dict[image_name]
|
||||
if image_ is not None:
|
||||
tripo_file = upload_image_to_tripo(image_, **kwargs)
|
||||
images.append(tripo_file)
|
||||
else:
|
||||
images.append(None)
|
||||
response = SynchronousOperation(
|
||||
endpoint=ApiEndpoint(
|
||||
path="/proxy/tripo/v2/openapi/task",
|
||||
method=HttpMethod.POST,
|
||||
request_model=TripoMultiviewToModelRequest,
|
||||
response_model=TripoTaskResponse,
|
||||
),
|
||||
request=TripoMultiviewToModelRequest(
|
||||
type=TripoTaskType.MULTIVIEW_TO_MODEL,
|
||||
images=images,
|
||||
model_version=model_version,
|
||||
orientation=orientation,
|
||||
texture=texture,
|
||||
pbr=pbr,
|
||||
model_seed=model_seed,
|
||||
texture_seed=texture_seed,
|
||||
texture_quality=texture_quality,
|
||||
texture_alignment=texture_alignment,
|
||||
face_limit=face_limit,
|
||||
quad=quad,
|
||||
),
|
||||
auth_kwargs=kwargs,
|
||||
).execute()
|
||||
return poll_until_finished(kwargs, response)
|
||||
|
||||
class TripoTextureNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"model_task_id": ("MODEL_TASK_ID",),
|
||||
},
|
||||
"optional": {
|
||||
"texture": ("BOOLEAN", {"default": True}),
|
||||
"pbr": ("BOOLEAN", {"default": True}),
|
||||
"texture_seed": ("INT", {"default": 42}),
|
||||
"texture_quality": (["standard", "detailed"], {"default": "standard"}),
|
||||
"texture_alignment": (["original_image", "geometry"], {"default": "original_image"}),
|
||||
},
|
||||
"hidden": {
|
||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "MODEL_TASK_ID",)
|
||||
RETURN_NAMES = ("model_file", "model task_id")
|
||||
FUNCTION = "generate_mesh"
|
||||
CATEGORY = "api node/3d/Tripo"
|
||||
API_NODE = True
|
||||
OUTPUT_NODE = True
|
||||
AVERAGE_DURATION = 80
|
||||
|
||||
def generate_mesh(self, model_task_id, texture=None, pbr=None, texture_seed=None, texture_quality=None, texture_alignment=None, **kwargs):
|
||||
response = SynchronousOperation(
|
||||
endpoint=ApiEndpoint(
|
||||
path="/proxy/tripo/v2/openapi/task",
|
||||
method=HttpMethod.POST,
|
||||
request_model=TripoTextureModelRequest,
|
||||
response_model=TripoTaskResponse,
|
||||
),
|
||||
request=TripoTextureModelRequest(
|
||||
original_model_task_id=model_task_id,
|
||||
texture=texture,
|
||||
pbr=pbr,
|
||||
texture_seed=texture_seed,
|
||||
texture_quality=texture_quality,
|
||||
texture_alignment=texture_alignment
|
||||
),
|
||||
auth_kwargs=kwargs,
|
||||
).execute()
|
||||
return poll_until_finished(kwargs, response)
|
||||
|
||||
|
||||
class TripoRefineNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"model_task_id": ("MODEL_TASK_ID",),
|
||||
},
|
||||
"hidden": {
|
||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "MODEL_TASK_ID",)
|
||||
RETURN_NAMES = ("model_file", "model task_id")
|
||||
FUNCTION = "generate_mesh"
|
||||
CATEGORY = "api node/3d/Tripo"
|
||||
API_NODE = True
|
||||
OUTPUT_NODE = True
|
||||
AVERAGE_DURATION = 240
|
||||
|
||||
def generate_mesh(self, model_task_id, **kwargs):
|
||||
response = SynchronousOperation(
|
||||
endpoint=ApiEndpoint(
|
||||
path="/proxy/tripo/v2/openapi/task",
|
||||
method=HttpMethod.POST,
|
||||
request_model=TripoRefineModelRequest,
|
||||
response_model=TripoTaskResponse,
|
||||
),
|
||||
request=TripoRefineModelRequest(
|
||||
draft_model_task_id=model_task_id
|
||||
),
|
||||
auth_kwargs=kwargs,
|
||||
).execute()
|
||||
return poll_until_finished(kwargs, response)
|
||||
|
||||
|
||||
class TripoRigNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"original_model_task_id": ("MODEL_TASK_ID",),
|
||||
},
|
||||
"hidden": {
|
||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "RIG_TASK_ID")
|
||||
RETURN_NAMES = ("model_file", "rig task_id")
|
||||
FUNCTION = "generate_mesh"
|
||||
CATEGORY = "api node/3d/Tripo"
|
||||
API_NODE = True
|
||||
OUTPUT_NODE = True
|
||||
AVERAGE_DURATION = 180
|
||||
|
||||
def generate_mesh(self, original_model_task_id, **kwargs):
|
||||
response = SynchronousOperation(
|
||||
endpoint=ApiEndpoint(
|
||||
path="/proxy/tripo/v2/openapi/task",
|
||||
method=HttpMethod.POST,
|
||||
request_model=TripoAnimateRigRequest,
|
||||
response_model=TripoTaskResponse,
|
||||
),
|
||||
request=TripoAnimateRigRequest(
|
||||
original_model_task_id=original_model_task_id,
|
||||
out_format="glb",
|
||||
spec="tripo"
|
||||
),
|
||||
auth_kwargs=kwargs,
|
||||
).execute()
|
||||
return poll_until_finished(kwargs, response)
|
||||
|
||||
class TripoRetargetNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"original_model_task_id": ("RIG_TASK_ID",),
|
||||
"animation": ([
|
||||
"preset:idle",
|
||||
"preset:walk",
|
||||
"preset:climb",
|
||||
"preset:jump",
|
||||
"preset:slash",
|
||||
"preset:shoot",
|
||||
"preset:hurt",
|
||||
"preset:fall",
|
||||
"preset:turn",
|
||||
],),
|
||||
},
|
||||
"hidden": {
|
||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "RETARGET_TASK_ID")
|
||||
RETURN_NAMES = ("model_file", "retarget task_id")
|
||||
FUNCTION = "generate_mesh"
|
||||
CATEGORY = "api node/3d/Tripo"
|
||||
API_NODE = True
|
||||
OUTPUT_NODE = True
|
||||
AVERAGE_DURATION = 30
|
||||
|
||||
def generate_mesh(self, animation, original_model_task_id, **kwargs):
|
||||
response = SynchronousOperation(
|
||||
endpoint=ApiEndpoint(
|
||||
path="/proxy/tripo/v2/openapi/task",
|
||||
method=HttpMethod.POST,
|
||||
request_model=TripoAnimateRetargetRequest,
|
||||
response_model=TripoTaskResponse,
|
||||
),
|
||||
request=TripoAnimateRetargetRequest(
|
||||
original_model_task_id=original_model_task_id,
|
||||
animation=animation,
|
||||
out_format="glb",
|
||||
bake_animation=True
|
||||
),
|
||||
auth_kwargs=kwargs,
|
||||
).execute()
|
||||
return poll_until_finished(kwargs, response)
|
||||
|
||||
class TripoConversionNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"original_model_task_id": ("MODEL_TASK_ID,RIG_TASK_ID,RETARGET_TASK_ID",),
|
||||
"format": (["GLTF", "USDZ", "FBX", "OBJ", "STL", "3MF"],),
|
||||
},
|
||||
"optional": {
|
||||
"quad": ("BOOLEAN", {"default": False}),
|
||||
"face_limit": ("INT", {"min": -1, "max": 500000, "default": -1}),
|
||||
"texture_size": ("INT", {"min": 128, "max": 4096, "default": 4096}),
|
||||
"texture_format": (["BMP", "DPX", "HDR", "JPEG", "OPEN_EXR", "PNG", "TARGA", "TIFF", "WEBP"], {"default": "JPEG"})
|
||||
},
|
||||
"hidden": {
|
||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, input_types):
|
||||
# The min and max of input1 and input2 are still validated because
|
||||
# we didn't take `input1` or `input2` as arguments
|
||||
if input_types["original_model_task_id"] not in ("MODEL_TASK_ID", "RIG_TASK_ID", "RETARGET_TASK_ID"):
|
||||
return "original_model_task_id must be MODEL_TASK_ID, RIG_TASK_ID or RETARGET_TASK_ID type"
|
||||
return True
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "generate_mesh"
|
||||
CATEGORY = "api node/3d/Tripo"
|
||||
API_NODE = True
|
||||
OUTPUT_NODE = True
|
||||
AVERAGE_DURATION = 30
|
||||
|
||||
def generate_mesh(self, original_model_task_id, format, quad, face_limit, texture_size, texture_format, **kwargs):
|
||||
if not original_model_task_id:
|
||||
raise RuntimeError("original_model_task_id is required")
|
||||
response = SynchronousOperation(
|
||||
endpoint=ApiEndpoint(
|
||||
path="/proxy/tripo/v2/openapi/task",
|
||||
method=HttpMethod.POST,
|
||||
request_model=TripoConvertModelRequest,
|
||||
response_model=TripoTaskResponse,
|
||||
),
|
||||
request=TripoConvertModelRequest(
|
||||
original_model_task_id=original_model_task_id,
|
||||
format=format,
|
||||
quad=quad,
|
||||
face_limit=face_limit,
|
||||
texture_size=texture_size,
|
||||
texture_format=texture_format
|
||||
),
|
||||
auth_kwargs=kwargs,
|
||||
).execute()
|
||||
return poll_until_finished(kwargs, response)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"TripoTextToModelNode": TripoTextToModelNode,
|
||||
"TripoImageToModelNode": TripoImageToModelNode,
|
||||
"TripoMultiviewToModelNode": TripoMultiviewToModelNode,
|
||||
"TripoTextureNode": TripoTextureNode,
|
||||
"TripoRefineNode": TripoRefineNode,
|
||||
"TripoRigNode": TripoRigNode,
|
||||
"TripoRetargetNode": TripoRetargetNode,
|
||||
"TripoConversionNode": TripoConversionNode,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"TripoTextToModelNode": "Tripo: Text to Model",
|
||||
"TripoImageToModelNode": "Tripo: Image to Model",
|
||||
"TripoMultiviewToModelNode": "Tripo: Multiview to Model",
|
||||
"TripoTextureNode": "Tripo: Texture model",
|
||||
"TripoRefineNode": "Tripo: Refine Draft model",
|
||||
"TripoRigNode": "Tripo: Rig model",
|
||||
"TripoRetargetNode": "Tripo: Retarget rigged model",
|
||||
"TripoConversionNode": "Tripo: Convert model",
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user