Huiwenshi commited on
Commit
4defbc8
·
verified ·
1 Parent(s): 76d0f59

Update gradio_app.py

Browse files
Files changed (1) hide show
  1. gradio_app.py +1 -928
gradio_app.py CHANGED
@@ -920,934 +920,7 @@ if __name__ == '__main__':
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)
928
- # Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT
929
- # except for the third-party components listed below.
930
- # Hunyuan 3D does not impose any additional limitations beyond what is outlined
931
- # in the repsective licenses of these third-party components.
932
- # Users must comply with all terms and conditions of original licenses of these third-party
933
- # components and must ensure that the usage of the third party components adheres to
934
- # all relevant laws and regulations.
935
-
936
- # For avoidance of doubts, Hunyuan 3D means the large language models and
937
- # their software and algorithms, including trained model weights, parameters (including
938
- # optimizer states), machine-learning model code, inference-enabling code, training-enabling code,
939
- # fine-tuning enabling code and other elements of the foregoing made publicly available
940
- # by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.
941
-
942
- # Apply torchvision compatibility fix before other imports
943
-
944
- import sys
945
- sys.path.insert(0, './hy3dshape')
946
- sys.path.insert(0, './hy3dpaint')
947
-
948
- pythonpath = sys.executable
949
- print(pythonpath)
950
-
951
- try:
952
- from torchvision_fix import apply_fix
953
- apply_fix()
954
- except ImportError:
955
- print("Warning: torchvision_fix module not found, proceeding without compatibility fix")
956
- except Exception as e:
957
- print(f"Warning: Failed to apply torchvision fix: {e}")
958
-
959
-
960
- import os
961
- import random
962
- import shutil
963
- import subprocess
964
- import time
965
- from glob import glob
966
- from pathlib import Path
967
-
968
- import gradio as gr
969
- import torch
970
- import trimesh
971
- import uvicorn
972
- from fastapi import FastAPI
973
- from fastapi.staticfiles import StaticFiles
974
- import uuid
975
- import numpy as np
976
-
977
- from hy3dshape.utils import logger
978
- from hy3dpaint.convert_utils import create_glb_with_pbr_materials
979
-
980
-
981
- MAX_SEED = 1e7
982
- ENV = "Huggingface" # "Huggingface"
983
- if ENV == 'Huggingface':
984
- """
985
- Setup environment for running on Huggingface platform.
986
-
987
- This block performs the following:
988
- - Changes directory to the differentiable renderer folder and runs a shell
989
- script to compile the mesh painter.
990
- - Installs a custom rasterizer wheel package via pip.
991
-
992
- Note:
993
- This setup assumes the script is running in the Huggingface environment
994
- with the specified directory structure.
995
- """
996
- import os, spaces, subprocess, sys, shlex
997
- from spaces import zero
998
-
999
- def install_cuda_toolkit():
1000
- # CUDA_TOOLKIT_URL = "https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run"
1001
- CUDA_TOOLKIT_URL = "https://developer.download.nvidia.com/compute/cuda/12.2.0/local_installers/cuda_12.2.0_535.54.03_linux.run"
1002
- CUDA_TOOLKIT_FILE = "/tmp/%s" % os.path.basename(CUDA_TOOLKIT_URL)
1003
- subprocess.call(["wget", "-q", CUDA_TOOLKIT_URL, "-O", CUDA_TOOLKIT_FILE])
1004
- subprocess.call(["chmod", "+x", CUDA_TOOLKIT_FILE])
1005
- subprocess.call([CUDA_TOOLKIT_FILE, "--silent", "--toolkit"])
1006
-
1007
- os.environ["CUDA_HOME"] = "/usr/local/cuda"
1008
- os.environ["PATH"] = "%s/bin:%s" % (os.environ["CUDA_HOME"], os.environ["PATH"])
1009
- os.environ["LD_LIBRARY_PATH"] = "%s/lib:%s" % (
1010
- os.environ["CUDA_HOME"],
1011
- "" if "LD_LIBRARY_PATH" not in os.environ else os.environ["LD_LIBRARY_PATH"],
1012
- )
1013
- # Fix: arch_list[-1] += '+PTX'; IndexError: list index out of range
1014
- os.environ["TORCH_CUDA_ARCH_LIST"] = "8.0;8.6"
1015
-
1016
- def prepare_env():
1017
- # print('install custom')
1018
- # os.system(f"cd /home/user/app/hy3dpaint/custom_rasterizer && {pythonpath} -m pip install -e .")
1019
- # os.system(f"cd /home/user/app/hy3dpaint/packages/custom_rasterizer && pip install -e .")
1020
- subprocess.run(shlex.split("pip install custom_rasterizer-0.1-cp310-cp310-linux_x86_64.whl"), check=True)
1021
-
1022
- print("cd /home/user/app/hy3dpaint/differentiable_renderer/ && bash compile_mesh_painter.sh")
1023
- os.system("cd /home/user/app/hy3dpaint/DifferentiableRenderer && bash compile_mesh_painter.sh")
1024
- # print("wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P /home/user/app/hy3dpaint/ckpt")
1025
- # os.system("wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P /home/user/app/hy3dpaint/ckpt")
1026
-
1027
- def check():
1028
- import custom_rasterizer
1029
- print(type(custom_rasterizer))
1030
- print(dir(custom_rasterizer))
1031
- print(getattr(custom_rasterizer, '__file__', None))
1032
-
1033
- package_dir = None
1034
- if hasattr(custom_rasterizer, '__file__') and custom_rasterizer.__file__:
1035
- package_dir = os.path.dirname(custom_rasterizer.__file__)
1036
- elif hasattr(custom_rasterizer, '__path__'):
1037
- package_dir = list(custom_rasterizer.__path__)[0]
1038
- else:
1039
- raise RuntimeError("Cannot determine package path")
1040
- print(package_dir)
1041
-
1042
- for root, dirs, files in os.walk(package_dir):
1043
- level = root.replace(package_dir, '').count(os.sep)
1044
- indent = ' ' * 4 * level
1045
- print(f"{indent}{os.path.basename(root)}/")
1046
- subindent = ' ' * 4 * (level + 1)
1047
- for f in files:
1048
- print(f"{subindent}{f}")
1049
-
1050
- # print(torch.__version__)
1051
- # install_cuda_toolkit()
1052
- print(torch.__version__)
1053
- prepare_env()
1054
- check()
1055
-
1056
- else:
1057
- """
1058
- Define a dummy `spaces` module with a GPU decorator class for local environment.
1059
-
1060
- The GPU decorator is a no-op that simply returns the decorated function unchanged.
1061
- This allows code that uses the `spaces.GPU` decorator to run without modification locally.
1062
- """
1063
- class spaces:
1064
- class GPU:
1065
- def __init__(self, duration=60):
1066
- self.duration = duration
1067
- def __call__(self, func):
1068
- return func
1069
-
1070
- def get_example_img_list():
1071
- """
1072
- Load and return a sorted list of example image file paths.
1073
-
1074
- Searches recursively for PNG images under the './assets/example_images/' directory.
1075
-
1076
- Returns:
1077
- list[str]: Sorted list of file paths to example PNG images.
1078
- """
1079
- print('Loading example img list ...')
1080
- return sorted(glob('./assets/example_images/**/*.png', recursive=True))
1081
-
1082
-
1083
- def get_example_txt_list():
1084
- """
1085
- Load and return a list of example text prompts.
1086
-
1087
- Reads lines from the './assets/example_prompts.txt' file, stripping whitespace.
1088
-
1089
- Returns:
1090
- list[str]: List of example text prompts.
1091
- """
1092
- print('Loading example txt list ...')
1093
- txt_list = list()
1094
- for line in open('./assets/example_prompts.txt', encoding='utf-8'):
1095
- txt_list.append(line.strip())
1096
- return txt_list
1097
-
1098
-
1099
- def gen_save_folder(max_size=200):
1100
- """
1101
- Generate a new save folder inside SAVE_DIR, maintaining a maximum number of folders.
1102
-
1103
- If the number of existing folders in SAVE_DIR exceeds `max_size`, the oldest folder is removed.
1104
-
1105
- Args:
1106
- max_size (int, optional): Maximum number of folders to keep in SAVE_DIR. Defaults to 200.
1107
-
1108
- Returns:
1109
- str: Path to the newly created save folder.
1110
- """
1111
- os.makedirs(SAVE_DIR, exist_ok=True)
1112
- dirs = [f for f in Path(SAVE_DIR).iterdir() if f.is_dir()]
1113
- if len(dirs) >= max_size:
1114
- oldest_dir = min(dirs, key=lambda x: x.stat().st_ctime)
1115
- shutil.rmtree(oldest_dir)
1116
- print(f"Removed the oldest folder: {oldest_dir}")
1117
- new_folder = os.path.join(SAVE_DIR, str(uuid.uuid4()))
1118
- os.makedirs(new_folder, exist_ok=True)
1119
- print(f"Created new folder: {new_folder}")
1120
- return new_folder
1121
-
1122
-
1123
- # Removed complex PBR conversion functions - using simple trimesh-based conversion
1124
- def export_mesh(mesh, save_folder, textured=False, type='glb'):
1125
- """
1126
- Export a mesh to a file in the specified folder, optionally including textures.
1127
-
1128
- Args:
1129
- mesh (trimesh.Trimesh): The mesh object to export.
1130
- save_folder (str): Directory path where the mesh file will be saved.
1131
- textured (bool, optional): Whether to include textures/normals in the export. Defaults to False.
1132
- type (str, optional): File format to export ('glb' or 'obj' supported). Defaults to 'glb'.
1133
-
1134
- Returns:
1135
- str: The full path to the exported mesh file.
1136
- """
1137
- if textured:
1138
- path = os.path.join(save_folder, f'textured_mesh.{type}')
1139
- else:
1140
- path = os.path.join(save_folder, f'white_mesh.{type}')
1141
- if type not in ['glb', 'obj']:
1142
- mesh.export(path)
1143
- else:
1144
- mesh.export(path, include_normals=textured)
1145
- return path
1146
-
1147
-
1148
-
1149
-
1150
- def quick_convert_with_obj2gltf(obj_path: str, glb_path: str) -> bool:
1151
- # 执行转换
1152
- textures = {
1153
- 'albedo': obj_path.replace('.obj', '.jpg'),
1154
- 'metallic': obj_path.replace('.obj', '_metallic.jpg'),
1155
- 'roughness': obj_path.replace('.obj', '_roughness.jpg')
1156
- }
1157
- create_glb_with_pbr_materials(obj_path, textures, glb_path)
1158
-
1159
-
1160
-
1161
- def randomize_seed_fn(seed: int, randomize_seed: bool) -> int:
1162
- if randomize_seed:
1163
- seed = random.randint(0, MAX_SEED)
1164
- return seed
1165
-
1166
-
1167
- def build_model_viewer_html(save_folder, height=660, width=790, textured=False):
1168
- # Remove first folder from path to make relative path
1169
- if textured:
1170
- related_path = f"./textured_mesh.glb"
1171
- template_name = './assets/modelviewer-textured-template.html'
1172
- output_html_path = os.path.join(save_folder, f'textured_mesh.html')
1173
- else:
1174
- related_path = f"./white_mesh.glb"
1175
- template_name = './assets/modelviewer-template.html'
1176
- output_html_path = os.path.join(save_folder, f'white_mesh.html')
1177
- offset = 50 if textured else 10
1178
- with open(os.path.join(CURRENT_DIR, template_name), 'r', encoding='utf-8') as f:
1179
- template_html = f.read()
1180
-
1181
- with open(output_html_path, 'w', encoding='utf-8') as f:
1182
- template_html = template_html.replace('#height#', f'{height - offset}')
1183
- template_html = template_html.replace('#width#', f'{width}')
1184
- template_html = template_html.replace('#src#', f'{related_path}/')
1185
- f.write(template_html)
1186
-
1187
- rel_path = os.path.relpath(output_html_path, SAVE_DIR)
1188
- iframe_tag = f'<iframe src="/static/{rel_path}" \
1189
- height="{height}" width="100%" frameborder="0"></iframe>'
1190
- print(f'Find html file {output_html_path}, \
1191
- {os.path.exists(output_html_path)}, relative HTML path is /static/{rel_path}')
1192
-
1193
- return f"""
1194
- <div style='height: {height}; width: 100%;'>
1195
- {iframe_tag}
1196
- </div>
1197
- """
1198
-
1199
- @spaces.GPU(duration=50)
1200
- def _gen_shape(
1201
- caption=None,
1202
- image=None,
1203
- mv_image_front=None,
1204
- mv_image_back=None,
1205
- mv_image_left=None,
1206
- mv_image_right=None,
1207
- steps=50,
1208
- guidance_scale=7.5,
1209
- seed=1234,
1210
- octree_resolution=256,
1211
- check_box_rembg=False,
1212
- num_chunks=200000,
1213
- randomize_seed: bool = False,
1214
- ):
1215
- if not MV_MODE and image is None and caption is None:
1216
- raise gr.Error("Please provide either a caption or an image.")
1217
- if MV_MODE:
1218
- if mv_image_front is None and mv_image_back is None \
1219
- and mv_image_left is None and mv_image_right is None:
1220
- raise gr.Error("Please provide at least one view image.")
1221
- image = {}
1222
- if mv_image_front:
1223
- image['front'] = mv_image_front
1224
- if mv_image_back:
1225
- image['back'] = mv_image_back
1226
- if mv_image_left:
1227
- image['left'] = mv_image_left
1228
- if mv_image_right:
1229
- image['right'] = mv_image_right
1230
-
1231
- seed = int(randomize_seed_fn(seed, randomize_seed))
1232
-
1233
- octree_resolution = int(octree_resolution)
1234
- if caption: print('prompt is', caption)
1235
- save_folder = gen_save_folder()
1236
- stats = {
1237
- 'model': {
1238
- 'shapegen': f'{args.model_path}/{args.subfolder}',
1239
- 'texgen': f'{args.texgen_model_path}',
1240
- },
1241
- 'params': {
1242
- 'caption': caption,
1243
- 'steps': steps,
1244
- 'guidance_scale': guidance_scale,
1245
- 'seed': seed,
1246
- 'octree_resolution': octree_resolution,
1247
- 'check_box_rembg': check_box_rembg,
1248
- 'num_chunks': num_chunks,
1249
- }
1250
- }
1251
- time_meta = {}
1252
-
1253
- if image is None:
1254
- start_time = time.time()
1255
- try:
1256
- image = t2i_worker(caption)
1257
- except Exception as e:
1258
- raise gr.Error(f"Text to 3D is disable. \
1259
- Please enable it by `python gradio_app.py --enable_t23d`.")
1260
- time_meta['text2image'] = time.time() - start_time
1261
-
1262
- # remove disk io to make responding faster, uncomment at your will.
1263
- # image.save(os.path.join(save_folder, 'input.png'))
1264
- if MV_MODE:
1265
- start_time = time.time()
1266
- for k, v in image.items():
1267
- if check_box_rembg or v.mode == "RGB":
1268
- img = rmbg_worker(v.convert('RGB'))
1269
- image[k] = img
1270
- time_meta['remove background'] = time.time() - start_time
1271
- else:
1272
- if check_box_rembg or image.mode == "RGB":
1273
- start_time = time.time()
1274
- image = rmbg_worker(image.convert('RGB'))
1275
- time_meta['remove background'] = time.time() - start_time
1276
-
1277
- # remove disk io to make responding faster, uncomment at your will.
1278
- # image.save(os.path.join(save_folder, 'rembg.png'))
1279
-
1280
- # image to white model
1281
- start_time = time.time()
1282
-
1283
- generator = torch.Generator()
1284
- generator = generator.manual_seed(int(seed))
1285
- outputs = i23d_worker(
1286
- image=image,
1287
- num_inference_steps=steps,
1288
- guidance_scale=guidance_scale,
1289
- generator=generator,
1290
- octree_resolution=octree_resolution,
1291
- num_chunks=num_chunks,
1292
- output_type='mesh'
1293
- )
1294
- time_meta['shape generation'] = time.time() - start_time
1295
- logger.info("---Shape generation takes %s seconds ---" % (time.time() - start_time))
1296
-
1297
- tmp_start = time.time()
1298
- mesh = export_to_trimesh(outputs)[0]
1299
- time_meta['export to trimesh'] = time.time() - tmp_start
1300
-
1301
- stats['number_of_faces'] = mesh.faces.shape[0]
1302
- stats['number_of_vertices'] = mesh.vertices.shape[0]
1303
-
1304
- stats['time'] = time_meta
1305
- main_image = image if not MV_MODE else image['front']
1306
- return mesh, main_image, save_folder, stats, seed
1307
-
1308
- @spaces.GPU(duration=110)
1309
- def generation_all(
1310
- caption=None,
1311
- image=None,
1312
- mv_image_front=None,
1313
- mv_image_back=None,
1314
- mv_image_left=None,
1315
- mv_image_right=None,
1316
- steps=50,
1317
- guidance_scale=7.5,
1318
- seed=1234,
1319
- octree_resolution=256,
1320
- check_box_rembg=False,
1321
- num_chunks=200000,
1322
- randomize_seed: bool = False,
1323
- ):
1324
- start_time_0 = time.time()
1325
- mesh, image, save_folder, stats, seed = _gen_shape(
1326
- caption,
1327
- image,
1328
- mv_image_front=mv_image_front,
1329
- mv_image_back=mv_image_back,
1330
- mv_image_left=mv_image_left,
1331
- mv_image_right=mv_image_right,
1332
- steps=steps,
1333
- guidance_scale=guidance_scale,
1334
- seed=seed,
1335
- octree_resolution=octree_resolution,
1336
- check_box_rembg=check_box_rembg,
1337
- num_chunks=num_chunks,
1338
- randomize_seed=randomize_seed,
1339
- )
1340
- path = export_mesh(mesh, save_folder, textured=False)
1341
-
1342
-
1343
- print(path)
1344
- print('='*40)
1345
-
1346
- # tmp_time = time.time()
1347
- # mesh = floater_remove_worker(mesh)
1348
- # mesh = degenerate_face_remove_worker(mesh)
1349
- # logger.info("---Postprocessing takes %s seconds ---" % (time.time() - tmp_time))
1350
- # stats['time']['postprocessing'] = time.time() - tmp_time
1351
-
1352
- tmp_time = time.time()
1353
- mesh = face_reduce_worker(mesh)
1354
-
1355
- # path = export_mesh(mesh, save_folder, textured=False, type='glb')
1356
- path = export_mesh(mesh, save_folder, textured=False, type='obj') # 这样操作也会 core dump
1357
-
1358
- logger.info("---Face Reduction takes %s seconds ---" % (time.time() - tmp_time))
1359
- stats['time']['face reduction'] = time.time() - tmp_time
1360
-
1361
- tmp_time = time.time()
1362
-
1363
- text_path = os.path.join(save_folder, f'textured_mesh.obj')
1364
- path_textured = tex_pipeline(mesh_path=path, image_path=image, output_mesh_path=text_path, save_glb=False)
1365
-
1366
- logger.info("---Texture Generation takes %s seconds ---" % (time.time() - tmp_time))
1367
- stats['time']['texture generation'] = time.time() - tmp_time
1368
-
1369
- tmp_time = time.time()
1370
- # Convert textured OBJ to GLB using obj2gltf with PBR support
1371
- glb_path_textured = os.path.join(save_folder, 'textured_mesh.glb')
1372
- conversion_success = quick_convert_with_obj2gltf(path_textured, glb_path_textured)
1373
-
1374
- logger.info("---Convert textured OBJ to GLB takes %s seconds ---" % (time.time() - tmp_time))
1375
- stats['time']['convert textured OBJ to GLB'] = time.time() - tmp_time
1376
- stats['time']['total'] = time.time() - start_time_0
1377
- model_viewer_html_textured = build_model_viewer_html(save_folder,
1378
- height=HTML_HEIGHT,
1379
- width=HTML_WIDTH, textured=True)
1380
- if args.low_vram_mode:
1381
- torch.cuda.empty_cache()
1382
- return (
1383
- gr.update(value=path),
1384
- gr.update(value=glb_path_textured),
1385
- model_viewer_html_textured,
1386
- stats,
1387
- seed,
1388
- )
1389
-
1390
- @spaces.GPU(duration=60)
1391
- def shape_generation(
1392
- caption=None,
1393
- image=None,
1394
- mv_image_front=None,
1395
- mv_image_back=None,
1396
- mv_image_left=None,
1397
- mv_image_right=None,
1398
- steps=50,
1399
- guidance_scale=7.5,
1400
- seed=1234,
1401
- octree_resolution=256,
1402
- check_box_rembg=False,
1403
- num_chunks=200000,
1404
- randomize_seed: bool = False,
1405
- ):
1406
- start_time_0 = time.time()
1407
- mesh, image, save_folder, stats, seed = _gen_shape(
1408
- caption,
1409
- image,
1410
- mv_image_front=mv_image_front,
1411
- mv_image_back=mv_image_back,
1412
- mv_image_left=mv_image_left,
1413
- mv_image_right=mv_image_right,
1414
- steps=steps,
1415
- guidance_scale=guidance_scale,
1416
- seed=seed,
1417
- octree_resolution=octree_resolution,
1418
- check_box_rembg=check_box_rembg,
1419
- num_chunks=num_chunks,
1420
- randomize_seed=randomize_seed,
1421
- )
1422
- stats['time']['total'] = time.time() - start_time_0
1423
- mesh.metadata['extras'] = stats
1424
-
1425
- path = export_mesh(mesh, save_folder, textured=False)
1426
- model_viewer_html = build_model_viewer_html(save_folder, height=HTML_HEIGHT, width=HTML_WIDTH)
1427
- if args.low_vram_mode:
1428
- torch.cuda.empty_cache()
1429
- return (
1430
- gr.update(value=path),
1431
- model_viewer_html,
1432
- stats,
1433
- seed,
1434
- )
1435
-
1436
-
1437
- def build_app():
1438
- title = 'Hunyuan3D-2: High Resolution Textured 3D Assets Generation'
1439
- if MV_MODE:
1440
- title = 'Hunyuan3D-2mv: Image to 3D Generation with 1-4 Views'
1441
- if 'mini' in args.subfolder:
1442
- title = 'Hunyuan3D-2mini: Strong 0.6B Image to Shape Generator'
1443
-
1444
- title = 'Hunyuan-3D-2.1'
1445
-
1446
- if TURBO_MODE:
1447
- title = title.replace(':', '-Turbo: Fast ')
1448
-
1449
- title_html = f"""
1450
- <div style="font-size: 2em; font-weight: bold; text-align: center; margin-bottom: 5px">
1451
-
1452
- {title}
1453
- </div>
1454
- <div align="center">
1455
- Tencent Hunyuan3D Team
1456
- </div>
1457
- """
1458
- custom_css = """
1459
- .app.svelte-wpkpf6.svelte-wpkpf6:not(.fill_width) {
1460
- max-width: 1480px;
1461
- }
1462
- .mv-image button .wrap {
1463
- font-size: 10px;
1464
- }
1465
-
1466
- .mv-image .icon-wrap {
1467
- width: 20px;
1468
- }
1469
-
1470
- """
1471
-
1472
- with gr.Blocks(theme=gr.themes.Base(), title='Hunyuan-3D-2.1', analytics_enabled=False, css=custom_css) as demo:
1473
- gr.HTML(title_html)
1474
-
1475
- with gr.Row():
1476
- with gr.Column(scale=3):
1477
- with gr.Tabs(selected='tab_img_prompt') as tabs_prompt:
1478
- with gr.Tab('Image Prompt', id='tab_img_prompt', visible=not MV_MODE) as tab_ip:
1479
- image = gr.Image(label='Image', type='pil', image_mode='RGBA', height=290)
1480
- caption = gr.State(None)
1481
- # with gr.Tab('Text Prompt', id='tab_txt_prompt', visible=HAS_T2I and not MV_MODE) as tab_tp:
1482
- # caption = gr.Textbox(label='Text Prompt',
1483
- # placeholder='HunyuanDiT will be used to generate image.',
1484
- # info='Example: A 3D model of a cute cat, white background')
1485
- with gr.Tab('MultiView Prompt', visible=MV_MODE) as tab_mv:
1486
- # gr.Label('Please upload at least one front image.')
1487
- with gr.Row():
1488
- mv_image_front = gr.Image(label='Front', type='pil', image_mode='RGBA', height=140,
1489
- min_width=100, elem_classes='mv-image')
1490
- mv_image_back = gr.Image(label='Back', type='pil', image_mode='RGBA', height=140,
1491
- min_width=100, elem_classes='mv-image')
1492
- with gr.Row():
1493
- mv_image_left = gr.Image(label='Left', type='pil', image_mode='RGBA', height=140,
1494
- min_width=100, elem_classes='mv-image')
1495
- mv_image_right = gr.Image(label='Right', type='pil', image_mode='RGBA', height=140,
1496
- min_width=100, elem_classes='mv-image')
1497
-
1498
- with gr.Row():
1499
- btn = gr.Button(value='Gen Shape', variant='primary', min_width=100)
1500
- btn_all = gr.Button(value='Gen Textured Shape',
1501
- variant='primary',
1502
- visible=HAS_TEXTUREGEN,
1503
- min_width=100)
1504
-
1505
- with gr.Group():
1506
- file_out = gr.File(label="File", visible=False)
1507
- file_out2 = gr.File(label="File", visible=False)
1508
-
1509
- with gr.Tabs(selected='tab_options' if TURBO_MODE else 'tab_export'):
1510
- with gr.Tab("Options", id='tab_options', visible=TURBO_MODE):
1511
- gen_mode = gr.Radio(
1512
- label='Generation Mode',
1513
- info='Recommendation: Turbo for most cases, \
1514
- Fast for very complex cases, Standard seldom use.',
1515
- choices=['Turbo', 'Fast', 'Standard'],
1516
- value='Turbo')
1517
- decode_mode = gr.Radio(
1518
- label='Decoding Mode',
1519
- info='The resolution for exporting mesh from generated vectset',
1520
- choices=['Low', 'Standard', 'High'],
1521
- value='Standard')
1522
- with gr.Tab('Advanced Options', id='tab_advanced_options'):
1523
- with gr.Row():
1524
- check_box_rembg = gr.Checkbox(
1525
- value=True,
1526
- label='Remove Background',
1527
- min_width=100)
1528
- randomize_seed = gr.Checkbox(
1529
- label="Randomize seed",
1530
- value=True,
1531
- min_width=100)
1532
- seed = gr.Slider(
1533
- label="Seed",
1534
- minimum=0,
1535
- maximum=MAX_SEED,
1536
- step=1,
1537
- value=1234,
1538
- min_width=100,
1539
- )
1540
- with gr.Row():
1541
- num_steps = gr.Slider(maximum=100,
1542
- minimum=1,
1543
- value=5 if 'turbo' in args.subfolder else 30,
1544
- step=1, label='Inference Steps')
1545
- octree_resolution = gr.Slider(maximum=512,
1546
- minimum=16,
1547
- value=256,
1548
- label='Octree Resolution')
1549
- with gr.Row():
1550
- cfg_scale = gr.Number(value=5.0, label='Guidance Scale', min_width=100)
1551
- num_chunks = gr.Slider(maximum=5000000, minimum=1000, value=8000,
1552
- label='Number of Chunks', min_width=100)
1553
- with gr.Tab("Export", id='tab_export'):
1554
- with gr.Row():
1555
- file_type = gr.Dropdown(label='File Type',
1556
- choices=SUPPORTED_FORMATS,
1557
- value='glb', min_width=100)
1558
- reduce_face = gr.Checkbox(label='Simplify Mesh',
1559
- value=False, min_width=100)
1560
- export_texture = gr.Checkbox(label='Include Texture', value=False,
1561
- visible=False, min_width=100)
1562
- target_face_num = gr.Slider(maximum=1000000, minimum=100, value=10000,
1563
- label='Target Face Number')
1564
- with gr.Row():
1565
- confirm_export = gr.Button(value="Transform", min_width=100)
1566
- file_export = gr.DownloadButton(label="Download", variant='primary',
1567
- interactive=False, min_width=100)
1568
-
1569
- with gr.Column(scale=6):
1570
- with gr.Tabs(selected='gen_mesh_panel') as tabs_output:
1571
- with gr.Tab('Generated Mesh', id='gen_mesh_panel'):
1572
- html_gen_mesh = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output')
1573
- with gr.Tab('Exporting Mesh', id='export_mesh_panel'):
1574
- html_export_mesh = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output')
1575
- with gr.Tab('Mesh Statistic', id='stats_panel'):
1576
- stats = gr.Json({}, label='Mesh Stats')
1577
-
1578
- with gr.Column(scale=3 if MV_MODE else 2):
1579
- with gr.Tabs(selected='tab_img_gallery') as gallery:
1580
- with gr.Tab('Image to 3D Gallery',
1581
- id='tab_img_gallery',
1582
- visible=not MV_MODE) as tab_gi:
1583
- with gr.Row():
1584
- gr.Examples(examples=example_is, inputs=[image],
1585
- label=None, examples_per_page=18)
1586
-
1587
- tab_ip.select(fn=lambda: gr.update(selected='tab_img_gallery'), outputs=gallery)
1588
- #if HAS_T2I:
1589
- # tab_tp.select(fn=lambda: gr.update(selected='tab_txt_gallery'), outputs=gallery)
1590
-
1591
- btn.click(
1592
- shape_generation,
1593
- inputs=[
1594
- caption,
1595
- image,
1596
- mv_image_front,
1597
- mv_image_back,
1598
- mv_image_left,
1599
- mv_image_right,
1600
- num_steps,
1601
- cfg_scale,
1602
- seed,
1603
- octree_resolution,
1604
- check_box_rembg,
1605
- num_chunks,
1606
- randomize_seed,
1607
- ],
1608
- outputs=[file_out, html_gen_mesh, stats, seed]
1609
- ).then(
1610
- lambda: (gr.update(visible=False, value=False), gr.update(interactive=True), gr.update(interactive=True),
1611
- gr.update(interactive=False)),
1612
- outputs=[export_texture, reduce_face, confirm_export, file_export],
1613
- ).then(
1614
- lambda: gr.update(selected='gen_mesh_panel'),
1615
- outputs=[tabs_output],
1616
- )
1617
-
1618
- btn_all.click(
1619
- generation_all,
1620
- inputs=[
1621
- caption,
1622
- image,
1623
- mv_image_front,
1624
- mv_image_back,
1625
- mv_image_left,
1626
- mv_image_right,
1627
- num_steps,
1628
- cfg_scale,
1629
- seed,
1630
- octree_resolution,
1631
- check_box_rembg,
1632
- num_chunks,
1633
- randomize_seed,
1634
- ],
1635
- outputs=[file_out, file_out2, html_gen_mesh, stats, seed]
1636
- ).then(
1637
- lambda: (gr.update(visible=True, value=True), gr.update(interactive=False), gr.update(interactive=True),
1638
- gr.update(interactive=False)),
1639
- outputs=[export_texture, reduce_face, confirm_export, file_export],
1640
- ).then(
1641
- lambda: gr.update(selected='gen_mesh_panel'),
1642
- outputs=[tabs_output],
1643
- )
1644
-
1645
- def on_gen_mode_change(value):
1646
- if value == 'Turbo':
1647
- return gr.update(value=5)
1648
- elif value == 'Fast':
1649
- return gr.update(value=10)
1650
- else:
1651
- return gr.update(value=30)
1652
-
1653
- gen_mode.change(on_gen_mode_change, inputs=[gen_mode], outputs=[num_steps])
1654
-
1655
- def on_decode_mode_change(value):
1656
- if value == 'Low':
1657
- return gr.update(value=196)
1658
- elif value == 'Standard':
1659
- return gr.update(value=256)
1660
- else:
1661
- return gr.update(value=384)
1662
-
1663
- decode_mode.change(on_decode_mode_change, inputs=[decode_mode],
1664
- outputs=[octree_resolution])
1665
-
1666
- def on_export_click(file_out, file_out2, file_type,
1667
- reduce_face, export_texture, target_face_num):
1668
- if file_out is None:
1669
- raise gr.Error('Please generate a mesh first.')
1670
-
1671
- print(f'exporting {file_out}')
1672
- print(f'reduce face to {target_face_num}')
1673
- if export_texture:
1674
- mesh = trimesh.load(file_out2)
1675
- save_folder = gen_save_folder()
1676
- path = export_mesh(mesh, save_folder, textured=True, type=file_type)
1677
-
1678
- # for preview
1679
- save_folder = gen_save_folder()
1680
- _ = export_mesh(mesh, save_folder, textured=True)
1681
- model_viewer_html = build_model_viewer_html(save_folder,
1682
- height=HTML_HEIGHT,
1683
- width=HTML_WIDTH,
1684
- textured=True)
1685
- else:
1686
- mesh = trimesh.load(file_out)
1687
- mesh = floater_remove_worker(mesh)
1688
- mesh = degenerate_face_remove_worker(mesh)
1689
- if reduce_face:
1690
- mesh = face_reduce_worker(mesh, target_face_num)
1691
- save_folder = gen_save_folder()
1692
- path = export_mesh(mesh, save_folder, textured=False, type=file_type)
1693
-
1694
- # for preview
1695
- save_folder = gen_save_folder()
1696
- _ = export_mesh(mesh, save_folder, textured=False)
1697
- model_viewer_html = build_model_viewer_html(save_folder,
1698
- height=HTML_HEIGHT,
1699
- width=HTML_WIDTH,
1700
- textured=False)
1701
- print(f'export to {path}')
1702
- return model_viewer_html, gr.update(value=path, interactive=True)
1703
-
1704
- confirm_export.click(
1705
- lambda: gr.update(selected='export_mesh_panel'),
1706
- outputs=[tabs_output],
1707
- ).then(
1708
- on_export_click,
1709
- inputs=[file_out, file_out2, file_type, reduce_face, export_texture, target_face_num],
1710
- outputs=[html_export_mesh, file_export]
1711
- )
1712
-
1713
- return demo
1714
-
1715
-
1716
- if __name__ == '__main__':
1717
- import argparse
1718
-
1719
- parser = argparse.ArgumentParser()
1720
- parser.add_argument("--model_path", type=str, default='tencent/Hunyuan3D-2.1')
1721
- parser.add_argument("--subfolder", type=str, default='hunyuan3d-dit-v2-1')
1722
- parser.add_argument("--texgen_model_path", type=str, default='tencent/Hunyuan3D-2.1')
1723
- parser.add_argument('--port', type=int, default=7860)
1724
- parser.add_argument('--host', type=str, default='0.0.0.0')
1725
- parser.add_argument('--device', type=str, default='cuda')
1726
- parser.add_argument('--mc_algo', type=str, default='mc')
1727
- parser.add_argument('--cache-path', type=str, default='/root/save_dir')
1728
- parser.add_argument('--enable_t23d', action='store_true')
1729
- parser.add_argument('--disable_tex', action='store_true')
1730
- parser.add_argument('--enable_flashvdm', action='store_true')
1731
- parser.add_argument('--compile', action='store_true')
1732
- parser.add_argument('--low_vram_mode', action='store_true')
1733
- args = parser.parse_args()
1734
- args.enable_flashvdm = False
1735
-
1736
- SAVE_DIR = args.cache_path
1737
- os.makedirs(SAVE_DIR, exist_ok=True)
1738
-
1739
- CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
1740
- MV_MODE = 'mv' in args.model_path
1741
- TURBO_MODE = 'turbo' in args.subfolder
1742
-
1743
- HTML_HEIGHT = 690 if MV_MODE else 650
1744
- HTML_WIDTH = 500
1745
- HTML_OUTPUT_PLACEHOLDER = f"""
1746
- <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;'>
1747
- <div style='text-align: center; font-size: 16px; color: #6b7280;'>
1748
- <p style="color: #8d8d8d;">Welcome to Hunyuan3D!</p>
1749
- <p style="color: #8d8d8d;">No mesh here.</p>
1750
- </div>
1751
- </div>
1752
- """
1753
-
1754
- INPUT_MESH_HTML = """
1755
- <div style='height: 490px; width: 100%; border-radius: 8px;
1756
- border-color: #e5e7eb; order-style: solid; border-width: 1px;'>
1757
- </div>
1758
- """
1759
- example_is = get_example_img_list()
1760
- example_ts = get_example_txt_list()
1761
-
1762
- SUPPORTED_FORMATS = ['glb', 'obj', 'ply', 'stl']
1763
-
1764
- HAS_TEXTUREGEN = False
1765
- if not args.disable_tex:
1766
- try:
1767
- # Apply torchvision fix before importing basicsr/RealESRGAN
1768
- print("Applying torchvision compatibility fix for texture generation...")
1769
- try:
1770
- from torchvision_fix import apply_fix
1771
- fix_result = apply_fix()
1772
- if not fix_result:
1773
- print("Warning: Torchvision fix may not have been applied successfully")
1774
- except Exception as fix_error:
1775
- print(f"Warning: Failed to apply torchvision fix: {fix_error}")
1776
-
1777
- # from hy3dgen.texgen import Hunyuan3DPaintPipeline
1778
- # texgen_worker = Hunyuan3DPaintPipeline.from_pretrained(args.texgen_model_path)
1779
- # if args.low_vram_mode:
1780
- # texgen_worker.enable_model_cpu_offload()
1781
-
1782
- from hy3dpaint.textureGenPipeline import Hunyuan3DPaintPipeline, Hunyuan3DPaintConfig
1783
- conf = Hunyuan3DPaintConfig(max_num_view=8, resolution=768)
1784
- conf.realesrgan_ckpt_path = "hy3dpaint/ckpt/RealESRGAN_x4plus.pth"
1785
- conf.multiview_cfg_path = "hy3dpaint/cfgs/hunyuan-paint-pbr.yaml"
1786
- conf.custom_pipeline = "hy3dpaint/hunyuanpaintpbr"
1787
- tex_pipeline = Hunyuan3DPaintPipeline(conf)
1788
-
1789
- # Not help much, ignore for now.
1790
- # if args.compile:
1791
- # texgen_worker.models['delight_model'].pipeline.unet.compile()
1792
- # texgen_worker.models['delight_model'].pipeline.vae.compile()
1793
- # texgen_worker.models['multiview_model'].pipeline.unet.compile()
1794
- # texgen_worker.models['multiview_model'].pipeline.vae.compile()
1795
-
1796
- HAS_TEXTUREGEN = True
1797
-
1798
- except Exception as e:
1799
- print(f"Error loading texture generator: {e}")
1800
- print("Failed to load texture generator.")
1801
- print('Please try to install requirements by following README.md')
1802
- HAS_TEXTUREGEN = False
1803
-
1804
- # HAS_T2I = True
1805
- # if args.enable_t23d:
1806
- # from hy3dgen.text2image import HunyuanDiTPipeline
1807
-
1808
- # t2i_worker = HunyuanDiTPipeline('Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled')
1809
- # HAS_T2I = True
1810
-
1811
- from hy3dshape import FaceReducer, FloaterRemover, DegenerateFaceRemover, MeshSimplifier, \
1812
- Hunyuan3DDiTFlowMatchingPipeline
1813
- from hy3dshape.pipelines import export_to_trimesh
1814
- from hy3dshape.rembg import BackgroundRemover
1815
-
1816
- rmbg_worker = BackgroundRemover()
1817
- i23d_worker = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(
1818
- args.model_path,
1819
- subfolder=args.subfolder,
1820
- use_safetensors=False,
1821
- device=args.device,
1822
- )
1823
- if args.enable_flashvdm:
1824
- mc_algo = 'mc' if args.device in ['cpu', 'mps'] else args.mc_algo
1825
- i23d_worker.enable_flashvdm(mc_algo=mc_algo)
1826
- if args.compile:
1827
- i23d_worker.compile()
1828
-
1829
- floater_remove_worker = FloaterRemover()
1830
- degenerate_face_remove_worker = DegenerateFaceRemover()
1831
- face_reduce_worker = FaceReducer()
1832
-
1833
- # https://discuss.huggingface.co/t/how-to-serve-an-html-file/33921/2
1834
- # create a FastAPI app
1835
- app = FastAPI()
1836
-
1837
- # create a static directory to store the static files
1838
- static_dir = Path(SAVE_DIR).absolute()
1839
- static_dir.mkdir(parents=True, exist_ok=True)
1840
- app.mount("/static", StaticFiles(directory=static_dir, html=True), name="static")
1841
- shutil.copytree('./assets/env_maps', os.path.join(static_dir, 'env_maps'), dirs_exist_ok=True)
1842
-
1843
- if args.low_vram_mode:
1844
- torch.cuda.empty_cache()
1845
-
1846
- demo = build_app()
1847
- app = gr.mount_gradio_app(app, demo, path="/")
1848
-
1849
- if ENV == 'Huggingface':
1850
- # for ZeroGpu
1851
  from spaces import zero
1852
  zero.startup()
1853
 
 
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