PrismJeff commited on
Commit
476a8c0
·
1 Parent(s): 9660988

Fix: pip error2

Browse files
Files changed (2) hide show
  1. gradio_app(original).py +927 -0
  2. gradio_app.py +1 -2
gradio_app(original).py ADDED
@@ -0,0 +1,927 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ # 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
+
32
+
33
+ import os
34
+ import random
35
+ import shutil
36
+ import subprocess
37
+ import time
38
+ from glob import glob
39
+ from pathlib import Path
40
+
41
+ import gradio as gr
42
+ import torch
43
+ import trimesh
44
+ import uvicorn
45
+ from fastapi import FastAPI
46
+ from fastapi.staticfiles import StaticFiles
47
+ import uuid
48
+ import numpy as np
49
+
50
+ from hy3dshape.utils import logger
51
+ from hy3dpaint.convert_utils import create_glb_with_pbr_materials
52
+
53
+
54
+ MAX_SEED = 1e7
55
+ ENV = "Huggingface" # "Huggingface"
56
+ if ENV == 'Huggingface':
57
+ """
58
+ Setup environment for running on Huggingface platform.
59
+
60
+ This block performs the following:
61
+ - Changes directory to the differentiable renderer folder and runs a shell
62
+ script to compile the mesh painter.
63
+ - Installs a custom rasterizer wheel package via pip.
64
+
65
+ Note:
66
+ This setup assumes the script is running in the Huggingface environment
67
+ with the specified directory structure.
68
+ """
69
+ import os, spaces, subprocess, sys, shlex
70
+ from spaces import zero
71
+
72
+ def install_cuda_toolkit():
73
+ # CUDA_TOOLKIT_URL = "https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run"
74
+ CUDA_TOOLKIT_URL = "https://developer.download.nvidia.com/compute/cuda/12.2.0/local_installers/cuda_12.2.0_535.54.03_linux.run"
75
+ CUDA_TOOLKIT_FILE = "/tmp/%s" % os.path.basename(CUDA_TOOLKIT_URL)
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"
88
+
89
+ def prepare_env():
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
+
123
+ # print(torch.__version__)
124
+ # install_cuda_toolkit()
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.
132
+
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
+ """
145
+ Load and return a sorted list of example image file paths.
146
+
147
+ Searches recursively for PNG images under the './assets/example_images/' directory.
148
+
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():
157
+ """
158
+ Load and return a list of example text prompts.
159
+
160
+ Reads lines from the './assets/example_prompts.txt' file, stripping whitespace.
161
+
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
+
171
+
172
+ def gen_save_folder(max_size=200):
173
+ """
174
+ Generate a new save folder inside SAVE_DIR, maintaining a maximum number of folders.
175
+
176
+ If the number of existing folders in SAVE_DIR exceeds `max_size`, the oldest folder is removed.
177
+
178
+ Args:
179
+ max_size (int, optional): Maximum number of folders to keep in SAVE_DIR. Defaults to 200.
180
+
181
+ Returns:
182
+ str: Path to the newly created save folder.
183
+ """
184
+ os.makedirs(SAVE_DIR, exist_ok=True)
185
+ dirs = [f for f in Path(SAVE_DIR).iterdir() if f.is_dir()]
186
+ if len(dirs) >= max_size:
187
+ oldest_dir = min(dirs, key=lambda x: x.stat().st_ctime)
188
+ shutil.rmtree(oldest_dir)
189
+ print(f"Removed the oldest folder: {oldest_dir}")
190
+ new_folder = os.path.join(SAVE_DIR, str(uuid.uuid4()))
191
+ os.makedirs(new_folder, exist_ok=True)
192
+ print(f"Created new folder: {new_folder}")
193
+ return new_folder
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
+
201
+ Args:
202
+ mesh (trimesh.Trimesh): The mesh object to export.
203
+ save_folder (str): Directory path where the mesh file will be saved.
204
+ textured (bool, optional): Whether to include textures/normals in the export. Defaults to False.
205
+ type (str, optional): File format to export ('glb' or 'obj' supported). Defaults to 'glb'.
206
+
207
+ Returns:
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:
235
+ if randomize_seed:
236
+ seed = random.randint(0, MAX_SEED)
237
+ return seed
238
+
239
+
240
+ 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%;'>
268
+ {iframe_tag}
269
+ </div>
270
+ """
271
+
272
+ @spaces.GPU(duration=60)
273
+ def _gen_shape(
274
+ caption=None,
275
+ image=None,
276
+ mv_image_front=None,
277
+ mv_image_back=None,
278
+ mv_image_left=None,
279
+ mv_image_right=None,
280
+ steps=50,
281
+ guidance_scale=7.5,
282
+ seed=1234,
283
+ octree_resolution=256,
284
+ check_box_rembg=False,
285
+ num_chunks=200000,
286
+ randomize_seed: bool = False,
287
+ ):
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
+
326
+ if image is None:
327
+ start_time = time.time()
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'))
337
+ if MV_MODE:
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'))
352
+
353
+ # image to white model
354
+ start_time = time.time()
355
+
356
+ generator = torch.Generator()
357
+ generator = generator.manual_seed(int(seed))
358
+ outputs = i23d_worker(
359
+ image=image,
360
+ num_inference_steps=steps,
361
+ guidance_scale=guidance_scale,
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,
384
+ image=None,
385
+ mv_image_front=None,
386
+ mv_image_back=None,
387
+ mv_image_left=None,
388
+ mv_image_right=None,
389
+ steps=50,
390
+ guidance_scale=7.5,
391
+ seed=1234,
392
+ octree_resolution=256,
393
+ check_box_rembg=False,
394
+ num_chunks=200000,
395
+ randomize_seed: bool = False,
396
+ ):
397
+ start_time_0 = time.time()
398
+ mesh, image, save_folder, stats, seed = _gen_shape(
399
+ caption,
400
+ image,
401
+ mv_image_front=mv_image_front,
402
+ mv_image_back=mv_image_back,
403
+ mv_image_left=mv_image_left,
404
+ mv_image_right=mv_image_right,
405
+ steps=steps,
406
+ guidance_scale=guidance_scale,
407
+ seed=seed,
408
+ octree_resolution=octree_resolution,
409
+ check_box_rembg=check_box_rembg,
410
+ num_chunks=num_chunks,
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)
421
+ # mesh = degenerate_face_remove_worker(mesh)
422
+ # logger.info("---Postprocessing takes %s seconds ---" % (time.time() - tmp_time))
423
+ # stats['time']['postprocessing'] = time.time() - tmp_time
424
+
425
+ tmp_time = time.time()
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 (
456
+ gr.update(value=path),
457
+ gr.update(value=glb_path_textured),
458
+ model_viewer_html_textured,
459
+ stats,
460
+ seed,
461
+ )
462
+
463
+ @spaces.GPU(duration=60)
464
+ def shape_generation(
465
+ caption=None,
466
+ image=None,
467
+ mv_image_front=None,
468
+ mv_image_back=None,
469
+ mv_image_left=None,
470
+ mv_image_right=None,
471
+ steps=50,
472
+ guidance_scale=7.5,
473
+ seed=1234,
474
+ octree_resolution=256,
475
+ check_box_rembg=False,
476
+ num_chunks=200000,
477
+ randomize_seed: bool = False,
478
+ ):
479
+ start_time_0 = time.time()
480
+ mesh, image, save_folder, stats, seed = _gen_shape(
481
+ caption,
482
+ image,
483
+ mv_image_front=mv_image_front,
484
+ mv_image_back=mv_image_back,
485
+ mv_image_left=mv_image_left,
486
+ mv_image_right=mv_image_right,
487
+ steps=steps,
488
+ guidance_scale=guidance_scale,
489
+ seed=seed,
490
+ octree_resolution=octree_resolution,
491
+ check_box_rembg=check_box_rembg,
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 (
503
+ gr.update(value=path),
504
+ model_viewer_html,
505
+ stats,
506
+ seed,
507
+ )
508
+
509
+
510
+ def build_app():
511
+ title = 'Hunyuan3D-2: High Resolution Textured 3D Assets Generation'
512
+ if MV_MODE:
513
+ title = 'Hunyuan3D-2mv: Image to 3D Generation with 1-4 Views'
514
+ if 'mini' in args.subfolder:
515
+ title = 'Hunyuan3D-2mini: Strong 0.6B Image to Shape Generator'
516
+
517
+ title = 'Hunyuan-3D-2.1'
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">
524
+
525
+ {title}
526
+ </div>
527
+ <div align="center">
528
+ Tencent Hunyuan3D Team
529
+ </div>
530
+ """
531
+ custom_css = """
532
+ .app.svelte-wpkpf6.svelte-wpkpf6:not(.fill_width) {
533
+ max-width: 1480px;
534
+ }
535
+ .mv-image button .wrap {
536
+ font-size: 10px;
537
+ }
538
+
539
+ .mv-image .icon-wrap {
540
+ width: 20px;
541
+ }
542
+
543
+ """
544
+
545
+ with gr.Blocks(theme=gr.themes.Base(), title='Hunyuan-3D-2.1', 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,
608
+ maximum=MAX_SEED,
609
+ step=1,
610
+ value=1234,
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(
665
+ shape_generation,
666
+ inputs=[
667
+ caption,
668
+ image,
669
+ mv_image_front,
670
+ mv_image_back,
671
+ mv_image_left,
672
+ mv_image_right,
673
+ num_steps,
674
+ cfg_scale,
675
+ seed,
676
+ octree_resolution,
677
+ check_box_rembg,
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
+
691
+ btn_all.click(
692
+ generation_all,
693
+ inputs=[
694
+ caption,
695
+ image,
696
+ mv_image_front,
697
+ mv_image_back,
698
+ mv_image_left,
699
+ mv_image_right,
700
+ num_steps,
701
+ cfg_scale,
702
+ seed,
703
+ octree_resolution,
704
+ check_box_rembg,
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)
725
+
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()
749
+ path = export_mesh(mesh, save_folder, textured=True, type=file_type)
750
+
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)
761
+ mesh = degenerate_face_remove_worker(mesh)
762
+ if reduce_face:
763
+ mesh = face_reduce_worker(mesh, target_face_num)
764
+ save_folder = gen_save_folder()
765
+ path = export_mesh(mesh, save_folder, textured=False, type=file_type)
766
+
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
+
809
+ SAVE_DIR = args.cache_path
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
818
+ HTML_OUTPUT_PLACEHOLDER = f"""
819
+ <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;'>
820
+ <div style='text-align: center; font-size: 16px; color: #6b7280;'>
821
+ <p style="color: #8d8d8d;">Welcome to Hunyuan3D!</p>
822
+ <p style="color: #8d8d8d;">No mesh here.</p>
823
+ </div>
824
+ </div>
825
+ """
826
+
827
+ INPUT_MESH_HTML = """
828
+ <div style='height: 490px; width: 100%; border-radius: 8px;
829
+ border-color: #e5e7eb; order-style: solid; border-width: 1px;'>
830
+ </div>
831
+ """
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
878
+ # if args.enable_t23d:
879
+ # from hy3dgen.text2image import HunyuanDiTPipeline
880
+
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
+
889
+ rmbg_worker = BackgroundRemover()
890
+ i23d_worker = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(
891
+ args.model_path,
892
+ subfolder=args.subfolder,
893
+ use_safetensors=False,
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()
901
+
902
+ floater_remove_worker = FloaterRemover()
903
+ degenerate_face_remove_worker = DegenerateFaceRemover()
904
+ face_reduce_worker = FaceReducer()
905
+
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)
gradio_app.py CHANGED
@@ -68,8 +68,7 @@ if ENV == 'Huggingface':
68
 
69
  print("Installing custom_rasterizer...")
70
  subprocess.run(
71
- "cd ./hy3dpaint/custom_rasterizer && pip install -e .",
72
- shell=True,
73
  check=True
74
  )
75
 
 
68
 
69
  print("Installing custom_rasterizer...")
70
  subprocess.run(
71
+ shlex.split("pip install custom_rasterizer-0.1-cp310-cp310-linux_x86_64.whl"),
 
72
  check=True
73
  )
74