diff --git a/resources/images/plate_imex_copy_mode.svg b/resources/images/plate_imex_copy_mode.svg
new file mode 100644
index 0000000000..b938e909a1
--- /dev/null
+++ b/resources/images/plate_imex_copy_mode.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_copy_mode_dark.svg b/resources/images/plate_imex_copy_mode_dark.svg
new file mode 100644
index 0000000000..c87b0e0ffe
--- /dev/null
+++ b/resources/images/plate_imex_copy_mode_dark.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_copy_mode_hover.svg b/resources/images/plate_imex_copy_mode_hover.svg
new file mode 100644
index 0000000000..4238552426
--- /dev/null
+++ b/resources/images/plate_imex_copy_mode_hover.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_copy_mode_hover_dark.svg b/resources/images/plate_imex_copy_mode_hover_dark.svg
new file mode 100644
index 0000000000..4633b747fe
--- /dev/null
+++ b/resources/images/plate_imex_copy_mode_hover_dark.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_custom_mode.svg b/resources/images/plate_imex_custom_mode.svg
new file mode 100644
index 0000000000..99bc534382
--- /dev/null
+++ b/resources/images/plate_imex_custom_mode.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_custom_mode_dark.svg b/resources/images/plate_imex_custom_mode_dark.svg
new file mode 100644
index 0000000000..d31da3bb3a
--- /dev/null
+++ b/resources/images/plate_imex_custom_mode_dark.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_custom_mode_hover.svg b/resources/images/plate_imex_custom_mode_hover.svg
new file mode 100644
index 0000000000..c0cc65a2a4
--- /dev/null
+++ b/resources/images/plate_imex_custom_mode_hover.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_custom_mode_hover_dark.svg b/resources/images/plate_imex_custom_mode_hover_dark.svg
new file mode 100644
index 0000000000..4b5682dbb2
--- /dev/null
+++ b/resources/images/plate_imex_custom_mode_hover_dark.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_mirror_mode.svg b/resources/images/plate_imex_mirror_mode.svg
new file mode 100644
index 0000000000..92f57755b3
--- /dev/null
+++ b/resources/images/plate_imex_mirror_mode.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_mirror_mode_dark.svg b/resources/images/plate_imex_mirror_mode_dark.svg
new file mode 100644
index 0000000000..b0ab1ee257
--- /dev/null
+++ b/resources/images/plate_imex_mirror_mode_dark.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_mirror_mode_hover.svg b/resources/images/plate_imex_mirror_mode_hover.svg
new file mode 100644
index 0000000000..1772d2c7fb
--- /dev/null
+++ b/resources/images/plate_imex_mirror_mode_hover.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_mirror_mode_hover_dark.svg b/resources/images/plate_imex_mirror_mode_hover_dark.svg
new file mode 100644
index 0000000000..d50131ed4b
--- /dev/null
+++ b/resources/images/plate_imex_mirror_mode_hover_dark.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_mode.svg b/resources/images/plate_imex_mode.svg
deleted file mode 100644
index 30a67cdf35..0000000000
--- a/resources/images/plate_imex_mode.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/resources/images/plate_imex_mode_dark.svg b/resources/images/plate_imex_mode_dark.svg
deleted file mode 100644
index 981fd88447..0000000000
--- a/resources/images/plate_imex_mode_dark.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/resources/images/plate_imex_mode_hover.svg b/resources/images/plate_imex_mode_hover.svg
deleted file mode 100644
index 9463e767df..0000000000
--- a/resources/images/plate_imex_mode_hover.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/resources/images/plate_imex_mode_hover_dark.svg b/resources/images/plate_imex_mode_hover_dark.svg
deleted file mode 100644
index 301fa995ab..0000000000
--- a/resources/images/plate_imex_mode_hover_dark.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/resources/images/plate_imex_normal_mode.svg b/resources/images/plate_imex_normal_mode.svg
new file mode 100644
index 0000000000..2d2324236f
--- /dev/null
+++ b/resources/images/plate_imex_normal_mode.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_normal_mode_dark.svg b/resources/images/plate_imex_normal_mode_dark.svg
new file mode 100644
index 0000000000..86d4b2ec0e
--- /dev/null
+++ b/resources/images/plate_imex_normal_mode_dark.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_normal_mode_hover.svg b/resources/images/plate_imex_normal_mode_hover.svg
new file mode 100644
index 0000000000..3cdfb350fa
--- /dev/null
+++ b/resources/images/plate_imex_normal_mode_hover.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/resources/images/plate_imex_normal_mode_hover_dark.svg b/resources/images/plate_imex_normal_mode_hover_dark.svg
new file mode 100644
index 0000000000..70b26ed81b
--- /dev/null
+++ b/resources/images/plate_imex_normal_mode_hover_dark.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/src/libslic3r/IMEXHelpers.cpp b/src/libslic3r/IMEXHelpers.cpp
index f1a93f1619..57162638d4 100644
--- a/src/libslic3r/IMEXHelpers.cpp
+++ b/src/libslic3r/IMEXHelpers.cpp
@@ -308,6 +308,20 @@ std::vector> parse_imex_active_tools(const std::string&
return out;
}
+ImexModeKind imex_mode_kind(const std::string& active_tools_for_mode)
+{
+ const int primary = imex_primary_tool_for_mode(active_tools_for_mode);
+ std::set roles;
+ for (const auto& [phys, role] : parse_imex_active_tools(active_tools_for_mode))
+ if (phys != primary && role != ImexRole::Primary)
+ roles.insert(role);
+ if (roles.empty())
+ return ImexModeKind::Primary;
+ if (roles.count(ImexRole::Span))
+ return ImexModeKind::Custom;
+ return roles.count(ImexRole::Mirror) ? ImexModeKind::Mirror : ImexModeKind::Copy;
+}
+
ImexGantryGrouping group_imex_active_tools_by_gantry(const std::string& active_tools_for_mode,
int tools_per_gantry)
{
diff --git a/src/libslic3r/IMEXHelpers.hpp b/src/libslic3r/IMEXHelpers.hpp
index 9a55f4ce63..eb54b6ee0f 100644
--- a/src/libslic3r/IMEXHelpers.hpp
+++ b/src/libslic3r/IMEXHelpers.hpp
@@ -439,6 +439,15 @@ ImexRole imex_role_from_suffix(const std::string& suffix);
// in hand and need the full head/role list (e.g. ghost factory/updater).
std::vector> parse_imex_active_tools(const std::string& active_tools_for_mode);
+// What a mode does, as the plate's mode icon shows it. The heads beside the primary that
+// imex_primary_tool_for_mode() finds decide: none is Primary, any Span head is Custom (the
+// multicolor modes), any Mirror head is Mirror, and all heads copying is Copy. One Mirror head is
+// enough because an IQEX mirror mode copies within the primary's gantry.
+enum class ImexModeKind { Primary, Copy, Mirror, Custom };
+inline constexpr size_t kImexModeKindCount = 4;
+static_assert(size_t(ImexModeKind::Custom) + 1 == kImexModeKindCount);
+ImexModeKind imex_mode_kind(const std::string& active_tools_for_mode);
+
// =============================================================================
// The one derivation of "what does this plate's mode do, and does its filament reach
// the primary" — shared by the hard block and the pre-slice warning
diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp
index 2bd7f2ee87..dab20fb356 100644
--- a/src/slic3r/GUI/PartPlate.cpp
+++ b/src/slic3r/GUI/PartPlate.cpp
@@ -2115,11 +2115,17 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id)
PresetBundle* pb = wxGetApp().preset_bundle;
auto* is_imex_opt = pb ? pb->printers.get_edited_preset().config.option("is_imex") : nullptr;
if (is_imex_opt && is_imex_opt->value) {
+ // The icon shows the mode the plate slices as, the one its ghosts follow.
+ std::string active_tools;
+ int primary_phys = -1;
+ const size_t kind = size_t(resolve_active_mode_tools(active_tools, primary_phys) ?
+ imex_mode_kind(active_tools) :
+ ImexModeKind::Primary);
// Both the mode name and the conflict warning describe the same hovered icon,
// and set_hover_tooltip records one string per frame, so they are composed here.
std::string hover_tip;
if (hover_id == (int)PLATE_IMEX_MODE_ID) {
- render_icon_texture(m_imex_mode_icon.model, m_partplate_list->m_imex_mode_hovered_texture);
+ render_icon_texture(m_imex_mode_icon.model, m_partplate_list->m_imex_mode_hovered_textures[kind]);
// The stored value, not the resolved one: this line carries the click
// affordances, and the menu checkmark and the left-click cycle both act
// on what the plate stores. Reporting the inherited mode here would leave
@@ -2151,7 +2157,7 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id)
hover_tip = (boost::format("IDEX/IQEX mode: %1% (left-click to cycle, right-click for menu)") % cur).str();
}
} else {
- render_icon_texture(m_imex_mode_icon.model, m_partplate_list->m_imex_mode_texture);
+ render_icon_texture(m_imex_mode_icon.model, m_partplate_list->m_imex_mode_textures[kind]);
}
// Warning badge: IMEX parallel mode active alongside multi-material objects
if (has_imex_multimaterial_conflict()) {
@@ -5407,18 +5413,17 @@ void PartPlateList::generate_icon_textures()
}
}
- // IDEX/IQEX mode icon textures (fall back gracefully if SVG not present yet)
+ // IDEX/IQEX mode icon textures, one per ImexModeKind (fall back gracefully if an SVG is missing)
{
- file_name = path + (m_is_dark ? "plate_imex_mode_dark.svg" : "plate_imex_mode.svg");
- if (!m_imex_mode_texture.load_from_svg_file(file_name, true, false, false, icon_size)) {
- BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(":load file %1% failed (IDEX/IQEX mode icon)") % file_name;
- }
- }
- {
- file_name = path + (m_is_dark ? "plate_imex_mode_hover_dark.svg" : "plate_imex_mode_hover.svg");
- if (!m_imex_mode_hovered_texture.load_from_svg_file(file_name, true, false, false, icon_size)) {
- BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(":load file %1% failed (IDEX/IQEX mode hover icon)") % file_name;
- }
+ static const char* kind_names[] = { "normal", "copy", "mirror", "custom" }; // the Primary kind shows "normal"
+ static_assert(std::size(kind_names) == kImexModeKindCount && std::tuple_size_v == kImexModeKindCount);
+ for (size_t kind = 0; kind < kImexModeKindCount; ++kind)
+ for (const bool hovered : { false, true }) {
+ file_name = path + "plate_imex_" + kind_names[kind] + "_mode" + (hovered ? "_hover" : "") + (m_is_dark ? "_dark.svg" : ".svg");
+ GLTexture& texture = hovered ? m_imex_mode_hovered_textures[kind] : m_imex_mode_textures[kind];
+ if (!texture.load_from_svg_file(file_name, true, false, false, icon_size))
+ BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(":load file %1% failed (IDEX/IQEX mode icon)") % file_name;
+ }
}
std::string text_str = "01";
@@ -5464,8 +5469,10 @@ void PartPlateList::release_icon_textures()
m_plate_set_filament_map_hovered_texture.reset();
m_plate_name_edit_texture.reset();
m_plate_name_edit_hovered_texture.reset();
- m_imex_mode_texture.reset();
- m_imex_mode_hovered_texture.reset();
+ for (GLTexture& texture : m_imex_mode_textures)
+ texture.reset();
+ for (GLTexture& texture : m_imex_mode_hovered_textures)
+ texture.reset();
m_imex_warn_texture.reset();
for (int i = 0;i < MAX_PLATE_COUNT; i++) {
m_idx_textures[i].reset();
diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp
index b74606572e..687e743343 100644
--- a/src/slic3r/GUI/PartPlate.hpp
+++ b/src/slic3r/GUI/PartPlate.hpp
@@ -769,8 +769,8 @@ class PartPlateList : public ObjectBase
GLTexture m_plate_set_filament_map_hovered_texture;
GLTexture m_plate_name_edit_texture;
GLTexture m_plate_name_edit_hovered_texture;
- GLTexture m_imex_mode_texture;
- GLTexture m_imex_mode_hovered_texture;
+ std::array m_imex_mode_textures; // indexed by ImexModeKind
+ std::array m_imex_mode_hovered_textures;
GLTexture m_imex_warn_texture; // warning badge for IMEX + multi-material conflict
GLTexture m_idx_textures[MAX_PLATE_COUNT];
// set render option
diff --git a/tests/libslic3r/test_imex_helpers.cpp b/tests/libslic3r/test_imex_helpers.cpp
index 0c4fb5dbf7..e27630a78a 100644
--- a/tests/libslic3r/test_imex_helpers.cpp
+++ b/tests/libslic3r/test_imex_helpers.cpp
@@ -873,6 +873,26 @@ TEST_CASE("parse_imex_active_tools - Span suffix whitespace tolerant", "[IMEX]")
REQUIRE(out[1].second == ImexRole::Span);
}
+TEST_CASE("A mode's icon kind follows the roles of the heads beside its primary", "[IMEX]") {
+ const auto [roster, kind] = GENERATE(table({
+ { "", ImexModeKind::Primary }, // a mode with no row prints as Primary
+ { "0:P", ImexModeKind::Primary },
+ { "0", ImexModeKind::Primary }, // a legacy bare index is the primary
+ { "0:P,0:C", ImexModeKind::Primary }, // the primary's head, whatever else it lists
+ { "0:P,1:C", ImexModeKind::Copy },
+ { "0:P,1:C,2:C,3:C", ImexModeKind::Copy },
+ { "0,1,2", ImexModeKind::Copy },
+ { "0:P,1:M", ImexModeKind::Mirror },
+ { "0:P,1:C,2:M,3:M", ImexModeKind::Mirror }, // IQEX mirror copies within the primary's gantry
+ { "0,1:M", ImexModeKind::Mirror },
+ { "0:P,1:S,2:C,3:C", ImexModeKind::Custom },
+ { "0:P,1:S,2:M,3:M", ImexModeKind::Custom },
+ { "0:P,1:S", ImexModeKind::Custom },
+ }));
+ CAPTURE(roster);
+ REQUIRE(imex_mode_kind(roster) == kind);
+}
+
TEST_CASE("group_imex_active_tools_by_gantry - paired-gantry mc-mirror aggregates", "[IMEX]") {
// 2x2 IQEX, primary T0, T1 declared Span (multicolor partner on primary's gantry),
// T2/T3 mirror with column-pairing T2↔T0 and T3↔T1.