mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-20 09:03:43 +02:00
Fix AMS auto-matching to use tray_info_idx from 3MF files
When multiple AMS trays have the same filament type and color, Bambuddy now uses the tray_info_idx attribute from the 3MF file to identify the exact spool selected during slicing. This ensures the correct tray is used rather than just picking the first match. Matching priority: tray_info_idx > exact color > similar color > type-only Closes #245
This commit is contained in:
@@ -265,3 +265,166 @@ class TestMatchFilamentsToSlots:
|
||||
|
||||
result = scheduler._match_filaments_to_slots(required, loaded)
|
||||
assert result == [254]
|
||||
|
||||
def test_match_by_tray_info_idx_priority(self, scheduler):
|
||||
"""tray_info_idx match should have highest priority over color match."""
|
||||
required = [{"slot_id": 1, "type": "PLA", "color": "#000000", "tray_info_idx": "GFA00"}]
|
||||
loaded = [
|
||||
{
|
||||
"type": "PLA",
|
||||
"color": "#000000",
|
||||
"global_tray_id": 0,
|
||||
"tray_info_idx": "GFB00",
|
||||
}, # Same color, different spool
|
||||
{
|
||||
"type": "PLA",
|
||||
"color": "#000000",
|
||||
"global_tray_id": 1,
|
||||
"tray_info_idx": "GFA00",
|
||||
}, # Same color, exact spool
|
||||
{
|
||||
"type": "PLA",
|
||||
"color": "#000000",
|
||||
"global_tray_id": 2,
|
||||
"tray_info_idx": "GFC00",
|
||||
}, # Same color, different spool
|
||||
]
|
||||
|
||||
result = scheduler._match_filaments_to_slots(required, loaded)
|
||||
assert result == [1] # Should pick tray 1 (exact tray_info_idx match)
|
||||
|
||||
def test_match_by_tray_info_idx_with_different_colors(self, scheduler):
|
||||
"""tray_info_idx match should work even if colors differ slightly."""
|
||||
required = [{"slot_id": 1, "type": "PLA", "color": "#000000", "tray_info_idx": "P4d64437"}]
|
||||
loaded = [
|
||||
{"type": "PLA", "color": "#000000", "global_tray_id": 0, "tray_info_idx": ""}, # No idx
|
||||
{
|
||||
"type": "PLA",
|
||||
"color": "#000010",
|
||||
"global_tray_id": 3,
|
||||
"tray_info_idx": "P4d64437",
|
||||
}, # Exact spool (slightly different color reported)
|
||||
]
|
||||
|
||||
result = scheduler._match_filaments_to_slots(required, loaded)
|
||||
assert result == [3] # Should pick tray 3 (exact tray_info_idx match)
|
||||
|
||||
def test_match_fallback_to_color_when_no_tray_info_idx(self, scheduler):
|
||||
"""Should fall back to color matching when tray_info_idx is empty."""
|
||||
required = [{"slot_id": 1, "type": "PLA", "color": "#FF0000", "tray_info_idx": ""}]
|
||||
loaded = [
|
||||
{"type": "PLA", "color": "#00FF00", "global_tray_id": 0, "tray_info_idx": "GFA00"},
|
||||
{"type": "PLA", "color": "#FF0000", "global_tray_id": 1, "tray_info_idx": "GFB00"}, # Color match
|
||||
]
|
||||
|
||||
result = scheduler._match_filaments_to_slots(required, loaded)
|
||||
assert result == [1] # Should pick tray 1 (color match)
|
||||
|
||||
def test_match_fallback_to_color_when_no_matching_tray_info_idx(self, scheduler):
|
||||
"""Should fall back to color when tray_info_idx doesn't match any loaded spool."""
|
||||
required = [{"slot_id": 1, "type": "PLA", "color": "#FF0000", "tray_info_idx": "OLD_SPOOL"}]
|
||||
loaded = [
|
||||
{
|
||||
"type": "PLA",
|
||||
"color": "#FF0000",
|
||||
"global_tray_id": 0,
|
||||
"tray_info_idx": "NEW_SPOOL",
|
||||
}, # Different idx but same color
|
||||
]
|
||||
|
||||
result = scheduler._match_filaments_to_slots(required, loaded)
|
||||
assert result == [0] # Should fall back to color match
|
||||
|
||||
def test_match_multiple_same_color_with_tray_info_idx(self, scheduler):
|
||||
"""Multiple identical filaments should be matched by tray_info_idx (H2D Pro scenario)."""
|
||||
# This is the exact scenario from issue #245 - 3 black PLA spools
|
||||
required = [
|
||||
{"slot_id": 1, "type": "PLA", "color": "#000000", "tray_info_idx": "GFA03"}, # Wants tray 3
|
||||
]
|
||||
loaded = [
|
||||
{"type": "PLA", "color": "#000000", "global_tray_id": 0, "tray_info_idx": "GFA00"}, # Tray 0
|
||||
{"type": "PLA", "color": "#000000", "global_tray_id": 1, "tray_info_idx": "GFA01"}, # Tray 1
|
||||
{"type": "PLA", "color": "#000000", "global_tray_id": 2, "tray_info_idx": "GFA02"}, # Tray 2
|
||||
{
|
||||
"type": "PLA",
|
||||
"color": "#000000",
|
||||
"global_tray_id": 3,
|
||||
"tray_info_idx": "GFA03",
|
||||
}, # Tray 3 - the one we want
|
||||
]
|
||||
|
||||
result = scheduler._match_filaments_to_slots(required, loaded)
|
||||
assert result == [3] # Should pick tray 3, not tray 0
|
||||
|
||||
def test_match_tray_info_idx_not_reused(self, scheduler):
|
||||
"""tray_info_idx matched trays should not be reused for other slots."""
|
||||
required = [
|
||||
{"slot_id": 1, "type": "PLA", "color": "#000000", "tray_info_idx": "GFA00"},
|
||||
{"slot_id": 2, "type": "PLA", "color": "#000000", "tray_info_idx": "GFA01"},
|
||||
]
|
||||
loaded = [
|
||||
{"type": "PLA", "color": "#000000", "global_tray_id": 0, "tray_info_idx": "GFA00"},
|
||||
{"type": "PLA", "color": "#000000", "global_tray_id": 1, "tray_info_idx": "GFA01"},
|
||||
]
|
||||
|
||||
result = scheduler._match_filaments_to_slots(required, loaded)
|
||||
assert result == [0, 1] # Each slot gets its specific tray
|
||||
|
||||
|
||||
class TestBuildLoadedFilamentsTrayInfoIdx:
|
||||
"""Test tray_info_idx extraction in _build_loaded_filaments."""
|
||||
|
||||
@pytest.fixture
|
||||
def scheduler(self):
|
||||
return PrintScheduler()
|
||||
|
||||
def test_build_loaded_filaments_includes_tray_info_idx(self, scheduler):
|
||||
"""Should extract tray_info_idx from AMS trays."""
|
||||
|
||||
class MockStatus:
|
||||
raw_data = {
|
||||
"ams": [
|
||||
{
|
||||
"id": 0,
|
||||
"tray": [
|
||||
{"id": 0, "tray_type": "PLA", "tray_color": "000000", "tray_info_idx": "GFA00"},
|
||||
{"id": 1, "tray_type": "PLA", "tray_color": "000000", "tray_info_idx": "GFA01"},
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
result = scheduler._build_loaded_filaments(MockStatus())
|
||||
assert len(result) == 2
|
||||
assert result[0]["tray_info_idx"] == "GFA00"
|
||||
assert result[1]["tray_info_idx"] == "GFA01"
|
||||
|
||||
def test_build_loaded_filaments_empty_tray_info_idx(self, scheduler):
|
||||
"""Missing tray_info_idx should default to empty string."""
|
||||
|
||||
class MockStatus:
|
||||
raw_data = {
|
||||
"ams": [
|
||||
{
|
||||
"id": 0,
|
||||
"tray": [
|
||||
{"id": 0, "tray_type": "PLA", "tray_color": "FF0000"}, # No tray_info_idx
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
result = scheduler._build_loaded_filaments(MockStatus())
|
||||
assert len(result) == 1
|
||||
assert result[0]["tray_info_idx"] == ""
|
||||
|
||||
def test_build_loaded_filaments_external_spool_tray_info_idx(self, scheduler):
|
||||
"""Should extract tray_info_idx from external spool."""
|
||||
|
||||
class MockStatus:
|
||||
raw_data = {"vt_tray": {"tray_type": "TPU", "tray_color": "0000FF", "tray_info_idx": "P4d64437"}}
|
||||
|
||||
result = scheduler._build_loaded_filaments(MockStatus())
|
||||
assert len(result) == 1
|
||||
assert result[0]["tray_info_idx"] == "P4d64437"
|
||||
assert result[0]["is_external"] is True
|
||||
|
||||
Reference in New Issue
Block a user