mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-06-02 01:22:11 +08:00

* Add Ideogram generate node. * Add staging api. * Add API_NODE and common error for missing auth token (#5) * Add Minimax Video Generation + Async Task queue polling example (#6) * [Minimax] Show video preview and embed workflow in ouput (#7) * Remove uv.lock * Remove polling operations. * Revert "Remove polling operations." This reverts commit 8415404ce8fbc0262b7de54fc700c5c8854a34fc. * Update stubs. * Added Ideogram and Minimax back in. * Added initial BFL Flux 1.1 [pro] Ultra node (#11) * Manually add BFL polling status response schema (#15) * Add function for uploading files. (#18) * Add Luma nodes (#16) Co-authored-by: Robin Huang <robin.j.huang@gmail.com> * Refactor util functions (#20) * Add rest of Luma node functionality (#19) Co-authored-by: Robin Huang <robin.j.huang@gmail.com> * Fix image_luma_ref not working (#28) Co-authored-by: Robin Huang <robin.j.huang@gmail.com> * [Bug] Remove duplicated option T2V-01 in MinimaxTextToVideoNode (#31) * add veo2, bump av req (#32) * Add Recraft nodes (#29) * Add Kling Nodes (#12) * Add Camera Concepts (luma_concepts) to Luma Video nodes (#33) Co-authored-by: Robin Huang <robin.j.huang@gmail.com> * Add Runway nodes (#17) * Convert Minimax node to use VIDEO output type (#34) * Standard `CATEGORY` system for api nodes (#35) * Set `Content-Type` header when uploading files (#36) * add better error propagation to veo2 (#37) * Add Realistic Image and Logo Raster styles for Recraft v3 (#38) * Fix runway image upload and progress polling (#39) * Fix image upload for Luma: only include `Content-Type` header field if it's set explicitly (#40) * Moved Luma nodes to nodes_luma.py (#47) * Moved Recraft nodes to nodes_recraft.py (#48) * Move and fix BFL nodes to node_bfl.py (#49) * Move and edit Minimax node to nodes_minimax.py (#50) * Add Recraft Text to Vector node, add Save SVG node to handle its output (#53) * Added pixverse_template support to Pixverse Text to Video node (#54) * Added Recraft Controls + Recraft Color RGB nodes (#57) * split remaining nodes out of nodes_api, make utility lib, refactor ideogram (#61) * Set request type explicitly (#66) * Add `control_after_generate` to all seed inputs (#69) * Fix bug: deleting `Content-Type` when property does not exist (#73) * Add Pixverse and updated Kling types (#75) * Added Recraft Style - Infinite Style Library node (#82) * add ideogram v3 (#83) * [Kling] Split Camera Control config to its own node (#81) * Add Pika i2v and t2v nodes (#52) * Remove Runway nodes (#88) * Fix: Prompt text can't be validated in Kling nodes when using primitive nodes (#90) * Update Pika Duration and Resolution options (#94) * Removed Infinite Style Library until later (#99) * fix multi image return (#101) close #96 * Serve SVG files directly (#107) * Add a bunch of nodes, 3 ready to use, the rest waiting for endpoint support (#108) * Revert "Serve SVG files directly" (#111) * Expose 4 remaining Recraft nodes (#112) * [Kling] Add `Duration` and `Video ID` outputs (#105) * Add Kling nodes: camera control, start-end frame, lip-sync, video extend (#115) * Fix error for Recraft ImageToImage error for nonexistent random_seed param (#118) * Add remaining Pika nodes (#119) * Make controls input work for Recraft Image to Image node (#120) * Fix: Nested `AnyUrl` in request model cannot be serialized (Kling, Runway) (#129) * Show errors and API output URLs to the user (change log levels) (#131) * Apply small fixes and most prompt validation (if needed to avoid API error) (#135) * Node name/category modifications (#140) * Add back Recraft Style - Infinite Style Library node (#141) * [Kling] Fix: Correct/verify supported subset of input combos in Kling nodes (#149) * Remove pixverse_template from PixVerse Transition Video node (#155) * Use 3.9 compat syntax (#164) * Handle Comfy API key based authorizaton (#167) Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com> * [BFL] Print download URL of successful task result directly on nodes (#175) * Show output URL and progress text on Pika nodes (#168) * [Ideogram] Print download URL of successful task result directly on nodes (#176) * [Kling] Print download URL of successful task result directly on nodes (#181) * Merge upstream may 14 25 (#186) Co-authored-by: comfyanonymous <comfyanonymous@protonmail.com> Co-authored-by: AustinMroz <austinmroz@utexas.edu> Co-authored-by: comfyanonymous <121283862+comfyanonymous@users.noreply.github.com> Co-authored-by: Benjamin Lu <benceruleanlu@proton.me> Co-authored-by: Andrew Kvochko <kvochko@users.noreply.github.com> Co-authored-by: Pam <42671363+pamparamm@users.noreply.github.com> Co-authored-by: chaObserv <154517000+chaObserv@users.noreply.github.com> Co-authored-by: Yoland Yan <4950057+yoland68@users.noreply.github.com> Co-authored-by: guill <guill@users.noreply.github.com> Co-authored-by: Chenlei Hu <hcl@comfy.org> Co-authored-by: Terry Jia <terryjia88@gmail.com> Co-authored-by: Silver <65376327+silveroxides@users.noreply.github.com> Co-authored-by: catboxanon <122327233+catboxanon@users.noreply.github.com> Co-authored-by: liesen <liesen.dev@gmail.com> Co-authored-by: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com> Co-authored-by: Robin Huang <robin.j.huang@gmail.com> Co-authored-by: thot experiment <94414189+thot-experiment@users.noreply.github.com> Co-authored-by: blepping <157360029+blepping@users.noreply.github.com> * Update instructions on how to develop API Nodes. (#171) * Add Runway FLF and I2V nodes (#187) * Add OpenAI chat node (#188) * Update README. * Add Google Gemini API node (#191) * Add Runway Gen 4 Text to Image Node (#193) * [Runway, Gemini] Update node display names and attributes (#194) * Update path from "image-to-video" to "image_to_video" (#197) * [Runway] Split I2V nodes into separate gen3 and gen4 nodes (#198) * Update runway i2v ratio enum (#201) * Rodin3D: implement Rodin3D API Nodes (#190) Co-authored-by: WhiteGiven <c15838568211@163.com> Co-authored-by: Robin Huang <robin.j.huang@gmail.com> * Add Tripo Nodes. (#189) Co-authored-by: Robin Huang <robin.j.huang@gmail.com> * Change casing of categories "3D" => "3d" (#208) * [tripo] fix negtive_prompt and mv2model (#212) * [tripo] set default param to None (#215) * Add description and tooltip to Tripo Refine model. (#218) * Update. * Fix rebase errors. * Fix rebase errors. * Update templates. * Bump frontend. * Add file type info for file inputs. --------- Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com> Co-authored-by: Chenlei Hu <hcl@comfy.org> Co-authored-by: thot experiment <94414189+thot-experiment@users.noreply.github.com> Co-authored-by: comfyanonymous <comfyanonymous@protonmail.com> Co-authored-by: AustinMroz <austinmroz@utexas.edu> Co-authored-by: comfyanonymous <121283862+comfyanonymous@users.noreply.github.com> Co-authored-by: Benjamin Lu <benceruleanlu@proton.me> Co-authored-by: Andrew Kvochko <kvochko@users.noreply.github.com> Co-authored-by: Pam <42671363+pamparamm@users.noreply.github.com> Co-authored-by: chaObserv <154517000+chaObserv@users.noreply.github.com> Co-authored-by: Yoland Yan <4950057+yoland68@users.noreply.github.com> Co-authored-by: guill <guill@users.noreply.github.com> Co-authored-by: Terry Jia <terryjia88@gmail.com> Co-authored-by: Silver <65376327+silveroxides@users.noreply.github.com> Co-authored-by: catboxanon <122327233+catboxanon@users.noreply.github.com> Co-authored-by: liesen <liesen.dev@gmail.com> Co-authored-by: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Co-authored-by: blepping <157360029+blepping@users.noreply.github.com> Co-authored-by: Changrz <51637999+WhiteGiven@users.noreply.github.com> Co-authored-by: WhiteGiven <c15838568211@163.com> Co-authored-by: seed93 <liangding1990@163.com>
575 lines
22 KiB
Python
575 lines
22 KiB
Python
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,
|
|
TripoFileEmptyReference,
|
|
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 if negative_prompt else None,
|
|
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
|
|
}
|
|
if image_left is None and image_back is None and image_right is None:
|
|
raise RuntimeError("At least one of left, back, or right image must be provided for multiview")
|
|
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(TripoFileEmptyReference())
|
|
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,
|
|
files=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", {
|
|
"tooltip": "Must be a v1.4 Tripo model"
|
|
}),
|
|
},
|
|
"hidden": {
|
|
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
|
"comfy_api_key": "API_KEY_COMFY_ORG",
|
|
"unique_id": "UNIQUE_ID",
|
|
},
|
|
}
|
|
|
|
DESCRIPTION = "Refine a draft model created by v1.4 Tripo models only."
|
|
|
|
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 if quad else None,
|
|
face_limit=face_limit if face_limit != -1 else None,
|
|
texture_size=texture_size if texture_size != 4096 else None,
|
|
texture_format=texture_format if texture_format != "JPEG" else None
|
|
),
|
|
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",
|
|
}
|