mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-21 01:57:41 +02:00
fix(slicer): wrong-printer slicing + sliced-archive filament list + per-instance MakerWorld compat
Five stacked slice-pipeline bugs that each made the modal's profile picker
theatrical for 3MF inputs:
(1) `_strip_3mf_embedded_settings` removed `model_settings.config` /
`slice_info.config` / `cut_information.xml` along with
`project_settings.config`. The CLI silently exited after
"Initializing StaticPrintConfigs" — exit 0, no result.json — and
Bambuddy masked the failure by re-running with embedded settings
and the source's bound printer. Strip removed from the dispatch
path entirely.
(2) Standard-tier preset stubs lacked the `type` field, so the CLI
rejected `--load-settings` with rc=-5 ("input preset file is
invalid") and the same masking fallback fired. Added
`_SLOT_TO_PROFILE_TYPE` so each stub carries the right
machine/process/filament discriminator.
(3) Sliced-archive cards listed every project-wide AMS slot (16+
swatches for a 2-color print). `slice_and_persist_as_archive` now
reads `filament_type` / `filament_color` from the sliced output's
`slice_info.config` (which `ThreeMFParser` already gates on
`used_g > 0`) instead of inheriting from the source archive.
(4) SliceModal had no warning when the picked printer profile didn't
match the source 3MF — the CLI rejects cross-printer slices
(rc=-16) and fell back to embedded settings, producing wrong-printer
g-code that errored at print dispatch. Plates response now exposes
`source_printer_model`; the modal compares against the picked
profile name and disables Slice + shows an inline warning on
mismatch.
(5) MakerWorld URL-paste resolver listed plate instances without
showing which printer each was sliced for (`/instances/hits`
omits compatibility info that lives on `design.instances[]
.extention.modelInfo`). The resolve route now joins both payloads
by instance ID and forwards `compatibility` + `otherCompatibility`
onto each hit; the MakerWorld page renders "Sliced for {primary}"
+ "Also marked compatible: ..." per row.
Tests: 6 unit tests for `extract_source_printer_model_from_3mf`, 1 for
filtered filament metadata via ThreeMFParser, 2 for makerworld resolve
compat-merge (happy path + missing modelInfo), 3 frontend SliceModal
tests for the printer-mismatch warning + Slice-disabled gate. New i18n
keys `slice.printerMismatch`, `makerworld.slicedFor`,
`makerworld.alsoCompatible` across all 8 locales.
This commit is contained in:
@@ -241,11 +241,38 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
|
||||
},
|
||||
});
|
||||
|
||||
// Pre-slice compatibility check: the slicer CLI (both OrcaSlicer and
|
||||
// BambuStudio) cannot re-slice a 3MF for a printer different from the one
|
||||
// it was originally bound to — the cross-printer "convert project" flow
|
||||
// is desktop-Studio only. If we can match the source's printer model to a
|
||||
// SliceModal-known model and the user's chosen printer profile names a
|
||||
// different model, surface a warning before they click Slice.
|
||||
const sourcePrinterModel = platesQuery.data?.source_printer_model ?? null;
|
||||
const printerProfileName = printerPreset
|
||||
? presetsQuery.data?.[printerPreset.source].printer.find((p) => p.id === printerPreset.id)?.name
|
||||
: null;
|
||||
// Profile names follow `<model> <nozzle> nozzle` (e.g. "Bambu Lab H2D 0.4
|
||||
// nozzle"). The CLI compat check uses the model prefix; substring match
|
||||
// catches both standard and locally-imported user-named profiles that
|
||||
// include the model in the name. Cloud presets with arbitrary names
|
||||
// (e.g. "My Custom X1C") fall through to no-warning, which is a
|
||||
// reasonable default — the user picked it knowingly.
|
||||
const printerMismatch =
|
||||
!!sourcePrinterModel &&
|
||||
!!printerProfileName &&
|
||||
!printerProfileName.toLowerCase().includes(sourcePrinterModel.toLowerCase());
|
||||
|
||||
// Slice button stays disabled while the printer mismatch warning is
|
||||
// visible: clicking it would silently fall back to embedded settings and
|
||||
// produce a wrong-printer file, the exact UX bug the warning is here to
|
||||
// prevent. Only re-enable when the user picks a matching profile (or
|
||||
// cloud preset whose name we can't parse).
|
||||
const isReady =
|
||||
printerPreset != null &&
|
||||
processPreset != null &&
|
||||
filamentPresets.length > 0 &&
|
||||
filamentPresets.every((r) => r != null);
|
||||
filamentPresets.every((r) => r != null) &&
|
||||
!printerMismatch;
|
||||
const isEnqueuing = enqueueMutation.isPending;
|
||||
|
||||
// Step 1: plate picker for multi-plate 3MF sources. Cancelling closes the
|
||||
@@ -301,7 +328,10 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
{(platesQuery.isLoading || presetsQuery.isLoading || filamentReqsQuery.isLoading) && (
|
||||
{/* Preset listing loader — printer/process dropdowns can't render
|
||||
without it. Plate query reuses the same spinner since it's
|
||||
also blocking. */}
|
||||
{(platesQuery.isLoading || presetsQuery.isLoading) && (
|
||||
<div className="flex items-center gap-2 text-bambu-gray text-sm">
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
{t('slice.loadingPresets', 'Loading presets…')}
|
||||
@@ -336,37 +366,62 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
|
||||
onChange={setProcessPreset}
|
||||
disabled={isEnqueuing}
|
||||
/>
|
||||
{filamentSlots.map((slot, idx) => (
|
||||
<PresetDropdown
|
||||
key={`filament-${idx}`}
|
||||
label={
|
||||
filamentSlots.length > 1
|
||||
? t('slice.filamentSlot', {
|
||||
index: idx + 1,
|
||||
type: slot.type,
|
||||
defaultValue: `Filament ${idx + 1} (${slot.type || ''})`,
|
||||
})
|
||||
: t('slice.filament', 'Filament profile')
|
||||
}
|
||||
slot="filament"
|
||||
data={presetsQuery.data}
|
||||
value={filamentPresets[idx] ?? null}
|
||||
onChange={(ref) =>
|
||||
setFilamentPresets((current) => {
|
||||
const next = current.length === filamentSlots.length
|
||||
? [...current]
|
||||
: filamentSlots.map((_, i) => current[i] ?? null);
|
||||
next[idx] = ref;
|
||||
return next;
|
||||
})
|
||||
}
|
||||
disabled={isEnqueuing}
|
||||
swatchColor={filamentSlots.length > 1 ? slot.color : undefined}
|
||||
/>
|
||||
))}
|
||||
{/* 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
|
||||
dropdowns instead of an opaque "Loading presets…" wait. */}
|
||||
{filamentReqsQuery.isLoading ? (
|
||||
<div className="flex items-center gap-2 text-bambu-gray text-sm py-2">
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
{t('slice.analyzingPlateFilaments', 'Analyzing plate filaments…')}
|
||||
</div>
|
||||
) : (
|
||||
filamentSlots.map((slot, idx) => (
|
||||
<PresetDropdown
|
||||
key={`filament-${idx}`}
|
||||
label={
|
||||
filamentSlots.length > 1
|
||||
? t('slice.filamentSlot', {
|
||||
index: idx + 1,
|
||||
type: slot.type,
|
||||
defaultValue: `Filament ${idx + 1} (${slot.type || ''})`,
|
||||
})
|
||||
: t('slice.filament', 'Filament profile')
|
||||
}
|
||||
slot="filament"
|
||||
data={presetsQuery.data}
|
||||
value={filamentPresets[idx] ?? null}
|
||||
onChange={(ref) =>
|
||||
setFilamentPresets((current) => {
|
||||
const next = current.length === filamentSlots.length
|
||||
? [...current]
|
||||
: filamentSlots.map((_, i) => current[i] ?? null);
|
||||
next[idx] = ref;
|
||||
return next;
|
||||
})
|
||||
}
|
||||
disabled={isEnqueuing}
|
||||
swatchColor={filamentSlots.length > 1 ? slot.color : undefined}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{printerMismatch && (
|
||||
<div
|
||||
className="text-sm text-amber-200 bg-amber-900/20 border border-amber-700/40 rounded p-2"
|
||||
role="alert"
|
||||
>
|
||||
{t('slice.printerMismatch', {
|
||||
source: sourcePrinterModel,
|
||||
target: printerProfileName,
|
||||
defaultValue:
|
||||
'This 3MF was sliced for {{source}}, but you picked {{target}}. The slicer CLI cannot re-slice a 3MF for a different printer — open the source in Bambu Studio, change the printer, and re-export.',
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errorMessage && (
|
||||
<div className="text-sm text-red-400 bg-red-900/20 border border-red-900/40 rounded p-2" role="alert">
|
||||
{errorMessage}
|
||||
|
||||
Reference in New Issue
Block a user