Merge branch 'main' into feat/speed-dial-fixes

This commit is contained in:
Lam Wei Lun
2026-09-24 10:42:19 +08:00
105 changed files with 4862 additions and 1026 deletions
+8
View File
@@ -286,6 +286,8 @@ set(SLIC3R_GUI_SOURCES
GUI/FilamentMapDialog.hpp
GUI/IconManager.cpp
GUI/IconManager.hpp
GUI/IdleScheduler.cpp
GUI/IdleScheduler.hpp
GUI/ImageGrid.cpp
GUI/ImageGrid.h
GUI/ImGuiWrapper.cpp
@@ -347,6 +349,10 @@ set(SLIC3R_GUI_SOURCES
GUI/KBShortcutsDialog.hpp
GUI/KeyChord.cpp
GUI/KeyChord.hpp
GUI/Lazy.cpp
GUI/Lazy.hpp
GUI/LazyPage.cpp
GUI/LazyPage.hpp
GUI/LibVGCode/LibVGCodeWrapper.hpp
GUI/LibVGCode/LibVGCodeWrapper.cpp
GUI/LinuxDisplayBackend.cpp
@@ -433,6 +439,7 @@ set(SLIC3R_GUI_SOURCES
GUI/Plater.hpp
GUI/PlateSettingsDialog.cpp
GUI/PlateSettingsDialog.hpp
GUI/PrebuildQueue.hpp
GUI/Preferences.cpp
GUI/Preferences.hpp
GUI/PresetBundleDialog.cpp
@@ -512,6 +519,7 @@ set(SLIC3R_GUI_SOURCES
GUI/SliceInfoPanel.hpp
GUI/SlicingProgressNotification.cpp
GUI/SlicingProgressNotification.hpp
GUI/StagedBuild.hpp
GUI/StatusPanel.cpp
GUI/StatusPanel.hpp
GUI/StepMeshDialog.cpp
+21 -11
View File
@@ -863,17 +863,27 @@ void AuxiliaryPanel::init_tabpanel()
m_tabpanel->SetBackgroundColour(wxColour("#FEFFFF"));
m_tabpanel->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, [](wxBookCtrlEvent &e) { /* Event handling */ });
m_designer_panel = new DesignerPanel(m_tabpanel, AuxiliaryFolderType::DESIGNER);
m_pictures_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::MODEL_PICTURE);
m_bill_of_materials_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::BILL_OF_MATERIALS);
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), false);
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), false);
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
add_build_step([this] {
m_designer_panel = new DesignerPanel(m_tabpanel, AuxiliaryFolderType::DESIGNER);
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
});
add_build_step([this] {
m_pictures_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::MODEL_PICTURE);
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), false);
});
add_build_step([this] {
m_bill_of_materials_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::BILL_OF_MATERIALS);
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), false);
});
add_build_step([this] {
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
});
add_build_step([this] {
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
Layout();
});
}
wxWindow *AuxiliaryPanel::create_side_tools()
+2 -1
View File
@@ -46,6 +46,7 @@
#include "slic3r/GUI/UpgradePanel.hpp"
#include "slic3r/GUI/AmsWidgets.hpp"
#include "Widgets/SideTools.hpp"
#include "StagedBuild.hpp"
#define AUFILE_GREY700 wxColour(107, 107, 107)
#define AUFILE_GREY500 wxColour(158, 158, 158)
@@ -194,7 +195,7 @@ public:
};
class AuxiliaryPanel : public wxPanel
class AuxiliaryPanel : public wxPanel, public StagedBuild
{
private:
Tabbook *m_tabpanel = {nullptr};
+2 -1
View File
@@ -12,6 +12,7 @@
#include "libslic3r/CAD/CadDocument.hpp"
#include "slic3r/GUI/CAD/DesignInteraction.hpp" // CadLevel: what one Esc press means
#include "slic3r/GUI/Lazy.hpp"
class ComboBox; // Orca dropdown (Widgets/ComboBox.hpp) — replaces wxChoice everywhere here
class StaticBox; // Orca rounded card frame (Widgets/StaticBox.hpp)
@@ -46,7 +47,7 @@ class DesignCanvas;
// Design (CAD) tab: a sketch-first, Onshape-style form-driven CAD panel.
// Sketch and Extrude are independent tools: the user creates a Sketch first,
// then selects it and Extrudes to produce a solid.
class DesignPanel : public wxPanel
class DesignPanel : public wxPanel, public LazyInstance<DesignPanel>
{
public:
explicit DesignPanel(wxWindow* parent);
+1 -2
View File
@@ -1989,11 +1989,10 @@ json action_set_feature_expr(DesignPanel* panel, const json& params)
// Dispatch one parsed request ON THE MAIN THREAD. Returns a JSON-RPC reply string.
std::string handle_on_main(const std::string& method, const json& params, const json& id)
{
MainFrame* mf = wxGetApp().mainframe;
// The panel is built on first use, and in a headless session nobody clicks the tab that
// would build it -- so build it here rather than refusing. Safe: this runs on the main
// thread (see the CallAfter that dispatches us).
DesignPanel* panel = mf ? mf->ensure_design_panel() : nullptr;
DesignPanel* panel = DesignPanel::ensure();
if (!panel)
return rpc_error(id, -32001, "Design panel not ready");
+6 -3
View File
@@ -491,14 +491,15 @@ void CalibrationPanel::init_tabpanel() {
selected);
}
for (int i = 0; i < (int)CALI_MODE_COUNT; i++)
add_build_steps_of(*m_cali_panels[i]);
// ORCA use standard paddings and keep arrow icon for consistent look between sidebars
//for (int i = 0; i < (int)CALI_MODE_COUNT; i++)
// m_tabpanel->SetPageImage(i, "");
//auto padding_size = m_tabpanel->GetBtnsListCtrl()->GetPaddingSize(0);
//m_tabpanel->GetBtnsListCtrl()->SetPaddingSize({ FromDIP(15), padding_size.y });
m_initialized = true;
}
void CalibrationPanel::init_timer()
@@ -534,6 +535,8 @@ void CalibrationPanel::update_print_error_info(int code, std::string msg, std::s
}
void CalibrationPanel::update_all() {
// Every wizard's pages exist once the last build step has run.
if (!built()) return;
NetworkAgent* m_agent = wxGetApp().getAgent();
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
@@ -597,7 +600,7 @@ void CalibrationPanel::update_all() {
void CalibrationPanel::show_status(int status)
{
if (!m_initialized) return;
if (!built()) return;
if (last_status == status)return;
last_status = status;
+2 -2
View File
@@ -3,6 +3,7 @@
#include "CalibrationWizard.hpp"
#include "Tabbook.hpp"
#include "Lazy.hpp"
//#include "Widgets/SideTools.hpp"
namespace Slic3r { namespace GUI {
@@ -88,7 +89,7 @@ private:
};
class CalibrationPanel : public wxPanel
class CalibrationPanel : public wxPanel, public StagedBuild, public LazyInstance<CalibrationPanel>
{
public:
CalibrationPanel(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
@@ -109,7 +110,6 @@ protected:
int last_status;
bool m_initialized { false };
std::string last_conn_type = "undedefined";
MachineObject* obj{ nullptr };
MachineObject* last_obj { nullptr };
+56 -65
View File
@@ -130,6 +130,15 @@ CalibrationWizard::~CalibrationWizard()
;
}
void CalibrationWizard::add_page_step(CalibrationWizardPageStep*& step, std::function<CalibrationWizardPage*()> make)
{
add_build_step([this, &step, make = std::move(make)] {
step = new CalibrationWizardPageStep(make());
m_all_pages_sizer->Add(step->page, 1, wxEXPAND | wxALL, FromDIP(25));
step->page->Hide();
});
}
void CalibrationWizard::on_cali_job_finished(wxCommandEvent& event)
{
this->on_cali_job_finished(event.GetString());
@@ -520,33 +529,28 @@ void PressureAdvanceWizard::on_cali_job_finished(wxString evt_data)
void PressureAdvanceWizard::create_pages()
{
start_step = new CalibrationWizardPageStep(new CalibrationPAStartPage(m_scrolledWindow));
preset_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, m_mode, false));
cali_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode));
save_step = new CalibrationWizardPageStep(new CalibrationPASavePage(m_scrolledWindow));
add_page_step(start_step, [this] { return new CalibrationPAStartPage(m_scrolledWindow); });
add_page_step(preset_step, [this] { return new CalibrationPresetPage(m_scrolledWindow, m_mode, false); });
add_page_step(cali_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode); });
add_page_step(save_step, [this] { return new CalibrationPASavePage(m_scrolledWindow); });
m_all_pages_sizer->Add(start_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(preset_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(cali_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
add_build_step([this] {
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(cali_step);
m_page_steps.push_back(save_step);
for (int i = 0; i < m_page_steps.size() -1; i++) {
m_page_steps[i]->chain(m_page_steps[i+1]);
}
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(cali_step);
m_page_steps.push_back(save_step);
for (int i = 0; i < m_page_steps.size(); i++) {
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &PressureAdvanceWizard::on_cali_action, this);
}
for (int i = 0; i < m_page_steps.size() -1; i++) {
m_page_steps[i]->chain(m_page_steps[i+1]);
}
for (int i = 0; i < m_page_steps.size(); i++) {
m_page_steps[i]->page->Hide();
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &PressureAdvanceWizard::on_cali_action, this);
}
if (!m_page_steps.empty())
show_step(m_page_steps.front());
if (!m_page_steps.empty())
show_step(m_page_steps.front());
});
}
void PressureAdvanceWizard::on_cali_action(wxCommandEvent& evt)
@@ -1053,59 +1057,46 @@ FlowRateWizard::FlowRateWizard(wxWindow* parent, wxWindowID id, const wxPoint& p
void FlowRateWizard::create_pages()
{
start_step = new CalibrationWizardPageStep(new CalibrationFlowRateStartPage(m_scrolledWindow));
preset_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, m_mode, false));
add_page_step(start_step, [this] { return new CalibrationFlowRateStartPage(m_scrolledWindow); });
add_page_step(preset_step, [this] { return new CalibrationPresetPage(m_scrolledWindow, m_mode, false); });
// manual
cali_coarse_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_CALI));
coarse_save_step = new CalibrationWizardPageStep(new CalibrationFlowCoarseSavePage(m_scrolledWindow));
cali_fine_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_FINE_CALI));
fine_save_step = new CalibrationWizardPageStep(new CalibrationFlowFineSavePage(m_scrolledWindow));
add_page_step(cali_coarse_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_CALI); });
add_page_step(coarse_save_step, [this] { return new CalibrationFlowCoarseSavePage(m_scrolledWindow); });
add_page_step(cali_fine_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_FINE_CALI); });
add_page_step(fine_save_step, [this] { return new CalibrationFlowFineSavePage(m_scrolledWindow); });
// auto
cali_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode));
save_step = new CalibrationWizardPageStep(new CalibrationFlowX1SavePage(m_scrolledWindow));
add_page_step(cali_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode); });
add_page_step(save_step, [this] { return new CalibrationFlowX1SavePage(m_scrolledWindow); });
m_all_pages_sizer->Add(start_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(preset_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(cali_coarse_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(coarse_save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(cali_fine_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(fine_save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
add_build_step([this] {
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(cali_coarse_step);
m_page_steps.push_back(coarse_save_step);
m_page_steps.push_back(cali_fine_step);
m_page_steps.push_back(fine_save_step);
m_all_pages_sizer->Add(cali_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
//m_page_steps.push_back(cali_step);
//m_page_steps.push_back(save_step);
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(cali_coarse_step);
m_page_steps.push_back(coarse_save_step);
m_page_steps.push_back(cali_fine_step);
m_page_steps.push_back(fine_save_step);
for (int i = 0; i < m_page_steps.size() - 1; i++) {
m_page_steps[i]->chain(m_page_steps[i + 1]);
}
//m_page_steps.push_back(cali_step);
//m_page_steps.push_back(save_step);
for (int i = 0; i < m_page_steps.size(); i++) {
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
}
for (int i = 0; i < m_page_steps.size() - 1; i++) {
m_page_steps[i]->chain(m_page_steps[i + 1]);
}
cali_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
save_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
// hide all pages
cali_step->page->Hide();
save_step->page->Hide();
for (int i = 0; i < m_page_steps.size(); i++) {
m_page_steps[i]->page->Hide();
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
}
if (!m_page_steps.empty())
show_step(m_page_steps.front());
cali_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
save_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
if (!m_page_steps.empty())
show_step(m_page_steps.front());
set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
});
}
void FlowRateWizard::on_cali_action(wxCommandEvent& evt)
+5 -1
View File
@@ -9,6 +9,7 @@
#include "CalibrationWizardPresetPage.hpp"
#include "CalibrationWizardCaliPage.hpp"
#include "CalibrationWizardSavePage.hpp"
#include "StagedBuild.hpp"
namespace Slic3r { namespace GUI {
@@ -36,7 +37,7 @@ struct ConfigIndexValue
int index{0};
};
class CalibrationWizard : public wxPanel {
class CalibrationWizard : public wxPanel, public StagedBuild {
public:
CalibrationWizard(wxWindow* parent, CalibMode mode,
wxWindowID id = wxID_ANY,
@@ -79,6 +80,9 @@ public:
protected:
void on_cali_go_home();
// Queues a page as a build step, created hidden and added to the pages sizer.
void add_page_step(CalibrationWizardPageStep*& step, std::function<CalibrationWizardPage*()> make);
protected:
/* wx widgets*/
wxScrolledWindow* m_scrolledWindow;
+3 -1
View File
@@ -192,8 +192,10 @@ void CalibrationCaliPage::update(MachineObject* obj)
set_cali_img();
}
// A calibration can run before the Device tab is ever opened, and only its status
// panel shows a print error.
if (obj->print_error > 0) {
StatusPanel* status_panel = Slic3r::GUI::wxGetApp().mainframe->m_monitor->get_status_panel();
StatusPanel* status_panel = MonitorPanel::ensure()->get_status_panel();
status_panel->obj = obj;
status_panel->update_error_message();
}
+6 -3
View File
@@ -538,7 +538,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
break;
}
case DeviceErrorDialog::CHECK_ASSISTANT: {
wxGetApp().mainframe->m_monitor->jump_to_HMS(); // go to assistant page
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_HMS(); // go to assistant page
break;
}
case DeviceErrorDialog::FILAMENT_EXTRUDED: {
@@ -568,7 +569,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
}
case DeviceErrorDialog::JUMP_TO_LIVEVIEW: {
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_LiveView();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_LiveView();
break;
}
case DeviceErrorDialog::NO_REMINDER_NEXT_TIME: {
@@ -618,7 +620,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
}
case DeviceErrorDialog::OK_JUMP_RACK: {
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_Rack();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_Rack();
break;
}
case DeviceErrorDialog::ABORT: {
@@ -934,7 +934,8 @@ void wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus(wxMouseEvent& evt)
dlg.AddButton(wxID_OK,_L("Jump to the upgrade page"), true);
if (dlg.ShowModal() == wxID_OK) {
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_Upgrade();
};
}
}
@@ -344,7 +344,8 @@ void wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick(wxMouseEvent& event)
if (dlg.ShowModal() == wxID_OK)
{
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_Upgrade();
wxCommandEvent evt(wxEVT_NOZZLE_JUMP_UPGRADE, GetId());
evt.SetEventObject(this);
+210 -51
View File
@@ -20,6 +20,7 @@
#include "libslic3r/AppConfig.hpp"
#include "3DScene.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "CameraUtils.hpp"
#include "GLShader.hpp"
#include "GUI.hpp"
#include "Tab.hpp"
@@ -117,6 +118,36 @@ void GLCanvas3D::load_render_colors()
namespace Slic3r {
namespace GUI {
static void pan_camera(Camera& camera, const Vec2d& screen_delta, const Vec3d& anchor)
{
// Orca: Derive world-units-per-pixel from the projection which produced the visible frame.
// Perspective additionally scales with the eye-space depth of the point being dragged.
const auto& viewport = camera.get_viewport();
const auto& projection = camera.get_projection_matrix().matrix();
const double depth_scale = camera.get_type() == Camera::EType::Perspective ?
(anchor - camera.get_position()).dot(camera.get_dir_forward()) : 1.0;
const double projection_x = projection(0, 0) * viewport[2];
const double projection_y = projection(1, 1) * viewport[3];
// Orca: X/Y projection coefficients already include zoom. Using them directly avoids a
// project/unproject round-trip through window depth, whose precision depends on the scene frustum.
if (viewport[2] > 0 && viewport[3] > 0 && anchor.allFinite() && depth_scale > EPSILON &&
std::abs(projection_x) > EPSILON && std::abs(projection_y) > EPSILON) {
const Vec3d displacement = 2.0 * depth_scale *
(screen_delta.y() / projection_y * camera.get_dir_up() -
screen_delta.x() / projection_x * camera.get_dir_right());
if (displacement.allFinite()) {
camera.translate(displacement);
return;
}
}
// Orca: Preserve the former target-plane behavior if the projection or anchor is invalid.
// Screen Y grows downward, and the camera moves opposite to the drag.
camera.translate(camera.get_inv_zoom() *
(screen_delta.y() * camera.get_dir_up() - screen_delta.x() * camera.get_dir_right()));
}
#ifdef __WXGTK3__
// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support.
RetinaHelper::RetinaHelper(wxWindow* window) : m_window(window), m_self(nullptr) {}
@@ -2206,15 +2237,14 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
_render_background();
//BBS add partplater rendering logic
bool only_current = false, only_body = false, no_partplate = false;
bool only_current = false, only_body = false;
const bool show_bed = is_bed_visible();
bool show_grid = true;
GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
if (!m_main_toolbar.is_enabled()) {
//only_body = true;
only_current = true;
}
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmSegmentation) || (gizmo_type == GLGizmosManager::FuzzySkin))
no_partplate = true;
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
show_grid = false;
if (m_axes_at_bed_center)
@@ -2228,11 +2258,11 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
if (m_canvas_type == ECanvasType::CanvasView3D) {
// m_show_bed gates the plate list too: hiding the bed but leaving its grid and outline
// floating would read as a rendering fault rather than a deliberate view option.
if (!no_partplate && m_show_bed)
if (show_bed)
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
if (!no_partplate && m_show_bed) //BBS: add outline logic
if (show_bed) //BBS: add outline logic
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
if (m_axes_at_bed_center && m_show_bed && !no_partplate)
if (m_axes_at_bed_center && show_bed)
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
@@ -3281,6 +3311,9 @@ void GLCanvas3D::unbind_event_handlers()
m_canvas->Unbind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
m_canvas->Unbind(wxEVT_GESTURE_ZOOM, &GLCanvas3D::on_gesture, this);
m_canvas->Unbind(wxEVT_GESTURE_ROTATE, &GLCanvas3D::on_gesture, this);
#if __WXOSX__
initGestures(m_canvas->GetHandle(), nullptr);
#endif
}
}
@@ -4029,27 +4062,38 @@ void GLCanvas3D::on_gesture(wxGestureEvent &evt)
auto & camera = wxGetApp().plater()->get_camera();
if (evt.GetEventType() == wxEVT_GESTURE_PAN) {
auto p = evt.GetPosition();
// Orca: Gesture coordinates must use framebuffer pixels, and one stable world-space
// anchor must be retained for the complete gesture to prevent perspective drift.
const auto p = evt.GetPosition();
auto d = static_cast<wxPanGestureEvent&>(evt).GetDelta();
float z = 0;
const Vec3d &p2 = _mouse_to_3d({p.x, p.y}, &z);
const Vec3d &p1 = _mouse_to_3d({p.x - d.x, p.y - d.y}, &z);
camera.set_target(camera.get_target() + p1 - p2);
Vec2d screen_position(p.x, p.y);
Vec2d screen_delta(d.x, d.y);
apply_retina_scale(screen_position);
apply_retina_scale(screen_delta);
if (evt.IsGestureStart() || !m_gesture_pan_anchor.has_value())
m_gesture_pan_anchor = get_camera_pan_anchor(camera, ECameraNavigationType::Gesture,
screen_position - screen_delta);
pan_camera(camera, screen_delta, *m_gesture_pan_anchor);
if (evt.IsGestureEnd())
m_gesture_pan_anchor.reset();
} else if (evt.GetEventType() == wxEVT_GESTURE_ZOOM) {
static float zoom_start = 1;
if (evt.IsGestureStart())
zoom_start = camera.get_zoom();
camera.set_zoom(zoom_start * static_cast<wxZoomGestureEvent&>(evt).GetZoomFactor());
} else if (evt.GetEventType() == wxEVT_GESTURE_ROTATE) {
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
// Orca: Rotation starts a different navigation operation, so a previous pan anchor
// must not be reused; rotation and pan fallbacks share the same navigation pivot.
m_gesture_pan_anchor.reset();
bool rotate_limit = current_printer_technology() != ptSLA;
static double last_rotate = 0;
if (evt.IsGestureStart())
last_rotate = 0;
auto rotate = static_cast<wxRotateGestureEvent&>(evt).GetRotationAngle() - last_rotate;
last_rotate += rotate;
if (plate)
camera.rotate_on_sphere_with_target(-rotate, 0, rotate_limit, plate->get_bounding_box().center());
const std::optional<Vec3d> rotate_target = get_camera_orbit_target(ECameraNavigationType::Gesture);
if (rotate_target.has_value())
camera.rotate_on_sphere_with_target(-rotate, 0, rotate_limit, *rotate_target);
else
camera.rotate_on_sphere(-rotate, 0, rotate_limit);
camera.auto_type(Camera::EType::Perspective);
@@ -4339,6 +4383,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
post_event(SimpleEvent(EVT_GLCANVAS_SWITCH_TO_GLOBAL));
}
else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) {
// Orca: Retain the click position even if the first motion event crosses a surface edge.
m_mouse.set_start_position_2D_as_invalid();
m_mouse.drag.start_position_2D = pos;
//BBS: add orient deactivate logic
if (!m_gizmos.on_mouse(evt)) {
if (_deactivate_arrange_menu() || _deactivate_orient_menu())
@@ -4532,6 +4580,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
// do not process the dragging if the left mouse was set down in another canvas
else if (is_camera_rotate(evt, button_mappings)) {
// Orca: Rotation and panning use different drag coordinates and cached anchors.
// Clear the pan state before processing rotation or switching buttons mid-drag.
m_mouse.set_start_position_2D_as_invalid();
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
@@ -4549,12 +4600,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (this->m_canvas_type == ECanvasType::CanvasAssembleView || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports ||
m_gizmos.get_current_type() == GLGizmosManager::Seam || m_gizmos.get_current_type() == GLGizmosManager::MmSegmentation ||
m_gizmos.get_current_type() == GLGizmosManager::FuzzySkin) {
Vec3d rotate_target = Vec3d::Zero();
if (!m_selection.is_empty())
rotate_target = m_selection.get_bounding_box().center();
// Orca: Reuse the centralized pivot policy for scene-oriented tools.
const std::optional<Vec3d> rotate_target = get_camera_orbit_target(ECameraNavigationType::Mouse);
if (rotate_target.has_value())
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), false, *rotate_target);
else
rotate_target = volumes_bounding_box().center();
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), false, rotate_target);
camera.rotate_on_sphere(rot.x(), rot.y(), false);
}
else {
if (wxGetApp().app_config->get_bool("use_free_camera"))
@@ -4578,28 +4629,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, m_rotation_center);
} else {
Vec3d rotate_target = Vec3d::Zero();
if (m_canvas_type == ECanvasType::CanvasPreview) {
PartPlate *plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
if (plate)
rotate_target = plate->get_bounding_box().center();
}
else {
if (!m_selection.is_empty())
rotate_target = m_selection.get_bounding_box().center();
else {
// Rotate around the center of objects on current plate
auto bbox = volumes_bounding_box(true);
if (!bbox.defined) {
// Rotate around current plate center if current plate is empty
bbox = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_bounding_box();
}
rotate_target = bbox.center();
}
}
if (!rotate_target.isZero())
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, rotate_target);
// Orca: Keep regular mouse orbit and perspective-pan fallback centered
// on the same selection, active-plate, or scene reference.
const std::optional<Vec3d> rotate_target = get_camera_orbit_target(ECameraNavigationType::Mouse);
if (rotate_target.has_value())
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, *rotate_target);
else
camera.rotate_on_sphere(rot.x(), rot.y(), rotate_limit);
}
@@ -4615,16 +4649,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
}
else if (is_camera_pan(evt, button_mappings)) {
// Orca: Pan uses screen coordinates and must not inherit the rotation start point.
m_mouse.set_start_position_3D_as_invalid();
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
// if dragging with right button or if button functions swapped and dragging with left button over blank area then pan
if (m_mouse.is_start_position_2D_defined()) {
// get point in model space at Z = 0
float z = 0.0f;
const Vec3d& cur_pos = _mouse_to_3d(pos, &z);
Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
Camera& camera = wxGetApp().plater()->get_camera();
if (this->m_canvas_type != ECanvasType::CanvasAssembleView) {
// Orca: Use a constrained camera when navigating the 3D scene with a regular mouse, if the free camera is not selected
@@ -4636,7 +4668,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
camera.recover_from_free_camera();
}
camera.set_target(camera.get_target() + orig - cur_pos);
// Orca: Cache the surface under the initial click and apply every incremental
// cursor delta at that depth, so perspective zoom and camera angle stay exact.
const Vec2d screen_delta =
pos.cast<double>() - m_mouse.drag.start_position_2D.cast<double>();
if (!m_mouse.drag.camera_pan_anchor.has_value())
m_mouse.drag.camera_pan_anchor = get_camera_pan_anchor(camera, ECameraNavigationType::Mouse,
m_mouse.drag.start_position_2D.cast<double>());
pan_camera(camera, screen_delta, *m_mouse.drag.camera_pan_anchor);
m_dirty = true;
m_mouse.ignore_right_up = true; // will be reset on button up event even if not right button is pressed
}
@@ -7329,11 +7368,8 @@ void GLCanvas3D::_picking_pass()
m_hover_volume_idxs.clear();
m_hover_plate_idxs.clear();
// Orca: ignore clipping plane if not applying
GLGizmoBase *current_gizmo = m_gizmos.get_current();
const ClippingPlane clipping_plane = ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? m_gizmos.get_clipping_plane() :
ClippingPlane::ClipsNothing())
.inverted_normal();
// Orca: Picking and camera navigation must interpret the active gizmo clipping plane identically.
const ClippingPlane clipping_plane = get_raycaster_clipping_plane();
const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(m_mouse.position, wxGetApp().plater()->get_camera(), &clipping_plane);
if (hit.is_valid()) {
switch (hit.type)
@@ -10627,6 +10663,129 @@ Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
return mouse_ray(mouse_pos).intersect_plane(0.0);
}
ClippingPlane GLCanvas3D::get_raycaster_clipping_plane() const
{
// Orca: Ignore the gizmo clipping plane when the active tool does not apply it, and
// invert the result into the convention expected by SceneRaycaster.
GLGizmoBase* current_gizmo = m_gizmos.get_current();
return ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? m_gizmos.get_clipping_plane() :
ClippingPlane::ClipsNothing())
.inverted_normal();
}
std::optional<Vec3d> GLCanvas3D::get_camera_orbit_target(ECameraNavigationType navigation_type) const
{
// Orca: Centralize the pre-existing pivot rules so orbiting and pan fallback cannot
// choose different reference depths for the same canvas and active tool.
PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
if (navigation_type == ECameraNavigationType::Gesture)
return current_plate == nullptr ? std::nullopt :
std::make_optional(current_plate->get_bounding_box().center());
const GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
const bool use_scene_target = m_canvas_type == ECanvasType::CanvasAssembleView ||
gizmo_type == GLGizmosManager::FdmSupports || gizmo_type == GLGizmosManager::Seam ||
gizmo_type == GLGizmosManager::MmSegmentation || gizmo_type == GLGizmosManager::FuzzySkin;
if (use_scene_target) {
if (!m_selection.is_empty())
return m_selection.get_bounding_box().center();
// Orca: Preserve the world-origin fallback used by orbit in an empty scene.
return volumes_bounding_box().center();
}
// Orca: Free-camera rotation uses Camera::m_target rather than a plate or selection pivot.
if (wxGetApp().app_config->get_bool("use_free_camera"))
return std::nullopt;
Vec3d target = Vec3d::Zero();
if (m_canvas_type == ECanvasType::CanvasPreview) {
if (current_plate != nullptr)
target = current_plate->get_bounding_box().center();
} else if (!m_selection.is_empty()) {
target = m_selection.get_bounding_box().center();
} else {
// Orca: Match regular mouse orbit: objects on the active plate, then the plate itself.
BoundingBoxf3 bbox = volumes_bounding_box(true);
if (!bbox.defined && current_plate != nullptr)
bbox = current_plate->get_bounding_box();
if (bbox.defined)
target = bbox.center();
}
// Orca: Preserve the existing zero sentinel used by regular mouse orbit.
return target.isZero() ? std::nullopt : std::make_optional(target);
}
bool GLCanvas3D::is_bed_visible() const
{
if (m_canvas_type == ECanvasType::CanvasPreview)
return m_render_preview;
if (m_canvas_type != ECanvasType::CanvasView3D || !m_show_bed)
return false;
if (!m_main_toolbar.is_enabled())
return true;
const auto type = m_gizmos.get_current_type();
return type != GLGizmosManager::FdmSupports && type != GLGizmosManager::Seam &&
type != GLGizmosManager::MmSegmentation && type != GLGizmosManager::FuzzySkin;
}
Vec3d GLCanvas3D::get_camera_pan_anchor(Camera& camera, ECameraNavigationType navigation_type,
const Vec2d& screen_position) const
{
// Orthographic panning has the same scale at every depth, so no raycast is needed.
if (camera.get_type() != Camera::EType::Perspective)
return camera.get_target();
// Orca: Reject non-finite anchors and points behind the camera before their depth is
// allowed to scale a perspective pan.
const Vec3d camera_position = camera.get_position();
const Vec3d camera_forward = camera.get_dir_forward();
const auto is_valid_anchor = [&camera_position, &camera_forward](const Vec3d& anchor) {
return anchor.allFinite() && (anchor - camera_position).dot(camera_forward) > EPSILON;
};
// Orca: Prefer the nearest visible bed or volume surface and exclude gizmos and
// selected-volume picking priority from navigation depth selection.
const ClippingPlane clipping_plane = get_raycaster_clipping_plane();
const bool bed_visible = is_bed_visible();
const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(screen_position, camera, &clipping_plane,
bed_visible ? SceneRaycaster::EHitMode::SceneOnly : SceneRaycaster::EHitMode::VolumesOnly);
if (hit.is_valid()) {
const Vec3d hit_position = hit.position.cast<double>();
if (is_valid_anchor(hit_position))
return hit_position;
}
// Orca: When the cursor is just outside the visible plate, use the point under it on the active
// plate plane. Using the plate center here would give it a different perspective depth.
PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
// Orca: An almost edge-on perspective makes intersection depth extremely sensitive to
// the cursor's vertical position. Use the stable orbit depth around horizontal views.
static constexpr double min_plate_plane_forward_z = 0.05;
if (bed_visible && std::abs(camera_forward.z()) >= min_plate_plane_forward_z &&
current_plate != nullptr && current_plate->get_bounding_box().defined) {
Vec3d ray_origin;
Vec3d ray_direction;
CameraUtils::ray_from_screen_pos(camera, screen_position, ray_origin, ray_direction);
const double z_direction = ray_direction.z();
if (ray_origin.allFinite() && ray_direction.allFinite() && std::abs(z_direction) > EPSILON) {
const double plate_z = current_plate->get_bounding_box().center().z();
const double distance = (plate_z - ray_origin.z()) / z_direction;
const Vec3d plate_position = ray_origin + distance * ray_direction;
const double eye_depth = (plate_position - camera_position).dot(camera_forward);
if (distance >= 0.0 && is_valid_anchor(plate_position) &&
eye_depth >= camera.get_near_z() && eye_depth <= camera.get_far_z())
return plate_position;
}
}
// Orca: Near-horizontal rays and points outside the scene depth use the orbit reference point.
const std::optional<Vec3d> orbit_target = get_camera_orbit_target(navigation_type);
return orbit_target.has_value() && is_valid_anchor(*orbit_target) ? *orbit_target : camera.get_target();
}
// While it looks like we can call
// this->reload_scene(true, true)
// the two functions are quite different:
+24 -1
View File
@@ -338,6 +338,8 @@ class GLCanvas3D
int move_volume_idx{ -1 };
bool move_requires_threshold{ false };
Point move_start_threshold_position_2D{ Invalid_2D_Point };
// Orca: Keep the world-space point selected at the start of a mouse pan.
std::optional<Vec3d> camera_pan_anchor;
};
bool dragging{ false };
@@ -346,7 +348,12 @@ class GLCanvas3D
Drag drag;
bool ignore_right_up;
void set_start_position_2D_as_invalid() { drag.start_position_2D = Drag::Invalid_2D_Point; }
// Orca: The screen-space start and world-space anchor describe the same pan session.
// Invalidating one must invalidate the other so a new drag cannot reuse stale depth.
void set_start_position_2D_as_invalid() {
drag.start_position_2D = Drag::Invalid_2D_Point;
drag.camera_pan_anchor.reset();
}
void set_start_position_3D_as_invalid() { drag.start_position_3D = Drag::Invalid_3D_Point; }
void set_move_start_threshold_position_2D_as_invalid() { drag.move_start_threshold_position_2D = Drag::Invalid_2D_Point; }
@@ -549,6 +556,8 @@ private:
bool m_fps_overlay_tick{ false };
LayersEditing m_layers_editing;
Mouse m_mouse;
// Orca: Gesture pans have their own lifecycle and stable world-space anchor.
std::optional<Vec3d> m_gesture_pan_anchor;
GLGizmosManager m_gizmos;
//BBS: GUI refactor: GLToolbar
mutable GLToolbar m_main_toolbar;
@@ -1406,6 +1415,20 @@ private:
// Convert the screen space coordinate to world coordinate on the bed.
Vec3d _mouse_to_bed_3d(const Point& mouse_pos);
// Orca: Navigation type selects the legacy pivot policy used when no visible surface is hit.
enum class ECameraNavigationType : unsigned char
{
Mouse,
Gesture
};
// Orca: These helpers keep clipping, orbit pivots, and perspective-pan depth selection consistent.
ClippingPlane get_raycaster_clipping_plane() const;
bool is_bed_visible() const;
std::optional<Vec3d> get_camera_orbit_target(ECameraNavigationType navigation_type) const;
Vec3d get_camera_pan_anchor(Camera& camera, ECameraNavigationType navigation_type,
const Vec2d& screen_position) const;
void _start_timer() { m_timer.Start(100, wxTIMER_CONTINUOUS); }
void _stop_timer() { m_timer.Stop(); }
+60 -32
View File
@@ -863,8 +863,11 @@ void GUI_App::post_init()
mainframe->Freeze();
#endif
plater_->canvas3D()->enable_render(false);
mainframe->select_tab(TAB_ID_PREPARE);
mainframe->select_prepare_for_gl_init();
plater_->select_view_3D("3D");
// The first render happens before the queued new_project() sets the same view.
plater_->get_camera().select_view("topfront");
plater_->get_camera().requires_zoom_to_bed = true;
//BBS init the opengl resource here
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
!plater_->canvas3D()->make_current_for_postinit()) {
@@ -900,10 +903,10 @@ void GUI_App::post_init()
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished rendering a first frame for test";
}
}
if (is_editor())
mainframe->select_tab(TAB_ID_HOME);
if (app_config->get("default_page") == "1")
if (starts_on_prepare())
mainframe->select_tab(TAB_ID_PREPARE);
else if (is_editor())
mainframe->select_tab(TAB_ID_HOME);
#ifndef __linux__
mainframe->Thaw();
#endif
@@ -912,6 +915,7 @@ void GUI_App::post_init()
plater_->trigger_restore_project(1);
//#endif
mainframe->prebuild_pages_when_idle();
//BBS: remove GCodeViewer as seperate APP logic
/*if (this->init_params->start_as_gcodeviewer) {
@@ -1926,9 +1930,9 @@ bool GUI_App::hot_reload_network_plugin()
m_device_manager->add_user_subscribe();
}
if (mainframe && mainframe->m_monitor) {
mainframe->m_monitor->update_network_version_footer();
mainframe->m_monitor->set_default();
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->update_network_version_footer();
monitor->set_default();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": reset monitor panel";
}
@@ -3419,14 +3423,16 @@ bool GUI_App::on_init_inner()
}
BOOST_LOG_TRIVIAL(info) << "create the main window";
mainframe = new MainFrame();
// hide settings tabs after first Layout
if (is_editor()) {
mainframe->select_tab(TAB_ID_HOME);
if (starts_on_prepare()) {
mainframe->select_tab(TAB_ID_PREPARE);
} else {
mainframe->select_tab(TAB_ID_HOME);
}
}
sidebar().obj_list()->init();
//sidebar().aux_list()->init_auxiliary();
mainframe->m_project->init_auxiliary();
// update_mode(); // !!! do that later
SetTopWindow(mainframe);
@@ -4137,13 +4143,13 @@ void GUI_App::select_machine(const std::string& agent_id)
// Use MonitorPanel::select_machine() to trigger full selection flow
// This reuses existing logic for machine switching (UI updates, callbacks, etc.)
if (mainframe && mainframe->m_monitor) {
mainframe->m_monitor->select_machine(dev_id);
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->select_machine(dev_id);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": triggered select_machine for dev_id=" << dev_id;
} else {
// Fallback if MonitorPanel not available
m_device_manager->set_selected_machine(dev_id);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": fallback set_selected_machine dev_id=" << dev_id;
} else if (m_device_manager->set_selected_machine(dev_id)) {
// The Device tab's own state is set when the tab is built.
MonitorPanel::on_machine_selected(m_device_manager->get_selected_machine());
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": set_selected_machine dev_id=" << dev_id;
}
}
@@ -4667,6 +4673,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
//BBS: trigger restore project logic here, and skip confirm
plater_->trigger_restore_project(1);
mainframe->prebuild_pages_when_idle();
// #ys_FIXME_delete_after_testing Do we still need this ?
// CallAfter([]() {
@@ -5008,7 +5015,8 @@ void GUI_App::get_login_info(const std::string& provider/* = ORCA_CLOUD_PROVIDER
wxString strJS = wxString::Format("window.postMessage(%s)", from_u8(logout_cmd));
GUI::wxGetApp().run_script(strJS);
}
mainframe->m_webview->SetLoginPanelVisibility(true);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SetLoginPanelVisibility(true);
}
}
@@ -5140,9 +5148,9 @@ std::string GUI_App::handle_web_request(std::string cmd)
"homepage_bambu_login_or_register",
};
if (app_config->get_stealth_mode() && stealth_blocked_info_commands.count(command_str)) {
CallAfter([this] {
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
CallAfter([] {
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendCloudProvidersInfo();
});
return "";
}
@@ -5156,8 +5164,8 @@ std::string GUI_App::handle_web_request(std::string cmd)
if (dlg.ShowModal() == wxID_OK) {
app_config->set_bool("stealth_mode", false);
app_config->save();
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendCloudProvidersInfo();
// Continue with login
if (command_str == "homepage_login_or_register")
this->request_login(true);
@@ -5238,8 +5246,8 @@ std::string GUI_App::handle_web_request(std::string cmd)
}
else if (command_str.compare("get_recent_projects") == 0) {
if (mainframe) {
if (mainframe->m_webview) {
mainframe->m_webview->SendRecentList(INT_MAX);
if (WebViewPanel* home = WebViewPanel::if_built()) {
home->SendRecentList(INT_MAX);
}
}
}
@@ -7772,8 +7780,8 @@ void GUI_App::on_stealth_mode_enter()
BOOST_LOG_TRIVIAL(info) << "logout: on_stealth_mode_enter";
request_user_logout(ORCA_CLOUD_PROVIDER);
request_user_logout(BBL_CLOUD_PROVIDER);
if (mainframe && mainframe->m_webview) {
mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built()) {
home->SendCloudProvidersInfo();
}
}
@@ -8183,6 +8191,24 @@ ConfigOptionMode GUI_App::get_saved_mode()
return saved_mode_from_string(app_config->get("user_mode"));
}
bool GUI_App::starts_on_prepare() const
{
return app_config->get("default_page") == "1";
}
int GUI_App::input_idle_ms() const
{
return int(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_last_input).count());
}
// Every wxCommandEvent claims the user-input category, so only real mouse and key events count.
int GUI_App::FilterEvent(wxEvent& event)
{
if (!event.IsCommandEvent() && (event.GetEventCategory() & wxEVT_CATEGORY_USER_INPUT))
m_last_input = std::chrono::steady_clock::now();
return Event_Skip;
}
ConfigOptionMode GUI_App::get_mode()
{
return app_config->get_bool("developer_mode") ? comDevelop : get_saved_mode();
@@ -8234,9 +8260,10 @@ void GUI_App::update_mode()
mainframe->m_param_panel->update_mode();
if (mainframe->m_param_dialog)
mainframe->m_param_dialog->panel()->update_mode();
if (mainframe->m_printer_view)
mainframe->m_printer_view->update_mode();
mainframe->m_webview->update_mode();
if (PrinterWebView* view = PrinterWebView::if_built())
view->update_mode();
if (WebViewPanel* home = WebViewPanel::if_built())
home->update_mode();
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
@@ -8254,9 +8281,10 @@ void GUI_App::update_mode()
}
void GUI_App::update_internal_development() {
mainframe->m_webview->update_mode();
if (mainframe->m_printer_view)
mainframe->m_printer_view->update_mode();
if (WebViewPanel* home = WebViewPanel::if_built())
home->update_mode();
if (PrinterWebView* view = PrinterWebView::if_built())
view->update_mode();
}
void GUI_App::show_ip_address_enter_dialog(wxString title)
+7
View File
@@ -1,6 +1,7 @@
#ifndef slic3r_GUI_App_hpp_
#define slic3r_GUI_App_hpp_
#include <chrono>
#include <functional>
#include <memory>
#include <string>
@@ -248,6 +249,7 @@ private:
bool m_app_conf_exists{ false };
EAppMode m_app_mode{ EAppMode::Editor };
bool m_is_recreating_gui{ false };
std::chrono::steady_clock::time_point m_last_input{ std::chrono::steady_clock::now() };
#ifdef __linux__
bool m_opengl_initialized{ false };
#endif
@@ -387,6 +389,11 @@ public:
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
bool is_recreating_gui() const { return m_is_recreating_gui; }
// Milliseconds since the last mouse or keyboard event the app processed.
int input_idle_ms() const;
int FilterEvent(wxEvent& event) override;
// The Preferences "Default page" choice, stored as its index: 0 Home, 1 Prepare.
bool starts_on_prepare() const;
std::string logo_name() const { return is_editor() ? "OrcaSlicer" : "OrcaSlicer-gcodeviewer"; }
bool is_closing() const { return m_is_closing.load(std::memory_order_acquire); }
+2 -1
View File
@@ -122,7 +122,8 @@ HMSNotifyItem::HMSNotifyItem(const std::string& dev_id, wxWindow *parent, DevHMS
m_hms_content->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) {
wxCommandEvent evt(EVT_ALREADY_READ_HMS);
evt.SetString(long_error_code);
wxPostEvent(wxGetApp().mainframe->m_monitor, evt);
if (MonitorPanel* monitor = MonitorPanel::if_built())
wxPostEvent(monitor, evt);
if (!m_url.empty()) wxLaunchDefaultBrowser(m_url);
});
+100
View File
@@ -0,0 +1,100 @@
#include "IdleScheduler.hpp"
#include <chrono>
#include <boost/log/trivial.hpp>
#include "libslic3r/Utils.hpp"
#ifdef _WIN32
#include <windows.h>
#endif
namespace Slic3r { namespace GUI {
namespace {
// The timer's period while waiting for the user to go quiet.
constexpr int tick_ms = 250;
// Input-free time before a slice may start, about a double-click interval, so a user
// mid-gesture is left alone.
constexpr int quiet_ms = 500;
// Budget of one slice. The next slice is a timer message, so paint, timers and input
// queued meanwhile are handled first; a click waits at most a slice plus the unit that
// overran it.
constexpr int slice_ms = 40;
// On GTK a due timer runs ahead of repaints and posted events, so the next slice waits a few ms.
#ifdef __WXGTK__
constexpr int next_slice_ms = 5;
#else
constexpr int next_slice_ms = 0;
#endif
// True when unhandled keyboard, button, touch or pen input is queued; only Windows can ask.
bool input_pending()
{
#ifdef _WIN32
// Windows synthesizes a mouse move whenever a window appears under the cursor, so those
// do not count.
return (GetQueueStatus(QS_INPUT & ~QS_MOUSEMOVE) >> 16) != 0;
#else
return false;
#endif
}
} // namespace
IdleScheduler::IdleScheduler(std::function<int()> input_idle_ms) : m_input_idle_ms(std::move(input_idle_ms))
{
m_timer.Bind(wxEVT_TIMER, [this](wxTimerEvent&) { tick(); });
}
void IdleScheduler::start()
{
if (!m_timer.IsRunning())
m_timer.Start(tick_ms);
}
void IdleScheduler::stop()
{
m_timer.Stop();
}
void IdleScheduler::tick()
{
// A unit that pumps the event loop lets the timer fire inside its own slice.
if (m_in_slice)
return;
if (!m_queue.pending()) {
stop();
return;
}
if (m_input_idle_ms() < quiet_ms || input_pending()) {
start();
return;
}
const auto now_ms = [] {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
};
const char* const fn = __FUNCTION__;
m_in_slice = true;
PrebuildQueue::Slice slice;
{
ScopeGuard in_slice([this] { m_in_slice = false; });
slice = m_queue.run_slice(slice_ms, now_ms, input_pending, [fn](const std::string& name, long long ms) {
BOOST_LOG_TRIVIAL(debug) << fn << ": " << name << ": unit took " << ms << " ms";
});
}
if (slice.completed)
BOOST_LOG_TRIVIAL(info) << fn << ": task complete: " << slice.name << ", " << slice.units << " unit(s) in " << slice.ms << " ms this slice";
else
BOOST_LOG_TRIVIAL(debug) << fn << ": " << slice.name << ": " << slice.units << " unit(s) in " << slice.ms << " ms, yielding";
if (!slice.remaining)
stop();
else if (input_pending())
m_timer.Start(tick_ms);
else
m_timer.StartOnce(next_slice_ms);
}
}} // namespace Slic3r::GUI
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <functional>
#include <string>
#include <wx/event.h>
#include <wx/timer.h>
#include "PrebuildQueue.hpp"
namespace Slic3r { namespace GUI {
// Runs the queue's units while the user is idle. Once the user has been idle long enough,
// slices run one per timer message until the work is done or input arrives, so the event
// loop handles what it has between slices. Main thread only.
class IdleScheduler
{
public:
// input_idle_ms reports how long ago the user last touched the mouse or keyboard.
explicit IdleScheduler(std::function<int()> input_idle_ms);
void add(LazyBase& task) { m_queue.add(task); }
void clear() { m_queue.clear(); }
// The task names in queue order, for a log line.
std::string names() const { return m_queue.names(); }
// Arms the timer; it stops itself once no task is pending, so call again when a task
// appears.
void start();
void stop();
private:
void tick();
PrebuildQueue m_queue;
wxTimer m_timer;
std::function<int()> m_input_idle_ms;
bool m_in_slice{ false };
};
}} // namespace Slic3r::GUI
+27
View File
@@ -0,0 +1,27 @@
#include "Lazy.hpp"
#include <boost/log/trivial.hpp>
#include <wx/app.h>
#include <wx/utils.h>
namespace Slic3r { namespace GUI {
LazyBase::OnDemandBuild::OnDemandBuild(const LazyBase& lazy) : m_lazy(lazy), m_started(std::chrono::steady_clock::now())
{
// The unit tests have no app.
if (wxTheApp != nullptr)
m_busy = std::make_unique<wxBusyCursor>();
}
LazyBase::OnDemandBuild::~OnDemandBuild()
{
BOOST_LOG_TRIVIAL(info) << "Lazy::ensure: built " << m_lazy.name() << " on demand, " << m_units << " unit(s) in "
<< std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_started).count() << " ms";
}
void LazyBase::log_null_factory(const std::string& name)
{
BOOST_LOG_TRIVIAL(error) << "Lazy::build_step: the factory for " << name << " returned null";
}
}} // namespace Slic3r::GUI
+198
View File
@@ -0,0 +1,198 @@
#pragma once
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "StagedBuild.hpp"
class wxBusyCursor;
// Deferred construction of one object. Lazy<T> holds the factory and builds the object on
// demand or, unit by unit, from an IdleScheduler; LazyBase is what the scheduler sees of
// it; LazyInstance<Self> gives a type with one such object app-wide static access to it.
// docs/HLSD/deferred-page-construction.md covers the design.
namespace Slic3r { namespace GUI {
template <class Self> class LazyInstance;
// A prebuild task: what PrebuildQueue sees of a Lazy<T>. A type with its own unit sequence
// implements it directly.
class LazyBase
{
public:
virtual ~LazyBase() = default;
// Labels the object in logs.
virtual const std::string& name() const = 0;
virtual bool built() const = 0;
// Whether the idle prebuild has work here.
virtual bool pending() const { return !built(); }
// Runs one unit and returns true while more remain.
virtual bool build_step() = 0;
// Position in the idle queue: lower builds first, negative is never prebuilt.
virtual int prebuild_order() const = 0;
protected:
// Busy cursor around a build the user is waiting on, and a log line after it.
class OnDemandBuild
{
public:
explicit OnDemandBuild(const LazyBase& lazy);
~OnDemandBuild();
void unit() { ++m_units; }
private:
const LazyBase& m_lazy;
std::unique_ptr<wxBusyCursor> m_busy;
std::chrono::steady_clock::time_point m_started;
int m_units{ 0 };
};
static void log_null_factory(const std::string& name);
};
// Holds an object a factory makes on the first build_step() or ensure(), followed by one
// StagedBuild step per unit if the type has them, and keeps callbacks until the object is
// complete. The object's parent owns it, not the holder. A LazyInstance type is registered
// for its statics; any other type is reached only through the holder.
template <class T>
class Lazy : public LazyBase
{
public:
using Factory = std::function<T*()>;
Lazy(std::string name, int order, Factory make) : m_name(std::move(name)), m_order(order), m_make(std::move(make))
{
if constexpr (registered())
LazyInstance<T>::s_lazy = this;
}
~Lazy() override
{
if constexpr (registered())
if (LazyInstance<T>::s_lazy == this)
LazyInstance<T>::s_lazy = nullptr;
}
Lazy(const Lazy&) = delete;
Lazy& operator=(const Lazy&) = delete;
// Null until completely built.
T* get() const { return built() ? m_object : nullptr; }
// Builds whatever is left now and returns the object, null if the factory returned null
// or a nested call finds the object mid-build.
T* ensure()
{
if (!built() && !m_building) {
OnDemandBuild build(*this);
do
build.unit();
while (build_step());
}
return get();
}
// Runs fn on the object now if it is built, otherwise once it is.
void when_built(std::function<void(T&)> fn)
{
if (built())
fn(*m_object);
else
m_deferred.push_back(std::move(fn));
}
const std::string& name() const override { return m_name; }
// Read by a worker thread through the statics; the last write in build_step() releases it.
bool built() const override { return m_complete.load(std::memory_order_acquire); }
bool pending() const override { return !built() && !m_failed; }
int prebuild_order() const override { return m_order; }
// The factory is the first unit, then one StagedBuild step each. A unit that pumps the
// event loop cannot re-enter; a nested call does nothing.
bool build_step() override
{
if (built() || m_building || m_failed)
return false;
m_building = true;
try {
if (m_object == nullptr)
m_object = m_make();
else
step();
} catch (...) {
m_building = false;
throw;
}
m_building = false;
if (m_object == nullptr) {
m_failed = true;
log_null_factory(m_name);
return false;
}
if (steps_remain())
return true;
m_complete.store(true, std::memory_order_release);
for (auto& fn : m_deferred)
fn(*m_object);
m_deferred.clear();
return false;
}
private:
static constexpr bool registered() { return std::is_base_of_v<LazyInstance<T>, T>; }
static constexpr bool staged() { return std::is_base_of_v<StagedBuild, T>; }
bool steps_remain() const
{
if constexpr (staged())
return !m_object->built();
else
return false;
}
void step()
{
if constexpr (staged())
m_object->build_step();
}
std::string m_name;
int m_order;
Factory m_make;
T* m_object{ nullptr };
std::atomic<bool> m_complete{ false };
bool m_building{ false };
bool m_failed{ false };
std::vector<std::function<void(T&)>> m_deferred;
};
// Mixin for a type with one lazily built instance in the app. The statics reach that
// instance through its Lazy holder (a recreated MainFrame's holder replaces the old
// frame's).
template <class Self>
class LazyInstance
{
public:
// Null until completely built.
static Self* if_built() { return s_lazy ? s_lazy->get() : nullptr; }
// Builds the object if needed; null while no holder exists or the holder has no
// object.
static Self* ensure() { return s_lazy ? s_lazy->ensure() : nullptr; }
// Runs fn on the object now if it is built, otherwise once it is.
static void when_built(std::function<void(Self&)> fn)
{
if (s_lazy)
s_lazy->when_built(std::move(fn));
}
private:
friend class Lazy<Self>;
inline static Lazy<Self>* s_lazy{ nullptr };
};
}} // namespace Slic3r::GUI
+14
View File
@@ -0,0 +1,14 @@
#include "LazyPage.hpp"
#include "GUI_App.hpp"
namespace Slic3r { namespace GUI {
void apply_dark_ui_to_lazy_panel(wxWindow* panel)
{
#ifdef _MSW_DARK_MODE
wxGetApp().UpdateDarkUIWin(panel);
#endif
}
}} // namespace Slic3r::GUI
+77
View File
@@ -0,0 +1,77 @@
#pragma once
#include <functional>
#include <string>
#include <utility>
#include <wx/bookctrl.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include "Lazy.hpp"
namespace Slic3r { namespace GUI {
// Applies the app's dark-mode pass to a panel built after the frame's own pass ran.
void apply_dark_ui_to_lazy_panel(wxWindow* panel);
// A page whose panel is built the first time the page is shown, or earlier by an
// IdleScheduler; nothing builds while the frame is still hidden. The Lazy side holds the
// panel and, for a LazyInstance panel type, registers it for the type's statics.
template <class Panel>
class LazyPage : public wxPanel, public Lazy<Panel>
{
public:
using Factory = std::function<Panel*(wxWindow* parent)>;
LazyPage(wxWindow* parent, std::string name, int order, Factory make = [](wxWindow* parent) { return new Panel(parent); })
: wxPanel(parent), Lazy<Panel>(std::move(name), order, [this, make = std::move(make)] {
Panel* panel = make(this);
GetSizer()->Add(panel, 1, wxEXPAND);
// Hidden while its page is, as an inserted page would be.
if (!IsShown())
panel->Hide();
return panel;
})
{
SetSizer(new wxBoxSizer(wxVERTICAL));
// Shown by the book when its tab is selected.
Hide();
this->when_built([this](Panel& panel) {
apply_dark_ui_to_lazy_panel(&panel);
Layout();
});
}
// The parent book currently lists this page.
bool in_book() const
{
auto* book = dynamic_cast<wxBookCtrlBase*>(GetParent());
return book != nullptr && book->FindPage(this) != wxNOT_FOUND;
}
// Pending only while the tab is in the book.
bool pending() const override { return in_book() && Lazy<Panel>::pending(); }
// Forwarded so the panel's own Show() override stays its activation hook; wx calls this
// virtual from the book's ShowWithEffect() only for wxSHOW_EFFECT_NONE, the default.
bool Show(bool show = true) override
{
const bool changed = wxPanel::Show(show);
if (show) {
// The book shows its first page as it is inserted, before startup has chosen the
// start page, so a hidden frame builds nothing; MainFrame::Show() completes it.
if (this->built() || wxGetTopLevelParent(this)->IsShown()) {
if (Panel* panel = this->ensure())
panel->Show(true);
// The sizer skipped the panel while the book kept it hidden.
Layout();
}
} else if (Panel* panel = this->get()) {
panel->Show(false);
}
return changed;
}
};
}} // namespace Slic3r::GUI
+157 -170
View File
@@ -317,7 +317,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
// BBS
, m_recent_projects(18)
, m_settings_dialog(this)
, diff_dialog(this)
, m_idle([] { return wxGetApp().input_idle_ms(); })
, m_diff_dialog("compare_presets", 100, [this] { return make_diff_dialog(); })
{
#ifdef __WXOSX__
set_miniaturizable(GetHandle());
@@ -737,9 +738,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
wxGetApp().persist_window_geometry(this, true);
wxGetApp().persist_window_geometry(&m_settings_dialog, true);
// bind events from DiffDlg
bind_diff_dialog();
}
bool MainFrame::handle_global_shortcut(const KeyChord& chord)
@@ -832,8 +830,10 @@ bool MainFrame::handle_global_shortcut(const KeyChord& chord)
return true;
}
void MainFrame::bind_diff_dialog()
DiffPresetDialog* MainFrame::make_diff_dialog()
{
auto* dialog = new DiffPresetDialog(this);
auto get_tab = [](Preset::Type type) {
Tab* null_tab = nullptr;
for (Tab* tab : wxGetApp().tabs_list)
@@ -842,23 +842,24 @@ void MainFrame::bind_diff_dialog()
return null_tab;
};
auto transfer = [this, get_tab](Preset::Type type) {
get_tab(type)->transfer_options(diff_dialog.get_left_preset_name(type),
diff_dialog.get_right_preset_name(type),
diff_dialog.get_selected_options(type));
auto transfer = [dialog, get_tab](Preset::Type type) {
get_tab(type)->transfer_options(dialog->get_left_preset_name(type),
dialog->get_right_preset_name(type),
dialog->get_selected_options(type));
};
auto process_options = [this](std::function<void(Preset::Type)> process) {
const Preset::Type diff_dlg_type = diff_dialog.view_type();
auto process_options = [dialog](std::function<void(Preset::Type)> process) {
const Preset::Type diff_dlg_type = dialog->view_type();
if (diff_dlg_type == Preset::TYPE_INVALID) {
for (const Preset::Type& type : diff_dialog.types_list() )
for (const Preset::Type& type : dialog->types_list() )
process(type);
}
else
process(diff_dlg_type);
};
diff_dialog.Bind(EVT_DIFF_DIALOG_TRANSFER, [process_options, transfer](SimpleEvent&) { process_options(transfer); });
dialog->Bind(EVT_DIFF_DIALOG_TRANSFER, [process_options, transfer](SimpleEvent&) { process_options(transfer); });
return dialog;
}
@@ -1186,8 +1187,9 @@ void MainFrame::update_edge_panels()
void MainFrame::shutdown()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
if (m_project != nullptr)
m_project->shutdown();
m_idle.stop();
if (ProjectPanel* project = ProjectPanel::if_built())
project->shutdown();
m_plugin_pages.shutdown();
if (m_plater != nullptr)
m_plater->remove_dock_panes();
@@ -1314,19 +1316,6 @@ void MainFrame::show_option(bool show)
}
}
#ifdef SLIC3R_CAD
DesignPanel* MainFrame::ensure_design_panel()
{
if (m_design_panel == nullptr && m_design_page != nullptr) {
wxBusyCursor busy;
m_design_panel = new DesignPanel(m_design_page);
m_design_page->GetSizer()->Add(m_design_panel, 1, wxEXPAND);
m_design_page->Layout();
}
return m_design_panel;
}
#endif
void MainFrame::init_tabpanel() {
// wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on
// Windows 10 with multiple high resolution displays connected.
@@ -1369,23 +1358,20 @@ void MainFrame::init_tabpanel() {
// m_param_panel->OnActivate();
#ifdef SLIC3R_CAD
else if (m_design_page != nullptr && panel == m_design_page) {
// Built on first activation, never at startup: the panel creates several hundred
// controls and its own GL canvas, which a user who does not open the tab should
// not pay for.
ensure_design_panel();
// Re-sync the Design bed to the active printer: the panel is built before the
// printer profile is fully applied, so its bed must refresh on activation or the
// grid (true bed) spills past the stale default bed quad.
m_design_panel->on_tab_shown();
DesignPanel::ensure()->on_tab_shown();
}
#endif
else if (panel == m_monitor) {
else if (panel == m_monitor_page) {
//monitor
}
#ifdef SLIC3R_CAD
// Any page that is not Design takes the Design status line down with it — see
// DesignPanel::on_tab_hidden for why the popup does not follow the page on its own.
if (m_design_panel != nullptr && panel != m_design_page) m_design_panel->on_tab_hidden();
if (DesignPanel* design = DesignPanel::if_built(); design != nullptr && panel != m_design_page)
design->on_tab_hidden();
#endif
#ifndef __APPLE__
if (m_last_selected_tab == TAB_ID_PREPARE) {
@@ -1401,13 +1387,14 @@ void MainFrame::init_tabpanel() {
});
if (wxGetApp().is_editor()) {
m_webview = new WebViewPanel(m_tabpanel);
m_home_page = new LazyPage<WebViewPanel>(m_tabpanel, TAB_ID_HOME, 10);
m_lazy_pages.push_back(m_home_page);
Bind(EVT_LOAD_URL, [this](wxCommandEvent &evt) {
wxString url = evt.GetString();
select_tab(TAB_ID_HOME);
m_webview->load_url(url);
WebViewPanel::ensure()->load_url(url);
});
m_tabpanel->AddPage(TAB_ID_HOME, m_webview, "", "tab_home_active");
m_tabpanel->AddPage(TAB_ID_HOME, m_home_page, "", "tab_home_active");
m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
}
@@ -1418,15 +1405,13 @@ void MainFrame::init_tabpanel() {
wxGetApp().plater_ = m_plater;
#ifdef SLIC3R_CAD
// Stand-in page for the Design tab. The real DesignPanel is built into it the first time
// the tab is selected (see the page-changed handler above), so nothing it constructs sits
// on the startup path. The experimental feature is off by default, and when it is off the
// page is never created, so the tab does not appear at all (the preference takes effect on
// the next start, like the other feature toggles).
// The experimental feature is off by default, and when it is off the page is never
// created, so the tab does not appear at all (the preference takes effect on the next
// start, like the other feature toggles).
if (wxGetApp().is_enable_cad_feature()) {
m_design_page = new wxPanel(this);
m_design_page->SetSizer(new wxBoxSizer(wxVERTICAL));
m_design_page->Hide();
// Experimental and heavy enough that building it unasked would cost more than it saves.
m_design_page = new LazyPage<DesignPanel>(this, TAB_ID_DESIGN, -1);
m_lazy_pages.push_back(m_design_page);
start_mcp_control_if_enabled(); // opens the MCP socket iff ORCA_CAD_MCP is set
}
#endif
@@ -1434,33 +1419,41 @@ void MainFrame::init_tabpanel() {
create_preset_tabs();
//BBS add pages
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor, _L("Device"), "tab_monitor_active");
m_monitor_page = new LazyPage<MonitorPanel>(m_tabpanel, TAB_ID_MONITOR, 20);
m_lazy_pages.push_back(m_monitor_page);
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor_page, _L("Device"), "tab_monitor_active");
m_printer_view = new PrinterWebView(m_tabpanel);
m_printer_view_page = new LazyPage<PrinterWebView>(m_tabpanel, TAB_ID_MONITOR_WEB, 50, [this](wxWindow* parent) {
auto* view = new PrinterWebView(parent);
if (!m_printer_url.empty())
view->load_url(m_printer_url, m_printer_api_key);
return view;
});
m_lazy_pages.push_back(m_printer_view_page);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
wxString url = evt.GetString();
wxString key = evt.GetAPIkey();
//select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key);
m_printer_url = evt.GetString();
m_printer_api_key = evt.GetAPIkey();
if (PrinterWebView* view = PrinterWebView::if_built())
view->load_url(m_printer_url, m_printer_api_key);
});
m_printer_view->Hide();
m_multi_machine_page = new LazyPage<MultiMachinePage>(m_tabpanel, TAB_ID_MULTI_DEVICE, 40);
m_lazy_pages.push_back(m_multi_machine_page);
if (wxGetApp().is_enable_multi_machine()) {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
// TODO: change the bitmap
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine_page, _L("Multi-device"), "tab_multi_active");
}
m_project = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_project->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project, _L("Project"), "tab_auxiliary_active");
m_project_page = new LazyPage<ProjectPanel>(m_tabpanel, TAB_ID_PROJECT, 60);
m_lazy_pages.push_back(m_project_page);
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project_page, _L("Project"), "tab_auxiliary_active");
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration, _L("Calibration"), "tab_calibration_active");
// show_device() removes this tab for printers without the Bambu device tab, and the page
// then never builds its panel.
m_calibration_page = new LazyPage<CalibrationPanel>(m_tabpanel, TAB_ID_CALIBRATION, 30);
m_lazy_pages.push_back(m_calibration_page);
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration_page, _L("Calibration"), "tab_calibration_active");
// Plugin pages are appended after the built-in tabs; their ids are namespaced
// (plugin.<plugin_key>.<name>) so they can't collide with the built-in TAB_ID_* constants.
@@ -1495,67 +1488,44 @@ void MainFrame::show_device(bool should_use_native) {
// Remove the extra page before switching to any layout that shouldn't have it.
if (!want_web_device_tab) {
if ((idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR_WEB)) != wxNOT_FOUND) {
m_printer_view->Show(false);
m_printer_view_page->Show(false);
m_tabpanel->RemovePage(idx);
}
}
if (use_printer_agents) {
if (!m_monitor) {
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
}
if (m_tabpanel->FindPage(m_monitor) == wxNOT_FOUND) {
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND) {
m_printer_view->Show(false);
if (!m_monitor_page->in_book()) {
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) != wxNOT_FOUND) {
m_printer_view_page->Show(false);
m_tabpanel->RemovePage(idx);
}
m_monitor->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
m_monitor_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor_page,
_L("Device"), "tab_monitor_active");
}
if (m_printer_view == nullptr) {
m_printer_view = new PrinterWebView(m_tabpanel);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
wxString url = evt.GetString();
wxString key = evt.GetAPIkey();
// select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key);
});
}
if (wxGetApp().is_enable_multi_machine()) {
if (!m_multi_machine) {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
}
// TODO: change the bitmap
if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) {
m_multi_machine->Show(false);
if (!m_multi_machine_page->in_book()) {
m_multi_machine_page->Show(false);
// Past the web Device tab when it is already there, so enabling multi-machine
// later can't wedge this page between the two Device tabs.
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR_WEB, TAB_ID_MONITOR}),
TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
TAB_ID_MULTI_DEVICE, m_multi_machine_page, _L("Multi-device"), "tab_multi_active");
}
}
if (!m_calibration) {
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
}
if (m_tabpanel->FindPage(m_calibration) == wxNOT_FOUND) {
m_calibration->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
if (!m_calibration_page->in_book()) {
m_calibration_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration_page,
_L("Calibration"), "tab_calibration_active");
}
if (want_web_device_tab) {
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
m_printer_view->Show(false);
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) == wxNOT_FOUND) {
m_printer_view_page->Show(false);
// Immediately right of the native Device tab, not at the end of the tab bar.
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MONITOR_WEB,
m_printer_view, _L("Device (Web)"), "tab_monitor_active");
m_printer_view_page, _L("Device (Web)"), "tab_monitor_active");
} else {
m_tabpanel->SetPageText(idx, _L("Device (Web)"));
}
@@ -1567,48 +1537,37 @@ void MainFrame::show_device(bool should_use_native) {
fit_tab_labels(); // ORCA on printer change
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
if (m_prebuild_started)
m_idle.start();
return;
}
if (should_use_native) {
if (m_tabpanel->FindPage(m_monitor) != wxNOT_FOUND) {
if (m_monitor_page->in_book()) {
fit_tab_labels(); // ORCA on printer change - same button layout
return;
}
// Remove printer view
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND) {
m_printer_view->Show(false);
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) != wxNOT_FOUND) {
m_printer_view_page->Show(false);
m_tabpanel->RemovePage(idx);
}
// Create/insert monitor page
if (!m_monitor) {
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
}
m_monitor->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
// Insert monitor page
m_monitor_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor_page,
_L("Device"), "tab_monitor_active");
if (wxGetApp().is_enable_multi_machine()) {
if (!m_multi_machine) {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
}
// TODO: change the bitmap
m_multi_machine->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine,
m_multi_machine_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine_page,
_L("Multi-device"), "tab_multi_active");
}
if (!m_calibration) {
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
}
m_calibration->Show(false);
m_calibration_page->Show(false);
// Last of the built-in tabs, but plugin tabs already sit past it — anchor rather than
// append, so its position doesn't depend on the relayout() below running afterwards.
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration_page,
_L("Calibration"), "tab_calibration_active");
#ifdef _MSW_DARK_MODE
@@ -1616,37 +1575,30 @@ void MainFrame::show_device(bool should_use_native) {
#endif // _MSW_DARK_MODE
} else {
if (m_tabpanel->FindPage(m_printer_view) != wxNOT_FOUND) {
if (m_printer_view_page->in_book()) {
fit_tab_labels(); // ORCA on printer change - same button layout
return;
}
if ((idx = m_tabpanel->FindPage(m_calibration)) != wxNOT_FOUND) {
m_calibration->Show(false);
if ((idx = m_tabpanel->FindPage(m_calibration_page)) != wxNOT_FOUND) {
m_calibration_page->Show(false);
m_tabpanel->RemovePage(idx);
}
if ((idx = m_tabpanel->FindPage(m_multi_machine)) != wxNOT_FOUND) {
m_multi_machine->Show(false);
if ((idx = m_tabpanel->FindPage(m_multi_machine_page)) != wxNOT_FOUND) {
m_multi_machine_page->Show(false);
m_tabpanel->RemovePage(idx);
}
if ((idx = m_tabpanel->FindPage(m_monitor)) != wxNOT_FOUND) {
m_monitor->Show(false);
if ((idx = m_tabpanel->FindPage(m_monitor_page)) != wxNOT_FOUND) {
m_monitor_page->Show(false);
m_tabpanel->RemovePage(idx);
}
if (m_printer_view == nullptr) {
m_printer_view = new PrinterWebView(m_tabpanel);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
wxString url = evt.GetString();
wxString key = evt.GetAPIkey();
// select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key);
});
}
m_printer_view->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view,
m_printer_view_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view_page,
_L("Device"), "tab_monitor_active");
}
fit_tab_labels(); // ORCA on printer change
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
if (m_prebuild_started)
m_idle.start();
}
bool MainFrame::is_prepare_or_preview_tab() const
@@ -2692,13 +2644,11 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
//BBS GUI refactor: remove unused layout new/dlg
//if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog
m_param_panel->msw_rescale();
m_project->msw_rescale();
if(m_monitor)
m_monitor->msw_rescale();
if(m_multi_machine)
m_multi_machine->msw_rescale();
if(m_calibration)
m_calibration->msw_rescale();
// A panel mid-build gets the pass once it is complete.
ProjectPanel::when_built([](ProjectPanel& project) { project.msw_rescale(); });
MonitorPanel::when_built([](MonitorPanel& monitor) { monitor.msw_rescale(); });
MultiMachinePage::when_built([](MultiMachinePage& multi_machine) { multi_machine.msw_rescale(); });
CalibrationPanel::when_built([](CalibrationPanel& calibration) { calibration.msw_rescale(); });
// BBS
#if 0
@@ -2752,7 +2702,8 @@ void MainFrame::on_sys_color_changed()
#endif
#endif
diff_dialog.on_sys_color_changed();
if (DiffPresetDialog* dialog = DiffPresetDialog::if_built())
dialog->on_sys_color_changed();
// BBS
m_tabpanel->Rescale();
@@ -2760,10 +2711,8 @@ void MainFrame::on_sys_color_changed()
// update Plater
wxGetApp().plater()->sys_color_changed();
if(m_monitor)
m_monitor->on_sys_color_changed();
if(m_calibration)
m_calibration->on_sys_color_changed();
MonitorPanel::when_built([](MonitorPanel& monitor) { monitor.on_sys_color_changed(); });
CalibrationPanel::when_built([](CalibrationPanel& calibration) { calibration.on_sys_color_changed(); });
// update Tabs
for (auto tab : wxGetApp().tabs_list)
tab->sys_color_changed();
@@ -3696,7 +3645,8 @@ void MainFrame::set_max_recent_count(int max)
}
wxGetApp().app_config->set_recent_projects(recent_projects);
wxGetApp().app_config->save();
m_webview->SendRecentList(-1);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(-1);
}
}
@@ -4040,23 +3990,56 @@ void MainFrame::select_tab(wxPanel* panel)
select_tab(page_name);
}
// Selects the Prepare page without the page-changed event, so the GL canvas is on screen
// and nothing else is built for the pass.
void MainFrame::select_prepare_for_gl_init()
{
m_tabpanel->ChangeSelection(m_tabpanel->FindPageByName(TAB_ID_PREPARE));
}
// The book shows its first page as it is inserted, while the frame is hidden and nothing
// may build; the first show completes that page.
bool MainFrame::Show(bool show)
{
const bool changed = DPIFrame::Show(show);
if (show && changed && m_tabpanel != nullptr)
if (wxWindow* page = m_tabpanel->GetCurrentPage())
page->Show(true);
return changed;
}
// A page out of the book stays registered and is passed over; a negative order is never
// registered.
void MainFrame::prebuild_pages_when_idle()
{
m_idle.clear();
if (m_param_panel)
m_idle.add(m_param_panel->settings_page_prebuild());
for (LazyBase* page : m_lazy_pages)
if (page->prebuild_order() >= 0)
m_idle.add(*page);
m_idle.add(m_diff_dialog);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": prebuild queue: " << m_idle.names();
m_idle.start();
m_prebuild_started = true;
}
//BBS
void MainFrame::jump_to_monitor(std::string dev_id)
{
if(!m_monitor)
return;
m_tabpanel->SelectPageByName(TAB_ID_MONITOR);
if (!dev_id.empty()) {
((MonitorPanel*)m_monitor)->select_machine(dev_id);
MonitorPanel::ensure()->select_machine(dev_id);
}
}
void MainFrame::jump_to_multipage()
{
if(!m_multi_machine)
if (!m_multi_machine_page->in_book())
return;
m_tabpanel->SelectPageByName(TAB_ID_MULTI_DEVICE);
((MultiMachinePage*)m_multi_machine)->jump_to_send_page();
if (MultiMachinePage* page = m_multi_machine_page->ensure())
page->jump_to_send_page();
}
@@ -4105,8 +4088,8 @@ void MainFrame::request_select_tab(const wxString& id)
}
int MainFrame::get_calibration_curr_tab() {
if (m_calibration)
return m_calibration->get_tabpanel()->GetSelection();
if (CalibrationPanel* calibration = CalibrationPanel::if_built())
return calibration->get_tabpanel()->GetSelection();
return -1;
}
@@ -4215,7 +4198,8 @@ void MainFrame::add_to_recent_projects(const wxString& filename)
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
}
wxGetApp().app_config->set_recent_projects(recent_projects);
m_webview->SendRecentList(0);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(0);
}
}
@@ -4331,7 +4315,8 @@ void MainFrame::open_recent_project(size_t file_id, wxString const & filename)
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
}
wxGetApp().app_config->set_recent_projects(recent_projects);
m_webview->SendRecentList(-1);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(-1);
}
}
}
@@ -4354,7 +4339,8 @@ void MainFrame::remove_recent_project(size_t file_id, wxString const &filename)
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
}
wxGetApp().app_config->set_recent_projects(recent_projects);
m_webview->SendRecentList(-1);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(-1);
}
void MainFrame::load_url(wxString url)
@@ -4408,14 +4394,14 @@ bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelectedPageName
void MainFrame::refresh_plugin_tips()
{
if (m_webview != nullptr)
m_webview->ShowNetpluginTip();
if (WebViewPanel* home = WebViewPanel::if_built())
home->ShowNetpluginTip();
}
void MainFrame::RunScript(wxString js)
{
if (m_webview != nullptr)
m_webview->RunScript(js);
if (WebViewPanel* home = WebViewPanel::if_built())
home->RunScript(js);
}
void MainFrame::technology_changed()
@@ -4524,7 +4510,8 @@ void MainFrame::update_side_preset_ui()
//take off multi machine
if(m_multi_machine){m_multi_machine->clear_page();}
if (MultiMachinePage* multi_machine = MultiMachinePage::if_built())
multi_machine->clear_page();
}
void MainFrame::on_select_default_preset(SimpleEvent& evt)
+27 -17
View File
@@ -26,6 +26,8 @@
#include "Widgets/SideButton.hpp"
#include "Widgets/SideMenuPopup.hpp"
#include "FilamentGroupPopup.hpp"
#include "LazyPage.hpp"
#include "IdleScheduler.hpp"
#include <boost/property_tree/ptree_fwd.hpp>
@@ -136,6 +138,13 @@ class MainFrame : public DPIFrame
#endif
bool m_loaded {false};
wxTimer* m_reset_title_text_colour_timer{ nullptr };
IdleScheduler m_idle;
bool m_prebuild_started{ false };
// Every LazyPage, in and out of the book; prebuild_pages_when_idle() registers them.
std::vector<LazyBase*> m_lazy_pages;
// The latest EVT_LOAD_PRINTER_URL, applied when the web Device view is built.
wxString m_printer_url;
wxString m_printer_api_key;
wxString m_qs_last_input_file = wxEmptyString;
wxString m_qs_last_output_file = wxEmptyString;
@@ -180,7 +189,7 @@ class MainFrame : public DPIFrame
bool can_delete() const;
bool can_delete_all() const;
bool can_reslice() const;
void bind_diff_dialog();
DiffPresetDialog* make_diff_dialog();
// BBS
wxBoxSizer* create_side_tools();
@@ -377,6 +386,12 @@ public:
void select_tab(wxPanel* panel);
void select_tab(const wxString& id = wxString());
void request_select_tab(const wxString& id);
// post_init() needs the plater's canvas on screen to initialize OpenGL; this pass does not
// build the settings page.
void select_prepare_for_gl_init();
// Builds the lazy tab pages while the user is idle; post_init() calls it once.
void prebuild_pages_when_idle();
bool Show(bool show = true) override;
int get_calibration_curr_tab();
void select_view(const std::string& direction);
void update_shortcut_labels();
@@ -435,27 +450,21 @@ public:
BBLTopbar* m_topbar{ nullptr };
PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; }
Plater* m_plater { nullptr };
// Lazy pages, created once and kept for the frame's life; their panels are reached
// through LazyInstance's statics, and show_device() only moves pages in and out of the book.
#ifdef SLIC3R_CAD
// The tab page is the placeholder; m_design_panel stays null until the tab is first
// selected, so everything the Design panel builds stays off the startup path.
wxPanel* m_design_page { nullptr };
DesignPanel* m_design_panel { nullptr };
// Builds the Design panel if it does not exist yet and returns it (null only before the
// placeholder page itself exists). Main thread only -- it creates wx controls. Both the
// tab activation and the MCP socket go through this: the socket is driven headlessly,
// with nobody to click the tab, and without this every verb would answer "not ready".
DesignPanel* ensure_design_panel();
LazyPage<DesignPanel>* m_design_page { nullptr };
#endif
//BBS: GUI refactor
MonitorPanel* m_monitor{ nullptr };
LazyPage<MonitorPanel>* m_monitor_page{ nullptr };
//AuxiliaryPanel* m_auxiliary{ nullptr };
MultiMachinePage* m_multi_machine{ nullptr };
ProjectPanel* m_project{ nullptr };
LazyPage<MultiMachinePage>* m_multi_machine_page{ nullptr };
LazyPage<ProjectPanel>* m_project_page{ nullptr };
CalibrationPanel* m_calibration{ nullptr };
WebViewPanel* m_webview { nullptr };
PrinterWebView* m_printer_view{nullptr};
LazyPage<CalibrationPanel>* m_calibration_page{ nullptr };
LazyPage<WebViewPanel>* m_home_page { nullptr };
LazyPage<PrinterWebView>* m_printer_view_page{nullptr};
PluginPages m_plugin_pages;
wxLogWindow* m_log_window { nullptr };
// BBS
@@ -466,7 +475,8 @@ public:
ParamsDialog* m_param_dialog{ nullptr };
//BBS
SettingsDialog m_settings_dialog;
DiffPresetDialog diff_dialog;
// The Compare presets dialog, built on first use or at idle through its holder.
Lazy<DiffPresetDialog> m_diff_dialog;
wxWindow* m_plater_page{ nullptr };
PrintHostQueueDialog* m_printhost_queue_dlg;
+44 -37
View File
@@ -103,6 +103,7 @@ MonitorPanel::MonitorPanel(wxWindow* parent, wxWindowID id, const wxPoint& pos,
: wxPanel(parent, id, pos, size, style),
m_select_machine(SelectMachinePopup(this))
{
SetBackgroundColour(*wxWHITE);
#ifdef __WINDOWS__
SetDoubleBuffered(true);
#endif //__WINDOWS__
@@ -180,31 +181,36 @@ void MonitorPanel::init_tabpanel()
auto title = m_tabpanel->GetPageText(m_tabpanel->GetSelection());
m_media_file_panel->SwitchStorage(title == _L("Storage"));
}
page->SetFocus();
// The first page is selected while the panel is built off screen.
if (page->IsShownOnScreen())
page->SetFocus();
update_all();
}, m_tabpanel->GetId());
//m_status_add_machine_panel = new AddMachinePanel(m_tabpanel);
m_status_info_panel = new StatusPanel(m_tabpanel);
m_status_info_panel = new StatusPanel(m_tabpanel);
// Queued before the page is added, since adding it selects it and runs the handler above,
// where built() must already be false.
add_build_steps_of(*m_status_info_panel);
m_tabpanel->AddPage(m_status_info_panel, _L("Status"), true);
add_build_step([this] {
m_media_file_panel = new MediaFilePanel(m_tabpanel);
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), false);
});
add_build_step([this] {
m_upgrade_panel = new UpgradePanel(m_tabpanel);
m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), false);
});
add_build_step([this] {
m_hms_panel = new HMSPanel(m_tabpanel);
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), false);
m_media_file_panel = new MediaFilePanel(m_tabpanel);
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), false);
//m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), false);
std::string network_ver = Slic3r::NetworkAgent::get_version();
if (!network_ver.empty()) {
m_tabpanel->SetFooterText(wxString::Format(_L("Network plug-in v%s"), network_ver));
}
m_upgrade_panel = new UpgradePanel(m_tabpanel);
m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), false);
m_hms_panel = new HMSPanel(m_tabpanel);
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), false);
std::string network_ver = Slic3r::NetworkAgent::get_version();
if (!network_ver.empty()) {
m_tabpanel->SetFooterText(wxString::Format(_L("Network plug-in v%s"), network_ver));
}
m_initialized = true;
show_status((int)MonitorStatus::MONITOR_NO_PRINTER);
show_status((int)MonitorStatus::MONITOR_NO_PRINTER);
});
}
void MonitorPanel::set_default()
@@ -288,19 +294,23 @@ void MonitorPanel::on_select_printer(wxCommandEvent& event)
set_default();
update_all();
MachineObject *obj_ = dev->get_selected_machine();
if (obj_) {
obj_->last_cali_version = -1;
obj_->reset_pa_cali_history_result();
obj_->reset_pa_cali_result();
Sidebar &sidebar = GUI::wxGetApp().sidebar();
sidebar.update_sync_status(obj_);
sidebar.set_need_auto_sync_after_connect_printer(sidebar.need_auto_sync_extruder_list_after_connect_priner(obj_));
}
on_machine_selected(dev->get_selected_machine());
Layout();
}
void MonitorPanel::on_machine_selected(MachineObject* obj)
{
if (obj == nullptr)
return;
obj->last_cali_version = -1;
obj->reset_pa_cali_history_result();
obj->reset_pa_cali_result();
Sidebar& sidebar = GUI::wxGetApp().sidebar();
sidebar.update_sync_status(obj);
sidebar.set_need_auto_sync_after_connect_printer(sidebar.need_auto_sync_extruder_list_after_connect_priner(obj));
}
void MonitorPanel::on_printer_clicked(wxMouseEvent &event)
{
auto mouse_pos = ClientToScreen(event.GetPosition());
@@ -331,7 +341,8 @@ void MonitorPanel::on_size(wxSizeEvent &event)
void MonitorPanel::update_all()
{
if (!m_initialized)
// Every page exists once the last build step has run.
if (!built())
return;
NetworkAgent* m_agent = wxGetApp().getAgent();
@@ -412,16 +423,12 @@ void MonitorPanel::update_hms_tag()
bool MonitorPanel::Show(bool show)
{
#ifdef __APPLE__
// Notebook::InsertPage() hides every page it appends, so this also runs while MainFrame is
// still constructing, before GUI_App::mainframe is assigned. Same guard as Plater::Show().
if (wxGetApp().mainframe)
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
#endif
NetworkAgent* m_agent = wxGetApp().getAgent();
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (show) {
#ifdef __APPLE__
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
#endif
start_update();
update_network_version_footer();
@@ -448,7 +455,7 @@ bool MonitorPanel::Show(bool show)
void MonitorPanel::show_status(int status)
{
if (!m_initialized) return;
if (!built()) return;
if (last_status == status)return;
if ((last_status & (int)MonitorStatus::MONITOR_CONNECTING) != 0) {
NetworkAgent* agent = wxGetApp().getAgent();
+8 -6
View File
@@ -49,6 +49,7 @@
#include "slic3r/GUI/AmsWidgets.hpp"
#include "Widgets/SideTools.hpp"
#include "SelectMachinePop.hpp"
#include "Lazy.hpp"
namespace Slic3r {
namespace GUI {
@@ -72,16 +73,16 @@ public:
void msw_rescale();
};
class MonitorPanel : public wxPanel
class MonitorPanel : public wxPanel, public StagedBuild, public LazyInstance<MonitorPanel>
{
private:
Tabbook* m_tabpanel{ nullptr };
wxSizer* m_main_sizer{ nullptr };
StatusPanel* m_status_info_panel;
MediaFilePanel* m_media_file_panel;
UpgradePanel* m_upgrade_panel;
HMSPanel* m_hms_panel;
StatusPanel* m_status_info_panel{ nullptr };
MediaFilePanel* m_media_file_panel{ nullptr };
UpgradePanel* m_upgrade_panel{ nullptr };
HMSPanel* m_hms_panel{ nullptr };
/* side tools */
SideTools* m_side_tools{nullptr};
@@ -96,7 +97,6 @@ private:
wxBitmap m_arrow_img;
int last_status;
bool m_initialized { false };
bool update_flag{false};
wxTimer* m_refresh_timer = nullptr;
@@ -125,6 +125,8 @@ public:
StatusPanel* get_status_panel() {return m_status_info_panel;};
void select_machine(std::string machine_sn);
// Resets the selected printer's calibration results and syncs the sidebar, without the Device tab.
static void on_machine_selected(MachineObject* obj);
void on_timer(wxTimerEvent& event);
void on_select_printer(wxCommandEvent& event);
void on_printer_clicked(wxMouseEvent &event);
+3 -2
View File
@@ -21,8 +21,9 @@ MultiMachineItem::MultiMachineItem(wxWindow* parent, MachineObject* obj)
Bind(wxEVT_MOTION, &MultiMachineItem::OnMove, this);
Bind(EVT_MULTI_DEVICE_VIEW, [obj](auto& e) {
wxGetApp().mainframe->jump_to_monitor(obj->get_dev_id());
if (wxGetApp().mainframe->m_monitor->get_status_panel()->get_media_play_ctrl()) {
wxGetApp().mainframe->m_monitor->get_status_panel()->get_media_play_ctrl()->jump_to_play();
MonitorPanel* monitor = MonitorPanel::if_built();
if (monitor && monitor->get_status_panel()->get_media_play_ctrl()) {
monitor->get_status_panel()->get_media_play_ctrl()->jump_to_play();
}
});
wxGetApp().UpdateDarkUIWin(this);
+1
View File
@@ -11,6 +11,7 @@ namespace GUI {
MultiMachinePage::MultiMachinePage(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
init_tabpanel();
m_main_sizer = new wxBoxSizer(wxHORIZONTAL);
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxLEFT, 0);
+2 -1
View File
@@ -7,6 +7,7 @@
#include "MultiTaskManagerPage.hpp"
#include "MultiMachineManagerPage.hpp"
#include "Tabbook.hpp"
#include "Lazy.hpp"
#include "wx/button.h"
@@ -19,7 +20,7 @@ namespace GUI {
#define PICK_LEFT_DEV_STATUS 250
#define PICK_DEVICE_MAX 6
class MultiMachinePage : public wxPanel
class MultiMachinePage : public wxPanel, public LazyInstance<MultiMachinePage>
{
private:
wxTimer* m_refresh_timer = nullptr;
+26 -8
View File
@@ -290,7 +290,7 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c
m_compare_btn = new ScalableButton(m_top_panel, wxID_ANY, "compare", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true);
m_compare_btn->SetToolTip(_L("Compare presets"));
m_compare_btn->Bind(wxEVT_BUTTON, ([](wxCommandEvent e) { wxGetApp().mainframe->diff_dialog.show(); }));
m_compare_btn->Bind(wxEVT_BUTTON, ([](wxCommandEvent e) { DiffPresetDialog::ensure()->show(); }));
m_setting_btn = new ScalableButton(m_top_panel, wxID_ANY, "table", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true);
m_setting_btn->SetToolTip(_L("View all object's settings"));
@@ -550,18 +550,36 @@ void ParamsPanel::clear_page()
void ParamsPanel::OnActivate()
{
if (m_current_tab == NULL)
{
//the first time
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": first time opened, set current tab to print");
// BBS: open/close tab
//m_current_tab = m_tab_print;
set_active_tab(m_tab_print ? m_tab_print : m_tab_filament);
}
select_default_tab();
Tab* cur_tab = dynamic_cast<Tab *> (m_current_tab);
if (cur_tab)
cur_tab->OnActivate();
}
void ParamsPanel::select_default_tab()
{
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": first time opened, set current tab to print");
// BBS: open/close tab
//m_current_tab = m_tab_print;
set_active_tab(m_tab_print ? m_tab_print : m_tab_filament);
}
bool ParamsPanel::SettingsPagePrebuild::built() const
{
Tab* tab = dynamic_cast<Tab*>(m_panel.m_current_tab);
return tab != nullptr && !tab->page_build_pending();
}
bool ParamsPanel::SettingsPagePrebuild::build_step()
{
if (m_panel.m_current_tab == nullptr) {
m_panel.select_default_tab();
return !built();
}
Tab* tab = dynamic_cast<Tab*>(m_panel.m_current_tab);
return tab != nullptr && tab->page_build_step();
}
void ParamsPanel::OnToggled(wxCommandEvent& event)
{
if (m_mode_region && m_mode_region->GetId() == event.GetId()) {
+21
View File
@@ -30,6 +30,7 @@
#include "wxExtensions.hpp"
#include "GUI_Utils.hpp"
#include "Widgets/Button.hpp"
#include "Lazy.hpp"
class ModeSwitchButton;
class SwitchButton;
@@ -121,6 +122,22 @@ class ParamsPanel : public wxPanel
wxPanel* m_current_tab { nullptr };
// Builds the selected page's option groups at idle; while no tab is selected yet,
// its first unit selects the default one.
class SettingsPagePrebuild : public LazyBase
{
public:
explicit SettingsPagePrebuild(ParamsPanel& panel) : m_panel(panel) {}
const std::string& name() const override { return m_name; }
bool built() const override;
bool build_step() override;
int prebuild_order() const override { return 0; }
private:
ParamsPanel& m_panel;
std::string m_name{ "settings_page" };
} m_settings_page_prebuild{ *this };
bool m_has_object_config { false };
struct Highlighter
@@ -149,6 +166,10 @@ class ParamsPanel : public wxPanel
//clear the right page
void clear_page();
void OnActivate();
// The print tab, or the filament tab without one.
void select_default_tab();
// The settings page's prebuild task.
LazyBase& settings_page_prebuild() { return m_settings_page_prebuild; }
void set_active_tab(wxPanel*tab);
bool is_active_and_shown_tab(wxPanel*tab);
void update_mode();
+4 -1
View File
@@ -3555,8 +3555,11 @@ void PartPlate::render(const Transform3d& view_matrix, const Transform3d& projec
shader->stop_using();
}
if (wxGetApp().show_plate_gridlines() && show_grid)
if (wxGetApp().show_plate_gridlines() && show_grid) {
glsafe(::glDepthMask(bottom ? GL_TRUE : GL_FALSE));
render_grid(bottom);
glsafe(::glDepthMask(GL_TRUE));
}
if (!hide_chrome && !bottom && m_selected && !force_background_color) {
if (m_partplate_list)
+25 -23
View File
@@ -7033,7 +7033,7 @@ struct Plater::priv
void remove(size_t obj_idx);
bool delete_object_from_model(size_t obj_idx, bool refresh_immediately = true); //BBS
void delete_all_objects_from_model();
void reset(bool apply_presets_change = false);
void reset(bool apply_presets_change = false, bool reload_presets = true);
void center_selection();
void drop_selection();
void mirror(Axis axis);
@@ -7891,7 +7891,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
if (this->q->get_project_filename().IsEmpty() && this->q->is_empty_project()) {
int skip_confirm = e.GetInt();
this->q->new_project(skip_confirm, true);
// Skips the preset reload; trigger_restore_project()'s callers load the presets first.
this->q->new_project(skip_confirm, true, wxString(), false);
}
});
//wxPostEvent(this->q, wxCommandEvent{EVT_RESTORE_PROJECT});
@@ -10356,7 +10357,7 @@ void Plater::priv::delete_all_objects_from_model()
model.plates_custom_gcodes.clear();
}
void Plater::priv::reset(bool apply_presets_change)
void Plater::priv::reset(bool apply_presets_change, bool reload_presets)
{
// TakeSnapshot below and load_current_presets() further down each re-evaluate the
// aggregate dirty flag against a baseline that hasn't been reset yet, so they can toggle
@@ -10411,8 +10412,8 @@ void Plater::priv::reset(bool apply_presets_change)
// 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();
if (DesignPanel* design = DesignPanel::if_built())
design->clear_document();
#endif
assemble_view->get_canvas3d()->reset_explosion_ratio();
update();
@@ -10431,7 +10432,7 @@ void Plater::priv::reset(bool apply_presets_change)
wxGetApp().preset_bundle->reset_project_embedded_presets();
if (apply_presets_change)
wxGetApp().apply_keeped_preset_modifications();
else
else if (reload_presets)
wxGetApp().load_current_presets(false, false);
//BBS
@@ -13183,17 +13184,17 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
// Pointer test, not a name lookup: in printer-agents mode this page is TAB_ID_MONITOR_WEB
// while the native Device tab holds TAB_ID_MONITOR, and in legacy-web mode it holds
// TAB_ID_MONITOR itself.
const bool selecting_web_device_tab = main_frame->m_printer_view &&
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view;
const bool selecting_web_device_tab = main_frame->m_printer_view_page &&
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view_page;
if (selecting_web_device_tab) {
// Use the selected discovered machine when the preset has no host.
main_frame->load_printer_url();
} else if (new_name == TAB_ID_MONITOR && wxGetApp().preset_bundle != nullptr) {
auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
if (main_frame->m_printer_view && url.empty()) {
if (PrinterWebView* view = PrinterWebView::if_built(); view != nullptr && url.empty()) {
// It's missing_connection page, reload so that we can replay the gif image
main_frame->m_printer_view->reload();
view->reload();
}
}
}
@@ -13924,8 +13925,8 @@ void Plater::priv::unbind_canvas_event_handlers()
// 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();
if (DesignPanel* design = DesignPanel::if_built())
design->unbind_canvas_event_handlers();
#endif
}
@@ -13938,8 +13939,8 @@ void Plater::priv::reset_canvas_volumes()
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();
if (DesignPanel* design = DesignPanel::if_built())
design->reset_canvas_volumes();
#endif
}
@@ -15351,7 +15352,7 @@ Print& Plater::fff_print() { return p->fff_print; }
const SLAPrint& Plater::sla_print() const { return p->sla_print; }
SLAPrint& Plater::sla_print() { return p->sla_print; }
int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_name)
int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_name, bool reload_presets)
{
model().calib_pa_pattern.reset(nullptr);
model().plates_custom_gcodes.clear();
@@ -15399,7 +15400,7 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
// completes, so hold notifications until then to avoid firing on transient flips.
ProjectDirtyStateManager::NotificationSuppressor dirty_notify_suppressor(p->dirty_state);
reset(transfer_preset_changes);
reset(transfer_preset_changes, reload_presets);
reset_project_dirty_after_save();
reset_project_dirty_initial_presets();
wxGetApp().update_saved_preset_from_current_preset();
@@ -17984,7 +17985,7 @@ void Plater::deselect_all() { p->deselect_all(); }
void Plater::exit_gizmo() { p->exit_gizmo(); }
void Plater::remove(size_t obj_idx) { p->remove(obj_idx); }
void Plater::reset(bool apply_presets_change) { p->reset(apply_presets_change); }
void Plater::reset(bool apply_presets_change, bool reload_presets) { p->reset(apply_presets_change, reload_presets); }
void Plater::reset_with_confirm()
{
if (p->model.objects.empty() || MessageDialog(static_cast<wxWindow *>(this), _L("All objects will be removed, continue?"),
@@ -20001,7 +20002,7 @@ int Plater::export_config_3mf(int plate_idx, Export3mfProgressFn proFn)
void Plater::send_calibration_job_finished(wxCommandEvent & evt)
{
p->main_frame->request_select_tab(TAB_ID_CALIBRATION);
auto calibration_panel = p->main_frame->m_calibration;
CalibrationPanel* calibration_panel = CalibrationPanel::ensure();
if (calibration_panel) {
auto curr_wizard = static_cast<CalibrationWizard*>(calibration_panel->get_tabpanel()->GetPage(evt.GetInt()));
wxCommandEvent event(EVT_CALIBRATION_JOB_FINISHED);
@@ -20033,8 +20034,8 @@ void Plater::print_job_finished(wxCommandEvent &evt)
dev->set_selected_machine(evt.GetString().ToStdString());
p->main_frame->request_select_tab(TAB_ID_MONITOR);
//jump to monitor and select device status panel
MonitorPanel* curr_monitor = p->main_frame->m_monitor;
// Selects the status page on a built Device tab; one built by the switch starts there.
MonitorPanel* curr_monitor = MonitorPanel::if_built();
if(curr_monitor)
curr_monitor->get_tabpanel()->ChangeSelection(MonitorPanel::PrinterTab::PT_STATUS);
}
@@ -20774,8 +20775,8 @@ void Plater::update_print_error_info(int code, std::string msg, std::string extr
if (p->m_send_to_sdcard_dlg) {
p->m_send_to_sdcard_dlg->update_print_error_info(code, msg, extra);
}
if (p->main_frame->m_calibration)
p->main_frame->m_calibration->update_print_error_info(code, msg, extra);
if (CalibrationPanel* calibration = CalibrationPanel::if_built())
calibration->update_print_error_info(code, msg, extra);
}
wxString Plater::get_project_filename(const wxString& extension) const
@@ -21068,7 +21069,8 @@ void Plater::pop_warning_and_go_to_device_page(wxString printer_name, PrinterWar
{
printer_name.Replace("Bambu Lab", "", false);
wxString content;
bool device_page = (wxGetApp().mainframe == nullptr) && (wxGetApp().mainframe->m_monitor->IsShown());
MainFrame* frame = wxGetApp().mainframe;
bool device_page = frame != nullptr && frame->m_monitor_page->in_book();
if (type == PrinterWarningType::NOT_CONNECTED) {
if (device_page) {
content = wxString::Format(_L("Printer not connected. Please go to the device page to connect %s before syncing."),
+2 -2
View File
@@ -339,7 +339,7 @@ public:
// Helper: returns config indices where filament_is_mixed == false
std::vector<size_t> physical_filament_config_indices() const;
int new_project(bool skip_confirm = false, bool silent = false, const wxString& project_name = wxString());
int new_project(bool skip_confirm = false, bool silent = false, const wxString& project_name = wxString(), bool reload_presets = true);
// BBS: save & backup
void load_project(wxString const & filename = "", wxString const & originfile = "-");
int save_project(bool saveAs = false);
@@ -499,7 +499,7 @@ public:
void deselect_all();
void exit_gizmo();
void remove(size_t obj_idx);
void reset(bool apply_presets_change = false);
void reset(bool apply_presets_change = false, bool reload_presets = true);
void reset_with_confirm();
//BBS: return int for various result
int close_with_confirm(std::function<bool(bool yes_or_no)> second_check = nullptr); // BBS close project
+89
View File
@@ -0,0 +1,89 @@
#pragma once
#include <algorithm>
#include <functional>
#include <string>
#include <vector>
#include "Lazy.hpp"
namespace Slic3r { namespace GUI {
// Holders to build at idle, ordered by prebuild_order(), run one slice at a time. The
// holders outlive the queue. Main thread only.
class PrebuildQueue
{
public:
struct Slice
{
std::string name; // the task that ran; empty when nothing was pending
int units{ 0 }; // units run
long long ms{ 0 }; // time spent
bool completed{ false }; // the task has no more units
bool remaining{ false }; // some task is still pending
};
// Lower order runs first, equal order in the order added.
void add(LazyBase& task)
{
auto after = std::find_if(m_tasks.begin(), m_tasks.end(), [&](const LazyBase* t) { return t->prebuild_order() > task.prebuild_order(); });
m_tasks.insert(after, &task);
}
void clear() { m_tasks.clear(); }
bool pending() const
{
return std::any_of(m_tasks.begin(), m_tasks.end(), [](const LazyBase* t) { return t->pending(); });
}
// The task names in queue order, comma separated.
std::string names() const
{
std::string out;
for (const LazyBase* t : m_tasks)
out += (out.empty() ? "" : ", ") + t->name();
return out;
}
// Runs units of the first pending task until it completes, budget_ms of now_ms() have
// passed, or interrupt() is true after a unit; on_unit gets the task name and each
// unit's duration. A task with no work is passed over and stays in the queue, so one
// whose work returns is pending again.
Slice run_slice(int budget_ms,
const std::function<long long()>& now_ms,
const std::function<bool()>& interrupt,
const std::function<void(const std::string&, long long)>& on_unit = {})
{
Slice slice;
auto next = std::find_if(m_tasks.begin(), m_tasks.end(), [](const LazyBase* t) { return t->pending(); });
if (next == m_tasks.end())
return slice;
// The pointer is copied out, since a unit may add tasks and reallocate m_tasks.
LazyBase* const task = *next;
slice.name = task->name();
const long long started = now_ms();
for (;;) {
const long long unit_started = now_ms();
const bool more = task->build_step();
const long long now = now_ms();
++slice.units;
if (on_unit)
on_unit(slice.name, now - unit_started);
slice.ms = now - started;
if (!more) {
slice.completed = true;
break;
}
if (slice.ms >= budget_ms || interrupt())
break;
}
slice.remaining = pending();
return slice;
}
private:
std::vector<LazyBase*> m_tasks;
};
}} // namespace Slic3r::GUI
+4 -5
View File
@@ -1044,8 +1044,8 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
if (m_bambu_cloud_checkbox) m_bambu_cloud_checkbox->Enable(!enabled);
}
else if (param == "hide_login_side_panel") {
if (wxGetApp().mainframe && wxGetApp().mainframe->m_webview) {
wxGetApp().mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built()) {
home->SendCloudProvidersInfo();
}
}
// ORCA: apply the preview dimming change immediately to the currently loaded preview
@@ -1254,9 +1254,8 @@ wxBoxSizer *PreferencesDialog::create_item_bambu_cloud(wxString title, wxString
app_config->save();
// Update homepage visibility immediately
auto *mainframe = wxGetApp().mainframe;
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendCloudProvidersInfo();
});
m_sizer->Add(cb, 0, wxALIGN_CENTER);
+2 -1
View File
@@ -26,6 +26,7 @@
#include "wx/textctrl.h"
#include <wx/timer.h>
#include <memory>
#include "Lazy.hpp"
namespace Slic3r {
@@ -34,7 +35,7 @@ namespace GUI {
class PrinterWebViewHandler;
class PrinterWebView : public wxPanel {
class PrinterWebView : public wxPanel, public LazyInstance<PrinterWebView> {
public:
PrinterWebView(wxWindow *parent);
virtual ~PrinterWebView();
+2
View File
@@ -44,6 +44,7 @@ const std::vector<std::string> license_list = {
ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
m_project_home_url = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir()));
wxString strlang = wxGetApp().current_language_code_safe();
if (strlang != "")
@@ -67,6 +68,7 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
m_auxiliary = new AuxiliaryPanel(this);
m_auxiliary->Hide();
main_sizer->Add(m_auxiliary, wxSizerFlags().Expand().Proportion(1));
add_build_steps_of(*m_auxiliary);
Bind(EVT_AUXILIARY_DONE, [this](wxCommandEvent& e) { update_model_data();});
SetSizer(main_sizer);
+2 -2
View File
@@ -35,6 +35,7 @@
#include "libslic3r/ProjectTask.hpp"
#include "wxExtensions.hpp"
#include "Auxiliary.hpp"
#include "Lazy.hpp"
#define AUFILE_GREY700 wxColour(107, 107, 107)
#define AUFILE_GREY500 wxColour(158, 158, 158)
@@ -59,7 +60,7 @@ struct project_file{
std::string size;
};
class ProjectPanel : public wxPanel
class ProjectPanel : public wxPanel, public StagedBuild, public LazyInstance<ProjectPanel>
{
private:
std::atomic<bool> m_web_init_completed{false};
@@ -90,7 +91,6 @@ public:
void msw_rescale();
void update_model_data();
void clear_model_info();
void init_auxiliary() { m_auxiliary->init_auxiliary(); }
bool Show(bool show);
void OnScriptMessage(wxWebViewEvent& evt);
+31 -10
View File
@@ -99,8 +99,11 @@ void SceneRaycaster::remove_raycaster(std::shared_ptr<SceneRaycasterItem> item)
}
}
SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const
SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera,
const ClippingPlane* clipping_plane, SceneRaycaster::EHitMode mode) const
{
// Orca: Picking may favor an already selected volume for interaction, while camera
// navigation must always use the geometrically closest visible scene surface.
// helper class used to return currently selected volume as hit when overlapping with other volumes
// to allow the user to click and drag on a selected volume
class VolumeKeeper
@@ -110,7 +113,11 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
bool m_selected_volume_already_found{ false };
public:
VolumeKeeper() {
explicit VolumeKeeper(bool enabled) {
// Orca: Disable selected-volume bias for navigation raycasts.
if (!enabled)
return;
const Selection& selection = wxGetApp().plater()->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
const GLVolume* volume = selection.get_first_volume();
@@ -135,7 +142,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
}
};
VolumeKeeper volume_keeper;
VolumeKeeper volume_keeper(mode == EHitMode::Picking);
double closest_hit_squared_distance = std::numeric_limits<double>::max();
auto is_closest = [&closest_hit_squared_distance, &volume_keeper](const Camera& camera, const Vec3f& hit) {
@@ -154,7 +161,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
HitResult ret;
auto test_raycasters = [this, is_closest, clipping_plane, &volume_keeper](EType type, const Vec2d& mouse_pos, const Camera& camera, HitResult& ret) {
auto test_raycasters = [this, is_closest, clipping_plane, mode, &volume_keeper](EType type, const Vec2d& mouse_pos, const Camera& camera, HitResult& ret) {
const ClippingPlane* clip_plane = (clipping_plane != nullptr && type == EType::Volume) ? clipping_plane : nullptr;
const std::vector<std::shared_ptr<SceneRaycasterItem>>* raycasters = get_raycasters(type);
const Vec3f camera_forward = camera.get_dir_forward().cast<float>();
@@ -163,12 +170,22 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
if (!item->is_active())
continue;
// Each plate's component 0 is its surface; the remaining Bed IDs are controls.
// Keep controls clickable, but do not use them as camera-pan anchors.
if (mode != EHitMode::Picking && type == EType::Bed &&
decode_id(type, item->get_id()) % PartPlate::GRABBER_COUNT != 0)
continue;
current_hit.raycaster_id = item->get_id();
const Transform3d& trafo = item->get_transform();
if (item->get_raycaster()->closest_hit(mouse_pos, trafo, camera, current_hit.position, current_hit.normal, clip_plane)) {
current_hit.position = (trafo * current_hit.position.cast<double>()).cast<float>();
current_hit.normal = (trafo.matrix().block(0, 0, 3, 3).inverse().transpose() * current_hit.normal.cast<double>()).normalized().cast<float>();
if (item->use_back_faces() || current_hit.normal.dot(camera_forward) < 0.0f) {
// Orca: Perspective rays away from the viewport center are not parallel to camera_forward.
// Keep picking's legacy policy, but accept every front-facing navigation surface.
const Vec3f view_direction = mode != EHitMode::Picking && camera.get_type() == Camera::EType::Perspective ?
Vec3f((current_hit.position.cast<double>() - camera.get_position()).cast<float>()) : camera_forward;
if (item->use_back_faces() || current_hit.normal.dot(view_direction) < 0.0f) {
if (is_closest(camera, current_hit.position)) {
if (volume_keeper.is_active()) {
if (volume_keeper.check_hit_result(current_hit))
@@ -182,14 +199,18 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
}
};
if (!m_gizmos.empty())
test_raycasters(EType::Gizmo, mouse_pos, camera, ret);
// Orca: Gizmo geometry is an interaction target, not a valid depth anchor for camera movement.
if (mode == EHitMode::Picking) {
if (!m_gizmos.empty())
test_raycasters(EType::Gizmo, mouse_pos, camera, ret);
if (!m_fallback_gizmos.empty() && !ret.is_valid())
test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret);
if (!m_fallback_gizmos.empty() && !ret.is_valid())
test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret);
}
if (!m_gizmos_on_top || !ret.is_valid()) {
if (camera.is_looking_downward() && !m_bed.empty())
// Orca: In perspective the bottom of the viewport can see the bed even at a horizontal view.
if ((mode == EHitMode::SceneOnly || (mode == EHitMode::Picking && camera.is_looking_downward())) && !m_bed.empty())
test_raycasters(EType::Bed, mouse_pos, camera, ret);
if (!m_volumes.empty())
test_raycasters(EType::Volume, mouse_pos, camera, ret);
+11 -1
View File
@@ -57,6 +57,15 @@ public:
FallbackGizmo = 2000000
};
// Orca: Navigation needs the closest visible scene surface, without picking-specific
// gizmo and selected-volume priority.
enum class EHitMode : unsigned char
{
Picking,
SceneOnly,
VolumesOnly // Navigation when the bed is hidden.
};
struct HitResult
{
EType type{ EType::None };
@@ -97,7 +106,8 @@ public:
void set_gizmos_on_top(bool value) { m_gizmos_on_top = value; }
HitResult hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane = nullptr) const;
HitResult hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane = nullptr,
EHitMode mode = EHitMode::Picking) const;
#if ENABLE_RAYCAST_PICKING_DEBUG
void render_hit(const Camera& camera);
+1 -1
View File
@@ -3470,7 +3470,7 @@ void SelectMachineDialog::navigate_to_timelapse_page()
main_frame->jump_to_monitor();
// then switch to Storage (Media) tab inside Monitor
auto* monitor = dynamic_cast<MonitorPanel*>(main_frame->m_monitor);
MonitorPanel* monitor = MonitorPanel::if_built();
if (monitor) {
auto* tabpanel = monitor->get_tabpanel();
if (tabpanel) {
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <algorithm>
#include <functional>
#include <vector>
namespace Slic3r { namespace GUI {
// A panel whose constructor builds the skeleton and queues the rest as steps, run in order
// on the main thread, one at a time.
class StagedBuild
{
public:
virtual ~StagedBuild() = default;
bool built() const
{
return m_next_step == m_steps.size() &&
std::all_of(m_children.begin(), m_children.end(), [](const StagedBuild* child) { return child->built(); });
}
// Runs one step and returns true while more remain. Own steps first, then whatever a
// child queued after its steps were forwarded.
bool build_step()
{
if (m_next_step < m_steps.size()) {
// Copied out, since a step may queue more steps and reallocate m_steps.
auto step = std::move(m_steps[m_next_step++]);
step();
} else if (StagedBuild* child = unfinished_child()) {
child->build_step();
}
return !built();
}
protected:
void add_build_step(std::function<void()> step) { m_steps.push_back(std::move(step)); }
// Queues one step per step the child has now, and keeps the child so that built() waits
// for any it queues later.
void add_build_steps_of(StagedBuild& child)
{
m_children.push_back(&child);
for (size_t i = child.m_next_step; i < child.m_steps.size(); ++i)
add_build_step([&child] { child.build_step(); });
}
private:
StagedBuild* unfinished_child() const
{
auto it = std::find_if(m_children.begin(), m_children.end(), [](const StagedBuild* child) { return !child->built(); });
return it == m_children.end() ? nullptr : *it;
}
std::vector<std::function<void()>> m_steps;
std::vector<StagedBuild*> m_children;
size_t m_next_step{ 0 };
};
}} // namespace Slic3r::GUI
+99 -66
View File
@@ -1328,18 +1328,21 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
wxBoxSizer *bSizer_left = new wxBoxSizer(wxVERTICAL);
auto m_monitoring_sizer = create_monitoring_page();
bSizer_left->Add(m_monitoring_sizer, 1, wxEXPAND | wxALL, 0);
// The sizers are nested here so each step only appends to its own.
add_build_step([this, bSizer_left] {
auto m_monitoring_sizer = create_monitoring_page();
bSizer_left->Add(m_monitoring_sizer, 1, wxEXPAND | wxALL, 0);
auto m_panel_separotor1 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_panel_separotor1->SetBackgroundColour(STATUS_PANEL_BG);
m_panel_separotor1->SetMinSize(wxSize(-1, PAGE_SPACING));
m_panel_separotor1->SetMaxSize(wxSize(-1, PAGE_SPACING));
m_monitoring_sizer->Add(m_panel_separotor1, 0, wxEXPAND, 0);
auto m_panel_separotor1 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_panel_separotor1->SetBackgroundColour(STATUS_PANEL_BG);
m_panel_separotor1->SetMinSize(wxSize(-1, PAGE_SPACING));
m_panel_separotor1->SetMaxSize(wxSize(-1, PAGE_SPACING));
m_monitoring_sizer->Add(m_panel_separotor1, 0, wxEXPAND, 0);
m_project_task_panel = new PrintingTaskPanel(this, PrintingTaskType::PRINGINT);
m_project_task_panel->init_bitmaps();
m_monitoring_sizer->Add(m_project_task_panel, 0, wxALL | wxEXPAND , 0);
m_project_task_panel = new PrintingTaskPanel(this, PrintingTaskType::PRINGINT);
m_project_task_panel->init_bitmaps();
m_monitoring_sizer->Add(m_project_task_panel, 0, wxALL | wxEXPAND , 0);
});
// auto m_panel_separotor2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
// m_panel_separotor2->SetBackgroundColour(STATUS_PANEL_BG);
@@ -1359,8 +1362,10 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
m_machine_ctrl_panel->SetDoubleBuffered(true);
auto m_machine_control = create_machine_control_page(m_machine_ctrl_panel);
m_machine_ctrl_panel->SetSizer(m_machine_control);
m_machine_ctrl_panel->Layout();
m_machine_control->Fit(m_machine_ctrl_panel);
add_build_step([this, m_machine_control] {
m_machine_ctrl_panel->Layout();
m_machine_control->Fit(m_machine_ctrl_panel);
});
bSizer_status_below->Add(m_machine_ctrl_panel, 0, wxALL, 0);
@@ -1375,8 +1380,11 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
m_panel_separotor_bottom->SetBackgroundColour(STATUS_PANEL_BG);
bSizer_status->Add(m_panel_separotor_bottom, 0, wxEXPAND | wxALL, 0);
this->SetSizerAndFit(bSizer_status);
this->Layout();
this->SetSizer(bSizer_status);
add_build_step([this, bSizer_status] {
bSizer_status->SetSizeHints(this);
this->Layout();
});
}
StatusBasePanel::~StatusBasePanel()
@@ -1614,16 +1622,24 @@ wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
wxBoxSizer *bSizer_control = new wxBoxSizer(wxVERTICAL);
auto temp_axis_ctrl_sizer = create_temp_axis_group(parent);
auto m_filament_load_sizer = create_filament_group(parent);
// The slot sizers keep each group's place in bSizer_control.
wxBoxSizer *temp_axis_slot = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *filament_slot = new wxBoxSizer(wxVERTICAL);
/* ams control box or live nozzle-rack panel (rack printers switch between the two) */
wxSizer *ams_rack_sizer = new wxBoxSizer(wxHORIZONTAL);
ams_rack_sizer->Add(create_ams_group(parent), 0, wxEXPAND | wxLEFT);
m_panel_nozzle_rack = new wgtDeviceNozzleRack(parent);
m_panel_nozzle_rack->Show(false);
ams_rack_sizer->Add(m_panel_nozzle_rack, 0, wxEXPAND | wxLEFT);
add_build_step([this, parent, temp_axis_slot, filament_slot] {
temp_axis_slot->Add(create_temp_axis_group(parent), 0, wxEXPAND);
filament_slot->Add(create_filament_group(parent), 0, wxEXPAND);
});
add_build_step([this, parent, ams_rack_sizer] {
ams_rack_sizer->Add(create_ams_group(parent), 0, wxEXPAND | wxLEFT);
});
add_build_step([this, parent, ams_rack_sizer] {
m_panel_nozzle_rack = new wgtDeviceNozzleRack(parent);
m_panel_nozzle_rack->Show(false);
ams_rack_sizer->Add(m_panel_nozzle_rack, 0, wxEXPAND | wxLEFT);
});
m_ams_rack_switch = new SwitchBoard(parent, _L("Filament"), _L("Hotends"), wxSize(FromDIP(126), FromDIP(26)));
m_ams_rack_switch->updateState("left");
@@ -1631,12 +1647,12 @@ wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
m_ams_rack_switch->Bind(wxCUSTOMEVT_SWITCH_POS, &StatusBasePanel::on_ams_rack_switch, this);
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(8));
bSizer_control->Add(temp_axis_ctrl_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(temp_axis_slot, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(m_ams_rack_switch, 0, wxALIGN_CENTRE|wxTOP, FromDIP(6));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(6));
bSizer_control->Add(ams_rack_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(6));
bSizer_control->Add(m_filament_load_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(filament_slot, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(4));
bSizer_right->Add(bSizer_control, 1, wxEXPAND | wxALL, 0);
@@ -2228,8 +2244,10 @@ void StatusBasePanel::expand_filament_loading(wxMouseEvent& e)
m_filament_step->Show(tag_show);
Layout();
Fit();
wxGetApp().mainframe->m_monitor->get_status_panel()->Layout();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->get_status_panel()->Layout();
monitor->Layout();
}
}
void StatusBasePanel::show_ams_group(bool show)
@@ -2240,7 +2258,8 @@ void StatusBasePanel::show_ams_group(bool show)
m_ams_control->Fit();
Layout();
Fit();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->Layout();
}
// On rack printers, don't clobber the rack view when the user has the switch on "Hotends".
@@ -2253,7 +2272,8 @@ void StatusBasePanel::show_ams_group(bool show)
m_ams_control->Fit();
Layout();
Fit();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->Layout();
}
}
@@ -2277,8 +2297,10 @@ void StatusBasePanel::show_filament_load_group(bool show)
Layout();
Fit();
wxGetApp().mainframe->m_monitor->get_status_panel()->Layout();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->get_status_panel()->Layout();
monitor->Layout();
}
}
}
@@ -2403,6 +2425,12 @@ void StatusPanel::update_camera_state(MachineObject* obj)
StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name)
: StatusBasePanel(parent, id, pos, size, style)
{
// Wires the controls the base class builds in steps, so it is the last step.
add_build_step([this] { wire_controls(); });
}
void StatusPanel::wire_controls()
{
init_scaled_buttons();
m_buttons.push_back(m_bpButton_z_10);
@@ -2514,47 +2542,50 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co
StatusPanel::~StatusPanel()
{
// Disconnect Events
m_project_task_panel->get_bitmap_thumbnail()->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this);
m_project_task_panel->get_partskip_button()->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this);
m_project_task_panel->get_pause_resume_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
m_project_task_panel->get_abort_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
m_project_task_panel->get_market_scoring_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
m_project_task_panel->get_market_retry_buttom()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
m_project_task_panel->get_clean_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_print_error_clean), NULL, this);
// The controls only exist once the last build step has run.
if (built()) {
// Disconnect Events
m_project_task_panel->get_bitmap_thumbnail()->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this);
m_project_task_panel->get_partskip_button()->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this);
m_project_task_panel->get_pause_resume_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
m_project_task_panel->get_abort_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
m_project_task_panel->get_market_scoring_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
m_project_task_panel->get_market_retry_buttom()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
m_project_task_panel->get_clean_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_print_error_clean), NULL, this);
m_setting_button->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
m_setting_button->Disconnect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
m_tempCtrl_bed->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_kill_focus), NULL, this);
m_tempCtrl_bed->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_set_focus), NULL, this);
m_tempCtrl_nozzle->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this);
m_tempCtrl_nozzle->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this);
m_setting_button->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
m_setting_button->Disconnect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
m_tempCtrl_bed->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_kill_focus), NULL, this);
m_tempCtrl_bed->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_set_focus), NULL, this);
m_tempCtrl_nozzle->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this);
m_tempCtrl_nozzle->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this);
m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this);
m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this);
m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this);
m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this);
m_switch_lamp->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_lamp_switch), NULL, this);
/*m_switch_nozzle_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_printing_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_cham_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);*/
m_switch_lamp->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_lamp_switch), NULL, this);
/*m_switch_nozzle_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_printing_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_cham_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);*/
//m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
//m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
//m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
//m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_bpButton_xy->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_xy), NULL, this);
m_bpButton_z_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_up_10), NULL, this);
m_bpButton_z_1->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_up_1), NULL, this);
m_bpButton_z_down_1->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_down_1), NULL, this);
m_bpButton_z_down_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_down_10), NULL, this);
m_bpButton_e_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_up_10), NULL, this);
m_bpButton_e_down_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_down_10), NULL, this);
m_nozzle_btn_panel->Disconnect(wxCUSTOMEVT_SWITCH_POS, wxCommandEventHandler(StatusPanel::on_nozzle_selected), NULL, this);
m_switch_speed->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_switch_speed), NULL, this);
m_calibration_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_start_calibration), NULL, this);
m_options_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_print_options), NULL, this);
m_safety_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_safety_options), NULL, this);
m_parts_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_parts_options), NULL, this);
m_bpButton_xy->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_xy), NULL, this);
m_bpButton_z_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_up_10), NULL, this);
m_bpButton_z_1->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_up_1), NULL, this);
m_bpButton_z_down_1->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_down_1), NULL, this);
m_bpButton_z_down_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_down_10), NULL, this);
m_bpButton_e_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_up_10), NULL, this);
m_bpButton_e_down_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_down_10), NULL, this);
m_nozzle_btn_panel->Disconnect(wxCUSTOMEVT_SWITCH_POS, wxCommandEventHandler(StatusPanel::on_nozzle_selected), NULL, this);
m_switch_speed->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_switch_speed), NULL, this);
m_calibration_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_start_calibration), NULL, this);
m_options_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_print_options), NULL, this);
m_safety_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_safety_options), NULL, this);
m_parts_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_parts_options), NULL, this);
}
// remove warning dialogs
if (abort_dlg != nullptr)
@@ -5216,7 +5247,9 @@ void StatusPanel::set_default()
m_filament_step->Hide();
error_info_reset();
#ifndef __WXGTK__
SetFocus();
// Also reached while the panel is built off screen.
if (IsShownOnScreen())
SetFocus();
#endif
}
+5 -3
View File
@@ -36,6 +36,7 @@
#include "HMS.hpp"
#include "PartSkipDialog.hpp"
#include "DeviceErrorDialog.hpp"
#include "StagedBuild.hpp"
class StepIndicator;
@@ -375,7 +376,7 @@ public:
void paint(wxPaintEvent&);
};
class StatusBasePanel : public wxScrolledWindow
class StatusBasePanel : public wxScrolledWindow, public StagedBuild
{
protected:
wxBitmap m_item_placeholder;
@@ -443,7 +444,7 @@ protected:
wxMediaCtrl3 * m_media_ctrl;
MediaPlayCtrl * m_media_play_ctrl;
MediaPlayCtrl * m_media_play_ctrl{nullptr};
Label * m_staticText_printing;
wxStaticBitmap *m_bitmap_thumbnail;
@@ -629,6 +630,7 @@ class StatusPanel : public StatusBasePanel
{
private:
friend class MonitorPanel;
void wire_controls();
protected:
std::shared_ptr<SliceInfoPopup> m_slice_info_popup;
@@ -679,7 +681,7 @@ protected:
std::map<std::string, std::string> m_print_connect_types;
std::vector<Button *> m_buttons;
int last_status;
ScoreData *m_score_data;
ScoreData *m_score_data = nullptr;
wxBitmap* calib_bitmap = nullptr;
CalibMode m_calib_mode;
CalibrationMethod m_calib_method;
+56 -17
View File
@@ -804,6 +804,10 @@ void Tab::OnActivate()
}
#endif
// The OnActivate() that shows the tab builds the page.
if (wxGetApp().mainframe != nullptr && !wxGetApp().mainframe->is_active_and_shown_tab(m_parent))
return;
// BBS: select on first active
if (!m_active_page)
restore_last_select_item();
@@ -7159,6 +7163,16 @@ void Tab::restore_last_select_item()
m_tabctrl->SelectItem(item);
}
bool Tab::page_build_pending() const
{
return m_active_page != nullptr && m_active_page->build_pending();
}
bool Tab::page_build_step()
{
return m_active_page != nullptr && m_active_page->build_step(m_mode);
}
void Tab::update_description_lines()
{
if (m_active_page && m_active_page->title() == "Dependencies" && m_parent_preset_description_line)
@@ -7375,7 +7389,7 @@ void Tab::OnKeyDown(wxKeyEvent& event)
void Tab::compare_preset()
{
wxGetApp().mainframe->diff_dialog.show(m_type);
DiffPresetDialog::ensure()->show(m_type);
}
void Tab::transfer_options(const std::string &name_from, const std::string &name_to, std::vector<std::string> options)
@@ -7543,8 +7557,10 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach, bool save_to_proje
wxGetApp().get_tab(preset_type)->update_tab_ui();
}
// update preset comboboxes in DiffPresetDlg
wxGetApp().mainframe->diff_dialog.update_presets(m_type);
// show() reloads the presets, so only a visible Compare dialog needs updating.
DiffPresetDialog* diff_dialog = DiffPresetDialog::if_built();
if (diff_dialog != nullptr && diff_dialog->IsShown())
diff_dialog->update_presets(m_type);
}
// Called for a currently selected preset.
@@ -8604,20 +8620,8 @@ void Page::activate(ConfigOptionMode mode, std::function<void()> throw_if_cancel
#else
//m_vsizer->AddSpacer(10);
#endif
#if HIDE_FIRST_SPLIT_LINE
// BBS: no line spliter for first group
bool first = true;
#endif
for (auto group : m_optgroups) {
if (!group->activate(throw_if_canceled))
continue;
m_vsizer->Add(group->sizer, 0, wxEXPAND | (group->is_legend_line() ? (wxLEFT|wxTOP) : wxALL), m_parent->FromDIP(5)); // ORCA use less margin on parameters section
group->update_visibility(mode);
#if HIDE_FIRST_SPLIT_LINE
if (first) group->stb->Hide();
first = false;
#endif
group->reload_config();
for (size_t i = 0; i < m_optgroups.size(); ++i) {
activate_group(i, mode, throw_if_canceled);
throw_if_canceled();
}
@@ -8636,6 +8640,41 @@ void Page::activate(ConfigOptionMode mode, std::function<void()> throw_if_cancel
#endif
}
// Builds one option group; false when it already has its controls.
bool Page::activate_group(size_t i, ConfigOptionMode mode, std::function<void()> throw_if_canceled)
{
auto& group = m_optgroups[i];
if (!group->activate(throw_if_canceled))
return false;
m_vsizer->Add(group->sizer, 0, wxEXPAND | (group->is_legend_line() ? (wxLEFT|wxTOP) : wxALL), m_parent->FromDIP(5)); // ORCA use less margin on parameters section
group->update_visibility(mode);
#if HIDE_FIRST_SPLIT_LINE
// BBS: no line spliter for first group
if (i == 0) group->stb->Hide();
#endif
group->reload_config();
return true;
}
// The first group without controls.
size_t Page::next_group_to_build() const
{
return std::find_if(m_optgroups.begin(), m_optgroups.end(), [](const auto& group) { return !group->is_activated(); }) - m_optgroups.begin();
}
bool Page::build_pending() const
{
return next_group_to_build() < m_optgroups.size();
}
bool Page::build_step(ConfigOptionMode mode)
{
const size_t i = next_group_to_build();
if (i < m_optgroups.size())
activate_group(i, mode, [] {});
return build_pending();
}
void Page::clear()
{
for (auto group : m_optgroups)
+10
View File
@@ -94,6 +94,10 @@ public:
void reload_config();
void update_visibility(ConfigOptionMode mode, bool update_contolls_visibility);
void activate(ConfigOptionMode mode, std::function<void()> throw_if_canceled);
// Whether an option group has no controls yet.
bool build_pending() const;
// Builds the next option group that has no controls yet; true while some remain.
bool build_step(ConfigOptionMode mode);
void clear();
void msw_rescale();
void sys_color_changed();
@@ -121,6 +125,8 @@ public:
std::map<std::string, std::string> m_opt_id_map;
protected:
size_t next_group_to_build() const;
bool activate_group(size_t i, ConfigOptionMode mode, std::function<void()> throw_if_canceled);
// Color of TreeCtrlItem. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_item_color;
};
@@ -437,6 +443,10 @@ public:
// BBS: new layout
void set_expanded(bool value);
void restore_last_select_item();
// page_build_pending() says whether the selected page has groups without controls, and
// page_build_step() builds one.
bool page_build_pending() const;
bool page_build_step();
static bool validate_custom_gcode(const wxString& title, const std::string& gcode);
bool validate_custom_gcodes();
+3 -2
View File
@@ -1962,8 +1962,9 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe)
assert(wxGetApp().preset_bundle);
m_preset_bundle_left = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle);
m_preset_bundle_right = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle);
// show() copies the app's bundle into both before anything is displayed.
m_preset_bundle_left = std::make_unique<PresetBundle>();
m_preset_bundle_right = std::make_unique<PresetBundle>();
// Create UI items
+2 -1
View File
@@ -10,6 +10,7 @@
#include "libslic3r/PresetBundle.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/ScrolledWindow.hpp"
#include "Lazy.hpp"
class ScalableButton;
class wxStaticText;
@@ -416,7 +417,7 @@ public:
//------------------------------------------
// DiffPresetDialog
//------------------------------------------
class DiffPresetDialog : public DPIDialog
class DiffPresetDialog : public DPIDialog, public LazyInstance<DiffPresetDialog>
{
DiffViewCtrl* m_tree { nullptr };
wxBoxSizer* m_presets_sizer { nullptr };
+36 -31
View File
@@ -1159,10 +1159,25 @@ bool GuideFrame::run()
}
int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList, std::string filepath, std::string &sVendor, std::string &sType)
{
std::unordered_set<std::string> visiting;
return GetFilamentInfo(VendorDirectory, pFilaList, filepath, sVendor, sType, visiting);
}
int GuideFrame::GetFilamentInfo(const std::string& VendorDirectory, json& pFilaList,
const std::string& filepath, std::string& sVendor,
std::string& sType, std::unordered_set<std::string>& visiting)
{
//GetStardardFilePath(filepath);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " GetFilamentInfo:VendorDirectory - " << VendorDirectory << ", Filepath - "<<filepath;
// Path-scoped guard: without it an `inherits` cycle would recurse forever.
// The repeated file was not inserted, so the cycle hit has nothing to erase.
if (!visiting.insert(filepath).second) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inherits cycle at " << filepath;
return -1;
}
// Resolve this file's own vendor/type into LOCAL variables, independent of
// whatever the caller already accumulated. The cache entry for `filepath`
// must reflect only this file's own inherits chain — passing the caller's
@@ -1197,41 +1212,30 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
else
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << filepath << " - Not Contains filament_type";
if (vendor == "" || type == "") {
if (jLocal.contains("inherits")) {
std::string FName = jLocal["inherits"];
if (jLocal.contains("inherits")) {
std::string FName = jLocal["inherits"];
if (!pFilaList.contains(FName)) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "pFilaList - Not Contains inherits filaments: " << FName;
status = -1;
} else {
std::string FPath = pFilaList[FName]["sub_path"];
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Before Format Inherits Path: VendorDirectory - " << VendorDirectory << ", sub_path - " << FPath;
wxString strNewFile = wxString::Format("%s%c%s", wxString(VendorDirectory.c_str(), wxConvUTF8), boost::filesystem::path::preferred_separator, FPath);
boost::filesystem::path inherits_path(w2s(strNewFile));
if (!boost::filesystem::exists(inherits_path))
inherits_path = (boost::filesystem::path(m_OrcaFilaLibPath) / boost::filesystem::path(FPath)).make_preferred();
if (boost::filesystem::exists(inherits_path)) {
// Recurse with this file's own (vendor, type) as the chain accumulator.
status = GetFilamentInfo(VendorDirectory, pFilaList, inherits_path.string(), vendor, type);
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inherits File Not Exist: " << inherits_path;
status = -1;
}
}
if (!pFilaList.contains(FName)) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "pFilaList - Not Contains inherits filaments: " << FName;
status = -1;
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << filepath << " - Not Contains inherits";
if (type == "") {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "sType is Empty";
status = -1;
std::string FPath = pFilaList[FName]["sub_path"];
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Before Format Inherits Path: VendorDirectory - " << VendorDirectory << ", sub_path - " << FPath;
wxString strNewFile = wxString::Format("%s%c%s", wxString(VendorDirectory.c_str(), wxConvUTF8), boost::filesystem::path::preferred_separator, FPath);
boost::filesystem::path inherits_path(w2s(strNewFile));
if (!boost::filesystem::exists(inherits_path))
inherits_path = (boost::filesystem::path(m_OrcaFilaLibPath) / boost::filesystem::path(FPath)).make_preferred();
if (boost::filesystem::exists(inherits_path)) {
// Traverse the full chain for errors; inherited values only fill local gaps.
status = GetFilamentInfo(VendorDirectory, pFilaList, inherits_path.string(), vendor, type, visiting);
} else {
if (vendor == "")
vendor = "Generic";
status = 0;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inherits File Not Exist: " << inherits_path;
status = -1;
}
}
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << filepath << " - Not Contains inherits";
status = 0;
}
}
@@ -1250,6 +1254,7 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
// Merge this file's resolved values into the caller's out-params (fill empties only).
if (sVendor.empty()) sVendor = vendor;
if (sType.empty()) sType = type;
visiting.erase(filepath);
return status;
}
@@ -1803,8 +1808,8 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
std::string sT;
int nRet = GetFilamentInfo(vendor_dir.string(),tFilaList, sub_file, sV, sT);
if (nRet != 0) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Load Filament:" << s1 << ",GetFilamentInfo Failed, Vendor:" << sV << ",Type:"<< sT;
if (nRet != 0 || sV.empty() || sT.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Load Filament:" << s1 << ",unresolved vendor/type, Vendor:" << sV << ",Type:"<< sT;
continue;
}
+4
View File
@@ -33,6 +33,7 @@
#include <atomic>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <nlohmann/json.hpp>
@@ -110,6 +111,9 @@ public:
void on_dpi_changed(const wxRect &suggested_rect) {}
private:
int GetFilamentInfo(const std::string& VendorDirectory, json& pFilaList, const std::string& filepath,
std::string& sVendor, std::string& sType, std::unordered_set<std::string>& visiting);
GUI_App *m_MainPtr;
AppConfig m_appconfig_new;
+2 -2
View File
@@ -219,6 +219,7 @@ WebViewPanel::WebViewPanel(wxWindow *parent)
Bind(wxEVT_CLOSE_WINDOW, &WebViewPanel::OnClose, this);
m_LoginUpdateTimer = nullptr;
update_mode();
}
WebViewPanel::~WebViewPanel()
@@ -416,8 +417,7 @@ void WebViewPanel::OnClose(wxCloseEvent& evt)
void WebViewPanel::OnFreshLoginStatus(wxTimerEvent &event)
{
auto mainframe = Slic3r::GUI::wxGetApp().mainframe;
if (mainframe && mainframe->m_webview == this) {
if (WebViewPanel::if_built() == this) {
auto* app_config = Slic3r::GUI::wxGetApp().app_config;
if (app_config && app_config->get_stealth_mode()) return;
Slic3r::GUI::wxGetApp().get_login_info(ORCA_CLOUD_PROVIDER);
+2 -1
View File
@@ -24,6 +24,7 @@
#include <wx/tbarbase.h>
#include "wx/textctrl.h"
#include <wx/timer.h>
#include "Lazy.hpp"
namespace Slic3r {
@@ -33,7 +34,7 @@ class NetworkAgent;
namespace GUI {
class WebViewPanel : public wxPanel
class WebViewPanel : public wxPanel, public LazyInstance<WebViewPanel>
{
public:
WebViewPanel(wxWindow *parent);
+50 -8
View File
@@ -1,7 +1,9 @@
#import "MacDarkMode.hpp"
#include "../GUI/Widgets/Label.hpp"
#include "wx/graphics.h"
#include "wx/osx/core/cfstring.h"
#include "wx/osx/private.h"
#import <algorithm>
@@ -348,11 +350,28 @@ bool addObserver = false;
}
@end
// Orca: A Shift-trackpad pan belongs to one GL view; sharing its lifecycle across views
// could make a gesture reuse another canvas's world-space anchor.
static char scroll_pan_active_key;
static char gesture_handler_key;
static wxEvtHandler* get_gesture_handler(NSView* view)
{
return static_cast<wxEvtHandler*>([objc_getAssociatedObject(view, &gesture_handler_key) pointerValue]);
}
static bool is_scroll_pan_active(NSView* view)
{
return [objc_getAssociatedObject(view, &scroll_pan_active_key) boolValue];
}
static void set_scroll_pan_active(NSView* view, bool active)
{
objc_setAssociatedObject(view, &scroll_pan_active_key, active ? @YES : nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@implementation wxNSCustomOpenGLView (Gesture)
wxEvtHandler * _gestureHandler = nullptr;
- (void) onGestureMove: (NSPanGestureRecognizer*) gesture
{
wxPanGestureEvent evt;
@@ -378,22 +397,43 @@ wxEvtHandler * _gestureHandler = nullptr;
- (void) postEvent: (wxGestureEvent &) evt withGesture: (NSGestureRecognizer* ) gesture
{
NSPoint pos = [gesture locationInView: self];
evt.SetPosition({(int) pos.x, (int) pos.y});
evt.SetPosition(wxFromNSPoint(self, pos));
if (gesture.state == NSGestureRecognizerStateBegan)
evt.SetGestureStart();
else if (gesture.state == NSGestureRecognizerStateEnded)
evt.SetGestureEnd();
_gestureHandler->ProcessEvent(evt);
if (wxEvtHandler* handler = get_gesture_handler(self))
handler->ProcessEvent(evt);
}
- (void) scrollWheel2:(NSEvent *)event
{
bool shiftDown = [event modifierFlags] & NSShiftKeyMask;
if (_gestureHandler && shiftDown && event.hasPreciseScrollingDeltas) {
wxEvtHandler* handler = get_gesture_handler(self);
if (handler && shiftDown && event.hasPreciseScrollingDeltas) {
wxPanGestureEvent evt;
evt.SetDelta({-(int)[event scrollingDeltaX], - (int)[event scrollingDeltaY]});
_gestureHandler->ProcessEvent(evt);
// NSOpenGLView uses bottom-left coordinates; wx gestures use top-left coordinates.
const wxPoint pos = wxFromNSPoint(self, [self convertPoint:[event locationInWindow] fromView:nil]);
const wxPoint delta(-(int)[event scrollingDeltaX], -(int)[event scrollingDeltaY]);
// Orca: GLCanvas3D derives the anchor position as position - delta, so synthesize
// the post-delta position from the native cursor coordinate.
evt.SetPosition(pos + delta);
evt.SetDelta(delta);
// Orca: Preserve the anchor throughout a trackpad scroll, including its momentum events.
// Keep it after phase Ended: momentum may follow. The next Began replaces it.
const NSEventPhase phase = event.phase;
const NSEventPhase momentum_phase = event.momentumPhase;
const bool unphased = phase == NSEventPhaseNone && momentum_phase == NSEventPhaseNone;
if (!is_scroll_pan_active(self) || unphased || (phase & (NSEventPhaseMayBegin | NSEventPhaseBegan)))
evt.SetGestureStart();
if (unphased || (phase & NSEventPhaseCancelled) ||
(momentum_phase & (NSEventPhaseEnded | NSEventPhaseCancelled)))
evt.SetGestureEnd();
set_scroll_pan_active(self, !evt.IsGestureEnd());
handler->ProcessEvent(evt);
} else {
// Orca: Switching away from Shift-pan must not reuse its depth when Shift is pressed again.
set_scroll_pan_active(self, false);
[self scrollWheel2: event];
}
}
@@ -415,7 +455,9 @@ wxEvtHandler * _gestureHandler = nullptr;
// [self addGestureRecognizer:pan];
// [self addGestureRecognizer:magnification];
// [self addGestureRecognizer:rotation];
_gestureHandler = handler;
objc_setAssociatedObject(self, &gesture_handler_key, handler ? [NSValue valueWithPointer:handler] : nil,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
set_scroll_pan_active(self, false);
}
@end