mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
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.
106 lines
4.0 KiB
Python
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"]
|