diff --git a/resources/images/plate_ixex_mode.svg b/resources/images/plate_ixex_mode.svg
new file mode 100644
index 0000000000..30a67cdf35
--- /dev/null
+++ b/resources/images/plate_ixex_mode.svg
@@ -0,0 +1,10 @@
+
diff --git a/resources/images/plate_ixex_mode_dark.svg b/resources/images/plate_ixex_mode_dark.svg
new file mode 100644
index 0000000000..981fd88447
--- /dev/null
+++ b/resources/images/plate_ixex_mode_dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/resources/images/plate_ixex_mode_hover.svg b/resources/images/plate_ixex_mode_hover.svg
new file mode 100644
index 0000000000..9463e767df
--- /dev/null
+++ b/resources/images/plate_ixex_mode_hover.svg
@@ -0,0 +1,10 @@
+
diff --git a/resources/images/plate_ixex_mode_hover_dark.svg b/resources/images/plate_ixex_mode_hover_dark.svg
new file mode 100644
index 0000000000..301fa995ab
--- /dev/null
+++ b/resources/images/plate_ixex_mode_hover_dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp
index dcbf638483..b7513ea240 100644
--- a/src/libslic3r/Format/bbs_3mf.cpp
+++ b/src/libslic3r/Format/bbs_3mf.cpp
@@ -333,6 +333,7 @@ static constexpr const char* FIRST_LAYER_PRINT_SEQUENCE_ATTR = "first_layer_prin
static constexpr const char* OTHER_LAYERS_PRINT_SEQUENCE_ATTR = "other_layers_print_sequence";
static constexpr const char* OTHER_LAYERS_PRINT_SEQUENCE_NUMS_ATTR = "other_layers_print_sequence_nums";
static constexpr const char* SPIRAL_VASE_MODE = "spiral_mode";
+static constexpr const char* IXEX_PARALLEL_MODE_ATTR = "ixex_parallel_mode";
static constexpr const char* FILAMENT_MAP_MODE_ATTR = "filament_map_mode";
static constexpr const char* FILAMENT_MAP_ATTR = "filament_maps";
static constexpr const char* LIMIT_FILAMENT_MAP_ATTR = "limit_filament_maps";
@@ -4292,6 +4293,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
std::istringstream(value) >> std::boolalpha >> spiral_mode;
m_curr_plater->config.set_key_value("spiral_mode", new ConfigOptionBool(spiral_mode));
}
+ else if (key == IXEX_PARALLEL_MODE_ATTR) {
+ m_curr_plater->config.set_key_value("ixex_parallel_mode", new ConfigOptionString(value));
+ }
else if (key == FILAMENT_MAP_MODE_ATTR)
{
FilamentMapMode map_mode = FilamentMapMode::fmmAutoForFlush;
@@ -7773,6 +7777,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
if (spiral_mode_opt)
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SPIRAL_VASE_MODE << "\" " << VALUE_ATTR << "=\"" << spiral_mode_opt->getBool() << "\"/>\n";
+ {
+ auto* ixex_mode_opt = plate_data->config.option("ixex_parallel_mode");
+ if (ixex_mode_opt && !ixex_mode_opt->value.empty() && ixex_mode_opt->value != "primary")
+ stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << IXEX_PARALLEL_MODE_ATTR << "\" " << VALUE_ATTR << "=\"" << ixex_mode_opt->value << "\"/>\n";
+ }
+
//filament map related
ConfigOption* filament_map_mode_opt = plate_data->config.option("filament_map_mode");
t_config_enum_names filament_map_mode_names = ConfigOptionEnum::get_enum_names();
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index cf4fba50d8..0b287b1dff 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -5506,22 +5506,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(2));
- def = this->add("ixex_primary_col", coInt);
- def->label = L("Primary Tool Column");
- def->tooltip = L("X index (0-based) of the primary toolhead. 0 = leftmost column. Determines which zone is the active print area.");
- def->min = 0;
- def->max = 3;
- def->mode = comAdvanced;
- def->set_default_value(new ConfigOptionInt(0));
-
- def = this->add("ixex_primary_row", coInt);
- def->label = L("Primary Tool Row");
- def->tooltip = L("Y index (0-based) of the primary toolhead's gantry. 0 = front gantry. Determines which zone is the active print area.");
- def->min = 0;
- def->max = 3;
- def->mode = comAdvanced;
- def->set_default_value(new ConfigOptionInt(0));
-
def = this->add("ixex_tool_layout", coString);
def->label = L("Tool 0 Corner");
def->tooltip = L("Physical corner of the bed where tool T0 (index 0) is located. "
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index 1e89eba383..a2739f1c9a 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -1339,8 +1339,6 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, is_ixex))
((ConfigOptionInt, ixex_gantry_count))
((ConfigOptionInt, ixex_tools_per_gantry))
- ((ConfigOptionInt, ixex_primary_col))
- ((ConfigOptionInt, ixex_primary_row))
((ConfigOptionFloat, ixex_nozzle_clearance_x))
((ConfigOptionFloat, ixex_nozzle_clearance_y))
((ConfigOptionStrings, ixex_mode_names))
diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp
index b4e2a9627c..5163580c30 100644
--- a/src/slic3r/GUI/GCodeViewer.cpp
+++ b/src/slic3r/GUI/GCodeViewer.cpp
@@ -1533,7 +1533,13 @@ void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin)
if (is_ixex_opt && is_ixex_opt->value) {
const DynamicPrintConfig& process_cfg = preset_bundle->prints.get_edited_preset().config;
auto* mode_opt = process_cfg.opt("ixex_parallel_mode");
- const std::string mode = mode_opt ? mode_opt->value : "primary";
+ std::string mode = mode_opt ? mode_opt->value : "primary";
+ // Per-plate mode overrides the process preset.
+ if (auto* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate()) {
+ std::string plate_mode = plate->get_ixex_mode();
+ if (plate_mode != "primary")
+ mode = plate_mode;
+ }
if (mode != m_ixex_last_mode) {
m_sequential_view.m_ixex_secondary_markers.clear();
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 5e23a9d4ae..66dc76661d 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -4648,9 +4648,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
//BBS change plate selection
+ bool plate_icon_popup_shown = false;
if (!m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !m_mouse.dragging) {
int hover_idx = m_hover_plate_idxs.front();
- wxGetApp().plater()->select_plate_by_hover_id(hover_idx, true);
+ plate_icon_popup_shown = (wxGetApp().plater()->select_plate_by_hover_id(hover_idx, true) == 1);
if (m_hover_volume_idxs.empty())
deselect_all();
render();
@@ -4670,10 +4671,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (!m_mouse.ignore_right_up && m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined) {
//BBS post right click event
- if (!m_hover_plate_idxs.empty()) {
+ if (!m_hover_plate_idxs.empty() && !plate_icon_popup_shown) {
post_event(RBtnPlateEvent(EVT_GLCANVAS_PLATE_RIGHT_CLICK, { logical_pos, m_hover_plate_idxs.front() }));
}
- else {
+ else if (!plate_icon_popup_shown) {
// do not post the event if the user is panning the scene
// or if right click was done over the wipe tower
bool post_right_click_event = m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower;
diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp
index 9d1d778504..b4ec570d00 100644
--- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp
+++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp
@@ -542,9 +542,16 @@ void ArrangeJob::process(Ctl &ctl)
// When an iXex parallel mode is active, constrain auto-arrange to the primary zone only
// and treat carriage collision strips as hard excluded regions.
+ // NOTE: m_ixex_primary_zone_box and ixex_collision_zones() are in global (world) coordinates
+ // because they are derived from m_shape which includes the plate origin offset. The arranger
+ // always works in plate-local space (origin = 0,0), so we subtract the plate origin here.
if (PartPlate* curr_plate = partplate_list.get_curr_plate()) {
if (auto pz = curr_plate->ixex_primary_zone()) {
- BoundingBox scaled_pz = scaled(*pz);
+ Vec3d plate_origin = curr_plate->get_origin();
+ double ox = plate_origin.x(), oy = plate_origin.y();
+ BoundingBoxf pz_local(Vec2d(pz->min.x() - ox, pz->min.y() - oy),
+ Vec2d(pz->max.x() - ox, pz->max.y() - oy));
+ BoundingBox scaled_pz = scaled(pz_local);
bedpts = {
{ scaled_pz.min.x(), scaled_pz.min.y() },
{ scaled_pz.max.x(), scaled_pz.min.y() },
@@ -553,10 +560,10 @@ void ArrangeJob::process(Ctl &ctl)
};
for (const BoundingBoxf3& cz : curr_plate->ixex_collision_zones()) {
Polygon poly({
- { scaled(cz.min.x()), scaled(cz.min.y()) },
- { scaled(cz.max.x()), scaled(cz.min.y()) },
- { scaled(cz.max.x()), scaled(cz.max.y()) },
- { scaled(cz.min.x()), scaled(cz.max.y()) },
+ { scaled(cz.min.x() - ox), scaled(cz.min.y() - oy) },
+ { scaled(cz.max.x() - ox), scaled(cz.min.y() - oy) },
+ { scaled(cz.max.x() - ox), scaled(cz.max.y() - oy) },
+ { scaled(cz.min.x() - ox), scaled(cz.max.y() - oy) },
});
arrangement::ArrangePolygon ap;
ap.poly.contour = poly;
diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp
index 16a87009a1..0f2f45d919 100644
--- a/src/slic3r/GUI/PartPlate.cpp
+++ b/src/slic3r/GUI/PartPlate.cpp
@@ -365,6 +365,34 @@ std::vector PartPlate::get_plate_wrapping_detection_area() const
return std::vector();
}
+std::string PartPlate::get_ixex_mode() const
+{
+ if (m_config.has("ixex_parallel_mode")) {
+ auto* opt = m_config.option("ixex_parallel_mode");
+ if (opt && !opt->value.empty())
+ return opt->value;
+ }
+ return "primary";
+}
+
+void PartPlate::set_ixex_mode(const std::string& mode)
+{
+ if (mode.empty() || mode == "primary") {
+ m_config.erase("ixex_parallel_mode");
+ } else {
+ m_config.set_key_value("ixex_parallel_mode", new ConfigOptionString(mode));
+ }
+ update_slice_result_valid_state(false);
+ m_ixex_zones_mode_cache = "\x01"; // force zone rebuild
+}
+
+void PartPlate::reset_ixex_mode()
+{
+ m_config.erase("ixex_parallel_mode");
+ update_slice_result_valid_state(false);
+ m_ixex_zones_mode_cache = "\x01";
+}
+
void PartPlate::set_spiral_vase_mode(bool spiral_mode, bool as_global)
{
std::string key = "spiral_mode";
@@ -475,9 +503,14 @@ void PartPlate::calc_ixex_zones()
if (!is_ixex_opt || !is_ixex_opt->value)
return;
- const DynamicPrintConfig& process_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
- auto* mode_opt = process_cfg.option("ixex_parallel_mode");
- std::string active_mode = mode_opt ? mode_opt->value : "primary";
+ // Per-plate mode takes priority over the process preset.
+ std::string active_mode = get_ixex_mode();
+ if (active_mode == "primary") {
+ const DynamicPrintConfig& process_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
+ auto* mode_opt = process_cfg.option("ixex_parallel_mode");
+ if (mode_opt && !mode_opt->value.empty())
+ active_mode = mode_opt->value;
+ }
if (active_mode == "primary" || active_mode.empty())
return;
@@ -767,7 +800,8 @@ void PartPlate::calc_ixex_zones()
}
// Build a cache key from the current iXex config options, or "" if iXex is off.
-static std::string build_ixex_cache_key()
+// Reads per-plate mode from m_config first, falling back to the process preset.
+std::string PartPlate::build_ixex_cache_key() const
{
if (!wxGetApp().preset_bundle)
return "";
@@ -775,14 +809,20 @@ static std::string build_ixex_cache_key()
auto* is_ixex_opt = printer_cfg.option("is_ixex");
if (!is_ixex_opt || !is_ixex_opt->value)
return "";
- const DynamicPrintConfig& process_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
- auto* mode_opt = process_cfg.option("ixex_parallel_mode");
+ // Per-plate mode takes priority over process preset.
+ std::string active_mode = get_ixex_mode();
+ if (active_mode == "primary") {
+ const DynamicPrintConfig& process_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
+ auto* mode_opt = process_cfg.option("ixex_parallel_mode");
+ if (mode_opt && !mode_opt->value.empty())
+ active_mode = mode_opt->value;
+ }
auto* n_col_opt = printer_cfg.option("ixex_tools_per_gantry");
auto* n_row_opt = printer_cfg.option("ixex_gantry_count");
auto* cw_opt = printer_cfg.option("ixex_nozzle_clearance_x");
auto* ch_opt = printer_cfg.option("ixex_nozzle_clearance_y");
auto* mgn_opt = printer_cfg.option("ixex_carriage_margin");
- return (mode_opt ? mode_opt->value : "primary")
+ return active_mode
+ "|" + std::to_string(n_col_opt ? n_col_opt->value : 2)
+ "x" + std::to_string(n_row_opt ? n_row_opt->value : 1)
+ "|cw" + std::to_string(cw_opt ? (int)cw_opt->value : 0)
@@ -1643,6 +1683,20 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id)
} else
render_icon_texture(m_move_front_icon.model, m_partplate_list->m_move_front_texture);
+ // iXex mode icon — only when is_ixex is active
+ {
+ PresetBundle* pb = wxGetApp().preset_bundle;
+ auto* is_ixex_opt = pb ? pb->printers.get_edited_preset().config.option("is_ixex") : nullptr;
+ if (is_ixex_opt && is_ixex_opt->value) {
+ if (hover_id == (int)PLATE_IXEX_MODE_ID) {
+ render_icon_texture(m_ixex_mode_icon.model, m_partplate_list->m_ixex_mode_hovered_texture);
+ std::string cur = get_ixex_mode();
+ show_tooltip(_u8L("iXex mode: ") + cur + _u8L(" (left-click to cycle, right-click for menu)"));
+ } else {
+ render_icon_texture(m_ixex_mode_icon.model, m_partplate_list->m_ixex_mode_texture);
+ }
+ }
+ }
if (m_partplate_list->render_plate_settings) {
bool has_plate_settings = get_bed_type() != BedType::btDefault || get_print_seq() != PrintSequence::ByDefault || !get_first_layer_print_sequence().empty() || !get_other_layers_print_sequence().empty() || has_spiral_mode_config();
@@ -1952,6 +2006,13 @@ void PartPlate::register_raycasters_for_picking(GLCanvas3D &canvas)
bool dual_bbl = (preset && preset->is_bbl_vendor() && preset->get_printer_extruder_count() == 2);
if (dual_bbl)
register_model_for_picking(canvas, m_plate_filament_map_icon, picking_id_component(PLATE_FILAMENT_MAP_ID));
+
+ // Register iXex mode icon only when iXex is active on the current printer preset.
+ if (preset) {
+ auto* is_ixex_opt = preset->printers.get_edited_preset().config.option("is_ixex");
+ if (is_ixex_opt && is_ixex_opt->value)
+ register_model_for_picking(canvas, m_ixex_mode_icon, picking_id_component(PLATE_IXEX_MODE_ID));
+ }
}
int PartPlate::picking_id_component(int idx) const
@@ -3605,6 +3666,11 @@ bool PartPlate::set_shape(const Pointfs& shape, const Pointfs& exclude_areas, co
dual_bbl = (preset->is_bbl_vendor() && preset->get_printer_extruder_count() == 2);
calc_vertex_for_icons(dual_bbl ? 5 : 6, m_plate_filament_map_icon);
calc_vertex_for_icons(dual_bbl ? 6 : 5, m_move_front_icon);
+ {
+ auto* is_ixex_opt = preset->printers.get_edited_preset().config.option("is_ixex");
+ if (is_ixex_opt && is_ixex_opt->value)
+ calc_vertex_for_icons(dual_bbl ? 7 : 6, m_ixex_mode_icon);
+ }
calc_vertex_for_number(0, false, m_plate_idx_icon);
// calc vertex for plate name
@@ -4474,6 +4540,20 @@ void PartPlateList::generate_icon_textures()
}
}
+ // iXex mode icon textures (fall back gracefully if SVG not present yet)
+ {
+ file_name = path + (m_is_dark ? "plate_ixex_mode_dark.svg" : "plate_ixex_mode.svg");
+ if (!m_ixex_mode_texture.load_from_svg_file(file_name, true, false, false, icon_size)) {
+ BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(":load file %1% failed (iXex mode icon)") % file_name;
+ }
+ }
+ {
+ file_name = path + (m_is_dark ? "plate_ixex_mode_hover_dark.svg" : "plate_ixex_mode_hover.svg");
+ if (!m_ixex_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 (iXex mode hover icon)") % file_name;
+ }
+ }
+
std::string text_str = "01";
// ORCA also scale font size to prevent low res texture
int size = wxGetApp().em_unit() * PARTPLATE_ICON_SIZE;
@@ -4517,6 +4597,8 @@ 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_ixex_mode_texture.reset();
+ m_ixex_mode_hovered_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 4954e6f3b5..773524c77c 100644
--- a/src/slic3r/GUI/PartPlate.hpp
+++ b/src/slic3r/GUI/PartPlate.hpp
@@ -155,6 +155,7 @@ private:
PickingModel m_plate_filament_map_icon;
PickingModel m_plate_name_edit_icon;
PickingModel m_move_front_icon;
+ PickingModel m_ixex_mode_icon;
GLModel m_plate_idx_icon;
GLTexture m_texture;
@@ -188,6 +189,7 @@ private:
void calc_height_limit();
void calc_ixex_zones();
void ensure_ixex_zones();
+ std::string build_ixex_cache_key() const;
void render_ixex_zones(bool force_default_color);
void calc_vertex_for_number(int index, bool one_number, GLModel &buffer);
void calc_vertex_for_plate_name_edit_icon(GLTexture *texture, int index, PickingModel &model);
@@ -220,7 +222,8 @@ private:
public:
static constexpr unsigned int PLATE_NAME_HOVER_ID = 6;
static constexpr unsigned int PLATE_FILAMENT_MAP_ID = 8;
- static constexpr unsigned int GRABBER_COUNT = 9;
+ static constexpr unsigned int PLATE_IXEX_MODE_ID = 9;
+ static constexpr unsigned int GRABBER_COUNT = 10;
static ColorRGBA SELECT_COLOR;
static ColorRGBA UNSELECT_COLOR;
@@ -280,6 +283,10 @@ public:
bool get_spiral_vase_mode() const;
void set_spiral_vase_mode(bool spiral_mode, bool as_global);
+ std::string get_ixex_mode() const;
+ void set_ixex_mode(const std::string& mode);
+ void reset_ixex_mode();
+
std::vector get_plate_wrapping_detection_area() const;
//static const int plate_x_offset = 20; //mm
@@ -627,6 +634,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_ixex_mode_texture;
+ GLTexture m_ixex_mode_hovered_texture;
GLTexture m_idx_textures[MAX_PLATE_COUNT];
// set render option
bool render_bedtype_logo = true;
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 2b07bc94a8..176b46d535 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -17640,7 +17640,61 @@ int Plater::select_plate_by_hover_id(int hover_id, bool right_click, bool isModi
update();
p->partplate_list.select_plate(0);
}
+ else if (action == (int)PartPlate::PLATE_IXEX_MODE_ID)
+ {
+ ret = select_plate(plate_index);
+ if (!ret) {
+ PartPlate* curr_plate = p->partplate_list.get_curr_plate();
+ // Build ordered mode list: "primary" first, then all named modes.
+ std::vector modes;
+ modes.push_back("primary");
+ const DynamicPrintConfig& printer_cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
+ auto* names_opt = printer_cfg.option("ixex_mode_names");
+ if (names_opt) {
+ for (const auto& n : names_opt->values)
+ if (!n.empty()) modes.push_back(n);
+ }
+ if (right_click) {
+ // Show a popup menu with all modes.
+ wxMenu menu;
+ std::string current = curr_plate->get_ixex_mode();
+ for (size_t i = 0; i < modes.size(); ++i) {
+ wxMenuItem* item = menu.AppendRadioItem(wxID_HIGHEST + (int)i, from_u8(modes[i]));
+ if (modes[i] == current)
+ item->Check(true);
+ }
+ menu.Bind(wxEVT_MENU, [this, curr_plate, &modes](wxCommandEvent& e) {
+ int idx = e.GetId() - wxID_HIGHEST;
+ if (idx >= 0 && idx < (int)modes.size()) {
+ take_snapshot("set ixex mode");
+ curr_plate->set_ixex_mode(modes[idx]);
+ update_project_dirty_from_presets();
+ set_plater_dirty(true);
+ update();
+ }
+ });
+ p->view3D->get_canvas3d()->get_wxglcanvas()->PopupMenu(&menu);
+ ret = 1; // signal to caller: popup was shown, suppress plate context menu
+ } else {
+ // Left-click: cycle to next mode.
+ std::string current = curr_plate->get_ixex_mode();
+ auto it = std::find(modes.begin(), modes.end(), current);
+ size_t next_idx = (it == modes.end()) ? 0 : ((it - modes.begin() + 1) % modes.size());
+ std::string next_mode = modes[next_idx];
+ if (next_mode != current) {
+ take_snapshot("set ixex mode");
+ curr_plate->set_ixex_mode(next_mode);
+ update_project_dirty_from_presets();
+ set_plater_dirty(true);
+ update();
+ }
+ }
+ } else {
+ BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "can not select plate %1%" << plate_index;
+ ret = -1;
+ }
+ }
else
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "invalid action %1%, with right_click=%2%" << action << right_click;
diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp
index 77283cfd8a..d6355a3a5d 100644
--- a/src/slic3r/GUI/Tab.cpp
+++ b/src/slic3r/GUI/Tab.cpp
@@ -2674,26 +2674,6 @@ void TabPrint::build()
optgroup->append_single_option_line("timelapse_type", "others_settings_special_mode#timelapse");
optgroup->append_single_option_line("enable_wrapping_detection");
- // Dynamic combo box — choices come from the printer preset's ixex_mode_names.
- // Use create_line_with_widget so the undo bitmaps are set (has_undo_ui()=true),
- // ensuring the widget x-position matches other option fields in OG_CustomCtrl.
- create_line_with_widget(optgroup.get(), "ixex_parallel_mode", "", [this](wxWindow* parent) -> wxSizer* {
- m_ixex_mode_combo = new ComboBox(parent, wxID_ANY, wxEmptyString,
- wxDefaultPosition,
- wxSize(12 * wxGetApp().em_unit(), -1),
- 0, nullptr, wxCB_READONLY);
- m_ixex_mode_combo->GetDropDown().SetUseContentWidth(true);
- refresh_ixex_mode_combo();
- m_ixex_mode_combo->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) {
- if (!m_ixex_mode_combo || !m_config) return;
- std::string val = m_ixex_mode_combo->GetStringSelection().ToStdString();
- m_config->set_key_value("ixex_parallel_mode", new ConfigOptionString(val));
- on_value_change("ixex_parallel_mode", val);
- });
- auto* s = new wxBoxSizer(wxHORIZONTAL);
- s->Add(m_ixex_mode_combo, 0, wxALIGN_CENTER_VERTICAL);
- return s;
- });
optgroup = page->new_optgroup(L("Fuzzy Skin"), L"fuzzy_skin");
optgroup->append_single_option_line("fuzzy_skin", "others_settings_fuzzy_skin");
@@ -2752,36 +2732,6 @@ void TabPrint::reload_config()
{
this->compatible_widget_reload(m_compatible_printers);
Tab::reload_config();
- refresh_ixex_mode_combo();
-}
-
-void TabPrint::refresh_ixex_mode_combo()
-{
- if (!m_ixex_mode_combo) return;
- // Rebuild choices: "primary" + names from the active printer preset
- m_ixex_mode_combo->Clear();
- m_ixex_mode_combo->Append("primary");
- if (m_preset_bundle) {
- auto* names = m_preset_bundle->printers.get_edited_preset().config
- .option("ixex_mode_names");
- if (names) {
- for (const auto& n : names->values)
- if (!n.empty()) m_ixex_mode_combo->Append(wxString::FromUTF8(n));
- }
- }
- // Restore current selection from process config (safe: option may not exist in older presets)
- std::string cur = "primary";
- if (m_config) {
- auto* opt = m_config->option("ixex_parallel_mode");
- if (opt) cur = opt->value;
- }
- if (!m_ixex_mode_combo->SetStringSelection(cur)) {
- m_ixex_mode_combo->SetSelection(0); // fall back to "primary"
- // The active mode no longer exists — reset config so stale name doesn't crash downstream.
- if (m_config)
- m_config->set_key_value("ixex_parallel_mode", new ConfigOptionString("primary"));
- on_value_change("ixex_parallel_mode", std::string("primary"));
- }
}
void TabPrint::update_description_lines()
@@ -2903,7 +2853,6 @@ void TabPrint::clear_pages()
m_recommended_thin_wall_thickness_description_line = nullptr;
m_top_bottom_shell_thickness_explanation = nullptr;
- m_ixex_mode_combo = nullptr;
}
//BBS: GUI refactor
@@ -5059,9 +5008,6 @@ void TabPrinter::build_fff()
m_config->set_key_value("ixex_mode_gcodes", new ConfigOptionStrings(gcodes));
update_dirty();
on_value_change("ixex_mode_names", std::string(""));
- // Refresh the process tab's mode dropdown immediately
- if (auto* print_tab = dynamic_cast(wxGetApp().get_tab(Preset::TYPE_PRINT)))
- print_tab->refresh_ixex_mode_combo();
};
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_ixex_modes_ctrl, 1, wxEXPAND);
diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp
index 687cfeea73..7d86278db7 100644
--- a/src/slic3r/GUI/Tab.hpp
+++ b/src/slic3r/GUI/Tab.hpp
@@ -464,12 +464,10 @@ public:
void update() override;
void clear_pages() override;
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; }
- void refresh_ixex_mode_combo();
private:
ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr;
ogStaticText* m_top_bottom_shell_thickness_explanation = nullptr;
- ComboBox* m_ixex_mode_combo {nullptr};
};
class TabPrintModel : public TabPrint