Compare commits

...

6 Commits

Author SHA1 Message Date
Ian Chua
e00906a833 feat: plugin pages 2026-07-28 19:17:26 +08:00
Ian Chua
56c28fc102 feat: refactor notebook/tabs to be string based instead of fixed index based 2026-07-28 19:17:05 +08:00
Ian Chua
43725128d3 chore: make console logging optional (#14932)
# Description

Adding optional flag for enabling console logging introduced in #14439.
Console logging not working with LLDB-DAP on VSCode as per #14897
Code changes won't fix this because it is a LLDB-DAP issue documented in
#14909

Use flag `-DUSE_SLIC3R_CONSOLE_LOG=ON` or `-DUSE_SLIC3R_CONSOLE_LOG=OFF`
to enable/disable it.
Alternatively in you VSCode's settings.json, 
```
"cmake.configureSettings": {
   "USE_SLIC3R_CONSOLE_LOG": "OFF"
}
```

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-24 14:23:21 +08:00
SoftFever
51612b504a Silence CMake configure warnings (#14925)
# Description

Remove DEPENDS/empty-COMMAND args that are invalid in the
add_custom_command(TARGET) form (CMP0175), fix the FindDraco.cmake case
mismatch, and opt Boost lookup into upstream BoostConfig via CMP0167 for
the OpenVDB module and the CGAL find.
# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-24 14:13:35 +08:00
Ian Chua
1ce21c98ac chore: make console logging optional 2026-07-24 12:22:57 +08:00
SoftFever
5b32213d44 Silence CMake configure warnings
Remove DEPENDS/empty-COMMAND args that are invalid in the
add_custom_command(TARGET) form (CMP0175), fix the FindDraco.cmake case
mismatch, and opt Boost lookup into upstream BoostConfig via CMP0167 for
the OpenVDB module and the CGAL find.
2026-07-24 00:56:56 +08:00
37 changed files with 868 additions and 223 deletions

View File

@@ -853,7 +853,6 @@ foreach(po_file ${BBL_L10N_PO_FILES})
add_custom_command(
TARGET gettext_merge_po_with_pot PRE_BUILD
COMMAND msgmerge -N -o ${po_file} ${po_file} "${BBL_L18N_DIR}/OrcaSlicer.pot"
DEPENDS ${po_file}
)
endforeach()
add_custom_target(gettext_po_to_mo
@@ -869,7 +868,6 @@ foreach(po_file ${BBL_L10N_PO_FILES})
TARGET gettext_po_to_mo PRE_BUILD
COMMAND msgfmt ARGS --check-format -o ${mo_file} ${po_file}
#COMMAND msgfmt ARGS --check-compatibility -o ${mo_file} ${po_file}
DEPENDS ${po_file}
)
endforeach()

View File

@@ -128,6 +128,10 @@ cmake_minimum_required(VERSION 3.13)
if(POLICY CMP0074)
cmake_policy(SET CMP0074 NEW)
endif()
# Re-set after cmake_minimum_required above cleared it; use BoostConfig, not the removed FindBoost.
if(POLICY CMP0167)
cmake_policy(SET CMP0167 NEW)
endif()
if(OpenVDB_FIND_QUIETLY)
set (_quiet "QUIET")

View File

@@ -283,10 +283,6 @@ else ()
COMMAND ln -sf OrcaSlicer orca-slicer
WORKING_DIRECTORY "$<TARGET_FILE_DIR:OrcaSlicer>"
VERBATIM)
else ()
add_custom_command(TARGET OrcaSlicer POST_BUILD
WORKING_DIRECTORY "$<TARGET_FILE_DIR:OrcaSlicer>"
VERBATIM)
endif ()
if (XCODE)
# Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level

View File

@@ -19,6 +19,7 @@ if (TARGET OpenVDB::openvdb)
endif()
option(BUILD_SHARED_LIBS "Build shared libs" OFF)
option(USE_SLIC3R_CONSOLE_LOG "Enable console logging in RelWithDebInfo builds" OFF)
set(lisbslic3r_sources
AABBMesh.cpp
@@ -497,8 +498,12 @@ set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
cmake_policy(PUSH)
cmake_policy(SET CMP0011 NEW)
# CGAL's config resets policies (cmake_minimum_required ...3.23), so a plain SET
# can't reach it; the default opts its Boost lookup into BoostConfig (CMP0167).
set(CMAKE_POLICY_DEFAULT_CMP0167 NEW)
find_package(CGAL REQUIRED)
find_package(OpenCV REQUIRED core)
unset(CMAKE_POLICY_DEFAULT_CMP0167)
cmake_policy(POP)
add_library(libslic3r_cgal STATIC
@@ -535,7 +540,9 @@ endif ()
encoding_check(libslic3r)
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
target_compile_definitions(libslic3r PRIVATE $<$<CONFIG:RelWithDebInfo>:SLIC3R_CONSOLE_LOG>)
if (USE_SLIC3R_CONSOLE_LOG)
target_compile_definitions(libslic3r PRIVATE $<$<CONFIG:RelWithDebInfo>:SLIC3R_CONSOLE_LOG>)
endif()
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(libslic3r SYSTEM PUBLIC ${EXPAT_INCLUDE_DIRS})
@@ -545,7 +552,7 @@ find_package(OpenCASCADE REQUIRED)
target_include_directories(libslic3r SYSTEM PUBLIC ${OpenCASCADE_INCLUDE_DIR})
find_package(JPEG REQUIRED)
find_package(draco REQUIRED)
find_package(Draco REQUIRED)
set(OCCT_LIBS
TKXDESTEP

View File

@@ -620,6 +620,8 @@ set(SLIC3R_GUI_SOURCES
plugin/host/PluginHostSlicing.cpp
plugin/host/PluginHostUi.cpp
plugin/host/PluginHostUi.hpp
plugin/host/PluginPages.cpp
plugin/host/PluginPages.hpp
plugin/CloudPluginService.cpp
plugin/CloudPluginService.hpp
plugin/PluginFsUtils.cpp
@@ -640,6 +642,9 @@ set(SLIC3R_GUI_SOURCES
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp
plugin/pluginTypes/pages/PagesPluginCapability.hpp
plugin/pluginTypes/pages/PagesPluginCapability.cpp
plugin/pluginTypes/pages/PagesPluginCapabilityTrampoline.hpp
plugin/pluginTypes/script/ScriptPluginCapability.hpp
plugin/pluginTypes/script/ScriptPluginCapability.cpp
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp

View File

@@ -134,7 +134,7 @@ void Downloader::start_download(const std::string& full_url)
Plater* plater = wxGetApp().plater();
mainframe->Freeze();
mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
mainframe->select_tab(TAB_ID_PREPARE);
plater->select_view_3D("3D");
plater->select_view("plate");
plater->get_current_canvas3D()->zoom_to_bed();

View File

@@ -330,8 +330,10 @@ void Field::PostInitialize()
}
default: break;
}
if (tab_id >= 0)
wxGetApp().mainframe->select_tab(tab_id);
if (tab_id >= 0) {
static constexpr const char* kShortcutTabIds[] = {TAB_ID_HOME, TAB_ID_PREPARE, TAB_ID_PREVIEW, TAB_ID_MONITOR};
wxGetApp().mainframe->select_tab(kShortcutTabIds[tab_id]);
}
if (tab_id > 0)
// tab panel should be focused for correct navigation between tabs
wxGetApp().tab_panel()->SetFocus();

View File

@@ -9132,7 +9132,7 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
view3d_canvas->get_gizmos_manager().reset_all_states(); // close all gizmos
view3d_canvas->reload_scene(true);
}
app.mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
app.mainframe->select_tab(TAB_ID_PREPARE);
}
}
});

View File

@@ -798,12 +798,12 @@ void GUI_App::post_init()
m_open_method = "url";
} else {
if (this->init_params->input_gcode) {
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
mainframe->select_tab(TAB_ID_PREPARE);
plater_->select_view_3D("3D");
this->plater()->load_gcode(from_u8(this->init_params->input_files.front()));
m_open_method = "gcode";
} else {
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
mainframe->select_tab(TAB_ID_PREPARE);
plater_->select_view_3D("3D");
wxArrayString input_files;
for (auto& file : this->init_params->input_files) {
@@ -837,7 +837,7 @@ void GUI_App::post_init()
mainframe->Freeze();
#endif
plater_->canvas3D()->enable_render(false);
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
mainframe->select_tab(TAB_ID_PREPARE);
plater_->select_view_3D("3D");
//BBS init the opengl resource here
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
@@ -875,9 +875,9 @@ void GUI_App::post_init()
}
}
if (is_editor())
mainframe->select_tab(size_t(0));
mainframe->select_tab(TAB_ID_HOME);
if (app_config->get("default_page") == "1")
mainframe->select_tab(size_t(1));
mainframe->select_tab(TAB_ID_PREPARE);
#ifndef __linux__
mainframe->Thaw();
#endif
@@ -1814,10 +1814,10 @@ bool GUI_App::hot_reload_network_plugin()
wxWindowDisabler disabler;
if (mainframe) {
int current_tab = mainframe->m_tabpanel->GetSelection();
if (current_tab == MainFrame::TabPosition::tpMonitor) {
wxString current_tab = mainframe->m_tabpanel->GetSelectedPageName();
if (current_tab == TAB_ID_MONITOR) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": navigating away from Monitor tab before unload";
mainframe->m_tabpanel->SetSelection(MainFrame::TabPosition::tp3DEditor);
mainframe->m_tabpanel->SelectPageByName(TAB_ID_PREPARE);
}
}
@@ -2799,6 +2799,16 @@ void GUI_App::init_plugin_gui_wiring()
plugin_mgr.subscribe_on_unload_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
plugin_mgr.subscribe_on_load_callback(NetworkAgentFactory::register_python_plugin);
plugin_mgr.subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin);
plugin_mgr.subscribe_on_load_callback([](const std::string& plugin_key) {
if (wxTheApp == nullptr || wxGetApp().is_closing() || wxGetApp().mainframe == nullptr)
return;
wxGetApp().mainframe->plugin_pages().on_plugin_register(plugin_key);
});
plugin_mgr.subscribe_on_unload_callback([](const std::string& plugin_key) {
if (wxTheApp == nullptr || wxGetApp().is_closing() || wxGetApp().mainframe == nullptr)
return;
wxGetApp().mainframe->plugin_pages().on_plugin_deregister(plugin_key);
});
plugin_mgr.subscribe_on_capability_load_callback(
[refresh_plugins_dialog](const PluginCapabilityId& capability) {
if (capability.type == PluginCapabilityType::PrinterConnection)
@@ -2811,11 +2821,15 @@ void GUI_App::init_plugin_gui_wiring()
if (Plater* plater = wxGetApp().plater())
plater->revalidate_current_plate_if_plugins_missing();
});
if (capability.type == PluginCapabilityType::Pages && wxTheApp && !wxGetApp().is_closing() && wxGetApp().mainframe)
wxGetApp().mainframe->plugin_pages().on_cap_register(capability);
});
plugin_mgr.subscribe_on_capability_unload_callback(
[refresh_plugins_dialog](const PluginCapabilityId& capability) {
if (capability.type == PluginCapabilityType::PrinterConnection)
NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name);
if (capability.type == PluginCapabilityType::Pages && wxTheApp && !wxGetApp().is_closing() && wxGetApp().mainframe)
wxGetApp().mainframe->plugin_pages().on_cap_deregister(capability);
refresh_plugins_dialog();
});
}
@@ -3323,7 +3337,7 @@ bool GUI_App::on_init_inner()
mainframe = new MainFrame();
// hide settings tabs after first Layout
if (is_editor()) {
mainframe->select_tab(size_t(0));
mainframe->select_tab(TAB_ID_HOME);
}
sidebar().obj_list()->init();
@@ -4487,7 +4501,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
mainframe = new MainFrame();
if (is_editor())
// hide settings tabs after first Layout
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
mainframe->select_tab(TAB_ID_PREPARE);
// Propagate model objects to object list.
sidebar().obj_list()->init();
//sidebar().aux_list()->init_auxiliary();

