fix(front-end): calculator improvements
This commit is contained in:
@@ -3,6 +3,7 @@ package com.printcalculator.controller;
|
||||
import com.printcalculator.entity.PrinterMachine;
|
||||
import com.printcalculator.entity.QuoteLineItem;
|
||||
import com.printcalculator.entity.QuoteSession;
|
||||
import com.printcalculator.model.ModelDimensions;
|
||||
import com.printcalculator.model.PrintStats;
|
||||
import com.printcalculator.model.QuoteResult;
|
||||
import com.printcalculator.repository.PrinterMachineRepository;
|
||||
@@ -28,6 +29,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.Optional;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
|
||||
@@ -183,6 +185,8 @@ public class QuoteSessionController {
|
||||
null, // machine overrides
|
||||
processOverrides
|
||||
);
|
||||
|
||||
Optional<ModelDimensions> modelDimensions = slicerService.inspectModelDimensions(persistentPath.toFile());
|
||||
|
||||
// 4. Calculate Quote
|
||||
QuoteResult result = quoteCalculator.calculate(stats, machine.getPrinterDisplayName(), filamentProfile);
|
||||
@@ -206,14 +210,16 @@ public class QuoteSessionController {
|
||||
breakdown.put("setup_fee", 0);
|
||||
item.setPricingBreakdown(breakdown);
|
||||
|
||||
// Dimensions
|
||||
// Cannot get bb from GCodeParser yet?
|
||||
// If GCodeParser doesn't return size, we might defaults or 0.
|
||||
// Stats has filament used.
|
||||
// Let's set dummy for now or upgrade parser later.
|
||||
item.setBoundingBoxXMm(settings.getBoundingBoxX() != null ? BigDecimal.valueOf(settings.getBoundingBoxX()) : BigDecimal.ZERO);
|
||||
item.setBoundingBoxYMm(settings.getBoundingBoxY() != null ? BigDecimal.valueOf(settings.getBoundingBoxY()) : BigDecimal.ZERO);
|
||||
item.setBoundingBoxZMm(settings.getBoundingBoxZ() != null ? BigDecimal.valueOf(settings.getBoundingBoxZ()) : BigDecimal.ZERO);
|
||||
// Dimensions for shipping/package checks are computed server-side from the uploaded model.
|
||||
item.setBoundingBoxXMm(modelDimensions
|
||||
.map(dim -> BigDecimal.valueOf(dim.xMm()))
|
||||
.orElseGet(() -> settings.getBoundingBoxX() != null ? BigDecimal.valueOf(settings.getBoundingBoxX()) : BigDecimal.ZERO));
|
||||
item.setBoundingBoxYMm(modelDimensions
|
||||
.map(dim -> BigDecimal.valueOf(dim.yMm()))
|
||||
.orElseGet(() -> settings.getBoundingBoxY() != null ? BigDecimal.valueOf(settings.getBoundingBoxY()) : BigDecimal.ZERO));
|
||||
item.setBoundingBoxZMm(modelDimensions
|
||||
.map(dim -> BigDecimal.valueOf(dim.zMm()))
|
||||
.orElseGet(() -> settings.getBoundingBoxZ() != null ? BigDecimal.valueOf(settings.getBoundingBoxZ()) : BigDecimal.ZERO));
|
||||
|
||||
item.setCreatedAt(OffsetDateTime.now());
|
||||
item.setUpdatedAt(OffsetDateTime.now());
|
||||
@@ -371,7 +377,16 @@ public class QuoteSessionController {
|
||||
break;
|
||||
}
|
||||
}
|
||||
BigDecimal shippingCostChf = exceedsBaseSize ? BigDecimal.valueOf(4.00) : BigDecimal.valueOf(2.00);
|
||||
int totalQuantity = items.stream()
|
||||
.mapToInt(i -> i.getQuantity() != null ? i.getQuantity() : 1)
|
||||
.sum();
|
||||
|
||||
BigDecimal shippingCostChf;
|
||||
if (exceedsBaseSize) {
|
||||
shippingCostChf = totalQuantity > 5 ? BigDecimal.valueOf(9.00) : BigDecimal.valueOf(4.00);
|
||||
} else {
|
||||
shippingCostChf = BigDecimal.valueOf(2.00);
|
||||
}
|
||||
|
||||
BigDecimal grandTotal = itemsTotal.add(setupFee).add(shippingCostChf);
|
||||
|
||||
|
||||
@@ -176,7 +176,15 @@ public class OrderService {
|
||||
break;
|
||||
}
|
||||
}
|
||||
order.setShippingCostChf(exceedsBaseSize ? BigDecimal.valueOf(4.00) : BigDecimal.valueOf(2.00));
|
||||
int totalQuantity = quoteItems.stream()
|
||||
.mapToInt(i -> i.getQuantity() != null ? i.getQuantity() : 1)
|
||||
.sum();
|
||||
|
||||
if (exceedsBaseSize) {
|
||||
order.setShippingCostChf(totalQuantity > 5 ? BigDecimal.valueOf(9.00) : BigDecimal.valueOf(4.00));
|
||||
} else {
|
||||
order.setShippingCostChf(BigDecimal.valueOf(2.00));
|
||||
}
|
||||
|
||||
order = orderRepo.save(order);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.printcalculator.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.printcalculator.model.ModelDimensions;
|
||||
import com.printcalculator.model.PrintStats;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -15,13 +16,19 @@ import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Service
|
||||
public class SlicerService {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SlicerService.class.getName());
|
||||
private static final Pattern SIZE_X_PATTERN = Pattern.compile("(?m)^\\s*size_x\\s*=\\s*([-+]?\\d+(?:\\.\\d+)?)\\s*$");
|
||||
private static final Pattern SIZE_Y_PATTERN = Pattern.compile("(?m)^\\s*size_y\\s*=\\s*([-+]?\\d+(?:\\.\\d+)?)\\s*$");
|
||||
private static final Pattern SIZE_Z_PATTERN = Pattern.compile("(?m)^\\s*size_z\\s*=\\s*([-+]?\\d+(?:\\.\\d+)?)\\s*$");
|
||||
|
||||
private final String slicerPath;
|
||||
private final ProfileManager profileManager;
|
||||
@@ -149,6 +156,89 @@ public class SlicerService {
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<ModelDimensions> inspectModelDimensions(File inputModel) {
|
||||
Path tempDir = null;
|
||||
try {
|
||||
tempDir = Files.createTempDirectory("slicer_info_");
|
||||
Path infoLogPath = tempDir.resolve("orcaslicer-info.log");
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(slicerPath);
|
||||
command.add("--info");
|
||||
command.add(inputModel.getAbsolutePath());
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(command);
|
||||
pb.directory(tempDir.toFile());
|
||||
pb.redirectErrorStream(true);
|
||||
pb.redirectOutput(infoLogPath.toFile());
|
||||
|
||||
Process process = pb.start();
|
||||
boolean finished = process.waitFor(2, TimeUnit.MINUTES);
|
||||
if (!finished) {
|
||||
process.destroyForcibly();
|
||||
logger.warning("Model info extraction timed out for " + inputModel.getName());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String output = Files.exists(infoLogPath)
|
||||
? Files.readString(infoLogPath, StandardCharsets.UTF_8)
|
||||
: "";
|
||||
|
||||
if (process.exitValue() != 0) {
|
||||
logger.warning("OrcaSlicer --info failed (exit " + process.exitValue() + ") for "
|
||||
+ inputModel.getName() + ": " + output);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<ModelDimensions> parsed = parseModelDimensionsFromInfoOutput(output);
|
||||
if (parsed.isEmpty()) {
|
||||
logger.warning("Could not parse size_x/size_y/size_z from OrcaSlicer --info output for "
|
||||
+ inputModel.getName() + ": " + output);
|
||||
}
|
||||
return parsed;
|
||||
} catch (Exception e) {
|
||||
logger.warning("Failed to inspect model dimensions for " + inputModel.getName() + ": " + e.getMessage());
|
||||
return Optional.empty();
|
||||
} finally {
|
||||
if (tempDir != null) {
|
||||
deleteRecursively(tempDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Optional<ModelDimensions> parseModelDimensionsFromInfoOutput(String output) {
|
||||
if (output == null || output.isBlank()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Double x = extractDouble(SIZE_X_PATTERN, output);
|
||||
Double y = extractDouble(SIZE_Y_PATTERN, output);
|
||||
Double z = extractDouble(SIZE_Z_PATTERN, output);
|
||||
|
||||
if (x == null || y == null || z == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (x <= 0 || y <= 0 || z <= 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(new ModelDimensions(x, y, z));
|
||||
}
|
||||
|
||||
private static Double extractDouble(Pattern pattern, String text) {
|
||||
Matcher matcher = pattern.matcher(text);
|
||||
if (!matcher.find()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Double.parseDouble(matcher.group(1));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteRecursively(Path path) {
|
||||
if (path == null || !Files.exists(path)) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user