aakashimagine commited on
Commit
e3305f0
·
1 Parent(s): bd1396f

enable texture

Browse files
Files changed (1) hide show
  1. gradio_app.py +381 -244
gradio_app.py CHANGED
@@ -15,17 +15,21 @@
15
  # Apply torchvision compatibility fix before other imports
16
 
17
  import sys
18
- sys.path.insert(0, './hy3dshape')
19
- sys.path.insert(0, './hy3dpaint')
 
20
 
21
  pythonpath = sys.executable
22
  print(pythonpath)
23
 
24
  try:
25
  from torchvision_fix import apply_fix
 
26
  apply_fix()
27
  except ImportError:
28
- print("Warning: torchvision_fix module not found, proceeding without compatibility fix")
 
 
29
  except Exception as e:
30
  print(f"Warning: Failed to apply torchvision fix: {e}")
31
 
@@ -52,8 +56,8 @@ from hy3dpaint.convert_utils import create_glb_with_pbr_materials
52
 
53
 
54
  MAX_SEED = 1e7
55
- ENV = "Local" # "Huggingface"
56
- if ENV == 'Huggingface':
57
  """
58
  Setup environment for running on Huggingface platform.
59
 
@@ -76,12 +80,14 @@ if ENV == 'Huggingface':
76
  subprocess.call(["wget", "-q", CUDA_TOOLKIT_URL, "-O", CUDA_TOOLKIT_FILE])
77
  subprocess.call(["chmod", "+x", CUDA_TOOLKIT_FILE])
78
  subprocess.call([CUDA_TOOLKIT_FILE, "--silent", "--toolkit"])
79
-
80
  os.environ["CUDA_HOME"] = "/usr/local/cuda"
81
  os.environ["PATH"] = "%s/bin:%s" % (os.environ["CUDA_HOME"], os.environ["PATH"])
82
  os.environ["LD_LIBRARY_PATH"] = "%s/lib:%s" % (
83
  os.environ["CUDA_HOME"],
84
- "" if "LD_LIBRARY_PATH" not in os.environ else os.environ["LD_LIBRARY_PATH"],
 
 
85
  )
86
  # Fix: arch_list[-1] += '+PTX'; IndexError: list index out of range
87
  os.environ["TORCH_CUDA_ARCH_LIST"] = "8.0;8.6"
@@ -90,33 +96,43 @@ if ENV == 'Huggingface':
90
  # print('install custom')
91
  # os.system(f"cd /home/user/app/hy3dpaint/custom_rasterizer && {pythonpath} -m pip install -e .")
92
  # os.system(f"cd /home/user/app/hy3dpaint/packages/custom_rasterizer && pip install -e .")
93
- subprocess.run(shlex.split("pip install custom_rasterizer-0.1-cp310-cp310-linux_x86_64.whl"), check=True)
 
 
 
 
 
94
 
95
- print("cd /home/user/app/hy3dpaint/differentiable_renderer/ && bash compile_mesh_painter.sh")
96
- os.system("cd /home/user/app/hy3dpaint/DifferentiableRenderer && bash compile_mesh_painter.sh")
 
 
 
 
97
  # print("wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P /home/user/app/hy3dpaint/ckpt")
98
  # os.system("wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P /home/user/app/hy3dpaint/ckpt")
99
 
100
  def check():
101
  import custom_rasterizer
 
102
  print(type(custom_rasterizer))
103
  print(dir(custom_rasterizer))
104
- print(getattr(custom_rasterizer, '__file__', None))
105
 
106
  package_dir = None
107
- if hasattr(custom_rasterizer, '__file__') and custom_rasterizer.__file__:
108
  package_dir = os.path.dirname(custom_rasterizer.__file__)
109
- elif hasattr(custom_rasterizer, '__path__'):
110
  package_dir = list(custom_rasterizer.__path__)[0]
111
  else:
112
  raise RuntimeError("Cannot determine package path")
113
  print(package_dir)
114
 
115
  for root, dirs, files in os.walk(package_dir):
116
- level = root.replace(package_dir, '').count(os.sep)
117
- indent = ' ' * 4 * level
118
  print(f"{indent}{os.path.basename(root)}/")
119
- subindent = ' ' * 4 * (level + 1)
120
  for f in files:
121
  print(f"{subindent}{f}")
122
 
@@ -125,7 +141,7 @@ if ENV == 'Huggingface':
125
  print(torch.__version__)
126
  prepare_env()
127
  check()
128
-
129
  else:
130
  """
131
  Define a dummy `spaces` module with a GPU decorator class for local environment.
@@ -133,12 +149,15 @@ else:
133
  The GPU decorator is a no-op that simply returns the decorated function unchanged.
134
  This allows code that uses the `spaces.GPU` decorator to run without modification locally.
135
  """
 
136
  class spaces:
137
  class GPU:
138
  def __init__(self, duration=60):
139
  self.duration = duration
 
140
  def __call__(self, func):
141
- return func
 
142
 
143
  def get_example_img_list():
144
  """
@@ -149,8 +168,8 @@ def get_example_img_list():
149
  Returns:
150
  list[str]: Sorted list of file paths to example PNG images.
151
  """
152
- print('Loading example img list ...')
153
- return sorted(glob('./assets/example_images/**/*.png', recursive=True))
154
 
155
 
156
  def get_example_txt_list():
@@ -162,9 +181,9 @@ def get_example_txt_list():
162
  Returns:
163
  list[str]: List of example text prompts.
164
  """
165
- print('Loading example txt list ...')
166
  txt_list = list()
167
- for line in open('./assets/example_prompts.txt', encoding='utf-8'):
168
  txt_list.append(line.strip())
169
  return txt_list
170
 
@@ -194,7 +213,7 @@ def gen_save_folder(max_size=200):
194
 
195
 
196
  # Removed complex PBR conversion functions - using simple trimesh-based conversion
197
- def export_mesh(mesh, save_folder, textured=False, type='glb'):
198
  """
199
  Export a mesh to a file in the specified folder, optionally including textures.
200
 
@@ -208,27 +227,24 @@ def export_mesh(mesh, save_folder, textured=False, type='glb'):
208
  str: The full path to the exported mesh file.
209
  """
210
  if textured:
211
- path = os.path.join(save_folder, f'textured_mesh.{type}')
212
  else:
213
- path = os.path.join(save_folder, f'white_mesh.{type}')
214
- if type not in ['glb', 'obj']:
215
  mesh.export(path)
216
  else:
217
  mesh.export(path, include_normals=textured)
218
  return path
219
 
220
 
221
-
222
-
223
  def quick_convert_with_obj2gltf(obj_path: str, glb_path: str) -> bool:
224
  # 执行转换
225
  textures = {
226
- 'albedo': obj_path.replace('.obj', '.jpg'),
227
- 'metallic': obj_path.replace('.obj', '_metallic.jpg'),
228
- 'roughness': obj_path.replace('.obj', '_roughness.jpg')
229
- }
230
  create_glb_with_pbr_materials(obj_path, textures, glb_path)
231
-
232
 
233
 
234
  def randomize_seed_fn(seed: int, randomize_seed: bool) -> int:
@@ -241,27 +257,29 @@ def build_model_viewer_html(save_folder, height=660, width=790, textured=False):
241
  # Remove first folder from path to make relative path
242
  if textured:
243
  related_path = f"./textured_mesh.glb"
244
- template_name = './assets/modelviewer-textured-template.html'
245
- output_html_path = os.path.join(save_folder, f'textured_mesh.html')
246
  else:
247
  related_path = f"./white_mesh.glb"
248
- template_name = './assets/modelviewer-template.html'
249
- output_html_path = os.path.join(save_folder, f'white_mesh.html')
250
  offset = 50 if textured else 10
251
- with open(os.path.join(CURRENT_DIR, template_name), 'r', encoding='utf-8') as f:
252
  template_html = f.read()
253
 
254
- with open(output_html_path, 'w', encoding='utf-8') as f:
255
- template_html = template_html.replace('#height#', f'{height - offset}')
256
- template_html = template_html.replace('#width#', f'{width}')
257
- template_html = template_html.replace('#src#', f'{related_path}/')
258
  f.write(template_html)