View File

@@ -493,9 +493,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
});
//BBS
Bind(EVT_SELECT_TAB, [this](wxCommandEvent&evt) {
TabPosition pos = (TabPosition)evt.GetInt();
m_tabpanel->SetSelection(pos);
Bind(EVT_SELECT_TAB, [this](wxCommandEvent& evt) {
m_tabpanel->SelectPageByName(evt.GetString());
});
Bind(EVT_SYNC_CLOUD_PRESET, &MainFrame::on_select_default_preset, this);
@@ -702,7 +701,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
}
return;}
#endif
if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SetSelection(tpPreview); } return; }
if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW); } return; }
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'G') {
m_plater->apply_background_progress();
m_print_enable = get_enable_print_status();
@@ -723,7 +722,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'S') { if (can_save_as()) m_plater->save_project(true); return;}
else if (evt.CmdDown() && evt.GetKeyCode() == 'S') { if (can_save()) m_plater->save_project(); return;}
if (evt.CmdDown() && evt.GetKeyCode() == 'F') {
if (m_plater && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview)) {
if (m_plater && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE || m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW)) {
m_plater->sidebar().can_search();
}
}
@@ -1007,8 +1006,8 @@ void MainFrame::update_layout()
m_layout = layout;
// From the very beginning the Print settings should be selected
//m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? 0 : 1;
m_last_selected_tab = 1;
//m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? TAB_ID_HOME : TAB_ID_PREPARE;
m_last_selected_tab = TAB_ID_PREPARE;
// Set new settings
switch (m_layout)
@@ -1016,14 +1015,18 @@ void MainFrame::update_layout()
case ESettingsLayout::Old:
{
m_plater->Reparent(m_tabpanel);
m_tabpanel->InsertPage(tp3DEditor, m_plater, _L("Prepare"), std::string("tab_3d_active"), std::string("tab_3d_active"), false);
m_tabpanel->InsertPage(tpPreview, m_plater, _L("Preview"), std::string("tab_preview_active"), std::string("tab_preview_active"), false);
{
const int home_idx = m_tabpanel->FindPageByName(TAB_ID_HOME);
const size_t prepare_pos = (home_idx == wxNOT_FOUND) ? 0 : static_cast<size_t>(home_idx) + 1;
m_tabpanel->InsertPage(prepare_pos, TAB_ID_PREPARE, m_plater, _L("Prepare"), std::string("tab_3d_active"), std::string("tab_3d_active"), false);
m_tabpanel->InsertPage(prepare_pos + 1, TAB_ID_PREVIEW, m_plater, _L("Preview"), std::string("tab_preview_active"), std::string("tab_preview_active"), false);
}
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 0);
m_tabpanel->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, [this](wxCommandEvent& evt)
{
// jump to 3deditor under preview_only mode
if (evt.GetId() == tp3DEditor){
if (evt.GetId() == m_tabpanel->FindPageByName(TAB_ID_PREPARE)) {
Sidebar& sidebar = GUI::wxGetApp().sidebar();
if (sidebar.need_auto_sync_after_connect_printer()) {
sidebar.set_need_auto_sync_after_connect_printer(false);
@@ -1107,6 +1110,7 @@ void MainFrame::update_edge_panels()
void MainFrame::shutdown()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
m_plugin_pages.shutdown();
#ifdef __WXGTK__
// Edge panels are child windows — wxWidgets destroys them automatically.
m_edge_bottom = nullptr;
@@ -1252,15 +1256,14 @@ void MainFrame::init_tabpanel() {
#endif
//BBS
wxWindow* panel = m_tabpanel->GetCurrentPage();
int sel = m_tabpanel->GetSelection();
//wxString page_text = m_tabpanel->GetPageText(sel);
m_last_selected_tab = m_tabpanel->GetSelection();
m_last_selected_tab = m_tabpanel->GetSelectedPageName();
if (panel == m_plater) {
if (sel == tp3DEditor) {
if (m_last_selected_tab == TAB_ID_PREPARE) {
wxPostEvent(m_plater, SimpleEvent(EVT_GLVIEWTOOLBAR_3D));
m_param_panel->OnActivate();
}
else if (sel == tpPreview) {
else if (m_last_selected_tab == TAB_ID_PREVIEW) {
m_plater->reset_check_status();
if (!m_plater->check_ams_status(m_slice_select == eSliceAll))
return;
@@ -1275,7 +1278,7 @@ void MainFrame::init_tabpanel() {
//monitor
}
#ifndef __APPLE__
if (sel == tp3DEditor) {
if (m_last_selected_tab == TAB_ID_PREPARE) {
m_topbar->EnableUndoRedoItems();
}
else {
@@ -1309,10 +1312,10 @@ void MainFrame::init_tabpanel() {
m_webview = new WebViewPanel(m_tabpanel);
Bind(EVT_LOAD_URL, [this](wxCommandEvent &evt) {
wxString url = evt.GetString();
select_tab(MainFrame::tpHome);
select_tab(TAB_ID_HOME);
m_webview->load_url(url);
});
m_tabpanel->AddPage(m_webview, "", "tab_home_active", "tab_home_active", false);
m_tabpanel->AddPage(TAB_ID_HOME, m_webview, "", "tab_home_active", "tab_home_active", false);
m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
}
@@ -1327,7 +1330,7 @@ void MainFrame::init_tabpanel() {
//BBS add pages
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"), false);
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"), false);
m_printer_view = new PrinterWebView(m_tabpanel);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
@@ -1342,16 +1345,20 @@ void MainFrame::init_tabpanel() {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
// TODO: change the bitmap
m_tabpanel->AddPage(m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"), std::string("tab_multi_active"), false);
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"), std::string("tab_multi_active"), false);
}
m_project = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_project->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(m_project, _L("Project"), std::string("tab_auxiliary_active"), std::string("tab_auxiliary_active"), false);
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project, _L("Project"), std::string("tab_auxiliary_active"), std::string("tab_auxiliary_active"), false);
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"), std::string("tab_calibration_active"), false);
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration, _L("Calibration"), std::string("tab_calibration_active"), std::string("tab_calibration_active"), false);
// 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.
m_plugin_pages.initialize(m_tabpanel);
if (m_plater) {
// load initial config
@@ -1387,7 +1394,11 @@ void MainFrame::show_device(bool bBBLPrinter) {
m_monitor->SetBackgroundColour(*wxWHITE);
}
m_monitor->Show(false);
m_tabpanel->InsertPage(tpMonitor, m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"));
{
const int preview_idx = m_tabpanel->FindPageByName(TAB_ID_PREVIEW);
const size_t monitor_pos = (preview_idx == wxNOT_FOUND) ? m_tabpanel->GetPageCount() : static_cast<size_t>(preview_idx) + 1;
m_tabpanel->InsertPage(monitor_pos, TAB_ID_MONITOR, m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"));
}
if (wxGetApp().is_enable_multi_machine()) {
if (!m_multi_machine) {
@@ -1396,17 +1407,22 @@ void MainFrame::show_device(bool bBBLPrinter) {
}
// TODO: change the bitmap
m_multi_machine->Show(false);
m_tabpanel->InsertPage(tpMultiDevice, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"),
std::string("tab_multi_active"), false);
{
const int monitor_idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR);
const size_t multi_pos = (monitor_idx == wxNOT_FOUND) ? m_tabpanel->GetPageCount() : static_cast<size_t>(monitor_idx) + 1;
m_tabpanel->InsertPage(multi_pos, TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"),
std::string("tab_multi_active"), false);
}
}
if (!m_calibration) {
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
}
m_calibration->Show(false);
// Calibration is always the last page, so don't use InsertPage here. Otherwise, if multi_machine page is not enabled,
// the calibration tab won't be properly added as well, due to the TabPosition::tpCalibration no longer matches the real tab position.
m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"),
// Calibration is always appended last (AddPage), so it lands after whichever of Monitor/Multi-device
// actually got inserted above — no longer position-sensitive now that insertion position is computed
// from FindPageByName rather than a fixed TabPosition index.
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration, _L("Calibration"), std::string("tab_calibration_active"),
std::string("tab_calibration_active"), false);
#ifdef _MSW_DARK_MODE
@@ -1440,8 +1456,12 @@ void MainFrame::show_device(bool bBBLPrinter) {
});
}
m_printer_view->Show(false);
m_tabpanel->InsertPage(tpMonitor, m_printer_view, _L("Device"), std::string("tab_monitor_active"),
std::string("tab_monitor_active"));
{
const int preview_idx = m_tabpanel->FindPageByName(TAB_ID_PREVIEW);
const size_t monitor_pos = (preview_idx == wxNOT_FOUND) ? m_tabpanel->GetPageCount() : static_cast<size_t>(preview_idx) + 1;
m_tabpanel->InsertPage(monitor_pos, TAB_ID_MONITOR, m_printer_view, _L("Device"), std::string("tab_monitor_active"),
std::string("tab_monitor_active"));
}
}
fit_tab_labels(); // ORCA on printer change
}
@@ -1475,7 +1495,7 @@ void MainFrame::fit_tab_labels()
bool MainFrame::preview_only_hint()
{
if (m_plater && (m_plater->only_gcode_mode() || (m_plater->using_exported_file()))) {
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2% in preview mode")%m_tabpanel->GetSelection() %tp3DEditor;
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2% in preview mode")%m_tabpanel->GetSelectedPageName() %wxString(TAB_ID_PREPARE);
ConfirmBeforeSendDialog confirm_dlg(this, wxID_ANY, _L("Warning"));
confirm_dlg.Bind(EVT_SECONDARY_CHECK_CONFIRM, [this](wxCommandEvent& e) {
@@ -1793,22 +1813,22 @@ bool MainFrame::can_clone() const {
bool MainFrame::can_select() const
{
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->model().objects.empty();
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->model().objects.empty();
}
bool MainFrame::can_deselect() const
{
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->is_selection_empty();
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->is_selection_empty();
}
bool MainFrame::can_delete() const
{
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->is_selection_empty();
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->is_selection_empty();
}
bool MainFrame::can_delete_all() const
{
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->model().objects.empty();
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->model().objects.empty();
}
bool MainFrame::can_reslice() const
@@ -1917,7 +1937,7 @@ wxBoxSizer* MainFrame::create_side_tools()
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
else
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
this->m_tabpanel->SetSelection(tpPreview);
this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW);
}
});
@@ -3063,7 +3083,7 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().app_config->set_bool("auto_perspective", !wxGetApp().app_config->get_bool("auto_perspective"));
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
},
this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE || m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; },
[this]() { return wxGetApp().app_config->get_bool("auto_perspective"); }, this);
viewMenu->AppendSeparator();
@@ -3072,7 +3092,7 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().toggle_show_gcode_window();
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
},
this, [this]() { return m_tabpanel->GetSelection() == tpPreview; },
this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; },
[this]() { return wxGetApp().show_gcode_window(); }, this);
append_menu_check_item(
@@ -3081,7 +3101,7 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().toggle_show_3d_navigator();
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
},
this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE || m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; },
[this]() { return wxGetApp().show_3d_navigator(); }, this);
append_menu_check_item(viewMenu, wxID_ANY, _L("Show Gridlines"), _L("Show Gridlines on plate"),
@@ -3089,14 +3109,14 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().toggle_show_plate_gridlines();
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
}, this,
[this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
[this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE || m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; },
[this]() { return wxGetApp().show_plate_gridlines(); }, this);
append_menu_item(
viewMenu, wxID_ANY, _L("Reset Window Layout"), _L("Reset to default window layout"),
[this](wxCommandEvent&) { m_plater->reset_window_layout(); }, "", this,
[this]() {
return (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview) &&
return (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE || m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW) &&
m_plater->is_sidebar_enabled();
},
this);
@@ -3119,7 +3139,7 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().toggle_show_outline();
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
},
this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor; },
this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE; },
[this]() { return wxGetApp().show_outline(); }, this);
/*viewMenu->AppendSeparator();
@@ -3920,13 +3940,18 @@ void MainFrame::select_tab(wxPanel* panel)
wxGetApp().params_dialog()->Popup();
return;
}
// page_name cannot be resolved via panel->GetName() — Prepare and Preview
// share the single m_plater window, so the window itself has no single correct
// name (see Global Constraints). Resolve via Notebook's per-slot m_pageNames
// instead, via the index -> id lookup, which works for any page (built-in or not).
int page_idx = m_tabpanel->FindPage(panel);
if (page_idx == tp3DEditor && m_tabpanel->GetSelection() == tpPreview)
wxString page_name = (page_idx == wxNOT_FOUND) ? wxString() : m_tabpanel->GetPageName(static_cast<size_t>(page_idx));
if (page_name == TAB_ID_PREPARE && m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW)
return;
//BBS GUI refactor: remove unused layout new/dlg
/*if (page_idx != wxNOT_FOUND && m_layout == ESettingsLayout::Dlg)
page_idx++;*/
select_tab(size_t(page_idx));
select_tab(page_name);
}
//BBS
@@ -3934,7 +3959,7 @@ void MainFrame::jump_to_monitor(std::string dev_id)
{
if(!m_monitor)
return;
m_tabpanel->SetSelection(tpMonitor);
m_tabpanel->SelectPageByName(TAB_ID_MONITOR);
if (!dev_id.empty()) {
((MonitorPanel*)m_monitor)->select_machine(dev_id);
}
@@ -3944,26 +3969,26 @@ void MainFrame::jump_to_multipage()
{
if(!m_multi_machine)
return;
m_tabpanel->SetSelection(tpMultiDevice);
m_tabpanel->SelectPageByName(TAB_ID_MULTI_DEVICE);
((MultiMachinePage*)m_multi_machine)->jump_to_send_page();
}
//BBS GUI refactor: remove unused layout new/dlg
void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
void MainFrame::select_tab(const wxString& id/* = wxString()*/)
{
//bool tabpanel_was_hidden = false;
// Controls on page are created on active page of active tab now.
// We should select/activate tab before its showing to avoid an UI-flickering
auto select = [this, tab](bool was_hidden) {
// when tab == -1, it means we should show the last selected tab
auto select = [this, id](bool was_hidden) {
// when id is empty, it means we should show the last selected tab
//BBS GUI refactor: remove unused layout new/dlg
//size_t new_selection = tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab;
size_t new_selection = tab == (size_t)(-1) ? m_last_selected_tab : tab;
wxString new_selection = id.empty() ? m_last_selected_tab : id;
if (m_tabpanel->GetSelection() != (int)new_selection)
m_tabpanel->SetSelection(new_selection);
if (m_tabpanel->GetSelectedPageName() != new_selection)
m_tabpanel->SelectPageByName(new_selection);
#ifdef _MSW_DARK_MODE
/*if (wxGetApp().tabs_as_menu()) {
if (Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(new_selection)))
@@ -3972,10 +3997,14 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
m_plater->get_current_canvas3D()->render();
}*/
#endif
if (tab == MainFrame::tp3DEditor && m_layout == ESettingsLayout::Old)
// NOTE: this checks the ORIGINAL parameter (id), not the resolved new_selection —
// preserving that the fallback-to-last-tab path never triggers this render call
// even if the last selected tab happened to be Prepare. Do not "simplify" to
// new_selection == TAB_ID_PREPARE, that changes behavior.
if (id == TAB_ID_PREPARE && m_layout == ESettingsLayout::Old)
m_plater->canvas3D()->render();
else if (was_hidden) {
Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(new_selection));
Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPageByName(new_selection));
if (cur_tab)
cur_tab->OnActivate();
}
@@ -3984,10 +4013,10 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
select(false);
}
void MainFrame::request_select_tab(TabPosition pos)
void MainFrame::request_select_tab(const wxString& id)
{
wxCommandEvent* evt = new wxCommandEvent(EVT_SELECT_TAB);
evt->SetInt(pos);
evt->SetString(id);
wxQueueEvent(this, evt);
}
@@ -4267,7 +4296,7 @@ void MainFrame::load_printer_url()
}
}
bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelection() == TabPosition::tpMonitor; }
bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelectedPageName() == TAB_ID_MONITOR; }
void MainFrame::refresh_plugin_tips()

