mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-05-15 03:27:24 +08:00
Some fixes and adjustments
This commit is contained in:
parent
aaee77df7c
commit
603fc17d5b
@ -15,13 +15,21 @@ class MESH:
|
|||||||
def __init__(self, vertices: torch.Tensor, faces: torch.Tensor,
|
def __init__(self, vertices: torch.Tensor, faces: torch.Tensor,
|
||||||
uvs: torch.Tensor | None = None,
|
uvs: torch.Tensor | None = None,
|
||||||
vertex_colors: torch.Tensor | None = None,
|
vertex_colors: torch.Tensor | None = None,
|
||||||
texture: torch.Tensor | None = None):
|
texture: torch.Tensor | None = None,
|
||||||
|
vertex_counts: torch.Tensor | None = None,
|
||||||
|
face_counts: torch.Tensor | None = None):
|
||||||
|
|
||||||
|
assert (vertex_counts is None) == (face_counts is None), \
|
||||||
|
"vertex_counts and face_counts must be provided together (both or neither)"
|
||||||
self.vertices = vertices # vertices: (B, N, 3)
|
self.vertices = vertices # vertices: (B, N, 3)
|
||||||
self.faces = faces # faces: (B, M, 3)
|
self.faces = faces # faces: (B, M, 3)
|
||||||
self.uvs = uvs # uvs: (B, N, 2)
|
self.uvs = uvs # uvs: (B, N, 2)
|
||||||
self.vertex_colors = vertex_colors # vertex_colors: (B, N, 3 or 4)
|
self.vertex_colors = vertex_colors # vertex_colors: (B, N, 3 or 4)
|
||||||
self.texture = texture # texture: (B, H, W, 3)
|
self.texture = texture # texture: (B, H, W, 3)
|
||||||
|
# When vertices/faces are zero-padded to a common N/M across the batch (variable-size mesh batch),
|
||||||
|
# these hold the real per-item lengths (B,). None means rows are uniform and no slicing is needed.
|
||||||
|
self.vertex_counts = vertex_counts
|
||||||
|
self.face_counts = face_counts
|
||||||
|
|
||||||
|
|
||||||
class File3D:
|
class File3D:
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import os
|
|||||||
import struct
|
import struct
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
import torch
|
import torch
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ from comfy_api.latest import ComfyExtension, IO, Types
|
|||||||
def pack_variable_mesh_batch(vertices, faces, colors=None, uvs=None, texture=None):
|
def pack_variable_mesh_batch(vertices, faces, colors=None, uvs=None, texture=None):
|
||||||
# Pack lists of (Nᵢ, *) vertex/face/color/uv tensors into padded batched tensors,
|
# Pack lists of (Nᵢ, *) vertex/face/color/uv tensors into padded batched tensors,
|
||||||
# stashing per-item lengths as runtime attrs so consumers can recover the real slice.
|
# stashing per-item lengths as runtime attrs so consumers can recover the real slice.
|
||||||
# uvs are 1:1 with vertices, so they're padded to max_vertices and read with vertex_counts.
|
# colors and uvs are 1:1 with vertices, so they're padded to max_vertices and read with vertex_counts.
|
||||||
# texture is (B, H, W, 3) — passed through unchanged since it doesn't need ragged packing.
|
# texture is (B, H, W, 3) — passed through unchanged since it doesn't need ragged packing.
|
||||||
batch_size = len(vertices)
|
batch_size = len(vertices)
|
||||||
max_vertices = max(v.shape[0] for v in vertices)
|
max_vertices = max(v.shape[0] for v in vertices)
|
||||||
@ -36,51 +37,45 @@ def pack_variable_mesh_batch(vertices, faces, colors=None, uvs=None, texture=Non
|
|||||||
packed_faces[i, :f.shape[0]] = f
|
packed_faces[i, :f.shape[0]] = f
|
||||||
|
|
||||||
packed_colors = None
|
packed_colors = None
|
||||||
color_counts = None
|
|
||||||
if colors is not None:
|
if colors is not None:
|
||||||
max_colors = max(c.shape[0] for c in colors)
|
packed_colors = colors[0].new_zeros((batch_size, max_vertices, colors[0].shape[1]))
|
||||||
packed_colors = colors[0].new_zeros((batch_size, max_colors, colors[0].shape[1]))
|
|
||||||
color_counts = torch.tensor([c.shape[0] for c in colors], device=colors[0].device, dtype=torch.int64)
|
|
||||||
for i, c in enumerate(colors):
|
for i, c in enumerate(colors):
|
||||||
|
assert c.shape[0] == vertices[i].shape[0], (
|
||||||
|
f"vertex_colors[{i}] has {c.shape[0]} entries, expected {vertices[i].shape[0]} (1:1 with vertices)"
|
||||||
|
)
|
||||||
packed_colors[i, :c.shape[0]] = c
|
packed_colors[i, :c.shape[0]] = c
|
||||||
|
|
||||||
packed_uvs = None
|
packed_uvs = None
|
||||||
if uvs is not None:
|
if uvs is not None:
|
||||||
packed_uvs = uvs[0].new_zeros((batch_size, max_vertices, uvs[0].shape[1]))
|
packed_uvs = uvs[0].new_zeros((batch_size, max_vertices, uvs[0].shape[1]))
|
||||||
for i, u in enumerate(uvs):
|
for i, u in enumerate(uvs):
|
||||||
|
assert u.shape[0] == vertices[i].shape[0], (
|
||||||
|
f"uvs[{i}] has {u.shape[0]} entries, expected {vertices[i].shape[0]} (1:1 with vertices)"
|
||||||
|
)
|
||||||
packed_uvs[i, :u.shape[0]] = u
|
packed_uvs[i, :u.shape[0]] = u
|
||||||
|
|
||||||
mesh = Types.MESH(packed_vertices, packed_faces, uvs=packed_uvs, vertex_colors=packed_colors, texture=texture)
|
return Types.MESH(packed_vertices, packed_faces,
|
||||||
mesh.vertex_counts = vertex_counts
|
uvs=packed_uvs, vertex_colors=packed_colors, texture=texture,
|
||||||
mesh.face_counts = face_counts
|
vertex_counts=vertex_counts, face_counts=face_counts)
|
||||||
if color_counts is not None:
|
|
||||||
mesh.color_counts = color_counts
|
|
||||||
return mesh
|
|
||||||
|
|
||||||
|
|
||||||
def get_mesh_batch_item(mesh, index):
|
def get_mesh_batch_item(mesh, index):
|
||||||
# Returns (vertices, faces, colors) for batch index, slicing to real lengths
|
# Returns (vertices, faces, colors, uvs) for batch index, slicing to real lengths
|
||||||
# if pack_variable_mesh_batch added per-item counts.
|
# if the mesh carries per-item counts (variable-size batch).
|
||||||
if hasattr(mesh, "vertex_counts"):
|
v_colors = getattr(mesh, "vertex_colors", None)
|
||||||
|
v_uvs = getattr(mesh, "uvs", None)
|
||||||
|
if getattr(mesh, "vertex_counts", None) is not None:
|
||||||
vertex_count = int(mesh.vertex_counts[index].item())
|
vertex_count = int(mesh.vertex_counts[index].item())
|
||||||
face_count = int(mesh.face_counts[index].item())
|
face_count = int(mesh.face_counts[index].item())
|
||||||
vertices = mesh.vertices[index, :vertex_count]
|
vertices = mesh.vertices[index, :vertex_count]
|
||||||
faces = mesh.faces[index, :face_count]
|
faces = mesh.faces[index, :face_count]
|
||||||
colors = None
|
colors = v_colors[index, :vertex_count] if v_colors is not None else None
|
||||||
v_colors = getattr(mesh, "vertex_colors", None)
|
uvs = v_uvs[index, :vertex_count] if v_uvs is not None else None
|
||||||
if v_colors is not None:
|
return vertices, faces, colors, uvs
|
||||||
if hasattr(mesh, "color_counts"):
|
|
||||||
color_count = int(mesh.color_counts[index].item())
|
|
||||||
colors = v_colors[index, :color_count]
|
|
||||||
else:
|
|
||||||
colors = v_colors[index, :vertex_count]
|
|
||||||
return vertices, faces, colors
|
|
||||||
|
|
||||||
colors = None
|
colors = v_colors[index] if v_colors is not None else None
|
||||||
v_colors = getattr(mesh, "vertex_colors", None)
|
uvs = v_uvs[index] if v_uvs is not None else None
|
||||||
if v_colors is not None:
|
return mesh.vertices[index], mesh.faces[index], colors, uvs
|
||||||
colors = v_colors[index]
|
|
||||||
return mesh.vertices[index], mesh.faces[index], colors
|
|
||||||
|
|
||||||
|
|
||||||
def save_glb(vertices, faces, filepath, metadata=None,
|
def save_glb(vertices, faces, filepath, metadata=None,
|
||||||
@ -107,6 +102,8 @@ def save_glb(vertices, faces, filepath, metadata=None,
|
|||||||
colors_np = np.clip(colors_np, 0.0, 1.0)
|
colors_np = np.clip(colors_np, 0.0, 1.0)
|
||||||
|
|
||||||
n_verts = vertices_np.shape[0]
|
n_verts = vertices_np.shape[0]
|
||||||
|
if n_verts == 0:
|
||||||
|
raise ValueError("save_glb: vertices is empty")
|
||||||
if faces_signed.size > 0:
|
if faces_signed.size > 0:
|
||||||
fmin = int(faces_signed.min())
|
fmin = int(faces_signed.min())
|
||||||
fmax = int(faces_signed.max())
|
fmax = int(faces_signed.max())
|
||||||
@ -360,20 +357,18 @@ class SaveGLB(IO.ComfyNode):
|
|||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
# Handle Mesh input - save vertices and faces as GLB; carry optional UVs / colors / texture.
|
# Handle Mesh input - save vertices and faces as GLB; carry optional UVs / colors / texture.
|
||||||
uvs_b = getattr(mesh, "uvs", None)
|
|
||||||
texture_b = getattr(mesh, "texture", None)
|
texture_b = getattr(mesh, "texture", None)
|
||||||
for i in range(mesh.vertices.shape[0]):
|
for i in range(mesh.vertices.shape[0]):
|
||||||
vertices_i, faces_i, v_colors = get_mesh_batch_item(mesh, i)
|
vertices_i, faces_i, v_colors, uvs_i = get_mesh_batch_item(mesh, i)
|
||||||
if vertices_i.shape[0] == 0 or faces_i.shape[0] == 0:
|
if vertices_i.shape[0] == 0 or faces_i.shape[0] == 0:
|
||||||
logging.warning(f"SaveGLB: skipping empty mesh at batch index {i}")
|
logging.warning(f"SaveGLB: skipping empty mesh at batch index {i}")
|
||||||
continue
|
continue
|
||||||
uvs_i = None
|
|
||||||
if uvs_b is not None:
|
|
||||||
uvs_i = uvs_b[i, :vertices_i.shape[0]] if hasattr(mesh, "vertex_counts") else uvs_b[i]
|
|
||||||
tex_img = None
|
tex_img = None
|
||||||
if texture_b is not None:
|
if texture_b is not None:
|
||||||
from PIL import Image
|
|
||||||
arr = (texture_b[i].clamp(0.0, 1.0).cpu().numpy() * 255).astype(np.uint8)
|
arr = (texture_b[i].clamp(0.0, 1.0).cpu().numpy() * 255).astype(np.uint8)
|
||||||
|
assert arr.ndim == 3 and arr.shape[-1] == 3, (
|
||||||
|
f"texture[{i}] must be (H, W, 3) RGB, got shape {tuple(arr.shape)}"
|
||||||
|
)
|
||||||
tex_img = Image.fromarray(arr, mode="RGB")
|
tex_img = Image.fromarray(arr, mode="RGB")
|
||||||
f = f"{filename}_{counter:05}_.glb"
|
f = f"{filename}_{counter:05}_.glb"
|
||||||
save_glb(vertices_i, faces_i, os.path.join(full_output_folder, f), metadata,
|
save_glb(vertices_i, faces_i, os.path.join(full_output_folder, f), metadata,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user