|
|
@@ -10,6 +10,7 @@ import math
|
|
|
import zipfile
|
|
|
|
|
|
from backend.app.utils.threemf_tools import (
|
|
|
+ extract_bed_type_from_3mf,
|
|
|
extract_embedded_presets_from_3mf,
|
|
|
extract_filament_usage_from_3mf,
|
|
|
extract_plate_extruder_set_from_3mf,
|
|
|
@@ -700,3 +701,143 @@ class TestExtractEmbeddedPresetsFrom3mf:
|
|
|
"printer": None,
|
|
|
"process": None,
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+class TestExtractBedTypeFrom3mf:
|
|
|
+ """extract_bed_type_from_3mf reads per-plate `curr_bed_type` from
|
|
|
+ slice_info.config so the queue / print modal can show the right plate
|
|
|
+ even on multi-plate 3MFs where different plates target different beds
|
|
|
+ (#1281). archive.bed_type is one-value-per-archive (first plate's
|
|
|
+ curr_bed_type — see services/archive.py:235), so for accurate
|
|
|
+ per-plate surfacing we have to re-read the 3MF."""
|
|
|
+
|
|
|
+ def test_single_plate_returns_bed_type(self, tmp_path):
|
|
|
+ xml_content = """<?xml version="1.0" encoding="UTF-8"?>
|
|
|
+ <config>
|
|
|
+ <plate>
|
|
|
+ <metadata key="index" value="1"/>
|
|
|
+ <metadata key="curr_bed_type" value="Textured PEI Plate"/>
|
|
|
+ </plate>
|
|
|
+ </config>
|
|
|
+ """
|
|
|
+ file_path = tmp_path / "test.3mf"
|
|
|
+ file_path.write_bytes(create_mock_3mf(xml_content).read())
|
|
|
+
|
|
|
+ assert extract_bed_type_from_3mf(file_path) == "Textured PEI Plate"
|
|
|
+
|
|
|
+ def test_multi_plate_returns_per_plate_value(self, tmp_path):
|
|
|
+ # Reporter's case: a 3MF mixing PEI + Engineering across plates.
|
|
|
+ # Looking up by plate_id must return THAT plate's value, not the
|
|
|
+ # first plate's value the archive happens to cache.
|
|
|
+ xml_content = """<?xml version="1.0" encoding="UTF-8"?>
|
|
|
+ <config>
|
|
|
+ <plate>
|
|
|
+ <metadata key="index" value="1"/>
|
|
|
+ <metadata key="curr_bed_type" value="Textured PEI Plate"/>
|
|
|
+ </plate>
|
|
|
+ <plate>
|
|
|
+ <metadata key="index" value="2"/>
|
|
|
+ <metadata key="curr_bed_type" value="Engineering Plate"/>
|
|
|
+ </plate>
|
|
|
+ <plate>
|
|
|
+ <metadata key="index" value="3"/>
|
|
|
+ <metadata key="curr_bed_type" value="Cool Plate"/>
|
|
|
+ </plate>
|
|
|
+ </config>
|
|
|
+ """
|
|
|
+ file_path = tmp_path / "test.3mf"
|
|
|
+ file_path.write_bytes(create_mock_3mf(xml_content).read())
|
|
|
+
|
|
|
+ assert extract_bed_type_from_3mf(file_path, plate_id=1) == "Textured PEI Plate"
|
|
|
+ assert extract_bed_type_from_3mf(file_path, plate_id=2) == "Engineering Plate"
|
|
|
+ assert extract_bed_type_from_3mf(file_path, plate_id=3) == "Cool Plate"
|
|
|
+
|
|
|
+ def test_no_plate_id_returns_first_plate(self, tmp_path):
|
|
|
+ # The plate_id=None branch must match the archive-level capture
|
|
|
+ # convention (first plate wins) so callers that don't care about
|
|
|
+ # plate selection see the same value the archive table holds.
|
|
|
+ xml_content = """<?xml version="1.0" encoding="UTF-8"?>
|
|
|
+ <config>
|
|
|
+ <plate>
|
|
|
+ <metadata key="index" value="1"/>
|
|
|
+ <metadata key="curr_bed_type" value="Cool Plate SuperTack"/>
|
|
|
+ </plate>
|
|
|
+ <plate>
|
|
|
+ <metadata key="index" value="2"/>
|
|
|
+ <metadata key="curr_bed_type" value="Engineering Plate"/>
|
|
|
+ </plate>
|
|
|
+ </config>
|
|
|
+ """
|
|
|
+ file_path = tmp_path / "test.3mf"
|
|
|
+ file_path.write_bytes(create_mock_3mf(xml_content).read())
|
|
|
+
|
|
|
+ assert extract_bed_type_from_3mf(file_path) == "Cool Plate SuperTack"
|
|
|
+
|
|
|
+ def test_unknown_plate_id_returns_none(self, tmp_path):
|
|
|
+ xml_content = """<?xml version="1.0" encoding="UTF-8"?>
|
|
|
+ <config>
|
|
|
+ <plate>
|
|
|
+ <metadata key="index" value="1"/>
|
|
|
+ <metadata key="curr_bed_type" value="Textured PEI Plate"/>
|
|
|
+ </plate>
|
|
|
+ </config>
|
|
|
+ """
|
|
|
+ file_path = tmp_path / "test.3mf"
|
|
|
+ file_path.write_bytes(create_mock_3mf(xml_content).read())
|
|
|
+
|
|
|
+ assert extract_bed_type_from_3mf(file_path, plate_id=99) is None
|
|
|
+
|
|
|
+ def test_plate_without_bed_type_returns_none(self, tmp_path):
|
|
|
+ # Older slicers may export a plate without curr_bed_type. The
|
|
|
+ # helper must return None rather than falling through to another
|
|
|
+ # plate's value (which would silently lie).
|
|
|
+ xml_content = """<?xml version="1.0" encoding="UTF-8"?>
|
|
|
+ <config>
|
|
|
+ <plate>
|
|
|
+ <metadata key="index" value="1"/>
|
|
|
+ </plate>
|
|
|
+ <plate>
|
|
|
+ <metadata key="index" value="2"/>
|
|
|
+ <metadata key="curr_bed_type" value="Engineering Plate"/>
|
|
|
+ </plate>
|
|
|
+ </config>
|
|
|
+ """
|
|
|
+ file_path = tmp_path / "test.3mf"
|
|
|
+ file_path.write_bytes(create_mock_3mf(xml_content).read())
|
|
|
+
|
|
|
+ assert extract_bed_type_from_3mf(file_path, plate_id=1) is None
|
|
|
+ assert extract_bed_type_from_3mf(file_path, plate_id=2) == "Engineering Plate"
|
|
|
+
|
|
|
+ def test_missing_slice_info_returns_none(self, tmp_path):
|
|
|
+ buffer = io.BytesIO()
|
|
|
+ with zipfile.ZipFile(buffer, "w") as zf:
|
|
|
+ zf.writestr("other_file.txt", "content")
|
|
|
+ buffer.seek(0)
|
|
|
+
|
|
|
+ file_path = tmp_path / "test.3mf"
|
|
|
+ file_path.write_bytes(buffer.read())
|
|
|
+
|
|
|
+ assert extract_bed_type_from_3mf(file_path) is None
|
|
|
+
|
|
|
+ def test_invalid_file_returns_none(self, tmp_path):
|
|
|
+ file_path = tmp_path / "invalid.3mf"
|
|
|
+ file_path.write_text("not a zip file")
|
|
|
+
|
|
|
+ assert extract_bed_type_from_3mf(file_path) is None
|
|
|
+
|
|
|
+ def test_whitespace_trimmed(self, tmp_path):
|
|
|
+ # 3MF values sometimes carry surrounding whitespace from manual
|
|
|
+ # template tweaks; getBedTypeInfo() on the frontend is also
|
|
|
+ # whitespace-tolerant, but the wire shape should be clean.
|
|
|
+ xml_content = """<?xml version="1.0" encoding="UTF-8"?>
|
|
|
+ <config>
|
|
|
+ <plate>
|
|
|
+ <metadata key="index" value="1"/>
|
|
|
+ <metadata key="curr_bed_type" value=" Textured PEI Plate "/>
|
|
|
+ </plate>
|
|
|
+ </config>
|
|
|
+ """
|
|
|
+ file_path = tmp_path / "test.3mf"
|
|
|
+ file_path.write_bytes(create_mock_3mf(xml_content).read())
|
|
|
+
|
|
|
+ assert extract_bed_type_from_3mf(file_path) == "Textured PEI Plate"
|