View File

@@ -35,6 +35,17 @@
#include "PrinterWebView.hpp"
#include "calib_dlg.hpp"
#include "MultiMachinePage.hpp"
#include "slic3r/plugin/host/PluginPages.hpp"
// Stable identifiers for MainFrame::m_tabpanel's built-in pages. These are
// names rather than positional indices so optional pages cannot shift them.
#define TAB_ID_HOME "home"
#define TAB_ID_PREPARE "prepare"
#define TAB_ID_PREVIEW "preview"
#define TAB_ID_MONITOR "monitor"
#define TAB_ID_MULTI_DEVICE "multi_device"
#define TAB_ID_PROJECT "project"
#define TAB_ID_CALIBRATION "calibration"
#define ENABEL_PRINT_ALL 0
@@ -115,7 +126,7 @@ class MainFrame : public DPIFrame
wxMenuItem* m_menu_item_reslice_now { nullptr };
wxSizer* m_main_sizer{ nullptr };
size_t m_last_selected_tab;
wxString m_last_selected_tab;
std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const;
std::string get_dir_name(const wxString &full_name) const;
@@ -214,19 +225,6 @@ public:
#ifdef __APPLE__
bool get_mac_full_screen() { return m_mac_fullscreen; }
#endif
//BBS GUI refactor
enum TabPosition
{
tpHome = 0,
tp3DEditor = 1,
tpPreview = 2,
tpMonitor = 3,
tpMultiDevice = 4,
tpProject = 5,
tpCalibration = 6,
tpAuxiliary = 7,
toDebugTool = 8,
};
//BBS: add slice&&print status update logic
enum SlicePrintEventType
@@ -326,8 +324,8 @@ public:
// When tab == -1, will be selected last selected tab
//BBS: GUI refactor
void select_tab(wxPanel* panel);
void select_tab(size_t tab = size_t(-1));
void request_select_tab(TabPosition pos);
void select_tab(const wxString& id = wxString());
void request_select_tab(const wxString& id);
int get_calibration_curr_tab();
void select_view(const std::string& direction);
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
@@ -360,6 +358,7 @@ public:
//SoftFever
void show_device(bool bBBLPrinter);
void fit_tab_labels(); // ORCA
PluginPages& plugin_pages() { return m_plugin_pages; }
PA_Calibration_Dlg* m_pa_calib_dlg{ nullptr };
FlowRateCalibrationDialog* m_flow_rate_calib_dlg{ nullptr };
@@ -385,6 +384,7 @@ public:
CalibrationPanel* m_calibration{ nullptr };
WebViewPanel* m_webview { nullptr };
PrinterWebView* m_printer_view{nullptr};
PluginPages m_plugin_pages;
wxLogWindow* m_log_window { nullptr };
// BBS
//wxBookCtrlBase* m_tabpanel { nullptr };

