This commit is contained in:
Lam Wei Lun
2026-09-21 17:01:46 +08:00
3240 changed files with 299310 additions and 103072 deletions
+183 -106
View File
@@ -91,6 +91,9 @@
#include "wxExtensions.hpp"
#include "../Utils/PrintHost.hpp"
#include "MainFrame.hpp"
#ifdef SLIC3R_CAD
#include "slic3r/GUI/CAD/DesignPanel.hpp"
#endif
#include "format.hpp"
#include "3DScene.hpp"
#include "GLCanvas3D.hpp"
@@ -145,7 +148,6 @@
#include "Widgets/RadioGroup.hpp"
#include "Widgets/CheckBox.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/StaticGroup.hpp"
#include "GUI_ObjectTable.hpp"
#include "libslic3r/Thread.hpp"
@@ -472,14 +474,9 @@ enum class ActionButtonType : int {
abSendGCode
};
// Background for the extruder-group title chip and its edit buttons, matching the StaticGroup
// interior. macOS keeps a lighter #F7F7F7 tint in light mode; dark mode uses the mapped colour.
// Background for the extruder-group title chip and its edit buttons
static wxColour extruder_group_chip_bg()
{
#ifdef __WXOSX__
if (!wxGetApp().dark_mode())
return wxColour("#F7F7F7");
#endif
return StateColor::darkModeColorFor(*wxWHITE);
}
@@ -494,38 +491,39 @@ public:
SetBackgroundColour(extruder_group_chip_bg());
auto sizer = new wxBoxSizer(wxHORIZONTAL);
auto label_color = StateColor::darkModeColorFor(wxColour("#363636"));
m_label = new wxStaticText(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
m_label->SetFont(Label::Body_13);
m_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#6B6B6B")));
m_label->SetFont(Label::Body_12);
m_label->SetForegroundColour(label_color);
m_brace_left = new wxStaticText(this, wxID_ANY, "(", wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
m_brace_left->SetFont(Label::Body_13);
m_brace_left->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
m_brace_left->SetFont(Label::Body_12);
m_brace_left->SetForegroundColour(label_color);
m_brace_left->Hide();
m_count = new wxStaticText(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
m_count->SetFont(Label::Body_13.Bold());
m_count->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
m_count->SetFont(Label::Body_12.Bold());
m_count->SetForegroundColour(label_color);
m_count->Hide();
m_brace_right = new wxStaticText(this, wxID_ANY, ")", wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
m_brace_right->SetFont(Label::Body_13);
m_brace_right->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
m_brace_right->SetFont(Label::Body_12);
m_brace_right->SetForegroundColour(label_color);
m_brace_right->Hide();
m_hover_btn = new ScalableButton(this, wxID_ANY, "dot");
m_hover_btn->SetMinSize(wxSize(FromDIP(25), -1));
m_hover_btn = new ScalableButton(this, wxID_ANY, "edit_12px", wxEmptyString, FromDIP(wxSize(12,12)), wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 12);
m_hover_btn->SetBackgroundColour(extruder_group_chip_bg());
m_hover_btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &evt) {
if (m_enabled && m_hover_on_click)
m_hover_on_click();
});
sizer->Add(m_label, 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(m_brace_left, 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(m_count, 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(m_label , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(4));
sizer->Add(m_brace_left , 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(m_count , 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(m_brace_right, 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(m_hover_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(5));
sizer->Add(m_hover_btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(4));
// No SetSizerAndFit: that would record the count-hidden width as an explicit min size,
// which outranks best size in sizer allocation, so once the count is shown any ancestor
@@ -537,7 +535,7 @@ public:
void EnableEdit(bool enable)
{
m_enabled = enable;
m_hover_btn->SetBitmap_(enable ? "edit" : "dot");
//m_hover_btn->SetBitmap_(enable ? "edit_12px" : "dot"); // it causes crash if icon sizes not matches
}
void SetOnHoverClick(std::function<void()> on_click) { m_hover_on_click = std::move(on_click); }
@@ -548,11 +546,13 @@ public:
m_count->Hide();
m_brace_left->Hide();
m_brace_right->Hide();
m_hover_btn->Hide();
} else {
m_count->SetLabel(wxString::Format("%d", count));
m_count->Show();
m_brace_left->Show();
m_brace_right->Show();
m_hover_btn->Show();
}
UpdateSizing();
}
@@ -563,15 +563,19 @@ public:
UpdateSizing();
}
void Rescale() { m_hover_btn->msw_rescale(); }
void Rescale() {
m_hover_btn->msw_rescale();
UpdateSizing();
}
// Re-apply the chip colours on a live light/dark switch (they are set once at construction).
void sys_color_changed()
{
SetBackgroundColour(extruder_group_chip_bg());
m_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#6B6B6B")));
auto label_color = StateColor::darkModeColorFor(wxColour("#363636"));
m_label->SetForegroundColour(label_color);
for (wxStaticText *t : {m_brace_left, m_count, m_brace_right})
t->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
t->SetForegroundColour(label_color);
m_hover_btn->SetBackgroundColour(extruder_group_chip_bg());
Refresh();
}
@@ -598,11 +602,12 @@ private:
bool m_enabled{false};
};
struct ExtruderGroup : StaticGroup
struct ExtruderGroup : StaticBox
{
ExtruderGroup(wxWindow * parent, int index, wxString const &title);
wxStaticBoxSizer *sizer = nullptr;
wxBoxSizer * sizer = nullptr;
HoverLabel * hover_label = nullptr;
wxStaticText* ams_label{nullptr};
ScalableButton * btn_edit = nullptr;
ComboBox * combo_diameter = nullptr;
ComboBox * combo_flow = nullptr;
@@ -642,8 +647,10 @@ struct ExtruderGroup : StaticGroup
{
if (hover_label)
hover_label->Rescale();
if (btn_edit)
if (btn_edit){
btn_edit->msw_rescale();
btn_edit->SetMinSize(ams_label->GetSize());
}
btn_up->msw_rescale();
btn_down->msw_rescale();
combo_diameter->Rescale();
@@ -859,9 +866,9 @@ void Sidebar::priv::layout_printer(bool isBBL, bool isDual)
// double
extruder_dual_sizer = new wxBoxSizer(wxHORIZONTAL);
extruder_dual_sizer->Add(left_extruder->sizer, 1, wxEXPAND, 0);
extruder_dual_sizer->Add(left_extruder, 1, wxEXPAND, 0);
extruder_dual_sizer->AddSpacer(FromDIP(4));
extruder_dual_sizer->Add(right_extruder->sizer, 1, wxEXPAND, 0);
extruder_dual_sizer->Add(right_extruder, 1, wxEXPAND, 0);
// Filament Track Switch status icon, floated over the extruder AMS area (positioned in
// update_extruder_separator_icon). Created hidden; a click re-shows the ready/not-ready tip.
@@ -876,7 +883,9 @@ void Sidebar::priv::layout_printer(bool isBBL, bool isDual)
}
// single
extruder_single_sizer = single_extruder->sizer;
extruder_single_sizer = new wxBoxSizer(wxHORIZONTAL);
extruder_single_sizer->Add(single_extruder, 1, wxEXPAND, 0);
wxBoxSizer * extruder_sizer = new wxBoxSizer(wxVERTICAL);
extruder_sizer->Add(extruder_dual_sizer , 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(SidebarProps::ContentMargin()));
extruder_sizer->Add(extruder_single_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(SidebarProps::ContentMargin()));
@@ -1254,7 +1263,7 @@ public:
Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {
wxPaintDC dc(this);
dc.SetPen(StateColor::darkModeColorFor(wxColour("#DBDBDB"))); // ORCA match popup border color
dc.SetPen(StateColor::darkModeColorFor(wxColour("#009688"))); // ORCA match popup border color
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRoundedRectangle(0, 0, GetSize().x, GetSize().y, 0);
});
@@ -1308,29 +1317,25 @@ public:
};
ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title)
: StaticGroup(parent, wxID_ANY, wxString())
: StaticBox(parent)
{
SetFont(Label::Body_10);
SetForegroundColour(wxColour("#CECECE"));
SetBorderColor(wxColour("#EEEEEE"));
SetCornerRadius(FromDIP(PRINTER_PANEL_RADIUS)); // ORCA match radius with other boxes
ShowBadge(true);
SetTopMargin(FromDIP(7)); // ORCA
// The title lives in an interactive row inside the card (with the nozzle-count badge and its edit
// button) instead of being painted on the border by StaticGroup.
// The title lives in an interactive row inside the card (with the nozzle-count badge and its edit button)
hover_label = new HoverLabel(this, title);
hover_label->SetPosition(wxPoint(FromDIP(PRINTER_PANEL_RADIUS), 0)); // position it without putting in a sizer so it will look like title
// Nozzle
wxStaticText *label_diameter = new wxStaticText(this, wxID_ANY, _L("Diameter"));
label_diameter->SetFont(Label::Body_14);
label_diameter->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
if (index >= 0) label_diameter->SetMinSize({FromDIP(80), -1});
auto combo_diameter = new ComboBox(this, wxID_ANY, wxString(""), wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
this->combo_diameter = combo_diameter;
wxStaticText *label_flow = new wxStaticText(this, wxID_ANY, _L("Flow"));
label_flow->SetFont(Label::Body_14);
label_flow->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
if (index >= 0) label_flow->SetMinSize({FromDIP(80), -1});
combo_diameter->SetToolTip(_L("Diameter"));
// Flow
auto combo_flow = new ComboBox(this, wxID_ANY, wxString(""), wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
combo_flow->GetDropDown().SetUseContentWidth(true);
combo_flow->Bind(wxEVT_COMBOBOX, [index, combo_flow](wxCommandEvent &evt) {
@@ -1347,51 +1352,75 @@ ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title
}
});
this->combo_flow = combo_flow;
combo_flow->SetToolTip(_L("Flow"));
// AMS
wxStaticText *label_ams = new wxStaticText(this, wxID_ANY, _L("AMS"));
label_ams->SetFont(Label::Body_14);
label_ams->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
//label_ams->SetMinSize({FromDIP(70), -1});
auto ams_panel = new wxPanel(this, wxID_ANY);
ams_panel->SetBackgroundColour(*wxWHITE);
ams_label = new wxStaticText(ams_panel, wxID_ANY, _L("AMS"));
ams_label->SetFont(Label::Body_14);
ams_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
// AMS not installed message
ams_not_installed_msg = new wxStaticText(ams_panel, wxID_ANY, _L("Not installed"));
ams_not_installed_msg->SetFont(Label::Body_14);
ams_not_installed_msg->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#6B6B6B")));
if (index >= 0) {
btn_edit = new ScalableButton(this, wxID_ANY, "dot");
btn_edit = new ScalableButton(ams_panel, wxID_ANY, "edit");
btn_edit->SetMinSize(ams_label->GetSize());
btn_edit->SetBackgroundColour(extruder_group_chip_bg());
btn_edit->Hide();
btn_edit->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this, index](auto &evt) {
btn_edit->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this, index, combo_diameter](auto &evt) {
PopupWindow *window = new AMSCountPopupWindow(this, index);
auto size = GetSize();
auto pos = ClientToScreen({0, size.y + 12});
auto size = GetSize();
auto pos = ClientToScreen({0, size.y - FromDIP(8) - combo_diameter->GetSize().y});
size.SetWidth(size.GetWidth() + FromDIP(10));
window->Position(pos, {0, 0});
window->Popup();
});
auto hovered = std::make_shared<wxWindow *>();
for (wxWindow *w : std::initializer_list<wxWindow *>{this, label_diameter, combo_diameter, label_flow, combo_flow, btn_edit, label_ams}) {
w->Bind(wxEVT_ENTER_WINDOW, [w, hovered, this](wxMouseEvent &evt) { *hovered = w; btn_edit->SetBitmap_("edit"); });
w->Bind(wxEVT_LEAVE_WINDOW, [w, hovered, this](wxMouseEvent &evt) { if (*hovered == w) { btn_edit->SetBitmap_("dot"); *hovered = nullptr; } });
for (wxWindow *w : std::initializer_list<wxWindow *>{this, btn_edit, ams_not_installed_msg, ams_label, ams_panel}) {
// ORCA using CallAfter fixes crash on linux while clicking edit button
w->Bind(wxEVT_ENTER_WINDOW, [w, hovered, this](wxMouseEvent &evt) {
*hovered = w;
this->CallAfter([this]() {
btn_edit->Show();
ams_label->Hide();
hsizer_ams->Layout();
});
});
w->Bind(wxEVT_LEAVE_WINDOW, [w, hovered, this](wxMouseEvent &evt) {
if (*hovered == w) {
*hovered = nullptr;
this->CallAfter([this]() {
btn_edit->Hide();
ams_label->Show();
hsizer_ams->Layout();
});
}
});
}
}
// AMS not installed message
ams_not_installed_msg = new wxStaticText(this, wxID_ANY, _L("Not installed"));
ams_not_installed_msg->SetFont(Label::Body_14);
ams_not_installed_msg->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
// AMS group
for (size_t i = 0; i < 4; ++i) {
ams[i] = new AMSPreview(this, wxID_ANY, AMSinfo(), AMSModel::GENERIC_AMS);
ams[i] = new AMSPreview(ams_panel, wxID_ANY, AMSinfo(), AMSModel::GENERIC_AMS);
ams[i]->Close();
}
hsizer_ams = new wxBoxSizer(wxHORIZONTAL);
hsizer_ams->SetMinSize(0, ams[0]->GetMinHeight());
hsizer_ams->Add(label_ams, 0, wxALIGN_CENTER);
hsizer_ams->Add(ams_label, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(5));
if (btn_edit)
hsizer_ams->Add(btn_edit, 0, wxLEFT | wxALIGN_CENTER, FromDIP(2));
hsizer_ams->Add(ams_not_installed_msg, 0, wxALIGN_CENTER);
hsizer_ams->Add(btn_edit, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(5));
hsizer_ams->Add(ams_not_installed_msg, 1, wxALIGN_CENTER);
btn_up = new ScalableButton(this, wxID_ANY, "page_up", "", {FromDIP(14), FromDIP(14)}, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 14);
ams_panel->SetSizer(hsizer_ams);
btn_up = new ScalableButton(ams_panel, wxID_ANY, "page_up", "", {FromDIP(14), FromDIP(14)}, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 14);
btn_up->SetBackgroundColour(*wxWHITE);
btn_up->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &evt) {
if (page_cur > 0)
@@ -1399,7 +1428,7 @@ ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title
update_ams();
});
btn_up->Hide();
btn_down = new ScalableButton(this, wxID_ANY, "page_down", "", {FromDIP(14), FromDIP(14)}, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 14);
btn_down = new ScalableButton(ams_panel, wxID_ANY, "page_down", "", {FromDIP(14), FromDIP(14)}, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 14);
btn_down->SetBackgroundColour(*wxWHITE);
btn_down->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &evt) {
if (page_cur + 1 < page_num)
@@ -1408,31 +1437,24 @@ ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title
});
btn_down->Hide();
wxBoxSizer *hsizer_diameter = new wxBoxSizer(wxHORIZONTAL);
hsizer_diameter->Add(label_diameter, 0, wxALIGN_CENTER);
hsizer_diameter->Add(combo_diameter, 1, wxEXPAND);
wxBoxSizer * hsizer_nozzle = new wxBoxSizer(wxHORIZONTAL);
hsizer_nozzle->Add(label_flow, 0, wxALIGN_CENTER);
hsizer_nozzle->Add(combo_flow, 1, wxEXPAND);
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
hsizer->Add(combo_diameter, 1, wxRIGHT, FromDIP(5));
hsizer->Add(combo_flow , 1);
vsizer->AddSpacer(FromDIP(16)); // spacing for title and control
if (index < 0) {
label_ams->Hide();
ams_not_installed_msg->Hide();
wxStaticBoxSizer *vsizer = new wxStaticBoxSizer(this, wxVERTICAL);
wxBoxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
hsizer->Add(hsizer_diameter, 1, wxEXPAND | wxTOP| wxBOTTOM, FromDIP(8));
hsizer->Add(hsizer_nozzle, 1, wxEXPAND | wxALL, FromDIP(8));
hsizer->AddSpacer(FromDIP(2)); // Avoid badge
vsizer->Add(hover_label, 0, wxLEFT | wxALL, FromDIP(2));
vsizer->Add(hsizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(2));
this->sizer = vsizer;
ams_panel->Hide();
} else {
wxStaticBoxSizer *vsizer = new wxStaticBoxSizer(this, wxVERTICAL);
vsizer->Add(hover_label, 0, wxLEFT | wxALL, FromDIP(2));
vsizer->Add(hsizer_ams, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, FromDIP(2));
vsizer->Add(hsizer_diameter, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, FromDIP(2));
vsizer->Add(hsizer_nozzle, 0, wxEXPAND | wxALL, FromDIP(2));
this->sizer = vsizer;
vsizer->Add(ams_panel, 0, wxEXPAND | wxLEFT | wxRIGHT , FromDIP(5));
vsizer->AddSpacer(FromDIP(2));
}
vsizer->Add(hsizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5));
SetSizer(vsizer);
Layout();
AMSCountPopupWindow::UpdateAMSCount(index < 0 ? 0 : index, this);
}
@@ -1503,7 +1525,7 @@ void ExtruderGroup::update_ams()
}
}
sizer->Layout();
Layout();
}
void ExtruderGroup::sync_ams(MachineObject const *obj, std::vector<DevAms *> const &ams4, std::vector<DevAms *> const &ams1)
@@ -6492,6 +6514,11 @@ Search::OptionsSearcher& Sidebar::get_searcher()
return p->searcher;
}
Search::SettingsIndex& Sidebar::settings_index()
{
return p->searcher.index();
}
std::string& Sidebar::get_search_line()
{
return p->searcher.search_string();
@@ -7641,10 +7668,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
view3D_canvas->Bind(EVT_GLCANVAS_OPEN_SPEED_DIAL, [this](SimpleEvent&) {
if (this->q->is_view3D_shown())
wxGetApp().open_speed_dial();
});
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int>& evt)
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
@@ -8400,6 +8423,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
int answer_convert_from_meters = wxOK_DEFAULT;
int answer_convert_from_imperial_units = wxOK_DEFAULT;
int tolal_model_count = 0;
// Whether one of the files being loaded here carried a CAD recipe. A statement about these
// files, not about the plater — q->model() may still hold the previous project's recipe.
bool loaded_cad_recipe = false;
int progress_percent = 0;
int total_files = input_files.size();
@@ -9525,6 +9551,16 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
auto loaded_idxs = load_model_objects(model.objects, is_project_file);
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
// load_model_objects only transfers ModelObjects; carry the Model-level CAD recipe
// onto the plater model so the Design tab can rehydrate the editable feature tree on
// reopen. Assigned unconditionally on the project-replacing path so that opening a
// project without a recipe clears whatever the previous one left behind; importing a
// plain model into the open project leaves the current recipe alone.
if (is_project_file) {
q->model().cad_recipe = model.cad_recipe;
loaded_cad_recipe = !model.cad_recipe.empty();
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", finished load_model_objects");
wxString msg = wxString::Format(_L("Loading file: %s"), from_path(real_filename));
dlg_cont = dlg.Update(progress_percent, msg);
@@ -9741,7 +9777,12 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// q->model().stl_design_country = "";
//}
if (tolal_model_count <= 0 && !q->m_exported_file) {
// A CAD project legitimately carries no mesh: the model lives in the feature tree until it is
// committed to the plate. Warning "no geometry data" for one is false, and it is the LAST thing
// a user sees after opening a design they spent an hour on — it reads as "your work is gone"
// when the recipe has in fact just been loaded and the Design tab will rehydrate it. Count a
// recipe that came from THESE files as geometry.
if (tolal_model_count <= 0 && !loaded_cad_recipe && !q->m_exported_file) {
dlg.Hide();
if (!is_user_cancel) {
MessageDialog msg(wxGetApp().mainframe, _L("The file does not contain any geometry data."), _L("Warning"), wxYES | wxICON_WARNING);
@@ -10288,6 +10329,16 @@ void Plater::priv::reset(bool apply_presets_change)
// Stop and reset the Print content.
this->background_process.reset();
model.clear_objects();
// clear_objects() only drops the ModelObjects; the CAD recipe is Model-level state and would
// otherwise be written into every project saved for the rest of the session.
model.cad_recipe.clear();
#ifdef SLIC3R_CAD
// Same reason, one level up: the Design tab keeps the editable document, not the Model, so
// clearing the recipe alone leaves the tab showing the previous project's feature tree —
// and its next edit syncs that tree straight back into the new project.
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
wxGetApp().mainframe->m_design_panel->clear_document();
#endif
assemble_view->get_canvas3d()->reset_explosion_ratio();
update();
@@ -12873,17 +12924,8 @@ void Plater::priv::on_action_add(SimpleEvent&)
//BBS: add plate from toolbar
void Plater::priv::on_action_add_plate(SimpleEvent&)
{
if (q != nullptr) {
take_snapshot("add partplate");
this->partplate_list.create_plate();
int new_plate = this->partplate_list.get_plate_count() - 1;
this->partplate_list.select_plate(new_plate);
update();
// BBS set default view
//q->get_camera().select_view("topfront");
q->get_camera().requires_zoom_to_plate = REQUIRES_ZOOM_TO_ALL_PLATE;
}
if (q != nullptr)
q->add_plate();
}
//BBS: remove plate from toolbar
@@ -13790,6 +13832,14 @@ void Plater::priv::unbind_canvas_event_handlers()
if (assemble_view != nullptr)
assemble_view->get_canvas3d()->unbind_event_handlers();
#ifdef SLIC3R_CAD
// The Design tab's viewport is a fourth GLCanvas3D on the same shared GL context, owned by
// MainFrame rather than by us — same reach as reset() uses for clear_document(). Null until
// the tab has been opened once, so most sessions skip it.
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
wxGetApp().mainframe->m_design_panel->unbind_canvas_event_handlers();
#endif
}
void Plater::priv::reset_canvas_volumes()
@@ -13799,6 +13849,11 @@ void Plater::priv::reset_canvas_volumes()
if (preview != nullptr)
preview->get_canvas3d()->reset_volumes();
#ifdef SLIC3R_CAD
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
wxGetApp().mainframe->m_design_panel->reset_canvas_volumes();
#endif
}
bool Plater::priv::check_ams_status_impl(bool is_slice_all)
@@ -15750,8 +15805,12 @@ bool Plater::up_to_date(bool saved, bool backup)
Slic3r::clear_other_changes(backup);
return p->up_to_date(saved, backup);
}
return p->model.objects.empty() || (p->up_to_date(saved, backup) &&
!Slic3r::has_other_changes(backup));
// A Design-tab project is object-less until it is committed to the plate, but its feature
// tree is real work: treating it as an empty project skipped both the autosave and the
// "unsaved changes" prompt, so quitting threw it away without asking. Non-CAD projects
// never carry a recipe, so the empty-project shortcut is unchanged for them.
return (p->model.objects.empty() && p->model.cad_recipe.empty()) ||
(p->up_to_date(saved, backup) && !Slic3r::has_other_changes(backup));
}
bool Plater::add_model(bool imperial_units, std::string fname)
@@ -21943,6 +22002,24 @@ int Plater::select_plate_by_hover_id(int hover_id, bool right_click, bool isModi
return ret;
}
//BBS: add an empty plate and switch to it (mirrors the toolbar's Add Plate).
int Plater::add_plate()
{
if (!p->can_add_plate())
return -1;
take_snapshot("add partplate");
int new_plate = p->partplate_list.create_plate();
if (new_plate < 0)
return new_plate;
p->partplate_list.select_plate(new_plate);
update();
// BBS set default view
//get_camera().select_view("topfront");
p->camera.requires_zoom_to_plate = REQUIRES_ZOOM_TO_ALL_PLATE;
return new_plate;
}
int Plater::duplicate_plate(int plate_index)
{
int index = plate_index, ret;