From 3dbbd7ea198f2479ed0293c11dcc08783655c0bb Mon Sep 17 00:00:00 2001 From: maziggy Date: Wed, 14 Jan 2026 07:57:49 +0100 Subject: [PATCH] Fix FTP download failing for .3mf files without .gcode extension The cover and objects endpoints only tried downloading files with the .gcode.3mf extension pattern, but files uploaded directly (not sliced via Bambu Studio) are stored as plain .3mf files. This caused FTP error 550 (file not found) when trying to display cover images for prints started from BamBuddy archive. Now tries both naming patterns: - {name}.gcode.3mf (sliced via Bambu Studio) - {name}.3mf (uploaded directly) Fixes #79 --- backend/app/api/routes/printers.py | 66 +++++++++++++++++++----------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/backend/app/api/routes/printers.py b/backend/app/api/routes/printers.py index 106a8a53c..f45fc69d3 100644 --- a/backend/app/api/routes/printers.py +++ b/backend/app/api/routes/printers.py @@ -488,24 +488,37 @@ async def get_printer_cover( if cache_key in _cover_cache[printer_id]: return Response(content=_cover_cache[printer_id][cache_key], media_type="image/png") - # Build 3MF filename from subtask_name - # Bambu printers store files as "name.gcode.3mf" - filename = subtask_name - if not filename.endswith(".3mf"): - filename = filename + ".gcode.3mf" + # Build possible 3MF filenames from subtask_name + # Bambu printers may store files as "name.gcode.3mf" (sliced via Bambu Studio) + # or just "name.3mf" (uploaded directly) + possible_filenames = [] + if subtask_name.endswith(".3mf"): + possible_filenames.append(subtask_name) + else: + # Try both naming patterns + possible_filenames.append(f"{subtask_name}.gcode.3mf") + possible_filenames.append(f"{subtask_name}.3mf") - # Try to download the 3MF file from printer - temp_path = settings.archive_dir / "temp" / f"cover_{printer_id}_{filename}" + # Build list of all remote paths to try + remote_paths = [] + for filename in possible_filenames: + remote_paths.extend( + [ + f"/{filename}", # Root directory (most common) + f"/cache/{filename}", + f"/model/{filename}", + f"/data/{filename}", + ] + ) + + # Use first filename for temp path (will be reused) + temp_filename = possible_filenames[0] + temp_path = settings.archive_dir / "temp" / f"cover_{printer_id}_{temp_filename}" temp_path.parent.mkdir(parents=True, exist_ok=True) - remote_paths = [ - f"/{filename}", # Root directory (most common) - f"/cache/{filename}", - f"/model/{filename}", - f"/data/{filename}", - ] - - logger.info(f"Trying to download cover for '{filename}' from {printer.ip_address}") + logger.info( + f"Trying to download cover for '{subtask_name}' from {printer.ip_address} (trying {len(remote_paths)} paths)" + ) # Retry logic for transient FTP failures max_retries = 2 @@ -535,7 +548,8 @@ async def get_printer_cover( if not downloaded: raise HTTPException( - 404, f"Could not download 3MF file '{filename}' from printer {printer.ip_address}. Tried: {remote_paths}" + 404, + f"Could not download 3MF file for '{subtask_name}' from printer {printer.ip_address}. Tried: {possible_filenames}", ) # Verify file actually exists and has content @@ -547,7 +561,7 @@ async def get_printer_cover( if file_size == 0: temp_path.unlink() - raise HTTPException(500, f"Downloaded file is empty: {filename}") + raise HTTPException(500, f"Downloaded file is empty for '{subtask_name}'") try: # Extract thumbnail from 3MF (which is a ZIP file) @@ -1207,16 +1221,22 @@ async def get_printable_objects( from backend.app.services.archive import extract_printable_objects_from_3mf from backend.app.services.bambu_ftp import download_file_try_paths_async - # Build 3MF filename - filename = subtask_name - if not filename.endswith(".3mf"): - filename = filename + ".gcode.3mf" + # Build possible 3MF filenames (try both .gcode.3mf and .3mf) + possible_filenames = [] + if subtask_name.endswith(".3mf"): + possible_filenames.append(subtask_name) + else: + possible_filenames.append(f"{subtask_name}.gcode.3mf") + possible_filenames.append(f"{subtask_name}.3mf") # Download 3MF from printer - temp_path = settings.archive_dir / "temp" / f"objects_{printer_id}_{filename}" + temp_path = settings.archive_dir / "temp" / f"objects_{printer_id}_{possible_filenames[0]}" temp_path.parent.mkdir(parents=True, exist_ok=True) - remote_paths = [f"/{filename}", f"/cache/{filename}", f"/model/{filename}"] + # Build list of all remote paths to try + remote_paths = [] + for filename in possible_filenames: + remote_paths.extend([f"/{filename}", f"/cache/{filename}", f"/model/{filename}"]) try: downloaded = await download_file_try_paths_async(