Small adjustments

This commit is contained in:
kijai 2026-05-13 17:06:25 +03:00
parent aac9a56d79
commit be79f67188

View File

@ -1,12 +1,10 @@
"""Save-side 3D nodes: mesh packing/slicing helpers + GLB writer + SaveGLB node. """Save-side 3D nodes: mesh packing/slicing helpers + GLB writer + SaveGLB node."""
Pairs with nodes_load_3d.py (load-side counterpart).
"""
import json import json
import logging import logging
import os import os
import struct import struct
from io import BytesIO
import numpy as np import numpy as np
from PIL import Image from PIL import Image
@ -22,7 +20,7 @@ def pack_variable_mesh_batch(vertices, faces, colors=None, uvs=None, texture=Non
# 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.
# colors and 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
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)
max_faces = max(f.shape[0] for f in faces) max_faces = max(f.shape[0] for f in faces)
@ -122,8 +120,7 @@ def save_glb(vertices, faces, filepath, metadata=None,
faces_np = faces_signed.astype(np.uint32) faces_np = faces_signed.astype(np.uint32)
texture_png_bytes = None texture_png_bytes = None
if texture_image is not None: if texture_image is not None:
import io as _io buf = BytesIO()
buf = _io.BytesIO()
texture_image.save(buf, format="PNG") texture_image.save(buf, format="PNG")
texture_png_bytes = buf.getvalue() texture_png_bytes = buf.getvalue()
@ -143,8 +140,13 @@ def save_glb(vertices, faces, filepath, metadata=None,
colors_buffer_padded = pad_to_4_bytes(colors_buffer) colors_buffer_padded = pad_to_4_bytes(colors_buffer)
texture_buffer_padded = pad_to_4_bytes(texture_buffer) texture_buffer_padded = pad_to_4_bytes(texture_buffer)
buffer_data = (vertices_buffer_padded + indices_buffer_padded buffer_data = b"".join([
+ uvs_buffer_padded + colors_buffer_padded + texture_buffer_padded) vertices_buffer_padded,
indices_buffer_padded,
uvs_buffer_padded,
colors_buffer_padded,
texture_buffer_padded,
])
vertices_byte_length = len(vertices_buffer) vertices_byte_length = len(vertices_buffer)
vertices_byte_offset = 0 vertices_byte_offset = 0
@ -270,7 +272,7 @@ def save_glb(vertices, faces, filepath, metadata=None,
if materials: if materials:
gltf["materials"] = materials gltf["materials"] = materials
if metadata is not None: if metadata:
gltf["asset"]["extras"] = metadata gltf["asset"]["extras"] = metadata
# Convert the JSON to bytes # Convert the JSON to bytes
@ -282,8 +284,7 @@ def save_glb(vertices, faces, filepath, metadata=None,
gltf_json_padded = pad_json_to_4_bytes(gltf_json) gltf_json_padded = pad_json_to_4_bytes(gltf_json)
# Create the GLB header # Create the GLB header (a 4-byte ASCII magic identifier glTF)
# Magic glTF
glb_header = struct.pack('<4sII', b'glTF', 2, 12 + 8 + len(gltf_json_padded) + 8 + len(buffer_data)) glb_header = struct.pack('<4sII', b'glTF', 2, 12 + 8 + len(gltf_json_padded) + 8 + len(buffer_data))
# Create JSON chunk header (chunk type 0) # Create JSON chunk header (chunk type 0)
@ -355,21 +356,22 @@ class SaveGLB(IO.ComfyNode):
"subfolder": subfolder, "subfolder": subfolder,
"type": "output" "type": "output"
}) })
counter += 1
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.
texture_b = getattr(mesh, "texture", None) texture_b = getattr(mesh, "texture", None)
texture_np = None
if texture_b is not None:
texture_np = (texture_b.clamp(0.0, 1.0).cpu().numpy() * 255).astype(np.uint8)
assert texture_np.ndim == 4 and texture_np.shape[-1] == 3, (
f"texture must be (B, H, W, 3) RGB, got shape {tuple(texture_np.shape)}"
)
for i in range(mesh.vertices.shape[0]): for i in range(mesh.vertices.shape[0]):
vertices_i, faces_i, v_colors, uvs_i = 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
tex_img = None tex_img = Image.fromarray(texture_np[i], mode="RGB") if texture_np is not None else None
if texture_b is not None:
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")
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,
uvs=uvs_i, uvs=uvs_i,