dev #29

Merged
JoeKung merged 30 commits from dev into main 2026-03-09 09:58:45 +01:00
7 changed files with 160 additions and 62 deletions
Showing only changes of commit aa6322e928 - Show all commits

View File

@@ -284,8 +284,8 @@
<span class="filename">{{ item.originalFilename }}</span>
<span class="file-color">
{{ item.materialCode || "-" }} | {{ item.nozzleDiameterMm ?? "-" }} mm
| {{ item.layerHeightMm ?? "-" }} mm | {{ item.infillPercent ?? "-" }}%
| {{ item.infillPattern || "-" }} |
| {{ item.layerHeightMm ?? "-" }} mm |
{{ item.infillPercent ?? "-" }}% | {{ item.infillPattern || "-" }} |
{{ item.supportsEnabled ? "Supporti ON" : "Supporti OFF" }}
</span>
</div>

View File

@@ -149,7 +149,9 @@
{{ item.layerHeightMm ?? "-" }} mm |
{{ item.infillPercent ?? "-" }}% |
{{ item.infillPattern || "-" }} |
{{ item.supportsEnabled ? "Supporti ON" : "Supporti OFF" }}
{{
item.supportsEnabled ? "Supporti ON" : "Supporti OFF"
}}
</td>
<td>{{ item.status }}</td>
<td>{{ item.unitPriceChf | currency: "CHF" }}</td>

View File

