Spaces:
Sleeping
Sleeping
from __future__ import annotations | |
import math | |
import os | |
import subprocess | |
import gradio as gr | |
import pygltflib | |
import trimesh | |
def convert_formats(path_input, target_ext): | |
""" | |
Converts an input 3D model under input path to the target extensions format and returns a path to that file. | |
:param path_input: path to user input | |
:param target_ext: target extension | |
:return: path to the input 3D model stored in target format. | |
""" | |
path_input_base, ext = os.path.splitext(path_input) | |
if ext == "." + target_ext: | |
return path_input | |
path_output = path_input_base + "." + target_ext | |
if not os.path.exists(path_output): | |
trimesh.load_mesh(path_input).export(path_output) | |
return path_output | |
def add_lights(path_input, path_output): | |
""" | |
Adds directional lights in the horizontal plane to the glb file. | |
:param path_input: path to input glb | |
:param path_output: path to output glb | |
:return: None | |
""" | |
glb = pygltflib.GLTF2().load(path_input) | |
N = 3 # default max num lights in Babylon.js is 4 | |
angle_step = 2 * math.pi / N | |
lights_extension = { | |
"lights": [ | |
{"type": "directional", "color": [1.0, 1.0, 1.0], "intensity": 2.0} | |
for _ in range(N) | |
] | |
} | |
if "KHR_lights_punctual" not in glb.extensionsUsed: | |
glb.extensionsUsed.append("KHR_lights_punctual") | |
glb.extensions["KHR_lights_punctual"] = lights_extension | |
light_nodes = [] | |
for i in range(N): | |
angle = i * angle_step | |
rotation = [0.0, math.sin(angle / 2), 0.0, math.cos(angle / 2)] | |
node = { | |
"rotation": rotation, | |
"extensions": {"KHR_lights_punctual": {"light": i}}, | |
} | |
light_nodes.append(node) | |
light_node_indices = list(range(len(glb.nodes), len(glb.nodes) + N)) | |
glb.nodes.extend(light_nodes) | |
root_node_index = glb.scenes[glb.scene].nodes[0] | |
root_node = glb.nodes[root_node_index] | |
if hasattr(root_node, "children"): | |
root_node.children.extend(light_node_indices) | |
else: | |
root_node.children = light_node_indices | |
glb.save(path_output) | |
def breathe_new_life_into_3d_model(path_input, prompt, path_output=None): | |
""" | |
Paints the input 3D model using the following method: | |
@inproceedings{wang2023breathing, | |
title={Breathing New Life into 3D Assets with Generative Repainting}, | |
author={Wang, Tianfu and Kanakis, Menelaos and Schindler, Konrad and Van Gool, Luc and Obukhov, Anton}, | |
booktitle={Proceedings of the British Machine Vision Conference (BMVC)}, | |
year={2023}, | |
publisher={BMVA Press} | |
} | |
:param path_input: path to input 3D model | |
:param prompt: text description of the expected output | |
:param path_output: path to precomputed (cached) output if available | |
:return: path to the painted 3D model in glb format with lights for Gradio web component | |
""" | |
if path_output is not None: | |
path_output = path_output.name | |
path_output_glb_vis = path_output[:-4] + "_vis.glb" | |
add_lights(path_output, path_output_glb_vis) | |
return path_output_glb_vis | |
path_output_dir = path_input + ".output" | |
os.makedirs(path_output_dir, exist_ok=True) | |
path_input_ply = convert_formats(path_input, "ply") | |
cmd = [ | |
"bash", | |
"/repainting_3d_assets/code/scripts/conda_run.sh", | |
"/repainting_3d_assets", | |
path_input_ply, | |
path_output_dir, | |
prompt, | |
] | |
result = subprocess.run(cmd, env=os.environ, text=True) | |
if result.returncode != 0: | |
print(f"Output: {result.stdout}") | |
print(f"Stderr: {result.stderr}") | |
raise RuntimeError("Processing failed") | |
path_output_glb = os.path.join(path_output_dir, "model_draco.glb") | |
path_output_glb_vis = path_output_glb[:-4] + "_vis.glb" | |
add_lights(path_output_glb, path_output_glb_vis) | |
return path_output_glb_vis | |
def run(): | |
""" | |
Gradio entry point | |
""" | |
desc = """ | |
<p align="center"> | |
<a title="Website" href="https://www.obukhov.ai/repainting_3d_assets" target="_blank" rel="noopener noreferrer" style="display: inline-block;"> | |
<img src="https://www.obukhov.ai/img/badges/badge-website.svg"> | |
</a> | |
<a title="arXiv" href="https://arxiv.org/abs/2309.08523" target="_blank" rel="noopener noreferrer" style="display: inline-block;"> | |
<img src="https://www.obukhov.ai/img/badges/badge-pdf.svg"> | |
</a> | |
<a title="Github" href="https://github.com/kongdai123/repainting_3d_assets" target="_blank" rel="noopener noreferrer" style="display: inline-block;"> | |
<img src="https://img.shields.io/github/stars/kongdai123/repainting_3d_assets?label=GitHub%20%E2%98%85&logo=github&color=C8C" alt="badge-github-stars"> | |
</a> | |
<a title="Social" href="https://twitter.com/antonobukhov1" target="_blank" rel="noopener noreferrer" style="display: inline-block;"> | |
<img src="https://www.obukhov.ai/img/badges/badge-social.svg" alt="social"> | |
</a> | |
</p> | |
<p align="justify"> | |
Repaint your 3D models with a text prompt, guided by a method from our BMVC'2023 Oral paper 'Breathing New Life | |
into 3D Assets with Generative Repainting'. Simply drop a model into the left pane, specify your repainting | |
preferences, and wait for the outcome (~20 min). Explore precomputed examples at the bottom, or follow the | |
Project Website badge for additional precomputed models and comparison with other repainting techniques. | |
</p> | |
""" | |
demo = gr.Interface( | |
title="Repainting 3D Assets", | |
description=desc, | |
thumbnail="thumbnail.jpg", | |
fn=breathe_new_life_into_3d_model, | |
inputs=[ | |
gr.Model3D( | |
camera_position=(30.0, 90.0, 3.0), | |
elem_classes="viewport", | |
label="Input Model", | |
), | |
gr.Textbox( | |
label="Text Prompt", | |
), | |
gr.File(visible=False, label="Cached Output"), | |
], | |
outputs=[ | |
gr.Model3D( | |
camera_position=(30.0, 90.0, 3.0), | |
elem_classes="viewport", | |
label="Repainted Model", | |
), | |
], | |
examples=[ | |
[ | |
os.path.join(os.path.dirname(__file__), "files/horse.glb"), | |
"pastel superhero unicorn", | |
os.path.join( | |
os.path.dirname(__file__), "files/horse.glb.output/output.glb" | |
), | |
], | |
], | |
cache_examples=True, | |
css=""" | |
.viewport { | |
aspect-ratio: 16/9; | |
} | |
""", | |
allow_flagging="never", | |
) | |
demo.queue().launch(server_name="0.0.0.0", server_port=7860) | |
if __name__ == "__main__": | |
run() | |