Huiwenshi commited on
Commit
76d0f59
·
verified ·
1 Parent(s): 600759a

Update gradio_app.py

Browse files
Files changed (1) hide show
  1. gradio_app.py +933 -1
gradio_app.py CHANGED
@@ -918,5 +918,937 @@ if __name__ == '__main__':
918
 
919
  demo = build_app()
920
  app = gr.mount_gradio_app(app, demo, path="/")
921
- # zero.startup()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
922
  uvicorn.run(app, host=args.host, port=args.port)
 
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)
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
+
1854
  uvicorn.run(app, host=args.host, port=args.port)