Files
bambuddy/backend/tests/integration/test_design_settings_plates.py
maziggy 7e77bf5833 Take the layer height from the plate that actually printed
The archive card, the library file details and the slice dialog all read
the layer height from a 3MF's project_settings.config. That records the
project's settings and can still describe an earlier process or another
plate; the plate's own G-code - what the printer executes - was never
consulted for it, because the parser read the first 4KB, enough for the
layer count in the header block but not for the config block that carries
layer_height 14-25KB in. A print running at 0.08 on the H2C archived as
0.2 with the layer count from the same file correct beside it.

The plate G-code now wins wherever the two disagree, and the plate that
was printed is the one read - the header parse used to take the first
gcode entry in the zip regardless of which plate the archive was for.
Source 3MFs, which carry no G-code, keep the project value as before.

---

Stop carrying a file's layer height over the preset you picked

Bambuddy carries a designer's process deviations across a re-slice
(#2622) and pre-ticked every one that was not machine-coupled.
layer_height is one MakerWorld projects routinely carry, so picking
"0.08mm High Quality" for a file whose designer had moved layer height to
0.2 sliced at 0.2 while the dropdown still read 0.08 - the same 0.2 the
settings panel showed, tagged "from file".

Layer height and first layer height are now classified preset_defining
and treated like the machine-coupled keys: offered, never pre-selected.
The flag travels on DesignOverride so the modal and the backend agree,
and the panel's badge names the conflict and shows the preset's own value
next to the file's, so ticking one is a deliberate choice.
2026-08-18 16:23:32 +02:00

106 lines
4.0 KiB
Python

"""The plates endpoints must surface the designer's changed settings (#2622).
Parsing is covered in ``unit/test_design_settings.py``. What is asserted here is
the wiring: SliceModal reads ``design_overrides`` off the plates response, so a
correct parser that never reaches the payload is a feature that silently does
nothing.
"""
import json
import zipfile
from pathlib import Path
import pytest
from httpx import AsyncClient
def _designed_3mf(path: Path, *, with_deviations: bool = True) -> None:
"""A Bambu-style project 3MF, optionally carrying designer deviations."""
config = {
"print_settings_id": "0.20mm Standard @BBL A1",
"printer_settings_id": "Bambu Lab A1 0.4 nozzle",
"filament_settings_id": ["Bambu PLA Basic @BBL A1"],
"wall_loops": "5",
"outer_wall_speed": "200",
"machine_start_gcode": "G28 ; designer printer",
"different_settings_to_system": (
["wall_loops;outer_wall_speed", "", "machine_start_gcode"] if with_deviations else ["", "", ""]
),
}
with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as zf:
zf.writestr("Metadata/plate_1.gcode", "G0\n")
zf.writestr("Metadata/project_settings.config", json.dumps(config))
@pytest.fixture
def _patch_base_dir(monkeypatch, tmp_path):
from backend.app.core.config import settings
monkeypatch.setattr(settings, "base_dir", tmp_path)
return tmp_path
class TestArchivePlatesDesignOverrides:
@pytest.mark.asyncio
@pytest.mark.integration
async def test_returns_the_process_deviations_with_classification(
self, async_client: AsyncClient, archive_factory, printer_factory, _patch_base_dir
):
_designed_3mf(_patch_base_dir / "designed.3mf")
printer = await printer_factory()
archive = await archive_factory(printer.id, filename="designed.3mf", file_path="designed.3mf")
response = await async_client.get(f"/api/v1/archives/{archive.id}/plates")
assert response.status_code == 200
overrides = response.json()["design_overrides"]
assert [o["key"] for o in overrides] == ["outer_wall_speed", "wall_loops"]
by_key = {o["key"]: o for o in overrides}
assert by_key["wall_loops"] == {
"key": "wall_loops",
"value": "5",
"printer_coupled": False,
"preset_defining": False,
}
assert by_key["outer_wall_speed"]["printer_coupled"] is True
# The printer slot must never leak into the process list.
assert "machine_start_gcode" not in by_key
@pytest.mark.asyncio
@pytest.mark.integration
async def test_empty_for_a_file_that_changes_nothing(
self, async_client: AsyncClient, archive_factory, printer_factory, _patch_base_dir
):
_designed_3mf(_patch_base_dir / "stock.3mf", with_deviations=False)
printer = await printer_factory()
archive = await archive_factory(printer.id, filename="stock.3mf", file_path="stock.3mf")
response = await async_client.get(f"/api/v1/archives/{archive.id}/plates")
assert response.status_code == 200
assert response.json()["design_overrides"] == []
class TestLibraryPlatesDesignOverrides:
@pytest.mark.asyncio
@pytest.mark.integration
async def test_returns_the_process_deviations(self, async_client: AsyncClient, db_session, tmp_path):
from backend.app.models.library import LibraryFile
path = tmp_path / "designed.3mf"
_designed_3mf(path)
lib_file = LibraryFile(
filename="designed.3mf",
file_path=str(path),
file_type="3mf",
file_size=path.stat().st_size,
)
db_session.add(lib_file)
await db_session.commit()
await db_session.refresh(lib_file)
response = await async_client.get(f"/api/v1/library/files/{lib_file.id}/plates")
assert response.status_code == 200
assert [o["key"] for o in response.json()["design_overrides"]] == ["outer_wall_speed", "wall_loops"]