# Open Source Model Licensed under the Apache License Version 2.0
# and Other Licenses of the Third-Party Components therein:
# The below Model in this distribution may have been modified by THL A29 Limited
# ("Tencent Modifications"). All Tencent Modifications are Copyright (C) 2024 THL A29 Limited.
# Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved.
# The below software and/or models in this distribution may have been
# modified by THL A29 Limited ("Tencent Modifications").
# All Tencent Modifications are Copyright (C) THL A29 Limited.
# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT
# except for the third-party components listed below.
# Hunyuan 3D does not impose any additional limitations beyond what is outlined
# in the repsective licenses of these third-party components.
# Users must comply with all terms and conditions of original licenses of these third-party
# components and must ensure that the usage of the third party components adheres to
# all relevant laws and regulations.
# For avoidance of doubts, Hunyuan 3D means the large language models and
# their software and algorithms, including trained model weights, parameters (including
# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,
# fine-tuning enabling code and other elements of the foregoing made publicly available
# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.
import os
import warnings
import argparse
import gradio as gr
import gradio_litmodel3d as gr_l3d
from glob import glob
import shutil
import torch
import numpy as np
from PIL import Image
from einops import rearrange
import pandas as pd
# import spaces
from infer import seed_everything, save_gif
from infer import Text2Image, Removebg, Image2Views, Views2Mesh, GifRenderer
from third_party.check import check_bake_available
try:
from third_party.mesh_baker import MeshBaker
assert check_bake_available()
BAKE_AVAILEBLE = True
except Exception as err:
print(err)
print("import baking related fail, run without baking")
BAKE_AVAILEBLE = False
warnings.simplefilter('ignore', category=UserWarning)
warnings.simplefilter('ignore', category=FutureWarning)
warnings.simplefilter('ignore', category=DeprecationWarning)
parser = argparse.ArgumentParser()
parser.add_argument("--use_lite", default=False, action="store_true")
parser.add_argument("--mv23d_cfg_path", default="./svrm/configs/svrm.yaml", type=str)
parser.add_argument("--mv23d_ckt_path", default="weights/svrm/svrm.safetensors", type=str)
parser.add_argument("--text2image_path", default="weights/hunyuanDiT", type=str)
parser.add_argument("--save_memory", default=False)
parser.add_argument("--device", default="cuda:0", type=str)
args = parser.parse_args()
################################################################
# initial setting
################################################################
CONST_HEADER = '''
⭐️Technical report: ArXiv. ⭐️Code: GitHub.
'''
CONST_NOTE = '''
❗️❗️❗️Usage❗️❗️❗️
Limited by format, the model can only export *.obj mesh with vertex colors. The "face" mod can only work on *.glb.
Please click "Do Rendering" to export a GIF.
You can click "Do Baking" to bake multi-view imgaes onto the shape.
If the results aren't satisfactory, please try a different radnom seed (default is 0).
'''
################################################################
# prepare text examples and image examples
################################################################
def get_example_img_list():
print('Loading example img list ...')
return sorted(glob('./demos/example_*.png'))
def get_example_txt_list():
print('Loading example txt list ...')
txt_list = list()
for line in open('./demos/example_list.txt'):
txt_list.append(line.strip())
return txt_list
example_is = get_example_img_list()
example_ts = get_example_txt_list()
################################################################
# initial models
################################################################
worker_xbg = Removebg()
print(f"loading {args.text2image_path}")
worker_t2i = Text2Image(
pretrain = args.text2image_path,
device = args.device,
save_memory = args.save_memory
)
worker_i2v = Image2Views(
use_lite = args.use_lite,
device = args.device,
save_memory = args.save_memory
)
worker_v23 = Views2Mesh(
args.mv23d_cfg_path,
args.mv23d_ckt_path,
use_lite = args.use_lite,
device = args.device,
save_memory = args.save_memory
)
worker_gif = GifRenderer(args.device)
if BAKE_AVAILEBLE:
worker_baker = MeshBaker()
### functional modules
def gen_save_folder(max_size=30):
os.makedirs('./outputs/app_output', exist_ok=True)
exists = set(int(_) for _ in os.listdir('./outputs/app_output') if not _.startswith("."))
cur_id = min(set(range(max_size)) - exists) if len(exists) (n m) h w c', n=3, m=2)
show_img = rearrange(show_img[worker_i2v.order, ...], '(n m) h w c -> (n h) (m w) c', n=2, m=3)
show_img = Image.fromarray(show_img)
yield img_nobg, show_img, None, None, None, None
do_texture_mapping = color == 'face'
worker_v23(
views_img, cond_img, seed = SSEED,
save_folder = save_folder,
target_face_count = max_faces,
do_texture_mapping = do_texture_mapping
)
glb_v23 = save_folder + '/mesh.glb' if do_texture_mapping else None
obj_v23 = save_folder + '/mesh.obj'
obj_v23 = save_folder + '/mesh_vertex_colors.obj'
yield img_nobg, show_img, obj_v23, glb_v23, None, None
glb_dst = None
if do_texture_mapping and bake:
obj_dst = worker_baker(save_folder, force, front, others, align_times)
glb_dst = obj_dst.replace(".obj", ".glb")
yield img_nobg, show_img, obj_v23, glb_v23, glb_dst, None
if do_texture_mapping and render:
baked_obj_list = sorted(glob(save_folder + '/view_*/bake/mesh.obj'))
obj_dst = baked_obj_list[-1] if len(baked_obj_list)>=1 else save_folder+'/mesh.obj'
assert os.path.exists(obj_dst), f"{obj_dst} file not found"
gif_dst = obj_dst.replace(".obj", ".gif")
worker_gif(obj_dst, gif_dst_path=gif_dst)
yield img_nobg, show_img, obj_v23, glb_v23, glb_dst, gif_dst
def check_image_available(image):
if image is None:
return "Please upload image", gr.update()
elif not hasattr(image, 'mode'):
return "Not support, please upload other image", gr.update()
elif image.mode == "RGBA":
data = np.array(image)
alpha_channel = data[:, :, 3]
unique_alpha_values = np.unique(alpha_channel)
if len(unique_alpha_values) == 1:
msg = "The alpha channel is missing or invalid. The background removal option is selected for you."
return msg, gr.update(value=True, interactive=False)
else:
msg = "The image has four channels, and you can choose to remove the background or not."
return msg, gr.update(value=False, interactive=True)
elif image.mode == "RGB":
msg = "The alpha channel is missing or invalid. The background removal option is selected for you."
return msg, gr.update(value=True, interactive=False)
else:
raise Exception("Image Error")
def update_mode(mode):
color_change = {
'Vertex color': gr.update(value='vertex'),
'Face color': gr.update(value='face'),
'Baking': gr.update(value='face')
}[mode]
bake_change = {
'Vertex color': gr.update(value=False, interactive=False, visible=False),
'Face color': gr.update(value=False),
'Baking': gr.update(value=BAKE_AVAILEBLE)
}[mode]
face_change = {
'Vertex color': gr.update(value=120000, maximum=300000),
'Face color': gr.update(value=60000, maximum=300000),
'Baking': gr.update(value=10000, maximum=60000)
}[mode]
render_change = {
'Vertex color': gr.update(value=False, interactive=False, visible=False),
'Face color': gr.update(value=True),
'Baking': gr.update(value=True)
}[mode]
return color_change, bake_change, face_change, render_change
# ===============================================================
# gradio display
# ===============================================================
with gr.Blocks() as demo:
gr.Markdown(CONST_HEADER)
with gr.Row(variant="panel"):
with gr.Column(scale=2):
with gr.Tab("Text to 3D"):
with gr.Column():
text = gr.TextArea('一只黑白相间的熊猫在白色背景上居中坐着,呈现出卡通风格和可爱氛围。',
lines=3, max_lines=20, label='Input text (within 70 words)')
with gr.Row():
gr.Examples(examples=example_ts, inputs=[text], label="Text examples", examples_per_page=10)
with gr.Row():
textgen_submit = gr.Button("Generate", variant="primary")
with gr.Tab("Image to 3D"):
with gr.Row():
input_image = gr.Image(label="Input image", width=256, height=256, type="pil",
image_mode="RGBA", sources="upload", interactive=True)
with gr.Row():
alert_message = gr.Markdown("") # for warning
with gr.Row():
gr.Examples(examples=example_is, inputs=[input_image],
label="Img examples", examples_per_page=10)
with gr.Row():
removebg = gr.Checkbox(
label="Remove Background",
value=True,
interactive=True
)
imggen_submit = gr.Button("Generate", variant="primary")
mode = gr.Radio(
choices=['Vertex color', 'Face color', 'Baking'],
label="Texture mode",
value='Baking',
interactive=True
)
with gr.Accordion("Custom settings", open=False):
color = gr.Radio(choices=["vertex", "face"], label="Color", value="face")
with gr.Row():
render = gr.Checkbox(
label="Do Rendering",
value=True,
interactive=True
)
bake = gr.Checkbox(
label="Do Baking",
value=True if BAKE_AVAILEBLE else False,
interactive=True if BAKE_AVAILEBLE else False
)
with gr.Row():
seed = gr.Number(value=0, label="T2I seed", precision=0, interactive=True)
SEED = gr.Number(value=0, label="Gen seed", precision=0, interactive=True)
step = gr.Slider(
value=25,
minimum=15,
maximum=50,
step=1,
label="T2I steps",
interactive=True
)
STEP = gr.Slider(
value=50,
minimum=20,
maximum=80,
step=1,
label="Gen steps",
interactive=True
)
max_faces = gr.Slider(
value=10000,
minimum=2000,
maximum=60000,
step=1000,
label="Face number limit",
interactive=True
)
with gr.Accordion("Baking Options", open=False):
force_bake = gr.Checkbox(
label="Force (Ignore the degree of matching)",
value=False,
interactive=True
)
front_baking = gr.Radio(
choices=['input image', 'multi-view front view', 'auto'],
label="Front view baking",
value='auto',
interactive=True,
visible=True
)
other_views = gr.CheckboxGroup(
choices=['60°', '120°', '180°', '240°', '300°'],
label="Other views baking",
value=['180°'],
interactive=True,
visible=True
)
align_times =gr.Slider(
value=1,
minimum=1,
maximum=5,
step=1,
label="Number of alignment attempts per view",
interactive=True
)
input_image.change(
fn=check_image_available,
inputs=input_image,
outputs=[alert_message, removebg]
)
mode.change(
fn=update_mode,
inputs=mode,
outputs=[color, bake, max_faces, render]
)
gr.Markdown(CONST_NOTE)
###### Output region
with gr.Column(scale=3):
with gr.Row():
with gr.Column(scale=2):
rembg_image = gr.Image(
label="Image without background",
type="pil",
image_mode="RGBA",
interactive=False
)
with gr.Column(scale=3):
result_image = gr.Image(
label="Multi-view images",
type="pil",
interactive=False
)
result_3dobj = gr_l3d.LitModel3D(
clear_color=[0.0, 0.0, 0.0, 0.0],
label="OBJ vertex color",
show_label=True,
visible=True,
camera_position=[90, 90, None],
interactive=False
)
result_3dglb_texture = gr_l3d.LitModel3D(
exposure=16.0,
clear_color=[0.0, 0.0, 0.0, 0.0],
label="GLB face color",
show_label=True,
visible=True,
camera_position=[90, 90, None],
interactive=False)
result_3dglb_baked = gr_l3d.LitModel3D(
exposure=16.0,
clear_color=[0.0, 0.0, 0.0, 0.0],
label="GLB baking",
show_label=True,
visible=True,
camera_position=[90, 90, None],
interactive=False)
result_gif = gr.Image(label="GIF", interactive=False)
with gr.Row():
gr.Markdown(
"Due to Gradio limitations, OBJ files are displayed with vertex shading only, "
"while GLB files can be viewed with face color.
For the best experience, "
"we recommend downloading the GLB files and opening them with 3D software "
"like Blender or MeshLab."
)
#===============================================================
# gradio running code
#===============================================================
none = gr.State(None)
textgen_submit.click(
fn=gen_pipe,
inputs=[text, none, removebg, seed, step, SEED, STEP, color, bake, render, max_faces, force_bake,
front_baking, other_views, align_times],
outputs=[rembg_image, result_image, result_3dobj, result_3dglb_texture, result_3dglb_baked, result_gif],
)
imggen_submit.click(
fn=gen_pipe,
inputs=[none, input_image, removebg, seed, step, SEED, STEP, color, bake, render, max_faces, force_bake,
front_baking, other_views, align_times],
outputs=[rembg_image, result_image, result_3dobj, result_3dglb_texture, result_3dglb_baked, result_gif],
)
demo.queue(max_size=1)
demo.launch(server_name='0.0.0.0', server_port=8080)
# demo.launch()