feat(api-nodes): add TencentSmartTopology node

This commit is contained in:
bigcat88 2026-03-03 14:30:02 +02:00
parent dff0a4a158
commit e921120765
No known key found for this signature in database
GPG Key ID: 1F0BF0EC3CF22721
2 changed files with 112 additions and 8 deletions

View File

@ -66,13 +66,17 @@ class To3DProTaskQueryRequest(BaseModel):
JobId: str = Field(...)
class To3DUVFileInput(BaseModel):
class TaskFile3DInput(BaseModel):
Type: str = Field(..., description="File type: GLB, OBJ, or FBX")
Url: str = Field(...)
class To3DUVTaskRequest(BaseModel):
File: To3DUVFileInput = Field(...)
File: TaskFile3DInput = Field(...)
class To3DPartTaskRequest(BaseModel):
File: TaskFile3DInput = Field(...)
class TextureEditImageInfo(BaseModel):
@ -80,7 +84,13 @@ class TextureEditImageInfo(BaseModel):
class TextureEditTaskRequest(BaseModel):
File3D: To3DUVFileInput = Field(...)
File3D: TaskFile3DInput = Field(...)
Image: TextureEditImageInfo | None = Field(None)
Prompt: str | None = Field(None)
EnablePBR: bool | None = Field(None)
class SmartTopologyRequest(BaseModel):
File3D: TaskFile3DInput = Field(...)
PolygonType: str | None = Field(...)
FaceLevel: str | None = Field(...)

View File

@ -5,12 +5,14 @@ from comfy_api_nodes.apis.hunyuan3d import (
Hunyuan3DViewImage,
InputGenerateType,
ResultFile3D,
SmartTopologyRequest,
TaskFile3DInput,
TextureEditTaskRequest,
To3DPartTaskRequest,
To3DProTaskCreateResponse,
To3DProTaskQueryRequest,
To3DProTaskRequest,
To3DProTaskResultResponse,
To3DUVFileInput,
To3DUVTaskRequest,
)
from comfy_api_nodes.util import (
@ -375,7 +377,7 @@ class TencentModelTo3DUVNode(IO.ComfyNode):
ApiEndpoint(path="/proxy/tencent/hunyuan/3d-uv", method="POST"),
response_model=To3DProTaskCreateResponse,
data=To3DUVTaskRequest(
File=To3DUVFileInput(
File=TaskFile3DInput(
Type=file_format.upper(),
Url=await upload_3d_model_to_comfyapi(cls, model_3d, file_format),
)
@ -463,7 +465,7 @@ class Tencent3DTextureEditNode(IO.ComfyNode):
ApiEndpoint(path="/proxy/tencent/hunyuan/3d-texture-edit", method="POST"),
response_model=To3DProTaskCreateResponse,
data=TextureEditTaskRequest(
File3D=To3DUVFileInput(Type=file_format.upper(), Url=model_url),
File3D=TaskFile3DInput(Type=file_format.upper(), Url=model_url),
Prompt=prompt,
EnablePBR=True,
),
@ -538,8 +540,8 @@ class Tencent3DPartNode(IO.ComfyNode):
cls,
ApiEndpoint(path="/proxy/tencent/hunyuan/3d-part", method="POST"),
response_model=To3DProTaskCreateResponse,
data=To3DUVTaskRequest(
File=To3DUVFileInput(Type=file_format.upper(), Url=model_url),
data=To3DPartTaskRequest(
File=TaskFile3DInput(Type=file_format.upper(), Url=model_url),
),
is_rate_limited=_is_tencent_rate_limited,
)
@ -557,6 +559,97 @@ class Tencent3DPartNode(IO.ComfyNode):
)
class TencentSmartTopologyNode(IO.ComfyNode):
@classmethod
def define_schema(cls):
return IO.Schema(
node_id="TencentSmartTopologyNode",
display_name="Hunyuan3D: Smart Topology",
category="api node/3d/Tencent",
description="Perform smart retopology on a 3D model. "
"Supports GLB/OBJ formats; max 200MB; recommended for high-poly models.",
inputs=[
IO.MultiType.Input(
"model_3d",
types=[IO.File3DGLB, IO.File3DOBJ, IO.File3DAny],
tooltip="Input 3D model (GLB or OBJ)",
),
IO.Combo.Input(
"polygon_type",
options=["triangle", "quadrilateral"],
tooltip="Surface composition type.",
),
IO.Combo.Input(
"face_level",
options=["medium", "high", "low"],
tooltip="Polygon reduction level.",
),
IO.Int.Input(
"seed",
default=0,
min=0,
max=2147483647,
display_mode=IO.NumberDisplay.number,
control_after_generate=True,
tooltip="Seed controls whether the node should re-run; "
"results are non-deterministic regardless of seed.",
),
],
outputs=[
IO.File3DOBJ.Output(display_name="OBJ"),
],
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(expr='{"type":"usd","usd":1.0}'),
)
SUPPORTED_FORMATS = {"glb", "obj"}
@classmethod
async def execute(
cls,
model_3d: Types.File3D,
polygon_type: str,
face_level: str,
seed: int,
) -> IO.NodeOutput:
_ = seed
file_format = model_3d.format.lower()
if file_format not in cls.SUPPORTED_FORMATS:
raise ValueError(
f"Unsupported file format: '{file_format}'. " f"Supported: {', '.join(sorted(cls.SUPPORTED_FORMATS))}."
)
model_url = await upload_3d_model_to_comfyapi(cls, model_3d, file_format)
response = await sync_op(
cls,
ApiEndpoint(path="/proxy/tencent/hunyuan/3d-smart-topology", method="POST"),
response_model=To3DProTaskCreateResponse,
data=SmartTopologyRequest(
File3D=TaskFile3DInput(Type=file_format.upper(), Url=model_url),
PolygonType=polygon_type,
FaceLevel=face_level,
),
is_rate_limited=_is_tencent_rate_limited,
)
if response.Error:
raise ValueError(f"Task creation failed: [{response.Error.Code}] {response.Error.Message}")
result = await poll_op(
cls,
ApiEndpoint(path="/proxy/tencent/hunyuan/3d-smart-topology/query", method="POST"),
data=To3DProTaskQueryRequest(JobId=response.JobId),
response_model=To3DProTaskResultResponse,
status_extractor=lambda r: r.Status,
)
return IO.NodeOutput(
await download_url_to_file_3d(get_file_from_response(result.ResultFile3Ds, "obj").Url, "obj"),
)
class TencentHunyuan3DExtension(ComfyExtension):
@override
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
@ -566,6 +659,7 @@ class TencentHunyuan3DExtension(ComfyExtension):
# TencentModelTo3DUVNode,
# Tencent3DTextureEditNode,
Tencent3DPartNode,
TencentSmartTopologyNode,
]