mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-15 16:02:32 +08:00
Compare commits
1 Commits
d2910852cd
...
ad29575cc6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad29575cc6 |
@ -332,12 +332,6 @@ def model_lora_keys_unet(model, key_map={}):
|
|||||||
key_map["{}".format(key_lora)] = k
|
key_map["{}".format(key_lora)] = k
|
||||||
key_map["transformer.{}".format(key_lora)] = k
|
key_map["transformer.{}".format(key_lora)] = k
|
||||||
|
|
||||||
if isinstance(model, comfy.model_base.ACEStep15):
|
|
||||||
for k in sdk:
|
|
||||||
if k.startswith("diffusion_model.decoder.") and k.endswith(".weight"):
|
|
||||||
key_lora = k[len("diffusion_model.decoder."):-len(".weight")]
|
|
||||||
key_map["base_model.model.{}".format(key_lora)] = k # Official base model loras
|
|
||||||
|
|
||||||
return key_map
|
return key_map
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -554,8 +554,6 @@ class VAE:
|
|||||||
elif "decoder.layers.1.layers.0.beta" in sd:
|
elif "decoder.layers.1.layers.0.beta" in sd:
|
||||||
config = {}
|
config = {}
|
||||||
param_key = None
|
param_key = None
|
||||||
self.upscale_ratio = 2048
|
|
||||||
self.downscale_ratio = 2048
|
|
||||||
if "decoder.layers.2.layers.1.weight_v" in sd:
|
if "decoder.layers.2.layers.1.weight_v" in sd:
|
||||||
param_key = "decoder.layers.2.layers.1.weight_v"
|
param_key = "decoder.layers.2.layers.1.weight_v"
|
||||||
if "decoder.layers.2.layers.1.parametrizations.weight.original1" in sd:
|
if "decoder.layers.2.layers.1.parametrizations.weight.original1" in sd:
|
||||||
@ -564,8 +562,6 @@ class VAE:
|
|||||||
if sd[param_key].shape[-1] == 12:
|
if sd[param_key].shape[-1] == 12:
|
||||||
config["strides"] = [2, 4, 4, 6, 10]
|
config["strides"] = [2, 4, 4, 6, 10]
|
||||||
self.audio_sample_rate = 48000
|
self.audio_sample_rate = 48000
|
||||||
self.upscale_ratio = 1920
|
|
||||||
self.downscale_ratio = 1920
|
|
||||||
|
|
||||||
self.first_stage_model = AudioOobleckVAE(**config)
|
self.first_stage_model = AudioOobleckVAE(**config)
|
||||||
self.memory_used_encode = lambda shape, dtype: (1000 * shape[2]) * model_management.dtype_size(dtype)
|
self.memory_used_encode = lambda shape, dtype: (1000 * shape[2]) * model_management.dtype_size(dtype)
|
||||||
@ -573,6 +569,8 @@ class VAE:
|
|||||||
self.latent_channels = 64
|
self.latent_channels = 64
|
||||||
self.output_channels = 2
|
self.output_channels = 2
|
||||||
self.pad_channel_value = "replicate"
|
self.pad_channel_value = "replicate"
|
||||||
|
self.upscale_ratio = 2048
|
||||||
|
self.downscale_ratio = 2048
|
||||||
self.latent_dim = 1
|
self.latent_dim = 1
|
||||||
self.process_output = lambda audio: audio
|
self.process_output = lambda audio: audio
|
||||||
self.process_input = lambda audio: audio
|
self.process_input = lambda audio: audio
|
||||||
@ -872,7 +870,7 @@ class VAE:
|
|||||||
/ 3.0)
|
/ 3.0)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def decode_tiled_1d(self, samples, tile_x=256, overlap=32):
|
def decode_tiled_1d(self, samples, tile_x=128, overlap=32):
|
||||||
if samples.ndim == 3:
|
if samples.ndim == 3:
|
||||||
decode_fn = lambda a: self.first_stage_model.decode(a.to(self.vae_dtype).to(self.device)).float()
|
decode_fn = lambda a: self.first_stage_model.decode(a.to(self.vae_dtype).to(self.device)).float()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import comfy.text_encoders.llama
|
|||||||
from comfy import sd1_clip
|
from comfy import sd1_clip
|
||||||
import torch
|
import torch
|
||||||
import math
|
import math
|
||||||
import comfy.utils
|
|
||||||
|
|
||||||
|
|
||||||
def sample_manual_loop_no_classes(
|
def sample_manual_loop_no_classes(
|
||||||
@ -43,8 +42,6 @@ def sample_manual_loop_no_classes(
|
|||||||
for x in range(model_config.num_hidden_layers):
|
for x in range(model_config.num_hidden_layers):
|
||||||
past_key_values.append((torch.empty([embeds.shape[0], model_config.num_key_value_heads, embeds.shape[1] + min_tokens, model_config.head_dim], device=device, dtype=execution_dtype), torch.empty([embeds.shape[0], model_config.num_key_value_heads, embeds.shape[1] + min_tokens, model_config.head_dim], device=device, dtype=execution_dtype), 0))
|
past_key_values.append((torch.empty([embeds.shape[0], model_config.num_key_value_heads, embeds.shape[1] + min_tokens, model_config.head_dim], device=device, dtype=execution_dtype), torch.empty([embeds.shape[0], model_config.num_key_value_heads, embeds.shape[1] + min_tokens, model_config.head_dim], device=device, dtype=execution_dtype), 0))
|
||||||
|
|
||||||
progress_bar = comfy.utils.ProgressBar(max_new_tokens)
|
|
||||||
|
|
||||||
for step in range(max_new_tokens):
|
for step in range(max_new_tokens):
|
||||||
outputs = model.transformer(None, attention_mask, embeds=embeds.to(execution_dtype), num_tokens=num_tokens, intermediate_output=None, dtype=execution_dtype, embeds_info=embeds_info, past_key_values=past_key_values)
|
outputs = model.transformer(None, attention_mask, embeds=embeds.to(execution_dtype), num_tokens=num_tokens, intermediate_output=None, dtype=execution_dtype, embeds_info=embeds_info, past_key_values=past_key_values)
|
||||||
next_token_logits = model.transformer.logits(outputs[0])[:, -1]
|
next_token_logits = model.transformer.logits(outputs[0])[:, -1]
|
||||||
@ -57,9 +54,8 @@ def sample_manual_loop_no_classes(
|
|||||||
if eos_token_id is not None and eos_token_id < audio_start_id and min_tokens < step:
|
if eos_token_id is not None and eos_token_id < audio_start_id and min_tokens < step:
|
||||||
eos_score = cfg_logits[:, eos_token_id].clone()
|
eos_score = cfg_logits[:, eos_token_id].clone()
|
||||||
|
|
||||||
remove_logit_value = torch.finfo(cfg_logits.dtype).min
|
|
||||||
# Only generate audio tokens
|
# Only generate audio tokens
|
||||||
cfg_logits[:, :audio_start_id] = remove_logit_value
|
cfg_logits[:, :audio_start_id] = float('-inf')
|
||||||
|
|
||||||
if eos_token_id is not None and eos_token_id < audio_start_id and min_tokens < step:
|
if eos_token_id is not None and eos_token_id < audio_start_id and min_tokens < step:
|
||||||
cfg_logits[:, eos_token_id] = eos_score
|
cfg_logits[:, eos_token_id] = eos_score
|
||||||
@ -67,7 +63,7 @@ def sample_manual_loop_no_classes(
|
|||||||
if top_k is not None and top_k > 0:
|
if top_k is not None and top_k > 0:
|
||||||
top_k_vals, _ = torch.topk(cfg_logits, top_k)
|
top_k_vals, _ = torch.topk(cfg_logits, top_k)
|
||||||
min_val = top_k_vals[..., -1, None]
|
min_val = top_k_vals[..., -1, None]
|
||||||
cfg_logits[cfg_logits < min_val] = remove_logit_value
|
cfg_logits[cfg_logits < min_val] = float('-inf')
|
||||||
|
|
||||||
if top_p is not None and top_p < 1.0:
|
if top_p is not None and top_p < 1.0:
|
||||||
sorted_logits, sorted_indices = torch.sort(cfg_logits, descending=True)
|
sorted_logits, sorted_indices = torch.sort(cfg_logits, descending=True)
|
||||||
@ -76,7 +72,7 @@ def sample_manual_loop_no_classes(
|
|||||||
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
||||||
sorted_indices_to_remove[..., 0] = 0
|
sorted_indices_to_remove[..., 0] = 0
|
||||||
indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)
|
indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)
|
||||||
cfg_logits[indices_to_remove] = remove_logit_value
|
cfg_logits[indices_to_remove] = float('-inf')
|
||||||
|
|
||||||
if temperature > 0:
|
if temperature > 0:
|
||||||
cfg_logits = cfg_logits / temperature
|
cfg_logits = cfg_logits / temperature
|
||||||
@ -94,7 +90,6 @@ def sample_manual_loop_no_classes(
|
|||||||
attention_mask = torch.cat([attention_mask, torch.ones((2, 1), device=device, dtype=attention_mask.dtype)], dim=1)
|
attention_mask = torch.cat([attention_mask, torch.ones((2, 1), device=device, dtype=attention_mask.dtype)], dim=1)
|
||||||
|
|
||||||
output_audio_codes.append(token - audio_start_id)
|
output_audio_codes.append(token - audio_start_id)
|
||||||
progress_bar.update_absolute(step)
|
|
||||||
|
|
||||||
return output_audio_codes
|
return output_audio_codes
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import math
|
|||||||
|
|
||||||
from comfy.ldm.modules.attention import optimized_attention_for_device
|
from comfy.ldm.modules.attention import optimized_attention_for_device
|
||||||
import comfy.model_management
|
import comfy.model_management
|
||||||
import comfy.ops
|
|
||||||
import comfy.ldm.common_dit
|
import comfy.ldm.common_dit
|
||||||
import comfy.clip_model
|
import comfy.clip_model
|
||||||
|
|
||||||
@ -628,10 +627,10 @@ class Llama2_(nn.Module):
|
|||||||
mask = None
|
mask = None
|
||||||
if attention_mask is not None:
|
if attention_mask is not None:
|
||||||
mask = 1.0 - attention_mask.to(x.dtype).reshape((attention_mask.shape[0], 1, -1, attention_mask.shape[-1])).expand(attention_mask.shape[0], 1, seq_len, attention_mask.shape[-1])
|
mask = 1.0 - attention_mask.to(x.dtype).reshape((attention_mask.shape[0], 1, -1, attention_mask.shape[-1])).expand(attention_mask.shape[0], 1, seq_len, attention_mask.shape[-1])
|
||||||
mask = mask.masked_fill(mask.to(torch.bool), torch.finfo(x.dtype).min)
|
mask = mask.masked_fill(mask.to(torch.bool), float("-inf"))
|
||||||
|
|
||||||
if seq_len > 1:
|
if seq_len > 1:
|
||||||
causal_mask = torch.empty(past_len + seq_len, past_len + seq_len, dtype=x.dtype, device=x.device).fill_(torch.finfo(x.dtype).min).triu_(1)
|
causal_mask = torch.empty(past_len + seq_len, past_len + seq_len, dtype=x.dtype, device=x.device).fill_(float("-inf")).triu_(1)
|
||||||
if mask is not None:
|
if mask is not None:
|
||||||
mask += causal_mask
|
mask += causal_mask
|
||||||
else:
|
else:
|
||||||
@ -795,19 +794,7 @@ class Qwen3_2B_ACE15_lm(BaseLlama, torch.nn.Module):
|
|||||||
self.dtype = dtype
|
self.dtype = dtype
|
||||||
|
|
||||||
def logits(self, x):
|
def logits(self, x):
|
||||||
input = x[:, -1:]
|
return torch.nn.functional.linear(x[:, -1:], self.model.embed_tokens.weight.to(x), None)
|
||||||
module = self.model.embed_tokens
|
|
||||||
|
|
||||||
offload_stream = None
|
|
||||||
if module.comfy_cast_weights:
|
|
||||||
weight, _, offload_stream = comfy.ops.cast_bias_weight(module, input, offloadable=True)
|
|
||||||
else:
|
|
||||||
weight = self.model.embed_tokens.weight.to(x)
|
|
||||||
|
|
||||||
x = torch.nn.functional.linear(input, weight, None)
|
|
||||||
|
|
||||||
comfy.ops.uncast_bias_weight(module, weight, None, offload_stream)
|
|
||||||
return x
|
|
||||||
|
|
||||||
class Qwen3_4B(BaseLlama, torch.nn.Module):
|
class Qwen3_4B(BaseLlama, torch.nn.Module):
|
||||||
def __init__(self, config_dict, dtype, device, operations):
|
def __init__(self, config_dict, dtype, device, operations):
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from comfy_api.internal.singleton import ProxiedSingleton
|
|||||||
from comfy_api.internal.async_to_sync import create_sync_class
|
from comfy_api.internal.async_to_sync import create_sync_class
|
||||||
from ._input import ImageInput, AudioInput, MaskInput, LatentInput, VideoInput
|
from ._input import ImageInput, AudioInput, MaskInput, LatentInput, VideoInput
|
||||||
from ._input_impl import VideoFromFile, VideoFromComponents
|
from ._input_impl import VideoFromFile, VideoFromComponents
|
||||||
from ._util import VideoCodec, VideoContainer, VideoComponents, MESH, VOXEL, File3D
|
from ._util import VideoCodec, VideoContainer, VideoComponents, MESH, VOXEL
|
||||||
from . import _io_public as io
|
from . import _io_public as io
|
||||||
from . import _ui_public as ui
|
from . import _ui_public as ui
|
||||||
from comfy_execution.utils import get_executing_context
|
from comfy_execution.utils import get_executing_context
|
||||||
@ -105,7 +105,6 @@ class Types:
|
|||||||
VideoComponents = VideoComponents
|
VideoComponents = VideoComponents
|
||||||
MESH = MESH
|
MESH = MESH
|
||||||
VOXEL = VOXEL
|
VOXEL = VOXEL
|
||||||
File3D = File3D
|
|
||||||
|
|
||||||
ComfyAPI = ComfyAPI_latest
|
ComfyAPI = ComfyAPI_latest
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ if TYPE_CHECKING:
|
|||||||
from comfy_api.internal import (_ComfyNodeInternal, _NodeOutputInternal, classproperty, copy_class, first_real_override, is_class,
|
from comfy_api.internal import (_ComfyNodeInternal, _NodeOutputInternal, classproperty, copy_class, first_real_override, is_class,
|
||||||
prune_dict, shallow_clone_class)
|
prune_dict, shallow_clone_class)
|
||||||
from comfy_execution.graph_utils import ExecutionBlocker
|
from comfy_execution.graph_utils import ExecutionBlocker
|
||||||
from ._util import MESH, VOXEL, SVG as _SVG, File3D
|
from ._util import MESH, VOXEL, SVG as _SVG
|
||||||
|
|
||||||
|
|
||||||
class FolderType(str, Enum):
|
class FolderType(str, Enum):
|
||||||
@ -667,49 +667,6 @@ class Voxel(ComfyTypeIO):
|
|||||||
class Mesh(ComfyTypeIO):
|
class Mesh(ComfyTypeIO):
|
||||||
Type = MESH
|
Type = MESH
|
||||||
|
|
||||||
|
|
||||||
@comfytype(io_type="FILE_3D")
|
|
||||||
class File3DAny(ComfyTypeIO):
|
|
||||||
"""General 3D file type - accepts any supported 3D format."""
|
|
||||||
Type = File3D
|
|
||||||
|
|
||||||
|
|
||||||
@comfytype(io_type="FILE_3D_GLB")
|
|
||||||
class File3DGLB(ComfyTypeIO):
|
|
||||||
"""GLB format 3D file - binary glTF, best for web and cross-platform."""
|
|
||||||
Type = File3D
|
|
||||||
|
|
||||||
|
|
||||||
@comfytype(io_type="FILE_3D_GLTF")
|
|
||||||
class File3DGLTF(ComfyTypeIO):
|
|
||||||
"""GLTF format 3D file - JSON-based glTF with external resources."""
|
|
||||||
Type = File3D
|
|
||||||
|
|
||||||
|
|
||||||
@comfytype(io_type="FILE_3D_FBX")
|
|
||||||
class File3DFBX(ComfyTypeIO):
|
|
||||||
"""FBX format 3D file - best for game engines and animation."""
|
|
||||||
Type = File3D
|
|
||||||
|
|
||||||
|
|
||||||
@comfytype(io_type="FILE_3D_OBJ")
|
|
||||||
class File3DOBJ(ComfyTypeIO):
|
|
||||||
"""OBJ format 3D file - simple geometry format."""
|
|
||||||
Type = File3D
|
|
||||||
|
|
||||||
|
|
||||||
@comfytype(io_type="FILE_3D_STL")
|
|
||||||
class File3DSTL(ComfyTypeIO):
|
|
||||||
"""STL format 3D file - best for 3D printing."""
|
|
||||||
Type = File3D
|
|
||||||
|
|
||||||
|
|
||||||
@comfytype(io_type="FILE_3D_USDZ")
|
|
||||||
class File3DUSDZ(ComfyTypeIO):
|
|
||||||
"""USDZ format 3D file - Apple AR format."""
|
|
||||||
Type = File3D
|
|
||||||
|
|
||||||
|
|
||||||
@comfytype(io_type="HOOKS")
|
@comfytype(io_type="HOOKS")
|
||||||
class Hooks(ComfyTypeIO):
|
class Hooks(ComfyTypeIO):
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -2080,13 +2037,6 @@ __all__ = [
|
|||||||
"LossMap",
|
"LossMap",
|
||||||
"Voxel",
|
"Voxel",
|
||||||
"Mesh",
|
"Mesh",
|
||||||
"File3DAny",
|
|
||||||
"File3DGLB",
|
|
||||||
"File3DGLTF",
|
|
||||||
"File3DFBX",
|
|
||||||
"File3DOBJ",
|
|
||||||
"File3DSTL",
|
|
||||||
"File3DUSDZ",
|
|
||||||
"Hooks",
|
"Hooks",
|
||||||
"HookKeyframes",
|
"HookKeyframes",
|
||||||
"TimestepsRange",
|
"TimestepsRange",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from .video_types import VideoContainer, VideoCodec, VideoComponents
|
from .video_types import VideoContainer, VideoCodec, VideoComponents
|
||||||
from .geometry_types import VOXEL, MESH, File3D
|
from .geometry_types import VOXEL, MESH
|
||||||
from .image_types import SVG
|
from .image_types import SVG
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -9,6 +9,5 @@ __all__ = [
|
|||||||
"VideoComponents",
|
"VideoComponents",
|
||||||
"VOXEL",
|
"VOXEL",
|
||||||
"MESH",
|
"MESH",
|
||||||
"File3D",
|
|
||||||
"SVG",
|
"SVG",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,8 +1,3 @@
|
|||||||
import shutil
|
|
||||||
from io import BytesIO
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import IO
|
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
|
||||||
@ -15,75 +10,3 @@ class MESH:
|
|||||||
def __init__(self, vertices: torch.Tensor, faces: torch.Tensor):
|
def __init__(self, vertices: torch.Tensor, faces: torch.Tensor):
|
||||||
self.vertices = vertices
|
self.vertices = vertices
|
||||||
self.faces = faces
|
self.faces = faces
|
||||||
|
|
||||||
|
|
||||||
class File3D:
|
|
||||||
"""Class representing a 3D file from a file path or binary stream.
|
|
||||||
|
|
||||||
Supports both disk-backed (file path) and memory-backed (BytesIO) storage.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, source: str | IO[bytes], file_format: str = ""):
|
|
||||||
self._source = source
|
|
||||||
self._format = file_format or self._infer_format()
|
|
||||||
|
|
||||||
def _infer_format(self) -> str:
|
|
||||||
if isinstance(self._source, str):
|
|
||||||
return Path(self._source).suffix.lstrip(".").lower()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def format(self) -> str:
|
|
||||||
return self._format
|
|
||||||
|
|
||||||
@format.setter
|
|
||||||
def format(self, value: str) -> None:
|
|
||||||
self._format = value.lstrip(".").lower() if value else ""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_disk_backed(self) -> bool:
|
|
||||||
return isinstance(self._source, str)
|
|
||||||
|
|
||||||
def get_source(self) -> str | IO[bytes]:
|
|
||||||
if isinstance(self._source, str):
|
|
||||||
return self._source
|
|
||||||
if hasattr(self._source, "seek"):
|
|
||||||
self._source.seek(0)
|
|
||||||
return self._source
|
|
||||||
|
|
||||||
def get_data(self) -> BytesIO:
|
|
||||||
if isinstance(self._source, str):
|
|
||||||
with open(self._source, "rb") as f:
|
|
||||||
result = BytesIO(f.read())
|
|
||||||
return result
|
|
||||||
if hasattr(self._source, "seek"):
|
|
||||||
self._source.seek(0)
|
|
||||||
if isinstance(self._source, BytesIO):
|
|
||||||
return self._source
|
|
||||||
return BytesIO(self._source.read())
|
|
||||||
|
|
||||||
def save_to(self, path: str) -> str:
|
|
||||||
dest = Path(path)
|
|
||||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
if isinstance(self._source, str):
|
|
||||||
if Path(self._source).resolve() != dest.resolve():
|
|
||||||
shutil.copy2(self._source, dest)
|
|
||||||
else:
|
|
||||||
if hasattr(self._source, "seek"):
|
|
||||||
self._source.seek(0)
|
|
||||||
with open(dest, "wb") as f:
|
|
||||||
f.write(self._source.read())
|
|
||||||
return str(dest)
|
|
||||||
|
|
||||||
def get_bytes(self) -> bytes:
|
|
||||||
if isinstance(self._source, str):
|
|
||||||
return Path(self._source).read_bytes()
|
|
||||||
if hasattr(self._source, "seek"):
|
|
||||||
self._source.seek(0)
|
|
||||||
return self._source.read()
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
if isinstance(self._source, str):
|
|
||||||
return f"File3D(source={self._source!r}, format={self._format!r})"
|
|
||||||
return f"File3D(<stream>, format={self._format!r})"
|
|
||||||
|
|||||||
@ -109,19 +109,14 @@ class MeshyTextureRequest(BaseModel):
|
|||||||
|
|
||||||
class MeshyModelsUrls(BaseModel):
|
class MeshyModelsUrls(BaseModel):
|
||||||
glb: str = Field("")
|
glb: str = Field("")
|
||||||
fbx: str = Field("")
|
|
||||||
usdz: str = Field("")
|
|
||||||
obj: str = Field("")
|
|
||||||
|
|
||||||
|
|
||||||
class MeshyRiggedModelsUrls(BaseModel):
|
class MeshyRiggedModelsUrls(BaseModel):
|
||||||
rigged_character_glb_url: str = Field("")
|
rigged_character_glb_url: str = Field("")
|
||||||
rigged_character_fbx_url: str = Field("")
|
|
||||||
|
|
||||||
|
|
||||||
class MeshyAnimatedModelsUrls(BaseModel):
|
class MeshyAnimatedModelsUrls(BaseModel):
|
||||||
animation_glb_url: str = Field("")
|
animation_glb_url: str = Field("")
|
||||||
animation_fbx_url: str = Field("")
|
|
||||||
|
|
||||||
|
|
||||||
class MeshyResultTextureUrls(BaseModel):
|
class MeshyResultTextureUrls(BaseModel):
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
from comfy_api.latest import IO, ComfyExtension, Input
|
from comfy_api.latest import IO, ComfyExtension, Input
|
||||||
@ -12,7 +14,7 @@ from comfy_api_nodes.apis.hunyuan3d import (
|
|||||||
)
|
)
|
||||||
from comfy_api_nodes.util import (
|
from comfy_api_nodes.util import (
|
||||||
ApiEndpoint,
|
ApiEndpoint,
|
||||||
download_url_to_file_3d,
|
download_url_to_bytesio,
|
||||||
downscale_image_tensor_by_max_side,
|
downscale_image_tensor_by_max_side,
|
||||||
poll_op,
|
poll_op,
|
||||||
sync_op,
|
sync_op,
|
||||||
@ -20,13 +22,14 @@ from comfy_api_nodes.util import (
|
|||||||
validate_image_dimensions,
|
validate_image_dimensions,
|
||||||
validate_string,
|
validate_string,
|
||||||
)
|
)
|
||||||
|
from folder_paths import get_output_directory
|
||||||
|
|
||||||
|
|
||||||
def get_file_from_response(response_objs: list[ResultFile3D], file_type: str) -> ResultFile3D | None:
|
def get_glb_obj_from_response(response_objs: list[ResultFile3D]) -> ResultFile3D:
|
||||||
for i in response_objs:
|
for i in response_objs:
|
||||||
if i.Type.lower() == file_type.lower():
|
if i.Type.lower() == "glb":
|
||||||
return i
|
return i
|
||||||
return None
|
raise ValueError("No GLB file found in response. Please report this to the developers.")
|
||||||
|
|
||||||
|
|
||||||
class TencentTextToModelNode(IO.ComfyNode):
|
class TencentTextToModelNode(IO.ComfyNode):
|
||||||
@ -71,9 +74,7 @@ class TencentTextToModelNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
IO.File3DOBJ.Output(display_name="OBJ"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -123,20 +124,19 @@ class TencentTextToModelNode(IO.ComfyNode):
|
|||||||
)
|
)
|
||||||
if response.Error:
|
if response.Error:
|
||||||
raise ValueError(f"Task creation failed with code {response.Error.Code}: {response.Error.Message}")
|
raise ValueError(f"Task creation failed with code {response.Error.Code}: {response.Error.Message}")
|
||||||
task_id = response.JobId
|
|
||||||
result = await poll_op(
|
result = await poll_op(
|
||||||
cls,
|
cls,
|
||||||
ApiEndpoint(path="/proxy/tencent/hunyuan/3d-pro/query", method="POST"),
|
ApiEndpoint(path="/proxy/tencent/hunyuan/3d-pro/query", method="POST"),
|
||||||
data=To3DProTaskQueryRequest(JobId=task_id),
|
data=To3DProTaskQueryRequest(JobId=response.JobId),
|
||||||
response_model=To3DProTaskResultResponse,
|
response_model=To3DProTaskResultResponse,
|
||||||
status_extractor=lambda r: r.Status,
|
status_extractor=lambda r: r.Status,
|
||||||
)
|
)
|
||||||
glb_result = get_file_from_response(result.ResultFile3Ds, "glb")
|
model_file = f"hunyuan_model_{response.JobId}.glb"
|
||||||
obj_result = get_file_from_response(result.ResultFile3Ds, "obj")
|
await download_url_to_bytesio(
|
||||||
file_glb = await download_url_to_file_3d(glb_result.Url, "glb", task_id=task_id) if glb_result else None
|
get_glb_obj_from_response(result.ResultFile3Ds).Url,
|
||||||
return IO.NodeOutput(
|
os.path.join(get_output_directory(), model_file),
|
||||||
file_glb, file_glb, await download_url_to_file_3d(obj_result.Url, "obj", task_id=task_id) if obj_result else None
|
|
||||||
)
|
)
|
||||||
|
return IO.NodeOutput(model_file)
|
||||||
|
|
||||||
|
|
||||||
class TencentImageToModelNode(IO.ComfyNode):
|
class TencentImageToModelNode(IO.ComfyNode):
|
||||||
@ -184,9 +184,7 @@ class TencentImageToModelNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
IO.File3DOBJ.Output(display_name="OBJ"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -271,20 +269,19 @@ class TencentImageToModelNode(IO.ComfyNode):
|
|||||||
)
|
)
|
||||||
if response.Error:
|
if response.Error:
|
||||||
raise ValueError(f"Task creation failed with code {response.Error.Code}: {response.Error.Message}")
|
raise ValueError(f"Task creation failed with code {response.Error.Code}: {response.Error.Message}")
|
||||||
task_id = response.JobId
|
|
||||||
result = await poll_op(
|
result = await poll_op(
|
||||||
cls,
|
cls,
|
||||||
ApiEndpoint(path="/proxy/tencent/hunyuan/3d-pro/query", method="POST"),
|
ApiEndpoint(path="/proxy/tencent/hunyuan/3d-pro/query", method="POST"),
|
||||||
data=To3DProTaskQueryRequest(JobId=task_id),
|
data=To3DProTaskQueryRequest(JobId=response.JobId),
|
||||||
response_model=To3DProTaskResultResponse,
|
response_model=To3DProTaskResultResponse,
|
||||||
status_extractor=lambda r: r.Status,
|
status_extractor=lambda r: r.Status,
|
||||||
)
|
)
|
||||||
glb_result = get_file_from_response(result.ResultFile3Ds, "glb")
|
model_file = f"hunyuan_model_{response.JobId}.glb"
|
||||||
obj_result = get_file_from_response(result.ResultFile3Ds, "obj")
|
await download_url_to_bytesio(
|
||||||
file_glb = await download_url_to_file_3d(glb_result.Url, "glb", task_id=task_id) if glb_result else None
|
get_glb_obj_from_response(result.ResultFile3Ds).Url,
|
||||||
return IO.NodeOutput(
|
os.path.join(get_output_directory(), model_file),
|
||||||
file_glb, file_glb, await download_url_to_file_3d(obj_result.Url, "obj", task_id=task_id) if obj_result else None
|
|
||||||
)
|
)
|
||||||
|
return IO.NodeOutput(model_file)
|
||||||
|
|
||||||
|
|
||||||
class TencentHunyuan3DExtension(ComfyExtension):
|
class TencentHunyuan3DExtension(ComfyExtension):
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
from comfy_api.latest import IO, ComfyExtension, Input
|
from comfy_api.latest import IO, ComfyExtension, Input
|
||||||
@ -18,12 +20,13 @@ from comfy_api_nodes.apis.meshy import (
|
|||||||
)
|
)
|
||||||
from comfy_api_nodes.util import (
|
from comfy_api_nodes.util import (
|
||||||
ApiEndpoint,
|
ApiEndpoint,
|
||||||
download_url_to_file_3d,
|
download_url_to_bytesio,
|
||||||
poll_op,
|
poll_op,
|
||||||
sync_op,
|
sync_op,
|
||||||
upload_images_to_comfyapi,
|
upload_images_to_comfyapi,
|
||||||
validate_string,
|
validate_string,
|
||||||
)
|
)
|
||||||
|
from folder_paths import get_output_directory
|
||||||
|
|
||||||
|
|
||||||
class MeshyTextToModelNode(IO.ComfyNode):
|
class MeshyTextToModelNode(IO.ComfyNode):
|
||||||
@ -76,10 +79,8 @@ class MeshyTextToModelNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MESHY_TASK_ID").Output(display_name="meshy_task_id"),
|
IO.Custom("MESHY_TASK_ID").Output(display_name="meshy_task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
IO.File3DFBX.Output(display_name="FBX"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -121,20 +122,16 @@ class MeshyTextToModelNode(IO.ComfyNode):
|
|||||||
seed=seed,
|
seed=seed,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
task_id = response.result
|
|
||||||
result = await poll_op(
|
result = await poll_op(
|
||||||
cls,
|
cls,
|
||||||
ApiEndpoint(path=f"/proxy/meshy/openapi/v2/text-to-3d/{task_id}"),
|
ApiEndpoint(path=f"/proxy/meshy/openapi/v2/text-to-3d/{response.result}"),
|
||||||
response_model=MeshyModelResult,
|
response_model=MeshyModelResult,
|
||||||
status_extractor=lambda r: r.status,
|
status_extractor=lambda r: r.status,
|
||||||
progress_extractor=lambda r: r.progress,
|
progress_extractor=lambda r: r.progress,
|
||||||
)
|
)
|
||||||
return IO.NodeOutput(
|
model_file = f"meshy_model_{response.result}.glb"
|
||||||
f"{task_id}.glb",
|
await download_url_to_bytesio(result.model_urls.glb, os.path.join(get_output_directory(), model_file))
|
||||||
task_id,
|
return IO.NodeOutput(model_file, response.result)
|
||||||
await download_url_to_file_3d(result.model_urls.glb, "glb", task_id=task_id),
|
|
||||||
await download_url_to_file_3d(result.model_urls.fbx, "fbx", task_id=task_id),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MeshyRefineNode(IO.ComfyNode):
|
class MeshyRefineNode(IO.ComfyNode):
|
||||||
@ -170,10 +167,8 @@ class MeshyRefineNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MESHY_TASK_ID").Output(display_name="meshy_task_id"),
|
IO.Custom("MESHY_TASK_ID").Output(display_name="meshy_task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
IO.File3DFBX.Output(display_name="FBX"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -215,20 +210,16 @@ class MeshyRefineNode(IO.ComfyNode):
|
|||||||
ai_model=model,
|
ai_model=model,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
task_id = response.result
|
|
||||||
result = await poll_op(
|
result = await poll_op(
|
||||||
cls,
|
cls,
|
||||||
ApiEndpoint(path=f"/proxy/meshy/openapi/v2/text-to-3d/{task_id}"),
|
ApiEndpoint(path=f"/proxy/meshy/openapi/v2/text-to-3d/{response.result}"),
|
||||||
response_model=MeshyModelResult,
|
response_model=MeshyModelResult,
|
||||||
status_extractor=lambda r: r.status,
|
status_extractor=lambda r: r.status,
|
||||||
progress_extractor=lambda r: r.progress,
|
progress_extractor=lambda r: r.progress,
|
||||||
)
|
)
|
||||||
return IO.NodeOutput(
|
model_file = f"meshy_model_{response.result}.glb"
|
||||||
f"{task_id}.glb",
|
await download_url_to_bytesio(result.model_urls.glb, os.path.join(get_output_directory(), model_file))
|
||||||
task_id,
|
return IO.NodeOutput(model_file, response.result)
|
||||||
await download_url_to_file_3d(result.model_urls.glb, "glb", task_id=task_id),
|
|
||||||
await download_url_to_file_3d(result.model_urls.fbx, "fbx", task_id=task_id),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MeshyImageToModelNode(IO.ComfyNode):
|
class MeshyImageToModelNode(IO.ComfyNode):
|
||||||
@ -312,10 +303,8 @@ class MeshyImageToModelNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MESHY_TASK_ID").Output(display_name="meshy_task_id"),
|
IO.Custom("MESHY_TASK_ID").Output(display_name="meshy_task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
IO.File3DFBX.Output(display_name="FBX"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -379,20 +368,16 @@ class MeshyImageToModelNode(IO.ComfyNode):
|
|||||||
seed=seed,
|
seed=seed,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
task_id = response.result
|
|
||||||
result = await poll_op(
|
result = await poll_op(
|
||||||
cls,
|
cls,
|
||||||
ApiEndpoint(path=f"/proxy/meshy/openapi/v1/image-to-3d/{task_id}"),
|
ApiEndpoint(path=f"/proxy/meshy/openapi/v1/image-to-3d/{response.result}"),
|
||||||
response_model=MeshyModelResult,
|
response_model=MeshyModelResult,
|
||||||
status_extractor=lambda r: r.status,
|
status_extractor=lambda r: r.status,
|
||||||
progress_extractor=lambda r: r.progress,
|
progress_extractor=lambda r: r.progress,
|
||||||
)
|
)
|
||||||
return IO.NodeOutput(
|
model_file = f"meshy_model_{response.result}.glb"
|
||||||
f"{task_id}.glb",
|
await download_url_to_bytesio(result.model_urls.glb, os.path.join(get_output_directory(), model_file))
|
||||||
task_id,
|
return IO.NodeOutput(model_file, response.result)
|
||||||
await download_url_to_file_3d(result.model_urls.glb, "glb", task_id=task_id),
|
|
||||||
await download_url_to_file_3d(result.model_urls.fbx, "fbx", task_id=task_id),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MeshyMultiImageToModelNode(IO.ComfyNode):
|
class MeshyMultiImageToModelNode(IO.ComfyNode):
|
||||||
@ -479,10 +464,8 @@ class MeshyMultiImageToModelNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MESHY_TASK_ID").Output(display_name="meshy_task_id"),
|
IO.Custom("MESHY_TASK_ID").Output(display_name="meshy_task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
IO.File3DFBX.Output(display_name="FBX"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -548,20 +531,16 @@ class MeshyMultiImageToModelNode(IO.ComfyNode):
|
|||||||
seed=seed,
|
seed=seed,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
task_id = response.result
|
|
||||||
result = await poll_op(
|
result = await poll_op(
|
||||||
cls,
|
cls,
|
||||||
ApiEndpoint(path=f"/proxy/meshy/openapi/v1/multi-image-to-3d/{task_id}"),
|
ApiEndpoint(path=f"/proxy/meshy/openapi/v1/multi-image-to-3d/{response.result}"),
|
||||||
response_model=MeshyModelResult,
|
response_model=MeshyModelResult,
|
||||||
status_extractor=lambda r: r.status,
|
status_extractor=lambda r: r.status,
|
||||||
progress_extractor=lambda r: r.progress,
|
progress_extractor=lambda r: r.progress,
|
||||||
)
|
)
|
||||||
return IO.NodeOutput(
|
model_file = f"meshy_model_{response.result}.glb"
|
||||||
f"{task_id}.glb",
|
await download_url_to_bytesio(result.model_urls.glb, os.path.join(get_output_directory(), model_file))
|
||||||
task_id,
|
return IO.NodeOutput(model_file, response.result)
|
||||||
await download_url_to_file_3d(result.model_urls.glb, "glb", task_id=task_id),
|
|
||||||
await download_url_to_file_3d(result.model_urls.fbx, "fbx", task_id=task_id),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MeshyRigModelNode(IO.ComfyNode):
|
class MeshyRigModelNode(IO.ComfyNode):
|
||||||
@ -592,10 +571,8 @@ class MeshyRigModelNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MESHY_RIGGED_TASK_ID").Output(display_name="rig_task_id"),
|
IO.Custom("MESHY_RIGGED_TASK_ID").Output(display_name="rig_task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
IO.File3DFBX.Output(display_name="FBX"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -629,20 +606,18 @@ class MeshyRigModelNode(IO.ComfyNode):
|
|||||||
texture_image_url=texture_image_url,
|
texture_image_url=texture_image_url,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
task_id = response.result
|
|
||||||
result = await poll_op(
|
result = await poll_op(
|
||||||
cls,
|
cls,
|
||||||
ApiEndpoint(path=f"/proxy/meshy/openapi/v1/rigging/{task_id}"),
|
ApiEndpoint(path=f"/proxy/meshy/openapi/v1/rigging/{response.result}"),
|
||||||
response_model=MeshyRiggedResult,
|
response_model=MeshyRiggedResult,
|
||||||
status_extractor=lambda r: r.status,
|
status_extractor=lambda r: r.status,
|
||||||
progress_extractor=lambda r: r.progress,
|
progress_extractor=lambda r: r.progress,
|
||||||
)
|
)
|
||||||
return IO.NodeOutput(
|
model_file = f"meshy_model_{response.result}.glb"
|
||||||
f"{task_id}.glb",
|
await download_url_to_bytesio(
|
||||||
task_id,
|
result.result.rigged_character_glb_url, os.path.join(get_output_directory(), model_file)
|
||||||
await download_url_to_file_3d(result.result.rigged_character_glb_url, "glb", task_id=task_id),
|
|
||||||
await download_url_to_file_3d(result.result.rigged_character_fbx_url, "fbx", task_id=task_id),
|
|
||||||
)
|
)
|
||||||
|
return IO.NodeOutput(model_file, response.result)
|
||||||
|
|
||||||
|
|
||||||
class MeshyAnimateModelNode(IO.ComfyNode):
|
class MeshyAnimateModelNode(IO.ComfyNode):
|
||||||
@ -665,9 +640,7 @@ class MeshyAnimateModelNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
IO.File3DFBX.Output(display_name="FBX"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -696,19 +669,16 @@ class MeshyAnimateModelNode(IO.ComfyNode):
|
|||||||
action_id=action_id,
|
action_id=action_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
task_id = response.result
|
|
||||||
result = await poll_op(
|
result = await poll_op(
|
||||||
cls,
|
cls,
|
||||||
ApiEndpoint(path=f"/proxy/meshy/openapi/v1/animations/{task_id}"),
|
ApiEndpoint(path=f"/proxy/meshy/openapi/v1/animations/{response.result}"),
|
||||||
response_model=MeshyAnimationResult,
|
response_model=MeshyAnimationResult,
|
||||||
status_extractor=lambda r: r.status,
|
status_extractor=lambda r: r.status,
|
||||||
progress_extractor=lambda r: r.progress,
|
progress_extractor=lambda r: r.progress,
|
||||||
)
|
)
|
||||||
return IO.NodeOutput(
|
model_file = f"meshy_model_{response.result}.glb"
|
||||||
f"{task_id}.glb",
|
await download_url_to_bytesio(result.result.animation_glb_url, os.path.join(get_output_directory(), model_file))
|
||||||
await download_url_to_file_3d(result.result.animation_glb_url, "glb", task_id=task_id),
|
return IO.NodeOutput(model_file, response.result)
|
||||||
await download_url_to_file_3d(result.result.animation_fbx_url, "fbx", task_id=task_id),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MeshyTextureNode(IO.ComfyNode):
|
class MeshyTextureNode(IO.ComfyNode):
|
||||||
@ -745,10 +715,8 @@ class MeshyTextureNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MODEL_TASK_ID").Output(display_name="meshy_task_id"),
|
IO.Custom("MODEL_TASK_ID").Output(display_name="meshy_task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
IO.File3DFBX.Output(display_name="FBX"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -792,20 +760,16 @@ class MeshyTextureNode(IO.ComfyNode):
|
|||||||
image_style_url=image_style_url,
|
image_style_url=image_style_url,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
task_id = response.result
|
|
||||||
result = await poll_op(
|
result = await poll_op(
|
||||||
cls,
|
cls,
|
||||||
ApiEndpoint(path=f"/proxy/meshy/openapi/v1/retexture/{task_id}"),
|
ApiEndpoint(path=f"/proxy/meshy/openapi/v1/retexture/{response.result}"),
|
||||||
response_model=MeshyModelResult,
|
response_model=MeshyModelResult,
|
||||||
status_extractor=lambda r: r.status,
|
status_extractor=lambda r: r.status,
|
||||||
progress_extractor=lambda r: r.progress,
|
progress_extractor=lambda r: r.progress,
|
||||||
)
|
)
|
||||||
return IO.NodeOutput(
|
model_file = f"meshy_model_{response.result}.glb"
|
||||||
f"{task_id}.glb",
|
await download_url_to_bytesio(result.model_urls.glb, os.path.join(get_output_directory(), model_file))
|
||||||
task_id,
|
return IO.NodeOutput(model_file, response.result)
|
||||||
await download_url_to_file_3d(result.model_urls.glb, "glb", task_id=task_id),
|
|
||||||
await download_url_to_file_3d(result.model_urls.fbx, "fbx", task_id=task_id),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MeshyExtension(ComfyExtension):
|
class MeshyExtension(ComfyExtension):
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import folder_paths as comfy_paths
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
from typing import Optional
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@ -27,9 +28,8 @@ from comfy_api_nodes.util import (
|
|||||||
poll_op,
|
poll_op,
|
||||||
ApiEndpoint,
|
ApiEndpoint,
|
||||||
download_url_to_bytesio,
|
download_url_to_bytesio,
|
||||||
download_url_to_file_3d,
|
|
||||||
)
|
)
|
||||||
from comfy_api.latest import ComfyExtension, IO, Types
|
from comfy_api.latest import ComfyExtension, IO
|
||||||
|
|
||||||
|
|
||||||
COMMON_PARAMETERS = [
|
COMMON_PARAMETERS = [
|
||||||
@ -177,7 +177,7 @@ def check_rodin_status(response: Rodin3DCheckStatusResponse) -> str:
|
|||||||
return "DONE"
|
return "DONE"
|
||||||
return "Generating"
|
return "Generating"
|
||||||
|
|
||||||
def extract_progress(response: Rodin3DCheckStatusResponse) -> int | None:
|
def extract_progress(response: Rodin3DCheckStatusResponse) -> Optional[int]:
|
||||||
if not response.jobs:
|
if not response.jobs:
|
||||||
return None
|
return None
|
||||||
completed_count = sum(1 for job in response.jobs if job.status == JobStatus.Done)
|
completed_count = sum(1 for job in response.jobs if job.status == JobStatus.Done)
|
||||||
@ -207,25 +207,17 @@ async def get_rodin_download_list(uuid: str, cls: type[IO.ComfyNode]) -> Rodin3D
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def download_files(url_list, task_uuid: str) -> tuple[str | None, Types.File3D | None]:
|
async def download_files(url_list, task_uuid: str):
|
||||||
result_folder_name = f"Rodin3D_{task_uuid}"
|
result_folder_name = f"Rodin3D_{task_uuid}"
|
||||||
save_path = os.path.join(comfy_paths.get_output_directory(), result_folder_name)
|
save_path = os.path.join(comfy_paths.get_output_directory(), result_folder_name)
|
||||||
os.makedirs(save_path, exist_ok=True)
|
os.makedirs(save_path, exist_ok=True)
|
||||||
model_file_path = None
|
model_file_path = None
|
||||||
file_3d = None
|
|
||||||
|
|
||||||
for i in url_list.list:
|
for i in url_list.list:
|
||||||
file_path = os.path.join(save_path, i.name)
|
file_path = os.path.join(save_path, i.name)
|
||||||
if i.name.lower().endswith(".glb"):
|
if file_path.endswith(".glb"):
|
||||||
model_file_path = os.path.join(result_folder_name, i.name)
|
model_file_path = os.path.join(result_folder_name, i.name)
|
||||||
file_3d = await download_url_to_file_3d(i.url, "glb")
|
await download_url_to_bytesio(i.url, file_path)
|
||||||
# Save to disk for backward compatibility
|
return model_file_path
|
||||||
with open(file_path, "wb") as f:
|
|
||||||
f.write(file_3d.get_bytes())
|
|
||||||
else:
|
|
||||||
await download_url_to_bytesio(i.url, file_path)
|
|
||||||
|
|
||||||
return model_file_path, file_3d
|
|
||||||
|
|
||||||
|
|
||||||
class Rodin3D_Regular(IO.ComfyNode):
|
class Rodin3D_Regular(IO.ComfyNode):
|
||||||
@ -242,10 +234,7 @@ class Rodin3D_Regular(IO.ComfyNode):
|
|||||||
IO.Image.Input("Images"),
|
IO.Image.Input("Images"),
|
||||||
*COMMON_PARAMETERS,
|
*COMMON_PARAMETERS,
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[IO.String.Output(display_name="3D Model Path")],
|
||||||
IO.String.Output(display_name="3D Model Path"), # for backward compatibility only
|
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
IO.Hidden.api_key_comfy_org,
|
IO.Hidden.api_key_comfy_org,
|
||||||
@ -282,9 +271,9 @@ class Rodin3D_Regular(IO.ComfyNode):
|
|||||||
)
|
)
|
||||||
await poll_for_task_status(subscription_key, cls)
|
await poll_for_task_status(subscription_key, cls)
|
||||||
download_list = await get_rodin_download_list(task_uuid, cls)
|
download_list = await get_rodin_download_list(task_uuid, cls)
|
||||||
model_path, file_3d = await download_files(download_list, task_uuid)
|
model = await download_files(download_list, task_uuid)
|
||||||
|
|
||||||
return IO.NodeOutput(model_path, file_3d)
|
return IO.NodeOutput(model)
|
||||||
|
|
||||||
|
|
||||||
class Rodin3D_Detail(IO.ComfyNode):
|
class Rodin3D_Detail(IO.ComfyNode):
|
||||||
@ -301,10 +290,7 @@ class Rodin3D_Detail(IO.ComfyNode):
|
|||||||
IO.Image.Input("Images"),
|
IO.Image.Input("Images"),
|
||||||
*COMMON_PARAMETERS,
|
*COMMON_PARAMETERS,
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[IO.String.Output(display_name="3D Model Path")],
|
||||||
IO.String.Output(display_name="3D Model Path"), # for backward compatibility only
|
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
IO.Hidden.api_key_comfy_org,
|
IO.Hidden.api_key_comfy_org,
|
||||||
@ -341,9 +327,9 @@ class Rodin3D_Detail(IO.ComfyNode):
|
|||||||
)
|
)
|
||||||
await poll_for_task_status(subscription_key, cls)
|
await poll_for_task_status(subscription_key, cls)
|
||||||
download_list = await get_rodin_download_list(task_uuid, cls)
|
download_list = await get_rodin_download_list(task_uuid, cls)
|
||||||
model_path, file_3d = await download_files(download_list, task_uuid)
|
model = await download_files(download_list, task_uuid)
|
||||||
|
|
||||||
return IO.NodeOutput(model_path, file_3d)
|
return IO.NodeOutput(model)
|
||||||
|
|
||||||
|
|
||||||
class Rodin3D_Smooth(IO.ComfyNode):
|
class Rodin3D_Smooth(IO.ComfyNode):
|
||||||
@ -360,10 +346,7 @@ class Rodin3D_Smooth(IO.ComfyNode):
|
|||||||
IO.Image.Input("Images"),
|
IO.Image.Input("Images"),
|
||||||
*COMMON_PARAMETERS,
|
*COMMON_PARAMETERS,
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[IO.String.Output(display_name="3D Model Path")],
|
||||||
IO.String.Output(display_name="3D Model Path"), # for backward compatibility only
|
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
IO.Hidden.api_key_comfy_org,
|
IO.Hidden.api_key_comfy_org,
|
||||||
@ -399,9 +382,9 @@ class Rodin3D_Smooth(IO.ComfyNode):
|
|||||||
)
|
)
|
||||||
await poll_for_task_status(subscription_key, cls)
|
await poll_for_task_status(subscription_key, cls)
|
||||||
download_list = await get_rodin_download_list(task_uuid, cls)
|
download_list = await get_rodin_download_list(task_uuid, cls)
|
||||||
model_path, file_3d = await download_files(download_list, task_uuid)
|
model = await download_files(download_list, task_uuid)
|
||||||
|
|
||||||
return IO.NodeOutput(model_path, file_3d)
|
return IO.NodeOutput(model)
|
||||||
|
|
||||||
|
|
||||||
class Rodin3D_Sketch(IO.ComfyNode):
|
class Rodin3D_Sketch(IO.ComfyNode):
|
||||||
@ -425,10 +408,7 @@ class Rodin3D_Sketch(IO.ComfyNode):
|
|||||||
optional=True,
|
optional=True,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[IO.String.Output(display_name="3D Model Path")],
|
||||||
IO.String.Output(display_name="3D Model Path"), # for backward compatibility only
|
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
IO.Hidden.api_key_comfy_org,
|
IO.Hidden.api_key_comfy_org,
|
||||||
@ -461,9 +441,9 @@ class Rodin3D_Sketch(IO.ComfyNode):
|
|||||||
)
|
)
|
||||||
await poll_for_task_status(subscription_key, cls)
|
await poll_for_task_status(subscription_key, cls)
|
||||||
download_list = await get_rodin_download_list(task_uuid, cls)
|
download_list = await get_rodin_download_list(task_uuid, cls)
|
||||||
model_path, file_3d = await download_files(download_list, task_uuid)
|
model = await download_files(download_list, task_uuid)
|
||||||
|
|
||||||
return IO.NodeOutput(model_path, file_3d)
|
return IO.NodeOutput(model)
|
||||||
|
|
||||||
|
|
||||||
class Rodin3D_Gen2(IO.ComfyNode):
|
class Rodin3D_Gen2(IO.ComfyNode):
|
||||||
@ -495,10 +475,7 @@ class Rodin3D_Gen2(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
IO.Boolean.Input("TAPose", default=False),
|
IO.Boolean.Input("TAPose", default=False),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[IO.String.Output(display_name="3D Model Path")],
|
||||||
IO.String.Output(display_name="3D Model Path"), # for backward compatibility only
|
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
IO.Hidden.api_key_comfy_org,
|
IO.Hidden.api_key_comfy_org,
|
||||||
@ -534,9 +511,9 @@ class Rodin3D_Gen2(IO.ComfyNode):
|
|||||||
)
|
)
|
||||||
await poll_for_task_status(subscription_key, cls)
|
await poll_for_task_status(subscription_key, cls)
|
||||||
download_list = await get_rodin_download_list(task_uuid, cls)
|
download_list = await get_rodin_download_list(task_uuid, cls)
|
||||||
model_path, file_3d = await download_files(download_list, task_uuid)
|
model = await download_files(download_list, task_uuid)
|
||||||
|
|
||||||
return IO.NodeOutput(model_path, file_3d)
|
return IO.NodeOutput(model)
|
||||||
|
|
||||||
|
|
||||||
class Rodin3DExtension(ComfyExtension):
|
class Rodin3DExtension(ComfyExtension):
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import torch
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
from comfy_api.latest import IO, ComfyExtension, Input
|
from comfy_api.latest import IO, ComfyExtension
|
||||||
from comfy_api_nodes.apis.tripo import (
|
from comfy_api_nodes.apis.tripo import (
|
||||||
TripoAnimateRetargetRequest,
|
TripoAnimateRetargetRequest,
|
||||||
TripoAnimateRigRequest,
|
TripoAnimateRigRequest,
|
||||||
@ -22,11 +26,12 @@ from comfy_api_nodes.apis.tripo import (
|
|||||||
)
|
)
|
||||||
from comfy_api_nodes.util import (
|
from comfy_api_nodes.util import (
|
||||||
ApiEndpoint,
|
ApiEndpoint,
|
||||||
download_url_to_file_3d,
|
download_url_as_bytesio,
|
||||||
poll_op,
|
poll_op,
|
||||||
sync_op,
|
sync_op,
|
||||||
upload_images_to_comfyapi,
|
upload_images_to_comfyapi,
|
||||||
)
|
)
|
||||||
|
from folder_paths import get_output_directory
|
||||||
|
|
||||||
|
|
||||||
def get_model_url_from_response(response: TripoTaskResponse) -> str:
|
def get_model_url_from_response(response: TripoTaskResponse) -> str:
|
||||||
@ -40,7 +45,7 @@ def get_model_url_from_response(response: TripoTaskResponse) -> str:
|
|||||||
async def poll_until_finished(
|
async def poll_until_finished(
|
||||||
node_cls: type[IO.ComfyNode],
|
node_cls: type[IO.ComfyNode],
|
||||||
response: TripoTaskResponse,
|
response: TripoTaskResponse,
|
||||||
average_duration: int | None = None,
|
average_duration: Optional[int] = None,
|
||||||
) -> IO.NodeOutput:
|
) -> IO.NodeOutput:
|
||||||
"""Polls the Tripo API endpoint until the task reaches a terminal state, then returns the response."""
|
"""Polls the Tripo API endpoint until the task reaches a terminal state, then returns the response."""
|
||||||
if response.code != 0:
|
if response.code != 0:
|
||||||
@ -64,8 +69,12 @@ async def poll_until_finished(
|
|||||||
)
|
)
|
||||||
if response_poll.data.status == TripoTaskStatus.SUCCESS:
|
if response_poll.data.status == TripoTaskStatus.SUCCESS:
|
||||||
url = get_model_url_from_response(response_poll)
|
url = get_model_url_from_response(response_poll)
|
||||||
file_glb = await download_url_to_file_3d(url, "glb", task_id=task_id)
|
bytesio = await download_url_as_bytesio(url)
|
||||||
return IO.NodeOutput(f"{task_id}.glb", task_id, file_glb)
|
# 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 IO.NodeOutput(model_file, task_id)
|
||||||
raise RuntimeError(f"Failed to generate mesh: {response_poll}")
|
raise RuntimeError(f"Failed to generate mesh: {response_poll}")
|
||||||
|
|
||||||
|
|
||||||
@ -98,9 +107,8 @@ class TripoTextToModelNode(IO.ComfyNode):
|
|||||||
IO.Combo.Input("geometry_quality", default="standard", options=["standard", "detailed"], optional=True),
|
IO.Combo.Input("geometry_quality", default="standard", options=["standard", "detailed"], optional=True),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
|
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -147,18 +155,18 @@ class TripoTextToModelNode(IO.ComfyNode):
|
|||||||
async def execute(
|
async def execute(
|
||||||
cls,
|
cls,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
negative_prompt: str | None = None,
|
negative_prompt: Optional[str] = None,
|
||||||
model_version=None,
|
model_version=None,
|
||||||
style: str | None = None,
|
style: Optional[str] = None,
|
||||||
texture: bool | None = None,
|
texture: Optional[bool] = None,
|
||||||
pbr: bool | None = None,
|
pbr: Optional[bool] = None,
|
||||||
image_seed: int | None = None,
|
image_seed: Optional[int] = None,
|
||||||
model_seed: int | None = None,
|
model_seed: Optional[int] = None,
|
||||||
texture_seed: int | None = None,
|
texture_seed: Optional[int] = None,
|
||||||
texture_quality: str | None = None,
|
texture_quality: Optional[str] = None,
|
||||||
geometry_quality: str | None = None,
|
geometry_quality: Optional[str] = None,
|
||||||
face_limit: int | None = None,
|
face_limit: Optional[int] = None,
|
||||||
quad: bool | None = None,
|
quad: Optional[bool] = None,
|
||||||
) -> IO.NodeOutput:
|
) -> IO.NodeOutput:
|
||||||
style_enum = None if style == "None" else style
|
style_enum = None if style == "None" else style
|
||||||
if not prompt:
|
if not prompt:
|
||||||
@ -224,9 +232,8 @@ class TripoImageToModelNode(IO.ComfyNode):
|
|||||||
IO.Combo.Input("geometry_quality", default="standard", options=["standard", "detailed"], optional=True),
|
IO.Combo.Input("geometry_quality", default="standard", options=["standard", "detailed"], optional=True),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
|
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -272,19 +279,19 @@ class TripoImageToModelNode(IO.ComfyNode):
|
|||||||
@classmethod
|
@classmethod
|
||||||
async def execute(
|
async def execute(
|
||||||
cls,
|
cls,
|
||||||
image: Input.Image,
|
image: torch.Tensor,
|
||||||
model_version: str | None = None,
|
model_version: Optional[str] = None,
|
||||||
style: str | None = None,
|
style: Optional[str] = None,
|
||||||
texture: bool | None = None,
|
texture: Optional[bool] = None,
|
||||||
pbr: bool | None = None,
|
pbr: Optional[bool] = None,
|
||||||
model_seed: int | None = None,
|
model_seed: Optional[int] = None,
|
||||||
orientation=None,
|
orientation=None,
|
||||||
texture_seed: int | None = None,
|
texture_seed: Optional[int] = None,
|
||||||
texture_quality: str | None = None,
|
texture_quality: Optional[str] = None,
|
||||||
geometry_quality: str | None = None,
|
geometry_quality: Optional[str] = None,
|
||||||
texture_alignment: str | None = None,
|
texture_alignment: Optional[str] = None,
|
||||||
face_limit: int | None = None,
|
face_limit: Optional[int] = None,
|
||||||
quad: bool | None = None,
|
quad: Optional[bool] = None,
|
||||||
) -> IO.NodeOutput:
|
) -> IO.NodeOutput:
|
||||||
style_enum = None if style == "None" else style
|
style_enum = None if style == "None" else style
|
||||||
if image is None:
|
if image is None:
|
||||||
@ -361,9 +368,8 @@ class TripoMultiviewToModelNode(IO.ComfyNode):
|
|||||||
IO.Combo.Input("geometry_quality", default="standard", options=["standard", "detailed"], optional=True),
|
IO.Combo.Input("geometry_quality", default="standard", options=["standard", "detailed"], optional=True),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
|
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -405,21 +411,21 @@ class TripoMultiviewToModelNode(IO.ComfyNode):
|
|||||||
@classmethod
|
@classmethod
|
||||||
async def execute(
|
async def execute(
|
||||||
cls,
|
cls,
|
||||||
image: Input.Image,
|
image: torch.Tensor,
|
||||||
image_left: Input.Image | None = None,
|
image_left: Optional[torch.Tensor] = None,
|
||||||
image_back: Input.Image | None = None,
|
image_back: Optional[torch.Tensor] = None,
|
||||||
image_right: Input.Image | None = None,
|
image_right: Optional[torch.Tensor] = None,
|
||||||
model_version: str | None = None,
|
model_version: Optional[str] = None,
|
||||||
orientation: str | None = None,
|
orientation: Optional[str] = None,
|
||||||
texture: bool | None = None,
|
texture: Optional[bool] = None,
|
||||||
pbr: bool | None = None,
|
pbr: Optional[bool] = None,
|
||||||
model_seed: int | None = None,
|
model_seed: Optional[int] = None,
|
||||||
texture_seed: int | None = None,
|
texture_seed: Optional[int] = None,
|
||||||
texture_quality: str | None = None,
|
texture_quality: Optional[str] = None,
|
||||||
geometry_quality: str | None = None,
|
geometry_quality: Optional[str] = None,
|
||||||
texture_alignment: str | None = None,
|
texture_alignment: Optional[str] = None,
|
||||||
face_limit: int | None = None,
|
face_limit: Optional[int] = None,
|
||||||
quad: bool | None = None,
|
quad: Optional[bool] = None,
|
||||||
) -> IO.NodeOutput:
|
) -> IO.NodeOutput:
|
||||||
if image is None:
|
if image is None:
|
||||||
raise RuntimeError("front image for multiview is required")
|
raise RuntimeError("front image for multiview is required")
|
||||||
@ -481,9 +487,8 @@ class TripoTextureNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
|
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -507,11 +512,11 @@ class TripoTextureNode(IO.ComfyNode):
|
|||||||
async def execute(
|
async def execute(
|
||||||
cls,
|
cls,
|
||||||
model_task_id,
|
model_task_id,
|
||||||
texture: bool | None = None,
|
texture: Optional[bool] = None,
|
||||||
pbr: bool | None = None,
|
pbr: Optional[bool] = None,
|
||||||
texture_seed: int | None = None,
|
texture_seed: Optional[int] = None,
|
||||||
texture_quality: str | None = None,
|
texture_quality: Optional[str] = None,
|
||||||
texture_alignment: str | None = None,
|
texture_alignment: Optional[str] = None,
|
||||||
) -> IO.NodeOutput:
|
) -> IO.NodeOutput:
|
||||||
response = await sync_op(
|
response = await sync_op(
|
||||||
cls,
|
cls,
|
||||||
@ -542,9 +547,8 @@ class TripoRefineNode(IO.ComfyNode):
|
|||||||
IO.Custom("MODEL_TASK_ID").Input("model_task_id", tooltip="Must be a v1.4 Tripo model"),
|
IO.Custom("MODEL_TASK_ID").Input("model_task_id", tooltip="Must be a v1.4 Tripo model"),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
|
IO.Custom("MODEL_TASK_ID").Output(display_name="model task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -579,9 +583,8 @@ class TripoRigNode(IO.ComfyNode):
|
|||||||
category="api node/3d/Tripo",
|
category="api node/3d/Tripo",
|
||||||
inputs=[IO.Custom("MODEL_TASK_ID").Input("original_model_task_id")],
|
inputs=[IO.Custom("MODEL_TASK_ID").Input("original_model_task_id")],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("RIG_TASK_ID").Output(display_name="rig task_id"),
|
IO.Custom("RIG_TASK_ID").Output(display_name="rig task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
@ -639,9 +642,8 @@ class TripoRetargetNode(IO.ComfyNode):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IO.String.Output(display_name="model_file"), # for backward compatibility only
|
IO.String.Output(display_name="model_file"),
|
||||||
IO.Custom("RETARGET_TASK_ID").Output(display_name="retarget task_id"),
|
IO.Custom("RETARGET_TASK_ID").Output(display_name="retarget task_id"),
|
||||||
IO.File3DGLB.Output(display_name="GLB"),
|
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
IO.Hidden.auth_token_comfy_org,
|
IO.Hidden.auth_token_comfy_org,
|
||||||
|
|||||||
@ -28,7 +28,6 @@ from .conversions import (
|
|||||||
from .download_helpers import (
|
from .download_helpers import (
|
||||||
download_url_as_bytesio,
|
download_url_as_bytesio,
|
||||||
download_url_to_bytesio,
|
download_url_to_bytesio,
|
||||||
download_url_to_file_3d,
|
|
||||||
download_url_to_image_tensor,
|
download_url_to_image_tensor,
|
||||||
download_url_to_video_output,
|
download_url_to_video_output,
|
||||||
)
|
)
|
||||||
@ -70,7 +69,6 @@ __all__ = [
|
|||||||
# Download helpers
|
# Download helpers
|
||||||
"download_url_as_bytesio",
|
"download_url_as_bytesio",
|
||||||
"download_url_to_bytesio",
|
"download_url_to_bytesio",
|
||||||
"download_url_to_file_3d",
|
|
||||||
"download_url_to_image_tensor",
|
"download_url_to_image_tensor",
|
||||||
"download_url_to_video_output",
|
"download_url_to_video_output",
|
||||||
# Conversions
|
# Conversions
|
||||||
|
|||||||
@ -11,8 +11,7 @@ import torch
|
|||||||
from aiohttp.client_exceptions import ClientError, ContentTypeError
|
from aiohttp.client_exceptions import ClientError, ContentTypeError
|
||||||
|
|
||||||
from comfy_api.latest import IO as COMFY_IO
|
from comfy_api.latest import IO as COMFY_IO
|
||||||
from comfy_api.latest import InputImpl, Types
|
from comfy_api.latest import InputImpl
|
||||||
from folder_paths import get_output_directory
|
|
||||||
|
|
||||||
from . import request_logger
|
from . import request_logger
|
||||||
from ._helpers import (
|
from ._helpers import (
|
||||||
@ -262,38 +261,3 @@ def _generate_operation_id(method: str, url: str, attempt: int) -> str:
|
|||||||
except Exception:
|
except Exception:
|
||||||
slug = "download"
|
slug = "download"
|
||||||
return f"{method}_{slug}_try{attempt}_{uuid.uuid4().hex[:8]}"
|
return f"{method}_{slug}_try{attempt}_{uuid.uuid4().hex[:8]}"
|
||||||
|
|
||||||
|
|
||||||
async def download_url_to_file_3d(
|
|
||||||
url: str,
|
|
||||||
file_format: str,
|
|
||||||
*,
|
|
||||||
task_id: str | None = None,
|
|
||||||
timeout: float | None = None,
|
|
||||||
max_retries: int = 5,
|
|
||||||
cls: type[COMFY_IO.ComfyNode] = None,
|
|
||||||
) -> Types.File3D:
|
|
||||||
"""Downloads a 3D model file from a URL into memory as BytesIO.
|
|
||||||
|
|
||||||
If task_id is provided, also writes the file to disk in the output directory
|
|
||||||
for backward compatibility with the old save-to-disk behavior.
|
|
||||||
"""
|
|
||||||
file_format = file_format.lstrip(".").lower()
|
|
||||||
data = BytesIO()
|
|
||||||
await download_url_to_bytesio(
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
timeout=timeout,
|
|
||||||
max_retries=max_retries,
|
|
||||||
cls=cls,
|
|
||||||
)
|
|
||||||
|
|
||||||
if task_id is not None:
|
|
||||||
# This is only for backward compatability with current behavior when every 3D node is output node
|
|
||||||
# All new API nodes should not use "task_id" and instead users should use "SaveGLB" node to save results
|
|
||||||
output_dir = Path(get_output_directory())
|
|
||||||
output_path = output_dir / f"{task_id}.{file_format}"
|
|
||||||
output_path.write_bytes(data.getvalue())
|
|
||||||
data.seek(0)
|
|
||||||
|
|
||||||
return Types.File3D(source=data, file_format=file_format)
|
|
||||||
|
|||||||
@ -622,20 +622,14 @@ class SaveGLB(IO.ComfyNode):
|
|||||||
category="3d",
|
category="3d",
|
||||||
is_output_node=True,
|
is_output_node=True,
|
||||||
inputs=[
|
inputs=[
|
||||||
IO.MultiType.Input(
|
IO.Mesh.Input("mesh"),
|
||||||
IO.Mesh.Input("mesh"),
|
|
||||||
types=[
|
|
||||||
IO.File3DGLB,
|
|
||||||
],
|
|
||||||
tooltip="Mesh or GLB file to save",
|
|
||||||
),
|
|
||||||
IO.String.Input("filename_prefix", default="mesh/ComfyUI"),
|
IO.String.Input("filename_prefix", default="mesh/ComfyUI"),
|
||||||
],
|
],
|
||||||
hidden=[IO.Hidden.prompt, IO.Hidden.extra_pnginfo]
|
hidden=[IO.Hidden.prompt, IO.Hidden.extra_pnginfo]
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, mesh: Types.MESH | Types.File3D, filename_prefix: str) -> IO.NodeOutput:
|
def execute(cls, mesh, filename_prefix) -> IO.NodeOutput:
|
||||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, folder_paths.get_output_directory())
|
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, folder_paths.get_output_directory())
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
@ -647,26 +641,15 @@ class SaveGLB(IO.ComfyNode):
|
|||||||
for x in cls.hidden.extra_pnginfo:
|
for x in cls.hidden.extra_pnginfo:
|
||||||
metadata[x] = json.dumps(cls.hidden.extra_pnginfo[x])
|
metadata[x] = json.dumps(cls.hidden.extra_pnginfo[x])
|
||||||
|
|
||||||
if isinstance(mesh, Types.File3D):
|
for i in range(mesh.vertices.shape[0]):
|
||||||
# Handle File3D input - save BytesIO data to output folder
|
|
||||||
f = f"{filename}_{counter:05}_.glb"
|
f = f"{filename}_{counter:05}_.glb"
|
||||||
mesh.save_to(os.path.join(full_output_folder, f))
|
save_glb(mesh.vertices[i], mesh.faces[i], os.path.join(full_output_folder, f), metadata)
|
||||||
results.append({
|
results.append({
|
||||||
"filename": f,
|
"filename": f,
|
||||||
"subfolder": subfolder,
|
"subfolder": subfolder,
|
||||||
"type": "output"
|
"type": "output"
|
||||||
})
|
})
|
||||||
else:
|
counter += 1
|
||||||
# Handle Mesh input - save vertices and faces as GLB
|
|
||||||
for i in range(mesh.vertices.shape[0]):
|
|
||||||
f = f"{filename}_{counter:05}_.glb"
|
|
||||||
save_glb(mesh.vertices[i], mesh.faces[i], os.path.join(full_output_folder, f), metadata)
|
|
||||||
results.append({
|
|
||||||
"filename": f,
|
|
||||||
"subfolder": subfolder,
|
|
||||||
"type": "output"
|
|
||||||
})
|
|
||||||
counter += 1
|
|
||||||
return IO.NodeOutput(ui={"3d": results})
|
return IO.NodeOutput(ui={"3d": results})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import nodes
|
import nodes
|
||||||
import folder_paths
|
import folder_paths
|
||||||
import os
|
import os
|
||||||
import uuid
|
|
||||||
|
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
from comfy_api.latest import IO, UI, ComfyExtension, InputImpl, Types
|
from comfy_api.latest import IO, ComfyExtension, InputImpl, UI
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -82,19 +81,7 @@ class Preview3D(IO.ComfyNode):
|
|||||||
is_experimental=True,
|
is_experimental=True,
|
||||||
is_output_node=True,
|
is_output_node=True,
|
||||||
inputs=[
|
inputs=[
|
||||||
IO.MultiType.Input(
|
IO.String.Input("model_file", default="", multiline=False),
|
||||||
IO.String.Input("model_file", default="", multiline=False),
|
|
||||||
types=[
|
|
||||||
IO.File3DGLB,
|
|
||||||
IO.File3DGLTF,
|
|
||||||
IO.File3DFBX,
|
|
||||||
IO.File3DOBJ,
|
|
||||||
IO.File3DSTL,
|
|
||||||
IO.File3DUSDZ,
|
|
||||||
IO.File3DAny,
|
|
||||||
],
|
|
||||||
tooltip="3D model file or path string",
|
|
||||||
),
|
|
||||||
IO.Load3DCamera.Input("camera_info", optional=True),
|
IO.Load3DCamera.Input("camera_info", optional=True),
|
||||||
IO.Image.Input("bg_image", optional=True),
|
IO.Image.Input("bg_image", optional=True),
|
||||||
],
|
],
|
||||||
@ -102,15 +89,10 @@ class Preview3D(IO.ComfyNode):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, model_file: str | Types.File3D, **kwargs) -> IO.NodeOutput:
|
def execute(cls, model_file, **kwargs) -> IO.NodeOutput:
|
||||||
if isinstance(model_file, Types.File3D):
|
|
||||||
filename = f"preview3d_{uuid.uuid4().hex}.{model_file.format}"
|
|
||||||
model_file.save_to(os.path.join(folder_paths.get_output_directory(), filename))
|
|
||||||
else:
|
|
||||||
filename = model_file
|
|
||||||
camera_info = kwargs.get("camera_info", None)
|
camera_info = kwargs.get("camera_info", None)
|
||||||
bg_image = kwargs.get("bg_image", None)
|
bg_image = kwargs.get("bg_image", None)
|
||||||
return IO.NodeOutput(ui=UI.PreviewUI3D(filename, camera_info, bg_image=bg_image))
|
return IO.NodeOutput(ui=UI.PreviewUI3D(model_file, camera_info, bg_image=bg_image))
|
||||||
|
|
||||||
process = execute # TODO: remove
|
process = execute # TODO: remove
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
# This file is automatically generated by the build process when version is
|
# This file is automatically generated by the build process when version is
|
||||||
# updated in pyproject.toml.
|
# updated in pyproject.toml.
|
||||||
__version__ = "0.12.1"
|
__version__ = "0.12.0"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "ComfyUI"
|
name = "ComfyUI"
|
||||||
version = "0.12.1"
|
version = "0.12.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user