Skip to content

Commit 6c77981

Browse files
committed
Update to use Multi View models.
1 parent 52e5fe8 commit 6c77981

File tree

11 files changed

+1615
-53
lines changed

11 files changed

+1615
-53
lines changed

Hunyuan3D-2

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ You will need to install [Visual C++ build tools](https://visualstudio.microsoft
1414

1515
### ComfyUI\_windows\_portable
1616

17-
If you installed ComfyUI with ComfyUI\_windows\_portable, it is using a version of python which is missing the libs and header files. The way around it is to either copy some folders or install the module from else where. See [issue #]()
17+
If you installed ComfyUI with ComfyUI\_windows\_portable, it is using a version of python which is missing the libs and header files. The way around it is to either copy some folders or install the module from else where. See [issue #3](https://github.com/niknah/ComfyUI-Hunyuan-3D-2/issues/3#issuecomment-2623589325)
18+
19+
If you are getting a ` has no attribute '_get_vc_env'` error.
20+
Run `pip install setuptools==75.8.2` to downgrade setuptools.
21+
See [Issue #148877](https://github.com/pytorch/pytorch/issues/148877)
1822

1923

2024
### Ubuntu
@@ -61,3 +65,7 @@ git submodule update --init # You need to get the submodules if you install fr
6165

6266

6367
![Screenshot, workflow is in the examples/ folder](assets/workflow_screenshot.png)
68+
69+
### Changes
70+
71+
0.9.4: Added mini, turbo models.

hunyuan_3d_node.py

Lines changed: 167 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import hashlib
1212
import platform
1313
import re
14+
from packaging import version
1415
from PIL import Image
1516
import numpy as np
1617

@@ -20,7 +21,13 @@ class Hunyuan3DImageTo3D:
2021
def INPUT_TYPES(s):
2122
models = [
2223
'tencent/Hunyuan3D-2/hunyuan3d-dit-v2-0',
23-
'tencent/Hunyuan3D-2/hunyuan3d-dit-v2-0-fast[variant=fp16]',
24+
'tencent/Hunyuan3D-2/hunyuan3d-dit-v2-0-fast',
25+
'tencent/Hunyuan3D-2mini/hunyuan3d-dit-v2-mini',
26+
'tencent/Hunyuan3D-2mini/hunyuan3d-dit-v2-mini-fast',
27+
'tencent/Hunyuan3D-2mini/hunyuan3d-dit-v2-mini-turbo',
28+
'tencent/Hunyuan3D-2mv/hunyuan3d-dit-v2-mv-fast',
29+
'tencent/Hunyuan3D-2mv/hunyuan3d-dit-v2-mv-turbo',
30+
'tencent/Hunyuan3D-2mv/hunyuan3d-dit-v2-mv',
2431
]
2532
return {
2633
"required": {
@@ -39,6 +46,12 @@ def INPUT_TYPES(s):
3946
"model": (models, {
4047
"tooltip": "huggingface id of model(author/name/subfolder)", # noqa: E501
4148
}),
49+
"back_image": ("IMAGE",),
50+
"back_mask": ("MASK",),
51+
"left_image": ("IMAGE",),
52+
"left_mask": ("MASK",),
53+
"right_image": ("IMAGE",),
54+
"right_mask": ("MASK",),
4255
}
4356
}
4457
RETURN_TYPES = ("STRING",)
@@ -63,6 +76,45 @@ def popen_print_output(args, cwd=None, shell=False):
6376
"\n"
6477
)
6578

79+
@staticmethod
80+
def install_custom_rasterizer(this_path):
81+
print("Installing custom_rasterizer")
82+
Hunyuan3DImageTo3D.popen_print_output(
83+
[sys.executable, 'setup.py', 'install'],
84+
os.path.join(
85+
this_path,
86+
'Hunyuan3D-2/hy3dgen/texgen/custom_rasterizer'
87+
)
88+
)
89+
90+
@staticmethod
91+
def install_hy3dgen(this_path):
92+
print("Installing hy3dgen")
93+
Hunyuan3DImageTo3D.popen_print_output(
94+
[sys.executable, 'setup.py', 'install'],
95+
os.path.join(this_path, 'Hunyuan3D-2')
96+
)
97+
98+
@staticmethod
99+
def install_mesh_processor(this_path):
100+
renderer_dir = os.path.join(
101+
this_path,
102+
'Hunyuan3D-2/hy3dgen/texgen/differentiable_renderer'
103+
)
104+
if platform.system() == 'Windows':
105+
if importlib.util.find_spec('mesh_processor') is None:
106+
print("Installing mesh_processor")
107+
Hunyuan3DImageTo3D.popen_print_output(
108+
[sys.executable, 'setup.py', 'install'],
109+
renderer_dir
110+
)
111+
else: # Linux
112+
if len(glob.glob(f'{renderer_dir}/mesh_processor*.so')) == 0:
113+
Hunyuan3DImageTo3D.popen_print_output(
114+
['/bin/bash', 'compile_mesh_painter.sh'],
115+
renderer_dir
116+
)
117+
66118
@staticmethod
67119
def install_check():
68120
this_path = os.path.dirname(os.path.realpath(__file__))
@@ -84,40 +136,20 @@ def install_check():
84136
# shell=True,
85137
# )
86138

139+
cr_version = version.parse("0.1")
87140
if importlib.util.find_spec('custom_rasterizer') is None:
88-
print("Installing custom_rasterizer")
89-
Hunyuan3DImageTo3D.popen_print_output(
90-
[sys.executable, 'setup.py', 'install'],
91-
os.path.join(
92-
this_path,
93-
'Hunyuan3D-2/hy3dgen/texgen/custom_rasterizer'
94-
)
95-
)
141+
Hunyuan3DImageTo3D.install_custom_rasterizer(this_path)
142+
elif cr_version > version.parse(importlib.metadata.version('custom_rasterizer')): # noqa: E501
143+
Hunyuan3DImageTo3D.install_custom_rasterizer(this_path)
96144

145+
hy3dgen_version = version.parse("2.0.2")
97146
if importlib.util.find_spec('hy3dgen') is None:
98-
print("Installing hy3dgen")
99-
Hunyuan3DImageTo3D.popen_print_output(
100-
[sys.executable, 'setup.py', 'install'],
101-
os.path.join(this_path, 'Hunyuan3D-2')
102-
)
147+
Hunyuan3DImageTo3D.install_hy3dgen(this_path)
148+
elif hy3dgen_version > version.parse(importlib.metadata.version('hy3dgen')): # noqa: E501
149+
Hunyuan3DImageTo3D.install_hy3dgen(this_path)
103150

104-
renderer_dir = os.path.join(
105-
this_path,
106-
'Hunyuan3D-2/hy3dgen/texgen/differentiable_renderer'
107-
)
108-
if platform.system() == 'Windows':
109-
if importlib.util.find_spec('mesh_processor') is None:
110-
print("Installing mesh_processor")
111-
Hunyuan3DImageTo3D.popen_print_output(
112-
[sys.executable, 'setup.py', 'install'],
113-
renderer_dir
114-
)
115-
else: # Linux
116-
if len(glob.glob(f'{renderer_dir}/mesh_processor*.so')) == 0:
117-
Hunyuan3DImageTo3D.popen_print_output(
118-
['/bin/bash', 'compile_mesh_painter.sh'],
119-
renderer_dir
120-
)
151+
if importlib.util.find_spec('mesh_processor') is None:
152+
Hunyuan3DImageTo3D.install_differentiable_renderer(this_path)
121153

122154
@staticmethod
123155
def get_spare_filename(filename_format):
@@ -127,6 +159,19 @@ def get_spare_filename(filename_format):
127159
return filename
128160
return None
129161

162+
def comfy_img_to_file(self, image, mask, input_image_file):
163+
i = 255. * image.cpu().numpy()
164+
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
165+
if mask is not None:
166+
m = 255. * mask.cpu().numpy()
167+
mask_pil = Image.fromarray(m)
168+
mask_pil = np.clip(mask_pil, 0, 255).astype(np.uint8)
169+
mask_pil = 255 - mask_pil
170+
# If it crashes here,
171+
# check that you have transparency in the image
172+
img.putalpha(Image.fromarray(mask_pil, mode='L'))
173+
img.save(input_image_file)
174+
130175
def process(
131176
self, image,
132177
mask,
@@ -136,6 +181,12 @@ def process(
136181
face_reducer,
137182
paint,
138183
model='tencent/Hunyuan3D-2',
184+
back_image=None,
185+
back_mask=None,
186+
left_image=None,
187+
left_mask=None,
188+
right_image=None,
189+
right_mask=None,
139190
):
140191
from hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline
141192
import hy3dgen.shapegen
@@ -152,7 +203,7 @@ def process(
152203
# if 'HY3DGEN_MODELS' not in os.environ:
153204
# os.environ['HY3DGEN_MODELS'] = checkpoint_dir
154205

155-
variant = None
206+
variant = 'fp16'
156207
variant_m = re.match(r"^(.*)\[variant=([^\]]+)\]$", model)
157208
if variant_m is not None:
158209
model = variant_m.group(1)
@@ -164,23 +215,72 @@ def process(
164215
model = subfolder_m.group(1)
165216
subfolder = subfolder_m.group(2)
166217

218+
isMV = model == 'tencent/Hunyuan3D-2mv'
167219
output_3d_file = None
168220
with tempfile.TemporaryDirectory() as temp_dir:
169-
for img, mask1 in zip(image, mask):
170-
i = 255. * img.cpu().numpy()
171-
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
172-
if mask1 is not None:
173-
m = 255. * mask1.cpu().numpy()
174-
mask = Image.fromarray(m)
175-
mask = np.clip(mask, 0, 255).astype(np.uint8)
176-
mask = 255 - mask
177-
# If it crashes here,
178-
# check that you have transparency in the image
179-
img.putalpha(Image.fromarray(mask, mode='L'))
180-
input_image_file = os.path.join(temp_dir, "input.png")
181-
img.save(input_image_file)
182-
183-
mask = i = img = None
221+
nth = 0
222+
front_images_list = []
223+
back_images_list = []
224+
left_images_list = []
225+
right_images_list = []
226+
if image is not None:
227+
front_images_list = list(zip(image, mask))
228+
if back_image is not None:
229+
back_images_list = list(zip(back_image, back_mask))
230+
if left_image is not None:
231+
left_images_list = list(zip(left_image, left_mask))
232+
if right_image is not None:
233+
right_images_list = list(zip(right_image, right_mask))
234+
235+
max_len = max(
236+
len(front_images_list),
237+
len(back_images_list),
238+
len(left_images_list),
239+
len(right_images_list),
240+
)
241+
242+
sides = {
243+
'front': front_images_list,
244+
'back': back_images_list,
245+
'left': left_images_list,
246+
'right': right_images_list,
247+
}
248+
249+
# front should always be first
250+
side_names = [
251+
'front',
252+
'back',
253+
'left',
254+
'right',
255+
]
256+
257+
for nth in range(0, max_len):
258+
image_obj = None
259+
for side in side_names:
260+
side_list = sides[side]
261+
if side_list is None:
262+
continue
263+
264+
if nth >= len(side_list):
265+
continue
266+
267+
side_info = side_list[nth]
268+
if side_info is None:
269+
continue
270+
271+
(side_image, side_mask) = side_list[nth]
272+
input_image_file = os.path.join(
273+
temp_dir, f"{side}{nth}.png"
274+
)
275+
self.comfy_img_to_file(
276+
side_image, side_mask, input_image_file
277+
)
278+
image_obj = {}
279+
if isMV:
280+
image_obj[side] = input_image_file
281+
else:
282+
image_obj = input_image_file
283+
break
184284

185285
pipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(
186286
model,
@@ -191,9 +291,11 @@ def process(
191291
output_3d_file = Hunyuan3DImageTo3D.get_spare_filename(
192292
os.path.join(output_dir, 'hunyuan3d_{:05X}.glb')
193293
)
294+
194295
mesh = pipeline(
195-
image=input_image_file, num_inference_steps=steps,
196-
generator=torch.manual_seed(2025))[0]
296+
image=image_obj, num_inference_steps=steps,
297+
generator=torch.manual_seed(2025)
298+
)[0]
197299
if floater_remover:
198300
mesh = hy3dgen.shapegen.FloaterRemover()(mesh)
199301
if face_remover:
@@ -204,7 +306,8 @@ def process(
204306
if paint:
205307
from hy3dgen.texgen import Hunyuan3DPaintPipeline
206308
pipeline = Hunyuan3DPaintPipeline.from_pretrained(
207-
model,
309+
"tencent/Hunyuan3D-2",
310+
# model,
208311
)
209312
mesh = pipeline(mesh, image=input_image_file)
210313

@@ -222,12 +325,26 @@ def IS_CHANGED(
222325
face_reducer,
223326
paint,
224327
model,
328+
back_image,
329+
back_mask,
330+
left_image,
331+
left_mask,
332+
right_image,
333+
right_mask,
225334
):
226335
m = hashlib.sha256()
336+
m.update(image)
337+
m.update(mask)
227338
m.update(steps)
228339
m.update(floater_remover)
229340
m.update(face_remover)
230341
m.update(face_reducer)
231342
m.update(paint)
232343
m.update(model)
344+
m.update(back_image)
345+
m.update(back_mask)
346+
m.update(left_image)
347+
m.update(left_mask)
348+
m.update(right_image)
349+
m.update(right_mask)
233350
return m.digest().hex()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "comfyui-hunyuan-3d-2"
33
description = "Image to 3D using Hunyuan-3D-2"
4-
version = "0.9.3"
4+
version = "0.9.4"
55
license = {text = "MIT License"}
66

77
[project.urls]
File renamed without changes.

0 commit comments

Comments
 (0)