test_archive_bed_temperature_2989.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. """BambuStudio does not write a ``bed_temperature`` key, so we never read one.
  2. The 3MF stores one bed-temperature array per plate type -- ``cool_plate_temp``,
  3. ``eng_plate_temp``, ``hot_plate_temp``, ``textured_plate_temp``,
  4. ``supertack_plate_temp`` -- and names the plate the project is sliced for in
  5. ``curr_bed_type``. ``bed_temperature`` is the Orca/PrusaSlicer spelling. The
  6. extractor looked only for that spelling, so ``PrintArchive.bed_temperature`` was
  7. NULL for every archive produced from a Bambu slice: measured at 0 of 455 real
  8. 3MFs on a live install, against 455 of 455 once the plate keys are read.
  9. That NULL is not cosmetic. Preheat reads ``archive.bed_temperature`` to decide
  10. what to heat the bed to, and with nothing there it falls back to the configured
  11. chamber-heating bed temperature (90 by default) on jobs that never wanted it --
  12. which is how #2989's PLA print came to sit behind a 90°C bed.
  13. Key names and the plate mapping are BambuStudio's own ``get_bed_temp_key`` /
  14. ``get_bed_temp_1st_layer_key`` (PrintConfig.hpp); the plate names are the
  15. ``curr_bed_type`` enum values (PrintConfig.cpp).
  16. """
  17. import pytest
  18. from backend.app.services.archive import ThreeMFParser
  19. def _extract(config: dict) -> int | None:
  20. parser = ThreeMFParser.__new__(ThreeMFParser)
  21. parser.metadata = {}
  22. parser._extract_print_settings(config)
  23. return parser.metadata.get("bed_temperature")
  24. class TestThePlateTheProjectIsSlicedFor:
  25. @pytest.mark.parametrize(
  26. "bed_type,key",
  27. [
  28. ("Cool Plate", "cool_plate_temp"),
  29. ("Engineering Plate", "eng_plate_temp"),
  30. ("High Temp Plate", "hot_plate_temp"),
  31. ("Textured PEI Plate", "textured_plate_temp"),
  32. ("Supertack Plate", "supertack_plate_temp"),
  33. ("Textured Cool Plate", "textured_cool_plate_temp"),
  34. ],
  35. )
  36. def test_every_plate_type_reads_its_own_array(self, bed_type, key):
  37. """All five plates BambuStudio can name, and OrcaSlicer's Textured Cool
  38. Plate, each with its own key."""
  39. assert _extract({"curr_bed_type": bed_type, key: ["65", "65"]}) == 65
  40. def test_the_fitted_plate_wins_over_the_others(self):
  41. """The real failure mode this replaces: reading whichever key happened
  42. to be present. A Textured slice must not take the cool plate's 0."""
  43. config = {
  44. "curr_bed_type": "Textured PEI Plate",
  45. "cool_plate_temp": ["0", "0", "0"],
  46. "eng_plate_temp": ["90", "90", "90"],
  47. "hot_plate_temp": ["90", "90", "90"],
  48. "textured_plate_temp": ["55", "55", "55"],
  49. "supertack_plate_temp": ["0", "0", "0"],
  50. }
  51. assert _extract(config) == 55
  52. def test_the_first_layer_value_is_preferred(self):
  53. """It is what the printer heats to before the print starts, which is
  54. the number preheat is trying to reach."""
  55. config = {
  56. "curr_bed_type": "Textured PEI Plate",
  57. "textured_plate_temp_initial_layer": ["65"],
  58. "textured_plate_temp": ["60"],
  59. }
  60. assert _extract(config) == 65
  61. def test_the_regular_value_when_there_is_no_first_layer_one(self):
  62. config = {"curr_bed_type": "Textured PEI Plate", "textured_plate_temp": ["60"]}
  63. assert _extract(config) == 60
  64. class TestThePerFilamentArray:
  65. def test_the_highest_entry_wins(self):
  66. """One bed, several filaments: the print runs at the highest its
  67. filaments ask for."""
  68. config = {"curr_bed_type": "High Temp Plate", "hot_plate_temp": ["55", "100", "90"]}
  69. assert _extract(config) == 100
  70. def test_a_leading_zero_does_not_win(self):
  71. """0 means that filament cannot print on this plate. Taking entry 0 --
  72. what the neighbouring scalar settings do -- would store a cold bed for
  73. any project whose first filament is not one this plate is heated for."""
  74. config = {"curr_bed_type": "High Temp Plate", "hot_plate_temp": ["0", "90"]}
  75. assert _extract(config) == 90
  76. def test_a_scalar_rather_than_an_array(self):
  77. assert _extract({"curr_bed_type": "Cool Plate", "cool_plate_temp": 35}) == 35
  78. def test_numeric_strings_and_floats(self):
  79. assert _extract({"curr_bed_type": "Cool Plate", "cool_plate_temp": ["35.0"]}) == 35
  80. class TestWhatItRefusesToInvent:
  81. def test_an_all_zero_plate_array_is_not_a_bed_temperature(self):
  82. """No filament in the project prints on this plate. Recording the 0
  83. would read downstream as a deliberate cold bed."""
  84. config = {"curr_bed_type": "Cool Plate", "cool_plate_temp": ["0", "0"]}
  85. assert _extract(config) is None
  86. def test_a_plate_bambustudio_maps_to_no_key(self):
  87. """``Default Plate`` has no temperature array of its own, and guessing
  88. another plate's would invent a temperature the slice never specified."""
  89. config = {"curr_bed_type": "Default Plate", "hot_plate_temp": ["90"]}
  90. assert _extract(config) is None
  91. def test_a_plate_name_we_do_not_know(self):
  92. """A plate a future BambuStudio adds must not silently read another
  93. plate's array."""
  94. config = {"curr_bed_type": "Cryo Plate", "textured_plate_temp": ["55"]}
  95. assert _extract(config) is None
  96. def test_a_config_with_no_bed_temperature_at_all(self):
  97. assert _extract({"curr_bed_type": "Textured PEI Plate", "layer_height": ["0.2"]}) is None
  98. def test_junk_entries_are_stepped_over(self):
  99. config = {"curr_bed_type": "Cool Plate", "cool_plate_temp": [None, {}, "abc", "35"]}
  100. assert _extract(config) == 35
  101. class TestOrcaExportsStillWork:
  102. """The generic spelling is the fallback, not the primary -- Orca-exported
  103. 3MFs and anything else Prusa-shaped keep parsing exactly as before."""
  104. def test_the_generic_first_layer_key(self):
  105. assert _extract({"bed_temperature_initial_layer": ["60"]}) == 60
  106. def test_the_generic_key(self):
  107. assert _extract({"bed_temperature": 60}) == 60
  108. def test_the_plate_array_is_preferred_when_both_are_present(self):
  109. config = {
  110. "curr_bed_type": "Textured PEI Plate",
  111. "textured_plate_temp": ["55"],
  112. "bed_temperature": ["90"],
  113. }
  114. assert _extract(config) == 55
  115. class TestTheNeighbouringSettingsAreUntouched:
  116. """The same method extracts three other values; none of them changed."""
  117. def test_nozzle_temperature_layer_height_and_diameter(self):
  118. parser = ThreeMFParser.__new__(ThreeMFParser)
  119. parser.metadata = {}
  120. parser._extract_print_settings(
  121. {
  122. "curr_bed_type": "Textured PEI Plate",
  123. "textured_plate_temp": ["55"],
  124. "nozzle_temperature_initial_layer": ["220", "220"],
  125. "layer_height": ["0.2"],
  126. "nozzle_diameter": ["0.4"],
  127. }
  128. )
  129. assert parser.metadata == {
  130. "bed_type": "Textured PEI Plate",
  131. "layer_height": 0.2,
  132. "nozzle_diameter": 0.4,
  133. "bed_temperature": 55,
  134. "nozzle_temperature": 220,
  135. }