feat(orca): added orcaslicer preset
This commit is contained in:
132
backend/main.py
132
backend/main.py
@@ -1,19 +1,13 @@
|
||||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
import logging
|
||||
from fastapi import FastAPI, UploadFile, File, HTTPException
|
||||
import os
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Import custom modules
|
||||
from config import settings
|
||||
from slicer import slicer_service
|
||||
from calculator import GCodeParser, QuoteCalculator
|
||||
from api.routes import router as api_router
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("api")
|
||||
logger = logging.getLogger("main")
|
||||
|
||||
app = FastAPI(title="Print Calculator API")
|
||||
|
||||
@@ -29,85 +23,53 @@ app.add_middleware(
|
||||
# Ensure directories exist
|
||||
os.makedirs(settings.TEMP_DIR, exist_ok=True)
|
||||
|
||||
class QuoteResponse(BaseModel):
|
||||
printer: str
|
||||
print_time_seconds: int
|
||||
print_time_formatted: str
|
||||
material_grams: float
|
||||
cost: dict
|
||||
notes: list[str] = []
|
||||
# Include Router
|
||||
app.include_router(api_router, prefix="/api")
|
||||
|
||||
def cleanup_files(files: list):
|
||||
for f in files:
|
||||
try:
|
||||
if os.path.exists(f):
|
||||
os.remove(f)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete temp file {f}: {e}")
|
||||
# Legacy endpoint redirect or basic handler if needed for backward compatibility
|
||||
# The frontend likely calls /calculate/stl.
|
||||
# We should probably keep the old route or instruct user to update frontend.
|
||||
# But for this task, let's remap the old route to the new logic if possible,
|
||||
# or just expose the new route.
|
||||
# The user request said: "Creare api/routes.py ... @app.post('/api/quote')"
|
||||
# So we are creating a new endpoint.
|
||||
# Existing frontend might break?
|
||||
# The context says: "Currently uses hardcoded... Objective is to render system flexible... Frontend: Angular 19"
|
||||
# The user didn't explicitly ask to update the frontend, but the new API is at /api/quote.
|
||||
# I will keep the old "/calculate/stl" endpoint support by forwarding it or duplicating logic if critical,
|
||||
# OR I'll assume the user will handle frontend updates.
|
||||
# Better: I will alias the old route to the new one if parameters allow,
|
||||
# but the new one expects Form data with different names maybe?
|
||||
# Old: `/calculate/stl` just expected a file.
|
||||
# I'll enable a simplified version on the old route for backward compat using defaults.
|
||||
|
||||
def format_time(seconds: int) -> str:
|
||||
m, s = divmod(seconds, 60)
|
||||
h, m = divmod(m, 60)
|
||||
if h > 0:
|
||||
return f"{int(h)}h {int(m)}m"
|
||||
return f"{int(m)}m {int(s)}s"
|
||||
from fastapi import UploadFile, File
|
||||
from api.routes import calculate_quote
|
||||
|
||||
@app.post("/calculate/stl", response_model=QuoteResponse)
|
||||
async def calculate_from_stl(file: UploadFile = File(...)):
|
||||
if not file.filename.lower().endswith(".stl"):
|
||||
raise HTTPException(status_code=400, detail="Only .stl files are supported.")
|
||||
|
||||
# Unique ID for this request
|
||||
req_id = str(uuid.uuid4())
|
||||
input_filename = f"{req_id}.stl"
|
||||
output_filename = f"{req_id}.gcode"
|
||||
@app.post("/calculate/stl")
|
||||
async def legacy_calculate(file: UploadFile = File(...)):
|
||||
"""Legacy endpoint compatibility"""
|
||||
# Call the new logic with defaults
|
||||
resp = await calculate_quote(file=file)
|
||||
if not resp.success:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(status_code=500, detail=resp.error)
|
||||
|
||||
input_path = os.path.join(settings.TEMP_DIR, input_filename)
|
||||
output_path = os.path.join(settings.TEMP_DIR, output_filename)
|
||||
|
||||
try:
|
||||
# 1. Save Uploaded File
|
||||
logger.info(f"Received request {req_id} for file: {file.filename}")
|
||||
with open(input_path, "wb") as buffer:
|
||||
shutil.copyfileobj(file.file, buffer)
|
||||
|
||||
# 2. Slice
|
||||
# slicer_service methods raise exceptions on failure
|
||||
slicer_service.slice_stl(input_path, output_path)
|
||||
|
||||
# 3. Parse Results
|
||||
stats = GCodeParser.parse_metadata(output_path)
|
||||
|
||||
if stats["print_time_seconds"] == 0 and stats["filament_weight_g"] == 0:
|
||||
# Slicing likely failed or produced empty output without throwing error
|
||||
raise HTTPException(status_code=500, detail="Slicing completed but no stats found. Check mesh validity.")
|
||||
|
||||
# 4. Calculate Costs
|
||||
quote = QuoteCalculator.calculate(stats)
|
||||
|
||||
return {
|
||||
"printer": "BambuLab A1 (Estimated)",
|
||||
"print_time_seconds": stats["print_time_seconds"],
|
||||
"print_time_formatted": format_time(stats["print_time_seconds"]),
|
||||
"material_grams": stats["filament_weight_g"],
|
||||
"cost": {
|
||||
"material": quote["breakdown"]["material_cost"],
|
||||
"machine": quote["breakdown"]["machine_cost"],
|
||||
"energy": quote["breakdown"]["energy_cost"],
|
||||
"markup": quote["breakdown"]["markup_amount"],
|
||||
"total": quote["total_price"]
|
||||
},
|
||||
"notes": ["Estimation generated using OrcaSlicer headless."]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing request: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
cleanup_files([input_path, output_path])
|
||||
# Map Check response to old format
|
||||
data = resp.data
|
||||
return {
|
||||
"printer": data.get("printer", "Unknown"),
|
||||
"print_time_seconds": data.get("print_time_seconds", 0),
|
||||
"print_time_formatted": data.get("print_time_formatted", ""),
|
||||
"material_grams": data.get("material_grams", 0.0),
|
||||
"cost": data.get("cost", {}),
|
||||
"notes": ["Generated via Dynamic Slicer (Legacy Endpoint)"]
|
||||
}
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
return {"status": "ok", "slicer": settings.SLICER_PATH}
|
||||
return {"status": "ok", "slicer": settings.SLICER_PATH}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
Reference in New Issue
Block a user