PBR texture saving

This commit is contained in:
kijai 2025-01-25 14:55:45 +02:00
parent 57c84169cd
commit 699bb03e2c
2 changed files with 102 additions and 34 deletions

125
nodes.py
View File

@ -1,5 +1,6 @@
import os import os
import torch import torch
import torchvision.transforms as transforms
from PIL import Image from PIL import Image
from pathlib import Path from pathlib import Path
import numpy as np import numpy as np
@ -14,7 +15,7 @@ from comfy.utils import load_torch_file, ProgressBar
script_directory = os.path.dirname(os.path.abspath(__file__)) script_directory = os.path.dirname(os.path.abspath(__file__))
from .utils import log, print_memory, pil_list_to_torch_batch from .utils import log, print_memory
class ComfyProgressCallback: class ComfyProgressCallback:
def __init__(self, total_steps): def __init__(self, total_steps):
@ -138,28 +139,6 @@ class DownloadAndLoadHy3DDelightModel:
delight_pipe.enable_model_cpu_offload() delight_pipe.enable_model_cpu_offload()
return (delight_pipe,) return (delight_pipe,)
class Hy3DLoadMesh:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"glb_path": ("STRING", {"default": "", "tooltip": "The glb path with mesh to load."}),
}
}
RETURN_TYPES = ("HY3DMESH",)
RETURN_NAMES = ("mesh",)
OUTPUT_TOOLTIPS = ("The glb model with mesh to texturize.",)
FUNCTION = "load"
CATEGORY = "Hunyuan3DWrapper"
DESCRIPTION = "Loads a glb model from the given path."
def load(self, glb_path):
mesh = trimesh.load(glb_path, force="mesh")
return (mesh,)
class Hy3DDelightImage: class Hy3DDelightImage:
@classmethod @classmethod
@ -249,6 +228,7 @@ class DownloadAndLoadHy3DPaintModel:
pipeline.enable_model_cpu_offload() pipeline.enable_model_cpu_offload()
return (pipeline,) return (pipeline,)
#region Texture
class Hy3DCameraConfig: class Hy3DCameraConfig:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
@ -661,7 +641,31 @@ class Hy3DApplyTexture:
textured_mesh = self.render.save_mesh() textured_mesh = self.render.save_mesh()
return (textured_mesh,) return (textured_mesh,)
#region Mesh
class Hy3DLoadMesh:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"glb_path": ("STRING", {"default": "", "tooltip": "The glb path with mesh to load."}),
}
}
RETURN_TYPES = ("HY3DMESH",)
RETURN_NAMES = ("mesh",)
OUTPUT_TOOLTIPS = ("The glb model with mesh to texturize.",)
FUNCTION = "load"
CATEGORY = "Hunyuan3DWrapper"
DESCRIPTION = "Loads a glb model from the given path."
def load(self, glb_path):
mesh = trimesh.load(glb_path, force="mesh")
return (mesh,)
class Hy3DGenerateMesh: class Hy3DGenerateMesh:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
@ -754,6 +758,77 @@ class Hy3DPostprocessMesh:
return (mesh, ) return (mesh, )
class Hy3DGetMeshPBRTextures:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"mesh": ("HY3DMESH",),
"texture" : (["base_color", "emissive", "metallic_roughness", "normal", "occlusion"], ),
},
}
RETURN_TYPES = ("IMAGE", )
RETURN_NAMES = ("image",)
FUNCTION = "get_textures"
CATEGORY = "Hunyuan3DWrapper"
def get_textures(self, mesh, texture):
TEXTURE_MAPPING = {
'base_color': ('baseColorTexture', "Base color"),
'emissive': ('emissiveTexture', "Emissive"),
'metallic_roughness': ('metallicRoughnessTexture', "Metallic roughness"),
'normal': ('normalTexture', "Normal"),
'occlusion': ('occlusionTexture', "Occlusion"),
}
texture_attr, texture_name = TEXTURE_MAPPING[texture]
texture_data = getattr(mesh.visual.material, texture_attr)
if texture_data is None:
raise ValueError(f"{texture_name} texture not found")
to_tensor = transforms.ToTensor()
return (to_tensor(texture_data).unsqueeze(0).permute(0, 2, 3, 1).cpu().float(),)
class Hy3DSetMeshPBRTextures:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"mesh": ("HY3DMESH",),
"image": ("IMAGE", ),
"texture" : (["base_color", "emissive", "metallic_roughness", "normal", "occlusion"], ),
},
}
RETURN_TYPES = ("HY3DMESH", )
RETURN_NAMES = ("mesh",)
FUNCTION = "set_textures"
CATEGORY = "Hunyuan3DWrapper"
def set_textures(self, mesh, image, texture):
TEXTURE_MAPPING = {
'base_color': ('baseColorTexture', "Base color"),
'emissive': ('emissiveTexture', "Emissive"),
'metallic_roughness': ('metallicRoughnessTexture', "Metallic roughness"),
'normal': ('normalTexture', "Normal"),
'occlusion': ('occlusionTexture', "Occlusion"),
}
new_mesh = mesh.copy()
texture_attr, texture_name = TEXTURE_MAPPING[texture]
image_np = (image[0].cpu().numpy() * 255).astype(np.uint8)
if image_np.shape[2] == 4: # RGBA
pil_image = Image.fromarray(image_np, 'RGBA')
else: # RGB
pil_image = Image.fromarray(image_np, 'RGB')
setattr(new_mesh.visual.material, texture_attr, pil_image)
return (new_mesh,)
class Hy3DExportMesh: class Hy3DExportMesh:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
@ -798,6 +873,8 @@ NODE_CLASS_MAPPINGS = {
"Hy3DApplyTexture": Hy3DApplyTexture, "Hy3DApplyTexture": Hy3DApplyTexture,
"CV2InpaintTexture": CV2InpaintTexture, "CV2InpaintTexture": CV2InpaintTexture,
"Hy3DRenderMultiViewDepth": Hy3DRenderMultiViewDepth, "Hy3DRenderMultiViewDepth": Hy3DRenderMultiViewDepth,
"Hy3DGetMeshPBRTextures": Hy3DGetMeshPBRTextures,
"Hy3DSetMeshPBRTextures": Hy3DSetMeshPBRTextures
} }
NODE_DISPLAY_NAME_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = {
"Hy3DModelLoader": "Hy3DModelLoader", "Hy3DModelLoader": "Hy3DModelLoader",
@ -818,4 +895,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Hy3DApplyTexture": "Hy3D Apply Texture", "Hy3DApplyTexture": "Hy3D Apply Texture",
"CV2InpaintTexture": "CV2 Inpaint Texture", "CV2InpaintTexture": "CV2 Inpaint Texture",
"Hy3DRenderMultiViewDepth": "Hy3D Render MultiView Depth", "Hy3DRenderMultiViewDepth": "Hy3D Render MultiView Depth",
"Hy3DGetMeshPBRTextures": "Hy3D Get Mesh PBR Textures",
"Hy3DSetMeshPBRTextures": "Hy3D Set Mesh PBR Textures"
} }

View File

@ -23,14 +23,3 @@ def print_memory(device):
log.info(f"Max reserved memory: {max_reserved=:.3f} GB") log.info(f"Max reserved memory: {max_reserved=:.3f} GB")
#memory_summary = torch.cuda.memory_summary(device=device, abbreviated=False) #memory_summary = torch.cuda.memory_summary(device=device, abbreviated=False)
#log.info(f"Memory Summary:\n{memory_summary}") #log.info(f"Memory Summary:\n{memory_summary}")
def pil_list_to_torch_batch(normal_maps):
# Convert PIL images to numpy arrays and stack
arrays = [np.array(img) for img in normal_maps]
batch = np.stack(arrays, axis=0)
# Convert to torch tensor, ensure float32
tensor = torch.from_numpy(batch).float() / 255.0
# Tensor is now in B,H,W,C format
return tensor