@@ -72,7 +72,10 @@ export class CalculatorPageComponent implements OnInit {
Record<string, { differences: string[] }>
>({});
private baselinePrintSettings: TrackedPrintSettings | null = null;
private baselineItemSettingsByFileName = new Map<string, TrackedPrintSettings>();
private baselineItemSettingsByFileName = new Map<
string,
TrackedPrintSettings
>();
@ViewChild('uploadForm') uploadForm!: UploadFormComponent;
@ViewChild('resultCol') resultCol!: ElementRef;
@@ -528,7 +531,9 @@ export class CalculatorPageComponent implements OnInit {
});
}
private toTrackedSettingsFromRequest(req: QuoteRequest): TrackedPrintSettings {
private toTrackedSettingsFromRequest(
req: QuoteRequest,
): TrackedPrintSettings {
return {
mode: req.mode,
material: this.normalizeString(req.material || 'PLA'),
@@ -590,17 +595,17 @@ export class CalculatorPageComponent implements OnInit {
item: any,
fallback: TrackedPrintSettings,
): TrackedPrintSettings {
const layer = this.normalizeNumber(item?.layerHeightMm, fallback.layerHeight, 3);
const layer = this.normalizeNumber(
item?.layerHeightMm,
fallback.layerHeight,
3,
);
return {
mode: this.mode(),
material: this.normalizeString(item?.materialCode || fallback.material),
quality: this.normalizeString(
item?.quality ||
(layer >= 0.24
? 'draft'
: layer <= 0.12
? 'extra_fine'
: 'standard'),
(layer >= 0.24 ? 'draft' : layer <= 0.12 ? 'extra_fine' : 'standard'),
),
nozzleDiameter: this.normalizeNumber(
item?.nozzleDiameterMm,
@@ -616,9 +621,7 @@ export class CalculatorPageComponent implements OnInit {
infillPattern: this.normalizeString(
item?.infillPattern || fallback.infillPattern,
),
supportEnabled: Boolean(
item?.supportsEnabled ?? fallback.supportEnabled,
),
supportEnabled: Boolean(item?.supportsEnabled ?? fallback.supportEnabled),
};
}

View File

@@ -226,7 +226,12 @@
<label>
{{ "CALC.LAYER_HEIGHT" | translate }}
<select formControlName="layerHeight">
@for (l of getLayerHeightOptionsForNozzle(form.get('nozzleDiameter')?.value); track l.value) {
@for (
l of getLayerHeightOptionsForNozzle(
form.get("nozzleDiameter")?.value
);
track l.value
) {
<option [value]="l.value">{{ l.label }}</option>
}
</select>

View File

@@ -197,16 +197,28 @@ export class UploadFormComponent implements OnInit {
this.fullMaterialOptions = options.materials || [];
this.materials.set(
(options.materials || []).map((m) => ({ label: m.label, value: m.code })),
(options.materials || []).map((m) => ({
label: m.label,
value: m.code,
})),
);
this.qualities.set(
(options.qualities || []).map((q) => ({ label: q.label, value: q.id })),
(options.qualities || []).map((q) => ({
label: q.label,
value: q.id,
})),
);
this.infillPatterns.set(
(options.infillPatterns || []).map((p) => ({ label: p.label, value: p.id })),
(options.infillPatterns || []).map((p) => ({
label: p.label,
value: p.id,
})),
);
this.nozzleDiameters.set(
(options.nozzleDiameters || []).map((n) => ({ label: n.label, value: n.value })),
(options.nozzleDiameters || []).map((n) => ({
label: n.label,
value: n.value,
})),
);
this.allLayerHeights = (options.layerHeights || []).map((l) => ({
@@ -282,12 +294,19 @@ export class UploadFormComponent implements OnInit {
return this.items()[index] || null;
}
getVariantsForMaterial(materialCode: string | null | undefined): VariantOption[] {
const normalized = String(materialCode || '').trim().toUpperCase();
getVariantsForMaterial(
materialCode: string | null | undefined,
): VariantOption[] {
const normalized = String(materialCode || '')
.trim()
.toUpperCase();
if (!normalized) return [];
const found = this.fullMaterialOptions.find(
(m) => String(m.code || '').trim().toUpperCase() === normalized,
(m) =>
String(m.code || '')
.trim()
.toUpperCase() === normalized,
);
return found?.variants || [];
}
@@ -462,7 +481,9 @@ export class UploadFormComponent implements OnInit {
const colorName =
typeof newSelection === 'string' ? newSelection : newSelection.colorName;
const filamentVariantId =
typeof newSelection === 'string' ? undefined : newSelection.filamentVariantId;
typeof newSelection === 'string'
? undefined
: newSelection.filamentVariantId;
this.items.update((current) => {
if (index < 0 || index >= current.length) {
@@ -709,11 +730,17 @@ export class UploadFormComponent implements OnInit {
});
}
if (this.nozzleDiameters().length > 0 && !this.form.get('nozzleDiameter')?.value) {
if (
this.nozzleDiameters().length > 0 &&
!this.form.get('nozzleDiameter')?.value
) {
this.form.get('nozzleDiameter')?.setValue(0.4, { emitEvent: false });
}
if (this.infillPatterns().length > 0 && !this.form.get('infillPattern')?.value) {
if (
this.infillPatterns().length > 0 &&
!this.form.get('infillPattern')?.value
) {
this.form
.get('infillPattern')
?.setValue(this.infillPatterns()[0].value, { emitEvent: false });
@@ -726,7 +753,9 @@ export class UploadFormComponent implements OnInit {
);
if (this.mode() === 'easy') {
this.applyEasyPresetFromQuality(String(this.form.get('quality')?.value || 'standard'));
this.applyEasyPresetFromQuality(
String(this.form.get('quality')?.value || 'standard'),
);
}
this.emitPrintSettingsChange();
@@ -817,9 +846,18 @@ export class UploadFormComponent implements OnInit {
return {
material,
quality,
nozzleDiameter: this.normalizeNumber(this.form.get('nozzleDiameter')?.value, 0.4),
layerHeight: this.normalizeNumber(this.form.get('layerHeight')?.value, 0.2),
infillDensity: this.normalizeNumber(this.form.get('infillDensity')?.value, 20),
nozzleDiameter: this.normalizeNumber(
this.form.get('nozzleDiameter')?.value,
0.4,
),
layerHeight: this.normalizeNumber(
this.form.get('layerHeight')?.value,
0.2,
),
infillDensity: this.normalizeNumber(
this.form.get('infillDensity')?.value,
20,
),
infillPattern: String(this.form.get('infillPattern')?.value || 'grid'),
supportEnabled: Boolean(this.form.get('supportEnabled')?.value),
};
@@ -829,7 +867,9 @@ export class UploadFormComponent implements OnInit {
item: FormItem,
defaults: ReturnType<UploadFormComponent['getCurrentGlobalItemDefaults']>,
): QuoteRequestItem {
const quality = this.normalizeQualityValue(item.quality || defaults.quality);
const quality = this.normalizeQualityValue(
item.quality || defaults.quality,
);
if (this.mode() === 'easy') {
const preset = this.easyModePresetForQuality(quality);
@@ -856,10 +896,16 @@ export class UploadFormComponent implements OnInit {
color: item.color,
filamentVariantId: item.filamentVariantId,
supportEnabled: item.supportEnabled,
infillDensity: this.normalizeNumber(item.infillDensity, defaults.infillDensity),
infillDensity: this.normalizeNumber(
item.infillDensity,
defaults.infillDensity,
),
infillPattern: item.infillPattern || defaults.infillPattern,
layerHeight: this.normalizeNumber(item.layerHeight, defaults.layerHeight),
nozzleDiameter: this.normalizeNumber(item.nozzleDiameter, defaults.nozzleDiameter),
nozzleDiameter: this.normalizeNumber(
item.nozzleDiameter,
defaults.nozzleDiameter,
),
};
}
@@ -1029,11 +1075,15 @@ export class UploadFormComponent implements OnInit {
return;
}
const currentLayer = this.normalizeNumber(this.form.get('layerHeight')?.value, options[0].value as number);
const currentLayer = this.normalizeNumber(
this.form.get('layerHeight')?.value,
options[0].value as number,
);
const allowed = options.some(
(option) =>
Math.abs(this.normalizeNumber(option.value, currentLayer) - currentLayer) <
0.0001,
Math.abs(
this.normalizeNumber(option.value, currentLayer) - currentLayer,
) < 0.0001,
);
if (allowed) {
@@ -1076,13 +1126,17 @@ export class UploadFormComponent implements OnInit {
this.items().forEach((item) => {
const differences: string[] = [];
if (this.normalizeText(item.material) !== this.normalizeText(baseline.material)) {
if (
this.normalizeText(item.material) !==
this.normalizeText(baseline.material)
) {
differences.push(item.material.toUpperCase());
}
if (this.mode() === 'easy') {
if (
this.normalizeText(item.quality) !== this.normalizeText(baseline.quality)
this.normalizeText(item.quality) !==
this.normalizeText(baseline.quality)
) {
differences.push(`quality:${item.quality}`);
}
@@ -1173,19 +1227,28 @@ export class UploadFormComponent implements OnInit {
return (
this.normalizeText(a.material) === this.normalizeText(b.material) &&
this.normalizeText(a.quality) === this.normalizeText(b.quality) &&
Math.abs(this.normalizeNumber(a.nozzleDiameter, 0.4) - this.normalizeNumber(b.nozzleDiameter, 0.4)) <
0.0001 &&
Math.abs(this.normalizeNumber(a.layerHeight, 0.2) - this.normalizeNumber(b.layerHeight, 0.2)) <
0.0001 &&
Math.abs(this.normalizeNumber(a.infillDensity, 20) - this.normalizeNumber(b.infillDensity, 20)) <
0.0001 &&
this.normalizeText(a.infillPattern) === this.normalizeText(b.infillPattern) &&
Math.abs(
this.normalizeNumber(a.nozzleDiameter, 0.4) -
this.normalizeNumber(b.nozzleDiameter, 0.4),
) < 0.0001 &&
Math.abs(
this.normalizeNumber(a.layerHeight, 0.2) -
this.normalizeNumber(b.layerHeight, 0.2),
) < 0.0001 &&
Math.abs(
this.normalizeNumber(a.infillDensity, 20) -
this.normalizeNumber(b.infillDensity, 20),
) < 0.0001 &&
this.normalizeText(a.infillPattern) ===
this.normalizeText(b.infillPattern) &&
Boolean(a.supportEnabled) === Boolean(b.supportEnabled)
);
}
private normalizeQualityValue(value: any): string {
const normalized = String(value || 'standard').trim().toLowerCase();
const normalized = String(value || 'standard')
.trim()
.toLowerCase();
if (normalized === 'high' || normalized === 'high_definition') {
return 'extra_fine';
}

View File

@@ -127,16 +127,22 @@ export class QuoteEstimatorService {
getOptions(): Observable<OptionsResponse> {
const headers: any = {};
return this.http.get<OptionsResponse>(`${environment.apiUrl}/api/calculator/options`, {
headers,
});
return this.http.get<OptionsResponse>(
`${environment.apiUrl}/api/calculator/options`,
{
headers,
},
);
}
getQuoteSession(sessionId: string): Observable<any> {
const headers: any = {};
return this.http.get(`${environment.apiUrl}/api/quote-sessions/${sessionId}`, {
headers,
});
return this.http.get(
`${environment.apiUrl}/api/quote-sessions/${sessionId}`,
{
headers,
},
);
}
updateLineItem(lineItemId: string, changes: any): Observable<any> {
@@ -175,10 +181,13 @@ export class QuoteEstimatorService {
getOrderInvoice(orderId: string): Observable<Blob> {
const headers: any = {};
return this.http.get(`${environment.apiUrl}/api/orders/${orderId}/invoice`, {
headers,
responseType: 'blob',
});
return this.http.get(
`${environment.apiUrl}/api/orders/${orderId}/invoice`,
{
headers,
responseType: 'blob',
},
);
}
getOrderConfirmation(orderId: string): Observable<Blob> {
@@ -226,7 +235,8 @@ export class QuoteEstimatorService {
const emitProgress = () => {
const avg = Math.round(
uploadProgress.reduce((sum, value) => sum + value, 0) / totalItems,
uploadProgress.reduce((sum, value) => sum + value, 0) /
totalItems,
);
observer.next(avg);
};
@@ -239,7 +249,9 @@ export class QuoteEstimatorService {
const hasFailure = uploadResults.some((entry) => !entry.success);
if (hasFailure) {
observer.error('One or more files failed during upload/analysis');
observer.error(
'One or more files failed during upload/analysis',
);
return;
}
@@ -412,8 +424,13 @@ export class QuoteEstimatorService {
};
}
private buildSettingsPayload(request: QuoteRequest, item: QuoteRequestItem): any {
const normalizedQuality = this.normalizeQuality(item.quality || request.quality);
private buildSettingsPayload(
request: QuoteRequest,
item: QuoteRequestItem,
): any {
const normalizedQuality = this.normalizeQuality(
item.quality || request.quality,
);
const easyPreset =
request.mode === 'easy'
? this.buildEasyModePreset(normalizedQuality)
@@ -427,7 +444,10 @@ export class QuoteEstimatorService {
quality: easyPreset ? easyPreset.quality : normalizedQuality,
supportsEnabled: item.supportEnabled ?? request.supportEnabled ?? false,
layerHeight:
easyPreset?.layerHeight ?? item.layerHeight ?? request.layerHeight ?? 0.2,
easyPreset?.layerHeight ??
item.layerHeight ??
request.layerHeight ??
0.2,
infillDensity:
easyPreset?.infillDensity ??
item.infillDensity ??
@@ -447,7 +467,9 @@ export class QuoteEstimatorService {
}
private normalizeQuality(value: string | undefined): string {
const normalized = String(value || 'standard').trim().toLowerCase();
const normalized = String(value || 'standard')
.trim()
.toLowerCase();
if (normalized === 'high' || normalized === 'high_definition') {
return 'extra_fine';
}

View File

@@ -259,7 +259,10 @@
{{ item.printTimeSeconds / 3600 | number: "1.1-1" }}h |
{{ item.materialGrams | number: "1.0-0" }}g
</div>
<div class="item-preview" *ngIf="isCadSession() && isStlItem(item)">
<div
class="item-preview"
*ngIf="isCadSession() && isStlItem(item)"
>
<ng-container
*ngIf="previewFile(item) as itemPreview; else previewState"
>