| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- """API routes for local slicer presets (imported from OrcaSlicer, etc.)."""
- import json
- import logging
- from fastapi import APIRouter, Depends, HTTPException, UploadFile
- from sqlalchemy import select
- from sqlalchemy.ext.asyncio import AsyncSession
- from backend.app.core.auth import RequirePermissionIfAuthEnabled
- from backend.app.core.database import get_db
- from backend.app.core.permissions import Permission
- from backend.app.models.local_preset import LocalPreset
- from backend.app.models.user import User
- from backend.app.schemas.local_preset import (
- ImportResponse,
- LocalPresetCreate,
- LocalPresetDetail,
- LocalPresetResponse,
- LocalPresetsResponse,
- LocalPresetUpdate,
- )
- from backend.app.services.orca_profiles import (
- extract_core_fields,
- get_cache_status,
- import_orca_file,
- reclassify_presets,
- refresh_base_cache,
- resolve_preset,
- )
- logger = logging.getLogger(__name__)
- router = APIRouter(prefix="/local-presets", tags=["Local Presets"])
- @router.get("/", response_model=LocalPresetsResponse)
- async def list_local_presets(
- _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_READ),
- db: AsyncSession = Depends(get_db),
- ):
- """List all local presets grouped by type."""
- result = await db.execute(select(LocalPreset).order_by(LocalPreset.name))
- presets = result.scalars().all()
- grouped = LocalPresetsResponse()
- for p in presets:
- resp = LocalPresetResponse.model_validate(p)
- if p.preset_type == "filament":
- grouped.filament.append(resp)
- elif p.preset_type == "printer":
- grouped.printer.append(resp)
- elif p.preset_type == "process":
- grouped.process.append(resp)
- return grouped
- @router.get("/{preset_id}", response_model=LocalPresetDetail)
- async def get_local_preset(
- preset_id: int,
- _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_READ),
- db: AsyncSession = Depends(get_db),
- ):
- """Get full detail for a local preset including the setting JSON."""
- result = await db.execute(select(LocalPreset).where(LocalPreset.id == preset_id))
- preset = result.scalar_one_or_none()
- if not preset:
- raise HTTPException(404, "Local preset not found")
- data = LocalPresetResponse.model_validate(preset).model_dump()
- try:
- data["setting"] = json.loads(preset.setting)
- except Exception:
- data["setting"] = {}
- return LocalPresetDetail(**data)
- @router.post("/import", response_model=ImportResponse)
- async def import_presets(
- file: UploadFile,
- _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_UPDATE),
- db: AsyncSession = Depends(get_db),
- ):
- """Import presets from an OrcaSlicer export file (.json, .orca_filament, .bbscfg, .bbsflmt, .zip)."""
- if not file.filename:
- raise HTTPException(400, "No filename provided")
- content = await file.read()
- if not content:
- raise HTTPException(400, "Empty file")
- result = await import_orca_file(file.filename, content, db)
- return ImportResponse(**result)
- @router.post("/", response_model=LocalPresetResponse)
- async def create_local_preset(
- data: LocalPresetCreate,
- _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_UPDATE),
- db: AsyncSession = Depends(get_db),
- ):
- """Manually create a local preset."""
- if data.preset_type not in ("filament", "printer", "process"):
- raise HTTPException(400, "preset_type must be filament, printer, or process")
- # Extract core fields
- core = extract_core_fields(data.setting)
- preset = LocalPreset(
- name=data.name,
- preset_type=data.preset_type,
- source="manual",
- setting=json.dumps(data.setting),
- **core,
- )
- db.add(preset)
- await db.flush()
- await db.refresh(preset)
- return LocalPresetResponse.model_validate(preset)
- @router.put("/{preset_id}", response_model=LocalPresetResponse)
- async def update_local_preset(
- preset_id: int,
- data: LocalPresetUpdate,
- _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_UPDATE),
- db: AsyncSession = Depends(get_db),
- ):
- """Update a local preset's name or settings."""
- result = await db.execute(select(LocalPreset).where(LocalPreset.id == preset_id))
- preset = result.scalar_one_or_none()
- if not preset:
- raise HTTPException(404, "Local preset not found")
- if data.name is not None:
- preset.name = data.name
- if data.setting is not None:
- # Re-resolve and extract core fields
- resolved = await resolve_preset(data.setting, preset.preset_type, db)
- core = extract_core_fields(resolved)
- preset.setting = json.dumps(resolved)
- preset.filament_type = core.get("filament_type")
- preset.filament_vendor = core.get("filament_vendor")
- preset.nozzle_temp_min = core.get("nozzle_temp_min")
- preset.nozzle_temp_max = core.get("nozzle_temp_max")
- preset.pressure_advance = core.get("pressure_advance")
- preset.default_filament_colour = core.get("default_filament_colour")
- preset.filament_cost = core.get("filament_cost")
- preset.filament_density = core.get("filament_density")
- preset.compatible_printers = core.get("compatible_printers")
- await db.flush()
- await db.refresh(preset)
- return LocalPresetResponse.model_validate(preset)
- @router.delete("/{preset_id}")
- async def delete_local_preset(
- preset_id: int,
- _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_UPDATE),
- db: AsyncSession = Depends(get_db),
- ):
- """Delete a local preset."""
- result = await db.execute(select(LocalPreset).where(LocalPreset.id == preset_id))
- preset = result.scalar_one_or_none()
- if not preset:
- raise HTTPException(404, "Local preset not found")
- await db.delete(preset)
- return {"success": True}
- @router.get("/base-cache/status")
- async def base_cache_status(
- _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_READ),
- db: AsyncSession = Depends(get_db),
- ):
- """Get the status of the OrcaSlicer base profile cache."""
- return await get_cache_status(db)
- @router.post("/base-cache/refresh")
- async def refresh_cache(
- _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_UPDATE),
- db: AsyncSession = Depends(get_db),
- ):
- """Force refresh all cached base profiles from GitHub."""
- return await refresh_base_cache(db)
- @router.post("/reclassify")
- async def reclassify(
- _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_UPDATE),
- db: AsyncSession = Depends(get_db),
- ):
- """Re-evaluate preset types for all local presets using the improved heuristic."""
- return await reclassify_presets(db)
|