NicolasG2523 commited on
Commit
6cb3ee9
·
verified ·
1 Parent(s): 36c303f

Upload gradio_app.py

Browse files
hy3dgen/texgen/differentiable_renderer/gradio_app.py ADDED
@@ -0,0 +1,789 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT
2
+ # except for the third-party components listed below.
3
+ # Hunyuan 3D does not impose any additional limitations beyond what is outlined
4
+ # in the repsective licenses of these third-party components.
5
+ # Users must comply with all terms and conditions of original licenses of these third-party
6
+ # components and must ensure that the usage of the third party components adheres to
7
+ # all relevant laws and regulations.
8
+
9
+ # For avoidance of doubts, Hunyuan 3D means the large language models and
10
+ # their software and algorithms, including trained model weights, parameters (including
11
+ # optimizer states), machine-learning model code, inference-enabling code, training-enabling code,
12
+ # fine-tuning enabling code and other elements of the foregoing made publicly available
13
+ # by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.
14
+
15
+ import os
16
+ import random
17
+ import shutil
18
+ import time
19
+ from glob import glob
20
+ from pathlib import Path
21
+
22
+ import gradio as gr
23
+ import torch
24
+ import trimesh
25
+ import uvicorn
26
+ from fastapi import FastAPI
27
+ from fastapi.staticfiles import StaticFiles
28
+ import uuid
29
+
30
+ from hy3dgen.shapegen.utils import logger
31
+
32
+ MAX_SEED = 1e7
33
+
34
+ import spaces
35
+
36
+ if True:
37
+ import os
38
+ import subprocess
39
+ import sys
40
+ import shlex
41
+ print("cd /home/user/app/hy3dgen/texgen/differentiable_renderer/ && bash compile_mesh_painter.sh")
42
+ os.system("cd /home/user/app/hy3dgen/texgen/differentiable_renderer/ && bash compile_mesh_painter.sh")
43
+ print('install custom')
44
+ subprocess.run(shlex.split("pip install custom_rasterizer-0.1-cp310-cp310-linux_x86_64.whl"), check=True)
45
+
46
+ # 2 functions just for GPU usage at startup
47
+ @spaces.GPU
48
+ def my_gpu_function():
49
+ return "my_gpu_function processed on GPU"
50
+
51
+ def call_gpu_function():
52
+ return my_gpu_function() # Only called on Gradio event
53
+
54
+ def get_example_img_list():
55
+ print('Loading example img list ...')
56
+ return sorted(glob('./assets/example_images/**/*.png', recursive=True))
57
+
58
+
59
+ def get_example_txt_list():
60
+ print('Loading example txt list ...')
61
+ txt_list = list()
62
+ for line in open('./assets/example_prompts.txt', encoding='utf-8'):
63
+ txt_list.append(line.strip())
64
+ return txt_list
65
+
66
+
67
+ def get_example_mv_list():
68
+ print('Loading example mv list ...')
69
+ mv_list = list()
70
+ root = './assets/example_mv_images'
71
+ for mv_dir in os.listdir(root):
72
+ view_list = []
73
+ for view in ['front', 'back', 'left', 'right']:
74
+ path = os.path.join(root, mv_dir, f'{view}.png')
75
+ if os.path.exists(path):
76
+ view_list.append(path)
77
+ else:
78
+ view_list.append(None)
79
+ mv_list.append(view_list)
80
+ return mv_list
81
+
82
+
83
+ def gen_save_folder(max_size=200):
84
+ os.makedirs(SAVE_DIR, exist_ok=True)
85
+
86
+ # 获取所有文件夹路径
87
+ dirs = [f for f in Path(SAVE_DIR).iterdir() if f.is_dir()]
88
+
89
+ # 如果文件夹数量超过 max_size,删除创建时间最久的文件夹
90
+ if len(dirs) >= max_size:
91
+ # 按创建时间排序,最久的排在前面
92
+ oldest_dir = min(dirs, key=lambda x: x.stat().st_ctime)
93
+ shutil.rmtree(oldest_dir)
94
+ print(f"Removed the oldest folder: {oldest_dir}")
95
+
96
+ # 生成一个新的 uuid 文件夹名称
97
+ new_folder = os.path.join(SAVE_DIR, str(uuid.uuid4()))
98
+ os.makedirs(new_folder, exist_ok=True)
99
+ print(f"Created new folder: {new_folder}")
100
+
101
+ return new_folder
102
+
103
+
104
+ def export_mesh(mesh, save_folder, textured=False, type='glb'):
105
+ if textured:
106
+ path = os.path.join(save_folder, f'textured_mesh.{type}')
107
+ else:
108
+ path = os.path.join(save_folder, f'white_mesh.{type}')
109
+ if type not in ['glb', 'obj']:
110
+ mesh.export(path)
111
+ else:
112
+ mesh.export(path, include_normals=textured)
113
+ return path
114
+
115
+
116
+ def randomize_seed_fn(seed: int, randomize_seed: bool) -> int:
117
+ if randomize_seed:
118
+ seed = random.randint(0, MAX_SEED)
119
+ return seed
120
+
121
+
122
+ def build_model_viewer_html(save_folder, height=660, width=790, textured=False):
123
+ # Remove first folder from path to make relative path
124
+ if textured:
125
+ related_path = f"./textured_mesh.glb"
126
+ template_name = './assets/modelviewer-textured-template.html'
127
+ output_html_path = os.path.join(save_folder, f'textured_mesh.html')
128
+ else:
129
+ related_path = f"./white_mesh.glb"
130
+ template_name = './assets/modelviewer-template.html'
131
+ output_html_path = os.path.join(save_folder, f'white_mesh.html')
132
+ offset = 50 if textured else 10
133
+ with open(os.path.join(CURRENT_DIR, template_name), 'r', encoding='utf-8') as f:
134
+ template_html = f.read()
135
+
136
+ with open(output_html_path, 'w', encoding='utf-8') as f:
137
+ template_html = template_html.replace('#height#', f'{height - offset}')
138
+ template_html = template_html.replace('#width#', f'{width}')
139
+ template_html = template_html.replace('#src#', f'{related_path}/')
140
+ f.write(template_html)
141
+
142
+ rel_path = os.path.relpath(output_html_path, SAVE_DIR)
143
+ iframe_tag = f'<iframe src="/static/{rel_path}" height="{height}" width="100%" frameborder="0"></iframe>'
144
+ print(
145
+ f'Find html file {output_html_path}, {os.path.exists(output_html_path)}, relative HTML path is /static/{rel_path}')
146
+
147
+ return f"""
148
+ <div style='height: {height}; width: 100%;'>
149
+ {iframe_tag}
150
+ </div>
151
+ """
152
+
153
+ @spaces.GPU(duration=40)
154
+ def _gen_shape(
155
+ caption=None,
156
+ image=None,
157
+ mv_image_front=None,
158
+ mv_image_back=None,
159
+ mv_image_left=None,
160
+ mv_image_right=None,
161
+ steps=50,
162
+ guidance_scale=7.5,
163
+ seed=1234,
164
+ octree_resolution=256,
165
+ check_box_rembg=False,
166
+ num_chunks=200000,
167
+ randomize_seed: bool = False,
168
+ ):
169
+ if not MV_MODE and image is None and caption is None:
170
+ raise gr.Error("Please provide either a caption or an image.")
171
+ if MV_MODE:
172
+ if mv_image_front is None and mv_image_back is None and mv_image_left is None and mv_image_right is None:
173
+ raise gr.Error("Please provide at least one view image.")
174
+ image = {}
175
+ if mv_image_front:
176
+ image['front'] = mv_image_front
177
+ if mv_image_back:
178
+ image['back'] = mv_image_back
179
+ if mv_image_left:
180
+ image['left'] = mv_image_left
181
+ if mv_image_right:
182
+ image['right'] = mv_image_right
183
+
184
+ seed = int(randomize_seed_fn(seed, randomize_seed))
185
+
186
+ octree_resolution = int(octree_resolution)
187
+ if caption: print('prompt is', caption)
188
+ save_folder = gen_save_folder()
189
+ stats = {
190
+ 'model': {
191
+ 'shapegen': f'{args.model_path}/{args.subfolder}',
192
+ 'texgen': f'{args.texgen_model_path}',
193
+ },
194
+ 'params': {
195
+ 'caption': caption,
196
+ 'steps': steps,
197
+ 'guidance_scale': guidance_scale,
198
+ 'seed': seed,
199
+ 'octree_resolution': octree_resolution,
200
+ 'check_box_rembg': check_box_rembg,
201
+ 'num_chunks': num_chunks,
202
+ }
203
+ }
204
+ time_meta = {}
205
+
206
+ if image is None:
207
+ start_time = time.time()
208
+ try:
209
+ image = t2i_worker(caption)
210
+ except Exception as e:
211
+ raise gr.Error(f"Text to 3D is disable. Please enable it by `python gradio_app.py --enable_t23d`.")
212
+ time_meta['text2image'] = time.time() - start_time
213
+
214
+ # remove disk io to make responding faster, uncomment at your will.
215
+ # image.save(os.path.join(save_folder, 'input.png'))
216
+ if MV_MODE:
217
+ start_time = time.time()
218
+ for k, v in image.items():
219
+ if check_box_rembg or v.mode == "RGB":
220
+ img = rmbg_worker(v.convert('RGB'))
221
+ image[k] = img
222
+ time_meta['remove background'] = time.time() - start_time
223
+ else:
224
+ if check_box_rembg or image.mode == "RGB":
225
+ start_time = time.time()
226
+ image = rmbg_worker(image.convert('RGB'))
227
+ time_meta['remove background'] = time.time() - start_time
228
+
229
+ # remove disk io to make responding faster, uncomment at your will.
230
+ # image.save(os.path.join(save_folder, 'rembg.png'))
231
+
232
+ # image to white model
233
+ start_time = time.time()
234
+
235
+ generator = torch.Generator()
236
+ generator = generator.manual_seed(int(seed))
237
+ outputs = i23d_worker(
238
+ image=image,
239
+ num_inference_steps=steps,
240
+ guidance_scale=guidance_scale,
241
+ generator=generator,
242
+ octree_resolution=octree_resolution,
243
+ num_chunks=num_chunks,
244
+ output_type='mesh'
245
+ )
246
+ time_meta['shape generation'] = time.time() - start_time
247
+ logger.info("---Shape generation takes %s seconds ---" % (time.time() - start_time))
248
+
249
+ tmp_start = time.time()
250
+ mesh = export_to_trimesh(outputs)[0]
251
+ time_meta['export to trimesh'] = time.time() - tmp_start
252
+
253
+ stats['number_of_faces'] = mesh.faces.shape[0]
254
+ stats['number_of_vertices'] = mesh.vertices.shape[0]
255
+
256
+ stats['time'] = time_meta
257
+ main_image = image if not MV_MODE else image['front']
258
+ return mesh, main_image, save_folder, stats, seed
259
+
260
+ @spaces.GPU(duration=90)
261
+ def generation_all(
262
+ caption=None,
263
+ image=None,
264
+ mv_image_front=None,
265
+ mv_image_back=None,
266
+ mv_image_left=None,
267
+ mv_image_right=None,
268
+ steps=50,
269
+ guidance_scale=7.5,
270
+ seed=1234,
271
+ octree_resolution=256,
272
+ check_box_rembg=False,
273
+ num_chunks=200000,
274
+ randomize_seed: bool = False,
275
+ ):
276
+ start_time_0 = time.time()
277
+ mesh, image, save_folder, stats, seed = _gen_shape(
278
+ caption,
279
+ image,
280
+ mv_image_front=mv_image_front,
281
+ mv_image_back=mv_image_back,
282
+ mv_image_left=mv_image_left,
283
+ mv_image_right=mv_image_right,
284
+ steps=steps,
285
+ guidance_scale=guidance_scale,
286
+ seed=seed,
287
+ octree_resolution=octree_resolution,
288
+ check_box_rembg=check_box_rembg,
289
+ num_chunks=num_chunks,
290
+ randomize_seed=randomize_seed,
291
+ )
292
+ path = export_mesh(mesh, save_folder, textured=False)
293
+
294
+ # tmp_time = time.time()
295
+ # mesh = floater_remove_worker(mesh)
296
+ # mesh = degenerate_face_remove_worker(mesh)
297
+ # logger.info("---Postprocessing takes %s seconds ---" % (time.time() - tmp_time))
298
+ # stats['time']['postprocessing'] = time.time() - tmp_time
299
+
300
+ tmp_time = time.time()
301
+ mesh = face_reduce_worker(mesh)
302
+ logger.info("---Face Reduction takes %s seconds ---" % (time.time() - tmp_time))
303
+ stats['time']['face reduction'] = time.time() - tmp_time
304
+
305
+ tmp_time = time.time()
306
+ textured_mesh = texgen_worker(mesh, image)
307
+ logger.info("---Texture Generation takes %s seconds ---" % (time.time() - tmp_time))
308
+ stats['time']['texture generation'] = time.time() - tmp_time
309
+ stats['time']['total'] = time.time() - start_time_0
310
+
311
+ textured_mesh.metadata['extras'] = stats
312
+ path_textured = export_mesh(textured_mesh, save_folder, textured=True)
313
+ model_viewer_html_textured = build_model_viewer_html(save_folder, height=HTML_HEIGHT, width=HTML_WIDTH,
314
+ textured=True)
315
+ if args.low_vram_mode:
316
+ torch.cuda.empty_cache()
317
+ return (
318
+ gr.update(value=path),
319
+ gr.update(value=path_textured),
320
+ model_viewer_html_textured,
321
+ stats,
322
+ seed,
323
+ )
324
+
325
+ @spaces.GPU(duration=40)
326
+ def shape_generation(
327
+ caption=None,
328
+ image=None,
329
+ mv_image_front=None,
330
+ mv_image_back=None,
331
+ mv_image_left=None,
332
+ mv_image_right=None,
333
+ steps=50,
334
+ guidance_scale=7.5,
335
+ seed=1234,
336
+ octree_resolution=256,
337
+ check_box_rembg=False,
338
+ num_chunks=200000,
339
+ randomize_seed: bool = False,
340
+ ):
341
+ start_time_0 = time.time()
342
+ mesh, image, save_folder, stats, seed = _gen_shape(
343
+ caption,
344
+ image,
345
+ mv_image_front=mv_image_front,
346
+ mv_image_back=mv_image_back,
347
+ mv_image_left=mv_image_left,
348
+ mv_image_right=mv_image_right,
349
+ steps=steps,
350
+ guidance_scale=guidance_scale,
351
+ seed=seed,
352
+ octree_resolution=octree_resolution,
353
+ check_box_rembg=check_box_rembg,
354
+ num_chunks=num_chunks,
355
+ randomize_seed=randomize_seed,
356
+ )
357
+ stats['time']['total'] = time.time() - start_time_0
358
+ mesh.metadata['extras'] = stats
359
+
360
+ path = export_mesh(mesh, save_folder, textured=False)
361
+ model_viewer_html = build_model_viewer_html(save_folder, height=HTML_HEIGHT, width=HTML_WIDTH)
362
+ if args.low_vram_mode:
363
+ torch.cuda.empty_cache()
364
+ return (
365
+ gr.update(value=path),
366
+ model_viewer_html,
367
+ stats,
368
+ seed,
369
+ )
370
+
371
+
372
+ def build_app():
373
+ title = 'Hunyuan3D-2: High Resolution Textured 3D Assets Generation'
374
+ if MV_MODE:
375
+ title = 'Hunyuan3D-2mv: Image to 3D Generation with 1-4 Views'
376
+ if 'mini' in args.subfolder:
377
+ title = 'Hunyuan3D-2mini: Strong 0.6B Image to Shape Generator'
378
+ if TURBO_MODE:
379
+ title = title.replace(':', '-Turbo: Fast ')
380
+
381
+ title_html = f"""
382
+ <div style="font-size: 2em; font-weight: bold; text-align: center; margin-bottom: 5px">
383
+
384
+ {title}
385
+ </div>
386
+ <div align="center">
387
+ Tencent Hunyuan3D Team
388
+ </div>
389
+ <div align="center">
390
+ <a href="https://github.com/tencent/Hunyuan3D-2">Github</a> &ensp;
391
+ <a href="http://3d-models.hunyuan.tencent.com">Homepage</a> &ensp;
392
+ <a href="https://3d.hunyuan.tencent.com">Hunyuan3D Studio</a> &ensp;
393
+ <a href="#">Technical Report</a> &ensp;
394
+ <a href="https://huggingface.co/Tencent/Hunyuan3D-2"> Pretrained Models</a> &ensp;
395
+ </div>
396
+ """
397
+ custom_css = """
398
+ .app.svelte-wpkpf6.svelte-wpkpf6:not(.fill_width) {
399
+ max-width: 1480px;
400
+ }
401
+ .mv-image button .wrap {
402
+ font-size: 10px;
403
+ }
404
+
405
+ .mv-image .icon-wrap {
406
+ width: 20px;
407
+ }
408
+
409
+ """
410
+
411
+ with gr.Blocks(theme=gr.themes.Base(), title='Hunyuan-3D-2.0', analytics_enabled=False, css=custom_css) as demo:
412
+ gr.HTML(title_html)
413
+
414
+ with gr.Row():
415
+ with gr.Column(scale=3):
416
+ with gr.Tabs(selected='tab_img_prompt') as tabs_prompt:
417
+ with gr.Tab('Image Prompt', id='tab_img_prompt', visible=not MV_MODE) as tab_ip:
418
+ image = gr.Image(label='Image', type='pil', image_mode='RGBA', height=290)
419
+
420
+ with gr.Tab('Text Prompt', id='tab_txt_prompt', visible=HAS_T2I and not MV_MODE) as tab_tp:
421
+ caption = gr.Textbox(label='Text Prompt',
422
+ placeholder='HunyuanDiT will be used to generate image.',
423
+ info='Example: A 3D model of a cute cat, white background')
424
+ with gr.Tab('MultiView Prompt', visible=MV_MODE) as tab_mv:
425
+ # gr.Label('Please upload at least one front image.')
426
+ with gr.Row():
427
+ mv_image_front = gr.Image(label='Front', type='pil', image_mode='RGBA', height=140,
428
+ min_width=100, elem_classes='mv-image')
429
+ mv_image_back = gr.Image(label='Back', type='pil', image_mode='RGBA', height=140,
430
+ min_width=100, elem_classes='mv-image')
431
+ with gr.Row():
432
+ mv_image_left = gr.Image(label='Left', type='pil', image_mode='RGBA', height=140,
433
+ min_width=100, elem_classes='mv-image')
434
+ mv_image_right = gr.Image(label='Right', type='pil', image_mode='RGBA', height=140,
435
+ min_width=100, elem_classes='mv-image')
436
+
437
+ with gr.Row():
438
+ btn = gr.Button(value='Gen Shape', variant='primary', min_width=100)
439
+ btn_all = gr.Button(value='Gen Textured Shape',
440
+ variant='primary',
441
+ visible=HAS_TEXTUREGEN,
442
+ min_width=100)
443
+
444
+ with gr.Group():
445
+ file_out = gr.File(label="File", visible=False)
446
+ file_out2 = gr.File(label="File", visible=False)
447
+
448
+ with gr.Tabs(selected='tab_options' if TURBO_MODE else 'tab_export'):
449
+ with gr.Tab("Options", id='tab_options', visible=TURBO_MODE):
450
+ gen_mode = gr.Radio(label='Generation Mode',
451
+ info='Recommendation: Turbo for most cases, Fast for very complex cases, Standard seldom use.',
452
+ choices=['Turbo', 'Fast', 'Standard'], value='Turbo')
453
+ decode_mode = gr.Radio(label='Decoding Mode',
454
+ info='The resolution for exporting mesh from generated vectset',
455
+ choices=['Low', 'Standard', 'High'],
456
+ value='Standard')
457
+ with gr.Tab('Advanced Options', id='tab_advanced_options'):
458
+ with gr.Row():
459
+ check_box_rembg = gr.Checkbox(value=True, label='Remove Background', min_width=100)
460
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True, min_width=100)
461
+ seed = gr.Slider(
462
+ label="Seed",
463
+ minimum=0,
464
+ maximum=MAX_SEED,
465
+ step=1,
466
+ value=1234,
467
+ min_width=100,
468
+ )
469
+ with gr.Row():
470
+ num_steps = gr.Slider(maximum=100,
471
+ minimum=1,
472
+ value=5 if 'turbo' in args.subfolder else 30,
473
+ step=1, label='Inference Steps')
474
+ octree_resolution = gr.Slider(maximum=512, minimum=16, value=256, label='Octree Resolution')
475
+ with gr.Row():
476
+ cfg_scale = gr.Number(value=5.0, label='Guidance Scale', min_width=100)
477
+ num_chunks = gr.Slider(maximum=5000000, minimum=1000, value=8000,
478
+ label='Number of Chunks', min_width=100)
479
+ with gr.Tab("Export", id='tab_export'):
480
+ with gr.Row():
481
+ file_type = gr.Dropdown(label='File Type', choices=SUPPORTED_FORMATS,
482
+ value='glb', min_width=100)
483
+ reduce_face = gr.Checkbox(label='Simplify Mesh', value=False, min_width=100)
484
+ export_texture = gr.Checkbox(label='Include Texture', value=False,
485
+ visible=False, min_width=100)
486
+ target_face_num = gr.Slider(maximum=1000000, minimum=100, value=10000,
487
+ label='Target Face Number')
488
+ with gr.Row():
489
+ confirm_export = gr.Button(value="Transform", min_width=100)
490
+ file_export = gr.DownloadButton(label="Download", variant='primary',
491
+ interactive=False, min_width=100)
492
+
493
+ with gr.Column(scale=6):
494
+ with gr.Tabs(selected='gen_mesh_panel') as tabs_output:
495
+ with gr.Tab('Generated Mesh', id='gen_mesh_panel'):
496
+ html_gen_mesh = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output')
497
+ with gr.Tab('Exporting Mesh', id='export_mesh_panel'):
498
+ html_export_mesh = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output')
499
+ with gr.Tab('Mesh Statistic', id='stats_panel'):
500
+ stats = gr.Json({}, label='Mesh Stats')
501
+
502
+ with gr.Column(scale=3 if MV_MODE else 2):
503
+ with gr.Tabs(selected='tab_img_gallery') as gallery:
504
+ with gr.Tab('Image to 3D Gallery', id='tab_img_gallery', visible=not MV_MODE) as tab_gi:
505
+ with gr.Row():
506
+ gr.Examples(examples=example_is, inputs=[image],
507
+ label=None, examples_per_page=18)
508
+
509
+ with gr.Tab('Text to 3D Gallery', id='tab_txt_gallery', visible=HAS_T2I and not MV_MODE) as tab_gt:
510
+ with gr.Row():
511
+ gr.Examples(examples=example_ts, inputs=[caption],
512
+ label=None, examples_per_page=18)
513
+ with gr.Tab('MultiView to 3D Gallery', id='tab_mv_gallery', visible=MV_MODE) as tab_mv:
514
+ with gr.Row():
515
+ gr.Examples(examples=example_mvs,
516
+ inputs=[mv_image_front, mv_image_back, mv_image_left, mv_image_right],
517
+ label=None, examples_per_page=6)
518
+
519
+ gr.HTML(f"""
520
+ <div align="center">
521
+ Activated Model - Shape Generation ({args.model_path}/{args.subfolder}) ; Texture Generation ({'Hunyuan3D-2' if HAS_TEXTUREGEN else 'Unavailable'})
522
+ </div>
523
+ """)
524
+ if not HAS_TEXTUREGEN:
525
+ gr.HTML("""
526
+ <div style="margin-top: 5px;" align="center">
527
+ <b>Warning: </b>
528
+ Texture synthesis is disable due to missing requirements,
529
+ please install requirements following <a href="https://github.com/Tencent/Hunyuan3D-2?tab=readme-ov-file#install-requirements">README.md</a>to activate it.
530
+ </div>
531
+ """)
532
+ if not args.enable_t23d:
533
+ gr.HTML("""
534
+ <div style="margin-top: 5px;" align="center">
535
+ <b>Warning: </b>
536
+ Text to 3D is disable. To activate it, please run `python gradio_app.py --enable_t23d`.
537
+ </div>
538
+ """)
539
+
540
+ tab_ip.select(fn=lambda: gr.update(selected='tab_img_gallery'), outputs=gallery)
541
+ if HAS_T2I:
542
+ tab_tp.select(fn=lambda: gr.update(selected='tab_txt_gallery'), outputs=gallery)
543
+
544
+
545
+ btn.click(
546
+ shape_generation,
547
+ inputs=[
548
+ caption,
549
+ image,
550
+ mv_image_front,
551
+ mv_image_back,
552
+ mv_image_left,
553
+ mv_image_right,
554
+ num_steps,
555
+ cfg_scale,
556
+ seed,
557
+ octree_resolution,
558
+ check_box_rembg,
559
+ num_chunks,
560
+ randomize_seed,
561
+ ],
562
+ outputs=[file_out, html_gen_mesh, stats, seed]
563
+ ).then(
564
+ lambda: (gr.update(visible=False, value=False), gr.update(interactive=True), gr.update(interactive=True),
565
+ gr.update(interactive=False)),
566
+ outputs=[export_texture, reduce_face, confirm_export, file_export],
567
+ ).then(
568
+ lambda: gr.update(selected='gen_mesh_panel'),
569
+ outputs=[tabs_output],
570
+ )
571
+
572
+ btn_all.click(
573
+ generation_all,
574
+ inputs=[
575
+ caption,
576
+ image,
577
+ mv_image_front,
578
+ mv_image_back,
579
+ mv_image_left,
580
+ mv_image_right,
581
+ num_steps,
582
+ cfg_scale,
583
+ seed,
584
+ octree_resolution,
585
+ check_box_rembg,
586
+ num_chunks,
587
+ randomize_seed,
588
+ ],
589
+ outputs=[file_out, file_out2, html_gen_mesh, stats, seed]
590
+ ).then(
591
+ lambda: (gr.update(visible=True, value=True), gr.update(interactive=False), gr.update(interactive=True),
592
+ gr.update(interactive=False)),
593
+ outputs=[export_texture, reduce_face, confirm_export, file_export],
594
+ ).then(
595
+ lambda: gr.update(selected='gen_mesh_panel'),
596
+ outputs=[tabs_output],
597
+ )
598
+
599
+ def on_gen_mode_change(value):
600
+ if value == 'Turbo':
601
+ return gr.update(value=5)
602
+ elif value == 'Fast':
603
+ return gr.update(value=10)
604
+ else:
605
+ return gr.update(value=30)
606
+
607
+ gen_mode.change(on_gen_mode_change, inputs=[gen_mode], outputs=[num_steps])
608
+
609
+ def on_decode_mode_change(value):
610
+ if value == 'Low':
611
+ return gr.update(value=196)
612
+ elif value == 'Standard':
613
+ return gr.update(value=256)
614
+ else:
615
+ return gr.update(value=384)
616
+
617
+ decode_mode.change(on_decode_mode_change, inputs=[decode_mode], outputs=[octree_resolution])
618
+
619
+ def on_export_click(file_out, file_out2, file_type, reduce_face, export_texture, target_face_num):
620
+ if file_out is None:
621
+ raise gr.Error('Please generate a mesh first.')
622
+
623
+ print(f'exporting {file_out}')
624
+ print(f'reduce face to {target_face_num}')
625
+ if export_texture:
626
+ mesh = trimesh.load(file_out2)
627
+ save_folder = gen_save_folder()
628
+ path = export_mesh(mesh, save_folder, textured=True, type=file_type)
629
+
630
+ # for preview
631
+ save_folder = gen_save_folder()
632
+ _ = export_mesh(mesh, save_folder, textured=True)
633
+ model_viewer_html = build_model_viewer_html(save_folder, height=HTML_HEIGHT, width=HTML_WIDTH,
634
+ textured=True)
635
+ else:
636
+ mesh = trimesh.load(file_out)
637
+ mesh = floater_remove_worker(mesh)
638
+ mesh = degenerate_face_remove_worker(mesh)
639
+ if reduce_face:
640
+ mesh = face_reduce_worker(mesh, target_face_num)
641
+ save_folder = gen_save_folder()
642
+ path = export_mesh(mesh, save_folder, textured=False, type=file_type)
643
+
644
+ # for preview
645
+ save_folder = gen_save_folder()
646
+ _ = export_mesh(mesh, save_folder, textured=False)
647
+ model_viewer_html = build_model_viewer_html(save_folder, height=HTML_HEIGHT, width=HTML_WIDTH,
648
+ textured=False)
649
+ print(f'export to {path}')
650
+ return model_viewer_html, gr.update(value=path, interactive=True)
651
+
652
+ confirm_export.click(
653
+ lambda: gr.update(selected='export_mesh_panel'),
654
+ outputs=[tabs_output],
655
+ ).then(
656
+ on_export_click,
657
+ inputs=[file_out, file_out2, file_type, reduce_face, export_texture, target_face_num],
658
+ outputs=[html_export_mesh, file_export]
659
+ )
660
+
661
+ return demo
662
+
663
+
664
+ if __name__ == '__main__':
665
+ import sys
666
+ print("sys.argv:", sys.argv)
667
+
668
+ import argparse
669
+
670
+ parser = argparse.ArgumentParser()
671
+ parser.add_argument("--model_path", type=str, default='tencent/Hunyuan3D-2mv')
672
+ parser.add_argument("--subfolder", type=str, default='hunyuan3d-dit-v2-mv-turbo')
673
+ parser.add_argument("--texgen_model_path", type=str, default='tencent/Hunyuan3D-2')
674
+ #parser.add_argument('--port', type=int, default=7860)
675
+ parser.add_argument('--host', type=str, default='0.0.0.0')
676
+ parser.add_argument('--device', type=str, default='cuda')
677
+ parser.add_argument('--mc_algo', type=str, default='mc')
678
+ parser.add_argument('--cache-path', type=str, default='gradio_cache')
679
+ parser.add_argument('--enable_t23d', action='store_true')
680
+ parser.add_argument('--disable_tex', action='store_true')
681
+ parser.add_argument('--enable_flashvdm', action='store_true')
682
+ parser.add_argument('--compile', action='store_true')
683
+ parser.add_argument('--low_vram_mode', action='store_true')
684
+ args = parser.parse_args()
685
+
686
+ try:
687
+ port = int(args.port)
688
+ except ValueError:
689
+ print(f"Invalid port argument detected: {args.port} — using default 7860")
690
+ port = 7860
691
+
692
+ args.enable_flashvdm = True
693
+ SAVE_DIR = args.cache_path
694
+ os.makedirs(SAVE_DIR, exist_ok=True)
695
+
696
+ CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
697
+ MV_MODE = 'mv' in args.model_path
698
+ TURBO_MODE = 'turbo' in args.subfolder
699
+
700
+ HTML_HEIGHT = 690 if MV_MODE else 650
701
+ HTML_WIDTH = 500
702
+ HTML_OUTPUT_PLACEHOLDER = f"""
703
+ <div style='height: {650}px; width: 100%; border-radius: 8px; border-color: #e5e7eb; border-style: solid; border-width: 1px; display: flex; justify-content: center; align-items: center;'>
704
+ <div style='text-align: center; font-size: 16px; color: #6b7280;'>
705
+ <p style="color: #8d8d8d;">Welcome to Hunyuan3D!</p>
706
+ <p style="color: #8d8d8d;">No mesh here.</p>
707
+ </div>
708
+ </div>
709
+ """
710
+
711
+ INPUT_MESH_HTML = """
712
+ <div style='height: 490px; width: 100%; border-radius: 8px;
713
+ border-color: #e5e7eb; order-style: solid; border-width: 1px;'>
714
+ </div>
715
+ """
716
+
717
+ gpu_usage = gr.Interface(fn=call_gpu_function, inputs=[], outputs="text")
718
+ gpu_usage.launch()
719
+
720
+ example_is = get_example_img_list()
721
+ example_ts = get_example_txt_list()
722
+ example_mvs = get_example_mv_list()
723
+
724
+ SUPPORTED_FORMATS = ['glb', 'obj', 'ply', 'stl']
725
+
726
+ HAS_TEXTUREGEN = False
727
+ if not args.disable_tex:
728
+ try:
729
+ from hy3dgen.texgen import Hunyuan3DPaintPipeline
730
+
731
+ texgen_worker = Hunyuan3DPaintPipeline.from_pretrained(args.texgen_model_path)
732
+ if args.low_vram_mode:
733
+ texgen_worker.enable_model_cpu_offload()
734
+ # Not help much, ignore for now.
735
+ # if args.compile:
736
+ # texgen_worker.models['delight_model'].pipeline.unet.compile()
737
+ # texgen_worker.models['delight_model'].pipeline.vae.compile()
738
+ # texgen_worker.models['multiview_model'].pipeline.unet.compile()
739
+ # texgen_worker.models['multiview_model'].pipeline.vae.compile()
740
+ HAS_TEXTUREGEN = True
741
+ except Exception as e:
742
+ print(e)
743
+ print("Failed to load texture generator.")
744
+ print('Please try to install requirements by following README.md')
745
+ HAS_TEXTUREGEN = False
746
+
747
+ HAS_T2I = True
748
+ if args.enable_t23d:
749
+ from hy3dgen.text2image import HunyuanDiTPipeline
750
+
751
+ t2i_worker = HunyuanDiTPipeline('Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled')
752
+ HAS_T2I = True
753
+
754
+ from hy3dgen.shapegen import FaceReducer, FloaterRemover, DegenerateFaceRemover, MeshSimplifier, \
755
+ Hunyuan3DDiTFlowMatchingPipeline
756
+ from hy3dgen.shapegen.pipelines import export_to_trimesh
757
+ from hy3dgen.rembg import BackgroundRemover
758
+
759
+ rmbg_worker = BackgroundRemover()
760
+ i23d_worker = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(
761
+ args.model_path,
762
+ subfolder=args.subfolder,
763
+ use_safetensors=True,
764
+ device=args.device,
765
+ )
766
+ if args.enable_flashvdm:
767
+ mc_algo = 'mc' if args.device in ['cpu', 'mps'] else args.mc_algo
768
+ i23d_worker.enable_flashvdm(mc_algo=mc_algo)
769
+ if args.compile:
770
+ i23d_worker.compile()
771
+
772
+ floater_remove_worker = FloaterRemover()
773
+ degenerate_face_remove_worker = DegenerateFaceRemover()
774
+ face_reduce_worker = FaceReducer()
775
+
776
+ # https://discuss.huggingface.co/t/how-to-serve-an-html-file/33921/2
777
+ # create a FastAPI app
778
+ app = FastAPI()
779
+ # create a static directory to store the static files
780
+ static_dir = Path(SAVE_DIR).absolute()
781
+ static_dir.mkdir(parents=True, exist_ok=True)
782
+ app.mount("/static", StaticFiles(directory=static_dir, html=True), name="static")
783
+ shutil.copytree('./assets/env_maps', os.path.join(static_dir, 'env_maps'), dirs_exist_ok=True)
784
+
785
+ if args.low_vram_mode:
786
+ torch.cuda.empty_cache()
787
+ demo = build_app()
788
+ app = gr.mount_gradio_app(app, demo, path="/")
789
+ uvicorn.run(app, host=args.host, port=args.port)