add multiview

This commit is contained in:
Denis Kochetov
2025-05-08 02:22:13 +00:00
parent b0951721dc
commit feaa3e03b1
4 changed files with 284 additions and 69 deletions
@@ -0,0 +1,69 @@
model:
target: Hunyuan3D_V2.hy3dgen.shapegen.models.Hunyuan3DDiT
params:
in_channels: 64
context_in_dim: 1536
hidden_size: 1024
mlp_ratio: 4.0
num_heads: 16
depth: 16
depth_single_blocks: 32
axes_dim: [ 64 ]
theta: 10000
qkv_bias: true
guidance_embed: true
vae:
target: Hunyuan3D_V2.hy3dgen.shapegen.models.ShapeVAE
params:
num_latents: 3072
embed_dim: 64
num_freqs: 8
include_pi: false
heads: 16
width: 1024
num_decoder_layers: 16
qkv_bias: false
qk_norm: true
scale_factor: 0.9990943042622529
conditioner:
target: Hunyuan3D_V2.hy3dgen.shapegen.models.SingleImageEncoder
params:
main_image_encoder:
type: DinoImageEncoderMV # dino giant
kwargs:
config:
attention_probs_dropout_prob: 0.0
drop_path_rate: 0.0
hidden_act: gelu
hidden_dropout_prob: 0.0
hidden_size: 1536
image_size: 518
initializer_range: 0.02
layer_norm_eps: 1.e-6
layerscale_value: 1.0
mlp_ratio: 4
model_type: dinov2
num_attention_heads: 24
num_channels: 3
num_hidden_layers: 40
patch_size: 14
qkv_bias: true
torch_dtype: float32
use_swiglu_ffn: true
image_size: 518
scheduler:
target: Hunyuan3D_V2.hy3dgen.shapegen.schedulers.FlowMatchEulerDiscreteScheduler
params:
num_train_timesteps: 1000
image_processor:
target: Hunyuan3D_V2.hy3dgen.shapegen.preprocessors.MVImageProcessorV2
params:
size: 512
border_ratio: 0.15
pipeline:
target: Hunyuan3D_V2.hy3dgen.shapegen.pipelines.Hunyuan3DDiTFlowMatchingPipeline
@@ -0,0 +1,68 @@
model:
target: Hunyuan3D_V2.hy3dgen.shapegen.models.Hunyuan3DDiT
params:
in_channels: 64
context_in_dim: 1536
hidden_size: 1024
mlp_ratio: 4.0
num_heads: 16
depth: 16
depth_single_blocks: 32
axes_dim: [ 64 ]
theta: 10000
qkv_bias: true
vae:
target: Hunyuan3D_V2.hy3dgen.shapegen.models.ShapeVAE
params:
num_latents: 3072
embed_dim: 64
num_freqs: 8
include_pi: false
heads: 16
width: 1024
num_decoder_layers: 16
qkv_bias: false
qk_norm: true
scale_factor: 0.9990943042622529
conditioner:
target: Hunyuan3D_V2.hy3dgen.shapegen.models.SingleImageEncoder
params:
main_image_encoder:
type: DinoImageEncoderMV # dino giant
kwargs:
config:
attention_probs_dropout_prob: 0.0
drop_path_rate: 0.0
hidden_act: gelu
hidden_dropout_prob: 0.0
hidden_size: 1536
image_size: 518
initializer_range: 0.02
layer_norm_eps: 1.e-6
layerscale_value: 1.0
mlp_ratio: 4
model_type: dinov2
num_attention_heads: 24
num_channels: 3
num_hidden_layers: 40
patch_size: 14
qkv_bias: true
torch_dtype: float32
use_swiglu_ffn: true
image_size: 518
scheduler:
target: Hunyuan3D_V2.hy3dgen.shapegen.schedulers.FlowMatchEulerDiscreteScheduler
params:
num_train_timesteps: 1000
image_processor:
target: Hunyuan3D_V2.hy3dgen.shapegen.preprocessors.MVImageProcessorV2
params:
size: 512
border_ratio: 0.15
pipeline:
target: Hunyuan3D_V2.hy3dgen.shapegen.pipelines.Hunyuan3DDiTFlowMatchingPipeline
@@ -93,8 +93,8 @@ class Hunyuan3DPaintPipeline:
texture_size=self.config.texture_size)
self.load_models()
def to(self, device=None, dtype=None): # ⬅️  тот же уровень, что и __init__
def to(self, device=None, dtype=None):
if device is not None:
for m in self.models.values():
m.pipeline.to(device)
+145 -67
View File
@@ -97,6 +97,7 @@ from Hunyuan3D_V1.mvd.hunyuan3d_mvd_lite_pipeline import Hunyuan3D_MVD_Lite_Pipe
from Hunyuan3D_V1.infer import Views2Mesh
from Hunyuan3D_V2.hy3dgen.shapegen import FaceReducer, FloaterRemover, DegenerateFaceRemover, Hunyuan3DDiTFlowMatchingPipeline
from Hunyuan3D_V2.hy3dgen.texgen import Hunyuan3DPaintPipeline
from Hunyuan3D_V2.hy3dgen.rembg import BackgroundRemover
from TRELLIS.trellis.pipelines import TrellisImageTo3DPipeline
from TRELLIS.trellis.utils import postprocessing_utils
from TripoSG.pipelines.pipeline_triposg import TripoSGPipeline
@@ -4262,11 +4263,14 @@ class Load_Hunyuan3D_V2_ShapeGen_Pipeline:
_MODES = {
"Hunyuan3D-2": ("Hunyuan3D-2", "hunyuan3d-dit-v2-0", 30),
"Hunyuan3D-2-Fast": ("Hunyuan3D-2", "hunyuan3d-dit-v2-0-fast", 5),
"Hunyuan3D-2-Fast": ("Hunyuan3D-2", "hunyuan3d-dit-v2-0-fast", 20),
"Hunyuan3D-2-Turbo": ("Hunyuan3D-2", "hunyuan3d-dit-v2-0-turbo", 5),
"Hunyuan3D-2mini": ("Hunyuan3D-2mini", "hunyuan3d-dit-v2-mini", 30),
"Hunyuan3D-2mini-Fast": ("Hunyuan3D-2mini", "hunyuan3d-dit-v2-mini-fast", 5),
"Hunyuan3D-2mini-Fast": ("Hunyuan3D-2mini", "hunyuan3d-dit-v2-mini-fast", 20),
"Hunyuan3D-2mini-Turbo": ("Hunyuan3D-2mini", "hunyuan3d-dit-v2-mini-turbo", 5),
"Hunyuan3D-2mv": ("Hunyuan3D-2mv", "hunyuan3d-dit-v2-mv", 30),
"Hunyuan3D-2mv-Fast": ("Hunyuan3D-2mv", "hunyuan3d-dit-v2-mv-fast", 20),
"Hunyuan3D-2mv-Turbo": ("Hunyuan3D-2mv", "hunyuan3d-dit-v2-mv-turbo", 5),
}
@classmethod
@@ -4290,7 +4294,6 @@ class Load_Hunyuan3D_V2_ShapeGen_Pipeline:
repo_id=f"{Load_Hunyuan3D_V2_ShapeGen_Pipeline._REPO_ID_BASE}/{repo}",
repo_type="model",
local_dir=base_dir,
local_dir_use_symlinks=False,
resume_download=True,
ignore_patterns = HF_DOWNLOAD_IGNORE
)
@@ -4329,51 +4332,6 @@ class Load_Hunyuan3D_V2_ShapeGen_Pipeline:
pipe = self._build_pipe(repo, subfolder, use_safe, flash_vdm)
pipe.default_steps = def_steps
return (pipe,)
class Hunyuan3D_V2_ShapeGen:
CATEGORY = "Comfy3D/Algorithm"
RETURN_TYPES = ("MESH",)
RETURN_NAMES = ("mesh",)
FUNCTION = "run"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"shapegen_pipe" : ("DIFFUSERS_PIPE",),
"reference_image" : ("IMAGE",),
"reference_mask" : ("MASK",),
"seed" : ("INT", {"default": 1234, "min": 0, "max": 0xffffffffffffffff}),
"guidance_scale" : ("FLOAT", {"default": 5.0, "min": 0.0, "step": 0.1}),
"num_inference_steps": ("INT", {"default": 5, "min": 0}),
"octree_resolution" : ("INT", {"default": 256, "min": 64}),
}
}
@torch.no_grad()
def run(self, shapegen_pipe, reference_image, reference_mask, seed,
guidance_scale, num_inference_steps, octree_resolution):
img = torch_imgs_to_pils(reference_image, reference_mask)[0]
steps = (shapegen_pipe.default_steps if num_inference_steps == 0
else num_inference_steps)
gen = torch.Generator(device=shapegen_pipe.device).manual_seed(seed)
mesh = shapegen_pipe(
image = img,
num_inference_steps = steps,
guidance_scale = guidance_scale,
generator = gen,
octree_resolution = octree_resolution,
output_type = "trimesh",
)[0]
for fn in (FloaterRemover(), DegenerateFaceRemover(), FaceReducer()):
mesh = fn(mesh)
return (Mesh.load_trimesh(given_mesh=mesh),)
class Load_Hunyuan3D_V2_TexGen_Pipeline:
CATEGORY = "Comfy3D/Algorithm"
@@ -4403,7 +4361,6 @@ class Load_Hunyuan3D_V2_TexGen_Pipeline:
repo_id=repo_id,
repo_type="model",
local_dir=base_dir,
local_dir_use_symlinks=False,
resume_download=True,
ignore_patterns = HF_DOWNLOAD_IGNORE
)
@@ -4418,33 +4375,154 @@ class Load_Hunyuan3D_V2_TexGen_Pipeline:
)
return (pipe.to("cuda",torch.float16),)
class Hunyuan3D_V2_Paint_Model_Turbo:
CATEGORY = "Comfy3D/Algorithm"
RETURN_TYPES = ("MESH",)
RETURN_NAMES = ("mesh",)
FUNCTION = "run"
class Hunyuan3D_V2_Paint_Model_Turbo_MV:
"""
Texture-painting pipeline using a list of PIL images.
If list contains 1 image → single-view; if >1 → multi-view mode.
"""
CATEGORY = "Comfy3D/Algorithm"
RETURN_TYPES = ("MESH",)
RETURN_NAMES = ("mesh",)
FUNCTION = "run"
@classmethod
def INPUT_TYPES(cls):
return {"required": {
"hunyuan3d_v2_texgen_pipe": ("DIFFUSERS_PIPE",),
"reference_image": ("IMAGE",),
"reference_mask": ("MASK",),
"mesh": ("MESH",),
}}
return {
"required": {
"hunyuan3d_v2_texgen_pipe": ("DIFFUSERS_PIPE",),
"mesh": ("MESH",),
"images": ("LIST",),
}
}
@torch.no_grad()
def run(self, hunyuan3d_v2_texgen_pipe,
reference_image, reference_mask, mesh):
img_pil = torch_imgs_to_pils(reference_image, reference_mask)[0]
def run(self, hunyuan3d_v2_texgen_pipe, mesh, images):
if not isinstance(images, list) or len(images) == 0:
raise Exception("[Hunyuan3D_V2_Paint_Model_Turbo_MV] 'images' must be a non-empty list of PIL images")
v_np = mesh.v.detach().cpu().numpy()
f_np = mesh.f.detach().cpu().numpy()
tri = trimesh.Trimesh(vertices=v_np, faces=f_np)
tri = trimesh.Trimesh(vertices=v_np, faces=f_np)
try:
textured = hunyuan3d_v2_texgen_pipe(tri, images)
except Exception as e:
raise Exception(f"[Hunyuan3D_V2_Paint_Model_Turbo_MV] Texture generation failed: {str(e)}")
textured = hunyuan3d_v2_texgen_pipe(tri, img_pil)
m_out = Mesh.load_trimesh(given_mesh=textured)
m_out.auto_normal()
return (m_out,)
class Multi_Background_Remover:
"""
Converts 1 to 4 image inputs (front/back/left/right) to a list of processed PIL images.
Applies RGBA conversion and background removal.
Suitable for feeding directly into ShapeGen or Paint models.
"""
CATEGORY = "Comfy3D/Preprocessors"
RETURN_TYPES = ("LIST",) # List of PIL images
RETURN_NAMES = ("images",)
FUNCTION = "run"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image_front": ("IMAGE",),
},
"optional": {
"image_back": ("IMAGE",),
"image_left": ("IMAGE",),
}
}
@torch.no_grad()
def run(
self,
image_front,
image_back=None,
image_left=None
):
rmbg = BackgroundRemover()
mv_inputs = {
k: v for k, v in {
"front": image_front,
"back": image_back,
"left": image_left
}.items() if v is not None
}
images = []
for key, tensor_img in mv_inputs.items():
pil_img = torch_imgs_to_pils(tensor_img)[0].convert("RGBA")
if pil_img.mode == "RGB":
pil_img = rmbg(pil_img.convert("RGB"))
images.append(pil_img)
return (images,)
class Hunyuan3D_V2_ShapeGen_MV:
"""
Shape generation pipeline using a list of processed PIL images.
If len(images) == 1 → single-view; if >1 → multi-view dict.
"""
CATEGORY = "Comfy3D/Algorithm"
RETURN_TYPES = ("MESH",)
RETURN_NAMES = ("mesh",)
FUNCTION = "run"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"shapegen_pipe": ("DIFFUSERS_PIPE",),
"images": ("LIST",),
"seed": ("INT", {"default": 1234, "min": 0, "max": 0xffffffffffffffff}),
"guidance_scale": ("FLOAT", {"default": 5.0, "min": 0.0, "step": 0.1}),
"num_inference_steps": ("INT", {"default": 5, "min": 0, "tooltip": "Turbo: 5; Fast/Standard: 30–40"}),
"octree_resolution": ("INT", {"default": 256, "min": 64}),
}
}
@torch.no_grad()
def run(
self,
shapegen_pipe,
images,
seed=1234,
guidance_scale=5.0,
num_inference_steps=5,
octree_resolution=256
):
if not isinstance(images, list) or len(images) == 0:
raise Exception("[Hunyuan3D_V2_ShapeGen_MV] 'images' must be a non-empty list of PIL images")
if len(images) == 1:
image = images[0]
else:
directions = ["front", "back", "left"]
image = {k: v for k, v in zip(directions, images)}
steps = shapegen_pipe.default_steps if num_inference_steps == 0 else num_inference_steps
gen = torch.Generator(device=shapegen_pipe.device).manual_seed(seed)
try:
mesh = shapegen_pipe(
image=image,
num_inference_steps=steps,
guidance_scale=guidance_scale,
generator=gen,
octree_resolution=octree_resolution,
output_type="trimesh",
)[0]
except Exception as e:
raise Exception(f"[Hunyuan3D_V2_ShapeGen_MV] Shape generation failed: {str(e)}")
for fn in (FloaterRemover(), DegenerateFaceRemover(), FaceReducer()):
mesh = fn(mesh)
return (Mesh.load_trimesh(given_mesh=mesh),)