Merge remote-tracking branch 'origin/feat/calculator-options' into feat/calculator-options
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -127,16 +127,22 @@ export class QuoteEstimatorService {
|
||||
|
||||
getOptions(): Observable<OptionsResponse> {
|
||||
const headers: any = {};
|
||||
return this.http.get<OptionsResponse>(`${environment.apiUrl}/api/calculator/options`, {
|
||||
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}`, {
|
||||
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`, {
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user