feat(slicing): build-plate override in the SliceModal (#1337)

Slicing an STL via the integrated slicer always defaulted to whatever
  curr_bed_type lived in the chosen process preset (typically "Cool
  Plate"), which the slicer CLI rejected for high-temp filaments with
  "Plate 1: Cool Plate does not support filament 1". The user had no
  way to switch plates without cloning the preset in BambuStudio.

  The Slice modal now exposes a Build plate dropdown with the six
  canonical BambuStudio / OrcaSlicer plates (Cool Plate, Cool Plate
  SuperTack, Engineering Plate, High Temp Plate, Textured PEI Plate,
  Smooth PEI Plate) plus an "Auto (use process preset)" option that
  preserves the previous behavior. Positioned between Process profile
  and Filament rows so a long filament list never pushes it off the
  modal's scrolled viewport, and always enabled regardless of whether
  the user picked a Printer Preset Bundle.

  A new bed_type field on SliceRequest flows through both dispatch
  paths:
  - Resolved-preset path: _patch_process_bed_type overwrites
    curr_bed_type on the process JSON before forwarding to the sidecar.
    Works end-to-end today, no sidecar change needed.
  - Bundle dispatch path: slice_with_bundle adds a bedType form field
    to the sidecar multipart. The sidecar (maziggy/orca-slicer-api
    fork) needs a matching change to honor it as --curr_bed_type on
    the CLI invocation; until then the field is silently ignored and
    the slice runs with the bundle's default plate.
This commit is contained in:
maziggy
2026-05-14 15:28:41 +02:00
parent b51ef33472
commit ccf985abfc
19 changed files with 643 additions and 119 deletions
+73
View File
@@ -261,6 +261,13 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
// and we'll backfill 1 at submit time). Set to a 1-indexed plate number once
// the user picks one (or implicitly for single-plate sources).
const [selectedPlate, setSelectedPlate] = useState<number | null>(null);
// Build-plate override (#1337). null = inherit from the process preset
// (the default). Set to a canonical slicer enum value to patch
// curr_bed_type into the resolved process JSON before slicing — needed
// because the process preset's default plate (typically "Cool Plate") is
// incompatible with high-temp filaments like ABS / ASA / PC, and the
// user had no way to switch plates without cloning the preset.
const [bedType, setBedType] = useState<string | null>(null);
const platesQuery = useQuery({
queryKey: ['slicePlates', source.kind, source.id],
@@ -429,6 +436,9 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
body = {
bundle: bundleSpec,
...(selectedPlate != null ? { plate: selectedPlate } : {}),
// Bed-type override (#1337) also flows through the bundle path —
// the sidecar forwards `bedType` as --curr_bed_type to the CLI.
...(bedType != null ? { bed_type: bedType } : {}),
};
} else {
if (
@@ -451,6 +461,8 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
// omit otherwise so the backend default applies for STL / single-plate
// 3MF sources where the concept doesn't apply.
...(selectedPlate != null ? { plate: selectedPlate } : {}),
// Bed-type override (#1337).
...(bedType != null ? { bed_type: bedType } : {}),
};
}
if (source.kind === 'libraryFile') {
@@ -651,6 +663,17 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
/>
</>
)}
{/* Bed-type override (#1337). Always visible, always enabled.
In non-bundle mode the backend patches curr_bed_type on the
resolved process JSON before forwarding to the sidecar; in
bundle mode the same value rides through as a sidecar form
field so the bundle's materialised process JSON gets the
override applied there too. */}
<BedTypeDropdown
value={bedType}
onChange={setBedType}
disabled={isEnqueuing}
/>
{/* Filament reqs may need a server-side preview-slice for
unsliced project files (single-pass, then cached). Show a
scoped spinner so the user sees the printer/process
@@ -831,6 +854,56 @@ function CloudStatusBanner({ status }: { status: SlicerCloudStatus }) {
);
}
// Build-plate options offered in the SliceModal (#1337). Values are the
// canonical strings the slicer's StaticPrintConfig validator accepts as
// `curr_bed_type` — BambuStudio is the default sidecar, so this matches its
// enum; OrcaSlicer accepts the same set with a Supertack alias that users
// can target via the same dropdown if they re-import their presets.
const BED_TYPE_OPTIONS: { value: string; labelKey: string; fallback: string }[] = [
{ value: 'Cool Plate', labelKey: 'slice.bedType.coolPlate', fallback: 'Cool Plate' },
{
value: 'Cool Plate (SuperTack)',
labelKey: 'slice.bedType.coolPlateSuperTack',
fallback: 'Cool Plate SuperTack',
},
{ value: 'Engineering Plate', labelKey: 'slice.bedType.engineering', fallback: 'Engineering Plate' },
{ value: 'High Temp Plate', labelKey: 'slice.bedType.highTemp', fallback: 'High Temp Plate' },
{ value: 'Textured PEI Plate', labelKey: 'slice.bedType.texturedPEI', fallback: 'Textured PEI Plate' },
{ value: 'Smooth PEI Plate', labelKey: 'slice.bedType.smoothPEI', fallback: 'Smooth PEI Plate' },
];
function BedTypeDropdown({
value,
onChange,
disabled,
}: {
value: string | null;
onChange: (value: string | null) => void;
disabled?: boolean;
}) {
const { t } = useTranslation();
return (
<label className="block">
<span className="block text-xs text-bambu-gray mb-1">
{t('slice.bedType.label', 'Build plate')}
</span>
<select
value={value ?? ''}
onChange={(e) => onChange(e.target.value === '' ? null : e.target.value)}
disabled={disabled}
className="w-full px-3 py-2 rounded-md bg-bambu-dark border border-bambu-dark-tertiary text-white text-sm focus:outline-none focus:border-bambu-gray disabled:opacity-50"
>
<option value="">{t('slice.bedType.auto', 'Auto (use process preset)')}</option>
{BED_TYPE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{t(opt.labelKey, opt.fallback)}
</option>
))}
</select>
</label>
);
}
interface PresetDropdownProps {
label: string;
slot: Slot;