Spaces:
Runtime error
Runtime error
add output caching
Browse fileslock requirements
move assets to git lfs
add comments in code
- .gitattributes +2 -0
- files/horse.ply.output/output.glb +3 -0
- main.py +37 -19
- requirements.txt +4 -3
.gitattributes
CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
*.glb filter=lfs diff=lfs merge=lfs -text
|
37 |
+
*.ply filter=lfs diff=lfs merge=lfs -text
|
files/horse.ply.output/output.glb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a6e57814fdca91e80b2dad1912093f94d348802be932e3b0560d73c895519a31
|
3 |
+
size 1700664
|
main.py
CHANGED
@@ -27,6 +27,12 @@ def convert_formats(path_input, target_ext):
|
|
27 |
|
28 |
|
29 |
def add_lights(path_input, path_output):
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
glb = pygltflib.GLTF2().load(path_input)
|
31 |
|
32 |
N = 3 # default max num lights in Babylon.js is 4
|
@@ -34,11 +40,7 @@ def add_lights(path_input, path_output):
|
|
34 |
|
35 |
lights_extension = {
|
36 |
"lights": [
|
37 |
-
{
|
38 |
-
"type": "directional",
|
39 |
-
"color": [1.0, 1.0, 1.0],
|
40 |
-
"intensity": 2.0
|
41 |
-
}
|
42 |
for _ in range(N)
|
43 |
]
|
44 |
}
|
@@ -50,19 +52,10 @@ def add_lights(path_input, path_output):
|
|
50 |
light_nodes = []
|
51 |
for i in range(N):
|
52 |
angle = i * angle_step
|
53 |
-
rotation = [
|
54 |
-
0.0,
|
55 |
-
math.sin(angle / 2),
|
56 |
-
0.0,
|
57 |
-
math.cos(angle / 2)
|
58 |
-
]
|
59 |
node = {
|
60 |
"rotation": rotation,
|
61 |
-
"extensions": {
|
62 |
-
"KHR_lights_punctual": {
|
63 |
-
"light": i
|
64 |
-
}
|
65 |
-
}
|
66 |
}
|
67 |
light_nodes.append(node)
|
68 |
|
@@ -71,7 +64,7 @@ def add_lights(path_input, path_output):
|
|
71 |
|
72 |
root_node_index = glb.scenes[glb.scene].nodes[0]
|
73 |
root_node = glb.nodes[root_node_index]
|
74 |
-
if hasattr(root_node,
|
75 |
root_node.children.extend(light_node_indices)
|
76 |
else:
|
77 |
root_node.children = light_node_indices
|
@@ -85,14 +78,20 @@ class Model3D(gr.Model3D):
|
|
85 |
"""
|
86 |
|
87 |
def postprocess(self, y: str | Path | None) -> dict[str, str] | None:
|
|
|
|
|
|
|
|
|
|
|
88 |
if y is not None:
|
89 |
y = convert_formats(y, "glb")
|
90 |
out = super().postprocess(y)
|
91 |
return out
|
92 |
|
93 |
|
94 |
-
def breathe_new_life_into_3d_model(path_input, prompt):
|
95 |
"""
|
|
|
96 |
@inproceedings{wang2023breathing,
|
97 |
title={Breathing New Life into 3D Assets with Generative Repainting},
|
98 |
author={Wang, Tianfu and Kanakis, Menelaos and Schindler, Konrad and Van Gool, Luc and Obukhov, Anton},
|
@@ -100,7 +99,17 @@ def breathe_new_life_into_3d_model(path_input, prompt):
|
|
100 |
year={2023},
|
101 |
publisher={BMVA Press}
|
102 |
}
|
|
|
|
|
|
|
|
|
103 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
path_output_dir = path_input + ".output"
|
105 |
os.makedirs(path_output_dir, exist_ok=True)
|
106 |
|
@@ -129,6 +138,9 @@ def breathe_new_life_into_3d_model(path_input, prompt):
|
|
129 |
|
130 |
|
131 |
def run():
|
|
|
|
|
|
|
132 |
desc = """
|
133 |
<p align="center">
|
134 |
<a title="Website" href="https://www.obukhov.ai/repainting_3d_assets" target="_blank" rel="noopener noreferrer" style="display: inline-block;">
|
@@ -162,7 +174,10 @@ def run():
|
|
162 |
elem_classes="viewport",
|
163 |
label="Input Model",
|
164 |
),
|
165 |
-
gr.Textbox(
|
|
|
|
|
|
|
166 |
],
|
167 |
outputs=[
|
168 |
gr.Model3D(
|
@@ -175,6 +190,9 @@ def run():
|
|
175 |
[
|
176 |
os.path.join(os.path.dirname(__file__), "files/horse.ply"),
|
177 |
"pastel superhero unicorn",
|
|
|
|
|
|
|
178 |
],
|
179 |
],
|
180 |
cache_examples=True,
|
|
|
27 |
|
28 |
|
29 |
def add_lights(path_input, path_output):
|
30 |
+
"""
|
31 |
+
Adds directional lights in the horizontal plane to the glb file.
|
32 |
+
:param path_input: path to input glb
|
33 |
+
:param path_output: path to output glb
|
34 |
+
:return: None
|
35 |
+
"""
|
36 |
glb = pygltflib.GLTF2().load(path_input)
|
37 |
|
38 |
N = 3 # default max num lights in Babylon.js is 4
|
|
|
40 |
|
41 |
lights_extension = {
|
42 |
"lights": [
|
43 |
+
{"type": "directional", "color": [1.0, 1.0, 1.0], "intensity": 2.0}
|
|
|
|
|
|
|
|
|
44 |
for _ in range(N)
|
45 |
]
|
46 |
}
|
|
|
52 |
light_nodes = []
|
53 |
for i in range(N):
|
54 |
angle = i * angle_step
|
55 |
+
rotation = [0.0, math.sin(angle / 2), 0.0, math.cos(angle / 2)]
|
|
|
|
|
|
|
|
|
|
|
56 |
node = {
|
57 |
"rotation": rotation,
|
58 |
+
"extensions": {"KHR_lights_punctual": {"light": i}},
|
|
|
|
|
|
|
|
|
59 |
}
|
60 |
light_nodes.append(node)
|
61 |
|
|
|
64 |
|
65 |
root_node_index = glb.scenes[glb.scene].nodes[0]
|
66 |
root_node = glb.nodes[root_node_index]
|
67 |
+
if hasattr(root_node, "children"):
|
68 |
root_node.children.extend(light_node_indices)
|
69 |
else:
|
70 |
root_node.children = light_node_indices
|
|
|
78 |
"""
|
79 |
|
80 |
def postprocess(self, y: str | Path | None) -> dict[str, str] | None:
|
81 |
+
"""
|
82 |
+
Converts user input 3D model in any format acceptable by trimesh to glb, required by the web component
|
83 |
+
:param y: path to input 3D model
|
84 |
+
:return: file name mapped to base64 url data
|
85 |
+
"""
|
86 |
if y is not None:
|
87 |
y = convert_formats(y, "glb")
|
88 |
out = super().postprocess(y)
|
89 |
return out
|
90 |
|
91 |
|
92 |
+
def breathe_new_life_into_3d_model(path_input, prompt, path_output=None):
|
93 |
"""
|
94 |
+
Paints the input 3D model using the following method:
|
95 |
@inproceedings{wang2023breathing,
|
96 |
title={Breathing New Life into 3D Assets with Generative Repainting},
|
97 |
author={Wang, Tianfu and Kanakis, Menelaos and Schindler, Konrad and Van Gool, Luc and Obukhov, Anton},
|
|
|
99 |
year={2023},
|
100 |
publisher={BMVA Press}
|
101 |
}
|
102 |
+
:param path_input: path to input 3D model
|
103 |
+
:param prompt: text description of the expected output
|
104 |
+
:param path_output: path to precomputed (cached) output if available
|
105 |
+
:return: path to the painted 3D model in glb format with lights for Gradio web component
|
106 |
"""
|
107 |
+
if path_output is not None:
|
108 |
+
path_output = path_output.name
|
109 |
+
path_output_glb_vis = path_output[:-4] + "_vis.glb"
|
110 |
+
add_lights(path_output, path_output_glb_vis)
|
111 |
+
return path_output_glb_vis
|
112 |
+
|
113 |
path_output_dir = path_input + ".output"
|
114 |
os.makedirs(path_output_dir, exist_ok=True)
|
115 |
|
|
|
138 |
|
139 |
|
140 |
def run():
|
141 |
+
"""
|
142 |
+
Gradio entry point
|
143 |
+
"""
|
144 |
desc = """
|
145 |
<p align="center">
|
146 |
<a title="Website" href="https://www.obukhov.ai/repainting_3d_assets" target="_blank" rel="noopener noreferrer" style="display: inline-block;">
|
|
|
174 |
elem_classes="viewport",
|
175 |
label="Input Model",
|
176 |
),
|
177 |
+
gr.Textbox(
|
178 |
+
label="Text Prompt",
|
179 |
+
),
|
180 |
+
gr.File(visible=False, label="Cached Output"),
|
181 |
],
|
182 |
outputs=[
|
183 |
gr.Model3D(
|
|
|
190 |
[
|
191 |
os.path.join(os.path.dirname(__file__), "files/horse.ply"),
|
192 |
"pastel superhero unicorn",
|
193 |
+
os.path.join(
|
194 |
+
os.path.dirname(__file__), "files/horse.ply.output/output.glb"
|
195 |
+
),
|
196 |
],
|
197 |
],
|
198 |
cache_examples=True,
|
requirements.txt
CHANGED
@@ -1,3 +1,4 @@
|
|
1 |
-
gradio
|
2 |
-
|
3 |
-
|
|
|
|
1 |
+
gradio==3.44.4
|
2 |
+
gradio_client==0.5.1
|
3 |
+
pygltflib==1.16.0
|
4 |
+
trimesh==3.23.5
|