259
 
260
  rel_path = os.path.relpath(output_html_path, SAVE_DIR)
261
  iframe_tag = f'<iframe src="/static/{rel_path}" \
262
  height="{height}" width="100%" frameborder="0"></iframe>'
263
- print(f'Find html file {output_html_path}, \
264
- {os.path.exists(output_html_path)}, relative HTML path is /static/{rel_path}')
 
 
265
 
266
  return f"""
267
  <div style='height: {height}; width: 100%;'>
@@ -269,6 +287,7 @@ height="{height}" width="100%" frameborder="0"></iframe>'
269
  </div>
270
  """
271
 
 
272
  @spaces.GPU(duration=60)
273
  def _gen_shape(
274
  caption=None,
@@ -288,38 +307,43 @@ def _gen_shape(
288
  if not MV_MODE and image is None and caption is None:
289
  raise gr.Error("Please provide either a caption or an image.")
290
  if MV_MODE:
291
- if mv_image_front is None and mv_image_back is None \
292
- and mv_image_left is None and mv_image_right is None:
 
 
 
 
293
  raise gr.Error("Please provide at least one view image.")
294
  image = {}
295
  if mv_image_front:
296
- image['front'] = mv_image_front
297
  if mv_image_back:
298
- image['back'] = mv_image_back
299
  if mv_image_left:
300
- image['left'] = mv_image_left
301
  if mv_image_right:
302
- image['right'] = mv_image_right
303
 
304
  seed = int(randomize_seed_fn(seed, randomize_seed))
305
 
306
  octree_resolution = int(octree_resolution)
307
- if caption: print('prompt is', caption)
 
308
  save_folder = gen_save_folder()
309
  stats = {
310
- 'model': {
311
- 'shapegen': f'{args.model_path}/{args.subfolder}',
312
- 'texgen': f'{args.texgen_model_path}',
 
 
 
 
 
 
 
 
 
313
  },
314
- 'params': {
315
- 'caption': caption,
316
- 'steps': steps,
317
- 'guidance_scale': guidance_scale,
318
- 'seed': seed,
319
- 'octree_resolution': octree_resolution,
320
- 'check_box_rembg': check_box_rembg,
321
- 'num_chunks': num_chunks,
322
- }
323
  }
324
  time_meta = {}
325
 
@@ -328,9 +352,11 @@ def _gen_shape(
328
  try:
329
  image = t2i_worker(caption)
330
  except Exception as e:
331
- raise gr.Error(f"Text to 3D is disable. \
332
- Please enable it by `python gradio_app.py --enable_t23d`.")
333
- time_meta['text2image'] = time.time() - start_time
 
 
334
 
335
  # remove disk io to make responding faster, uncomment at your will.
336
  # image.save(os.path.join(save_folder, 'input.png'))
@@ -338,14 +364,14 @@ def _gen_shape(
338
  start_time = time.time()
339
  for k, v in image.items():
340
  if check_box_rembg or v.mode == "RGB":
341
- img = rmbg_worker(v.convert('RGB'))
342
  image[k] = img
343
- time_meta['remove background'] = time.time() - start_time
344
  else:
345
  if check_box_rembg or image.mode == "RGB":
346
  start_time = time.time()
347
- image = rmbg_worker(image.convert('RGB'))
348
- time_meta['remove background'] = time.time() - start_time
349
 
350
  # remove disk io to make responding faster, uncomment at your will.
351
  # image.save(os.path.join(save_folder, 'rembg.png'))
@@ -362,22 +388,23 @@ def _gen_shape(
362
  generator=generator,
363
  octree_resolution=octree_resolution,
364
  num_chunks=num_chunks,
365
- output_type='mesh'
366
  )
367
- time_meta['shape generation'] = time.time() - start_time
368
  logger.info("---Shape generation takes %s seconds ---" % (time.time() - start_time))
369
 
370
  tmp_start = time.time()
371
  mesh = export_to_trimesh(outputs)[0]
372
- time_meta['export to trimesh'] = time.time() - tmp_start
373
 
374
- stats['number_of_faces'] = mesh.faces.shape[0]
375
- stats['number_of_vertices'] = mesh.vertices.shape[0]
376
 
377
- stats['time'] = time_meta
378
- main_image = image if not MV_MODE else image['front']
379
  return mesh, main_image, save_folder, stats, seed
380
 
 
381
  @spaces.GPU(duration=130)
382
  def generation_all(
383
  caption=None,
@@ -411,10 +438,9 @@ def generation_all(
411
  randomize_seed=randomize_seed,
412
  )
413
  path = export_mesh(mesh, save_folder, textured=False)
414
-
415
 
416
  print(path)
417
- print('='*40)
418
 
419
  # tmp_time = time.time()
420
  # mesh = floater_remove_worker(mesh)
@@ -426,30 +452,36 @@ def generation_all(
426
  mesh = face_reduce_worker(mesh)
427
 
428
  # path = export_mesh(mesh, save_folder, textured=False, type='glb')
429
- path = export_mesh(mesh, save_folder, textured=False, type='obj') # 这样操作也会 core dump
 
 
430
 
431
  logger.info("---Face Reduction takes %s seconds ---" % (time.time() - tmp_time))
432
- stats['time']['face reduction'] = time.time() - tmp_time
433
 
434
  tmp_time = time.time()
435
 
436
- text_path = os.path.join(save_folder, f'textured_mesh.obj')
437
- path_textured = tex_pipeline(mesh_path=path, image_path=image, output_mesh_path=text_path, save_glb=False)
438
-
 
 
439
  logger.info("---Texture Generation takes %s seconds ---" % (time.time() - tmp_time))
440
- stats['time']['texture generation'] = time.time() - tmp_time
441
 
442
  tmp_time = time.time()
443
  # Convert textured OBJ to GLB using obj2gltf with PBR support
444
- glb_path_textured = os.path.join(save_folder, 'textured_mesh.glb')
445
  conversion_success = quick_convert_with_obj2gltf(path_textured, glb_path_textured)
446
 
447
- logger.info("---Convert textured OBJ to GLB takes %s seconds ---" % (time.time() - tmp_time))
448
- stats['time']['convert textured OBJ to GLB'] = time.time() - tmp_time
449
- stats['time']['total'] = time.time() - start_time_0
450
- model_viewer_html_textured = build_model_viewer_html(save_folder,
451
- height=HTML_HEIGHT,
452
- width=HTML_WIDTH, textured=True)
 
 
453
  if args.low_vram_mode:
454
  torch.cuda.empty_cache()
455
  return (
@@ -460,6 +492,7 @@ def generation_all(
460
  seed,
461
  )
462
 
 
463
  @spaces.GPU(duration=60)
464
  def shape_generation(
465
  caption=None,
@@ -492,11 +525,13 @@ def shape_generation(
492
  num_chunks=num_chunks,
493
  randomize_seed=randomize_seed,
494
  )
495
- stats['time']['total'] = time.time() - start_time_0
496
- mesh.metadata['extras'] = stats
497
 
498
  path = export_mesh(mesh, save_folder, textured=False)
499
- model_viewer_html = build_model_viewer_html(save_folder, height=HTML_HEIGHT, width=HTML_WIDTH)
 
 
500
  if args.low_vram_mode:
501
  torch.cuda.empty_cache()
502
  return (
@@ -508,16 +543,16 @@ def shape_generation(
508
 
509
 
510
  def build_app():
511
- title = 'High Resolution Textured 3D Assets Generation'
512
  if MV_MODE:
513
- title = 'Image to 3D Generation with 1-4 Views'
514
- if 'mini' in args.subfolder:
515
- title = 'Strong 0.6B Image to Shape Generator'
 
 
516
 
517
- title = 'Image to 3D Generation'
518
-
519
  if TURBO_MODE:
520
- title = title.replace(':', '-Turbo: Fast ')
521
 
522
  title_html = f"""
523
  <div style="font-size: 2em; font-weight: bold; text-align: center; margin-bottom: 5px">
@@ -542,66 +577,101 @@ def build_app():
542
 
543
  """
544
 
545
- with gr.Blocks(theme=gr.themes.Base(), title='Image to 3D Generation', analytics_enabled=False, css=custom_css) as demo:
 
 
 
 
 
546
  gr.HTML(title_html)
547
 
548
  with gr.Row():
549
  with gr.Column(scale=3):
550
- with gr.Tabs(selected='tab_img_prompt') as tabs_prompt:
551
- with gr.Tab('Image Prompt', id='tab_img_prompt', visible=not MV_MODE) as tab_ip:
552
- image = gr.Image(label='Image', type='pil', image_mode='RGBA', height=290)
 
 
 
 
553
  caption = gr.State(None)
554
- # with gr.Tab('Text Prompt', id='tab_txt_prompt', visible=HAS_T2I and not MV_MODE) as tab_tp:
555
- # caption = gr.Textbox(label='Text Prompt',
556
- # placeholder='HunyuanDiT will be used to generate image.',
557
- # info='Example: A 3D model of a cute cat, white background')
558
- with gr.Tab('MultiView Prompt', visible=MV_MODE) as tab_mv:
559
  # gr.Label('Please upload at least one front image.')
560
  with gr.Row():
561
- mv_image_front = gr.Image(label='Front', type='pil', image_mode='RGBA', height=140,
562
- min_width=100, elem_classes='mv-image')
563
- mv_image_back = gr.Image(label='Back', type='pil', image_mode='RGBA', height=140,
564
- min_width=100, elem_classes='mv-image')
 
 
 
 
 
 
 
 
 
 
 
 
565
  with gr.Row():
566
- mv_image_left = gr.Image(label='Left', type='pil', image_mode='RGBA', height=140,
567
- min_width=100, elem_classes='mv-image')
568
- mv_image_right = gr.Image(label='Right', type='pil', image_mode='RGBA', height=140,
569
- min_width=100, elem_classes='mv-image')
 
 
 
 
 
 
 
 
 
 
 
 
570
 
571
  with gr.Row():
572
- btn = gr.Button(value='Gen Shape', variant='primary', min_width=100)
573
- btn_all = gr.Button(value='Gen Textured Shape',
574
- variant='primary',
575
- visible=HAS_TEXTUREGEN,
576
- min_width=100)
 
 
577
 
578
  with gr.Group():
579
  file_out = gr.File(label="File", visible=False)
580
  file_out2 = gr.File(label="File", visible=False)
581
 
582
- with gr.Tabs(selected='tab_options' if TURBO_MODE else 'tab_export'):
583
- with gr.Tab("Options", id='tab_options', visible=TURBO_MODE):
584
  gen_mode = gr.Radio(
585
- label='Generation Mode',
586
- info='Recommendation: Turbo for most cases, \
587
- Fast for very complex cases, Standard seldom use.',
588
- choices=['Turbo', 'Fast', 'Standard'],
589
- value='Turbo')
 
590
  decode_mode = gr.Radio(
591
- label='Decoding Mode',
592
- info='The resolution for exporting mesh from generated vectset',
593
- choices=['Low', 'Standard', 'High'],
594
- value='Standard')
595
- with gr.Tab('Advanced Options', id='tab_advanced_options'):
 
596
  with gr.Row():
597
  check_box_rembg = gr.Checkbox(
598
- value=True,
599
- label='Remove Background',
600
- min_width=100)
601
  randomize_seed = gr.Checkbox(
602
- label="Randomize seed",
603
- value=True,
604
- min_width=100)
605
  seed = gr.Slider(
606
  label="Seed",
607
  minimum=0,
@@ -611,54 +681,88 @@ Fast for very complex cases, Standard seldom use.',
611
  min_width=100,
612
  )
613
  with gr.Row():
614
- num_steps = gr.Slider(maximum=100,
615
- minimum=1,
616
- value=5 if 'turbo' in args.subfolder else 30,
617
- step=1, label='Inference Steps')
618
- octree_resolution = gr.Slider(maximum=512,
619
- minimum=16,
620
- value=256,
621
- label='Octree Resolution')
 
 
 
 
 
622
  with gr.Row():
623
- cfg_scale = gr.Number(value=5.0, label='Guidance Scale', min_width=100)
624
- num_chunks = gr.Slider(maximum=5000000, minimum=1000, value=8000,
625
- label='Number of Chunks', min_width=100)
626
- with gr.Tab("Export", id='tab_export'):
 
 
 
 
 
 
 
627
  with gr.Row():
628
- file_type = gr.Dropdown(label='File Type',
629
- choices=SUPPORTED_FORMATS,
630
- value='glb', min_width=100)
631
- reduce_face = gr.Checkbox(label='Simplify Mesh',
632
- value=False, min_width=100)
633
- export_texture = gr.Checkbox(label='Include Texture', value=False,
634
- visible=False, min_width=100)
635
- target_face_num = gr.Slider(maximum=1000000, minimum=100, value=10000,
636
- label='Target Face Number')
 
 
 
 
 
 
 
 
 
 
 
 
637
  with gr.Row():
638
  confirm_export = gr.Button(value="Transform", min_width=100)
639
- file_export = gr.DownloadButton(label="Download", variant='primary',
640
- interactive=False, min_width=100)
 
 
 
 
641
 
642
  with gr.Column(scale=6):
643
- with gr.Tabs(selected='gen_mesh_panel') as tabs_output:
644
- with gr.Tab('Generated Mesh', id='gen_mesh_panel'):
645
- html_gen_mesh = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output')
646
- with gr.Tab('Exporting Mesh', id='export_mesh_panel'):
647
- html_export_mesh = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output')
648
- with gr.Tab('Mesh Statistic', id='stats_panel'):
649
- stats = gr.Json({}, label='Mesh Stats')
 
 
650
 
651
  with gr.Column(scale=3 if MV_MODE else 2):
652
- with gr.Tabs(selected='tab_img_gallery') as gallery:
653
- with gr.Tab('Image to 3D Gallery',
654
- id='tab_img_gallery',
655
- visible=not MV_MODE) as tab_gi:
656
  with gr.Row():
657
- gr.Examples(examples=example_is, inputs=[image],
658
- label=None, examples_per_page=18)
659
-
660
- tab_ip.select(fn=lambda: gr.update(selected='tab_img_gallery'), outputs=gallery)
661
- #if HAS_T2I:
 
 
 
 
662
  # tab_tp.select(fn=lambda: gr.update(selected='tab_txt_gallery'), outputs=gallery)
663
 
664
  btn.click(
@@ -678,13 +782,17 @@ Fast for very complex cases, Standard seldom use.',
678
  num_chunks,
679
  randomize_seed,
680
  ],
681
- outputs=[file_out, html_gen_mesh, stats, seed]
682
  ).then(
683
- lambda: (gr.update(visible=False, value=False), gr.update(interactive=True), gr.update(interactive=True),
684
- gr.update(interactive=False)),
 
 
 
 
685
  outputs=[export_texture, reduce_face, confirm_export, file_export],
686
  ).then(
687
- lambda: gr.update(selected='gen_mesh_panel'),
688
  outputs=[tabs_output],
689
  )
690
 
@@ -705,20 +813,24 @@ Fast for very complex cases, Standard seldom use.',
705
  num_chunks,
706
  randomize_seed,
707
  ],
708
- outputs=[file_out, file_out2, html_gen_mesh, stats, seed]
709
  ).then(
710
- lambda: (gr.update(visible=True, value=True), gr.update(interactive=False), gr.update(interactive=True),
711
- gr.update(interactive=False)),
 
 
 
 
712
  outputs=[export_texture, reduce_face, confirm_export, file_export],
713
  ).then(
714
- lambda: gr.update(selected='gen_mesh_panel'),
715
  outputs=[tabs_output],
716
  )
717
 
718
  def on_gen_mode_change(value):
719
- if value == 'Turbo':
720
  return gr.update(value=5)
721
- elif value == 'Fast':
722
  return gr.update(value=10)
723
  else:
724
  return gr.update(value=30)
@@ -726,23 +838,25 @@ Fast for very complex cases, Standard seldom use.',
726
  gen_mode.change(on_gen_mode_change, inputs=[gen_mode], outputs=[num_steps])
727
 
728
  def on_decode_mode_change(value):
729
- if value == 'Low':
730
  return gr.update(value=196)
731
- elif value == 'Standard':
732
  return gr.update(value=256)
733
  else:
734
  return gr.update(value=384)
735
 
736
- decode_mode.change(on_decode_mode_change, inputs=[decode_mode],
737
- outputs=[octree_resolution])
 
738
 
739
- def on_export_click(file_out, file_out2, file_type,
740
- reduce_face, export_texture, target_face_num):
 
741
  if file_out is None:
742
- raise gr.Error('Please generate a mesh first.')
743
 
744
- print(f'exporting {file_out}')
745
- print(f'reduce face to {target_face_num}')
746
  if export_texture:
747
  mesh = trimesh.load(file_out2)
748
  save_folder = gen_save_folder()
@@ -751,10 +865,9 @@ Fast for very complex cases, Standard seldom use.',
751
  # for preview
752
  save_folder = gen_save_folder()
753
  _ = export_mesh(mesh, save_folder, textured=True)
754
- model_viewer_html = build_model_viewer_html(save_folder,
755
- height=HTML_HEIGHT,
756
- width=HTML_WIDTH,
757
- textured=True)
758
  else:
759
  mesh = trimesh.load(file_out)
760
  mesh = floater_remove_worker(mesh)
@@ -767,42 +880,50 @@ Fast for very complex cases, Standard seldom use.',
767
  # for preview
768
  save_folder = gen_save_folder()
769
  _ = export_mesh(mesh, save_folder, textured=False)
770
- model_viewer_html = build_model_viewer_html(save_folder,
771
- height=HTML_HEIGHT,
772
- width=HTML_WIDTH,
773
- textured=False)
774
- print(f'export to {path}')
775
  return model_viewer_html, gr.update(value=path, interactive=True)
776
 
777
  confirm_export.click(
778
- lambda: gr.update(selected='export_mesh_panel'),
779
  outputs=[tabs_output],
780
  ).then(
781
  on_export_click,
782
- inputs=[file_out, file_out2, file_type, reduce_face, export_texture, target_face_num],
783
- outputs=[html_export_mesh, file_export]
 
 
 
 
 
 
 
784
  )
785
 
786
  return demo
787
 
788
 
789
- if __name__ == '__main__':
790
  import argparse
791
 
792
  parser = argparse.ArgumentParser()
793
- parser.add_argument("--model_path", type=str, default='tencent/Hunyuan3D-2.1')
794
- parser.add_argument("--subfolder", type=str, default='hunyuan3d-dit-v2-1')
795
- parser.add_argument("--texgen_model_path", type=str, default='tencent/Hunyuan3D-2.1')
796
- parser.add_argument('--port', type=int, default=7860)
797
- parser.add_argument('--host', type=str, default='0.0.0.0')
798
- parser.add_argument('--device', type=str, default='cuda')
799
- parser.add_argument('--mc_algo', type=str, default='mc')
800
- parser.add_argument('--cache-path', type=str, default='/root/save_dir')
801
- parser.add_argument('--enable_t23d', action='store_true')
802
- parser.add_argument('--disable_tex', action='store_true')
803
- parser.add_argument('--enable_flashvdm', action='store_true')
804
- parser.add_argument('--compile', action='store_true')
805
- parser.add_argument('--low_vram_mode', action='store_true')
 
 
806
  args = parser.parse_args()
807
  args.enable_flashvdm = False
808
 
@@ -810,8 +931,8 @@ if __name__ == '__main__':
810
  os.makedirs(SAVE_DIR, exist_ok=True)
811
 
812
  CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
813
- MV_MODE = 'mv' in args.model_path
814
- TURBO_MODE = 'turbo' in args.subfolder
815
 
816
  HTML_HEIGHT = 690 if MV_MODE else 650
817
  HTML_WIDTH = 500
@@ -832,46 +953,54 @@ if __name__ == '__main__':
832
  example_is = get_example_img_list()
833
  example_ts = get_example_txt_list()
834
 
835
- SUPPORTED_FORMATS = ['glb', 'obj', 'ply', 'stl']
836
 
837
- HAS_TEXTUREGEN = False
 
838
  if not args.disable_tex:
839
  try:
840
  # Apply torchvision fix before importing basicsr/RealESRGAN
841
  print("Applying torchvision compatibility fix for texture generation...")
842
  try:
843
  from torchvision_fix import apply_fix
 
844
  fix_result = apply_fix()
845
  if not fix_result:
846
- print("Warning: Torchvision fix may not have been applied successfully")
 
 
847
  except Exception as fix_error:
848
  print(f"Warning: Failed to apply torchvision fix: {fix_error}")
849
-
850
  # from hy3dgen.texgen import Hunyuan3DPaintPipeline
851
  # texgen_worker = Hunyuan3DPaintPipeline.from_pretrained(args.texgen_model_path)
852
  # if args.low_vram_mode:
853
  # texgen_worker.enable_model_cpu_offload()
854
 
855
- from hy3dpaint.textureGenPipeline import Hunyuan3DPaintPipeline, Hunyuan3DPaintConfig
 
 
 
 
856
  conf = Hunyuan3DPaintConfig(max_num_view=8, resolution=768)
857
  conf.realesrgan_ckpt_path = "hy3dpaint/ckpt/RealESRGAN_x4plus.pth"
858
  conf.multiview_cfg_path = "hy3dpaint/cfgs/hunyuan-paint-pbr.yaml"
859
  conf.custom_pipeline = "hy3dpaint/hunyuanpaintpbr"
860
  tex_pipeline = Hunyuan3DPaintPipeline(conf)
861
-
862
  # Not help much, ignore for now.
863
  # if args.compile:
864
  # texgen_worker.models['delight_model'].pipeline.unet.compile()
865
  # texgen_worker.models['delight_model'].pipeline.vae.compile()
866
  # texgen_worker.models['multiview_model'].pipeline.unet.compile()
867
  # texgen_worker.models['multiview_model'].pipeline.vae.compile()
868
-
869
  HAS_TEXTUREGEN = True
870
-
871
  except Exception as e:
872
  print(f"Error loading texture generator: {e}")
873
  print("Failed to load texture generator.")
874
- print('Please try to install requirements by following README.md')
875
  HAS_TEXTUREGEN = False
876
 
877
  # HAS_T2I = True
@@ -881,8 +1010,13 @@ if __name__ == '__main__':
881
  # t2i_worker = HunyuanDiTPipeline('Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled')
882
  # HAS_T2I = True
883
 
884
- from hy3dshape import FaceReducer, FloaterRemover, DegenerateFaceRemover, MeshSimplifier, \
885
- Hunyuan3DDiTFlowMatchingPipeline
 
 
 
 
 
886
  from hy3dshape.pipelines import export_to_trimesh
887
  from hy3dshape.rembg import BackgroundRemover
888
 
@@ -894,7 +1028,7 @@ if __name__ == '__main__':
894
  device=args.device,
895
  )
896
  if args.enable_flashvdm:
897
- mc_algo = 'mc' if args.device in ['cpu', 'mps'] else args.mc_algo
898
  i23d_worker.enable_flashvdm(mc_algo=mc_algo)
899
  if args.compile:
900
  i23d_worker.compile()
@@ -906,22 +1040,25 @@ if __name__ == '__main__':
906
  # https://discuss.huggingface.co/t/how-to-serve-an-html-file/33921/2
907
  # create a FastAPI app
908
  app = FastAPI()
909
-
910
  # create a static directory to store the static files
911
  static_dir = Path(SAVE_DIR).absolute()
912
  static_dir.mkdir(parents=True, exist_ok=True)
913
  app.mount("/static", StaticFiles(directory=static_dir, html=True), name="static")
914
- shutil.copytree('./assets/env_maps', os.path.join(static_dir, 'env_maps'), dirs_exist_ok=True)
 
 
915
 
916
  if args.low_vram_mode:
917
  torch.cuda.empty_cache()
918
-
919
  demo = build_app()
920
  app = gr.mount_gradio_app(app, demo, path="/")
921
 
922
- if ENV == 'Huggingface':
923
  # for Zerogpu
924
  from spaces import zero
 
925
  zero.startup()
926
 
927
  uvicorn.run(app, host=args.host, port=args.port)
 
15
  # Apply torchvision compatibility fix before other imports
16
 
17
  import sys
18
+
19
+ sys.path.insert(0, "./hy3dshape")
20
+ sys.path.insert(0, "./hy3dpaint")
21
 
22
  pythonpath = sys.executable
23
  print(pythonpath)
24
 
25
  try:
26
  from torchvision_fix import apply_fix
27
+
28
  apply_fix()
29
  except ImportError:
30
+ print(
31
+ "Warning: torchvision_fix module not found, proceeding without compatibility fix"
32
+ )
33
  except Exception as e:
34
  print(f"Warning: Failed to apply torchvision fix: {e}")
35
 
 
56
 
57
 
58
  MAX_SEED = 1e7
59
+ ENV = "Local" # "Huggingface"
60
+ if ENV == "Huggingface":
61
  """
62
  Setup environment for running on Huggingface platform.
63
 
 
80
  subprocess.call(["wget", "-q", CUDA_TOOLKIT_URL, "-O", CUDA_TOOLKIT_FILE])
81
  subprocess.call(["chmod", "+x", CUDA_TOOLKIT_FILE])
82
  subprocess.call([CUDA_TOOLKIT_FILE, "--silent", "--toolkit"])
83
+
84
  os.environ["CUDA_HOME"] = "/usr/local/cuda"
85
  os.environ["PATH"] = "%s/bin:%s" % (os.environ["CUDA_HOME"], os.environ["PATH"])
86
  os.environ["LD_LIBRARY_PATH"] = "%s/lib:%s" % (
87
  os.environ["CUDA_HOME"],
88
+ ""
89
+ if "LD_LIBRARY_PATH" not in os.environ
90
+ else os.environ["LD_LIBRARY_PATH"],
91
  )
92
  # Fix: arch_list[-1] += '+PTX'; IndexError: list index out of range
93
  os.environ["TORCH_CUDA_ARCH_LIST"] = "8.0;8.6"
 
96
  # print('install custom')
97
  # os.system(f"cd /home/user/app/hy3dpaint/custom_rasterizer && {pythonpath} -m pip install -e .")
98
  # os.system(f"cd /home/user/app/hy3dpaint/packages/custom_rasterizer && pip install -e .")
99
+ subprocess.run(
100
+ shlex.split(
101
+ "pip install custom_rasterizer-0.1-cp310-cp310-linux_x86_64.whl"
102
+ ),
103
+ check=True,
104
+ )
105
 
106
+ print(
107
+ "cd /home/user/app/hy3dpaint/differentiable_renderer/ && bash compile_mesh_painter.sh"
108
+ )
109
+ os.system(
110
+ "cd /home/user/app/hy3dpaint/DifferentiableRenderer && bash compile_mesh_painter.sh"
111
+ )
112
  # print("wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P /home/user/app/hy3dpaint/ckpt")
113
  # os.system("wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P /home/user/app/hy3dpaint/ckpt")
114
 
115
  def check():
116
  import custom_rasterizer
117
+
118
  print(type(custom_rasterizer))
119
  print(dir(custom_rasterizer))
120
+ print(getattr(custom_rasterizer, "__file__", None))
121
 
122
  package_dir = None
123
+ if hasattr(custom_rasterizer, "__file__") and custom_rasterizer.__file__:
124
  package_dir = os.path.dirname(custom_rasterizer.__file__)
125
+ elif hasattr(custom_rasterizer, "__path__"):
126
  package_dir = list(custom_rasterizer.__path__)[0]
127
  else:
128
  raise RuntimeError("Cannot determine package path")
129
  print(package_dir)
130
 
131
  for root, dirs, files in os.walk(package_dir):
132
+ level = root.replace(package_dir, "").count(os.sep)
133
+ indent = " " * 4 * level
134
  print(f"{indent}{os.path.basename(root)}/")
135
+ subindent = " " * 4 * (level + 1)
136
  for f in files:
137
  print(f"{subindent}{f}")
138
 
 
141
  print(torch.__version__)
142
  prepare_env()
143
  check()
144
+
145
  else:
146
  """
147
  Define a dummy `spaces` module with a GPU decorator class for local environment.
 
149
  The GPU decorator is a no-op that simply returns the decorated function unchanged.
150
  This allows code that uses the `spaces.GPU` decorator to run without modification locally.
151
  """
152
+
153
  class spaces:
154
  class GPU:
155
  def __init__(self, duration=60):
156
  self.duration = duration
157
+
158
  def __call__(self, func):
159
+ return func
160
+
161
 
162
  def get_example_img_list():
163
  """
 
168
  Returns:
169
  list[str]: Sorted list of file paths to example PNG images.
170
  """
171
+ print("Loading example img list ...")
172
+ return sorted(glob("./assets/example_images/**/*.png", recursive=True))
173
 
174
 
175
  def get_example_txt_list():
 
181
  Returns:
182
  list[str]: List of example text prompts.
183
  """
184
+ print("Loading example txt list ...")
185
  txt_list = list()
186
+ for line in open("./assets/example_prompts.txt", encoding="utf-8"):
187
  txt_list.append(line.strip())
188
  return txt_list
189
 
 
213
 
214
 
215
  # Removed complex PBR conversion functions - using simple trimesh-based conversion
216
+ def export_mesh(mesh, save_folder, textured=False, type="glb"):
217
  """
218
  Export a mesh to a file in the specified folder, optionally including textures.
219
 
 
227
  str: The full path to the exported mesh file.
228
  """
229
  if textured:
230
+ path = os.path.join(save_folder, f"textured_mesh.{type}")
231
  else:
232
+ path = os.path.join(save_folder, f"white_mesh.{type}")
233
+ if type not in ["glb", "obj"]:
234
  mesh.export(path)
235
  else:
236
  mesh.export(path, include_normals=textured)
237
  return path
238
 
239
 
 
 
240
  def quick_convert_with_obj2gltf(obj_path: str, glb_path: str) -> bool:
241
  # 执行转换
242
  textures = {
243
+ "albedo": obj_path.replace(".obj", ".jpg"),
244
+ "metallic": obj_path.replace(".obj", "_metallic.jpg"),
245
+ "roughness": obj_path.replace(".obj", "_roughness.jpg"),
246
+ }
247
  create_glb_with_pbr_materials(obj_path, textures, glb_path)
 
248
 
249
 
250
  def randomize_seed_fn(seed: int, randomize_seed: bool) -> int:
 
257
  # Remove first folder from path to make relative path
258
  if textured:
259
  related_path = f"./textured_mesh.glb"
260
+ template_name = "./assets/modelviewer-textured-template.html"
261
+ output_html_path = os.path.join(save_folder, f"textured_mesh.html")
262
  else:
263
  related_path = f"./white_mesh.glb"
264
+ template_name = "./assets/modelviewer-template.html"
265
+ output_html_path = os.path.join(save_folder, f"white_mesh.html")
266
  offset = 50 if textured else 10
267
+ with open(os.path.join(CURRENT_DIR, template_name), "r", encoding="utf-8") as f:
268
  template_html = f.read()
269
 
270
+ with open(output_html_path, "w", encoding="utf-8") as f:
271
+ template_html = template_html.replace("#height#", f"{height - offset}")
272
+ template_html = template_html.replace("#width#", f"{width}")
273
+ template_html = template_html.replace("#src#", f"{related_path}/")
274
  f.write(template_html)
275
 
276
  rel_path = os.path.relpath(output_html_path, SAVE_DIR)
277
  iframe_tag = f'<iframe src="/static/{rel_path}" \
278
  height="{height}" width="100%" frameborder="0"></iframe>'
279
+ print(
280
+ f"Find html file {output_html_path}, \
281
+ {os.path.exists(output_html_path)}, relative HTML path is /static/{rel_path}"
282
+ )
283
 
284
  return f"""
285
  <div style='height: {height}; width: 100%;'>
 
287
  </div>
288
  """
289
 
290
+
291
  @spaces.GPU(duration=60)
292
  def _gen_shape(
293
  caption=None,
 
307
  if not MV_MODE and image is None and caption is None:
308
  raise gr.Error("Please provide either a caption or an image.")
309
  if MV_MODE:
310
+ if (
311
+ mv_image_front is None
312
+ and mv_image_back is None
313
+ and mv_image_left is None
314
+ and mv_image_right is None
315
+ ):
316
  raise gr.Error("Please provide at least one view image.")
317
  image = {}
318
  if mv_image_front:
319
+ image["front"] = mv_image_front
320
  if mv_image_back:
321
+ image["back"] = mv_image_back
322
  if mv_image_left:
323
+ image["left"] = mv_image_left
324
  if mv_image_right:
325
+ image["right"] = mv_image_right
326
 
327
  seed = int(randomize_seed_fn(seed, randomize_seed))
328
 
329
  octree_resolution = int(octree_resolution)
330
+ if caption:
331
+ print("prompt is", caption)
332
  save_folder = gen_save_folder()
333
  stats = {
334
+ "model": {
335
+ "shapegen": f"{args.model_path}/{args.subfolder}",
336
+ "texgen": f"{args.texgen_model_path}",
337
+ },
338
+ "params": {
339
+ "caption": caption,
340
+ "steps": steps,
341
+ "guidance_scale": guidance_scale,
342
+ "seed": seed,
343
+ "octree_resolution": octree_resolution,
344
+ "check_box_rembg": check_box_rembg,
345
+ "num_chunks": num_chunks,
346
  },
 
 
 
 
 
 
 
 
 
347
  }
348
  time_meta = {}
349
 
 
352
  try:
353
  image = t2i_worker(caption)
354
  except Exception as e:
355
+ raise gr.Error(
356
+ f"Text to 3D is disable. \
357
+ Please enable it by `python gradio_app.py --enable_t23d`."
358
+ )
359
+ time_meta["text2image"] = time.time() - start_time
360
 
361
  # remove disk io to make responding faster, uncomment at your will.
362
  # image.save(os.path.join(save_folder, 'input.png'))
 
364
  start_time = time.time()
365
  for k, v in image.items():
366
  if check_box_rembg or v.mode == "RGB":
367
+ img = rmbg_worker(v.convert("RGB"))
368
  image[k] = img
369
+ time_meta["remove background"] = time.time() - start_time
370
  else:
371
  if check_box_rembg or image.mode == "RGB":
372
  start_time = time.time()
373
+ image = rmbg_worker(image.convert("RGB"))
374
+ time_meta["remove background"] = time.time() - start_time
375
 
376
  # remove disk io to make responding faster, uncomment at your will.
377
  # image.save(os.path.join(save_folder, 'rembg.png'))
 
388
  generator=generator,
389
  octree_resolution=octree_resolution,
390
  num_chunks=num_chunks,
391
+ output_type="mesh",
392
  )
393
+ time_meta["shape generation"] = time.time() - start_time
394
  logger.info("---Shape generation takes %s seconds ---" % (time.time() - start_time))
395
 
396
  tmp_start = time.time()
397
  mesh = export_to_trimesh(outputs)[0]
398
+ time_meta["export to trimesh"] = time.time() - tmp_start
399
 
400
+ stats["number_of_faces"] = mesh.faces.shape[0]
401
+ stats["number_of_vertices"] = mesh.vertices.shape[0]
402
 
403
+ stats["time"] = time_meta
404
+ main_image = image if not MV_MODE else image["front"]
405
  return mesh, main_image, save_folder, stats, seed
406
 
407
+
408
  @spaces.GPU(duration=130)
409
  def generation_all(
410
  caption=None,
 
438
  randomize_seed=randomize_seed,
439
  )
440
  path = export_mesh(mesh, save_folder, textured=False)
 
441
 
442
  print(path)
443
+ print("=" * 40)
444
 
445
  # tmp_time = time.time()
446
  # mesh = floater_remove_worker(mesh)
 
452
  mesh = face_reduce_worker(mesh)
453
 
454
  # path = export_mesh(mesh, save_folder, textured=False, type='glb')
455
+ path = export_mesh(
456
+ mesh, save_folder, textured=False, type="obj"
457
+ ) # 这样操作也会 core dump
458
 
459
  logger.info("---Face Reduction takes %s seconds ---" % (time.time() - tmp_time))
460
+ stats["time"]["face reduction"] = time.time() - tmp_time
461
 
462
  tmp_time = time.time()
463
 
464
+ text_path = os.path.join(save_folder, f"textured_mesh.obj")
465
+ path_textured = tex_pipeline(
466
+ mesh_path=path, image_path=image, output_mesh_path=text_path, save_glb=False
467
+ )
468
+
469
  logger.info("---Texture Generation takes %s seconds ---" % (time.time() - tmp_time))
470
+ stats["time"]["texture generation"] = time.time() - tmp_time
471
 
472
  tmp_time = time.time()
473
  # Convert textured OBJ to GLB using obj2gltf with PBR support
474
+ glb_path_textured = os.path.join(save_folder, "textured_mesh.glb")
475
  conversion_success = quick_convert_with_obj2gltf(path_textured, glb_path_textured)
476
 
477
+ logger.info(
478
+ "---Convert textured OBJ to GLB takes %s seconds ---" % (time.time() - tmp_time)
479
+ )
480
+ stats["time"]["convert textured OBJ to GLB"] = time.time() - tmp_time
481
+ stats["time"]["total"] = time.time() - start_time_0
482
+ model_viewer_html_textured = build_model_viewer_html(
483
+ save_folder, height=HTML_HEIGHT, width=HTML_WIDTH, textured=True
484
+ )
485
  if args.low_vram_mode:
486
  torch.cuda.empty_cache()
487
  return (
 
492
  seed,
493
  )
494
 
495
+
496
  @spaces.GPU(duration=60)
497
  def shape_generation(
498
  caption=None,
 
525
  num_chunks=num_chunks,
526
  randomize_seed=randomize_seed,
527
  )
528
+ stats["time"]["total"] = time.time() - start_time_0
529
+ mesh.metadata["extras"] = stats
530
 
531
  path = export_mesh(mesh, save_folder, textured=False)
532
+ model_viewer_html = build_model_viewer_html(
533
+ save_folder, height=HTML_HEIGHT, width=HTML_WIDTH
534
+ )
535
  if args.low_vram_mode:
536
  torch.cuda.empty_cache()
537
  return (
 
543
 
544
 
545
  def build_app():
546
+ title = "High Resolution Textured 3D Assets Generation"
547
  if MV_MODE:
548
+ title = "Image to 3D Generation with 1-4 Views"
549
+ if "mini" in args.subfolder:
550
+ title = "Strong 0.6B Image to Shape Generator"
551
+
552
+ title = "Image to 3D Generation"
553
 
 
 
554
  if TURBO_MODE:
555
+ title = title.replace(":", "-Turbo: Fast ")
556
 
557
  title_html = f"""
558
  <div style="font-size: 2em; font-weight: bold; text-align: center; margin-bottom: 5px">
 
577
 
578
  """
579
 
580
+ with gr.Blocks(
581
+ theme=gr.themes.Base(),
582
+ title="Image to 3D Generation",
583
+ analytics_enabled=False,
584
+ css=custom_css,
585
+ ) as demo:
586
  gr.HTML(title_html)
587
 
588
  with gr.Row():
589
  with gr.Column(scale=3):
590
+ with gr.Tabs(selected="tab_img_prompt") as tabs_prompt:
591
+ with gr.Tab(
592
+ "Image Prompt", id="tab_img_prompt", visible=not MV_MODE
593
+ ) as tab_ip:
594
+ image = gr.Image(
595
+ label="Image", type="pil", image_mode="RGBA", height=290
596
+ )
597
  caption = gr.State(None)
598
+ # with gr.Tab('Text Prompt', id='tab_txt_prompt', visible=HAS_T2I and not MV_MODE) as tab_tp:
599
+ # caption = gr.Textbox(label='Text Prompt',
600
+ # placeholder='HunyuanDiT will be used to generate image.',
601
+ # info='Example: A 3D model of a cute cat, white background')
602
+ with gr.Tab("MultiView Prompt", visible=MV_MODE) as tab_mv:
603
  # gr.Label('Please upload at least one front image.')
604
  with gr.Row():
605
+ mv_image_front = gr.Image(
606
+ label="Front",
607
+ type="pil",
608
+ image_mode="RGBA",
609
+ height=140,
610
+ min_width=100,
611
+ elem_classes="mv-image",
612
+ )
613
+ mv_image_back = gr.Image(
614
+ label="Back",
615
+ type="pil",
616
+ image_mode="RGBA",
617
+ height=140,
618
+ min_width=100,
619
+ elem_classes="mv-image",
620
+ )
621
  with gr.Row():
622
+ mv_image_left = gr.Image(
623
+ label="Left",
624
+ type="pil",
625
+ image_mode="RGBA",
626
+ height=140,
627
+ min_width=100,
628
+ elem_classes="mv-image",
629
+ )
630
+ mv_image_right = gr.Image(
631
+ label="Right",
632
+ type="pil",
633
+ image_mode="RGBA",
634
+ height=140,
635
+ min_width=100,
636
+ elem_classes="mv-image",
637
+ )
638
 
639
  with gr.Row():
640
+ btn = gr.Button(value="Gen Shape", variant="primary", min_width=100)
641
+ btn_all = gr.Button(
642
+ value="Gen Textured Shape",
643
+ variant="primary",
644
+ visible=HAS_TEXTUREGEN,
645
+ min_width=100,
646
+ )
647
 
648
  with gr.Group():
649
  file_out = gr.File(label="File", visible=False)
650
  file_out2 = gr.File(label="File", visible=False)
651
 
652
+ with gr.Tabs(selected="tab_options" if TURBO_MODE else "tab_export"):
653
+ with gr.Tab("Options", id="tab_options", visible=TURBO_MODE):
654
  gen_mode = gr.Radio(
655
+ label="Generation Mode",
656
+ info="Recommendation: Turbo for most cases, \
657
+ Fast for very complex cases, Standard seldom use.",
658
+ choices=["Turbo", "Fast", "Standard"],
659
+ value="Turbo",
660
+ )
661
  decode_mode = gr.Radio(
662
+ label="Decoding Mode",
663
+ info="The resolution for exporting mesh from generated vectset",
664
+ choices=["Low", "Standard", "High"],
665
+ value="Standard",
666
+ )
667
+ with gr.Tab("Advanced Options", id="tab_advanced_options"):
668
  with gr.Row():
669
  check_box_rembg = gr.Checkbox(
670
+ value=True, label="Remove Background", min_width=100
671
+ )
 
672
  randomize_seed = gr.Checkbox(
673
+ label="Randomize seed", value=True, min_width=100
674
+ )
 
675
  seed = gr.Slider(
676
  label="Seed",
677
  minimum=0,
 
681
  min_width=100,
682
  )
683
  with gr.Row():
684
+ num_steps = gr.Slider(
685
+ maximum=100,
686
+ minimum=1,
687
+ value=5 if "turbo" in args.subfolder else 30,
688
+ step=1,
689
+ label="Inference Steps",
690
+ )
691
+ octree_resolution = gr.Slider(
692
+ maximum=512,
693
+ minimum=16,
694
+ value=256,
695
+ label="Octree Resolution",
696
+ )
697
  with gr.Row():
698
+ cfg_scale = gr.Number(
699
+ value=5.0, label="Guidance Scale", min_width=100
700
+ )
701
+ num_chunks = gr.Slider(
702
+ maximum=5000000,
703
+ minimum=1000,
704
+ value=8000,
705
+ label="Number of Chunks",
706
+ min_width=100,
707
+ )
708
+ with gr.Tab("Export", id="tab_export"):
709
  with gr.Row():
710
+ file_type = gr.Dropdown(
711
+ label="File Type",
712
+ choices=SUPPORTED_FORMATS,
713
+ value="glb",
714
+ min_width=100,
715
+ )
716
+ reduce_face = gr.Checkbox(
717
+ label="Simplify Mesh", value=False, min_width=100
718
+ )
719
+ export_texture = gr.Checkbox(
720
+ label="Include Texture",
721
+ value=False,
722
+ visible=False,
723
+ min_width=100,
724
+ )
725
+ target_face_num = gr.Slider(
726
+ maximum=1000000,
727
+ minimum=100,
728
+ value=10000,
729
+ label="Target Face Number",
730
+ )
731
  with gr.Row():
732
  confirm_export = gr.Button(value="Transform", min_width=100)
733
+ file_export = gr.DownloadButton(
734
+ label="Download",
735
+ variant="primary",
736
+ interactive=False,
737
+ min_width=100,
738
+ )
739
 
740
  with gr.Column(scale=6):
741
+ with gr.Tabs(selected="gen_mesh_panel") as tabs_output:
742
+ with gr.Tab("Generated Mesh", id="gen_mesh_panel"):
743
+ html_gen_mesh = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label="Output")
744
+ with gr.Tab("Exporting Mesh", id="export_mesh_panel"):
745
+ html_export_mesh = gr.HTML(
746
+ HTML_OUTPUT_PLACEHOLDER, label="Output"
747
+ )
748
+ with gr.Tab("Mesh Statistic", id="stats_panel"):
749
+ stats = gr.Json({}, label="Mesh Stats")
750
 
751
  with gr.Column(scale=3 if MV_MODE else 2):
752
+ with gr.Tabs(selected="tab_img_gallery") as gallery:
753
+ with gr.Tab(
754
+ "Image to 3D Gallery", id="tab_img_gallery", visible=not MV_MODE
755
+ ) as tab_gi:
756
  with gr.Row():
757
+ gr.Examples(
758
+ examples=example_is,
759
+ inputs=[image],
760
+ label=None,
761
+ examples_per_page=18,
762
+ )
763
+
764
+ tab_ip.select(fn=lambda: gr.update(selected="tab_img_gallery"), outputs=gallery)
765
+ # if HAS_T2I:
766
  # tab_tp.select(fn=lambda: gr.update(selected='tab_txt_gallery'), outputs=gallery)
767
 
768
  btn.click(
 
782
  num_chunks,
783
  randomize_seed,
784
  ],
785
+ outputs=[file_out, html_gen_mesh, stats, seed],
786
  ).then(
787
+ lambda: (
788
+ gr.update(visible=False, value=False),
789
+ gr.update(interactive=True),
790
+ gr.update(interactive=True),
791
+ gr.update(interactive=False),
792
+ ),
793
  outputs=[export_texture, reduce_face, confirm_export, file_export],
794
  ).then(
795
+ lambda: gr.update(selected="gen_mesh_panel"),
796
  outputs=[tabs_output],
797
  )
798
 
 
813
  num_chunks,
814
  randomize_seed,
815
  ],
816
+ outputs=[file_out, file_out2, html_gen_mesh, stats, seed],
817
  ).then(
818
+ lambda: (
819
+ gr.update(visible=True, value=True),
820
+ gr.update(interactive=False),
821
+ gr.update(interactive=True),
822
+ gr.update(interactive=False),
823
+ ),
824
  outputs=[export_texture, reduce_face, confirm_export, file_export],
825
  ).then(
826
+ lambda: gr.update(selected="gen_mesh_panel"),
827
  outputs=[tabs_output],
828
  )
829
 
830
  def on_gen_mode_change(value):
831
+ if value == "Turbo":
832
  return gr.update(value=5)
833
+ elif value == "Fast":
834
  return gr.update(value=10)
835
  else:
836
  return gr.update(value=30)
 
838
  gen_mode.change(on_gen_mode_change, inputs=[gen_mode], outputs=[num_steps])
839
 
840
  def on_decode_mode_change(value):
841
+ if value == "Low":
842
  return gr.update(value=196)
843
+ elif value == "Standard":
844
  return gr.update(value=256)
845
  else:
846
  return gr.update(value=384)
847
 
848
+ decode_mode.change(
849
+ on_decode_mode_change, inputs=[decode_mode], outputs=[octree_resolution]
850
+ )
851
 
852
+ def on_export_click(
853
+ file_out, file_out2, file_type, reduce_face, export_texture, target_face_num
854
+ ):
855
  if file_out is None:
856
+ raise gr.Error("Please generate a mesh first.")
857
 
858
+ print(f"exporting {file_out}")
859
+ print(f"reduce face to {target_face_num}")
860
  if export_texture:
861
  mesh = trimesh.load(file_out2)
862
  save_folder = gen_save_folder()
 
865
  # for preview
866
  save_folder = gen_save_folder()
867
  _ = export_mesh(mesh, save_folder, textured=True)
868
+ model_viewer_html = build_model_viewer_html(
869
+ save_folder, height=HTML_HEIGHT, width=HTML_WIDTH, textured=True
870
+ )
 
871
  else:
872
  mesh = trimesh.load(file_out)
873
  mesh = floater_remove_worker(mesh)
 
880
  # for preview
881
  save_folder = gen_save_folder()
882
  _ = export_mesh(mesh, save_folder, textured=False)
883
+ model_viewer_html = build_model_viewer_html(
884
+ save_folder, height=HTML_HEIGHT, width=HTML_WIDTH, textured=False
885
+ )
886
+ print(f"export to {path}")
 
887
  return model_viewer_html, gr.update(value=path, interactive=True)
888
 
889
  confirm_export.click(
890
+ lambda: gr.update(selected="export_mesh_panel"),
891
  outputs=[tabs_output],
892
  ).then(
893
  on_export_click,
894
+ inputs=[
895
+ file_out,
896
+ file_out2,
897
+ file_type,
898
+ reduce_face,
899
+ export_texture,
900
+ target_face_num,
901
+ ],
902
+ outputs=[html_export_mesh, file_export],
903
  )
904
 
905
  return demo
906
 
907
 
908
+ if __name__ == "__main__":
909
  import argparse
910
 
911
  parser = argparse.ArgumentParser()
912
+ parser.add_argument("--model_path", type=str, default="tencent/Hunyuan3D-2.1")
913
+ parser.add_argument("--subfolder", type=str, default="hunyuan3d-dit-v2-1")
914
+ parser.add_argument(
915
+ "--texgen_model_path", type=str, default="tencent/Hunyuan3D-2.1"
916
+ )
917
+ parser.add_argument("--port", type=int, default=7860)
918
+ parser.add_argument("--host", type=str, default="0.0.0.0")
919
+ parser.add_argument("--device", type=str, default="cuda")
920
+ parser.add_argument("--mc_algo", type=str, default="mc")
921
+ parser.add_argument("--cache-path", type=str, default="/root/save_dir")
922
+ parser.add_argument("--enable_t23d", action="store_true")
923
+ parser.add_argument("--disable_tex", action="store_true")
924
+ parser.add_argument("--enable_flashvdm", action="store_true")
925
+ parser.add_argument("--compile", action="store_true")
926
+ parser.add_argument("--low_vram_mode", action="store_true")
927
  args = parser.parse_args()
928
  args.enable_flashvdm = False
929
 
 
931
  os.makedirs(SAVE_DIR, exist_ok=True)
932
 
933
  CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
934
+ MV_MODE = "mv" in args.model_path
935
+ TURBO_MODE = "turbo" in args.subfolder
936
 
937
  HTML_HEIGHT = 690 if MV_MODE else 650
938
  HTML_WIDTH = 500
 
953
  example_is = get_example_img_list()
954
  example_ts = get_example_txt_list()
955
 
956
+ SUPPORTED_FORMATS = ["glb", "obj", "ply", "stl"]
957
 
958
+ HAS_TEXTUREGEN = True
959
+ args.disable_tex = True
960
  if not args.disable_tex:
961
  try:
962
  # Apply torchvision fix before importing basicsr/RealESRGAN
963
  print("Applying torchvision compatibility fix for texture generation...")
964
  try:
965
  from torchvision_fix import apply_fix
966
+
967
  fix_result = apply_fix()
968
  if not fix_result:
969
+ print(
970
+ "Warning: Torchvision fix may not have been applied successfully"
971
+ )
972
  except Exception as fix_error:
973
  print(f"Warning: Failed to apply torchvision fix: {fix_error}")
974
+
975
  # from hy3dgen.texgen import Hunyuan3DPaintPipeline
976
  # texgen_worker = Hunyuan3DPaintPipeline.from_pretrained(args.texgen_model_path)
977
  # if args.low_vram_mode:
978
  # texgen_worker.enable_model_cpu_offload()
979
 
980
+ from hy3dpaint.textureGenPipeline import (
981
+ Hunyuan3DPaintPipeline,
982
+ Hunyuan3DPaintConfig,
983
+ )
984
+
985
  conf = Hunyuan3DPaintConfig(max_num_view=8, resolution=768)
986
  conf.realesrgan_ckpt_path = "hy3dpaint/ckpt/RealESRGAN_x4plus.pth"
987
  conf.multiview_cfg_path = "hy3dpaint/cfgs/hunyuan-paint-pbr.yaml"
988
  conf.custom_pipeline = "hy3dpaint/hunyuanpaintpbr"
989
  tex_pipeline = Hunyuan3DPaintPipeline(conf)
990
+
991
  # Not help much, ignore for now.
992
  # if args.compile:
993
  # texgen_worker.models['delight_model'].pipeline.unet.compile()
994
  # texgen_worker.models['delight_model'].pipeline.vae.compile()
995
  # texgen_worker.models['multiview_model'].pipeline.unet.compile()
996
  # texgen_worker.models['multiview_model'].pipeline.vae.compile()
997
+
998
  HAS_TEXTUREGEN = True
999
+
1000
  except Exception as e:
1001
  print(f"Error loading texture generator: {e}")
1002
  print("Failed to load texture generator.")
1003
+ print("Please try to install requirements by following README.md")
1004
  HAS_TEXTUREGEN = False
1005
 
1006
  # HAS_T2I = True
 
1010
  # t2i_worker = HunyuanDiTPipeline('Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled')
1011
  # HAS_T2I = True
1012
 
1013
+ from hy3dshape import (
1014
+ FaceReducer,
1015
+ FloaterRemover,
1016
+ DegenerateFaceRemover,
1017
+ MeshSimplifier,
1018
+ Hunyuan3DDiTFlowMatchingPipeline,
1019
+ )
1020
  from hy3dshape.pipelines import export_to_trimesh
1021
  from hy3dshape.rembg import BackgroundRemover
1022
 
 
1028
  device=args.device,
1029
  )
1030
  if args.enable_flashvdm:
1031
+ mc_algo = "mc" if args.device in ["cpu", "mps"] else args.mc_algo
1032
  i23d_worker.enable_flashvdm(mc_algo=mc_algo)
1033
  if args.compile:
1034
  i23d_worker.compile()
 
1040
  # https://discuss.huggingface.co/t/how-to-serve-an-html-file/33921/2
1041
  # create a FastAPI app
1042
  app = FastAPI()
1043
+
1044
  # create a static directory to store the static files
1045
  static_dir = Path(SAVE_DIR).absolute()
1046
  static_dir.mkdir(parents=True, exist_ok=True)
1047
  app.mount("/static", StaticFiles(directory=static_dir, html=True), name="static")
1048
+ shutil.copytree(
1049
+ "./assets/env_maps", os.path.join(static_dir, "env_maps"), dirs_exist_ok=True
1050
+ )
1051
 
1052
  if args.low_vram_mode:
1053
  torch.cuda.empty_cache()
1054
+
1055
  demo = build_app()
1056
  app = gr.mount_gradio_app(app, demo, path="/")
1057
 
1058
+ if ENV == "Huggingface":
1059
  # for Zerogpu
1060
  from spaces import zero
1061
+
1062
  zero.startup()
1063
 
1064
  uvicorn.run(app, host=args.host, port=args.port)