Hide unnecessary target model selector on "Any" tab (#528)

When scheduling a print to "Any {model}", a redundant "Target Model"
dropdown appeared even though the G-code is already sliced for a
specific printer model. Changing it would lead to print failures.

Hide the dropdown when slicedForModel is known — the tab label already
communicates the target. The dropdown still appears as a fallback for
legacy files without model metadata.
This commit is contained in:
maziggy
2026-02-26 09:13:06 +01:00
parent 20c291d678
commit f4f09ccc2d
5 changed files with 29 additions and 26 deletions
+1
View File
@@ -5,6 +5,7 @@ All notable changes to Bambuddy will be documented in this file.
## [0.2.1] - Unreleased
### Fixed
- **Unnecessary Target Model Selector on "Any" Tab** ([#528](https://github.com/maziggy/bambuddy/issues/528)) — When scheduling a print to "Any {model}", a redundant "Target Model" dropdown appeared even though the G-code is already sliced for a specific printer model. Changing the target model would lead to print failures. The dropdown is now hidden when the sliced model is known (the tab label already shows "Any {model}"). It still appears as a fallback for legacy files without model metadata.
- **"Clear Plate & Start Next" Button Shown on Printers Without Correct Filament** ([#527](https://github.com/maziggy/bambuddy/issues/527)) — When a print job was queued for "any printer" of a model (e.g., "any H2S"), the "Clear Plate & Start Next" button appeared on ALL printers of that model, including those without the required filament loaded. Clicking it on a printer without the right filament would start a print that fails. The `PrinterQueueWidget` now filters queue items by filament compatibility — it checks the printer's loaded filament types (from AMS and external spools) against the queue item's `required_filament_types` and only shows items the printer can actually print. If no compatible items exist, the widget is hidden.
- **Manual Spool Weight Overwritten by AMS Auto-Sync** ([#525](https://github.com/maziggy/bambuddy/issues/525)) — When a user manually entered a spool weight (via UI or API), the value was overwritten by the automatic AMS remain% sync that runs on every MQTT update. The AMS remain% is integer-only (~10g resolution for 1kg spool) and can't match precise manual entries. Added a `weight_locked` flag that is automatically set when `weight_used` is explicitly updated via the API. Locked spools are skipped by both the automatic AMS remain% sync and the manual force-sync endpoint. The usage tracker (3MF/gcode delta tracking) is unaffected. Users can re-enable AMS sync by setting `weight_locked: false`.
- **Inconsistent Print Cost on Reprints** ([#505](https://github.com/maziggy/bambuddy/issues/505)) — Reprinting the same model produced different costs each time (e.g., £0.77, £1.54, £2.03 for the same print). Three independent code paths wrote to `archive.cost` with conflicting strategies: the usage tracker summed ALL historical `SpoolUsageHistory` rows for the archive (including rows from previous reprints), and a separate `add_reprint_cost` method added yet another full print's cost on top. Removed the redundant `add_reprint_cost` path entirely and changed the usage tracker to compute cost only from the current print session's results instead of querying all historical rows. `archive.cost` now always reflects the cost of a single print.
+2 -2
View File
@@ -51,7 +51,7 @@ def event_loop():
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
# Drain pending callbacks so aiosqlite threads can finish before loop closes
loop.run_until_complete(asyncio.sleep(0.05))
loop.run_until_complete(asyncio.sleep(0.1))
loop.close()
@@ -94,7 +94,7 @@ async def test_engine():
# Allow aiosqlite's background thread to finish processing the close
# response before the per-function event loop shuts down, preventing
# "RuntimeError: Event loop is closed" in call_soon_threadsafe.
await asyncio.sleep(0.05)
await asyncio.sleep(0.1)
@pytest.fixture
@@ -389,28 +389,30 @@ export function PrinterSelector({
{/* Model selection and location filter (when in model mode) */}
{assignmentMode === 'model' && modelAssignmentAvailable && (
<div className="space-y-3 mb-4">
{/* Model selector */}
<div>
<label className="block text-xs text-bambu-gray mb-1">Target Model</label>
<select
value={targetModel || ''}
onChange={(e) => {
onTargetModelChange!(e.target.value || null);
// Clear location when model changes
if (onTargetLocationChange) {
onTargetLocationChange(null);
}
}}
className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none text-sm"
>
<option value="">Select a model...</option>
{uniqueModels.map((model) => (
<option key={model} value={model}>
{model}
</option>
))}
</select>
</div>
{/* Model selector — only show when sliced model is unknown */}
{!slicedForModel && (
<div>
<label className="block text-xs text-bambu-gray mb-1">Target Model</label>
<select
value={targetModel || ''}
onChange={(e) => {
onTargetModelChange!(e.target.value || null);
// Clear location when model changes
if (onTargetLocationChange) {
onTargetLocationChange(null);
}
}}
className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none text-sm"
>
<option value="">Select a model...</option>
{uniqueModels.map((model) => (
<option key={model} value={model}>
{model}
</option>
))}
</select>
</div>
)}
{/* Location filter (only show when target model is selected and locations exist) */}
{targetModel && uniqueLocations.length > 0 && onTargetLocationChange && (
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -23,7 +23,7 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-D6vc8KCS.js"></script>
<script type="module" crossorigin src="/assets/index-BXXkRrQK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-1Ts9jjQl.css">
</head>
<body>