mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-21 18:47:13 +02:00
feat(print-options): add "Auto" state to bed levelling, flow & nozzle-offset calibration
Bed levelling, flow calibration, and nozzle-offset calibration were on/off only, so the sole way to run bed levelling was to force a full level before every print. Bambu Studio has always offered a third "Auto" state that lets the printer skip the calibration when it was done recently -- the state most users actually want. Make these three options tri-state (off/on/auto), defaulting to auto, and leave vibration/layer-inspect/timelapse as on/off (Bambu Studio exposes no auto for those). Wire encoding follows Bambu Studio's source exactly: each option sends a JSON bool (true only for "on") plus a companion int -- off=0, on=1, auto=2. The bool fields stay booleans (the #1478 H2S regression); only the companion int widened from {0,1} to {0,1,2}. #1721's observation that stage 8/39 stays queued when sending 2 is the auto contract (queued, skipped at runtime if recent), not a broken "off". - schemas: TriState = Literal[off/on/auto] with a BeforeValidator coercing legacy bool / 0-1 / true-false so old clients and un-migrated rows validate - model + migration: boolean columns -> String; SQLite via column affinity + data backfill, PostgreSQL via ALTER COLUMN TYPE guarded on information_schema (verified on both dialects); settings rows normalised true/false -> on/off - MQTT: start_print takes the tri-state strings and emits the paired bool+int - Virtual Printer: reconstructs the slicer's auto/on/off from the int companion (auto_bed_leveling / extrude_cali_flag) in both capture paths - frontend: CalibrationMode type; off/auto/on segmented controls in the print dialog, queue bulk-edit, and Settings -> Workflow; calibrationMode_* strings in all 11 locales
This commit is contained in:
@@ -289,8 +289,8 @@ class TestPrintQueueAPI:
|
||||
data = {
|
||||
"printer_id": printer.id,
|
||||
"archive_id": archive.id,
|
||||
"bed_levelling": False,
|
||||
"flow_cali": True,
|
||||
"bed_levelling": "off",
|
||||
"flow_cali": "on",
|
||||
"vibration_cali": False,
|
||||
"layer_inspect": True,
|
||||
"timelapse": True,
|
||||
@@ -299,8 +299,8 @@ class TestPrintQueueAPI:
|
||||
response = await async_client.post("/api/v1/queue/", json=data)
|
||||
assert response.status_code == 200
|
||||
result = response.json()
|
||||
assert result["bed_levelling"] is False
|
||||
assert result["flow_cali"] is True
|
||||
assert result["bed_levelling"] == "off"
|
||||
assert result["flow_cali"] == "on"
|
||||
assert result["vibration_cali"] is False
|
||||
assert result["layer_inspect"] is True
|
||||
assert result["timelapse"] is True
|
||||
@@ -324,13 +324,13 @@ class TestPrintQueueAPI:
|
||||
response = await async_client.patch(
|
||||
f"/api/v1/queue/{item.id}",
|
||||
json={
|
||||
"bed_levelling": False,
|
||||
"bed_levelling": "off",
|
||||
"timelapse": True,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
result = response.json()
|
||||
assert result["bed_levelling"] is False
|
||||
assert result["bed_levelling"] == "off"
|
||||
assert result["timelapse"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -929,7 +929,7 @@ class TestQueueLibraryFileSupport:
|
||||
"library_file_id": lib_file.id,
|
||||
"ams_mapping": [1, 2, -1, -1],
|
||||
"plate_id": 2,
|
||||
"bed_levelling": False,
|
||||
"bed_levelling": "off",
|
||||
"timelapse": True,
|
||||
"manual_start": True,
|
||||
}
|
||||
@@ -939,7 +939,7 @@ class TestQueueLibraryFileSupport:
|
||||
assert result["library_file_id"] == lib_file.id
|
||||
assert result["ams_mapping"] == [1, 2, -1, -1]
|
||||
assert result["plate_id"] == 2
|
||||
assert result["bed_levelling"] is False
|
||||
assert result["bed_levelling"] == "off"
|
||||
assert result["timelapse"] is True
|
||||
assert result["manual_start"] is True
|
||||
|
||||
@@ -1105,8 +1105,8 @@ class TestBulkUpdateEndpoint:
|
||||
defaults = {
|
||||
"status": "pending",
|
||||
"position": 1,
|
||||
"bed_levelling": True,
|
||||
"flow_cali": False,
|
||||
"bed_levelling": "on",
|
||||
"flow_cali": "off",
|
||||
"vibration_cali": True,
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
@@ -1123,12 +1123,12 @@ class TestBulkUpdateEndpoint:
|
||||
@pytest.mark.integration
|
||||
async def test_bulk_update_single_field(self, async_client: AsyncClient, queue_item_factory, db_session):
|
||||
"""Verify bulk update can change a single field on multiple items."""
|
||||
item1 = await queue_item_factory(bed_levelling=True)
|
||||
item2 = await queue_item_factory(bed_levelling=True)
|
||||
item1 = await queue_item_factory(bed_levelling="on")
|
||||
item2 = await queue_item_factory(bed_levelling="on")
|
||||
|
||||
response = await async_client.patch(
|
||||
"/api/v1/queue/bulk",
|
||||
json={"item_ids": [item1.id, item2.id], "bed_levelling": False},
|
||||
json={"item_ids": [item1.id, item2.id], "bed_levelling": "off"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
result = response.json()
|
||||
@@ -1138,22 +1138,22 @@ class TestBulkUpdateEndpoint:
|
||||
# Verify items were updated
|
||||
await db_session.refresh(item1)
|
||||
await db_session.refresh(item2)
|
||||
assert item1.bed_levelling is False
|
||||
assert item2.bed_levelling is False
|
||||
assert item1.bed_levelling == "off"
|
||||
assert item2.bed_levelling == "off"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_bulk_update_multiple_fields(self, async_client: AsyncClient, queue_item_factory, db_session):
|
||||
"""Verify bulk update can change multiple fields at once."""
|
||||
item1 = await queue_item_factory(bed_levelling=True, flow_cali=False, manual_start=False)
|
||||
item2 = await queue_item_factory(bed_levelling=True, flow_cali=False, manual_start=False)
|
||||
item1 = await queue_item_factory(bed_levelling="on", flow_cali="off", manual_start=False)
|
||||
item2 = await queue_item_factory(bed_levelling="on", flow_cali="off", manual_start=False)
|
||||
|
||||
response = await async_client.patch(
|
||||
"/api/v1/queue/bulk",
|
||||
json={
|
||||
"item_ids": [item1.id, item2.id],
|
||||
"bed_levelling": False,
|
||||
"flow_cali": True,
|
||||
"bed_levelling": "off",
|
||||
"flow_cali": "on",
|
||||
"manual_start": True,
|
||||
},
|
||||
)
|
||||
@@ -1162,23 +1162,23 @@ class TestBulkUpdateEndpoint:
|
||||
assert result["updated_count"] == 2
|
||||
|
||||
await db_session.refresh(item1)
|
||||
assert item1.bed_levelling is False
|
||||
assert item1.flow_cali is True
|
||||
assert item1.bed_levelling == "off"
|
||||
assert item1.flow_cali == "on"
|
||||
assert item1.manual_start is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_bulk_update_skips_non_pending(self, async_client: AsyncClient, queue_item_factory, db_session):
|
||||
"""Verify bulk update skips non-pending items."""
|
||||
pending_item = await queue_item_factory(status="pending", bed_levelling=True)
|
||||
printing_item = await queue_item_factory(status="printing", bed_levelling=True)
|
||||
completed_item = await queue_item_factory(status="completed", bed_levelling=True)
|
||||
pending_item = await queue_item_factory(status="pending", bed_levelling="on")
|
||||
printing_item = await queue_item_factory(status="printing", bed_levelling="on")
|
||||
completed_item = await queue_item_factory(status="completed", bed_levelling="on")
|
||||
|
||||
response = await async_client.patch(
|
||||
"/api/v1/queue/bulk",
|
||||
json={
|
||||
"item_ids": [pending_item.id, printing_item.id, completed_item.id],
|
||||
"bed_levelling": False,
|
||||
"bed_levelling": "off",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
@@ -1190,9 +1190,9 @@ class TestBulkUpdateEndpoint:
|
||||
await db_session.refresh(pending_item)
|
||||
await db_session.refresh(printing_item)
|
||||
await db_session.refresh(completed_item)
|
||||
assert pending_item.bed_levelling is False
|
||||
assert printing_item.bed_levelling is True
|
||||
assert completed_item.bed_levelling is True
|
||||
assert pending_item.bed_levelling == "off"
|
||||
assert printing_item.bed_levelling == "on"
|
||||
assert completed_item.bed_levelling == "on"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
@@ -2250,7 +2250,7 @@ class TestAbortedStatusNormalisation:
|
||||
"printer_id": printer.id,
|
||||
"archive_id": archive.id,
|
||||
"quantity": 2,
|
||||
"bed_levelling": False,
|
||||
"bed_levelling": "off",
|
||||
"timelapse": True,
|
||||
}
|
||||
response = await async_client.post("/api/v1/queue/", json=data)
|
||||
@@ -2261,7 +2261,7 @@ class TestAbortedStatusNormalisation:
|
||||
batch_items = [i for i in list_response.json() if i["batch_id"] == batch_id]
|
||||
assert len(batch_items) == 2
|
||||
for item in batch_items:
|
||||
assert item["bed_levelling"] is False
|
||||
assert item["bed_levelling"] == "off"
|
||||
assert item["timelapse"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user