Surn commited on
Commit
3124586
·
1 Parent(s): 9259c5f

Permalink Update

Browse files
Files changed (5) hide show
  1. app.py +88 -9
  2. requirements.txt +3 -3
  3. utils/constants.py +6 -0
  4. utils/file_utils.py +1 -20
  5. utils/storage.py +127 -0
app.py CHANGED
@@ -34,9 +34,12 @@ import gc
34
  # Import functions from modules
35
  from utils.file_utils import (
36
  cleanup_temp_files,
37
- get_file_parts,
38
- generate_permalink_from_urls
39
  )
 
 
 
 
40
 
41
  from utils.color_utils import (
42
  hex_to_rgb,
@@ -133,6 +136,7 @@ user_dir = constants.TMPDIR
133
  lora_models = get_lora_models()
134
  selected_index = gr.State(value=-1)
135
  #html_versions = version_info.versions_html()
 
136
 
137
  image_processor: Optional[DPTImageProcessor] = None
138
  depth_model: Optional[DPTForDepthEstimation] = None
@@ -804,7 +808,7 @@ def load_trellis_model():
804
  loaded = False
805
  if TRELLIS_PIPELINE == None:
806
  try:
807
- TRELLIS_PIPELINE = TrellisImageTo3DPipeline.from_pretrained("jetx/TRELLIS-image-large")
808
  TRELLIS_PIPELINE.cuda()
809
  # Preload with a dummy image to finalize initialization
810
  try:
@@ -1175,6 +1179,42 @@ def update_permalink(glb, gaussian, depth_out, depth_src_file):
1175
  permalink = generate_permalink_from_urls(smallest_file, depth_out, depth_src_file)
1176
  return gr.update(visible=True), permalink
1177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1178
  @spaces.GPU()
1179
  def getVersions():
1180
  #return html_versions
@@ -1595,9 +1635,28 @@ with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty',
1595
  gr.Markdown("""
1596
  ### Files over 10 MB may not display in the 3D model viewer
1597
  """, elem_id="file_size_info", elem_classes="intro" )
 
 
 
 
 
 
 
 
 
 
 
 
 
1598
  with gr.Row(visible=False) as permalink_row:
1599
- permalink_button = gr.Button("View in Permalink", elem_classes="solid small centered", variant="secondary")
1600
- permalink_text = gr.Textbox(label="Permalink", elem_classes="solid small centered")
 
 
 
 
 
 
1601
 
1602
  is_multiimage = gr.State(False)
1603
  output_buf = gr.State()
@@ -1847,8 +1906,8 @@ with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty',
1847
  outputs=[glb_file]
1848
  ).then(
1849
  fn=update_permalink,
1850
- inputs=[glb_file, gaussian_file, depth_output, ddd_image_path], # ddd_image_path now holds the source file
1851
- outputs=[permalink_row, permalink_text]
1852
  )
1853
 
1854
  extract_gaussian_btn.click(
@@ -1861,9 +1920,29 @@ with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty',
1861
  ).then(
1862
  fn=update_permalink,
1863
  inputs=[glb_file, gaussian_file, depth_output, ddd_image_path], # Use the actual depth source file here
1864
- outputs=[permalink_row, permalink_text]
1865
  )
1866
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1867
  if __name__ == "__main__":
1868
  constants.load_env_vars(constants.dotenv_path)
