feat: add Orcaslicer and docker

This commit is contained in:
2026-01-27 22:02:49 +01:00
parent 00e62fc558
commit 7dc6741808
21 changed files with 1986 additions and 1123 deletions

1
frontend/.nvmrc Normal file
View File

@@ -0,0 +1 @@
22

14
frontend/Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
# Stage 1: Build
FROM node:20 as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build --configuration=production
# Stage 2: Serve
FROM nginx:alpine
COPY --from=build /app/dist/frontend/browser /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

9
frontend/nginx.conf Normal file
View File

@@ -0,0 +1,9 @@
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,24 +9,27 @@
"test": "ng test"
},
"private": true,
"engines": {
"node": "^22.0.0"
},
"dependencies": {
"@angular/cdk": "^19.2.16",
"@angular/common": "^19.2.0",
"@angular/compiler": "^19.2.0",
"@angular/core": "^19.2.0",
"@angular/forms": "^19.2.0",
"@angular/material": "^19.2.16",
"@angular/platform-browser": "^19.2.0",
"@angular/platform-browser-dynamic": "^19.2.0",
"@angular/router": "^19.2.0",
"@angular/cdk": "^19.2.19",
"@angular/common": "^19.2.18",
"@angular/compiler": "^19.2.18",
"@angular/core": "^19.2.18",
"@angular/forms": "^19.2.18",
"@angular/material": "^19.2.19",
"@angular/platform-browser": "^19.2.18",
"@angular/platform-browser-dynamic": "^19.2.18",
"@angular/router": "^19.2.18",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.2.12",
"@angular/cli": "^19.2.12",
"@angular/compiler-cli": "^19.2.0",
"@angular-devkit/build-angular": "^19.2.19",
"@angular/cli": "^19.2.19",
"@angular/compiler-cli": "^19.2.18",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.6.0",
"karma": "~6.4.0",
@@ -35,5 +38,8 @@
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.7.2"
},
"overrides": {
"tar": "7.5.6"
}
}
}

View File

@@ -1,37 +1,69 @@
<mat-card>
<mat-card-title>3D Print Cost Calculator</mat-card-title>
<div class="calculator-container">
<mat-card>
<mat-card-header>
<mat-card-title>3D Print Quote Calculator</mat-card-title>
<mat-card-subtitle>Bambu Lab A1 Estimation</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div class="upload-section">
<input type="file" (change)="onFileSelected($event)" accept=".stl" #fileInput style="display: none;">
<button mat-raised-button color="primary" (click)="fileInput.click()">
{{ file ? file.name : 'Select STL File' }}
</button>
<button mat-raised-button color="accent"
[disabled]="!file || loading"
(click)="uploadAndCalculate()">
Calculate Quote
</button>
</div>
<input
type="file"
accept=".stl"
(change)="onFileSelected($event)"
/>
<div *ngIf="loading" class="spinner-container">
<mat-progress-spinner mode="indeterminate"></mat-progress-spinner>
<p>Slicing model... this may take a minute...</p>
</div>
<button
mat-raised-button
color="primary"
(click)="uploadAndCalculate()"
[disabled]="!file || loading"
>
{{ loading ? 'Calculating...' : 'Calculate' }}
</button>
<div *ngIf="error" class="error-message">
{{ error }}
</div>
<mat-progress-spinner
*ngIf="loading"
diameter="30"
mode="indeterminate"
></mat-progress-spinner>
<div *ngIf="results" class="results-section">
<div class="total-price">
<h3>Total Estimate: € {{ results.cost.total | number:'1.2-2' }}</h3>
</div>
<p *ngIf="error" class="error">{{ error }}</p>
<div class="details-grid">
<div class="detail-item">
<span class="label">Print Time:</span>
<span class="value">{{ results.print_time_formatted }}</span>
</div>
<div class="detail-item">
<span class="label">Material Used:</span>
<span class="value">{{ results.material_grams | number:'1.1-1' }} g</span>
</div>
</div>
<div *ngIf="results">
<p>Volume STL: {{ results.stl_volume_mm3 }} mm³</p>
<p>Superficie: {{ results.surface_area_mm2 }} mm²</p>
<p>Volume pareti: {{ results.wall_volume_mm3 }} mm³</p>
<p>Volume infill: {{ results.infill_volume_mm3 }} mm³</p>
<p>Volume stampa: {{ results.print_volume_mm3 }} mm³</p>
<p>Peso stimato: {{ results.weight_g }} g</p>
<p>Costo stimato: CHF {{ results.cost_chf }}</p>
<p>Tempo stimato: {{ results.time_min }} min</p>
</div>
</mat-card>
<h4>Cost Breakdown</h4>
<ul class="breakdown-list">
<li>
<span>Material</span>
<span>€ {{ results.cost.material | number:'1.2-2' }}</span>
</li>
<li>
<span>Machine Time</span>
<span>€ {{ results.cost.machine | number:'1.2-2' }}</span>
</li>
<li>
<span>Energy</span>
<span>€ {{ results.cost.energy | number:'1.2-2' }}</span>
</li>
<li>
<span>Service/Markup</span>
<span>€ {{ results.cost.markup | number:'1.2-2' }}</span>
</li>
</ul>
</div>
</mat-card-content>
</mat-card>
</div>

View File

@@ -4,10 +4,22 @@ import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
interface QuoteResponse {
printer: string;
print_time_formatted: string;
material_grams: number;
cost: {
material: number;
machine: number;
energy: number;
markup: number;
total: number;
};
}
@Component({
selector: 'app-calculator',
standalone: true,
@@ -15,7 +27,6 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
CommonModule,
FormsModule,
MatCardModule,
MatFormFieldModule,
MatButtonModule,
MatProgressSpinnerModule
],
@@ -24,7 +35,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
})
export class CalculatorComponent {
file: File | null = null;
results: any = null;
results: QuoteResponse | null = null;
error = '';
loading = false;
@@ -41,22 +52,26 @@ export class CalculatorComponent {
uploadAndCalculate(): void {
if (!this.file) {
this.error = 'Seleziona un file STL prima di procedere.';
this.error = 'Please select a file first.';
return;
}
const formData = new FormData();
formData.append('file', this.file);
this.loading = true;
this.http.post<any>('http://localhost:8000/calculate/stl', formData)
this.error = '';
this.results = null;
this.http.post<QuoteResponse>('http://localhost:8000/calculate/stl', formData)
.subscribe({
next: res => {
this.results = res;
this.loading = false;
},
error: err => {
this.error = err.error?.detail || err.message;
console.error(err);
this.error = err.error?.detail || "An error occurred during calculation.";
this.loading = false;
}
});
}
}
}