From db748fb6497955e862f355d52cb1911b7b9bd958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joe=20K=C3=BCng?= Date: Wed, 4 Mar 2026 09:24:21 +0100 Subject: [PATCH] fix(back-end): fix 3mf calculator --- .../controller/QuoteSessionController.java | 22 ++++++++++-- .../service/SlicerService.java | 36 ++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/printcalculator/controller/QuoteSessionController.java b/backend/src/main/java/com/printcalculator/controller/QuoteSessionController.java index 5ab36a9..b3724fe 100644 --- a/backend/src/main/java/com/printcalculator/controller/QuoteSessionController.java +++ b/backend/src/main/java/com/printcalculator/controller/QuoteSessionController.java @@ -146,6 +146,7 @@ public class QuoteSessionController { Files.copy(inputStream, persistentPath, StandardCopyOption.REPLACE_EXISTING); } + Path convertedPersistentPath = null; try { // Apply Basic/Advanced Logic applyPrintSettings(settings); @@ -182,10 +183,21 @@ public class QuoteSessionController { if (settings.getLayerHeight() != null) processOverrides.put("layer_height", String.valueOf(settings.getLayerHeight())); if (settings.getInfillDensity() != null) processOverrides.put("sparse_infill_density", settings.getInfillDensity() + "%"); if (settings.getInfillPattern() != null) processOverrides.put("sparse_infill_pattern", settings.getInfillPattern()); + + Path slicerInputPath = persistentPath; + if ("3mf".equals(ext)) { + String convertedFilename = UUID.randomUUID() + "-converted.stl"; + convertedPersistentPath = sessionStorageDir.resolve(convertedFilename).normalize(); + if (!convertedPersistentPath.startsWith(sessionStorageDir)) { + throw new IOException("Invalid converted STL storage path"); + } + slicerService.convert3mfToPersistentStl(persistentPath.toFile(), convertedPersistentPath); + slicerInputPath = convertedPersistentPath; + } // 3. Slice (Use persistent path) PrintStats stats = slicerService.slice( - persistentPath.toFile(), + slicerInputPath.toFile(), machineProfile, filamentProfile, processProfile, @@ -193,7 +205,7 @@ public class QuoteSessionController { processOverrides ); - Optional modelDimensions = slicerService.inspectModelDimensions(persistentPath.toFile()); + Optional modelDimensions = slicerService.inspectModelDimensions(slicerInputPath.toFile()); // 4. Calculate Quote QuoteResult result = quoteCalculator.calculate(stats, machine.getPrinterDisplayName(), selectedVariant); @@ -216,6 +228,9 @@ public class QuoteSessionController { Map breakdown = new HashMap<>(); breakdown.put("machine_cost", result.getTotalPrice()); // Excludes setup fee which is at session level breakdown.put("setup_fee", 0); + if (convertedPersistentPath != null) { + breakdown.put("convertedStoredPath", QUOTE_STORAGE_ROOT.relativize(convertedPersistentPath).toString()); + } item.setPricingBreakdown(breakdown); // Dimensions for shipping/package checks are computed server-side from the uploaded model. @@ -237,6 +252,9 @@ public class QuoteSessionController { } catch (Exception e) { // Cleanup if failed Files.deleteIfExists(persistentPath); + if (convertedPersistentPath != null) { + Files.deleteIfExists(convertedPersistentPath); + } throw e; } } diff --git a/backend/src/main/java/com/printcalculator/service/SlicerService.java b/backend/src/main/java/com/printcalculator/service/SlicerService.java index ed4e1c1..844adad 100644 --- a/backend/src/main/java/com/printcalculator/service/SlicerService.java +++ b/backend/src/main/java/com/printcalculator/service/SlicerService.java @@ -331,6 +331,31 @@ public class SlicerService { return convertedStlPaths; } + public Path convert3mfToPersistentStl(File input3mf, Path destinationStl) throws IOException { + Path tempDir = Files.createTempDirectory("slicer_convert_"); + try { + List convertedPaths = convert3mfToStlInputPaths(input3mf, tempDir); + if (convertedPaths.isEmpty()) { + throw new ModelProcessingException( + "MODEL_CONVERSION_FAILED", + "Unable to process this 3MF file. Try another format or contact us directly via Request Consultation." + ); + } + Path source = Path.of(convertedPaths.get(0)); + Path parent = destinationStl.toAbsolutePath().normalize().getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + Files.copy(source, destinationStl, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + return destinationStl; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted during 3MF conversion", e); + } finally { + deleteRecursively(tempDir); + } + } + private List convert3mfToStlInputPaths(File input3mf, Path tempDir) throws IOException, InterruptedException { Path conversionOutputDir = tempDir.resolve("converted-from-3mf"); Files.createDirectories(conversionOutputDir); @@ -378,7 +403,16 @@ public class SlicerService { try { objLog = runAssimpExport(input3mfPath, conversionOutputObjPath, tempDir.resolve("assimp-convert-obj.log")); if (hasRenderableGeometry(convertedObj)) { - return List.of(convertedObj.toString()); + Path stlFromObj = conversionOutputDir.resolve("converted-from-obj.stl"); + runAssimpExport( + convertedObj.toString(), + stlFromObj.toString(), + tempDir.resolve("assimp-convert-obj-to-stl.log") + ); + if (hasRenderableGeometry(stlFromObj)) { + return List.of(stlFromObj.toString()); + } + logger.warning("Assimp OBJ->STL conversion produced empty geometry."); } logger.warning("Assimp OBJ conversion produced empty geometry."); } catch (IOException e) {