1869
  logging.basicConfig(
@@ -1896,7 +1975,7 @@ if __name__ == "__main__":
1896
  # image_processor = DPTImageProcessor.from_pretrained("Intel/dpt-large")
1897
  # depth_model = DPTForDepthEstimation.from_pretrained("Intel/dpt-large", ignore_mismatched_sizes=True)
1898
  if constants.IS_SHARED_SPACE:
1899
- TRELLIS_PIPELINE = TrellisImageTo3DPipeline.from_pretrained("jetx/TRELLIS-image-large")
1900
  TRELLIS_PIPELINE.to(device)
1901
  try:
1902
  TRELLIS_PIPELINE.preprocess_image(Image.fromarray(np.zeros((512, 512, 3), dtype=np.uint8)), 512, True) # Preload rembg
 
34
  # Import functions from modules
35
  from utils.file_utils import (
36
  cleanup_temp_files,
37
+ get_file_parts
 
38
  )
39
+ from utils.storage import (
40
+ generate_permalink_from_urls,
41
+ upload_files_to_repo,
42
+ )
43
 
44
  from utils.color_utils import (
45
  hex_to_rgb,
 
136
  lora_models = get_lora_models()
137
  selected_index = gr.State(value=-1)
138
  #html_versions = version_info.versions_html()
139
+ default_folder = "saved_models/3d_model_" + format(random.randint(1, 999999), "06d")
140
 
141
  image_processor: Optional[DPTImageProcessor] = None
142
  depth_model: Optional[DPTForDepthEstimation] = None
 
808
  loaded = False
809
  if TRELLIS_PIPELINE == None:
810
  try:
811
+ TRELLIS_PIPELINE = TrellisImageTo3DPipeline.from_pretrained("Surn/TRELLIS-image-large")
812
  TRELLIS_PIPELINE.cuda()
813
  # Preload with a dummy image to finalize initialization
814
  try:
 
1179
  permalink = generate_permalink_from_urls(smallest_file, depth_out, depth_src_file)
1180
  return gr.update(visible=True), permalink
1181
 
1182
+ def create_permalink(glb, gaussian, depth_out, depth_src_file, folder_name_permalink):
1183
+ """
1184
+ create a permalink for the 3D model and depth image.
1185
+ Args:
1186
+ glb (str): Path to the extracted GLB file.
1187
+ gaussian (str): Path to the extracted Gaussian file.
1188
+ depth_out (str): File path for the depth image.
1189
+ depth_src_file (str): File path for the image used to generate the 3D model.
1190
+ folder_path (str): The folder path where the files are stored.
1191
+ Returns:
1192
+ str: The permalink URL.
1193
+ """
1194
+ file_candidates = {}
1195
+ if glb and os.path.exists(glb):
1196
+ file_candidates[glb] = os.path.getsize(glb)
1197
+ if gaussian and os.path.exists(gaussian):
1198
+ file_candidates[gaussian] = os.path.getsize(gaussian)
1199
+
1200
+ # if not file_candidates:
1201
+ # return gr.update(visible=False), ""
1202
+ smallest_file = min(file_candidates, key=file_candidates.get)
1203
+
1204
+ permalink = upload_files_to_repo(
1205
+ files=[smallest_file, depth_out, depth_src_file],
1206
+ repo_id="Surn/Storage",
1207
+ folder_name=folder_name_permalink,
1208
+ create_permalink=True,
1209
+ repo_type="dataset"
1210
+ )[1]
1211
+ return permalink
1212
+
1213
+ def open_permalink(url: str) -> str:
1214
+ if url and url.strip():
1215
+ return f'<script>window.open("{url}", "_blank");</script>'
1216
+ return '<script>alert("Permalink textbox is empty!");</script>'
1217
+
1218
  @spaces.GPU()
1219
  def getVersions():
1220
  #return html_versions
 
1635
  gr.Markdown("""
1636
  ### Files over 10 MB may not display in the 3D model viewer
1637
  """, elem_id="file_size_info", elem_classes="intro" )
1638
+
1639
+ # New UI elements for general file upload and permalink generation
1640
+ with gr.Column(scale=1):
1641
+ folder_name_permalink = gr.Textbox(
1642
+ label="Folder Name in Permalink Repo",
1643
+ placeholder="e.g., my_cool_model_set",
1644
+ value=default_folder, # Default folder name
1645
+ elem_classes="solid small centered"
1646
+ )
1647
+ permalink_button = gr.Button(
1648
+ "Generate Permalink with Folder Name in Repo",
1649
+ elem_classes="solid small centered"
1650
+ )
1651
  with gr.Row(visible=False) as permalink_row:
1652
+ permalink = gr.Textbox(
1653
+ label="Viewer Permalink",
1654
+ show_copy_button=True,
1655
+ elem_classes="solid small centered",
1656
+ max_lines=3
1657
+ )
1658
+ view_permalink_button = gr.Button("View in Permalink", elem_classes="solid small centered", variant="secondary")
1659
+ hidden_html = gr.HTML(visible=False)
1660
 
1661
  is_multiimage = gr.State(False)
1662
  output_buf = gr.State()
 
1906
  outputs=[glb_file]
1907
  ).then(
1908
  fn=update_permalink,
1909
+ inputs=[glb_file, gaussian_file, depth_output, ddd_image_path], # Use the actual depth source file here
1910
+ outputs=[permalink_row, permalink] # This updates the GLB/Gaussian specific permalink
1911
  )
1912
 
1913
  extract_gaussian_btn.click(
 
1920
  ).then(
1921
  fn=update_permalink,
1922
  inputs=[glb_file, gaussian_file, depth_output, ddd_image_path], # Use the actual depth source file here
1923
+ outputs=[permalink_row, permalink] # This updates the GLB/Gaussian specific permalink
1924
  )
1925
 
1926
+ # Create a permalink based on the current model, images, and folder name.
1927
+ permalink_button.click(
1928
+ fn=create_permalink,
1929
+ inputs=[glb_file, gaussian_file, depth_output, ddd_image_path, folder_name_permalink],
1930
+ outputs=[permalink],
1931
+ scroll_to_output=True
1932
+ ).then(
1933
+ lambda link: gr.update(visible=True) if link and len(link) > 0 else gr.update(visible=False),
1934
+ inputs=[permalink],
1935
+ outputs=[permalink_row]
1936
+ )
1937
+
1938
+ view_permalink_button.click(
1939
+ fn=open_permalink,
1940
+ inputs=[permalink],
1941
+ outputs=[hidden_html],
1942
+ show_progress=False
1943
+ )
1944
+
1945
+
1946
  if __name__ == "__main__":
1947
  constants.load_env_vars(constants.dotenv_path)
1948
  logging.basicConfig(
 
1975
  # image_processor = DPTImageProcessor.from_pretrained("Intel/dpt-large")
1976
  # depth_model = DPTForDepthEstimation.from_pretrained("Intel/dpt-large", ignore_mismatched_sizes=True)
1977
  if constants.IS_SHARED_SPACE:
1978
+ TRELLIS_PIPELINE = TrellisImageTo3DPipeline.from_pretrained("Surn/TRELLIS-image-large")
1979
  TRELLIS_PIPELINE.to(device)
1980
  try:
1981
  TRELLIS_PIPELINE.preprocess_image(Image.fromarray(np.zeros((512, 512, 3), dtype=np.uint8)), 512, True) # Preload rembg
requirements.txt CHANGED
@@ -73,6 +73,6 @@ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.3/flash_attn
73
  https://huggingface.co/spaces/Surn/HexaGrid/resolve/main/wheels/diff_gaussian_rasterization-0.0.0-cp310-cp310-linux_x86_64.whl?download=true
74
  https://huggingface.co/spaces/Surn/Hexagrid/resolve/main/wheels/nvdiffrast-0.3.3-cp310-cp310-linux_x86_64.whl?download=true
75
  #Windows only
76
- #https://huggingface.co/spaces/Surn/HexaGrid/main/wheels/flash_attn-2.7.4.post1-cp312-cp312-win_amd64.whl?download=true
77
- #https://huggingface.co/spaces/Surn/HexaGrid/main/wheels/diff_gaussian_rasterization-0.0.0-cp312-cp312-win_amd64.whl?download=true
78
- #https://huggingface.co/spaces/Surn/HexaGrid/main/wheels/nvdiffrast-0.3.3-py3-none-any.whl?download=true
 
73
  https://huggingface.co/spaces/Surn/HexaGrid/resolve/main/wheels/diff_gaussian_rasterization-0.0.0-cp310-cp310-linux_x86_64.whl?download=true
74
  https://huggingface.co/spaces/Surn/Hexagrid/resolve/main/wheels/nvdiffrast-0.3.3-cp310-cp310-linux_x86_64.whl?download=true
75
  #Windows only
76
+ #https://huggingface.co/spaces/Surn/HexaGrid/resolve/main/wheels/flash_attn-2.7.4.post1-cp312-cp312-win_amd64.whl?download=true
77
+ #https://huggingface.co/spaces/Surn/HexaGrid/resolve/main/wheels/diff_gaussian_rasterization-0.0.0-cp312-cp312-win_amd64.whl?download=true
78
+ #https://huggingface.co/spaces/Surn/HexaGrid/resolve/main/wheels/nvdiffrast-0.3.3-py3-none-any.whl
utils/constants.py CHANGED
@@ -67,6 +67,12 @@ SCALE_FACTOR = (12/5)
67
  TMPDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
68
  os.makedirs(TMPDIR, exist_ok=True)
69
 
 
 
 
 
 
 
70
 
71
  PROMPTS = {
72
  "Mecha Wasteland Arena": "Regional overhead view, directly from above, centered on the map, orthographic Mecha battlefield map. post-industrial wasteland with crumbling structures, volcanic ridges, scrapyards, and ash plains. Features elevated overwatch positions for long-range combat and tight brawling areas for close-quarters engagements. Partial edge hexes are black. Colors: red, gray, muted orange, ash white, dark brown.",
 
67
  TMPDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
68
  os.makedirs(TMPDIR, exist_ok=True)
69
 
70
+ model_extensions = {".glb", ".gltf", ".obj", ".ply"}
71
+ model_extensions_list = list(model_extensions)
72
+ image_extensions = {".png", ".jpg", ".jpeg", ".webp"}
73
+ image_extensions_list = list(image_extensions)
74
+ upload_file_types = model_extensions_list + image_extensions_list
75
+
76
 
77
  PROMPTS = {
78
  "Mecha Wasteland Arena": "Regional overhead view, directly from above, centered on the map, orthographic Mecha battlefield map. post-industrial wasteland with crumbling structures, volcanic ridges, scrapyards, and ash plains. Features elevated overwatch positions for long-range combat and tight brawling areas for close-quarters engagements. Partial edge hexes are black. Colors: red, gray, muted orange, ash white, dark brown.",
utils/file_utils.py CHANGED
@@ -103,23 +103,4 @@ def get_unique_file_path(directory, filename, file_ext, counter=0):
103
  return get_unique_file_path(directory, filename, file_ext, counter + 1)
104
 
105
  # Example usage:
106
- # new_file_path = get_unique_file_path(video_dir, title_file_name, video_new_ext)
107
-
108
- def generate_permalink_from_urls(model_url, hm_url, img_url, permalink_viewer_url="surn-3d-viewer.hf.space"):
109
- """
110
- Constructs and returns a permalink URL with query string parameters for the viewer.
111
- Each parameter is passed separately so that the image positions remain consistent.
112
-
113
- Parameters:
114
- model_url (str): Processed URL for the 3D model.
115
- hm_url (str): Processed URL for the height map image.
116
- img_url (str): Processed URL for the main image.
117
- permalink_viewer_url (str): The base viewer URL.
118
-
119
- Returns:
120
- str: The generated permalink URL.
121
- """
122
- import urllib.parse
123
- params = {"3d": model_url, "hm": hm_url, "image": img_url}
124
- query_str = urllib.parse.urlencode(params)
125
- return f"https://{permalink_viewer_url}/?{query_str}"
 
103
  return get_unique_file_path(directory, filename, file_ext, counter + 1)
104
 
105
  # Example usage:
106
+ # new_file_path = get_unique_file_path(video_dir, title_file_name, video_new_ext)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/storage.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/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 utils.constants import HF_API_TOKEN, upload_file_types, model_extensions, image_extensions
8
+
9
+ def generate_permalink(valid_files, base_url_external, permalink_viewer_url="surn-3d-viewer.hf.space"):
10
+ """
11
+ Given a list of valid files, checks if they contain exactly 1 model file and 2 image files.
12
+ Constructs and returns a permalink URL with query parameters if the criteria is met.
13
+ Otherwise, returns None.
14
+ """
15
+ model_link = None
16
+ images_links = []
17
+ for f in valid_files:
18
+ filename = os.path.basename(f)
19
+ ext = os.path.splitext(filename)[1].lower()
20
+ if ext in model_extensions:
21
+ if model_link is None:
22
+ model_link = f"{base_url_external}/{filename}"
23
+ elif ext in image_extensions:
24
+ images_links.append(f"{base_url_external}/{filename}")
25
+ if model_link and len(images_links) == 2:
26
+ # Construct a permalink to the viewer project with query parameters.
27
+ permalink_viewer_url = f"https://{permalink_viewer_url}/"
28
+ params = {"3d": model_link, "hm": images_links[0], "image": images_links[1]}
29
+ query_str = urllib.parse.urlencode(params)
30
+ return f"{permalink_viewer_url}?{query_str}"
31
+ return None
32
+
33
+ def generate_permalink_from_urls(model_url, hm_url, img_url, permalink_viewer_url="surn-3d-viewer.hf.space"):
34
+ """
35
+ Constructs and returns a permalink URL with query string parameters for the viewer.
36
+ Each parameter is passed separately so that the image positions remain consistent.
37
+
38
+ Parameters:
39
+ model_url (str): Processed URL for the 3D model.
40
+ hm_url (str): Processed URL for the height map image.
41
+ img_url (str): Processed URL for the main image.
42
+ permalink_viewer_url (str): The base viewer URL.
43
+
44
+ Returns:
45
+ str: The generated permalink URL.
46
+ """
47
+ import urllib.parse
48
+ params = {"3d": model_url, "hm": hm_url, "image": img_url}
49
+ query_str = urllib.parse.urlencode(params)
50
+ return f"https://{permalink_viewer_url}/?{query_str}"
51
+
52
+ def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, repo_type="dataset", permalink_viewer_url="surn-3d-viewer.hf.space"):
53
+ """
54
+ Uploads multiple files to a Hugging Face repository using a batch upload approach via upload_folder.
55
+
56
+ Parameters:
57
+ files (list): A list of file paths (str) to upload.
58
+ repo_id (str): The repository ID on Hugging Face for storage, e.g. "Surn/Storage".
59
+ folder_name (str): The subfolder within the repository where files will be saved.
60
+ create_permalink (bool): If True and if exactly three files are uploaded (1 model and 2 images),
61
+ returns a single permalink to the project with query parameters.
62
+ Otherwise, returns individual permalinks for each file.
63
+ repo_type (str): Repository type ("space", "dataset", etc.). Default is "dataset".
64
+
65
+ Returns:
66
+ If create_permalink is True and files match the criteria:
67
+ tuple: (response, permalink) where response is the output of the batch upload
68
+ and permalink is the URL string (with fully qualified file paths) for the project.
69
+ Otherwise:
70
+ list: A list of tuples (response, permalink) for each file.
71
+ """
72
+ # Log in using the HF API token.
73
+ login(token=HF_API_TOKEN)
74
+
75
+ valid_files = []
76
+
77
+ # Ensure folder_name does not have a trailing slash.
78
+ folder_name = folder_name.rstrip("/")
79
+
80
+ # Filter for valid files based on allowed extensions.
81
+ for f in files:
82
+ file_name = f if isinstance(f, str) else f.name if hasattr(f, "name") else None
83
+ if file_name is None:
84
+ continue
85
+ ext = os.path.splitext(file_name)[1].lower()
86
+ if ext in upload_file_types:
87
+ valid_files.append(f)
88
+
89
+ if not valid_files:
90
+ return [] # or raise an exception
91
+
92
+ # Create a temporary directory; copy valid files directly into it.
93
+ with tempfile.TemporaryDirectory() as temp_dir:
94
+ for file_path in valid_files:
95
+ filename = os.path.basename(file_path)
96
+ dest_path = os.path.join(temp_dir, filename)
97
+ shutil.copy(file_path, dest_path)
98
+
99
+ # Batch upload all files in the temporary folder.
100
+ # Files will be uploaded under the folder (path_in_repo) given by folder_name.
101
+ response = upload_folder(
102
+ folder_path=temp_dir,
103
+ repo_id=repo_id,
104
+ repo_type=repo_type,
105
+ path_in_repo=folder_name,
106
+ commit_message="Batch upload files"
107
+ )
108
+
109
+ # Construct external URLs for each uploaded file.
110
+ # For datasets, files are served at:
111
+ # https://huggingface.co/datasets/<repo_id>/resolve/main/<folder_name>/<filename>
112
+ base_url_external = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{folder_name}"
113
+ individual_links = []
114
+ for file_path in valid_files:
115
+ filename = os.path.basename(file_path)
116
+ link = f"{base_url_external}/{filename}"
117
+ individual_links.append(link)
118
+
119
+ # If permalink creation is requested and exactly 3 valid files are provided,
120
+ # try to generate a permalink using generate_permalink().
121
+ if create_permalink and len(valid_files) == 3:
122
+ permalink = generate_permalink_from_urls(individual_links[0], individual_links[1], individual_links[2], permalink_viewer_url)
123
+ if permalink:
124
+ return [response, permalink]
125
+
126
+ # Otherwise, return individual tuples for each file.
127
+ return [(response, link) for link in individual_links]