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.