Redesign logo & storefront, light subtle admin theme, and 3D print cost calculator

This commit is contained in:
2026-11-01 21:23:53 +01:00
parent 0c95c7d120
commit e0592212e1
18 changed files with 1887 additions and 1153 deletions
+7 -12
View File
@@ -653,19 +653,14 @@ class MakerWorldService
$dimensions = is_array($design['dimensions']) ? implode(' x ', $design['dimensions']) : (string) $design['dimensions'];
}
// Calculate a smart suggested retail price
// Base starting price of 9.95, + ~0.08 per gram + 1.25 per print hour
$suggestedPrice = 14.95;
if ($weightGrams || $printTimeHours) {
$cost = 5.0;
if ($weightGrams) {
$cost += ($weightGrams * 0.07);
}
if ($printTimeHours) {
$cost += ($printTimeHours * 1.50);
}
$suggestedPrice = round(max(9.95, $cost * 1.6), 2);
// Calculate a smart suggested retail price using the 3D Printing Cost Calculator
$colorCount = 1;
if (! empty($profile['filaments']) && is_array($profile['filaments'])) {
$colorCount = max(1, min(4, count($profile['filaments'])));
}
$calculator = app(PriceCalculatorService::class);
$calcResult = $calculator->calculate($weightGrams ?: 85, $printTimeHours ?: 4.0, $colorCount);
$suggestedPrice = $calcResult['suggested_price'];
// Creator and MakerWorld URL
$authorName = $design['designCreator']['name'] ?? $design['designCreator']['handle'] ?? 'MakerWorld Creator';
+160
View File
@@ -0,0 +1,160 @@
<?php
namespace App\Services;
use App\Models\Setting;
class PriceCalculatorService
{
/**
* Purge/waste multipliers depending on the number of AMS/filament colors.
*/
public const COLOR_WASTE_MULTIPLIERS = [
1 => 1.00, // 0% extra waste for single-color
2 => 1.15, // +15% purge tower & poop waste
3 => 1.25, // +25% purge tower & poop waste
4 => 1.35, // +35% purge tower & poop waste
];
/**
* Material base multipliers relative to standard PLA.
*/
public const MATERIAL_MULTIPLIERS = [
'pla' => 1.00,
'petg' => 1.20,
'silk' => 1.30,
'tpu' => 1.40,
];
/**
* Calculate comprehensive 3D printing costs and suggested retail price.
*
* @param float|int|null $weightGrams Model weight in grams
* @param float|int|null $printTimeHours Printing duration in hours
* @param int $colorCount Number of filament colors (1 to 4)
* @param string $material Material code ('pla', 'petg', 'silk', 'tpu')
* @param float|null $customMarkupPercent Optional override for markup percentage (e.g. 60%)
* @return array<string, mixed> Detailed calculation breakdown
*/
public function calculate(
float|int|null $weightGrams = 80,
float|int|null $printTimeHours = 4.0,
int $colorCount = 1,
string $material = 'pla',
?float $customMarkupPercent = null
): array {
$weight = max(1.0, (float) ($weightGrams ?: 80));
$hours = max(0.2, (float) ($printTimeHours ?: 4.0));
$colors = max(1, min(4, $colorCount));
$matCode = strtolower(trim($material)) ?: 'pla';
// 1. Settings / Rates configuration
$filamentPriceKg = (float) Setting::get('calc_filament_price_kg', 20.00); // € 20 / kg
$electricityPriceKwh = (float) Setting::get('calc_electricity_kwh', 0.35); // € 0.35 / kWh
$printerWattage = (int) Setting::get('calc_printer_wattage', 150); // 150 W average Bambu Lab
$machineHourlyRate = (float) Setting::get('calc_machine_hourly_rate', 0.50); // € 0.50 / uur afschrijving & nozzle/bed slijtage
$handlingFlat = (float) Setting::get('calc_handling_flat', 1.50); // € 1.50 voorbereiding, supports & verpakking
$failureRate = (float) Setting::get('calc_failure_rate', 5.0); // 5% faalkans opslag
$markupPercent = $customMarkupPercent !== null ? $customMarkupPercent : (float) Setting::get('calc_default_markup', 65.0); // 65% winstmarge
$vatPercent = 21.0; // 21% btw
// 2. Multipliers
$colorWasteMult = self::COLOR_WASTE_MULTIPLIERS[$colors] ?? 1.00;
$matMult = self::MATERIAL_MULTIPLIERS[$matCode] ?? 1.00;
// 3. Material cost (includes multi-color purge tower waste)
$effectiveWeight = round($weight * $colorWasteMult, 1);
$materialCost = ($effectiveWeight / 1000.0) * ($filamentPriceKg * $matMult);
// 4. Electricity cost
$kw = $printerWattage / 1000.0;
$kwhUsed = $hours * $kw;
$electricityCost = $kwhUsed * $electricityPriceKwh;
// 5. Machine wear & depreciation
$machineCost = $hours * $machineHourlyRate;
// 6. Multi-color handling fee (extra setup for loading AMS spools)
$colorHandlingCost = max(0, $colors - 1) * 0.50;
// 7. Handling / labor
$handlingCost = $handlingFlat + $colorHandlingCost;
// 8. Direct production cost
$subtotalCost = $materialCost + $electricityCost + $machineCost + $handlingCost;
// 9. Failure rate risk buffer
$failureCost = $subtotalCost * ($failureRate / 100.0);
// 10. Net cost price (direct production cost + risk)
$totalCostPrice = $subtotalCost + $failureCost;
// 11. Margin & Retail price excl. VAT
$profitAmount = $totalCostPrice * ($markupPercent / 100.0);
$priceExclVat = $totalCostPrice + $profitAmount;
// 12. VAT (21%) & Consumer price
$vatAmount = $priceExclVat * ($vatPercent / 100.0);
$consumerPriceExact = $priceExclVat + $vatAmount;
// 13. Smart commercial rounding (.95 / .50)
$suggestedRetailPrice = $this->smartRound($consumerPriceExact);
return [
'inputs' => [
'weight_grams' => $weight,
'print_time_hours' => $hours,
'color_count' => $colors,
'material' => $matCode,
'markup_percent' => $markupPercent,
],
'rates' => [
'filament_price_kg' => $filamentPriceKg,
'electricity_price_kwh' => $electricityPriceKwh,
'machine_hourly_rate' => $machineHourlyRate,
'handling_flat' => $handlingFlat,
'failure_rate_percent' => $failureRate,
],
'breakdown' => [
'effective_weight_grams' => $effectiveWeight,
'purge_waste_grams' => round($effectiveWeight - $weight, 1),
'material_cost' => round($materialCost, 2),
'electricity_cost' => round($electricityCost, 2),
'machine_cost' => round($machineCost, 2),
'handling_cost' => round($handlingCost, 2),
'failure_cost' => round($failureCost, 2),
'total_cost_price' => round($totalCostPrice, 2),
'profit_amount' => round($profitAmount, 2),
'price_excl_vat' => round($priceExclVat, 2),
'vat_amount' => round($vatAmount, 2),
'price_incl_vat_exact' => round($consumerPriceExact, 2),
],
'suggested_price' => $suggestedRetailPrice,
'suggested_price_formatted' => '€ '.number_format($suggestedRetailPrice, 2, ',', '.'),
];
}
/**
* Smart commercial rounding to appealing .95 or .50 price points.
*/
protected function smartRound(float $rawPrice): float
{
// Enforce minimum price of 6.95
if ($rawPrice <= 7.00) {
return 6.95;
}
$floor = floor($rawPrice);
$fraction = $rawPrice - $floor;
if ($fraction <= 0.25) {
$rounded = $floor - 0.05; // e.g. 14.10 -> 13.95 or 14.00 -> 13.95, but keep at least floor
return max(6.95, $floor < $rawPrice ? $floor + 0.50 : $floor - 0.05);
} elseif ($fraction <= 0.70) {
return $floor + 0.50; // e.g. 14.40 -> 14.50
} else {
return $floor + 0.95; // e.g. 14.80 -> 14.95
}
}
}