Recover Lost File, Delete 3d_model folder
Browse files- .gitattributes +1 -1
- app.py +53 -6
- modules/constants.py +7 -1
- modules/storage.py +100 -0
- style_20250503.css +2 -2
.gitattributes
CHANGED
@@ -40,4 +40,4 @@ images/beeuty_545jlbh1_v12_alpha96_300dpi_depth.png filter=lfs diff=lfs merge=lf
|
|
40 |
*.gltf filter=lfs diff=lfs merge=lfs -text
|
41 |
*.ply filter=lfs diff=lfs merge=lfs -text
|
42 |
*.stl filter=lfs diff=lfs merge=lfs -text
|
43 |
-
*.obj filter=lfs diff=lfs merge=lfs -text
|
|
|
40 |
*.gltf filter=lfs diff=lfs merge=lfs -text
|
41 |
*.ply filter=lfs diff=lfs merge=lfs -text
|
42 |
*.stl filter=lfs diff=lfs merge=lfs -text
|
43 |
+
*.obj filter=lfs diff=lfs merge=lfs -text
|
app.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1 |
import gradio as gr
|
2 |
import os
|
|
|
3 |
import modules.constants as constants
|
4 |
import modules.version_info as version_info
|
|
|
5 |
|
6 |
|
7 |
user_dir = constants.TMPDIR
|
|
|
8 |
|
9 |
def getVersions():
|
10 |
#return html_versions
|
@@ -53,10 +56,10 @@ def process_upload(files, current_model, current_images):
|
|
53 |
file_name = f.name if hasattr(f, "name") else f
|
54 |
ext = os.path.splitext(file_name)[1].lower()
|
55 |
|
56 |
-
if ext in
|
57 |
if extracted_model is None:
|
58 |
extracted_model = file_name
|
59 |
-
elif ext in
|
60 |
if len(extracted_images) < 2:
|
61 |
extracted_images.append(file_name)
|
62 |
|
@@ -94,7 +97,8 @@ with gr.Blocks(css_paths="style_20250503.css", title="3D viewer", theme='Surn/be
|
|
94 |
label="3D Model",
|
95 |
value=None,
|
96 |
height=480,
|
97 |
-
elem_id="model_3d", key="model_3d"
|
|
|
98 |
|
99 |
)
|
100 |
image_slider = gr.ImageSlider(
|
@@ -105,12 +109,38 @@ with gr.Blocks(css_paths="style_20250503.css", title="3D viewer", theme='Surn/be
|
|
105 |
type="filepath"
|
106 |
)
|
107 |
|
|
|
|
|
|
|
108 |
with gr.Row():
|
109 |
upload_btn = gr.UploadButton(
|
110 |
-
"Upload", elem_id="upload_btn", key="upload_btn",
|
111 |
file_count="multiple",
|
112 |
-
file_types=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
with gr.Row():
|
115 |
gr.HTML(value=getVersions(), visible=True, elem_id="versions")
|
116 |
|
@@ -136,9 +166,26 @@ with gr.Blocks(css_paths="style_20250503.css", title="3D viewer", theme='Surn/be
|
|
136 |
show_progress=True
|
137 |
|
138 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
|
140 |
if __name__ == "__main__":
|
141 |
viewer3d.launch(
|
142 |
-
allowed_paths=["assets", "assets/", "./assets", "images/", "./images", 'e:/TMP', 'models/'],
|
143 |
favicon_path="./assets/favicon.ico", show_api=True, strict_cors=False
|
144 |
)
|
|
|
1 |
import gradio as gr
|
2 |
import os
|
3 |
+
import random
|
4 |
import modules.constants as constants
|
5 |
import modules.version_info as version_info
|
6 |
+
import modules.storage as storage
|
7 |
|
8 |
|
9 |
user_dir = constants.TMPDIR
|
10 |
+
default_folder = "saved_models/3d_model_" + format(random.randint(1, 999999), "06d")
|
11 |
|
12 |
def getVersions():
|
13 |
#return html_versions
|
|
|
56 |
file_name = f.name if hasattr(f, "name") else f
|
57 |
ext = os.path.splitext(file_name)[1].lower()
|
58 |
|
59 |
+
if ext in constants.model_extensions:
|
60 |
if extracted_model is None:
|
61 |
extracted_model = file_name
|
62 |
+
elif ext in constants.image_extensions:
|
63 |
if len(extracted_images) < 2:
|
64 |
extracted_images.append(file_name)
|
65 |
|
|
|
97 |
label="3D Model",
|
98 |
value=None,
|
99 |
height=480,
|
100 |
+
elem_id="model_3d", key="model_3d", clear_color=[1.0, 1.0, 1.0, 0.1],
|
101 |
+
elem_classes="centered solid imgcontainer", interactive=True
|
102 |
|
103 |
)
|
104 |
image_slider = gr.ImageSlider(
|
|
|
109 |
type="filepath"
|
110 |
)
|
111 |
|
112 |
+
with gr.Row():
|
113 |
+
gr.Markdown("## Upload your own files")
|
114 |
+
gr.Markdown("### Supported formats: " + ", ".join([f"`{ext}`" for ext in constants.upload_file_types]))
|
115 |
with gr.Row():
|
116 |
upload_btn = gr.UploadButton(
|
117 |
+
"Upload 3D Files", elem_id="upload_btn", key="upload_btn",
|
118 |
file_count="multiple",
|
119 |
+
file_types=constants.upload_file_types
|
120 |
+
)
|
121 |
+
|
122 |
+
with gr.Row():
|
123 |
+
# New textbox for folder name.
|
124 |
+
folder_name_box = gr.Textbox(
|
125 |
+
label="Folder Name",
|
126 |
+
value=default_folder,
|
127 |
+
elem_id="folder_name",
|
128 |
+
key="folder_name",
|
129 |
+
placeholder="Enter folder name..."
|
130 |
)
|
131 |
+
permalink_button = gr.Button("Generate Permalink", elem_id="permalink_button", key="permalink_button", elem_classes="solid small centered")
|
132 |
+
|
133 |
+
with gr.Row(visible=False, elem_id="permalink_row") as permalink_row:
|
134 |
+
permalink = gr.Textbox(
|
135 |
+
show_copy_button=True,
|
136 |
+
label="Permalink",
|
137 |
+
elem_id="permalink",
|
138 |
+
key="permalink",
|
139 |
+
elem_classes="solid small centered",
|
140 |
+
max_lines=5,
|
141 |
+
lines=3
|
142 |
+
)
|
143 |
+
gr.Markdown("### Copy the link above to share your model and images.")
|
144 |
with gr.Row():
|
145 |
gr.HTML(value=getVersions(), visible=True, elem_id="versions")
|
146 |
|
|
|
166 |
show_progress=True
|
167 |
|
168 |
)
|
169 |
+
# Generate a permalink based on the current model, images, and folder name.
|
170 |
+
permalink_button.click(
|
171 |
+
lambda model, images, folder: storage.upload_files_to_repo(
|
172 |
+
files=[model] + list(images),
|
173 |
+
repo_id="Surn/Storage",
|
174 |
+
folder_name=folder,
|
175 |
+
create_permalink=True,
|
176 |
+
repo_type="dataset"
|
177 |
+
)[1], # Extract the permalink from the returned tuple if criteria met.
|
178 |
+
inputs=[model_3d, image_slider, folder_name_box],
|
179 |
+
outputs=[permalink],
|
180 |
+
scroll_to_output=True
|
181 |
+
).then(
|
182 |
+
lambda link: gr.update(visible=True) if link and len(link) > 0 else gr.update(visible=False),
|
183 |
+
inputs=[permalink],
|
184 |
+
outputs=[permalink_row]
|
185 |
+
)
|
186 |
|
187 |
if __name__ == "__main__":
|
188 |
viewer3d.launch(
|
189 |
+
allowed_paths=["assets", "assets/", "./assets", "images/", "./images", 'e:/TMP', 'models/', '3d_model_viewer/'],
|
190 |
favicon_path="./assets/favicon.ico", show_api=True, strict_cors=False
|
191 |
)
|
modules/constants.py
CHANGED
@@ -21,4 +21,10 @@ try:
|
|
21 |
except:
|
22 |
TMPDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
|
23 |
|
24 |
-
os.makedirs(TMPDIR, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
except:
|
22 |
TMPDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
|
23 |
|
24 |
+
os.makedirs(TMPDIR, exist_ok=True)
|
25 |
+
|
26 |
+
model_extensions = {".glb", ".gltf", ".obj", ".ply"}
|
27 |
+
model_extensions_list = list(model_extensions)
|
28 |
+
image_extensions = {".png", ".jpg", ".jpeg", ".webp"}
|
29 |
+
image_extensions_list = list(image_extensions)
|
30 |
+
upload_file_types = model_extensions_list + image_extensions_list
|
modules/storage.py
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# modules/storage.py
|
2 |
+
import os
|
3 |
+
import urllib.parse
|
4 |
+
import tempfile
|
5 |
+
import shutil
|
6 |
+
from huggingface_hub import login, upload_folder
|
7 |
+
from modules.constants import HF_API_TOKEN, upload_file_types, model_extensions, image_extensions
|
8 |
+
|
9 |
+
def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, repo_type="dataset"):
|
10 |
+
"""
|
11 |
+
Uploads multiple files to a Hugging Face repository using a batch upload approach via upload_folder.
|
12 |
+
|
13 |
+
Parameters:
|
14 |
+
files (list): A list of file paths (str) to upload.
|
15 |
+
repo_id (str): The repository ID on Hugging Face for storage, e.g. "Surn/Storage".
|
16 |
+
folder_name (str): The subfolder within the repository where files will be saved.
|
17 |
+
create_permalink (bool): If True and if exactly three files are uploaded (1 model and 2 images),
|
18 |
+
returns a single permalink to the project with query parameters.
|
19 |
+
Otherwise, returns individual permalinks for each file.
|
20 |
+
repo_type (str): Repository type ("space", "dataset", etc.). Default is "dataset".
|
21 |
+
|
22 |
+
Returns:
|
23 |
+
If create_permalink is True and files match the criteria:
|
24 |
+
tuple: (response, permalink) where response is the output of the batch upload
|
25 |
+
and permalink is the URL string (with fully qualified file paths) for the project.
|
26 |
+
Otherwise:
|
27 |
+
list: A list of tuples (response, permalink) for each file.
|
28 |
+
"""
|
29 |
+
# Log in using the HF API token.
|
30 |
+
login(token=HF_API_TOKEN)
|
31 |
+
|
32 |
+
valid_files = []
|
33 |
+
|
34 |
+
# Ensure folder_name does not have a trailing slash.
|
35 |
+
folder_name = folder_name.rstrip("/")
|
36 |
+
|
37 |
+
# Filter for valid files based on allowed extensions.
|
38 |
+
for f in files:
|
39 |
+
file_name = f if isinstance(f, str) else f.name if hasattr(f, "name") else None
|
40 |
+
if file_name is None:
|
41 |
+
continue
|
42 |
+
ext = os.path.splitext(file_name)[1].lower()
|
43 |
+
if ext in upload_file_types:
|
44 |
+
valid_files.append(f)
|
45 |
+
|
46 |
+
if not valid_files:
|
47 |
+
return [] # or raise an exception
|
48 |
+
|
49 |
+
# Create a temporary directory and a sub-directory using folder_name; copy valid files into it.
|
50 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
51 |
+
target_dir = os.path.join(temp_dir, folder_name)
|
52 |
+
os.makedirs(target_dir, exist_ok=True)
|
53 |
+
for file_path in valid_files:
|
54 |
+
filename = os.path.basename(file_path)
|
55 |
+
dest_path = os.path.join(target_dir, filename)
|
56 |
+
shutil.copy(file_path, dest_path)
|
57 |
+
|
58 |
+
# Batch upload all files in the temporary folder.
|
59 |
+
# Files will be uploaded under the folder (path_in_repo) given by folder_name.
|
60 |
+
response = upload_folder(
|
61 |
+
folder_path=temp_dir,
|
62 |
+
repo_id=repo_id,
|
63 |
+
repo_type=repo_type,
|
64 |
+
path_in_repo=folder_name,
|
65 |
+
commit_message="Batch upload files"
|
66 |
+
)
|
67 |
+
|
68 |
+
# Construct external URLs for each uploaded file.
|
69 |
+
# For datasets, files are served at:
|
70 |
+
# https://huggingface.co/datasets/<repo_id>/resolve/main/<folder_name>/<filename>
|
71 |
+
base_url_external = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{folder_name}"
|
72 |
+
individual_links = []
|
73 |
+
for file_path in valid_files:
|
74 |
+
filename = os.path.basename(file_path)
|
75 |
+
link = f"{base_url_external}/{filename}"
|
76 |
+
individual_links.append(link)
|
77 |
+
|
78 |
+
# If exactly 3 files are provided and create_permalink is True,
|
79 |
+
# check if they contain 1 model file and 2 image files.
|
80 |
+
if create_permalink and len(valid_files) == 3:
|
81 |
+
model_link = None
|
82 |
+
images_links = []
|
83 |
+
for f in valid_files:
|
84 |
+
filename = os.path.basename(f)
|
85 |
+
ext = os.path.splitext(filename)[1].lower()
|
86 |
+
if ext in model_extensions:
|
87 |
+
if model_link is None:
|
88 |
+
model_link = f"{base_url_external}/{filename}"
|
89 |
+
elif ext in image_extensions:
|
90 |
+
images_links.append(f"{base_url_external}/{filename}")
|
91 |
+
if model_link and len(images_links) == 2:
|
92 |
+
# Construct a permalink to the viewer project with querystring parameters.
|
93 |
+
base_viewer_url = "https://huggingface.co/spaces/Surn/3D-Viewer"
|
94 |
+
params = {"3d": model_link, "hm": images_links[0], "image": images_links[1]}
|
95 |
+
query_str = urllib.parse.urlencode(params)
|
96 |
+
permalink = f"{base_viewer_url}?{query_str}"
|
97 |
+
return response, permalink
|
98 |
+
|
99 |
+
# Otherwise, return individual tuples for each file.
|
100 |
+
return [(response, link) for link in individual_links]
|
style_20250503.css
CHANGED
@@ -102,7 +102,7 @@ a {
|
|
102 |
position: relative !important;
|
103 |
}
|
104 |
|
105 |
-
.gradio-container .image-container
|
106 |
object-fit: cover;
|
107 |
}
|
108 |
.gradio-container::before {
|
@@ -115,7 +115,7 @@ a {
|
|
115 |
height: 100%;
|
116 |
opacity: 0.25;
|
117 |
background-image: url('gradio_api/file=./images/logo.png');
|
118 |
-
background-repeat:repeat
|
119 |
background-position: 50% 0;
|
120 |
background-size: contain;
|
121 |
background-color: rgba(0,0,0,0.9);
|
|
|
102 |
position: relative !important;
|
103 |
}
|
104 |
|
105 |
+
.gradio-container .image-container .preview.svelte-k63p1v {
|
106 |
object-fit: cover;
|
107 |
}
|
108 |
.gradio-container::before {
|
|
|
115 |
height: 100%;
|
116 |
opacity: 0.25;
|
117 |
background-image: url('gradio_api/file=./images/logo.png');
|
118 |
+
background-repeat:repeat;
|
119 |
background-position: 50% 0;
|
120 |
background-size: contain;
|
121 |
background-color: rgba(0,0,0,0.9);
|