|
|
@@ -0,0 +1,107 @@
|
|
|
+import re
|
|
|
+import requests
|
|
|
+import os
|
|
|
+import zipfile
|
|
|
+import io
|
|
|
+import uuid
|
|
|
+from typing import List, Dict
|
|
|
+
|
|
|
+# Standard headers to avoid bot detection
|
|
|
+HEADERS = {
|
|
|
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
|
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
|
+}
|
|
|
+
|
|
|
+class LinkImporter:
|
|
|
+ @staticmethod
|
|
|
+ def extract_id(url: str):
|
|
|
+ # Thingiverse: thingiverse.com/thing:12345 or thingiverse.com/thing:12345/files
|
|
|
+ thing_match = re.search(r"thingiverse\.com/thing:(\d+)", url)
|
|
|
+ if thing_match:
|
|
|
+ return "thingiverse", thing_match.group(1)
|
|
|
+
|
|
|
+ # Printables: printables.com/model/12345-name or printables.com/en/model/12345
|
|
|
+ printables_match = re.search(r"printables\.com/(?:[a-z]{2}/)?model/(\d+)", url)
|
|
|
+ if printables_match:
|
|
|
+ return "printables", printables_match.group(1)
|
|
|
+
|
|
|
+ return None, None
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def download_and_extract(url: str, upload_dir: str, background_tasks=None) -> List[Dict]:
|
|
|
+ service, item_id = LinkImporter.extract_id(url)
|
|
|
+ if not service:
|
|
|
+ raise ValueError("Unsupported URL format. Please use Thingiverse or Printables link.")
|
|
|
+
|
|
|
+ download_url = ""
|
|
|
+ if service == "thingiverse":
|
|
|
+ download_url = f"https://www.thingiverse.com/thing:{item_id}/zip"
|
|
|
+ elif service == "printables":
|
|
|
+ download_url = f"https://www.printables.com/model/{item_id}/files/zip"
|
|
|
+
|
|
|
+ print(f"DEBUG: Importing from {service} ID {item_id}. URL: {download_url}")
|
|
|
+
|
|
|
+ try:
|
|
|
+ response = requests.get(download_url, headers=HEADERS, stream=True, timeout=60)
|
|
|
+ response.raise_for_status()
|
|
|
+ except Exception as e:
|
|
|
+ raise Exception(f"Failed to download from {service}: {e}")
|
|
|
+
|
|
|
+ import db
|
|
|
+ import hashlib
|
|
|
+ from services import order_processing
|
|
|
+
|
|
|
+ # Process the ZIP in memory
|
|
|
+ imported_files = []
|
|
|
+ with zipfile.ZipFile(io.BytesIO(response.content)) as z:
|
|
|
+ for file_info in z.infolist():
|
|
|
+ filename = os.path.basename(file_info.filename)
|
|
|
+ if not filename or filename.startswith('.') or filename.startswith('__MACOSX'):
|
|
|
+ continue
|
|
|
+
|
|
|
+ ext = os.path.splitext(filename)[1].lower()
|
|
|
+ if ext in ['.stl', '.obj', '.step', '.stp', '.3mf']:
|
|
|
+ unique_name = f"{uuid.uuid4().hex}{ext}"
|
|
|
+ save_path = os.path.join(upload_dir, unique_name)
|
|
|
+ db_file_path = f"uploads/{unique_name}"
|
|
|
+
|
|
|
+ # Read content to memory to hash it
|
|
|
+ content = z.read(file_info.filename)
|
|
|
+ with open(save_path, "wb") as target:
|
|
|
+ target.write(content)
|
|
|
+
|
|
|
+ file_hash = hashlib.sha256(content).hexdigest()
|
|
|
+ file_size = len(content)
|
|
|
+
|
|
|
+ # Check cache for filament/time
|
|
|
+ filament_g = None
|
|
|
+ print_time = None
|
|
|
+ cached = db.execute_query(
|
|
|
+ "SELECT filament_g, print_time FROM order_files WHERE file_hash = %s AND print_time IS NOT NULL LIMIT 1",
|
|
|
+ (file_hash,)
|
|
|
+ )
|
|
|
+ if cached:
|
|
|
+ filament_g = cached[0]['filament_g']
|
|
|
+ print_time = cached[0]['print_time']
|
|
|
+
|
|
|
+ # Insert into DB
|
|
|
+ query = "INSERT INTO order_files (order_id, filename, file_path, file_size, quantity, file_hash, print_time, filament_g, preview_path) VALUES (NULL, %s, %s, %s, 1, %s, %s, %s, NULL)"
|
|
|
+ f_id = db.execute_commit(query, (filename, db_file_path, file_size, file_hash, print_time, filament_g))
|
|
|
+
|
|
|
+ # Trigger background processing (previews, etc.)
|
|
|
+ if background_tasks:
|
|
|
+ background_tasks.add_task(order_processing.process_single_file_async, f_id)
|
|
|
+
|
|
|
+ imported_files.append({
|
|
|
+ "id": f_id,
|
|
|
+ "filename": filename,
|
|
|
+ "size": file_size,
|
|
|
+ "print_time": print_time,
|
|
|
+ "filament_g": filament_g,
|
|
|
+ "status": "success"
|
|
|
+ })
|
|
|
+
|
|
|
+ if not imported_files:
|
|
|
+ raise Exception("No valid 3D models found in the downloaded archive.")
|
|
|
+
|
|
|
+ return imported_files
|