mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-07-04 21:51:00 +08:00
..
This commit is contained in:
parent
9b52e24430
commit
1ccf2d413e
@ -284,80 +284,6 @@ def fill_holes_fn(vertices, faces, max_perimeter=0.03):
|
|||||||
|
|
||||||
return v, f
|
return v, f
|
||||||
|
|
||||||
|
|
||||||
def make_double_sided(vertices, faces, colors=None, normals=None, z_offset=1e-4):
|
|
||||||
"""
|
|
||||||
Creates double-sided mesh using PER-FACE normals for offset.
|
|
||||||
This avoids pole singularities completely.
|
|
||||||
"""
|
|
||||||
is_batched = vertices.ndim == 3
|
|
||||||
|
|
||||||
if is_batched:
|
|
||||||
v_list, f_list, c_list = [], [], []
|
|
||||||
for i in range(vertices.shape[0]):
|
|
||||||
# Compute face normals for this mesh
|
|
||||||
v0 = vertices[i][faces[i][:, 0]]
|
|
||||||
v1 = vertices[i][faces[i][:, 1]]
|
|
||||||
v2 = vertices[i][faces[i][:, 2]]
|
|
||||||
fn = torch.cross(v1 - v0, v2 - v0, dim=-1)
|
|
||||||
fn = fn / (torch.norm(fn, dim=-1, keepdim=True) + 1e-8)
|
|
||||||
|
|
||||||
# Offset each face's vertices along its face normal
|
|
||||||
front = torch.stack([v0, v1, v2], dim=1) + fn.unsqueeze(1) * z_offset
|
|
||||||
back = torch.stack([v0, v1, v2], dim=1) - fn.unsqueeze(1) * z_offset
|
|
||||||
|
|
||||||
front = front.reshape(-1, 3)
|
|
||||||
back = back.reshape(-1, 3)
|
|
||||||
|
|
||||||
f_front = torch.arange(faces[i].shape[0] * 3, device=vertices.device).reshape(-1, 3)
|
|
||||||
f_back = f_front + faces[i].shape[0] * 3
|
|
||||||
f_back = f_back[:, [0, 2, 1]] # flip winding for back faces
|
|
||||||
|
|
||||||
v_list.append(torch.cat([front, back], dim=0))
|
|
||||||
f_list.append(torch.cat([f_front, f_back], dim=0))
|
|
||||||
|
|
||||||
if colors is not None:
|
|
||||||
c_faces = colors[i][faces[i]]
|
|
||||||
c_front = c_faces.reshape(-1, colors[i].shape[-1])
|
|
||||||
c_back = c_front.clone()
|
|
||||||
c_list.append(torch.cat([c_front, c_back], dim=0))
|
|
||||||
|
|
||||||
out_v = torch.stack(v_list)
|
|
||||||
out_f = torch.stack(f_list)
|
|
||||||
if colors is not None:
|
|
||||||
return out_v, out_f, torch.stack(c_list)
|
|
||||||
return out_v, out_f
|
|
||||||
|
|
||||||
# --- Unbatched ---
|
|
||||||
v0 = vertices[faces[:, 0]]
|
|
||||||
v1 = vertices[faces[:, 1]]
|
|
||||||
v2 = vertices[faces[:, 2]]
|
|
||||||
fn = torch.cross(v1 - v0, v2 - v0, dim=-1)
|
|
||||||
fn = fn / (torch.norm(fn, dim=-1, keepdim=True) + 1e-8)
|
|
||||||
|
|
||||||
# Offset each face's vertices along its face normal
|
|
||||||
front = torch.stack([v0, v1, v2], dim=1) + fn.unsqueeze(1) * z_offset
|
|
||||||
back = torch.stack([v0, v1, v2], dim=1) - fn.unsqueeze(1) * z_offset
|
|
||||||
|
|
||||||
front = front.reshape(-1, 3)
|
|
||||||
back = back.reshape(-1, 3)
|
|
||||||
|
|
||||||
f_front = torch.arange(faces.shape[0] * 3, device=vertices.device).reshape(-1, 3)
|
|
||||||
f_back = f_front + faces.shape[0] * 3
|
|
||||||
f_back = f_back[:, [0, 2, 1]] # flip winding for back faces
|
|
||||||
|
|
||||||
v_dup = torch.cat([front, back], dim=0)
|
|
||||||
f_dup = torch.cat([f_front, f_back], dim=0)
|
|
||||||
|
|
||||||
if colors is not None:
|
|
||||||
c_faces = colors[faces]
|
|
||||||
c_front = c_faces.reshape(-1, colors.shape[-1])
|
|
||||||
c_back = c_front.clone()
|
|
||||||
c_dup = torch.cat([c_front, c_back], dim=0)
|
|
||||||
return v_dup, f_dup, c_dup
|
|
||||||
|
|
||||||
return v_dup, f_dup, None
|
|
||||||
|
|
||||||
def _cleanup_mesh(verts, faces, min_angle_deg=0.5, max_aspect=100.0):
|
def _cleanup_mesh(verts, faces, min_angle_deg=0.5, max_aspect=100.0):
|
||||||
if faces.numel() == 0:
|
if faces.numel() == 0:
|
||||||
return verts, faces
|
return verts, faces
|
||||||
@ -905,32 +831,10 @@ class FillHoles(IO.ComfyNode):
|
|||||||
return v, f, c
|
return v, f, c
|
||||||
return _process_mesh_batch(mesh, _fn)
|
return _process_mesh_batch(mesh, _fn)
|
||||||
|
|
||||||
|
|
||||||
class MakeDoubleSided(IO.ComfyNode):
|
|
||||||
@classmethod
|
|
||||||
def define_schema(cls):
|
|
||||||
return IO.Schema(
|
|
||||||
node_id="MakeDoubleSided",
|
|
||||||
display_name="Make Double Sided",
|
|
||||||
category="latent/3d",
|
|
||||||
description="Duplicates faces with flipped normals so the mesh renders from both sides.",
|
|
||||||
inputs=[IO.Mesh.Input("mesh")],
|
|
||||||
outputs=[IO.Mesh.Output("mesh")],
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def execute(cls, mesh):
|
|
||||||
def _fn(v, f, c):
|
|
||||||
return make_double_sided(v, f, c)
|
|
||||||
return _process_mesh_batch(mesh, _fn)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PostProcessMeshExtension(ComfyExtension):
|
class PostProcessMeshExtension(ComfyExtension):
|
||||||
@override
|
@override
|
||||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||||
return [
|
return [
|
||||||
MakeDoubleSided,
|
|
||||||
FillHoles,
|
FillHoles,
|
||||||
DecimateMesh,
|
DecimateMesh,
|
||||||
PaintMesh
|
PaintMesh
|
||||||
|
|||||||
@ -234,6 +234,12 @@ def save_glb(vertices, faces, filepath, metadata=None,
|
|||||||
textures = []
|
textures = []
|
||||||
samplers = []
|
samplers = []
|
||||||
materials = []
|
materials = []
|
||||||
|
pbr = {
|
||||||
|
"metallicFactor": 0.0,
|
||||||
|
"roughnessFactor": 0.5,
|
||||||
|
"baseColorFactor": [0.22, 0.22, 0.22, 1.0],
|
||||||
|
}
|
||||||
|
|
||||||
if texture_png_bytes is not None and "TEXCOORD_0" in primitive_attributes:
|
if texture_png_bytes is not None and "TEXCOORD_0" in primitive_attributes:
|
||||||
buffer_views.append({
|
buffer_views.append({
|
||||||
"buffer": 0,
|
"buffer": 0,
|
||||||
@ -243,15 +249,13 @@ def save_glb(vertices, faces, filepath, metadata=None,
|
|||||||
images.append({"bufferView": len(buffer_views) - 1, "mimeType": "image/png"})
|
images.append({"bufferView": len(buffer_views) - 1, "mimeType": "image/png"})
|
||||||
samplers.append({"magFilter": 9729, "minFilter": 9729, "wrapS": 33071, "wrapT": 33071})
|
samplers.append({"magFilter": 9729, "minFilter": 9729, "wrapS": 33071, "wrapT": 33071})
|
||||||
textures.append({"source": 0, "sampler": 0})
|
textures.append({"source": 0, "sampler": 0})
|
||||||
materials.append({
|
pbr["baseColorTexture"] = {"index": 0, "texCoord": 0}
|
||||||
"pbrMetallicRoughness": {
|
|
||||||
"baseColorTexture": {"index": 0, "texCoord": 0},
|
materials.append({
|
||||||
"metallicFactor": 0.0,
|
"pbrMetallicRoughness": pbr,
|
||||||
"roughnessFactor": 1.0,
|
"doubleSided": True,
|
||||||
},
|
})
|
||||||
"doubleSided": True,
|
primitive["material"] = 0
|
||||||
})
|
|
||||||
primitive["material"] = 0
|
|
||||||
|
|
||||||
gltf = {
|
gltf = {
|
||||||
"asset": {"version": "2.0", "generator": "ComfyUI"},
|
"asset": {"version": "2.0", "generator": "ComfyUI"},
|
||||||
@ -373,10 +377,14 @@ class SaveGLB(IO.ComfyNode):
|
|||||||
continue
|
continue
|
||||||
tex_img = Image.fromarray(texture_np[i], mode="RGB") if texture_np is not None else None
|
tex_img = Image.fromarray(texture_np[i], mode="RGB") if texture_np is not None else None
|
||||||
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(
|
||||||
uvs=uvs_i,
|
vertices_i, faces_i,
|
||||||
vertex_colors=v_colors,
|
os.path.join(full_output_folder, f),
|
||||||
texture_image=tex_img)
|
metadata,
|
||||||
|
uvs=uvs_i,
|
||||||
|
vertex_colors=v_colors,
|
||||||
|
texture_image=tex_img,
|
||||||
|
)
|
||||||
results.append({
|
results.append({
|
||||||
"filename": f,
|
"filename": f,
|
||||||
"subfolder": subfolder,
|
"subfolder": subfolder,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user