55import logging
66from datetime import datetime , timezone
77from typing import Dict
8-
98from fastapi import APIRouter , Depends , HTTPException
109from sqlalchemy .orm import Session
1110
2322)
2423from api .security import require_write_token
2524from api .storage_dependencies import get_storage_client
26- from backend .services .storage import StorageClient
25+ from backend .services .storage import StorageClient , StoredObject
2726from services .cnc_service import CNCService
2827
2928logger = logging .getLogger (__name__ )
3231 tags = ["exports" ],
3332 dependencies = [Depends (require_write_token )],
3433)
34+ legacy_router = APIRouter (
35+ prefix = "/api/cnc" ,
36+ tags = ["cnc" ],
37+ dependencies = [Depends (require_write_token )],
38+ )
3539_cnc_service = CNCService ()
3640
41+ __all__ = ["router" , "legacy_router" ]
42+
43+
44+ def _to_stored_artifact (obj : StoredObject ) -> StoredArtifact :
45+ return StoredArtifact (
46+ bucket = obj .bucket ,
47+ key = obj .key ,
48+ url = obj .url ,
49+ content_type = obj .content_type ,
50+ )
51+
52+
53+ def _export_cnc (
54+ payload : CNCExportRequest ,
55+ db : Session ,
56+ storage : StorageClient ,
57+ settings : Settings ,
58+ ) -> CNCExportResponse :
59+ module_rows = db .query (models .Module ).all ()
60+ material_rows = db .query (models .Material ).all ()
61+
62+ modules_by_id : Dict [str , ModuleDTO ] = {
63+ module .id : ModuleDTO .model_validate (module ) for module in module_rows
64+ }
65+ materials_by_id : Dict [str , MaterialDTO ] = {
66+ material .id : MaterialDTO .model_validate (material )
67+ for material in material_rows
68+ }
69+
70+ artifacts = _cnc_service .export (
71+ request = payload ,
72+ modules_by_id = modules_by_id ,
73+ materials_by_id = materials_by_id ,
74+ )
75+
76+ timestamp = datetime .now (timezone .utc ).strftime ("%Y%m%dT%H%M%S" )
77+ prefix = settings .cnc_exports_prefix .strip ("/" )
78+ configuration_segment = payload .configuration_id .replace (" " , "-" )
79+ base_path_parts = [
80+ segment for segment in (prefix , configuration_segment , timestamp ) if segment
81+ ]
82+ base_path = "/" .join (base_path_parts ) if base_path_parts else timestamp
83+
84+ csv_key = f"{ base_path } /cut-list.csv"
85+ svg_key = f"{ base_path } /nesting.svg"
86+
87+ artifacts .cut_list_csv .seek (0 )
88+ csv_object = storage .upload_fileobj (
89+ artifacts .cut_list_csv ,
90+ csv_key ,
91+ content_type = "text/csv" ,
92+ )
93+
94+ artifacts .nesting_svg .seek (0 )
95+ svg_object = storage .upload_fileobj (
96+ artifacts .nesting_svg ,
97+ svg_key ,
98+ content_type = "image/svg+xml" ,
99+ )
100+
101+ placements = [
102+ CNCNestingPlacement (
103+ panel_id = placement .panel_id ,
104+ x = placement .x ,
105+ y = placement .y ,
106+ rotation = placement .rotation ,
107+ sheet_index = placement .sheet_index ,
108+ )
109+ for placement in artifacts .nesting .placements
110+ ]
111+
112+ return CNCExportResponse (
113+ panels = artifacts .panels ,
114+ cut_list = _to_stored_artifact (csv_object ),
115+ nesting = CNCNestingMetadata (
116+ sheet_width = artifacts .nesting .sheet_width ,
117+ sheet_height = artifacts .nesting .sheet_height ,
118+ placements = placements ,
119+ svg = _to_stored_artifact (svg_object ),
120+ ),
121+ )
122+
37123
38124@router .post ("/cnc" , response_model = CNCExportResponse )
39125def export_cnc (
@@ -45,81 +131,29 @@ def export_cnc(
45131 """Generate CNC export artifacts and store them via the configured storage client."""
46132
47133 try :
48- module_rows = db .query (models .Module ).all ()
49- material_rows = db .query (models .Material ).all ()
50-
51- modules_by_id : Dict [str , ModuleDTO ] = {
52- module .id : ModuleDTO .model_validate (module ) for module in module_rows
53- }
54- materials_by_id : Dict [str , MaterialDTO ] = {
55- material .id : MaterialDTO .model_validate (material )
56- for material in material_rows
57- }
58-
59- artifacts = _cnc_service .export (
60- request = payload ,
61- modules_by_id = modules_by_id ,
62- materials_by_id = materials_by_id ,
63- )
64-
65- timestamp = datetime .now (timezone .utc ).strftime ("%Y%m%dT%H%M%S" )
66- prefix = settings .cnc_exports_prefix .strip ("/" )
67- configuration_segment = payload .configuration_id .replace (" " , "-" )
68- base_path_parts = [
69- segment for segment in (prefix , configuration_segment , timestamp ) if segment
70- ]
71- base_path = "/" .join (base_path_parts ) if base_path_parts else timestamp
72-
73- csv_key = f"{ base_path } /cut-list.csv"
74- svg_key = f"{ base_path } /nesting.svg"
75-
76- artifacts .cut_list_csv .seek (0 )
77- csv_object = storage .upload_fileobj (
78- artifacts .cut_list_csv ,
79- csv_key ,
80- content_type = "text/csv" ,
81- )
134+ return _export_cnc (payload , db , storage , settings )
135+ except HTTPException :
136+ raise
137+ except Exception as exc : # noqa: BLE001
138+ logger .exception ("Error exporting CNC" )
139+ raise HTTPException (status_code = 500 , detail = str (exc )) from exc
82140
83- artifacts .nesting_svg .seek (0 )
84- svg_object = storage .upload_fileobj (
85- artifacts .nesting_svg ,
86- svg_key ,
87- content_type = "image/svg+xml" ,
88- )
89141
90- placements = [
91- CNCNestingPlacement (
92- panel_id = placement .panel_id ,
93- x = placement .x ,
94- y = placement .y ,
95- rotation = placement .rotation ,
96- sheet_index = placement .sheet_index ,
97- )
98- for placement in artifacts .nesting .placements
99- ]
100-
101- return CNCExportResponse (
102- panels = artifacts .panels ,
103- cut_list = StoredArtifact (
104- bucket = csv_object .bucket ,
105- key = csv_object .key ,
106- url = csv_object .url ,
107- content_type = csv_object .content_type ,
108- ),
109- nesting = CNCNestingMetadata (
110- sheet_width = artifacts .nesting .sheet_width ,
111- sheet_height = artifacts .nesting .sheet_height ,
112- placements = placements ,
113- svg = StoredArtifact (
114- bucket = svg_object .bucket ,
115- key = svg_object .key ,
116- url = svg_object .url ,
117- content_type = svg_object .content_type ,
118- ),
119- ),
120- )
142+ @legacy_router .post (
143+ "/export" ,
144+ response_model = CNCExportResponse ,
145+ include_in_schema = False ,
146+ )
147+ def export_cnc_legacy (
148+ payload : CNCExportRequest ,
149+ db : Session = Depends (get_db ),
150+ storage : StorageClient = Depends (get_storage_client ),
151+ settings : Settings = Depends (get_settings ),
152+ ) -> CNCExportResponse :
153+ try :
154+ return _export_cnc (payload , db , storage , settings )
121155 except HTTPException :
122156 raise
123157 except Exception as exc : # noqa: BLE001
124- logger .exception ("Error exporting CNC" )
158+ logger .exception ("Error exporting CNC (legacy path) " )
125159 raise HTTPException (status_code = 500 , detail = str (exc )) from exc
0 commit comments