import os import shutil import time from glob import glob from pathlib import Path import gradio as gr import torch import uvicorn from fastapi import FastAPI from fastapi.staticfiles import StaticFiles def get_example_img_list(): print('Loading example img list ...') return sorted(glob('./assets/example_images/*.png')) def get_example_txt_list(): print('Loading example txt list ...') txt_list = list() for line in open('./assets/example_prompts.txt'): txt_list.append(line.strip()) return txt_list def gen_save_folder(max_size=60): os.makedirs(SAVE_DIR, exist_ok=True) exists = set(int(_) for _ in os.listdir(SAVE_DIR) if not _.startswith(".")) cur_id = min(set(range(max_size)) - exists) if len(exists) < max_size else -1 if os.path.exists(f"{SAVE_DIR}/{(cur_id + 1) % max_size}"): shutil.rmtree(f"{SAVE_DIR}/{(cur_id + 1) % max_size}") print(f"remove {SAVE_DIR}/{(cur_id + 1) % max_size} success !!!") save_folder = f"{SAVE_DIR}/{max(0, cur_id)}" os.makedirs(save_folder, exist_ok=True) print(f"mkdir {save_folder} suceess !!!") return save_folder def export_mesh(mesh, save_folder, textured=False): if textured: path = os.path.join(save_folder, f'textured_mesh.glb') else: path = os.path.join(save_folder, f'white_mesh.glb') mesh.export(path, include_normals=textured) return path def build_model_viewer_html(save_folder, height=660, width=790, textured=False): if textured: related_path = f"./textured_mesh.glb" template_name = './assets/modelviewer-textured-template.html' output_html_path = os.path.join(save_folder, f'textured_mesh.html') else: related_path = f"./white_mesh.glb" template_name = './assets/modelviewer-template.html' output_html_path = os.path.join(save_folder, f'white_mesh.html') with open(os.path.join(CURRENT_DIR, template_name), 'r') as f: template_html = f.read() obj_html = f"""
""" with open(output_html_path, 'w') as f: f.write(template_html.replace('', obj_html)) output_html_path = output_html_path.replace(SAVE_DIR + '/', '') iframe_tag = f'' print(f'Find html {output_html_path}, {os.path.exists(output_html_path)}') return f"""
{iframe_tag}
""" def _gen_shape( caption, image, steps=50, guidance_scale=7.5, seed=1234, octree_resolution=256, check_box_rembg=False, ): if caption: print('prompt is', caption) save_folder = gen_save_folder() stats = {} time_meta = {} start_time_0 = time.time() if image is None: start_time = time.time() try: image = t2i_worker(caption) except Exception as e: raise gr.Error(f"Text to 3D is disable. Please enable it by `python gradio_app.py --enable_t23d`.") time_meta['text2image'] = time.time() - start_time image.save(os.path.join(save_folder, 'input.png')) print(image.mode) if check_box_rembg or image.mode == "RGB": start_time = time.time() image = rmbg_worker(image.convert('RGB')) time_meta['rembg'] = time.time() - start_time image.save(os.path.join(save_folder, 'rembg.png')) # image to white model start_time = time.time() generator = torch.Generator() generator = generator.manual_seed(int(seed)) mesh = i23d_worker( image=image, num_inference_steps=steps, guidance_scale=guidance_scale, generator=generator, octree_resolution=octree_resolution )[0] mesh = FloaterRemover()(mesh) mesh = DegenerateFaceRemover()(mesh) mesh = FaceReducer()(mesh) stats['number_of_faces'] = mesh.faces.shape[0] stats['number_of_vertices'] = mesh.vertices.shape[0] time_meta['image_to_textured_3d'] = {'total': time.time() - start_time} time_meta['total'] = time.time() - start_time_0 stats['time'] = time_meta return mesh, save_folder def generation_all( caption, image, steps=50, guidance_scale=7.5, seed=1234, octree_resolution=256, check_box_rembg=False ): mesh, save_folder = _gen_shape( caption, image, steps=steps, guidance_scale=guidance_scale, seed=seed, octree_resolution=octree_resolution, check_box_rembg=check_box_rembg ) path = export_mesh(mesh, save_folder, textured=False) model_viewer_html = build_model_viewer_html(save_folder, height=596, width=700) textured_mesh = texgen_worker(mesh, image) path_textured = export_mesh(textured_mesh, save_folder, textured=True) model_viewer_html_textured = build_model_viewer_html(save_folder, height=596, width=700, textured=True) return ( gr.update(value=path, visible=True), gr.update(value=path_textured, visible=True), model_viewer_html, model_viewer_html_textured, ) def shape_generation( caption, image, steps=50, guidance_scale=7.5, seed=1234, octree_resolution=256, check_box_rembg=False, ): mesh, save_folder = _gen_shape( caption, image, steps=steps, guidance_scale=guidance_scale, seed=seed, octree_resolution=octree_resolution, check_box_rembg=check_box_rembg ) path = export_mesh(mesh, save_folder, textured=False) model_viewer_html = build_model_viewer_html(save_folder, height=596, width=700) return ( gr.update(value=path, visible=True), model_viewer_html, ) def build_app(): title_html = """
Hunyuan3D-2: Scaling Diffusion Models for High Resolution Textured 3D Assets Generation
Tencent Hunyuan3D Team
Github PageHomepageTechnical Report Models
""" with gr.Blocks(theme=gr.themes.Base(), title='Hunyuan-3D-2.0') as demo: gr.HTML(title_html) with gr.Row(): with gr.Column(scale=2): with gr.Tabs() as tabs_prompt: with gr.Tab('Image Prompt', id='tab_img_prompt') as tab_ip: image = gr.Image(label='Image', type='pil', image_mode='RGBA', height=290) with gr.Row(): check_box_rembg = gr.Checkbox(value=True, label='Remove Background') with gr.Tab('Text Prompt', id='tab_txt_prompt', visible=HAS_T2I) as tab_tp: caption = gr.Textbox(label='Text Prompt', placeholder='HunyuanDiT will be used to generate image.', info='Example: A 3D model of a cute cat, white background') with gr.Accordion('Advanced Options', open=False): num_steps = gr.Slider(maximum=50, minimum=20, value=30, step=1, label='Inference Steps') octree_resolution = gr.Dropdown([256, 384, 512], value=256, label='Octree Resolution') cfg_scale = gr.Number(value=5.5, label='Guidance Scale') seed = gr.Slider(maximum=1e7, minimum=0, value=1234, label='Seed') with gr.Group(): btn = gr.Button(value='Generate Shape Only', variant='primary') btn_all = gr.Button(value='Generate Shape and Texture', variant='primary', visible=HAS_TEXTUREGEN) with gr.Group(): file_out = gr.File(label="File", visible=False) file_out2 = gr.File(label="File", visible=False) with gr.Column(scale=5): with gr.Tabs(): with gr.Tab('Generated Mesh') as mesh1: html_output1 = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output') with gr.Tab('Generated Textured Mesh') as mesh2: html_output2 = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output') with gr.Column(scale=2): with gr.Tabs() as gallery: with gr.Tab('Image to 3D Gallery', id='tab_img_gallery') as tab_gi: with gr.Row(): gr.Examples(examples=example_is, inputs=[image], label="Image Prompts", examples_per_page=18) with gr.Tab('Text to 3D Gallery', id='tab_txt_gallery', visible=HAS_T2I) as tab_gt: with gr.Row(): gr.Examples(examples=example_ts, inputs=[caption], label="Text Prompts", examples_per_page=18) if not HAS_TEXTUREGEN: gr.HTML(""")
Warning: Texture synthesis is disable due to missing requirements, please install requirements following README.md to activate it.
""") if not args.enable_t23d: gr.HTML("""
Warning: Text to 3D is disable. To activate it, please run `python gradio_app.py --enable_t23d`.
""") tab_gi.select(fn=lambda: gr.update(selected='tab_img_prompt'), outputs=tabs_prompt) if HAS_T2I: tab_gt.select(fn=lambda: gr.update(selected='tab_txt_prompt'), outputs=tabs_prompt) btn.click( shape_generation, inputs=[ caption, image, num_steps, cfg_scale, seed, octree_resolution, check_box_rembg, ], outputs=[file_out, html_output1] ).then( lambda: gr.update(visible=True), outputs=[file_out], ) btn_all.click( generation_all, inputs=[ caption, image, num_steps, cfg_scale, seed, octree_resolution, check_box_rembg, ], outputs=[file_out, file_out2, html_output1, html_output2] ).then( lambda: (gr.update(visible=True), gr.update(visible=True)), outputs=[file_out, file_out2], ) return demo if __name__ == '__main__': import argparse parser = argparse.ArgumentParser() parser.add_argument('--port', type=int, default=8080) parser.add_argument('--cache-path', type=str, default='gradio_cache') parser.add_argument('--enable_t23d', action='store_true') args = parser.parse_args() SAVE_DIR = args.cache_path os.makedirs(SAVE_DIR, exist_ok=True) CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) HTML_OUTPUT_PLACEHOLDER = """
""" INPUT_MESH_HTML = """
""" example_is = get_example_img_list() example_ts = get_example_txt_list() try: from hy3dgen.texgen import Hunyuan3DPaintPipeline texgen_worker = Hunyuan3DPaintPipeline.from_pretrained('tencent/Hunyuan3D-2') HAS_TEXTUREGEN = True except Exception as e: print(e) print("Failed to load texture generator.") print('Please try to install requirements by following README.md') HAS_TEXTUREGEN = False HAS_T2I = False if args.enable_t23d: from hy3dgen.text2image import HunyuanDiTPipeline t2i_worker = HunyuanDiTPipeline('Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled') HAS_T2I = True from hy3dgen.shapegen import FaceReducer, FloaterRemover, DegenerateFaceRemover, \ Hunyuan3DDiTFlowMatchingPipeline from hy3dgen.rembg import BackgroundRemover rmbg_worker = BackgroundRemover() i23d_worker = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained('tencent/Hunyuan3D-2') floater_remove_worker = FloaterRemover() degenerate_face_remove_worker = DegenerateFaceRemover() face_reduce_worker = FaceReducer() # https://discuss.huggingface.co/t/how-to-serve-an-html-file/33921/2 # create a FastAPI app app = FastAPI() # create a static directory to store the static files static_dir = Path('./gradio_cache') static_dir.mkdir(parents=True, exist_ok=True) app.mount("/static", StaticFiles(directory=static_dir), name="static") demo = build_app() app = gr.mount_gradio_app(app, demo, path="/") uvicorn.run(app, host="0.0.0.0", port=args.port)