View File

@@ -120,11 +120,11 @@ void ButtonsListCtrl::Rescale()
void ButtonsListCtrl::SetSelection(int sel)
{
if (m_selection == sel)
if (m_selection == sel && sel >= 0 && sel < static_cast<int>(m_pageButtons.size()))
return;
// BBS: change button color
wxColour selected_btn_bg("#009688"); // Gradient #009688
if (m_selection >= 0) {
if (m_selection >= 0 && m_selection < static_cast<int>(m_pageButtons.size())) {
StateColor bg_color = StateColor(
std::pair{wxColour(107, 107, 107), (int) StateColor::Hovered},
std::pair{wxColour(59, 68, 70), (int) StateColor::Normal});
@@ -135,6 +135,13 @@ void ButtonsListCtrl::SetSelection(int sel)
m_pageButtons[m_selection]->SetSelected(false);
m_pageButtons[m_selection]->SetTextColor(text_color);
}
if (sel < 0 || sel >= static_cast<int>(m_pageButtons.size())) {
m_selection = -1;
Refresh();
return;
}
m_selection = sel;
StateColor bg_color = StateColor(
@@ -192,6 +199,14 @@ bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /*
void ButtonsListCtrl::RemovePage(size_t n)
{
if (n >= m_pageButtons.size())
return;
if (m_selection == static_cast<int>(n))
m_selection = -1;
else if (m_selection > static_cast<int>(n))
--m_selection;
Button* btn = m_pageButtons[n];
m_pageButtons.erase(m_pageButtons.begin() + n);
m_pageLabels.erase(m_pageLabels.begin() + n); // ORCA
@@ -253,6 +268,8 @@ void Notebook::Init()
m_showTimeout = m_hideTimeout = 0;
m_pageNames.clear();
/* On Linux, Gstreamer wxMediaCtrl does not seem to get along well with
* 32-bit X11 visuals (the overlay does not work). Is this a wxWindows
* bug? Is this a Gstreamer bug? No idea, but it is our problem ...

View File

@@ -3,6 +3,7 @@
//#ifdef _WIN32
#include <vector>
#include <wx/bookctrl.h>
#include <wx/sizer.h>
@@ -42,7 +43,7 @@ private:
std::vector<wxString> m_pageLabels; // ORCA
};
class Notebook: public wxBookCtrlBase
class Notebook : public wxBookCtrlBase
{
public:
Notebook(wxWindow * parent,
@@ -103,7 +104,7 @@ public:
// by this control) and show it immediately.
bool ShowNewPage(wxWindow * page)
{
return AddPage(page, wxString(), "", "");
return AddPage(wxString(), page, wxString(), "", "");
}
@@ -136,14 +137,15 @@ public:
// Implement base class pure virtual methods.
// adds a new page to the control
bool AddPage(wxWindow* page,
bool AddPage(const wxString& id,
wxWindow* page,
const wxString& text,
const std::string& bmp_name,
const std::string& inactive_bmp_name,
bool bSelect = false)
{
DoInvalidateBestSize();
return InsertPage(GetPageCount(), page, text, bmp_name, inactive_bmp_name, bSelect);
return InsertPage(GetPageCount(), id, page, text, bmp_name, inactive_bmp_name, bSelect);
}
// Page management
@@ -156,6 +158,7 @@ public:
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId))
return false;
m_pageNames.insert(m_pageNames.begin() + n, wxString());
GetBtnsListCtrl()->InsertPage(n, text, bSelect);
if (!DoSetSelectionAfterInsertion(n, bSelect))
@@ -165,6 +168,7 @@ public:
}
bool InsertPage(size_t n,
const wxString& id,
wxWindow * page,
const wxString & text,
const std::string& bmp_name = "",
@@ -174,10 +178,17 @@ public:
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
return false;
m_pageNames.insert(m_pageNames.begin() + n, id);
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name, inactive_bmp_name);
if (bSelect)
SetSelection(n);
// wxBookCtrlBase::InsertPage() only inserts into the page list and sizes the
// new page to the current page's rect — it never touches visibility. A freshly
// constructed page defaults to shown, so without this it renders on top of
// whatever page is currently selected until the next SetSelection() call hides
// it. Mirrors the pure-virtual InsertPage() override above, which already does
// this correctly.
if (!DoSetSelectionAfterInsertion(n, bSelect))
page->Hide();
return true;
}
@@ -251,8 +262,58 @@ public:
page->SetFocus();
}
// wxBookCtrlBase::DeleteAllPages() clears its page list directly rather than
// going through DoRemovePage() per page, so it would otherwise leave
// m_pageNames desynchronized (a mutation path outside the four this class
// already keeps in sync). Not currently called on a Notebook anywhere in
// this codebase, but kept correct for the same reason the rest of this
// bookkeeping exists.
virtual bool DeleteAllPages() override
{
m_pageNames.clear();
return wxBookCtrlBase::DeleteAllPages();
}
ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast<ButtonsListCtrl*>(m_bookctrl); }
int FindPageByName(const wxString& id) const
{
if (id.empty())
return wxNOT_FOUND;
for (size_t i = 0; i < m_pageNames.size(); ++i)
if (m_pageNames[i] == id)
return static_cast<int>(i);
return wxNOT_FOUND;
}
wxWindow* GetPageByName(const wxString& id) const
{
const int idx = FindPageByName(id);
return idx == wxNOT_FOUND ? nullptr : GetPage(static_cast<size_t>(idx));
}
bool SelectPageByName(const wxString& id)
{
const int idx = FindPageByName(id);
if (idx == wxNOT_FOUND)
return false;
SetSelection(static_cast<size_t>(idx));
return true;
}
// Inverse of FindPageByName: index -> id. Empty string for an out-of-range
// index or a page that was never given an id (e.g. settings Tab pages).
wxString GetPageName(size_t n) const
{
return n < m_pageNames.size() ? m_pageNames[n] : wxString();
}
wxString GetSelectedPageName() const
{
const int sel = GetSelection();
return sel < 0 ? wxString() : GetPageName(static_cast<size_t>(sel));
}
void UpdateMode()
{
GetBtnsListCtrl()->UpdateMode();
@@ -369,6 +430,7 @@ protected:
wxWindow* const win = wxBookCtrlBase::DoRemovePage(page);
if (win)
{
m_pageNames.erase(m_pageNames.begin() + page);
GetBtnsListCtrl()->RemovePage(page);
DoSetSelectionAfterRemoval(page);
}
@@ -394,6 +456,8 @@ protected:
private:
void Init();
std::vector<wxString> m_pageNames; // index-parallel to wxBookCtrlBase::m_pages
wxShowEffect m_showEffect,
m_hideEffect;

View File

@@ -1918,7 +1918,7 @@ void NotificationManager::push_validate_error_notification(StringObjectException
wxGetApp().sidebar().jump_to_option(opt, Preset::TYPE_PRINT, L"");
}
else {
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
}
return false;
};
@@ -1985,7 +1985,7 @@ void NotificationManager::push_validate_error_notification(StringObjectException
wxGetApp().sidebar().jump_to_option(opt, opt_type, L"");
}
else {
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
}
return false;
};
@@ -2015,7 +2015,7 @@ void NotificationManager::push_slicing_error_notification(const std::string &tex
if (iter != objects.end()) { ovs.push_back({ *iter, nullptr }); }
}
if (!ovs.empty()) {
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
wxGetApp().obj_list()->select_items(ovs);
}
return false;
@@ -2046,7 +2046,7 @@ void NotificationManager::push_slicing_warning_notification(const std::string& t
auto& objects = wxGetApp().model().objects;
auto iter = std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; });
if (iter != objects.end()) {
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
wxGetApp().obj_list()->select_items({ {*iter, nullptr} });
}
return false;
@@ -2693,7 +2693,7 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s
if (iter != objects.end()) { ovs.push_back({ *iter, nullptr }); }
}
if (!ovs.empty()) {
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
wxGetApp().obj_list()->select_items(ovs);
wxGetApp().obj_list()->update_selections_on_canvas();
}
@@ -2777,7 +2777,7 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s
}
}
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (!sel_items.empty()) {
obj_list->select_items(sel_items);

View File

@@ -5787,7 +5787,7 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
#endif // WIN32
m_mainframe.Raise();
m_mainframe.select_tab(size_t(MainFrame::tp3DEditor));
m_mainframe.select_tab(TAB_ID_PREPARE);
if (wxGetApp().is_editor())
m_plater.select_view_3D("3D");
@@ -6569,9 +6569,9 @@ void Plater::priv::select_next_view_3D()
{
if (current_panel == view3D)
wxGetApp().mainframe->select_tab(size_t(MainFrame::tpPreview));
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
else if (current_panel == preview)
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
// else if (current_panel == assemble_view)
// set_current_panel(view3D);
}
@@ -7870,7 +7870,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
q->select_plate(first_plate_index);
//set to 3d tab
q->select_view_3D("Preview");
wxGetApp().mainframe->select_tab(MainFrame::tpPreview);
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
}
else {
//set to 3d tab
@@ -7889,7 +7889,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
else {
//always set to 3D after loading files
q->select_view_3D("3D");
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
}
if (load_model) {
@@ -8797,7 +8797,7 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
}
}
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (inst_idx != -1) {
auto* model = wxGetApp().obj_list()->GetModel();
@@ -8826,7 +8826,7 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
} else {
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
if (iter != objects.end()) {
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
wxGetApp().obj_list()->select_items({{*iter, nullptr}});
wxGetApp().obj_list()->update_selections_on_canvas();
}
@@ -11208,13 +11208,20 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
}
const int new_sel = e.GetSelection();
sidebar_layout.show = new_sel == MainFrame::tp3DEditor || new_sel == MainFrame::tpPreview;
if (new_sel == wxNOT_FOUND) {
// Guards against new_sel matching FindPageByName's own wxNOT_FOUND sentinel
// below when a TAB_ID_* isn't currently present in the tabpanel.
e.Skip();
return;
}
sidebar_layout.show = new_sel == main_frame->m_tabpanel->FindPageByName(TAB_ID_PREPARE) ||
new_sel == main_frame->m_tabpanel->FindPageByName(TAB_ID_PREVIEW);
update_sidebar();
int old_sel = e.GetOldSelection();
const bool is_printer_agent_plugin = NetworkAgentFactory::is_current_printer_agent_plugin();
const bool use_native_device_tab = wxGetApp().preset_bundle &&
(wxGetApp().preset_bundle->use_bbl_device_tab() || is_printer_agent_plugin);
if (use_native_device_tab && new_sel == MainFrame::tpMonitor) {
if (use_native_device_tab && new_sel == main_frame->m_tabpanel->FindPageByName(TAB_ID_MONITOR)) {
// BBL network module is only required for BBL-vendor printers.
// Non-BBL Python plugins (e.g. moonraker) drive the Device tab without it.
if (!is_printer_agent_plugin && wxGetApp().preset_bundle->is_bbl_vendor() && !Slic3r::NetworkAgent::is_network_module_loaded()) {
@@ -11226,7 +11233,7 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
}
}
} else {
if (new_sel == MainFrame::tpMonitor && wxGetApp().preset_bundle != nullptr) {
if (new_sel == main_frame->m_tabpanel->FindPageByName(TAB_ID_MONITOR) && wxGetApp().preset_bundle != nullptr) {
auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
if (main_frame->m_printer_view && url.empty()) {
@@ -12094,7 +12101,7 @@ bool Plater::priv::check_ams_status_impl(bool is_slice_all)
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
else
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
wxGetApp().mainframe->m_tabpanel->SetSelection(MainFrame::TabPosition::tpPreview);
wxGetApp().mainframe->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW);
}
return false;
}
@@ -13051,7 +13058,7 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
get_notification_manager()->clear_all();
if (!silent)
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
//get_partplate_list().reinit();
//get_partplate_list().update_slice_context_to_current_plate(p->background_process);
@@ -13200,7 +13207,7 @@ void Plater::load_project(wxString const& filename2,
if (!m_exported_file) {
p->select_view("topfront");
p->camera.requires_zoom_to_plate = REQUIRES_ZOOM_TO_ALL_PLATE;
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
}
else {
p->partplate_list.select_plate_view();
@@ -13314,7 +13321,7 @@ void Plater::import_model_id(wxString download_info)
const int max_retries = 3;
/* jump to 3D eidtor */
wxGetApp().mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
/* prepare progress dialog */
bool cont = true;
@@ -13623,7 +13630,7 @@ void Plater::calib_pa(const Calib_Params& params)
{
const auto calib_pa_name = wxString::Format(L"Pressure Advance Test");
new_project(false, false, calib_pa_name);
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
print_config->set_key_value("overhang_reverse", new ConfigOptionBool(false));
@@ -14104,7 +14111,7 @@ void Plater::calib_flowrate(bool is_linear, int pass, InfillPattern pattern) {
if (new_project(false, false, calib_name) == wxID_CANCEL)
return;
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (is_linear) {
if (pass == 1)
@@ -14141,7 +14148,7 @@ void Plater::calib_temp(const Calib_Params& params) {
const auto calib_temp_name = wxString::Format(L"Nozzle temperature test");
new_project(false, false, calib_temp_name);
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Temp_Tower) return;
if (!add_model(false, Slic3r::resources_dir() + "/calib/temperature_tower/temperature_tower.drc"))
@@ -14218,7 +14225,7 @@ void Plater::calib_max_vol_speed(const Calib_Params& params)
{
const auto calib_vol_speed_name = wxString::Format(L"Max volumetric speed test");
new_project(false, false, calib_vol_speed_name);
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Vol_speed_Tower)
return;
if (!add_model(false, Slic3r::resources_dir() + "/calib/volumetric_speed/SpeedTestStructure.drc"))
@@ -14297,7 +14304,7 @@ void Plater::calib_retraction(const Calib_Params& params)
{
const auto calib_retraction_name = wxString::Format(L"Retraction");
new_project(false, false, calib_retraction_name);
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Retraction_tower)
return;
@@ -14357,7 +14364,7 @@ void Plater::calib_VFA(const Calib_Params& params)
{
const auto calib_vfa_name = wxString::Format(L"VFA test");
new_project(false, false, calib_vfa_name);
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_VFA_Tower)
return;
@@ -14403,7 +14410,7 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
{
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Frequency test");
new_project(false, false, calib_input_shaping_name);
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Input_shaping_freq)
return;
@@ -14469,7 +14476,7 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
{
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Damping test");
new_project(false, false, calib_input_shaping_name);
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Input_shaping_damp)
return;
@@ -14534,7 +14541,7 @@ void Plater::Calib_Cornering(const Calib_Params& params)
{
const auto Calib_Cornering = wxString::Format(L"Cornering test");
new_project(false, false, Calib_Cornering);
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Cornering)
return;
@@ -14667,7 +14674,7 @@ void Plater::load_gcode(const wxString& filename)
//p->gcode_result.reset();
//reset_gcode_toolpaths();
p->preview->reload_print(m_only_gcode);
wxGetApp().mainframe->select_tab(MainFrame::tpPreview);
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
p->set_current_panel(p->preview, true);
p->get_current_canvas3D()->render();
//p->notification_manager->bbl_show_plateinfo_notification(into_u8(_L("Preview only mode for gcode file.")));
@@ -15340,7 +15347,7 @@ LoadType determine_load_type(std::string filename, std::string override_setting)
wxGetApp().app_config->set("import_project_action", std::to_string(choice));
// BBS: jump to plater panel
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
return load_type;
}
@@ -15569,7 +15576,7 @@ void Plater::reset_with_confirm()
.ShowModal() == wxID_YES) {
reset();
// BBS: jump to plater panel
wxGetApp().mainframe->select_tab(size_t(0));
wxGetApp().mainframe->select_tab(TAB_ID_HOME);
}
}
@@ -17383,7 +17390,7 @@ int Plater::export_config_3mf(int plate_idx, Export3mfProgressFn proFn)
//BBS
void Plater::send_calibration_job_finished(wxCommandEvent & evt)
{
p->main_frame->request_select_tab(MainFrame::TabPosition::tpCalibration);
p->main_frame->request_select_tab(TAB_ID_CALIBRATION);
auto calibration_panel = p->main_frame->m_calibration;
if (calibration_panel) {
auto curr_wizard = static_cast<CalibrationWizard*>(calibration_panel->get_tabpanel()->GetPage(evt.GetInt()));
@@ -17415,7 +17422,7 @@ void Plater::print_job_finished(wxCommandEvent &evt)
if (!dev) return;
dev->set_selected_machine(evt.GetString().ToStdString());
p->main_frame->request_select_tab(MainFrame::TabPosition::tpMonitor);
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;
if(curr_monitor)
@@ -17430,7 +17437,7 @@ void Plater::send_job_finished(wxCommandEvent& evt)
send_gcode_finish(evt.GetString());
p->hide_send_to_printer_dlg();
//p->main_frame->request_select_tab(MainFrame::TabPosition::tpMonitor);
//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;
//if (curr_monitor)
@@ -18324,7 +18331,7 @@ void Plater::pop_warning_and_go_to_device_page(wxString printer_name, PrinterWar
MessageDialog dlg(this, content, title, wxOK | wxFORWARD | wxICON_WARNING, _L("Device Page"));
auto result = dlg.ShowModal();
if (result == wxFORWARD) {
wxGetApp().mainframe->select_tab(size_t(MainFrame::tpMonitor));
wxGetApp().mainframe->select_tab(TAB_ID_MONITOR);
}
}

View File

@@ -15,39 +15,6 @@ namespace Slic3r { namespace GUI {
namespace {
// Low-specificity element defaults (no !important) for UNSTYLED plugin HTML, so a bare
// plugin page looks native while any CSS the plugin ships still wins. Built on the
// --orca-* variables the host injects (see WebViewHostDialog); document-start injected
// AFTER the host contract so the variables are defined (shares the base injector's
// WebView2 timing guard).
std::string plugin_defaults_user_script()
{
std::string css;
css += "<style id=\"orca-plugin-defaults\">";
css += "html,body{background:var(--orca-bg);color:var(--orca-fg);"
"font-family:var(--orca-font);font-size:13px;}";
css += "body{margin:0;}";
css += "h1,h2,h3,h4,h5,h6{color:var(--orca-fg);font-weight:600;}";
css += "a{color:var(--orca-accent);}";
css += "hr{border:0;border-top:1px solid var(--orca-border);}";
css += "button{font:inherit;color:var(--orca-accent-fg);background:var(--orca-accent);"
"border:1px solid var(--orca-accent);border-radius:4px;padding:5px 14px;cursor:pointer;}";
css += "button:hover{filter:brightness(1.1);}";
css += "button:disabled{opacity:.5;cursor:default;}";
css += "input,select,textarea{font:inherit;color:var(--orca-fg);"
"background:var(--orca-bg);border:1px solid var(--orca-border);"
"border-radius:4px;padding:4px 8px;}";
css += "input:focus,select:focus,textarea:focus{outline:none;border-color:var(--orca-accent);}";
css += "table{border-collapse:collapse;}";
css += "th,td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--orca-border);}";
css += "th{color:var(--orca-muted);font-weight:600;}";
css += "::-webkit-scrollbar{width:12px;height:12px;}";
css += "::-webkit-scrollbar-thumb{background:var(--orca-border);border-radius:6px;}";
css += "::-webkit-scrollbar-track{background:transparent;}";
css += "</style>";
return WebViewHostDialog::document_start_injector(css, "orca-plugin-defaults", "beforeend");
}
// Injected into the top-level page at document start (before the plugin's own
// scripts). Defines window.orca as the only host surface the page may use. It
// references window.wx lazily (at call time) so it never races the backend's
@@ -129,7 +96,7 @@ PluginWebDialog::PluginWebDialog(wxWindow* parent,
void PluginWebDialog::add_user_scripts()
{
if (wxWebView* wv = browser()) {
wv->AddUserScript(wxString::FromUTF8(plugin_defaults_user_script()));
wv->AddUserScript(wxString::FromUTF8(WebViewHostDialog::plugin_defaults_user_script()));
wv->AddUserScript(ORCA_BRIDGE_JS);
}
}

View File

@@ -1039,7 +1039,7 @@ bool PlaterPresetComboBox::switch_to_tab()
//BBS Select NoteBook Tab params
if (tab->GetParent() == wxGetApp().params_panel())
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
else {
wxGetApp().params_dialog()->Popup();
tab->OnActivate();

View File

@@ -1088,8 +1088,8 @@ void SelectMachineDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &res
}
}
relayout_nozzle_cards();
auto tab_index = (MainFrame::TabPosition) dynamic_cast<Notebook *>(wxGetApp().tab_panel())->GetSelection();
if (tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview) {
wxString tab_name = wxGetApp().tab_panel()->GetSelectedPageName();
if (tab_name == TAB_ID_PREPARE || tab_name == TAB_ID_PREVIEW) {
updata_thumbnail_data_after_connected_printer();
}
}

View File

@@ -1218,8 +1218,8 @@ void SyncAmsInfoDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &resul
iter++;
}
}
auto tab_index = (MainFrame::TabPosition) dynamic_cast<Notebook *>(wxGetApp().tab_panel())->GetSelection();
if (tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview) {
wxString tab_name = wxGetApp().tab_panel()->GetSelectedPageName();
if (tab_name == TAB_ID_PREPARE || tab_name == TAB_ID_PREVIEW) {
updata_thumbnail_data_after_connected_printer();
}
}

View File

@@ -6415,7 +6415,7 @@ void Tab::load_current_preset()
std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" :
tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "" : "cog";
tab->Hide(); // #ys_WORKAROUND : Hide tab before inserting to avoid unwanted rendering of the tab
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name);
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), wxString(), tab, tab->title(), bmp_name);
}
else
#endif

View File

@@ -57,18 +57,6 @@ std::string host_theme_vars_css()
return s;
}
// Document-start user script: injects the contract <style>, stamps data-orca-theme before
// first paint, and raises a JS flag so the legacy globalapi.js dark.css poll stands down for
// host-themed pages. The WebView2 timing guard lives in document_start_injector().
std::string host_theme_user_script()
{
const std::string style = "<style id=\"orca-host-theme-vars\">" + host_theme_vars_css() + "</style>";
return WebViewHostDialog::document_start_injector(
style, "orca-host-theme-vars", "afterbegin",
"window.__orcaHostThemed=true;var theme=\"" + host_theme_name() + "\";",
"if(document.documentElement)document.documentElement.setAttribute('data-orca-theme',theme);");
}
// JS to re-theme an already-loaded document live (no reload): replace the injected
// style's contents and update data-orca-theme. Everything downstream (theme.css
// tokens, plugin element defaults, page layout) re-cascades from these values.
@@ -87,6 +75,46 @@ if(document.documentElement)
} // namespace
// Document-start user script: injects the contract <style>, stamps data-orca-theme before
// first paint, and raises a JS flag so the legacy globalapi.js dark.css poll stands down for
// host-themed pages. The WebView2 timing guard lives in document_start_injector().
std::string WebViewHostDialog::theme_user_script()
{
const std::string style = "<style id=\"orca-host-theme-vars\">" + host_theme_vars_css() + "</style>";
return document_start_injector(
style, "orca-host-theme-vars", "afterbegin",
"window.__orcaHostThemed=true;var theme=\"" + host_theme_name() + "\";",
"if(document.documentElement)document.documentElement.setAttribute('data-orca-theme',theme);");
}
std::string WebViewHostDialog::plugin_defaults_user_script()
{
std::string css;
css += "<style id=\"orca-plugin-defaults\">";
css += "html,body{background:var(--orca-bg);color:var(--orca-fg);"
"font-family:var(--orca-font);font-size:13px;}";
css += "body{margin:0;}";
css += "h1,h2,h3,h4,h5,h6{color:var(--orca-fg);font-weight:600;}";
css += "a{color:var(--orca-accent);}";
css += "hr{border:0;border-top:1px solid var(--orca-border);}";
css += "button{font:inherit;color:var(--orca-accent-fg);background:var(--orca-accent);"
"border:1px solid var(--orca-accent);border-radius:4px;padding:5px 14px;cursor:pointer;}";
css += "button:hover{filter:brightness(1.1);}";
css += "button:disabled{opacity:.5;cursor:default;}";
css += "input,select,textarea{font:inherit;color:var(--orca-fg);"
"background:var(--orca-bg);border:1px solid var(--orca-border);"
"border-radius:4px;padding:4px 8px;}";
css += "input:focus,select:focus,textarea:focus{outline:none;border-color:var(--orca-accent);}";
css += "table{border-collapse:collapse;}";
css += "th,td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--orca-border);}";
css += "th{color:var(--orca-muted);font-weight:600;}";
css += "::-webkit-scrollbar{width:12px;height:12px;}";
css += "::-webkit-scrollbar-thumb{background:var(--orca-border);border-radius:6px;}";
css += "::-webkit-scrollbar-track{background:transparent;}";
css += "</style>";
return document_start_injector(css, "orca-plugin-defaults", "beforeend");
}
std::string WebViewHostDialog::document_start_injector(const std::string& markup,
const char* dom_id,
const char* position,
@@ -244,7 +272,7 @@ void WebViewHostDialog::register_theme_user_scripts()
// script message handler is registered separately (AddScriptMessageHandler), but on
// some backends RemoveAllUserScripts() drops it too, which would break
// window.wx.postMessage / HandleStudio. Live re-theme goes through apply_theme_live().
m_browser->AddUserScript(wxString::FromUTF8(host_theme_user_script()));
m_browser->AddUserScript(wxString::FromUTF8(theme_user_script()));
add_user_scripts();
}

