fix(tutto rotto):
This commit is contained in:
@@ -66,6 +66,24 @@ public class QuoteLineItem {
|
|||||||
@Column(name = "supports_enabled")
|
@Column(name = "supports_enabled")
|
||||||
private Boolean supportsEnabled;
|
private Boolean supportsEnabled;
|
||||||
|
|
||||||
|
@Column(name = "material_code", length = Integer.MAX_VALUE)
|
||||||
|
private String materialCode;
|
||||||
|
|
||||||
|
@Column(name = "nozzle_diameter_mm", precision = 5, scale = 2)
|
||||||
|
private BigDecimal nozzleDiameterMm;
|
||||||
|
|
||||||
|
@Column(name = "layer_height_mm", precision = 6, scale = 3)
|
||||||
|
private BigDecimal layerHeightMm;
|
||||||
|
|
||||||
|
@Column(name = "infill_pattern", length = Integer.MAX_VALUE)
|
||||||
|
private String infillPattern;
|
||||||
|
|
||||||
|
@Column(name = "infill_percent")
|
||||||
|
private Integer infillPercent;
|
||||||
|
|
||||||
|
@Column(name = "supports_enabled")
|
||||||
|
private Boolean supportsEnabled;
|
||||||
|
|
||||||
@Column(name = "bounding_box_x_mm", precision = 10, scale = 3)
|
@Column(name = "bounding_box_x_mm", precision = 10, scale = 3)
|
||||||
private BigDecimal boundingBoxXMm;
|
private BigDecimal boundingBoxXMm;
|
||||||
|
|
||||||
@@ -214,6 +232,54 @@ public class QuoteLineItem {
|
|||||||
this.supportsEnabled = supportsEnabled;
|
this.supportsEnabled = supportsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getMaterialCode() {
|
||||||
|
return materialCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaterialCode(String materialCode) {
|
||||||
|
this.materialCode = materialCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getNozzleDiameterMm() {
|
||||||
|
return nozzleDiameterMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
|
||||||
|
this.nozzleDiameterMm = nozzleDiameterMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getLayerHeightMm() {
|
||||||
|
return layerHeightMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLayerHeightMm(BigDecimal layerHeightMm) {
|
||||||
|
this.layerHeightMm = layerHeightMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInfillPattern() {
|
||||||
|
return infillPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInfillPattern(String infillPattern) {
|
||||||
|
this.infillPattern = infillPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getInfillPercent() {
|
||||||
|
return infillPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInfillPercent(Integer infillPercent) {
|
||||||
|
this.infillPercent = infillPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSupportsEnabled() {
|
||||||
|
return supportsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSupportsEnabled(Boolean supportsEnabled) {
|
||||||
|
this.supportsEnabled = supportsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal getBoundingBoxXMm() {
|
public BigDecimal getBoundingBoxXMm() {
|
||||||
return boundingBoxXMm;
|
return boundingBoxXMm;
|
||||||
}
|
}
|
||||||
|
|||||||
24
db.sql
24
db.sql
@@ -660,6 +660,12 @@ CREATE TABLE IF NOT EXISTS quote_line_items
|
|||||||
quantity integer NOT NULL DEFAULT 1 CHECK (quantity >= 1),
|
quantity integer NOT NULL DEFAULT 1 CHECK (quantity >= 1),
|
||||||
color_code text, -- es: white/black o codice interno
|
color_code text, -- es: white/black o codice interno
|
||||||
filament_variant_id bigint REFERENCES filament_variant (filament_variant_id),
|
filament_variant_id bigint REFERENCES filament_variant (filament_variant_id),
|
||||||
|
material_code text,
|
||||||
|
nozzle_diameter_mm numeric(5, 2),
|
||||||
|
layer_height_mm numeric(6, 3),
|
||||||
|
infill_pattern text,
|
||||||
|
infill_percent integer CHECK (infill_percent BETWEEN 0 AND 100),
|
||||||
|
supports_enabled boolean,
|
||||||
|
|
||||||
-- Output slicing / calcolo
|
-- Output slicing / calcolo
|
||||||
bounding_box_x_mm numeric(10, 3),
|
bounding_box_x_mm numeric(10, 3),
|
||||||
@@ -680,6 +686,24 @@ CREATE TABLE IF NOT EXISTS quote_line_items
|
|||||||
CREATE INDEX IF NOT EXISTS ix_quote_line_items_session
|
CREATE INDEX IF NOT EXISTS ix_quote_line_items_session
|
||||||
ON quote_line_items (quote_session_id);
|
ON quote_line_items (quote_session_id);
|
||||||
|
|
||||||
|
ALTER TABLE quote_line_items
|
||||||
|
ADD COLUMN IF NOT EXISTS material_code text;
|
||||||
|
|
||||||
|
ALTER TABLE quote_line_items
|
||||||
|
ADD COLUMN IF NOT EXISTS nozzle_diameter_mm numeric(5, 2);
|
||||||
|
|
||||||
|
ALTER TABLE quote_line_items
|
||||||
|
ADD COLUMN IF NOT EXISTS layer_height_mm numeric(6, 3);
|
||||||
|
|
||||||
|
ALTER TABLE quote_line_items
|
||||||
|
ADD COLUMN IF NOT EXISTS infill_pattern text;
|
||||||
|
|
||||||
|
ALTER TABLE quote_line_items
|
||||||
|
ADD COLUMN IF NOT EXISTS infill_percent integer;
|
||||||
|
|
||||||
|
ALTER TABLE quote_line_items
|
||||||
|
ADD COLUMN IF NOT EXISTS supports_enabled boolean;
|
||||||
|
|
||||||
-- Vista utile per totale quote
|
-- Vista utile per totale quote
|
||||||
CREATE OR REPLACE VIEW quote_session_totals AS
|
CREATE OR REPLACE VIEW quote_session_totals AS
|
||||||
SELECT qs.quote_session_id,
|
SELECT qs.quote_session_id,
|
||||||
|
|||||||
@@ -62,7 +62,8 @@
|
|||||||
<span class="file-name">{{ item.fileName }}</span>
|
<span class="file-name">{{ item.fileName }}</span>
|
||||||
<span class="file-details">
|
<span class="file-details">
|
||||||
{{ item.unitTime / 3600 | number: "1.1-1" }}h |
|
{{ item.unitTime / 3600 | number: "1.1-1" }}h |
|
||||||
{{ item.unitWeight | number: "1.0-0" }}g
|
{{ item.unitWeight | number: "1.0-0" }}g |
|
||||||
|
materiale: {{ item.material || "N/D" }}
|
||||||
@if (getItemDifferenceLabel(item.fileName)) {
|
@if (getItemDifferenceLabel(item.fileName)) {
|
||||||
|
|
|
|
||||||
<small class="item-settings-diff">
|
<small class="item-settings-diff">
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<app-color-selector
|
<app-color-selector
|
||||||
[selectedColor]="item.color"
|
[selectedColor]="item.color"
|
||||||
[selectedVariantId]="item.filamentVariantId ?? null"
|
[selectedVariantId]="item.filamentVariantId ?? null"
|
||||||
[variants]="getVariantsForItem(item)"
|
[variants]="getVariantsForMaterial(item.material)"
|
||||||
(colorSelected)="updateItemColor(i, $event)"
|
(colorSelected)="updateItemColor(i, $event)"
|
||||||
>
|
>
|
||||||
</app-color-selector>
|
</app-color-selector>
|
||||||
@@ -102,11 +102,6 @@
|
|||||||
+ {{ "CALC.ADD_FILES" | translate }}
|
+ {{ "CALC.ADD_FILES" | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
@if (items().length === 0 && form.get("itemsTouched")?.value) {
|
|
||||||
<div class="error-msg">{{ "CALC.ERR_FILE_REQUIRED" | translate }}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<p class="upload-privacy-note">
|
<p class="upload-privacy-note">
|
||||||
{{ "LEGAL.CONSENT.UPLOAD_NOTICE_PREFIX" | translate }}
|
{{ "LEGAL.CONSENT.UPLOAD_NOTICE_PREFIX" | translate }}
|
||||||
@@ -115,21 +110,145 @@
|
|||||||
}}</a
|
}}</a
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<label class="item-settings-checkbox item-settings-checkbox--top">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
[checked]="sameSettingsForAll()"
|
||||||
|
(change)="onSameSettingsToggle($any($event.target).checked)"
|
||||||
|
/>
|
||||||
|
<span>Tutti i file uguali (applica impostazioni a tutti)</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
@if (sameSettingsForAll()) {
|
||||||
|
<div class="item-settings-panel">
|
||||||
|
<h4 class="item-settings-title">Impostazioni globali</h4>
|
||||||
|
|
||||||
|
<div class="item-settings-grid">
|
||||||
|
<label>
|
||||||
|
{{ "CALC.MATERIAL" | translate }}
|
||||||
|
<select formControlName="material">
|
||||||
|
@for (mat of materials(); track mat.value) {
|
||||||
|
<option [value]="mat.value">{{ mat.label }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
@if (mode() === "easy") {
|
||||||
|
<label>
|
||||||
|
{{ "CALC.QUALITY" | translate }}
|
||||||
|
<select formControlName="quality">
|
||||||
|
@for (quality of qualities(); track quality.value) {
|
||||||
|
<option [value]="quality.value">{{ quality.label }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
} @else {
|
||||||
|
<label>
|
||||||
|
{{ "CALC.NOZZLE" | translate }}
|
||||||
|
<select formControlName="nozzleDiameter">
|
||||||
|
@for (n of nozzleDiameters(); track n.value) {
|
||||||
|
<option [value]="n.value">{{ n.label }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid">
|
@if (mode() === "advanced") {
|
||||||
@if (lockedSettings()) {
|
<div class="item-settings-grid">
|
||||||
<p class="upload-privacy-note">
|
<label>
|
||||||
Parametri stampa bloccati per sessione CAD: materiale, nozzle, layer,
|
{{ "CALC.PATTERN" | translate }}
|
||||||
infill e supporti sono definiti dal back-office.
|
<select formControlName="infillPattern">
|
||||||
</p>
|
@for (p of infillPatterns(); track p.value) {
|
||||||
|
<option [value]="p.value">{{ p.label }}</option>
|
||||||
}
|
}
|
||||||
<app-select
|
</select>
|
||||||
formControlName="material"
|
</label>
|
||||||
[label]="'CALC.MATERIAL' | translate"
|
|
||||||
[options]="materials()"
|
|
||||||
></app-select>
|
|
||||||
|
|
||||||
|
<label>
|
||||||
|
{{ "CALC.LAYER_HEIGHT" | translate }}
|
||||||
|
<select formControlName="layerHeight">
|
||||||
|
@for (l of layerHeights(); track l.value) {
|
||||||
|
<option [value]="l.value">{{ l.label }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-settings-grid">
|
||||||
|
<label>
|
||||||
|
{{ "CALC.INFILL" | translate }}
|
||||||
|
<input type="number" min="0" max="100" formControlName="infillDensity" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="item-settings-checkbox">
|
||||||
|
<input type="checkbox" formControlName="supportEnabled" />
|
||||||
|
<span>{{ "CALC.SUPPORT" | translate }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
@if (getSelectedItem(); as selectedItem) {
|
||||||
|
<div class="item-settings-panel">
|
||||||
|
<h4 class="item-settings-title">
|
||||||
|
Impostazioni file: {{ selectedItem.file.name }}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div class="item-settings-grid">
|
||||||
|
<label>
|
||||||
|
{{ "CALC.MATERIAL" | translate }}
|
||||||
|
<select
|
||||||
|
[value]="selectedItem.material || form.get('material')?.value"
|
||||||
|
(change)="
|
||||||
|
updateItemMaterial(getSelectedItemIndex(), $any($event.target).value)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
@for (mat of materials(); track mat.value) {
|
||||||
|
<option [value]="mat.value">{{ mat.label }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
@if (mode() === "easy") {
|
||||||
|
<label>
|
||||||
|
{{ "CALC.QUALITY" | translate }}
|
||||||
|
<select
|
||||||
|
[value]="selectedItem.quality || form.get('quality')?.value"
|
||||||
|
(change)="
|
||||||
|
updateSelectedItemStringField(
|
||||||
|
'quality',
|
||||||
|
$any($event.target).value
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
@for (quality of qualities(); track quality.value) {
|
||||||
|
<option [value]="quality.value">{{ quality.label }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
} @else {
|
||||||
|
<label>
|
||||||
|
{{ "CALC.NOZZLE" | translate }}
|
||||||
|
<select
|
||||||
|
[value]="
|
||||||
|
selectedItem.nozzleDiameter ?? form.get('nozzleDiameter')?.value
|
||||||
|
"
|
||||||
|
(change)="
|
||||||
|
updateSelectedItemNumberField(
|
||||||
|
'nozzleDiameter',
|
||||||
|
+$any($event.target).value
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
@for (n of nozzleDiameters(); track n.value) {
|
||||||
|
<option [value]="n.value">{{ n.label }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@if (mode() === "easy") {
|
@if (mode() === "easy") {
|
||||||
<app-select
|
<app-select
|
||||||
formControlName="quality"
|
formControlName="quality"
|
||||||
@@ -154,36 +273,84 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Global quantity removed, now per item -->
|
|
||||||
|
|
||||||
@if (mode() === "advanced") {
|
@if (mode() === "advanced") {
|
||||||
<div class="grid">
|
<div class="item-settings-grid">
|
||||||
<app-select
|
<label>
|
||||||
formControlName="infillPattern"
|
{{ "CALC.PATTERN" | translate }}
|
||||||
[label]="'CALC.PATTERN' | translate"
|
<select
|
||||||
[options]="infillPatterns()"
|
[value]="selectedItem.infillPattern || form.get('infillPattern')?.value"
|
||||||
></app-select>
|
(change)="
|
||||||
|
updateSelectedItemStringField(
|
||||||
|
'infillPattern',
|
||||||
|
$any($event.target).value
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
@for (p of infillPatterns(); track p.value) {
|
||||||
|
<option [value]="p.value">{{ p.label }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
<app-select
|
<label>
|
||||||
formControlName="layerHeight"
|
{{ "CALC.LAYER_HEIGHT" | translate }}
|
||||||
[label]="'CALC.LAYER_HEIGHT' | translate"
|
<select
|
||||||
[options]="layerHeights()"
|
[value]="selectedItem.layerHeight ?? form.get('layerHeight')?.value"
|
||||||
></app-select>
|
(change)="
|
||||||
|
updateSelectedItemNumberField(
|
||||||
|
'layerHeight',
|
||||||
|
+$any($event.target).value
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
@for (l of layerHeights(); track l.value) {
|
||||||
|
<option [value]="l.value">{{ l.label }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="item-settings-grid">
|
||||||
<app-input
|
<label>
|
||||||
formControlName="infillDensity"
|
{{ "CALC.INFILL" | translate }}
|
||||||
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
[label]="'CALC.INFILL' | translate"
|
min="0"
|
||||||
></app-input>
|
max="100"
|
||||||
|
[value]="
|
||||||
|
selectedItem.infillDensity ?? form.get('infillDensity')?.value
|
||||||
|
"
|
||||||
|
(change)="
|
||||||
|
updateSelectedItemNumberField(
|
||||||
|
'infillDensity',
|
||||||
|
+$any($event.target).value
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="checkbox-row">
|
<label class="item-settings-checkbox">
|
||||||
<input type="checkbox" formControlName="supportEnabled" id="support" />
|
<input
|
||||||
<label for="support">{{ "CALC.SUPPORT" | translate }}</label>
|
type="checkbox"
|
||||||
</div>
|
[checked]="
|
||||||
|
selectedItem.supportEnabled ?? form.get('supportEnabled')?.value
|
||||||
|
"
|
||||||
|
(change)="updateSelectedItemSupport($any($event.target).checked)"
|
||||||
|
/>
|
||||||
|
<span>{{ "CALC.SUPPORT" | translate }}</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (items().length === 0 && form.get("itemsTouched")?.value) {
|
||||||
|
<div class="error-msg">{{ "CALC.ERR_FILE_REQUIRED" | translate }}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<app-input
|
<app-input
|
||||||
formControlName="notes"
|
formControlName="notes"
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
margin-bottom: var(--space-6);
|
margin-bottom: var(--space-6);
|
||||||
}
|
}
|
||||||
.upload-privacy-note {
|
.upload-privacy-note {
|
||||||
margin-top: var(--space-3);
|
margin-top: var(--space-6);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: 0.78rem;
|
font-size: 0.8rem;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
@@ -250,3 +250,74 @@
|
|||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-settings-panel {
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
padding: var(--space-4);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--color-bg-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-settings-title {
|
||||||
|
margin: 0 0 var(--space-4);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-settings-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--space-3);
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-settings-grid label,
|
||||||
|
.item-settings-checkbox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-1);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-settings-grid input,
|
||||||
|
.item-settings-grid select {
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: var(--color-bg-card);
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-brand);
|
||||||
|
box-shadow: 0 0 0 2px rgba(250, 207, 10, 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-settings-checkbox {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
accent-color: var(--color-brand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-settings-checkbox--top {
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
import { AppInputComponent } from '../../../../shared/components/app-input/app-input.component';
|
import { AppInputComponent } from '../../../../shared/components/app-input/app-input.component';
|
||||||
import { AppSelectComponent } from '../../../../shared/components/app-select/app-select.component';
|
|
||||||
import { AppDropzoneComponent } from '../../../../shared/components/app-dropzone/app-dropzone.component';
|
import { AppDropzoneComponent } from '../../../../shared/components/app-dropzone/app-dropzone.component';
|
||||||
import { AppButtonComponent } from '../../../../shared/components/app-button/app-button.component';
|
import { AppButtonComponent } from '../../../../shared/components/app-button/app-button.component';
|
||||||
import { StlViewerComponent } from '../../../../shared/components/stl-viewer/stl-viewer.component';
|
import { StlViewerComponent } from '../../../../shared/components/stl-viewer/stl-viewer.component';
|
||||||
@@ -35,8 +34,15 @@ interface FormItem {
|
|||||||
file: File;
|
file: File;
|
||||||
previewFile?: File;
|
previewFile?: File;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
material?: string;
|
||||||
|
quality?: string;
|
||||||
color: string;
|
color: string;
|
||||||
filamentVariantId?: number;
|
filamentVariantId?: number;
|
||||||
|
supportEnabled?: boolean;
|
||||||
|
infillDensity?: number;
|
||||||
|
infillPattern?: string;
|
||||||
|
layerHeight?: number;
|
||||||
|
nozzleDiameter?: number;
|
||||||
printSettings: ItemPrintSettings;
|
printSettings: ItemPrintSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +70,6 @@ type ItemPrintSettingsUpdate = Partial<ItemPrintSettings>;
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
AppInputComponent,
|
AppInputComponent,
|
||||||
AppSelectComponent,
|
|
||||||
AppDropzoneComponent,
|
AppDropzoneComponent,
|
||||||
AppButtonComponent,
|
AppButtonComponent,
|
||||||
StlViewerComponent,
|
StlViewerComponent,
|
||||||
@@ -117,6 +122,7 @@ export class UploadFormComponent implements OnInit {
|
|||||||
private allLayerHeights: SimpleOption[] = [];
|
private allLayerHeights: SimpleOption[] = [];
|
||||||
private layerHeightsByNozzle: Record<string, SimpleOption[]> = {};
|
private layerHeightsByNozzle: Record<string, SimpleOption[]> = {};
|
||||||
private isPatchingSettings = false;
|
private isPatchingSettings = false;
|
||||||
|
sameSettingsForAll = signal(true);
|
||||||
|
|
||||||
// Computed variants for valid material
|
// Computed variants for valid material
|
||||||
currentMaterialVariants = signal<VariantOption[]>([]);
|
currentMaterialVariants = signal<VariantOption[]>([]);
|
||||||
@@ -152,6 +158,24 @@ export class UploadFormComponent implements OnInit {
|
|||||||
return item.previewFile ?? item.file;
|
return item.previewFile ?? item.file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSelectedItemIndex(): number {
|
||||||
|
const selected = this.selectedFile();
|
||||||
|
if (!selected) return -1;
|
||||||
|
return this.items().findIndex((item) => item.file === selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedItem(): FormItem | null {
|
||||||
|
const index = this.getSelectedItemIndex();
|
||||||
|
if (index < 0) return null;
|
||||||
|
return this.items()[index] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getVariantsForMaterial(materialCode: string | null | undefined): VariantOption[] {
|
||||||
|
if (!materialCode) return [];
|
||||||
|
const found = this.fullMaterialOptions.find((m) => m.code === materialCode);
|
||||||
|
return found?.variants ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.form = this.fb.group({
|
this.form = this.fb.group({
|
||||||
itemsTouched: [false], // Hack to track touched state for custom items list
|
itemsTouched: [false], // Hack to track touched state for custom items list
|
||||||
@@ -168,14 +192,50 @@ export class UploadFormComponent implements OnInit {
|
|||||||
supportEnabled: [false],
|
supportEnabled: [false],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen to material changes to update variants
|
// Listen to material changes to update variants and propagate when "all files equal" is active.
|
||||||
this.form.get('material')?.valueChanges.subscribe(() => {
|
this.form.get('material')?.valueChanges.subscribe((materialCode) => {
|
||||||
this.updateVariants();
|
this.updateVariants();
|
||||||
|
if (this.sameSettingsForAll() && !this.isPatchingSettings) {
|
||||||
|
this.applyGlobalMaterialToAll(String(materialCode || 'PLA'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.form.get('quality')?.valueChanges.subscribe((quality) => {
|
this.form.get('quality')?.valueChanges.subscribe((quality) => {
|
||||||
if (this.mode() !== 'easy' || this.isPatchingSettings) return;
|
if (this.mode() !== 'easy' || this.isPatchingSettings) return;
|
||||||
this.applyAdvancedPresetFromQuality(quality);
|
this.applyAdvancedPresetFromQuality(quality);
|
||||||
|
if (this.sameSettingsForAll()) {
|
||||||
|
this.applyGlobalFieldToAll('quality', String(quality || 'standard'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.form.get('nozzleDiameter')?.valueChanges.subscribe((value) => {
|
||||||
|
if (!this.sameSettingsForAll() || this.isPatchingSettings) return;
|
||||||
|
this.applyGlobalFieldToAll(
|
||||||
|
'nozzleDiameter',
|
||||||
|
Number.isFinite(Number(value)) ? Number(value) : 0.4,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.form.get('layerHeight')?.valueChanges.subscribe((value) => {
|
||||||
|
if (!this.sameSettingsForAll() || this.isPatchingSettings) return;
|
||||||
|
this.applyGlobalFieldToAll(
|
||||||
|
'layerHeight',
|
||||||
|
Number.isFinite(Number(value)) ? Number(value) : 0.2,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.form.get('infillDensity')?.valueChanges.subscribe((value) => {
|
||||||
|
if (!this.sameSettingsForAll() || this.isPatchingSettings) return;
|
||||||
|
this.applyGlobalFieldToAll(
|
||||||
|
'infillDensity',
|
||||||
|
Number.isFinite(Number(value)) ? Number(value) : 15,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.form.get('infillPattern')?.valueChanges.subscribe((value) => {
|
||||||
|
if (!this.sameSettingsForAll() || this.isPatchingSettings) return;
|
||||||
|
this.applyGlobalFieldToAll('infillPattern', String(value || 'grid'));
|
||||||
|
});
|
||||||
|
this.form.get('supportEnabled')?.valueChanges.subscribe((value) => {
|
||||||
|
if (!this.sameSettingsForAll() || this.isPatchingSettings) return;
|
||||||
|
this.applyGlobalFieldToAll('supportEnabled', !!value);
|
||||||
});
|
});
|
||||||
this.form.get('nozzleDiameter')?.valueChanges.subscribe((nozzle) => {
|
this.form.get('nozzleDiameter')?.valueChanges.subscribe((nozzle) => {
|
||||||
if (this.isPatchingSettings) return;
|
if (this.isPatchingSettings) return;
|
||||||
@@ -346,18 +406,26 @@ export class UploadFormComponent implements OnInit {
|
|||||||
const MAX_SIZE = 200 * 1024 * 1024; // 200MB
|
const MAX_SIZE = 200 * 1024 * 1024; // 200MB
|
||||||
const validItems: FormItem[] = [];
|
const validItems: FormItem[] = [];
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
|
const defaults = this.getCurrentGlobalItemDefaults();
|
||||||
|
|
||||||
for (const file of newFiles) {
|
for (const file of newFiles) {
|
||||||
if (file.size > MAX_SIZE) {
|
if (file.size > MAX_SIZE) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
} else {
|
} else {
|
||||||
const defaultSelection = this.getDefaultVariantSelection();
|
const defaultSelection = this.getDefaultVariantSelection(defaults.material);
|
||||||
validItems.push({
|
validItems.push({
|
||||||
file,
|
file,
|
||||||
previewFile: this.isStlFile(file) ? file : undefined,
|
previewFile: this.isStlFile(file) ? file : undefined,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
|
material: defaults.material,
|
||||||
|
quality: defaults.quality,
|
||||||
color: defaultSelection.colorName,
|
color: defaultSelection.colorName,
|
||||||
filamentVariantId: defaultSelection.filamentVariantId,
|
filamentVariantId: defaultSelection.filamentVariantId,
|
||||||
|
supportEnabled: defaults.supportEnabled,
|
||||||
|
infillDensity: defaults.infillDensity,
|
||||||
|
infillPattern: defaults.infillPattern,
|
||||||
|
layerHeight: defaults.layerHeight,
|
||||||
|
nozzleDiameter: defaults.nozzleDiameter,
|
||||||
printSettings: this.getCurrentItemPrintSettings(),
|
printSettings: this.getCurrentItemPrintSettings(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -391,19 +459,25 @@ export class UploadFormComponent implements OnInit {
|
|||||||
|
|
||||||
this.items.update((current) => {
|
this.items.update((current) => {
|
||||||
if (index >= current.length) return current;
|
if (index >= current.length) return current;
|
||||||
const updated = [...current];
|
const applyToAll = this.sameSettingsForAll();
|
||||||
updated[index] = { ...updated[index], quantity: normalizedQty };
|
return current.map((item, idx) => {
|
||||||
return updated;
|
if (!applyToAll && idx !== index) return item;
|
||||||
|
return { ...item, quantity: normalizedQty };
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateItemQuantityByName(fileName: string, quantity: number) {
|
updateItemQuantityByName(fileName: string, quantity: number) {
|
||||||
const targetName = this.normalizeFileName(fileName);
|
const targetName = this.normalizeFileName(fileName);
|
||||||
const normalizedQty = this.normalizeQuantity(quantity);
|
const normalizedQty = this.normalizeQuantity(quantity);
|
||||||
|
const applyToAll = this.sameSettingsForAll();
|
||||||
|
|
||||||
this.items.update((current) => {
|
this.items.update((current) => {
|
||||||
let matched = false;
|
let matched = false;
|
||||||
return current.map((item) => {
|
return current.map((item) => {
|
||||||
|
if (applyToAll) {
|
||||||
|
return { ...item, quantity: normalizedQty };
|
||||||
|
}
|
||||||
if (!matched && this.normalizeFileName(item.file.name) === targetName) {
|
if (!matched && this.normalizeFileName(item.file.name) === targetName) {
|
||||||
matched = true;
|
matched = true;
|
||||||
return { ...item, quantity: normalizedQty };
|
return { ...item, quantity: normalizedQty };
|
||||||
@@ -429,7 +503,7 @@ export class UploadFormComponent implements OnInit {
|
|||||||
|
|
||||||
const item = this.items().find((i) => i.file === file);
|
const item = this.items().find((i) => i.file === file);
|
||||||
if (item) {
|
if (item) {
|
||||||
const vars = this.currentMaterialVariants();
|
const vars = this.getVariantsForMaterial(item.material);
|
||||||
if (vars && vars.length > 0) {
|
if (vars && vars.length > 0) {
|
||||||
const found = item.filamentVariantId
|
const found = item.filamentVariantId
|
||||||
? vars.find((v) => v.id === item.filamentVariantId)
|
? vars.find((v) => v.id === item.filamentVariantId)
|
||||||
@@ -468,11 +542,206 @@ export class UploadFormComponent implements OnInit {
|
|||||||
: newSelection.filamentVariantId;
|
: newSelection.filamentVariantId;
|
||||||
this.items.update((current) => {
|
this.items.update((current) => {
|
||||||
const updated = [...current];
|
const updated = [...current];
|
||||||
updated[index] = {
|
const applyToAll = this.sameSettingsForAll();
|
||||||
...updated[index],
|
return updated.map((item, idx) => {
|
||||||
|
if (!applyToAll && idx !== index) return item;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
color: colorName,
|
color: colorName,
|
||||||
filamentVariantId,
|
filamentVariantId,
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateItemMaterial(index: number, materialCode: string) {
|
||||||
|
if (!Number.isInteger(index) || index < 0) return;
|
||||||
|
const variants = this.getVariantsForMaterial(materialCode);
|
||||||
|
const fallback = variants.find((v) => !v.isOutOfStock) || variants[0];
|
||||||
|
|
||||||
|
this.items.update((current) => {
|
||||||
|
if (index >= current.length) return current;
|
||||||
|
const applyToAll = this.sameSettingsForAll();
|
||||||
|
return current.map((item, idx) => {
|
||||||
|
if (!applyToAll && idx !== index) return item;
|
||||||
|
const next = { ...item, material: materialCode };
|
||||||
|
if (fallback) {
|
||||||
|
next.color = fallback.colorName;
|
||||||
|
next.filamentVariantId = fallback.id;
|
||||||
|
} else {
|
||||||
|
next.filamentVariantId = undefined;
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectedItemNumberField(
|
||||||
|
field:
|
||||||
|
| 'nozzleDiameter'
|
||||||
|
| 'layerHeight'
|
||||||
|
| 'infillDensity'
|
||||||
|
| 'quantity',
|
||||||
|
value: number,
|
||||||
|
) {
|
||||||
|
const index = this.getSelectedItemIndex();
|
||||||
|
if (index < 0) return;
|
||||||
|
const normalized =
|
||||||
|
field === 'quantity'
|
||||||
|
? this.normalizeQuantity(value)
|
||||||
|
: Number.isFinite(value)
|
||||||
|
? value
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
this.items.update((current) => {
|
||||||
|
if (index >= current.length) return current;
|
||||||
|
const applyToAll = this.sameSettingsForAll();
|
||||||
|
return current.map((item, idx) => {
|
||||||
|
if (!applyToAll && idx !== index) return item;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
[field]: normalized,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectedItemStringField(
|
||||||
|
field: 'quality' | 'infillPattern',
|
||||||
|
value: string,
|
||||||
|
) {
|
||||||
|
const index = this.getSelectedItemIndex();
|
||||||
|
if (index < 0) return;
|
||||||
|
this.items.update((current) => {
|
||||||
|
if (index >= current.length) return current;
|
||||||
|
const applyToAll = this.sameSettingsForAll();
|
||||||
|
return current.map((item, idx) => {
|
||||||
|
if (!applyToAll && idx !== index) return item;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
[field]: value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectedItemSupport(value: boolean) {
|
||||||
|
const index = this.getSelectedItemIndex();
|
||||||
|
if (index < 0) return;
|
||||||
|
this.items.update((current) => {
|
||||||
|
if (index >= current.length) return current;
|
||||||
|
const applyToAll = this.sameSettingsForAll();
|
||||||
|
return current.map((item, idx) => {
|
||||||
|
if (!applyToAll && idx !== index) return item;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
supportEnabled: value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSameSettingsToggle(enabled: boolean) {
|
||||||
|
this.sameSettingsForAll.set(enabled);
|
||||||
|
if (!enabled) {
|
||||||
|
// Keep per-file values aligned with what the user sees in global controls
|
||||||
|
// right before switching to single-file mode.
|
||||||
|
this.syncAllItemsWithGlobalForm();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = this.getSelectedItem() ?? this.items()[0];
|
||||||
|
if (!selected) return;
|
||||||
|
|
||||||
|
const normalizedQuality = this.normalizeQualityValue(
|
||||||
|
selected.quality ?? this.form.get('quality')?.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.isPatchingSettings = true;
|
||||||
|
this.form.patchValue(
|
||||||
|
{
|
||||||
|
material: selected.material || this.form.get('material')?.value || 'PLA',
|
||||||
|
quality: normalizedQuality,
|
||||||
|
nozzleDiameter:
|
||||||
|
selected.nozzleDiameter ?? this.form.get('nozzleDiameter')?.value ?? 0.4,
|
||||||
|
layerHeight:
|
||||||
|
selected.layerHeight ?? this.form.get('layerHeight')?.value ?? 0.2,
|
||||||
|
infillDensity:
|
||||||
|
selected.infillDensity ?? this.form.get('infillDensity')?.value ?? 15,
|
||||||
|
infillPattern:
|
||||||
|
selected.infillPattern || this.form.get('infillPattern')?.value || 'grid',
|
||||||
|
supportEnabled:
|
||||||
|
selected.supportEnabled ??
|
||||||
|
this.form.get('supportEnabled')?.value ??
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ emitEvent: false },
|
||||||
|
);
|
||||||
|
this.isPatchingSettings = false;
|
||||||
|
|
||||||
|
const sharedPatch: Partial<FormItem> = {
|
||||||
|
quantity: selected.quantity,
|
||||||
|
material: selected.material,
|
||||||
|
quality: normalizedQuality,
|
||||||
|
color: selected.color,
|
||||||
|
filamentVariantId: selected.filamentVariantId,
|
||||||
|
supportEnabled: selected.supportEnabled,
|
||||||
|
infillDensity: selected.infillDensity,
|
||||||
|
infillPattern: selected.infillPattern,
|
||||||
|
layerHeight: selected.layerHeight,
|
||||||
|
nozzleDiameter: selected.nozzleDiameter,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.items.update((current) =>
|
||||||
|
current.map((item) => ({
|
||||||
|
...item,
|
||||||
|
...sharedPatch,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyGlobalMaterialToAll(materialCode: string): void {
|
||||||
|
const normalizedMaterial = materialCode || 'PLA';
|
||||||
|
const variants = this.getVariantsForMaterial(normalizedMaterial);
|
||||||
|
const fallback = variants.find((v) => !v.isOutOfStock) || variants[0];
|
||||||
|
this.items.update((current) =>
|
||||||
|
current.map((item) => ({
|
||||||
|
...item,
|
||||||
|
material: normalizedMaterial,
|
||||||
|
color: fallback ? fallback.colorName : item.color,
|
||||||
|
filamentVariantId: fallback ? fallback.id : item.filamentVariantId,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyGlobalFieldToAll(
|
||||||
|
field:
|
||||||
|
| 'quality'
|
||||||
|
| 'nozzleDiameter'
|
||||||
|
| 'layerHeight'
|
||||||
|
| 'infillDensity'
|
||||||
|
| 'infillPattern'
|
||||||
|
| 'supportEnabled',
|
||||||
|
value: string | number | boolean,
|
||||||
|
): void {
|
||||||
|
this.items.update((current) =>
|
||||||
|
current.map((item) => ({
|
||||||
|
...item,
|
||||||
|
[field]: value,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
patchItemSettingsByIndex(index: number, patch: Partial<FormItem>) {
|
||||||
|
if (!Number.isInteger(index) || index < 0) return;
|
||||||
|
const normalizedPatch: Partial<FormItem> = { ...patch };
|
||||||
|
if (normalizedPatch.quality !== undefined && normalizedPatch.quality !== null) {
|
||||||
|
normalizedPatch.quality = this.normalizeQualityValue(normalizedPatch.quality);
|
||||||
|
}
|
||||||
|
this.items.update((current) => {
|
||||||
|
if (index >= current.length) return current;
|
||||||
|
const updated = [...current];
|
||||||
|
updated[index] = { ...updated[index], ...normalizedPatch };
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
this.emitItemSettingsDiffChange();
|
this.emitItemSettingsDiffChange();
|
||||||
@@ -528,15 +797,22 @@ export class UploadFormComponent implements OnInit {
|
|||||||
|
|
||||||
setFiles(files: File[]) {
|
setFiles(files: File[]) {
|
||||||
const validItems: FormItem[] = [];
|
const validItems: FormItem[] = [];
|
||||||
const defaultSelection = this.getDefaultVariantSelection();
|
const defaults = this.getCurrentGlobalItemDefaults();
|
||||||
|
const defaultSelection = this.getDefaultVariantSelection(defaults.material);
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
validItems.push({
|
validItems.push({
|
||||||
file,
|
file,
|
||||||
previewFile: this.isStlFile(file) ? file : undefined,
|
previewFile: this.isStlFile(file) ? file : undefined,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
|
material: defaults.material,
|
||||||
|
quality: defaults.quality,
|
||||||
color: defaultSelection.colorName,
|
color: defaultSelection.colorName,
|
||||||
filamentVariantId: defaultSelection.filamentVariantId,
|
filamentVariantId: defaultSelection.filamentVariantId,
|
||||||
printSettings: this.getCurrentItemPrintSettings(),
|
supportEnabled: defaults.supportEnabled,
|
||||||
|
infillDensity: defaults.infillDensity,
|
||||||
|
infillPattern: defaults.infillPattern,
|
||||||
|
layerHeight: defaults.layerHeight,
|
||||||
|
nozzleDiameter: defaults.nozzleDiameter,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,11 +835,28 @@ export class UploadFormComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDefaultVariantSelection(): {
|
private getCurrentGlobalItemDefaults(): Omit<FormItem, 'file' | 'previewFile' | 'quantity' | 'color' | 'filamentVariantId'> & {
|
||||||
|
material: string;
|
||||||
|
quality: string;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
material: this.form.get('material')?.value || 'PLA',
|
||||||
|
quality: this.normalizeQualityValue(this.form.get('quality')?.value),
|
||||||
|
supportEnabled: !!this.form.get('supportEnabled')?.value,
|
||||||
|
infillDensity: Number(this.form.get('infillDensity')?.value ?? 15),
|
||||||
|
infillPattern: this.form.get('infillPattern')?.value || 'grid',
|
||||||
|
layerHeight: Number(this.form.get('layerHeight')?.value ?? 0.2),
|
||||||
|
nozzleDiameter: Number(this.form.get('nozzleDiameter')?.value ?? 0.4),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDefaultVariantSelection(materialCode?: string): {
|
||||||
colorName: string;
|
colorName: string;
|
||||||
filamentVariantId?: number;
|
filamentVariantId?: number;
|
||||||
} {
|
} {
|
||||||
const vars = this.currentMaterialVariants();
|
const vars = materialCode
|
||||||
|
? this.getVariantsForMaterial(materialCode)
|
||||||
|
: this.currentMaterialVariants();
|
||||||
if (vars && vars.length > 0) {
|
if (vars && vars.length > 0) {
|
||||||
const preferred = vars.find((v) => !v.isOutOfStock) || vars[0];
|
const preferred = vars.find((v) => !v.isOutOfStock) || vars[0];
|
||||||
return {
|
return {
|
||||||
@@ -673,13 +966,15 @@ export class UploadFormComponent implements OnInit {
|
|||||||
console.log('Form Valid:', this.form.valid, 'Items:', this.items().length);
|
console.log('Form Valid:', this.form.valid, 'Items:', this.items().length);
|
||||||
|
|
||||||
if (this.form.valid && this.items().length > 0) {
|
if (this.form.valid && this.items().length > 0) {
|
||||||
|
const items = this.items();
|
||||||
|
const firstItemMaterial = items[0]?.material;
|
||||||
console.log(
|
console.log(
|
||||||
'UploadFormComponent: Emitting submitRequest',
|
'UploadFormComponent: Emitting submitRequest',
|
||||||
this.form.value,
|
this.form.value,
|
||||||
);
|
);
|
||||||
this.submitRequest.emit({
|
this.submitRequest.emit({
|
||||||
...this.form.getRawValue(),
|
...this.form.getRawValue(),
|
||||||
items: this.toQuoteRequestItems(), // Include per-item print settings overrides
|
items: this.items(), // Pass the items array explicitly AFTER form value to prevent overwrite
|
||||||
mode: this.mode(),
|
mode: this.mode(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -713,325 +1008,6 @@ export class UploadFormComponent implements OnInit {
|
|||||||
return (fileName || '').split(/[\\/]/).pop()?.trim().toLowerCase() ?? '';
|
return (fileName || '').split(/[\\/]/).pop()?.trim().toLowerCase() ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateLayerHeightOptionsForNozzle(
|
|
||||||
nozzleValue: unknown,
|
|
||||||
preserveCurrent: boolean,
|
|
||||||
): void {
|
|
||||||
const key = this.toNozzleKey(nozzleValue);
|
|
||||||
const nozzleSpecific = this.layerHeightsByNozzle[key] || [];
|
|
||||||
const available =
|
|
||||||
nozzleSpecific.length > 0 ? nozzleSpecific : this.allLayerHeights;
|
|
||||||
this.layerHeights.set(available);
|
|
||||||
|
|
||||||
const control = this.form.get('layerHeight');
|
|
||||||
if (!control) return;
|
|
||||||
|
|
||||||
const currentValue = Number(control.value);
|
|
||||||
const currentAllowed = available.some(
|
|
||||||
(option) => Math.abs(Number(option.value) - currentValue) < 0.0001,
|
|
||||||
);
|
|
||||||
if (preserveCurrent && currentAllowed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const preferred = available.find(
|
|
||||||
(option) => Math.abs(Number(option.value) - 0.2) < 0.0001,
|
|
||||||
);
|
|
||||||
const next = preferred ?? available[0];
|
|
||||||
if (next) {
|
|
||||||
control.setValue(next.value, { emitEvent: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private toNozzleKey(value: unknown): string {
|
|
||||||
const numeric = Number(value);
|
|
||||||
if (!Number.isFinite(numeric)) return '';
|
|
||||||
return numeric.toFixed(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentRequestDraft(): QuoteRequest | null {
|
|
||||||
if (this.items().length === 0) return null;
|
|
||||||
const raw = this.form.getRawValue();
|
|
||||||
return {
|
|
||||||
items: this.toQuoteRequestItems(),
|
|
||||||
material: raw.material,
|
|
||||||
quality: raw.quality,
|
|
||||||
notes: raw.notes,
|
|
||||||
infillDensity: raw.infillDensity,
|
|
||||||
infillPattern: raw.infillPattern,
|
|
||||||
supportEnabled: raw.supportEnabled,
|
|
||||||
layerHeight: raw.layerHeight,
|
|
||||||
nozzleDiameter: raw.nozzleDiameter,
|
|
||||||
mode: this.mode(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentPrintSettings(): {
|
|
||||||
mode: 'easy' | 'advanced';
|
|
||||||
material: string;
|
|
||||||
quality: string;
|
|
||||||
nozzleDiameter: number;
|
|
||||||
layerHeight: number;
|
|
||||||
infillDensity: number;
|
|
||||||
infillPattern: string;
|
|
||||||
supportEnabled: boolean;
|
|
||||||
} {
|
|
||||||
const raw = this.form.getRawValue();
|
|
||||||
return {
|
|
||||||
mode: this.mode(),
|
|
||||||
material: String(raw.material || 'PLA'),
|
|
||||||
quality: String(raw.quality || 'standard'),
|
|
||||||
nozzleDiameter: Number(raw.nozzleDiameter ?? 0.4),
|
|
||||||
layerHeight: Number(raw.layerHeight ?? 0.2),
|
|
||||||
infillDensity: Number(raw.infillDensity ?? 20),
|
|
||||||
infillPattern: String(raw.infillPattern || 'grid'),
|
|
||||||
supportEnabled: Boolean(raw.supportEnabled),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitPrintSettingsChange(): void {
|
|
||||||
this.printSettingsChange.emit(this.getCurrentPrintSettings());
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadSelectedItemSettingsIntoForm(): void {
|
|
||||||
const selected = this.selectedFile();
|
|
||||||
if (!selected) return;
|
|
||||||
const item = this.items().find((current) => current.file === selected);
|
|
||||||
if (!item) return;
|
|
||||||
|
|
||||||
this.isPatchingSettings = true;
|
|
||||||
this.form.patchValue(
|
|
||||||
{
|
|
||||||
material: item.printSettings.material,
|
|
||||||
quality: item.printSettings.quality,
|
|
||||||
nozzleDiameter: item.printSettings.nozzleDiameter,
|
|
||||||
layerHeight: item.printSettings.layerHeight,
|
|
||||||
infillDensity: item.printSettings.infillDensity,
|
|
||||||
infillPattern: item.printSettings.infillPattern,
|
|
||||||
supportEnabled: item.printSettings.supportEnabled,
|
|
||||||
},
|
|
||||||
{ emitEvent: false },
|
|
||||||
);
|
|
||||||
this.isPatchingSettings = false;
|
|
||||||
this.updateLayerHeightOptionsForNozzle(
|
|
||||||
item.printSettings.nozzleDiameter,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
this.updateVariants();
|
|
||||||
}
|
|
||||||
|
|
||||||
private syncSelectedItemSettingsFromForm(): void {
|
|
||||||
const currentSettings = this.getCurrentItemPrintSettings();
|
|
||||||
|
|
||||||
if (this.shouldApplySettingsToAllItems()) {
|
|
||||||
this.applyCurrentSettingsToAllItems(currentSettings);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selected = this.selectedFile();
|
|
||||||
if (!selected) return;
|
|
||||||
|
|
||||||
this.items.update((current) =>
|
|
||||||
current.map((item) => {
|
|
||||||
if (item.file !== selected) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
const variants = this.getVariantsForMaterialCode(currentSettings.material);
|
|
||||||
const fallback = variants.find((v) => !v.isOutOfStock) || variants[0];
|
|
||||||
const byId =
|
|
||||||
item.filamentVariantId != null
|
|
||||||
? variants.find((v) => v.id === item.filamentVariantId)
|
|
||||||
: null;
|
|
||||||
const byColor = variants.find((v) => v.colorName === item.color);
|
|
||||||
const selectedVariant = byId || byColor || fallback;
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
printSettings: { ...currentSettings },
|
|
||||||
color: selectedVariant ? selectedVariant.colorName : item.color,
|
|
||||||
filamentVariantId: selectedVariant ? selectedVariant.id : undefined,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitItemSettingsDiffChange(): void {
|
|
||||||
const currentItems = this.items();
|
|
||||||
if (currentItems.length === 0) {
|
|
||||||
this.itemSettingsDiffChange.emit({});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const signatureCounts = new Map<string, number>();
|
|
||||||
currentItems.forEach((item) => {
|
|
||||||
const signature = this.settingsSignature(item.printSettings);
|
|
||||||
signatureCounts.set(signature, (signatureCounts.get(signature) || 0) + 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
let dominantSignature = '';
|
|
||||||
let dominantCount = 0;
|
|
||||||
signatureCounts.forEach((count, signature) => {
|
|
||||||
if (count > dominantCount) {
|
|
||||||
dominantCount = count;
|
|
||||||
dominantSignature = signature;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasDominant = dominantCount > 1;
|
|
||||||
const dominantSettings = hasDominant
|
|
||||||
? currentItems.find(
|
|
||||||
(item) =>
|
|
||||||
this.settingsSignature(item.printSettings) === dominantSignature,
|
|
||||||
)?.printSettings
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const diffByFileName: Record<string, ItemSettingsDiffInfo> = {};
|
|
||||||
currentItems.forEach((item) => {
|
|
||||||
const differences = dominantSettings
|
|
||||||
? this.describeSettingsDifferences(dominantSettings, item.printSettings)
|
|
||||||
: [];
|
|
||||||
diffByFileName[item.file.name] = {
|
|
||||||
differences,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.itemSettingsDiffChange.emit(diffByFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sameItemPrintSettings(
|
|
||||||
a: ItemPrintSettings,
|
|
||||||
b: ItemPrintSettings,
|
|
||||||
): boolean {
|
|
||||||
return (
|
|
||||||
a.material.trim().toUpperCase() === b.material.trim().toUpperCase() &&
|
|
||||||
a.quality.trim().toLowerCase() === b.quality.trim().toLowerCase() &&
|
|
||||||
Math.abs(a.nozzleDiameter - b.nozzleDiameter) < 0.0001 &&
|
|
||||||
Math.abs(a.layerHeight - b.layerHeight) < 0.0001 &&
|
|
||||||
Math.abs(a.infillDensity - b.infillDensity) < 0.0001 &&
|
|
||||||
a.infillPattern.trim().toLowerCase() ===
|
|
||||||
b.infillPattern.trim().toLowerCase() &&
|
|
||||||
Boolean(a.supportEnabled) === Boolean(b.supportEnabled)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private settingsSignature(settings: ItemPrintSettings): string {
|
|
||||||
return JSON.stringify({
|
|
||||||
material: settings.material.trim().toUpperCase(),
|
|
||||||
quality: settings.quality.trim().toLowerCase(),
|
|
||||||
nozzleDiameter: Number(settings.nozzleDiameter.toFixed(2)),
|
|
||||||
layerHeight: Number(settings.layerHeight.toFixed(3)),
|
|
||||||
infillDensity: Number(settings.infillDensity.toFixed(2)),
|
|
||||||
infillPattern: settings.infillPattern.trim().toLowerCase(),
|
|
||||||
supportEnabled: Boolean(settings.supportEnabled),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private describeSettingsDifferences(
|
|
||||||
baseline: ItemPrintSettings,
|
|
||||||
current: ItemPrintSettings,
|
|
||||||
): string[] {
|
|
||||||
if (this.sameItemPrintSettings(baseline, current)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const differences: string[] = [];
|
|
||||||
if (baseline.material.trim().toUpperCase() !== current.material.trim().toUpperCase()) {
|
|
||||||
differences.push(`${current.material}`);
|
|
||||||
}
|
|
||||||
if (baseline.quality.trim().toLowerCase() !== current.quality.trim().toLowerCase()) {
|
|
||||||
differences.push(`Qualita: ${current.quality}`);
|
|
||||||
}
|
|
||||||
if (Math.abs(baseline.nozzleDiameter - current.nozzleDiameter) >= 0.0001) {
|
|
||||||
differences.push(`Nozzle: ${current.nozzleDiameter.toFixed(1)} mm`);
|
|
||||||
}
|
|
||||||
if (Math.abs(baseline.layerHeight - current.layerHeight) >= 0.0001) {
|
|
||||||
differences.push(`Layer: ${current.layerHeight.toFixed(2)} mm`);
|
|
||||||
}
|
|
||||||
if (Math.abs(baseline.infillDensity - current.infillDensity) >= 0.0001) {
|
|
||||||
differences.push(`Infill: ${current.infillDensity}%`);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
baseline.infillPattern.trim().toLowerCase() !==
|
|
||||||
current.infillPattern.trim().toLowerCase()
|
|
||||||
) {
|
|
||||||
differences.push(`Pattern: ${current.infillPattern}`);
|
|
||||||
}
|
|
||||||
if (Boolean(baseline.supportEnabled) !== Boolean(current.supportEnabled)) {
|
|
||||||
differences.push(
|
|
||||||
`Supporti: ${current.supportEnabled ? 'attivi' : 'disattivi'}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return differences;
|
|
||||||
}
|
|
||||||
|
|
||||||
private toQuoteRequestItems(): QuoteRequest['items'] {
|
|
||||||
return this.items().map((item) => ({
|
|
||||||
file: item.file,
|
|
||||||
quantity: item.quantity,
|
|
||||||
color: item.color,
|
|
||||||
filamentVariantId: item.filamentVariantId,
|
|
||||||
material: item.printSettings.material,
|
|
||||||
quality: item.printSettings.quality,
|
|
||||||
nozzleDiameter: item.printSettings.nozzleDiameter,
|
|
||||||
layerHeight: item.printSettings.layerHeight,
|
|
||||||
infillDensity: item.printSettings.infillDensity,
|
|
||||||
infillPattern: item.printSettings.infillPattern,
|
|
||||||
supportEnabled: item.printSettings.supportEnabled,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCurrentItemPrintSettings(): ItemPrintSettings {
|
|
||||||
const settings = this.getCurrentPrintSettings();
|
|
||||||
return {
|
|
||||||
material: settings.material,
|
|
||||||
quality: settings.quality,
|
|
||||||
nozzleDiameter: settings.nozzleDiameter,
|
|
||||||
layerHeight: settings.layerHeight,
|
|
||||||
infillDensity: settings.infillDensity,
|
|
||||||
infillPattern: settings.infillPattern,
|
|
||||||
supportEnabled: settings.supportEnabled,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private shouldApplySettingsToAllItems(): boolean {
|
|
||||||
return this.parseBooleanControlValue(this.form.get('syncAllItems')?.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private applyCurrentSettingsToAllItems(currentSettings: ItemPrintSettings): void {
|
|
||||||
this.items.update((current) =>
|
|
||||||
current.map((item) => {
|
|
||||||
const variants = this.getVariantsForMaterialCode(currentSettings.material);
|
|
||||||
const fallback = variants.find((v) => !v.isOutOfStock) || variants[0];
|
|
||||||
const byId =
|
|
||||||
item.filamentVariantId != null
|
|
||||||
? variants.find((v) => v.id === item.filamentVariantId)
|
|
||||||
: null;
|
|
||||||
const byColor = variants.find((v) => v.colorName === item.color);
|
|
||||||
const selectedVariant = byId || byColor || fallback;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
printSettings: { ...currentSettings },
|
|
||||||
color: selectedVariant ? selectedVariant.colorName : item.color,
|
|
||||||
filamentVariantId: selectedVariant ? selectedVariant.id : undefined,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseBooleanControlValue(raw: unknown): boolean {
|
|
||||||
if (this.items().length <= 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (raw === true || raw === 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (typeof raw === 'string') {
|
|
||||||
const normalized = raw.trim().toLowerCase();
|
|
||||||
return normalized === 'true' || normalized === '1' || normalized === 'on';
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private applySettingsLock(locked: boolean): void {
|
private applySettingsLock(locked: boolean): void {
|
||||||
const controlsToLock = [
|
const controlsToLock = [
|
||||||
'syncAllItems',
|
'syncAllItems',
|
||||||
|
|||||||
@@ -8,8 +8,15 @@ export interface QuoteRequest {
|
|||||||
items: {
|
items: {
|
||||||
file: File;
|
file: File;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
material?: string;
|
||||||
|
quality?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
filamentVariantId?: number;
|
filamentVariantId?: number;
|
||||||
|
supportEnabled?: boolean;
|
||||||
|
infillDensity?: number;
|
||||||
|
infillPattern?: string;
|
||||||
|
layerHeight?: number;
|
||||||
|
nozzleDiameter?: number;
|
||||||
material?: string;
|
material?: string;
|
||||||
quality?: string;
|
quality?: string;
|
||||||
nozzleDiameter?: number;
|
nozzleDiameter?: number;
|
||||||
@@ -37,8 +44,14 @@ export interface QuoteItem {
|
|||||||
unitWeight: number; // grams
|
unitWeight: number; // grams
|
||||||
quantity: number;
|
quantity: number;
|
||||||
material?: string;
|
material?: string;
|
||||||
|
quality?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
filamentVariantId?: number;
|
filamentVariantId?: number;
|
||||||
|
supportEnabled?: boolean;
|
||||||
|
infillDensity?: number;
|
||||||
|
infillPattern?: string;
|
||||||
|
layerHeight?: number;
|
||||||
|
nozzleDiameter?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QuoteResult {
|
export interface QuoteResult {
|
||||||
@@ -109,18 +122,12 @@ export interface NumericOption {
|
|||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NozzleLayerHeightsOption {
|
|
||||||
nozzleDiameter: number;
|
|
||||||
layerHeights: NumericOption[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OptionsResponse {
|
export interface OptionsResponse {
|
||||||
materials: MaterialOption[];
|
materials: MaterialOption[];
|
||||||
qualities: QualityOption[];
|
qualities: QualityOption[];
|
||||||
infillPatterns: InfillOption[];
|
infillPatterns: InfillOption[];
|
||||||
layerHeights: NumericOption[];
|
layerHeights: NumericOption[];
|
||||||
nozzleDiameters: NumericOption[];
|
nozzleDiameters: NumericOption[];
|
||||||
layerHeightsByNozzle?: NozzleLayerHeightsOption[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI Option for Select Component
|
// UI Option for Select Component
|
||||||
@@ -157,7 +164,7 @@ export class QuoteEstimatorService {
|
|||||||
|
|
||||||
if (normalized === 'draft') {
|
if (normalized === 'draft') {
|
||||||
return {
|
return {
|
||||||
quality: 'draft',
|
quality: 'extra_fine',
|
||||||
layerHeight: 0.24,
|
layerHeight: 0.24,
|
||||||
infillDensity: 12,
|
infillDensity: 12,
|
||||||
infillPattern: 'grid',
|
infillPattern: 'grid',
|
||||||
@@ -303,9 +310,10 @@ export class QuoteEstimatorService {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', item.file);
|
formData.append('file', item.file);
|
||||||
|
|
||||||
|
const effectiveQuality = item.quality || request.quality;
|
||||||
const easyPreset =
|
const easyPreset =
|
||||||
request.mode === 'easy'
|
request.mode === 'easy'
|
||||||
? this.buildEasyModePreset(request.quality)
|
? this.buildEasyModePreset(effectiveQuality)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
@@ -315,6 +323,10 @@ export class QuoteEstimatorService {
|
|||||||
: request.mode.toUpperCase(),
|
: request.mode.toUpperCase(),
|
||||||
material: item.material || request.material,
|
material: item.material || request.material,
|
||||||
filamentVariantId: item.filamentVariantId,
|
filamentVariantId: item.filamentVariantId,
|
||||||
|
quantity: item.quantity,
|
||||||
|
quality: easyPreset ? easyPreset.quality : effectiveQuality,
|
||||||
|
supportsEnabled:
|
||||||
|
item.supportEnabled ?? request.supportEnabled ?? false,
|
||||||
quality: easyPreset
|
quality: easyPreset
|
||||||
? easyPreset.quality
|
? easyPreset.quality
|
||||||
: item.quality || request.quality,
|
: item.quality || request.quality,
|
||||||
@@ -325,15 +337,19 @@ export class QuoteEstimatorService {
|
|||||||
color: item.color || '#FFFFFF',
|
color: item.color || '#FFFFFF',
|
||||||
layerHeight: easyPreset
|
layerHeight: easyPreset
|
||||||
? easyPreset.layerHeight
|
? easyPreset.layerHeight
|
||||||
|
: (item.layerHeight ?? request.layerHeight),
|
||||||
: item.layerHeight ?? request.layerHeight,
|
: item.layerHeight ?? request.layerHeight,
|
||||||
infillDensity: easyPreset
|
infillDensity: easyPreset
|
||||||
? easyPreset.infillDensity
|
? easyPreset.infillDensity
|
||||||
|
: (item.infillDensity ?? request.infillDensity),
|
||||||
: item.infillDensity ?? request.infillDensity,
|
: item.infillDensity ?? request.infillDensity,
|
||||||
infillPattern: easyPreset
|
infillPattern: easyPreset
|
||||||
? easyPreset.infillPattern
|
? easyPreset.infillPattern
|
||||||
|
: (item.infillPattern ?? request.infillPattern),
|
||||||
: item.infillPattern ?? request.infillPattern,
|
: item.infillPattern ?? request.infillPattern,
|
||||||
nozzleDiameter: easyPreset
|
nozzleDiameter: easyPreset
|
||||||
? easyPreset.nozzleDiameter
|
? easyPreset.nozzleDiameter
|
||||||
|
: (item.nozzleDiameter ?? request.nozzleDiameter),
|
||||||
: item.nozzleDiameter ?? request.nozzleDiameter,
|
: item.nozzleDiameter ?? request.nozzleDiameter,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -492,6 +508,11 @@ export class QuoteEstimatorService {
|
|||||||
material: item.materialCode || session.materialCode,
|
material: item.materialCode || session.materialCode,
|
||||||
color: item.colorCode,
|
color: item.colorCode,
|
||||||
filamentVariantId: item.filamentVariantId,
|
filamentVariantId: item.filamentVariantId,
|
||||||
|
supportEnabled: item.supportsEnabled,
|
||||||
|
infillDensity: item.infillPercent,
|
||||||
|
infillPattern: item.infillPattern,
|
||||||
|
layerHeight: item.layerHeightMm,
|
||||||
|
nozzleDiameter: item.nozzleDiameterMm,
|
||||||
})),
|
})),
|
||||||
setupCost: session.setupCostChf || 0,
|
setupCost: session.setupCostChf || 0,
|
||||||
globalMachineCost: sessionData.globalMachineCostChf || 0,
|
globalMachineCost: sessionData.globalMachineCostChf || 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user