mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 17:21:10 +00:00
feat: iMEX multi-material warning badge, conflict dialog, and mode row UI
- Add warning badge (obj_warning.svg overlay) to the iMEX plate icon when a parallel mode is active alongside multi-material objects on the same plate - Add has_imex_multimaterial_conflict() using get_extruders(true) so only filaments actually used on the plate are checked - Move multi-material caution dialog from reslice() into on_action_slice_plate / on_action_slice_all so it fires exactly once per user action and does not disrupt GL thumbnail generation during Slice All - Fix is_imex missing from p->config init key list so on_config_change() diff detection correctly triggers refresh_imex_icons() - Defer imex_changed handling until after set_bed_shape() so m_shape is current - Replace plain remove button with ScalableButton (imex_remove.svg) in mode rows - Add EditGCodeDialog launch button per mode row for placeholder browsing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
1d68f917b0
commit
73b5b750c6
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><line x1="2.5" y1="2.5" x2="13.5" y2="13.5" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/><line x1="13.5" y1="2.5" x2="2.5" y2="13.5" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>
|
||||
|
After Width: | Height: | Size: 354 B |
@@ -879,8 +879,11 @@ void PartPlate::refresh_imex_icon()
|
||||
if (!preset) return;
|
||||
bool dual_bbl = preset->is_bbl_vendor() && preset->get_printer_extruder_count() == 2;
|
||||
auto* is_imex_opt = preset->printers.get_edited_preset().config.option<ConfigOptionBool>("is_imex");
|
||||
if (is_imex_opt && is_imex_opt->value)
|
||||
calc_vertex_for_icons(dual_bbl ? 7 : 6, m_imex_mode_icon);
|
||||
if (is_imex_opt && is_imex_opt->value) {
|
||||
int imex_slot = dual_bbl ? 7 : 6;
|
||||
calc_vertex_for_icons(imex_slot, m_imex_mode_icon);
|
||||
calc_vertex_for_imex_warn_badge(imex_slot, m_imex_warn_icon);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure zone geometry (secondary boxes, collision strips, visual meshes) is up to date.
|
||||
@@ -920,6 +923,20 @@ bool PartPlate::has_imex_placement_violations()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PartPlate::has_imex_multimaterial_conflict() const
|
||||
{
|
||||
// Condition 1: iMEX is active and the plate mode is non-primary
|
||||
auto* pb = wxGetApp().preset_bundle;
|
||||
if (!pb) return false;
|
||||
auto* is_imex_opt = pb->printers.get_edited_preset().config.option<ConfigOptionBool>("is_imex");
|
||||
if (!is_imex_opt || !is_imex_opt->value) return false;
|
||||
if (get_imex_mode() == "primary") return false;
|
||||
|
||||
// Condition 2: objects on this plate actually use more than one unique filament/extruder
|
||||
std::vector<int> used = get_extruders(true);
|
||||
return used.size() > 1;
|
||||
}
|
||||
|
||||
void PartPlate::render_imex_zones(bool force_default_color)
|
||||
{
|
||||
if (force_default_color)
|
||||
@@ -1237,6 +1254,37 @@ void PartPlate::calc_vertex_for_icons(int index, PickingModel &model)
|
||||
init_raycaster_from_model(model);
|
||||
}
|
||||
|
||||
// Positions the iMEX multi-material warning badge as a small overlay at the bottom-right
|
||||
// corner of the iMEX mode icon. The icon slot index matches the one used in calc_vertex_for_icons.
|
||||
void PartPlate::calc_vertex_for_imex_warn_badge(int imex_icon_index, GLModel &model)
|
||||
{
|
||||
model.reset();
|
||||
|
||||
auto bed_ext = get_extents(m_shape);
|
||||
Vec2d p = bed_ext[2];
|
||||
auto factor = bed_ext.size()(1) / 200.0;
|
||||
float size = PARTPLATE_ICON_SIZE * factor;
|
||||
float gap_left = PARTPLATE_ICON_GAP_LEFT * factor;
|
||||
float gap_y = PARTPLATE_ICON_GAP_Y * factor;
|
||||
float gap_top = PARTPLATE_ICON_GAP_TOP * factor;
|
||||
|
||||
// Centre of the iMEX mode icon (top-left corner = p after offset)
|
||||
p += Vec2d(gap_left, -1 * (imex_icon_index * (size + gap_y) + gap_top));
|
||||
|
||||
// Badge is half the icon size, anchored to the bottom-right corner of the icon slot
|
||||
float badge = size * 0.55f;
|
||||
Vec2d bp(p(0) + size - badge, p(1) - size);
|
||||
|
||||
ExPolygon poly;
|
||||
poly.contour.append({ scale_(bp(0)) , scale_(bp(1)) });
|
||||
poly.contour.append({ scale_(bp(0) + badge), scale_(bp(1)) });
|
||||
poly.contour.append({ scale_(bp(0) + badge), scale_(bp(1) + badge)});
|
||||
poly.contour.append({ scale_(bp(0)) , scale_(bp(1) + badge)});
|
||||
|
||||
if (!init_model_from_poly(model, poly, GROUND_Z + 0.01f))
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry for iMEX warn badge\n";
|
||||
}
|
||||
|
||||
/*
|
||||
void PartPlate::calc_vertex_for_icons_background(int icon_count, GLModel &buffer)
|
||||
{
|
||||
@@ -1748,6 +1796,12 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id)
|
||||
} else {
|
||||
render_icon_texture(m_imex_mode_icon.model, m_partplate_list->m_imex_mode_texture);
|
||||
}
|
||||
// Warning badge: iMEX parallel mode active alongside multi-material objects
|
||||
if (has_imex_multimaterial_conflict()) {
|
||||
render_icon_texture(m_imex_warn_icon, m_partplate_list->m_imex_warn_texture);
|
||||
if (hover_id == (int)PLATE_IMEX_MODE_ID)
|
||||
show_tooltip(_u8L("Warning: this plate uses a parallel iMEX mode with multi-material objects. Proceed with caution — verify your G-code handles this combination correctly."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2060,10 +2114,10 @@ void PartPlate::register_raycasters_for_picking(GLCanvas3D &canvas)
|
||||
if (dual_bbl)
|
||||
register_model_for_picking(canvas, m_plate_filament_map_icon, picking_id_component(PLATE_FILAMENT_MAP_ID));
|
||||
|
||||
// Register IDEX/IQEX mode icon only when IDEX/IQEX is active on the current printer preset.
|
||||
// Register IDEX/IQEX mode icon only when IDEX/IQEX is active and geometry is initialized.
|
||||
if (preset) {
|
||||
auto* is_imex_opt = preset->printers.get_edited_preset().config.option<ConfigOptionBool>("is_imex");
|
||||
if (is_imex_opt && is_imex_opt->value)
|
||||
if (is_imex_opt && is_imex_opt->value && m_imex_mode_icon.mesh_raycaster)
|
||||
register_model_for_picking(canvas, m_imex_mode_icon, picking_id_component(PLATE_IMEX_MODE_ID));
|
||||
}
|
||||
}
|
||||
@@ -3753,8 +3807,11 @@ bool PartPlate::set_shape(const Pointfs& shape, const Pointfs& exclude_areas, co
|
||||
calc_vertex_for_icons(dual_bbl ? 6 : 5, m_move_front_icon);
|
||||
{
|
||||
auto* is_imex_opt = preset->printers.get_edited_preset().config.option<ConfigOptionBool>("is_imex");
|
||||
if (is_imex_opt && is_imex_opt->value)
|
||||
calc_vertex_for_icons(dual_bbl ? 7 : 6, m_imex_mode_icon);
|
||||
if (is_imex_opt && is_imex_opt->value) {
|
||||
int imex_slot = dual_bbl ? 7 : 6;
|
||||
calc_vertex_for_icons(imex_slot, m_imex_mode_icon);
|
||||
calc_vertex_for_imex_warn_badge(imex_slot, m_imex_warn_icon);
|
||||
}
|
||||
}
|
||||
|
||||
calc_vertex_for_number(0, false, m_plate_idx_icon);
|
||||
@@ -4625,6 +4682,14 @@ void PartPlateList::generate_icon_textures()
|
||||
}
|
||||
}
|
||||
|
||||
// iMEX multi-material conflict warning badge
|
||||
{
|
||||
file_name = path + "obj_warning.svg";
|
||||
if (!m_imex_warn_texture.load_from_svg_file(file_name, true, false, false, icon_size)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(":load file %1% failed (iMEX warn badge)") % file_name;
|
||||
}
|
||||
}
|
||||
|
||||
// IDEX/IQEX mode icon textures (fall back gracefully if SVG not present yet)
|
||||
{
|
||||
file_name = path + (m_is_dark ? "plate_imex_mode_dark.svg" : "plate_imex_mode.svg");
|
||||
@@ -4684,6 +4749,7 @@ void PartPlateList::release_icon_textures()
|
||||
m_plate_name_edit_hovered_texture.reset();
|
||||
m_imex_mode_texture.reset();
|
||||
m_imex_mode_hovered_texture.reset();
|
||||
m_imex_warn_texture.reset();
|
||||
for (int i = 0;i < MAX_PLATE_COUNT; i++) {
|
||||
m_idx_textures[i].reset();
|
||||
}
|
||||
|
||||
@@ -156,6 +156,7 @@ private:
|
||||
PickingModel m_plate_name_edit_icon;
|
||||
PickingModel m_move_front_icon;
|
||||
PickingModel m_imex_mode_icon;
|
||||
GLModel m_imex_warn_icon; // warning badge: iMEX parallel mode + multi-material conflict
|
||||
GLModel m_plate_idx_icon;
|
||||
GLTexture m_texture;
|
||||
|
||||
@@ -195,6 +196,7 @@ private:
|
||||
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);
|
||||
void calc_vertex_for_icons(int index, PickingModel &model);
|
||||
void calc_vertex_for_imex_warn_badge(int imex_icon_index, GLModel &model);
|
||||
// void calc_vertex_for_icons_background(int icon_count, GLModel &buffer);
|
||||
void render_background(bool force_default_color = false);
|
||||
void render_logo(bool bottom, bool render_cali = true);
|
||||
@@ -454,6 +456,9 @@ public:
|
||||
}
|
||||
// Returns true if any instance on this plate overlaps an IDEX/IQEX secondary or collision zone.
|
||||
bool has_imex_placement_violations();
|
||||
// Returns true when iMEX parallel mode is active (non-primary) AND the plate
|
||||
// has objects assigned to more than one filament — a combination that warrants caution.
|
||||
bool has_imex_multimaterial_conflict() const;
|
||||
// Returns the primary-zone bounding box in mm when an IDEX/IQEX parallel mode is active.
|
||||
// Empty (nullopt) when IDEX/IQEX is off or the mode is "primary" (full-bed).
|
||||
std::optional<BoundingBoxf> imex_primary_zone() { ensure_imex_zones(); return m_imex_primary_zone_box; }
|
||||
@@ -637,6 +642,7 @@ class PartPlateList : public ObjectBase
|
||||
GLTexture m_plate_name_edit_hovered_texture;
|
||||
GLTexture m_imex_mode_texture;
|
||||
GLTexture m_imex_mode_hovered_texture;
|
||||
GLTexture m_imex_warn_texture; // warning badge for iMEX + multi-material conflict
|
||||
GLTexture m_idx_textures[MAX_PLATE_COUNT];
|
||||
// set render option
|
||||
bool render_bedtype_logo = true;
|
||||
|
||||
@@ -4821,7 +4821,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
"wipe_tower_rotation_angle", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_max_purge_speed",
|
||||
"wipe_tower_wall_type", "wipe_tower_extra_rib_length","wipe_tower_rib_width","wipe_tower_fillet_wall",
|
||||
"wipe_tower_filament",
|
||||
"best_object_pos", "master_extruder_id"
|
||||
"best_object_pos", "master_extruder_id",
|
||||
"is_imex"
|
||||
}))
|
||||
, sidebar(new Sidebar(q))
|
||||
, notification_manager(std::make_unique<NotificationManager>(q))
|
||||
@@ -9781,6 +9782,24 @@ void Plater::priv::on_action_slice_plate(SimpleEvent&)
|
||||
{
|
||||
if (q != nullptr) {
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received slice plate event\n" ;
|
||||
|
||||
// iMEX parallel mode + multi-material caution check (current plate only)
|
||||
{
|
||||
PartPlate* plate = partplate_list.get_curr_plate();
|
||||
if (plate && plate->has_imex_multimaterial_conflict()) {
|
||||
int plate_num = partplate_list.get_curr_plate_index() + 1;
|
||||
wxString msg = wxString::Format(
|
||||
_L("Plate %d has an IDEX/IQEX parallel mode active alongside multi-material objects.\n\n"
|
||||
"Secondary tool heads operate at the firmware level and may not behave as expected with multi-material prints. "
|
||||
"Proceed with caution and verify your G-code handles this combination correctly.\n\n"
|
||||
"Continue slicing?"),
|
||||
plate_num);
|
||||
MessageDialog dlg(q, msg, _L("IDEX/IQEX Multi-Material Caution"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (dlg.ShowModal() != wxID_YES)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//BBS update extruder params and speed table before slicing
|
||||
const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config();
|
||||
auto& print = q->get_partplate_list().get_current_fff_print();
|
||||
@@ -9800,6 +9819,32 @@ void Plater::priv::on_action_slice_all(SimpleEvent&)
|
||||
{
|
||||
if (q != nullptr) {
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received slice project event\n" ;
|
||||
|
||||
// iMEX parallel mode + multi-material caution check (all plates)
|
||||
{
|
||||
std::vector<int> conflict_plates;
|
||||
int plate_count = partplate_list.get_plate_count();
|
||||
for (int i = 0; i < plate_count; ++i) {
|
||||
PartPlate* plate = partplate_list.get_plate(i);
|
||||
if (plate && plate->has_imex_multimaterial_conflict())
|
||||
conflict_plates.push_back(i + 1); // 1-based for display
|
||||
}
|
||||
if (!conflict_plates.empty()) {
|
||||
wxString plate_list;
|
||||
for (int n : conflict_plates)
|
||||
plate_list += wxString::Format(" %d", n);
|
||||
wxString msg = wxString::Format(
|
||||
_L("Plate(s)%s have an IDEX/IQEX parallel mode active alongside multi-material objects.\n\n"
|
||||
"Secondary tool heads operate at the firmware level and may not behave as expected with multi-material prints. "
|
||||
"Proceed with caution and verify your G-code handles this combination correctly.\n\n"
|
||||
"Continue slicing?"),
|
||||
plate_list);
|
||||
MessageDialog dlg(q, msg, _L("IDEX/IQEX Multi-Material Caution"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (dlg.ShowModal() != wxID_YES)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//BBS update extruder params and speed table before slicing
|
||||
const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config();
|
||||
auto& print = q->get_partplate_list().get_current_fff_print();
|
||||
@@ -16180,6 +16225,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
|
||||
{
|
||||
bool update_scheduled = false;
|
||||
bool bed_shape_changed = false;
|
||||
bool imex_changed = false;
|
||||
//bool print_sequence_changed = false;
|
||||
t_config_option_keys diff_keys = p->config->diff(config);
|
||||
|
||||
@@ -16270,8 +16316,9 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
|
||||
}
|
||||
// IDEX/IQEX: when is_imex toggles the mode icon needs to be repositioned on every plate.
|
||||
// set_shape() short-circuits when the bed geometry is unchanged, so we refresh explicitly.
|
||||
// Defer until after the loop so bed_shape_changed / set_bed_shape() run first if needed.
|
||||
else if (opt_key == "is_imex") {
|
||||
p->partplate_list.refresh_imex_icons();
|
||||
imex_changed = true;
|
||||
update_scheduled = true;
|
||||
}
|
||||
}
|
||||
@@ -16279,6 +16326,16 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
|
||||
if (bed_shape_changed)
|
||||
set_bed_shape();
|
||||
|
||||
// After any bed-shape or is_imex change, ensure the iMEX mode icon geometry and
|
||||
// raycaster are correct. set_shape() short-circuits when the bed is unchanged,
|
||||
// so we call refresh_imex_icons() explicitly whenever is_imex is active.
|
||||
// Always done AFTER set_bed_shape() so m_shape is current.
|
||||
if (bed_shape_changed || imex_changed) {
|
||||
auto* is_imex_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionBool>("is_imex");
|
||||
if (is_imex_opt && is_imex_opt->value)
|
||||
p->partplate_list.refresh_imex_icons();
|
||||
}
|
||||
|
||||
config_change_notification(config, std::string("print_sequence"));
|
||||
|
||||
if (update_scheduled)
|
||||
|
||||
+21
-4
@@ -4653,17 +4653,34 @@ private:
|
||||
sizer->Add(r.name, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
|
||||
}
|
||||
sizer->Add(grid_panel, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
|
||||
sizer->Add(r.gcode, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
|
||||
sizer->Add(r.gcode, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
|
||||
|
||||
// Placeholder search + (for non-primary) delete — stacked vertically
|
||||
auto* btn_col = new wxBoxSizer(wxVERTICAL);
|
||||
wxTextCtrl* gcode_ctrl = r.gcode;
|
||||
auto* ph_btn = new ScalableButton(r.panel, wxID_ANY, "edit", wxEmptyString,
|
||||
wxDefaultSize, wxDefaultPosition,
|
||||
wxBU_EXACTFIT | wxNO_BORDER, 16);
|
||||
ph_btn->SetToolTip(_L("Edit G-code / browse placeholders"));
|
||||
ph_btn->Bind(wxEVT_BUTTON, [this, gcode_ctrl](wxCommandEvent&) {
|
||||
EditGCodeDialog dlg(this, "imex_mode_gcode", gcode_ctrl->GetValue().ToStdString());
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
gcode_ctrl->SetValue(dlg.get_edited_gcode());
|
||||
});
|
||||
btn_col->Add(ph_btn, 0, wxBOTTOM, 2);
|
||||
|
||||
if (!is_primary) {
|
||||
auto* rm = new wxButton(r.panel, wxID_ANY, wxT("\u00d7"),
|
||||
wxDefaultPosition, wxSize(24, 24), wxBU_EXACTFIT);
|
||||
auto* rm = new ScalableButton(r.panel, wxID_ANY, "imex_remove", wxEmptyString,
|
||||
wxDefaultSize, wxDefaultPosition,
|
||||
wxBU_EXACTFIT | wxNO_BORDER, 16);
|
||||
rm->SetToolTip(_L("Remove mode"));
|
||||
rm->Bind(wxEVT_BUTTON, [this, this_panel](wxCommandEvent&) {
|
||||
remove_row(this_panel);
|
||||
notify();
|
||||
});
|
||||
sizer->Add(rm, 0, wxALIGN_CENTER_VERTICAL);
|
||||
btn_col->Add(rm, 0);
|
||||
}
|
||||
sizer->Add(btn_col, 0, wxALIGN_CENTER_VERTICAL);
|
||||
r.panel->SetSizer(sizer);
|
||||
|
||||
m_rows_sizer->Add(r.panel, 0, wxEXPAND | wxBOTTOM, 4);
|
||||
|
||||
Reference in New Issue
Block a user