View File

@@ -49,6 +49,10 @@ public:
const std::string& prelude = {},
const std::string& on_inject = {});
// Shared by modeless Pages tabs and PluginWebDialog.
static std::string theme_user_script();
static std::string plugin_defaults_user_script();
protected:
wxWebView* browser() const { return m_browser; }

View File

@@ -368,7 +368,7 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
emit_progress(100);
if (the_job.switch_to_device_tab) {
const auto mainframe = GUI::wxGetApp().mainframe;
mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
mainframe->request_select_tab(TAB_ID_MONITOR);
}
}
}

View File

@@ -325,7 +325,7 @@ bool SimplyPrint::do_temp_upload(const boost::filesystem::path& file_path,
wxLaunchDefaultBrowser(url);
} else {
const auto mainframe = GUI::wxGetApp().mainframe;
mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
mainframe->request_select_tab(TAB_ID_MONITOR);
mainframe->load_printer_url(url);
}

View File

@@ -19,6 +19,7 @@
#include "PyPluginPackage.hpp"
#include "PyPluginTrampoline.hpp"
#include "pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp"
#include "pluginTypes/pages/PagesPluginCapability.hpp"
#include "pluginTypes/script/ScriptPluginCapability.hpp"
#include "pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp"
@@ -319,17 +320,17 @@ void bind_python_api(pybind11::module_& m)
{
m.doc() = "OrcaSlicer plugin API";
auto pluginTypes = py::enum_<PluginCapabilityType>(m, "PluginType", "Available plugin capability groups")
.value("PrinterConnection", PluginCapabilityType::PrinterConnection)
.value("Automation", PluginCapabilityType::Automation)
.value("Analysis", PluginCapabilityType::Analysis)
.value("Importer", PluginCapabilityType::Importer)
.value("Exporter", PluginCapabilityType::Exporter)
.value("Visualization", PluginCapabilityType::Visualization)
.value("Script", PluginCapabilityType::Script)
.value("SlicingPipeline", PluginCapabilityType::SlicingPipeline)
.value("Unknown", PluginCapabilityType::Unknown)
.export_values();
py::enum_<PluginCapabilityType>(m, "PluginType", "Available plugin capability groups")
.value("PrinterConnection", PluginCapabilityType::PrinterConnection)
.value("Pages", PluginCapabilityType::Pages)
.value("Analysis", PluginCapabilityType::Analysis)
.value("Importer", PluginCapabilityType::Importer)
.value("Exporter", PluginCapabilityType::Exporter)
.value("Visualization", PluginCapabilityType::Visualization)
.value("Script", PluginCapabilityType::Script)
.value("SlicingPipeline", PluginCapabilityType::SlicingPipeline)
.value("Unknown", PluginCapabilityType::Unknown)
.export_values();
py::enum_<PluginResult>(m, "PluginResult", "Execution summary code")
.value("Success", PluginResult::Success)
@@ -419,9 +420,10 @@ void bind_python_api(pybind11::module_& m)
BOOST_LOG_TRIVIAL(debug) << "Registering embedded Python plugin type bindings";
// Make sure you register your bindings here
PrinterAgentPluginCapability::RegisterBindings(m, pluginTypes);
ScriptPluginCapability::RegisterBindings(m, pluginTypes);
SlicingPipelinePluginCapability::RegisterBindings(m, pluginTypes);
PrinterAgentPluginCapability::RegisterBindings(m);
PagesPluginCapability::RegisterBindings(m);
ScriptPluginCapability::RegisterBindings(m);
SlicingPipelinePluginCapability::RegisterBindings(m);
PluginHost::RegisterBindings(m);
BOOST_LOG_TRIVIAL(debug) << "Registered ScriptPluginCapability Python bindings";

View File

@@ -12,7 +12,7 @@
namespace Slic3r {
enum class PluginCapabilityType { PrinterConnection = 0, Automation, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
enum class PluginCapabilityType { PrinterConnection = 0, Pages, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
struct PluginCapabilityId
{
@@ -39,7 +39,7 @@ inline std::string plugin_capability_type_to_string(PluginCapabilityType type)
{
switch (type) {
case PluginCapabilityType::PrinterConnection: return "printer-connection";
case PluginCapabilityType::Automation: return "automation";
case PluginCapabilityType::Pages: return "pages";
case PluginCapabilityType::Analysis: return "analysis";
case PluginCapabilityType::Importer: return "importer";
case PluginCapabilityType::Exporter: return "exporter";
@@ -54,7 +54,7 @@ inline std::string plugin_capability_type_display_name(PluginCapabilityType type
{
switch (type) {
case PluginCapabilityType::PrinterConnection: return "Printer connection";
case PluginCapabilityType::Automation: return "Automation";
case PluginCapabilityType::Pages: return "Pages";
case PluginCapabilityType::Analysis: return "Analysis";
case PluginCapabilityType::Importer: return "Importer";
case PluginCapabilityType::Exporter: return "Exporter";
@@ -76,8 +76,8 @@ inline PluginCapabilityType plugin_capability_type_from_string(std::string_view
if (lowered == "printer-connection")
return PluginCapabilityType::PrinterConnection;
if (lowered == "automation")
return PluginCapabilityType::Automation;
if (lowered == "pages")
return PluginCapabilityType::Pages;
if (lowered == "analysis")
return PluginCapabilityType::Analysis;
if (lowered == "importer")

View File

@@ -0,0 +1,298 @@
#include "PluginPages.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Notebook.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Widgets/WebView.hpp"
#include "slic3r/GUI/Widgets/WebViewHostDialog.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include <libslic3r/Utils.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/log/trivial.hpp>
#include <nlohmann/json.hpp>
#include <wx/sizer.h>
#include <utility>
namespace Slic3r {
namespace {
constexpr char PLUGIN_PAGE_BRIDGE_JS[] = R"JS(
(function () {
if (window.top !== window.self) return;
if (window.orca) return;
var handlers = [];
function deliver(payload, attempts) {
try {
if (window.wx && typeof window.wx.postMessage === 'function') {
window.wx.postMessage(payload);
return;
}
} catch (e) { /* retry while the native handler is being registered */ }
if (attempts < 100)
window.setTimeout(function () { deliver(payload, attempts + 1); }, 25);
}
function send(data) {
deliver(JSON.stringify({
channel: 'orca', kind: 'message', data: (data === undefined ? null : data)
}), 0);
}
window.orca = {
postMessage: function (data) { send(data); },
onMessage: function (callback) {
if (typeof callback === 'function') handlers.push(callback);
}
};
window.__orcaDispatch = function (payload) {
var data = payload ? payload.data : null;
for (var i = 0; i < handlers.length; i++) {
try { handlers[i](data); } catch (e) {}
}
};
})();
)JS";
} // namespace
PluginPage::PluginPage(wxWindow* parent, std::shared_ptr<PagesPluginCapability> capability)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
, m_cap(std::move(capability))
, m_lifetime(std::make_shared<std::atomic<PluginPage*>>(this))
{
auto* topsizer = new wxBoxSizer(wxVERTICAL);
SetSizer(topsizer);
m_browser = WebView::CreateWebView(this, bootstrap_url());
if (m_browser == nullptr) {
wxLogError("Could not initialize plugin page web view");
return;
}
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PluginPage::on_bootstrap_event, this);
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PluginPage::on_bootstrap_event, this);
m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PluginPage::on_new_window, this);
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PluginPage::on_script_message, this);
m_browser->AddUserScript(wxString::FromUTF8(GUI::WebViewHostDialog::theme_user_script()));
m_browser->AddUserScript(wxString::FromUTF8(GUI::WebViewHostDialog::plugin_defaults_user_script()));
m_browser->AddUserScript(PLUGIN_PAGE_BRIDGE_JS);
const std::shared_ptr<std::atomic<PluginPage*>> lifetime = m_lifetime;
m_cap->set_message_sender([lifetime](const std::string& message) {
if (wxTheApp == nullptr)
return;
GUI::wxGetApp().CallAfter([lifetime, message] {
if (PluginPage* page = lifetime->load(std::memory_order_acquire))
page->push_message(message);
});
});
}
PluginPage::~PluginPage()
{
detach_capability();
if (m_lifetime)
m_lifetime->store(nullptr, std::memory_order_release);
}
void PluginPage::detach_capability()
{
if (m_lifetime)
m_lifetime->store(nullptr, std::memory_order_release);
if (m_cap)
m_cap->clear_message_sender();
m_cap.reset();
}
wxString PluginPage::web_base_url() const
{
const auto path = (boost::filesystem::path(resources_dir()) / "web").make_preferred().string();
return wxString("file://") + GUI::from_u8(path) + "/";
}
wxString PluginPage::bootstrap_url() const
{
const auto path = (boost::filesystem::path(resources_dir()) / "web/dialog/PluginWebDialog/blank.html").make_preferred().string();
return wxString("file://") + GUI::from_u8(path);
}
void PluginPage::on_bootstrap_event(wxWebViewEvent& event)
{
load_plugin_content();
event.Skip();
}
void PluginPage::load_plugin_content()
{
if (m_content_loaded || m_browser == nullptr || m_cap == nullptr)
return;
m_content_loaded = true;
try {
m_browser->SetPage(wxString::FromUTF8(m_cap->get_ui()), web_base_url());
} catch (const std::exception& error) {
BOOST_LOG_TRIVIAL(error) << "Failed to load plugin page '" << m_cap->name() << "': " << error.what();
detach_capability();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed to load plugin page '" << m_cap->name() << "'";
detach_capability();
}
}
void PluginPage::on_new_window(wxWebViewEvent& event)
{
const wxString url = event.GetURL();
if (!url.empty() && m_browser != nullptr)
m_browser->LoadURL(url);
event.Veto();
}
void PluginPage::on_script_message(wxWebViewEvent& event)
{
if (!m_cap)
return;
const wxString payload = event.GetString();
nlohmann::json root = nlohmann::json::parse(payload.utf8_string(), nullptr, false);
if (root.is_discarded() || root.value("channel", std::string()) != "orca" ||
root.value("kind", std::string()) != "message")
return;
const nlohmann::json data = root.contains("data") ? root["data"] : nlohmann::json();
try {
m_cap->on_message(data.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace));
} catch (const std::exception& error) {
BOOST_LOG_TRIVIAL(error) << "Plugin page message handler failed for '" << m_cap->name() << "': " << error.what();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Plugin page message handler failed for '" << m_cap->name() << "'";
}
}
void PluginPage::push_message(const std::string& message)
{
if (m_browser == nullptr)
return;
nlohmann::json data = nlohmann::json::parse(message, nullptr, false);
if (data.is_discarded())
data = message;
const wxString script = wxString("(function dispatch(payload, attempts) {\n") +
wxString(" if (typeof window.__orcaDispatch === 'function') { window.__orcaDispatch(payload); return; }\n") +
wxString(" if (attempts < 100) window.setTimeout(function() { dispatch(payload, attempts + 1); }, 25);\n") +
wxString("})({data: ") +
wxString::FromUTF8(data.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace)) +
wxString("}, 0);");
WebView::RunScript(m_browser, script);
}
PluginPages::~PluginPages()
{
shutdown();
}
void PluginPages::initialize(Notebook* parent)
{
shutdown();
m_parent = parent;
if (m_parent == nullptr)
return;
for (const auto& capability : PluginManager::instance().get_plugin_capabilities("", PluginCapabilityType::Pages)) {
if (capability)
on_cap_register(capability->identity());
}
}
void PluginPages::shutdown()
{
while (!m_pages.empty())
remove_page(m_pages.begin()->first);
m_parent = nullptr;
}
std::shared_ptr<PagesPluginCapability> PluginPages::get_pages_cap(const PluginCapabilityId& id, bool is_enabled) const
{
auto capability = PluginManager::instance().get_plugin_capability(id, /*only_enabled=*/false);
if (!capability || capability->is_enabled() != is_enabled || capability->type() != PluginCapabilityType::Pages)
return nullptr;
return std::dynamic_pointer_cast<PagesPluginCapability>(capability);
}
void PluginPages::on_cap_register(const PluginCapabilityId& id)
{
if (m_parent == nullptr || m_pages.find(id) != m_pages.end())
return;
auto capability = get_pages_cap(id, true);
if (!capability)
return;
auto* page = new PluginPage(m_parent, std::move(capability));
if (!page->is_valid()) {
page->Destroy();
return;
}
const wxString title = wxString::FromUTF8(id.name);
const wxString page_id = wxString::FromUTF8("plugin." + id.plugin_key + "." + id.name);
if (!m_parent->AddPage(page_id, page, title, "tab_auxiliary_active", "tab_auxiliary_active", false)) {
page->Destroy();
return;
}
m_pages.emplace(id, page);
}
void PluginPages::on_cap_deregister(const PluginCapabilityId& id)
{
remove_page(id);
}
void PluginPages::on_plugin_register(const std::string& plugin_key)
{
for (const auto& capability : PluginManager::instance().get_plugin_capabilities(plugin_key, PluginCapabilityType::Pages)) {
if (capability)
on_cap_register(capability->identity());
}
}
void PluginPages::on_plugin_deregister(const std::string& plugin_key)
{
for (auto it = m_pages.begin(); it != m_pages.end();) {
if (it->first.plugin_key != plugin_key) {
++it;
continue;
}
const PluginCapabilityId id = it->first;
++it;
remove_page(id);
}
}
void PluginPages::remove_page(const PluginCapabilityId& id)
{
auto it = m_pages.find(id);
if (it == m_pages.end())
return;
PluginPage* page = it->second;
page->detach_capability();
if (m_parent != nullptr) {
const int index = m_parent->FindPage(page);
if (index != wxNOT_FOUND)
m_parent->RemovePage(static_cast<size_t>(index));
}
page->Destroy();
m_pages.erase(it);
}
} // namespace Slic3r

View File

@@ -0,0 +1,69 @@
#pragma once
#include <slic3r/plugin/PythonPluginInterface.hpp>
#include <slic3r/plugin/pluginTypes/pages/PagesPluginCapability.hpp>
#include <atomic>
#include <map>
#include <memory>
#include <string>
#include <wx/panel.h>
#include <wx/webview.h>
class Notebook;
namespace Slic3r {
class PluginPage : public wxPanel
{
public:
PluginPage(wxWindow* parent, std::shared_ptr<PagesPluginCapability> capability);
~PluginPage() override;
PluginPage() = delete;
bool is_valid() const { return m_browser != nullptr && m_cap != nullptr; }
void detach_capability();
void on_bootstrap_event(wxWebViewEvent& event);
void on_new_window(wxWebViewEvent& event);
void on_script_message(wxWebViewEvent& event);
void push_message(const std::string& message);
private:
void load_plugin_content();
wxString bootstrap_url() const;
wxString web_base_url() const;
wxWebView* m_browser{nullptr};
std::shared_ptr<PagesPluginCapability> m_cap;
std::shared_ptr<std::atomic<PluginPage*>> m_lifetime;
bool m_content_loaded{false};
};
class PluginPages
{
public:
PluginPages() = default;
~PluginPages();
PluginPages(const PluginPages&) = delete;
PluginPages& operator=(const PluginPages&) = delete;
void initialize(Notebook* parent);
void shutdown();
void on_cap_register(const PluginCapabilityId& id);
void on_cap_deregister(const PluginCapabilityId& id);
void on_plugin_register(const std::string& plugin_key);
void on_plugin_deregister(const std::string& plugin_key);
private:
std::shared_ptr<PagesPluginCapability> get_pages_cap(const PluginCapabilityId& id, bool is_enabled) const;
void remove_page(const PluginCapabilityId& id);
std::map<PluginCapabilityId, PluginPage*> m_pages;
Notebook* m_parent{nullptr};
};
} // namespace Slic3r

View File

@@ -0,0 +1,59 @@
#include "PagesPluginCapability.hpp"
#include "PagesPluginCapabilityTrampoline.hpp"
#include "../../PluginFsUtils.hpp"
#include <boost/log/trivial.hpp>
#include <pybind11/pybind11.h>
#include <utility>
namespace py = pybind11;
namespace Slic3r {
void PagesPluginCapability::RegisterBindings(pybind11::module_& module)
{
BOOST_LOG_TRIVIAL(debug) << "Registering orca.pages bindings";
auto pages = module.def_submodule("pages", "Plugin page API");
py::class_<PagesPluginCapability, PluginCapabilityInterface, PyPagesPluginCapabilityTrampoline,
std::shared_ptr<PagesPluginCapability>>(pages, "PagesPluginCapabilityBase")
.def(py::init<>())
.def("get_type", &PagesPluginCapability::get_type)
.def("get_ui", &PagesPluginCapability::get_ui)
.def("on_message", &PagesPluginCapability::on_message)
.def(
"post_message",
[](PagesPluginCapability& capability, py::object data) {
capability.post_message(py_to_json(data).dump());
},
py::arg("data"), "Send a JSON-compatible value to the page's window.orca.onMessage handlers.");
}
void PagesPluginCapability::post_message(std::string message)
{
std::function<void(const std::string&)> sender;
{
std::lock_guard<std::mutex> lock(m_message_mutex);
sender = m_message_sender;
}
if (sender)
sender(message);
}
void PagesPluginCapability::set_message_sender(std::function<void(const std::string&)> sender)
{
std::lock_guard<std::mutex> lock(m_message_mutex);
m_message_sender = std::move(sender);
}
void PagesPluginCapability::clear_message_sender()
{
std::lock_guard<std::mutex> lock(m_message_mutex);
m_message_sender = nullptr;
}
} // namespace Slic3r

View File

@@ -0,0 +1,32 @@
#ifndef slic3r_PagesPluginCapability_hpp_
#define slic3r_PagesPluginCapability_hpp_
#include "../../PythonPluginInterface.hpp"
#include "pybind11/pybind11.h"
#include <functional>
#include <mutex>
#include <string>
namespace Slic3r {
class PagesPluginCapability : public PluginCapabilityInterface
{
public:
static void RegisterBindings(pybind11::module_& module);
PluginCapabilityType get_type() const override { return PluginCapabilityType::Pages; }
virtual std::string get_ui() = 0;
virtual void on_message(std::string message) { (void) message; }
void post_message(std::string message);
void set_message_sender(std::function<void(const std::string&)> sender);
void clear_message_sender();
private:
mutable std::mutex m_message_mutex;
std::function<void(const std::string&)> m_message_sender;
};
} // namespace Slic3r
#endif

View File

@@ -0,0 +1,48 @@
#pragma once
#include "PagesPluginCapability.hpp"
#include "../../PluginFsUtils.hpp"
#include "../../PyPluginTrampoline.hpp"
#include <nlohmann/json.hpp>
namespace Slic3r {
class PyPagesPluginCapabilityTrampoline : public PyPluginCommonTrampoline<PagesPluginCapability>
{
public:
using PyPluginCommonTrampoline<PagesPluginCapability>::PyPluginCommonTrampoline;
std::string get_ui() override
{
ORCA_PY_OVERRIDE_AUDITED(
::Slic3r::PluginAuditManager::AuditMode::Loading,
[] {},
PYBIND11_OVERRIDE_PURE,
std::string,
PagesPluginCapability,
get_ui);
}
void on_message(std::string message) override
{
PluginCapabilityInterface::RefCounter ref_counter(*this);
PythonGILState gil;
if (!gil)
throw std::runtime_error("Python interpreter is shutting down");
ORCA_PY_AUDIT_SCOPE(::Slic3r::PluginAuditManager::AuditMode::Loading);
pybind11::function override = pybind11::get_override(static_cast<PagesPluginCapability*>(this), "on_message");
if (!override)
return;
nlohmann::json data = nlohmann::json::parse(message, nullptr, false);
if (data.is_discarded())
data = message;
ORCA_PY_LOGGED_OVERRIDE_BODY(override(::Slic3r::json_to_py(data)));
}
};
} // namespace Slic3r

View File

@@ -13,10 +13,8 @@ namespace py = pybind11;
namespace Slic3r {
void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes)
void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
{
(void) pluginTypes;
auto printer_agent_module = module.def_submodule("printer_agent", "Printer Agent API");
py::enum_<FilamentSyncMode>(printer_agent_module, "FilamentSyncMode")

View File

@@ -19,7 +19,7 @@ namespace Slic3r {
class PrinterAgentPluginCapability : public PluginCapabilityInterface, public IPrinterAgent
{
public:
static void RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes);
static void RegisterBindings(pybind11::module_& module);
PluginCapabilityType get_type() const override { return PluginCapabilityType::PrinterConnection; }

View File

@@ -9,9 +9,8 @@
namespace py = pybind11;
namespace Slic3r {
void ScriptPluginCapability::RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes)
void ScriptPluginCapability::RegisterBindings(pybind11::module_& module)
{
(void) pluginTypes;
BOOST_LOG_TRIVIAL(debug) << "Registering orca.script bindings";
auto script = module.def_submodule("script", "Script Plugins API");

View File

@@ -11,8 +11,7 @@ public:
virtual ExecutionResult execute() = 0;
static void RegisterBindings(pybind11::module_ &module,
pybind11::enum_<PluginCapabilityType> &pluginTypes);
static void RegisterBindings(pybind11::module_ &module);
};
} // namespace Slic3r

View File

@@ -8,8 +8,7 @@ namespace Slic3r {
bool SlicingPipelineContext::cancelled() const { return print && print->canceled(); }
void SlicingPipelinePluginCapability::RegisterBindings(py::module_& module, py::enum_<PluginCapabilityType>& pluginTypes) {
(void) pluginTypes; // unused: this capability defines its own Step enum (below) rather than extending the shared PluginCapabilityType enum.
void SlicingPipelinePluginCapability::RegisterBindings(py::module_& module) {
auto slicing = module.def_submodule("slicing", "Slicing pipeline API (research/experimental).");
py::enum_<SlicingPipelineStepPlugin>(slicing, "Step")

View File

@@ -37,7 +37,7 @@ public:
// Runs on the slicing worker thread. Do not call orca.host.ui.* here: the UI thread can be
// blocked waiting on the slicing worker, so a marshaled UI call from this thread can deadlock.
virtual ExecutionResult execute(SlicingPipelineContext& ctx) = 0;
static void RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes);
static void RegisterBindings(pybind11::module_& module);
};
} // namespace Slic3r