This commit is contained in:
QIDI TECH
2024-09-03 09:34:33 +08:00
parent 27f34aa3e8
commit 585146181b
5147 changed files with 1734881 additions and 0 deletions

612
src/slic3r/CMakeLists.txt Normal file
View File

@@ -0,0 +1,612 @@
cmake_minimum_required(VERSION 3.13)
project(libslic3r_gui)
include(PrecompiledHeader)
set(SLIC3R_GUI_SOURCES
pchheader.cpp
pchheader.hpp
GUI/Printer/PrinterFileSystem.cpp
GUI/Printer/PrinterFileSystem.h
GUI/Widgets/AxisCtrlButton.cpp
GUI/Widgets/AxisCtrlButton.hpp
GUI/Widgets/Button.cpp
GUI/Widgets/Button.hpp
GUI/Widgets/CheckBox.cpp
GUI/Widgets/CheckBox.hpp
GUI/Widgets/RadioBox.hpp
GUI/Widgets/RadioBox.cpp
GUI/Widgets/ProgressDialog.hpp
GUI/Widgets/ProgressDialog.cpp
GUI/Widgets/RoundedRectangle.hpp
GUI/Widgets/RoundedRectangle.cpp
GUI/Widgets/ComboBox.cpp
GUI/Widgets/ComboBox.hpp
GUI/Widgets/SideButton.hpp
GUI/Widgets/SideButton.cpp
GUI/Widgets/SideMenuPopup.hpp
GUI/Widgets/SideMenuPopup.cpp
GUI/Widgets/DropDown.cpp
GUI/Widgets/DropDown.hpp
GUI/Widgets/PopupWindow.cpp
GUI/Widgets/PopupWindow.hpp
GUI/Widgets/Label.cpp
GUI/Widgets/Label.hpp
GUI/Widgets/Scrollbar.cpp
GUI/Widgets/Scrollbar.hpp
GUI/Widgets/ScrolledWindow.cpp
GUI/Widgets/ScrolledWindow.hpp
GUI/Widgets/StaticBox.cpp
GUI/Widgets/StaticBox.hpp
GUI/Widgets/ImageSwitchButton.cpp
GUI/Widgets/ImageSwitchButton.hpp
GUI/Widgets/SwitchButton.cpp
GUI/Widgets/SwitchButton.hpp
GUI/Widgets/DeviceButton.cpp
GUI/Widgets/DeviceButton.hpp
GUI/Widgets/SpinInput.cpp
GUI/Widgets/SpinInput.hpp
GUI/Widgets/StaticLine.cpp
GUI/Widgets/StaticLine.hpp
GUI/Widgets/StateColor.cpp
GUI/Widgets/StateColor.hpp
GUI/Widgets/StateHandler.cpp
GUI/Widgets/StateHandler.hpp
GUI/Widgets/TabCtrl.cpp
GUI/Widgets/TabCtrl.hpp
GUI/Widgets/TextInput.cpp
GUI/Widgets/TextInput.hpp
GUI/Widgets/TempInput.cpp
GUI/Widgets/TempInput.hpp
GUI/Widgets/AMSControl.cpp
GUI/Widgets/AMSControl.hpp
GUI/Widgets/FanControl.cpp
GUI/Widgets/FanControl.hpp
GUI/Widgets/Scrollbar.cpp
GUI/Widgets/Scrollbar.hpp
GUI/Widgets/ScrolledWindow.cpp
GUI/Widgets/ScrolledWindow.hpp
GUI/Widgets/StepCtrl.cpp
GUI/Widgets/StepCtrl.hpp
GUI/Widgets/ProgressBar.cpp
GUI/Widgets/ProgressBar.hpp
GUI/Widgets/SideTools.cpp
GUI/Widgets/SideTools.hpp
GUI/Widgets/WebView.cpp
GUI/Widgets/WebView.hpp
GUI/Widgets/ErrorMsgStaticText.cpp
GUI/Widgets/ErrorMsgStaticText.hpp
GUI/AboutDialog.cpp
GUI/AboutDialog.hpp
GUI/NetworkTestDialog.cpp
GUI/NetworkTestDialog.hpp
GUI/AuxiliaryDialog.cpp
GUI/AuxiliaryDialog.hpp
GUI/Auxiliary.cpp
GUI/Auxiliary.hpp
GUI/DailyTips.cpp
GUI/DailyTips.hpp
GUI/Project.cpp
GUI/Project.hpp
GUI/BackgroundSlicingProcess.cpp
GUI/BackgroundSlicingProcess.hpp
GUI/BitmapCache.cpp
GUI/BitmapCache.hpp
GUI/ImageGrid.cpp
GUI/ImageGrid.h
GUI/3DScene.cpp
GUI/3DScene.hpp
GUI/format.hpp
GUI/GLShadersManager.hpp
GUI/GLShadersManager.cpp
GUI/GLShader.cpp
GUI/GLShader.hpp
GUI/GLCanvas3D.hpp
GUI/GLCanvas3D.cpp
GUI/OpenGLManager.hpp
GUI/OpenGLManager.cpp
GUI/Selection.hpp
GUI/Selection.cpp
GUI/SlicingProgressNotification.cpp
GUI/SlicingProgressNotification.hpp
GUI/Gizmos/GLGizmosManager.cpp
GUI/Gizmos/GLGizmosManager.hpp
GUI/Gizmos/GLGizmosCommon.cpp
GUI/Gizmos/GLGizmosCommon.hpp
GUI/Gizmos/GLGizmoBase.cpp
GUI/Gizmos/GLGizmoBase.hpp
GUI/Gizmos/GLGizmoMove.cpp
GUI/Gizmos/GLGizmoMove.hpp
GUI/Gizmos/GLGizmoRotate.cpp
GUI/Gizmos/GLGizmoRotate.hpp
GUI/Gizmos/GLGizmoScale.cpp
GUI/Gizmos/GLGizmoScale.hpp
GUI/Gizmos/GLGizmoSlaSupports.cpp
GUI/Gizmos/GLGizmoSlaSupports.hpp
GUI/Gizmos/GLGizmoFdmSupports.cpp
GUI/Gizmos/GLGizmoFdmSupports.hpp
GUI/Gizmos/GLGizmoFlatten.cpp
GUI/Gizmos/GLGizmoFlatten.hpp
GUI/Gizmos/GLGizmoAdvancedCut.cpp
GUI/Gizmos/GLGizmoAdvancedCut.hpp
GUI/Gizmos/GLGizmoHollow.cpp
GUI/Gizmos/GLGizmoHollow.hpp
GUI/Gizmos/GLGizmoPainterBase.cpp
GUI/Gizmos/GLGizmoPainterBase.hpp
GUI/Gizmos/GLGizmoSimplify.cpp
GUI/Gizmos/GLGizmoSimplify.hpp
GUI/Gizmos/GLGizmoMmuSegmentation.cpp
GUI/Gizmos/GLGizmoMmuSegmentation.hpp
GUI/Gizmos/GLGizmoFaceDetector.cpp
GUI/Gizmos/GLGizmoFaceDetector.hpp
GUI/Gizmos/GLGizmoMeasure.cpp
GUI/Gizmos/GLGizmoMeasure.hpp
GUI/Gizmos/GLGizmoAssembly.cpp
GUI/Gizmos/GLGizmoAssembly.hpp
GUI/Gizmos/GLGizmoSeam.cpp
GUI/Gizmos/GLGizmoSeam.hpp
GUI/Gizmos/GLGizmoText.cpp
GUI/Gizmos/GLGizmoText.hpp
GUI/Gizmos/GLGizmoMeshBoolean.cpp
GUI/Gizmos/GLGizmoMeshBoolean.hpp
GUI/GLSelectionRectangle.cpp
GUI/GLSelectionRectangle.hpp
GUI/Gizmos/GizmoObjectManipulation.cpp
GUI/Gizmos/GizmoObjectManipulation.hpp
GUI/GLModel.hpp
GUI/GLModel.cpp
GUI/GLTexture.hpp
GUI/GLTexture.cpp
GUI/GLToolbar.hpp
GUI/GLToolbar.cpp
GUI/IMToolbar.hpp
GUI/IMToolbar.cpp
GUI/GCodeViewer.hpp
GUI/GCodeViewer.cpp
GUI/Preferences.cpp
GUI/Preferences.hpp
GUI/AMSSetting.cpp
GUI/AMSSetting.hpp
GUI/AMSMaterialsSetting.cpp
GUI/AMSMaterialsSetting.hpp
GUI/ExtrusionCalibration.cpp
GUI/ExtrusionCalibration.hpp
GUI/PresetHints.cpp
GUI/PresetHints.hpp
GUI/GUI.cpp
GUI/GUI.hpp
GUI/GUI_Init.cpp
GUI/GUI_Init.hpp
GUI/GUI_Preview.cpp
GUI/GUI_Preview.hpp
GUI/GUI_App.cpp
GUI/GUI_App.hpp
GUI/GUI_Utils.cpp
GUI/GUI_Utils.hpp
GUI/I18N.cpp
GUI/I18N.hpp
GUI/MainFrame.cpp
GUI/MainFrame.hpp
GUI/QDTTopbar.cpp
GUI/QDTTopbar.hpp
GUI/BedShapeDialog.cpp
GUI/BedShapeDialog.hpp
GUI/Plater.cpp
GUI/Plater.hpp
GUI/PartPlate.cpp
GUI/PartPlate.hpp
GUI/UserNotification.cpp
GUI/UserNotification.hpp
GUI/PresetComboBoxes.hpp
GUI/PresetComboBoxes.cpp
GUI/BitmapComboBox.hpp
GUI/BitmapComboBox.cpp
GUI/SavePresetDialog.hpp
GUI/SavePresetDialog.cpp
GUI/GUI_Colors.hpp
GUI/GUI_Colors.cpp
GUI/GUI_Factories.cpp
GUI/GUI_Factories.hpp
GUI/GUI_ObjectList.cpp
GUI/GUI_ObjectList.hpp
GUI/GUI_ObjectLayers.cpp
GUI/GUI_ObjectLayers.hpp
GUI/GUI_AuxiliaryList.cpp
GUI/GUI_AuxiliaryList.hpp
GUI/GUI_ObjectSettings.cpp
GUI/GUI_ObjectSettings.hpp
GUI/GUI_ObjectTable.cpp
GUI/GUI_ObjectTable.hpp
GUI/GUI_ObjectTableSettings.cpp
GUI/GUI_ObjectTableSettings.hpp
GUI/MeshUtils.cpp
GUI/MeshUtils.hpp
GUI/TickCode.cpp
GUI/TickCode.hpp
GUI/Tab.cpp
GUI/Tab.hpp
GUI/ParamsDialog.cpp
GUI/ParamsDialog.hpp
GUI/ParamsPanel.cpp
GUI/ParamsPanel.hpp
GUI/PrintHostDialogs.cpp
GUI/PrintHostDialogs.hpp
GUI/AmsWidgets.cpp
GUI/AmsWidgets.hpp
GUI/MediaFilePanel.cpp
GUI/MediaFilePanel.h
GUI/MediaPlayCtrl.cpp
GUI/MediaPlayCtrl.h
GUI/MonitorBasePanel.cpp
GUI/MonitorBasePanel.h
GUI/UpgradePanel.cpp
GUI/UpgradePanel.hpp
GUI/HintNotification.hpp
GUI/HintNotification.cpp
GUI/HMSPanel.hpp
GUI/HMSPanel.cpp
GUI/MonitorPage.cpp
GUI/MonitorPage.hpp
GUI/StatusPanel.cpp
GUI/StatusPanel.hpp
GUI/HMS.hpp
GUI/HMS.cpp
GUI/SliceInfoPanel.cpp
GUI/SliceInfoPanel.hpp
GUI/CameraPopup.cpp
GUI/CameraPopup.hpp
GUI/Monitor.cpp
GUI/Monitor.hpp
GUI/WebViewDialog.cpp
GUI/WebViewDialog.hpp
GUI/PrinterWebView.cpp
GUI/PrinterWebView.hpp
GUI/WebDownPluginDlg.hpp
GUI/WebDownPluginDlg.cpp
GUI/WebGuideDialog.hpp
GUI/WebGuideDialog.cpp
GUI/WebUserLoginDialog.cpp
GUI/WebUserLoginDialog.hpp
GUI/ConfigManipulation.cpp
GUI/ConfigManipulation.hpp
GUI/Field.cpp
GUI/Field.hpp
GUI/OptionsGroup.cpp
GUI/OptionsGroup.hpp
GUI/OG_CustomCtrl.cpp
GUI/OG_CustomCtrl.hpp
GUI/MarkdownTip.cpp
GUI/MarkdownTip.hpp
GUI/2DBed.cpp
GUI/2DBed.hpp
GUI/3DBed.cpp
GUI/3DBed.hpp
GUI/Camera.cpp
GUI/Camera.hpp
GUI/CameraUtils.cpp
GUI/CameraUtils.hpp
GUI/wxExtensions.cpp
GUI/wxExtensions.hpp
GUI/ObjColorDialog.cpp
GUI/ObjColorDialog.hpp
GUI/WipeTowerDialog.cpp
GUI/WipeTowerDialog.hpp
GUI/RemovableDriveManager.cpp
GUI/RemovableDriveManager.hpp
GUI/SendSystemInfoDialog.cpp
GUI/SendSystemInfoDialog.hpp
GUI/PlateSettingsDialog.cpp
GUI/PlateSettingsDialog.hpp
GUI/ImGuiWrapper.hpp
GUI/ImGuiWrapper.cpp
GUI/DeviceManager.hpp
GUI/DeviceManager.cpp
GUI/UserManager.hpp
GUI/UserManager.cpp
GUI/HttpServer.hpp
GUI/HttpServer.cpp
Config/Snapshot.cpp
Config/Snapshot.hpp
Config/Version.cpp
Config/Version.hpp
Utils/ASCIIFolding.cpp
Utils/ASCIIFolding.hpp
GUI/ConfigWizard.cpp
GUI/ConfigWizard.hpp
GUI/ConnectPrinter.cpp
GUI/ConnectPrinter.hpp
GUI/ConfigWizard_private.hpp
GUI/MsgDialog.cpp
GUI/MsgDialog.hpp
GUI/DownloadProgressDialog.hpp
GUI/DownloadProgressDialog.cpp
GUI/UpdateDialogs.cpp
GUI/UpdateDialogs.hpp
GUI/Jobs/Job.hpp
GUI/Jobs/Job.cpp
GUI/Jobs/PlaterJob.hpp
GUI/Jobs/PlaterJob.cpp
GUI/Jobs/UpgradeNetworkJob.hpp
GUI/Jobs/UpgradeNetworkJob.cpp
GUI/Jobs/ArrangeJob.hpp
GUI/Jobs/ArrangeJob.cpp
GUI/Jobs/OrientJob.hpp
GUI/Jobs/OrientJob.cpp
GUI/Jobs/RotoptimizeJob.hpp
GUI/Jobs/RotoptimizeJob.cpp
GUI/Jobs/FillBedJob.hpp
GUI/Jobs/FillBedJob.cpp
GUI/Jobs/SLAImportJob.hpp
GUI/Jobs/SLAImportJob.cpp
GUI/Jobs/ProgressIndicator.hpp
GUI/Jobs/PrintJob.hpp
GUI/Jobs/PrintJob.cpp
GUI/Jobs/SendJob.hpp
GUI/Jobs/SendJob.cpp
GUI/Jobs/BindJob.hpp
GUI/Jobs/BindJob.cpp
GUI/Jobs/NotificationProgressIndicator.hpp
GUI/Jobs/NotificationProgressIndicator.cpp
GUI/PhysicalPrinterDialog.hpp
GUI/PhysicalPrinterDialog.cpp
GUI/ProgressStatusBar.hpp
GUI/ProgressStatusBar.cpp
GUI/QDTStatusBar.hpp
GUI/QDTStatusBar.cpp
GUI/QDTStatusBarSend.hpp
GUI/QDTStatusBarSend.cpp
GUI/QDTStatusBarBind.hpp
GUI/QDTStatusBarBind.cpp
GUI/Mouse3DController.cpp
GUI/Mouse3DController.hpp
GUI/IMSlider.cpp
GUI/IMSlider.hpp
GUI/IMSlider_Utils.hpp
GUI/Notebook.cpp
GUI/Notebook.hpp
GUI/TabButton.cpp
GUI/TabButton.hpp
GUI/Tabbook.cpp
GUI/Tabbook.hpp
GUI/ObjectDataViewModel.cpp
GUI/ObjectDataViewModel.hpp
GUI/AuxiliaryDataViewModel.cpp
GUI/AuxiliaryDataViewModel.hpp
GUI/InstanceCheck.cpp
GUI/InstanceCheck.hpp
GUI/Search.cpp
GUI/Search.hpp
GUI/NotificationManager.cpp
GUI/NotificationManager.hpp
GUI/UnsavedChangesDialog.cpp
GUI/UnsavedChangesDialog.hpp
GUI/ExtraRenderers.cpp
GUI/ExtraRenderers.hpp
GUI/ProjectDirtyStateManager.hpp
GUI/ProjectDirtyStateManager.cpp
GUI/DesktopIntegrationDialog.cpp
GUI/DesktopIntegrationDialog.hpp
GUI/DragCanvas.cpp
GUI/DragCanvas.hpp
GUI/PublishDialog.cpp
GUI/PublishDialog.hpp
GUI/RecenterDialog.cpp
GUI/RecenterDialog.hpp
GUI/PrivacyUpdateDialog.cpp
GUI/PrivacyUpdateDialog.hpp
GUI/BonjourDialog.cpp
GUI/BonjourDialog.hpp
GUI/BindDialog.cpp
GUI/BindDialog.hpp
GUI/ModelMall.hpp
GUI/ModelMall.cpp
GUI/SelectMachine.hpp
GUI/SelectMachine.cpp
GUI/SendToPrinter.hpp
GUI/SendToPrinter.cpp
GUI/AmsMappingPopup.hpp
GUI/AmsMappingPopup.cpp
GUI/ReleaseNote.hpp
GUI/ReleaseNote.cpp
GUI/SingleChoiceDialog.hpp
GUI/SingleChoiceDialog.cpp
GUI/CaliHistoryDialog.hpp
GUI/CaliHistoryDialog.cpp
GUI/CalibrationPanel.hpp
GUI/CalibrationPanel.cpp
GUI/CalibrationWizard.hpp
GUI/CalibrationWizard.cpp
GUI/CalibrationWizardPage.cpp
GUI/CalibrationWizardPage.hpp
GUI/CalibrationWizardStartPage.cpp
GUI/CalibrationWizardStartPage.hpp
GUI/CalibrationWizardPresetPage.cpp
GUI/CalibrationWizardPresetPage.hpp
GUI/calib_dlg.cpp
GUI/calib_dlg.hpp
GUI/Calibration.hpp
GUI/Calibration.cpp
GUI/PrintOptionsDialog.hpp
GUI/PrintOptionsDialog.cpp
GUI/CreatePresetsDialog.hpp
GUI/CreatePresetsDialog.cpp
Utils/json_diff.hpp
Utils/json_diff.cpp
GUI/KBShortcutsDialog.hpp
GUI/KBShortcutsDialog.cpp
GUI/MultiMachine.hpp
GUI/MultiMachine.cpp
GUI/MultiMachinePage.hpp
GUI/MultiMachinePage.cpp
GUI/MultiMachineManagerPage.cpp
GUI/MultiMachineManagerPage.hpp
GUI/MultiPrintJob.cpp
GUI/MultiPrintJob.hpp
GUI/MultiSendMachineModel.hpp
GUI/MultiSendMachineModel.cpp
GUI/MultiTaskManagerPage.hpp
GUI/MultiTaskManagerPage.cpp
GUI/MultiTaskModel.hpp
GUI/MultiTaskModel.cpp
GUI/SendMultiMachinePage.hpp
GUI/SendMultiMachinePage.cpp
GUI/TaskManager.cpp
GUI/TaskManager.hpp
Utils/Http.cpp
Utils/Http.hpp
Utils/FixModelByWin10.cpp
Utils/FixModelByWin10.hpp
Utils/Bonjour.cpp
Utils/Bonjour.hpp
Utils/FileHelp.cpp
Utils/FileHelp.hpp
Utils/PresetUpdater.cpp
Utils/PresetUpdater.hpp
Utils/Process.cpp
Utils/Process.hpp
Utils/Profile.hpp
Utils/UndoRedo.cpp
Utils/UndoRedo.hpp
Utils/HexFile.cpp
Utils/HexFile.hpp
Utils/TCPConsole.cpp
Utils/TCPConsole.hpp
Utils/Udp.cpp
Utils/Udp.hpp
Utils/minilzo_extension.hpp
Utils/minilzo_extension.cpp
Utils/ColorSpaceConvert.hpp
Utils/ColorSpaceConvert.cpp
Utils/OctoPrint.hpp
Utils/OctoPrint.cpp
Utils/PrintHost.hpp
Utils/PrintHost.cpp
Utils/NetworkAgent.cpp
Utils/NetworkAgent.hpp
Utils/MKS.hpp
Utils/MKS.cpp
Utils/Duet.cpp
Utils/Duet.hpp
Utils/FlashAir.cpp
Utils/FlashAir.hpp
Utils/AstroBox.cpp
Utils/AstroBox.hpp
Utils/Repetier.cpp
Utils/Repetier.hpp
Utils/CalibUtils.cpp
Utils/CalibUtils.hpp
Utils/ProfileDescription.hpp
Utils/FontConfigHelp.cpp
Utils/FontConfigHelp.hpp
Utils/FontUtils.cpp
Utils/FontUtils.hpp
)
if(QDT_RELEASE_TO_PUBLIC)
list(APPEND SLIC3R_GUI_SOURCES
QIDI/QIDINetwork.cpp
QIDI/QIDINetwork.hpp
)
endif ()
if (WIN32)
list(APPEND SLIC3R_GUI_SOURCES
GUI/dark_mode/dark_mode.hpp
GUI/dark_mode/IatHook.hpp
GUI/dark_mode/UAHMenuBar.hpp
GUI/dark_mode.hpp
GUI/dark_mode.cpp
)
endif ()
if (APPLE)
list(APPEND SLIC3R_GUI_SOURCES
Utils/RetinaHelperImpl.mm
Utils/MacDarkMode.mm
GUI/RemovableDriveManagerMM.mm
GUI/RemovableDriveManagerMM.h
GUI/Mouse3DHandlerMac.mm
GUI/InstanceCheckMac.mm
GUI/InstanceCheckMac.h
GUI/wxMediaCtrl2.mm
GUI/wxMediaCtrl2.h
)
FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration)
else ()
list(APPEND SLIC3R_GUI_SOURCES
GUI/wxMediaCtrl2.cpp
GUI/wxMediaCtrl2.h
)
endif ()
if (UNIX AND NOT APPLE)
list(APPEND SLIC3R_GUI_SOURCES
GUI/Printer/gstQIDIsrc.c
)
endif ()
add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES})
target_include_directories(libslic3r_gui PRIVATE Utils)
if (WIN32)
target_include_directories(libslic3r_gui PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/WebView2/include)
endif()
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES})
encoding_check(libslic3r_gui)
target_link_libraries(libslic3r_gui libslic3r cereal imgui minilzo GLEW::GLEW OpenGL::GL hidapi ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto)
#target_link_libraries(libslic3r_gui libslic3r cereal imgui minilzo GLEW::GLEW OpenGL::GL hidapi libcurl OpenSSL::SSL OpenSSL::Crypto ${wxWidgets_LIBRARIES} glfw)
if (MSVC)
target_link_libraries(libslic3r_gui Setupapi.lib)
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
FIND_LIBRARY(WAYLAND_SERVER_LIBRARIES NAMES wayland-server)
FIND_LIBRARY(WAYLAND_EGL_LIBRARIES NAMES wayland-egl)
FIND_LIBRARY(WAYLAND_CLIENT_LIBRARIES NAMES wayland-client)
find_package(CURL REQUIRED)
target_link_libraries(libslic3r_gui ${DBUS_LIBRARIES} OSMesa)
target_link_libraries(libslic3r_gui
OpenGL::EGL
${WAYLAND_SERVER_LIBRARIES}
${WAYLAND_EGL_LIBRARIES}
${WAYLAND_CLIENT_LIBRARIES}
${CURL_LIBRARIES}
)
elseif (APPLE)
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY})
endif()
if (SLIC3R_STATIC)
# FIXME: This was previously exported by wx-config but the wxWidgets
# cmake build forgets this and the build fails in debug mode (or on raspberry release)
target_compile_definitions(libslic3r_gui PUBLIC -DwxDEBUG_LEVEL=0)
endif()
if (SLIC3R_STATIC AND NOT SLIC3R_STATIC_EXCLUDE_CURL AND UNIX AND NOT APPLE)
target_compile_definitions(libslic3r_gui PRIVATE OPENSSL_CERT_OVERRIDE)
endif ()
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE)
endif ()
# We need to implement some hacks for wxWidgets and touch the underlying GTK
# layer and sub-libraries. This forces us to use the include locations and
# link these libraries.
if (UNIX AND NOT APPLE)
find_package(GTK${SLIC3R_GTK} REQUIRED)
target_include_directories(libslic3r_gui PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS})
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES})
# We add GStreamer for qidi:/// support.
pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0)
pkg_check_modules(GST_BASE REQUIRED gstreamer-base-1.0)
target_link_libraries(libslic3r_gui ${GSTREAMER_LIBRARIES} ${GST_BASE_LIBRARIES})
target_include_directories(libslic3r_gui PRIVATE ${GSTREAMER_INCLUDE_DIRS} ${GST_BASE_INCLUDE_DIRS})
endif ()
# Add a definition so that we can tell we are compiling slic3r.
target_compile_definitions(libslic3r_gui PRIVATE SLIC3R_CURRENTLY_COMPILING_GUI_MODULE)

View File

@@ -0,0 +1,606 @@
#include "Snapshot.hpp"
#include <time.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Time.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/FileParserError.hpp"
#include "libslic3r/Utils.hpp"
//QDS
#include "libslic3r/Preset.hpp"
#include "../GUI/GUI.hpp"
#include "../GUI/GUI_App.hpp"
#include "../GUI/I18N.hpp"
#include "../GUI/MainFrame.hpp"
#include "../GUI/MsgDialog.hpp"
#include <wx/richmsgdlg.h>
#define SLIC3R_SNAPSHOTS_DIR "snapshots"
#define SLIC3R_SNAPSHOT_FILE "snapshot.ini"
namespace Slic3r {
namespace GUI {
namespace Config {
void Snapshot::clear()
{
this->id.clear();
this->time_captured = 0;
this->slic3r_version_captured = Semver::invalid();
this->comment.clear();
this->reason = SNAPSHOT_UNKNOWN;
this->print.clear();
this->sla_print.clear();
this->filaments.clear();
this->sla_material.clear();
this->printer.clear();
this->physical_printer.clear();
}
void Snapshot::load_ini(const std::string &path)
{
this->clear();
auto throw_on_parse_error = [&path](const std::string &msg) {
throw file_parser_error(std::string("Failed loading the snapshot file. Reason: ") + msg, path);
};
// Load the snapshot.ini file.
boost::property_tree::ptree tree;
try {
boost::nowide::ifstream ifs(path);
boost::property_tree::read_ini(ifs, tree);
} catch (const std::ifstream::failure &err) {
throw file_parser_error(std::string("The snapshot file cannot be loaded. Reason: ") + err.what(), path);
} catch (const std::runtime_error &err) {
throw_on_parse_error(err.what());
}
// Parse snapshot.ini
std::string group_name_vendor = "Vendor:";
std::string key_filament = "filament";
std::string key_prefix_model = "model_";
for (auto &section : tree) {
if (section.first == "snapshot") {
// Parse the common section.
for (auto &kvp : section.second) {
if (kvp.first == "id")
this->id = kvp.second.data();
else if (kvp.first == "time_captured") {
this->time_captured = Slic3r::Utils::parse_iso_utc_timestamp(kvp.second.data());
if (this->time_captured == (time_t)-1)
throw_on_parse_error("invalid timestamp");
} else if (kvp.first == "slic3r_version_captured") {
auto semver = Semver::parse(kvp.second.data());
if (! semver)
throw_on_parse_error("invalid slic3r_version_captured semver");
this->slic3r_version_captured = *semver;
} else if (kvp.first == "comment") {
this->comment = kvp.second.data();
} else if (kvp.first == "reason") {
std::string rsn = kvp.second.data();
if (rsn == "upgrade")
this->reason = SNAPSHOT_UPGRADE;
else if (rsn == "downgrade")
this->reason = SNAPSHOT_DOWNGRADE;
else if (rsn == "before_rollback")
this->reason = SNAPSHOT_BEFORE_ROLLBACK;
else if (rsn == "user")
this->reason = SNAPSHOT_USER;
else
this->reason = SNAPSHOT_UNKNOWN;
}
}
} else if (section.first == "presets") {
// Load the names of the active presets.
for (auto &kvp : section.second) {
if (kvp.first == PRESET_PRINT_NAME) {
this->print = kvp.second.data();
} else if (kvp.first == "sla_print") {
this->sla_print = kvp.second.data();
} else if (boost::starts_with(kvp.first, PRESET_FILAMENT_NAME)) {
int idx = 0;
if (kvp.first == "filament" || sscanf(kvp.first.c_str(), "filament_%d", &idx) == 1) {
if (int(this->filaments.size()) <= idx)
this->filaments.resize(idx + 1, std::string());
this->filaments[idx] = kvp.second.data();
}
} else if (kvp.first == "sla_material") {
this->sla_material = kvp.second.data();
} else if (kvp.first == PRESET_PRINTER_NAME) {
this->printer = kvp.second.data();
} else if (kvp.first == "physical_printer") {
this->physical_printer = kvp.second.data();
}
}
} else if (boost::starts_with(section.first, group_name_vendor) && section.first.size() > group_name_vendor.size()) {
// Vendor specific section.
VendorConfig vc;
vc.name = section.first.substr(group_name_vendor.size());
for (auto &kvp : section.second) {
if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") {
// Version of the vendor specific config bundle bundled with this snapshot.
auto semver = Semver::parse(kvp.second.data());
if (! semver)
throw_on_parse_error("invalid " + kvp.first + " format for " + section.first);
if (kvp.first == "version")
vc.version.config_version = *semver;
else if (kvp.first == "min_slic3r_version")
vc.version.min_slic3r_version = *semver;
else
vc.version.max_slic3r_version = *semver;
} else if (boost::starts_with(kvp.first, key_prefix_model) && kvp.first.size() > key_prefix_model.size()) {
// Parse the printer variants installed for the current model.
auto &set_variants = vc.models_variants_installed[kvp.first.substr(key_prefix_model.size())];
std::vector<std::string> variants;
if (unescape_strings_cstyle(kvp.second.data(), variants))
for (auto &variant : variants)
set_variants.insert(std::move(variant));
}
}
this->vendor_configs.emplace_back(std::move(vc));
}
}
// Sort the vendors lexicographically.
std::sort(this->vendor_configs.begin(), this->vendor_configs.begin(),
[](const VendorConfig &cfg1, const VendorConfig &cfg2) { return cfg1.name < cfg2.name; });
}
static std::string reason_string(const Snapshot::Reason reason)
{
switch (reason) {
case Snapshot::SNAPSHOT_UPGRADE:
return "upgrade";
case Snapshot::SNAPSHOT_DOWNGRADE:
return "downgrade";
case Snapshot::SNAPSHOT_BEFORE_ROLLBACK:
return "before_rollback";
case Snapshot::SNAPSHOT_USER:
return "user";
case Snapshot::SNAPSHOT_UNKNOWN:
default:
return "unknown";
}
}
void Snapshot::save_ini(const std::string &path)
{
boost::nowide::ofstream c;
c.open(path, std::ios::out | std::ios::trunc);
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
// Export the common "snapshot".
c << std::endl << "[snapshot]" << std::endl;
c << "id = " << this->id << std::endl;
c << "time_captured = " << Slic3r::Utils::iso_utc_timestamp(this->time_captured) << std::endl;
c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl;
c << "comment = " << this->comment << std::endl;
c << "reason = " << reason_string(this->reason) << std::endl;
// Export the active presets at the time of the snapshot.
c << std::endl << "[presets]" << std::endl;
c << "print = " << this->print << std::endl;
c << "sla_print = " << this->sla_print << std::endl;
c << "filament = " << this->filaments.front() << std::endl;
for (size_t i = 1; i < this->filaments.size(); ++ i)
c << "filament_" << std::to_string(i) << " = " << this->filaments[i] << std::endl;
c << "sla_material = " << this->sla_material << std::endl;
c << "printer = " << this->printer << std::endl;
c << "physical_printer = " << this->physical_printer << std::endl;
// Export the vendor configs.
for (const VendorConfig &vc : this->vendor_configs) {
c << std::endl << "[Vendor:" << vc.name << "]" << std::endl;
c << "version = " << vc.version.config_version.to_string() << std::endl;
c << "min_slic3r_version = " << vc.version.min_slic3r_version.to_string() << std::endl;
c << "max_slic3r_version = " << vc.version.max_slic3r_version.to_string() << std::endl;
// Export installed printer models and their variants.
for (const auto &model : vc.models_variants_installed) {
if (model.second.size() == 0)
continue;
const std::vector<std::string> variants(model.second.begin(), model.second.end());
const auto escaped = escape_strings_cstyle(variants);
c << "model_" << model.first << " = " << escaped << std::endl;
}
}
c.close();
}
void Snapshot::export_selections(AppConfig &config) const
{
assert(filaments.size() >= 1);
config.clear_section("presets");
config.set("presets", PRESET_PRINT_NAME, print);
config.set("presets", "sla_print", sla_print);
config.set("presets", PRESET_FILAMENT_NAME, filaments.front());
for (unsigned i = 1; i < filaments.size(); ++i) {
char name[64];
sprintf(name, "filament_%u", i);
config.set("presets", name, filaments[i]);
}
config.set("presets", "sla_material", sla_material);
config.set("presets", PRESET_PRINTER_NAME, printer);
config.set("presets", "physical_printer", physical_printer);
}
void Snapshot::export_vendor_configs(AppConfig &config) const
{
std::map<std::string, std::map<std::string, std::set<std::string>>> vendors;
for (const VendorConfig &vc : vendor_configs)
vendors[vc.name] = vc.models_variants_installed;
config.set_vendors(std::move(vendors));
}
//QDS: change directories by desigh
static constexpr auto snapshot_subdirs = { PRESET_PRINT_NAME, PRESET_SLA_PRINT_NAME, PRESET_FILAMENT_NAME, PRESET_SLA_MATERIALS_NAME, PRESET_PRINTER_NAME, PHYSICAL_PRINTER, PRESET_SYSTEM_DIR };
//static constexpr auto snapshot_subdirs = { "print", "sla_print", "filament", "sla_material", "printer", "physical_printer", "vendor" };
// Perform a deep compare of the active print / sla_print / filament / sla_material / printer / physical_printer / vendor directories.
// Return true if the content of the current print / sla_print / filament / sla_material / printer / physical_printer / vendor directories
// matches the state stored in this snapshot.
bool Snapshot::equal_to_active(const AppConfig &app_config) const
{
// 1) Check, whether this snapshot contains the same set of active vendors, printer models and variants
// as app_config.
{
std::set<std::string> matched;
for (const VendorConfig &vc : this->vendor_configs) {
auto it_vendor_models_variants = app_config.vendors().find(vc.name);
if (it_vendor_models_variants == app_config.vendors().end() ||
it_vendor_models_variants->second != vc.models_variants_installed)
// There are more vendors enabled in the snapshot than currently installed.
return false;
matched.insert(vc.name);
}
for (const auto &v : app_config.vendors())
if (matched.find(v.first) == matched.end() && ! v.second.empty())
// There are more vendors currently installed than enabled in the snapshot.
return false;
}
// 2) Check, whether this snapshot references the same set of ini files as the current state.
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshot_dir = boost::filesystem::path(Slic3r::data_dir()) / SLIC3R_SNAPSHOTS_DIR / this->id;
for (const char *subdir : snapshot_subdirs) {
boost::filesystem::path path1 = data_dir / subdir;
boost::filesystem::path path2 = snapshot_dir / subdir;
std::vector<std::string> files1, files2;
if (boost::filesystem::is_directory(path1))
for (auto &dir_entry : boost::filesystem::directory_iterator(path1))
if (Slic3r::is_ini_file(dir_entry))
files1.emplace_back(dir_entry.path().filename().string());
if (boost::filesystem::is_directory(path2))
for (auto &dir_entry : boost::filesystem::directory_iterator(path2))
if (Slic3r::is_ini_file(dir_entry))
files2.emplace_back(dir_entry.path().filename().string());
std::sort(files1.begin(), files1.end());
std::sort(files2.begin(), files2.end());
if (files1 != files2)
return false;
for (const std::string &filename : files1) {
FILE *f1 = boost::nowide::fopen((path1 / filename).string().c_str(), "rb");
FILE *f2 = boost::nowide::fopen((path2 / filename).string().c_str(), "rb");
bool same = true;
if (f1 && f2) {
char buf1[4096];
char buf2[4096];
do {
size_t r1 = fread(buf1, 1, 4096, f1);
size_t r2 = fread(buf2, 1, 4096, f2);
if (r1 != r2 || memcmp(buf1, buf2, r1)) {
same = false;
break;
}
} while (! feof(f1) || ! feof(f2));
} else
same = false;
if (f1)
fclose(f1);
if (f2)
fclose(f2);
if (! same)
return false;
}
}
return true;
}
size_t SnapshotDB::load_db()
{
boost::filesystem::path snapshots_dir = SnapshotDB::create_db_dir();
m_snapshots.clear();
// Walk over the snapshot directories and load their index.
std::string errors_cummulative;
for (auto &dir_entry : boost::filesystem::directory_iterator(snapshots_dir))
if (boost::filesystem::is_directory(dir_entry.status())) {
// Try to read "snapshot.ini".
boost::filesystem::path path_ini = dir_entry.path() / SLIC3R_SNAPSHOT_FILE;
Snapshot snapshot;
try {
snapshot.load_ini(path_ini.string());
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
errors_cummulative += "\n";
continue;
}
// Check that the name of the snapshot directory matches the snapshot id stored in the snapshot.ini file.
if (dir_entry.path().filename().string() != snapshot.id) {
errors_cummulative += std::string("Snapshot ID ") + snapshot.id + " does not match the snapshot directory " + dir_entry.path().filename().string() + "\n";
continue;
}
m_snapshots.emplace_back(std::move(snapshot));
}
// Sort the snapshots by their date/time.
std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; });
if (! errors_cummulative.empty())
throw Slic3r::RuntimeError(errors_cummulative);
return m_snapshots.size();
}
void SnapshotDB::update_slic3r_versions(std::vector<Index> &index_db)
{
for (Snapshot &snapshot : m_snapshots) {
for (Snapshot::VendorConfig &vendor_config : snapshot.vendor_configs) {
auto it = std::find_if(index_db.begin(), index_db.end(), [&vendor_config](const Index &idx) { return idx.vendor() == vendor_config.name; });
if (it != index_db.end()) {
Index::const_iterator it_version = it->find(vendor_config.version.config_version);
if (it_version != it->end()) {
vendor_config.version.min_slic3r_version = it_version->min_slic3r_version;
vendor_config.version.max_slic3r_version = it_version->max_slic3r_version;
}
}
}
}
}
static void copy_config_dir_single_level(const boost::filesystem::path &path_src, const boost::filesystem::path &path_dst)
{
//QDS: remove snapshots function currently
#if 0
if (! boost::filesystem::is_directory(path_dst) &&
! boost::filesystem::create_directory(path_dst))
throw Slic3r::RuntimeError(std::string("QIDIStudio was unable to create a directory at ") + path_dst.string());
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
if (Slic3r::is_ini_file(dir_entry))
if (std::string error_message; copy_file(dir_entry.path().string(), (path_dst / dir_entry.path().filename()).string(), error_message, false) != SUCCESS)
throw Slic3r::RuntimeError(format("Failed copying \"%1%\" to \"%2%\": %3%", path_src.string(), path_dst.string(), error_message));
#endif
}
static void delete_existing_ini_files(const boost::filesystem::path &path)
{
//QDS: remove snapshots function currently
#if 0
if (! boost::filesystem::is_directory(path))
return;
for (auto &dir_entry : boost::filesystem::directory_iterator(path))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
boost::filesystem::remove(dir_entry.path());
#endif
}
const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
{
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
// 1) Prepare the snapshot structure.
Snapshot snapshot;
// Snapshot header.
snapshot.time_captured = Slic3r::Utils::get_current_time_utc();
snapshot.id = Slic3r::Utils::iso_utc_timestamp(snapshot.time_captured);
snapshot.slic3r_version_captured = Slic3r::SEMVER;
snapshot.comment = comment;
snapshot.reason = reason;
// Active presets at the time of the snapshot.
snapshot.print = app_config.get("presets", PRESET_PRINT_NAME);
snapshot.sla_print = app_config.get("presets", "sla_print");
snapshot.filaments.emplace_back(app_config.get("presets", PRESET_FILAMENT_NAME));
snapshot.sla_material = app_config.get("presets", "sla_material");
snapshot.printer = app_config.get("presets", PRESET_PRINTER_NAME);
snapshot.physical_printer = app_config.get("presets", PHYSICAL_PRINTER);
for (unsigned i = 1; i < 1000; ++ i) {
char name[64];
sprintf(name, "filament_%u", i);
if (! app_config.has("presets", name))
break;
snapshot.filaments.emplace_back(app_config.get("presets", name));
}
// Vendor specific config bundles and installed printers.
for (const auto &vendor : app_config.vendors()) {
Snapshot::VendorConfig cfg;
cfg.name = vendor.first;
cfg.models_variants_installed = vendor.second;
for (auto it = cfg.models_variants_installed.begin(); it != cfg.models_variants_installed.end();)
if (it->second.empty())
cfg.models_variants_installed.erase(it ++);
else
++ it;
// Read the active config bundle, parse the config version.
PresetBundle bundle;
//QDS: change directoties by design
//bundle.load_configbundle((data_dir / PRESET_SYSTEM_DIR / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
bundle.load_vendor_configs_from_json((data_dir/PRESET_SYSTEM_DIR).string(), cfg.name, PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
for (const auto &vp : bundle.vendors)
if (vp.second.id == cfg.name)
cfg.version.config_version = vp.second.config_version;
snapshot.vendor_configs.emplace_back(std::move(cfg));
}
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
try {
boost::filesystem::create_directory(snapshot_dir);
// Backup the presets.
for (const char *subdir : snapshot_subdirs)
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
m_snapshots.emplace_back(std::move(snapshot));
} catch (...) {
if (boost::filesystem::is_directory(snapshot_dir)) {
try {
// Clean up partially copied snapshot.
boost::filesystem::remove_all(snapshot_dir);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed taking snapshot and failed removing the snapshot directory " << snapshot_dir;
}
}
throw;
}
return m_snapshots.back();
}
const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config)
{
for (const Snapshot &snapshot : m_snapshots)
if (snapshot.id == id) {
this->restore_snapshot(snapshot, app_config);
return snapshot;
}
throw Slic3r::RuntimeError(std::string("Snapshot with id " + id + " was not found."));
}
void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_config)
{
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
// Remove existing ini files and restore the ini files from the snapshot.
for (const char *subdir : snapshot_subdirs) {
boost::filesystem::path src = snapshot_dir / subdir;
boost::filesystem::path dst = data_dir / subdir;
delete_existing_ini_files(dst);
if (boost::filesystem::is_directory(src))
copy_config_dir_single_level(src, dst);
}
// Update AppConfig with the selections of the print / sla_print / filament / sla_material / printer profiles
// and about the installed printer types and variants.
snapshot.export_selections(app_config);
snapshot.export_vendor_configs(app_config);
}
bool SnapshotDB::is_on_snapshot(AppConfig &app_config) const
{
// Is the "on_snapshot" configuration value set?
std::string on_snapshot = app_config.get("on_snapshot");
if (on_snapshot.empty())
// No, we are not on a snapshot.
return false;
// Is the "on_snapshot" equal to the current configuration state?
auto it_snapshot = this->snapshot(on_snapshot);
if (it_snapshot != this->end() && it_snapshot->equal_to_active(app_config))
// Yes, we are on the snapshot.
return true;
// No, we are no more on a snapshot. Reset the state.
app_config.set("on_snapshot", "");
return false;
}
SnapshotDB::const_iterator SnapshotDB::snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version)
{
auto it_found = m_snapshots.end();
Snapshot::VendorConfig key;
key.name = vendor_name;
for (auto it = m_snapshots.begin(); it != m_snapshots.end(); ++ it) {
const Snapshot &snapshot = *it;
auto it_vendor_config = std::lower_bound(snapshot.vendor_configs.begin(), snapshot.vendor_configs.end(),
key, [](const Snapshot::VendorConfig &cfg1, const Snapshot::VendorConfig &cfg2) { return cfg1.name < cfg2.name; });
if (it_vendor_config != snapshot.vendor_configs.end() && it_vendor_config->name == vendor_name &&
config_version == it_vendor_config->version.config_version) {
// Vendor config found with the correct version.
// Save it, but continue searching, as we want the newest snapshot.
it_found = it;
}
}
return it_found;
}
SnapshotDB::const_iterator SnapshotDB::snapshot(const std::string &id) const
{
for (const_iterator it = m_snapshots.begin(); it != m_snapshots.end(); ++ it)
if (it->id == id)
return it;
return m_snapshots.end();
}
boost::filesystem::path SnapshotDB::create_db_dir()
{
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshots_dir = data_dir / SLIC3R_SNAPSHOTS_DIR;
for (const boost::filesystem::path &path : { data_dir, snapshots_dir }) {
boost::filesystem::path subdir = path;
subdir.make_preferred();
if (! boost::filesystem::is_directory(subdir) &&
! boost::filesystem::create_directory(subdir))
throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + subdir.string());
}
return snapshots_dir;
}
SnapshotDB& SnapshotDB::singleton()
{
static SnapshotDB instance;
static bool loaded = false;
if (! loaded) {
try {
loaded = true;
// Load the snapshot database.
instance.load_db();
// Load the vendor specific configuration indices.
std::vector<Index> index_db = Index::load_db();
// Update the min / max slic3r versions compatible with the configurations stored inside the snapshots
// based on the min / max slic3r versions defined by the vendor specific config indices.
instance.update_slic3r_versions(index_db);
} catch (std::exception & /* ex */) {
}
}
return instance;
}
const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
{
try {
return &SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
} catch (std::exception &err) {
show_error(static_cast<wxWindow*>(wxGetApp().mainframe),
_L("Taking a configuration snapshot failed.") + "\n\n" + from_u8(err.what()));
return nullptr;
}
}
bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message)
{
try {
SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
return true;
} catch (std::exception &err) {
RichMessageDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe),
_L("QIDIStudio has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message),
_L("QIDIStudio error"),
wxYES_NO);
dlg.SetYesNoLabels(_L("Continue"), _L("Abort"));
return dlg.ShowModal() == wxID_YES;
}
}
} // namespace Config
} // namespace GUI
} // namespace Slic3r

View File

@@ -0,0 +1,141 @@
#ifndef slic3r_GUI_Snapshot_
#define slic3r_GUI_Snapshot_
#include <map>
#include <set>
#include <string>
#include <vector>
#include <boost/filesystem/path.hpp>
#include "libslic3r/Semver.hpp"
#include "Version.hpp"
namespace Slic3r {
class AppConfig;
namespace GUI {
namespace Config {
// A snapshot contains:
// Slic3r.ini
// vendor/
// print/
// sla_print/
// filament/
// sla_material
// printer/
// physical_printer/
class Snapshot
{
public:
enum Reason {
SNAPSHOT_UNKNOWN,
SNAPSHOT_UPGRADE,
SNAPSHOT_DOWNGRADE,
SNAPSHOT_BEFORE_ROLLBACK,
SNAPSHOT_USER,
};
Snapshot() { clear(); }
void clear();
void load_ini(const std::string &path);
void save_ini(const std::string &path);
// Export the print / sla_print / filament / sla_material / printer selections to be activated into the AppConfig.
void export_selections(AppConfig &config) const;
void export_vendor_configs(AppConfig &config) const;
// Perform a deep compare of the active print / sla_print / filament / sla_material / printer / physical_printer / vendor directories.
// Return true if the content of the current print / sla_print / filament / sla_material / printer / physical_printer / vendor directories
// matches the state stored in this snapshot.
bool equal_to_active(const AppConfig &app_config) const;
// ID of a snapshot should equal to the name of the snapshot directory.
// The ID contains the date/time, reason and comment to be human readable.
std::string id;
std::time_t time_captured;
// Which Slic3r version captured this snapshot?
Semver slic3r_version_captured = Semver::invalid();
// Comment entered by the user at the start of the snapshot capture.
std::string comment;
Reason reason;
std::string format_reason() const;
// Active presets at the time of the snapshot.
std::string print;
std::string sla_print;
std::vector<std::string> filaments;
std::string sla_material;
std::string printer;
std::string physical_printer;
// Annotation of the vendor configuration stored in the snapshot.
// This information is displayed to the user and used to decide compatibility
// of the configuration stored in the snapshot with the running Slic3r version.
struct VendorConfig {
// Name of the vendor contained in this snapshot.
std::string name;
// Version of the vendor config contained in this snapshot, along with compatibility data.
Version version;
// Which printer models of this vendor were installed, and which variants of the models?
std::map<std::string, std::set<std::string>> models_variants_installed;
};
// List of vendor configs contained in this snapshot, sorted lexicographically.
std::vector<VendorConfig> vendor_configs;
};
class SnapshotDB
{
public:
// Initialize the SnapshotDB singleton instance. Load the database if it has not been loaded yet.
static SnapshotDB& singleton();
typedef std::vector<Snapshot>::const_iterator const_iterator;
// Load the snapshot database from the snapshots directory.
// If the snapshot directory or its parent does not exist yet, it will be created.
// Returns a number of snapshots loaded.
size_t load_db();
void update_slic3r_versions(std::vector<Index> &index_db);
// Create a snapshot directory, copy the vendor config bundles, user print / sla_print / filament / sla_material / printer / physical_printer profiles,
// create an index.
const Snapshot& take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment = "");
const Snapshot& restore_snapshot(const std::string &id, AppConfig &app_config);
void restore_snapshot(const Snapshot &snapshot, AppConfig &app_config);
// Test whether the AppConfig's on_snapshot variable points to an existing snapshot, and the existing snapshot
// matches the current state. If it does not match the current state, the AppConfig's "on_snapshot" ID is reset.
bool is_on_snapshot(AppConfig &app_config) const;
// Finds the newest snapshot, which contains a config bundle for vendor_name with config_version.
const_iterator snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version);
const_iterator begin() const { return m_snapshots.begin(); }
const_iterator end() const { return m_snapshots.end(); }
const_iterator snapshot(const std::string &id) const;
const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
private:
// Create the snapshots directory if it does not exist yet.
static boost::filesystem::path create_db_dir();
// Snapshots are sorted by their date/time, oldest first.
std::vector<Snapshot> m_snapshots;
};
// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report an error and return nullptr.
const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment);
// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report "message", and present a "Continue" or "Abort" buttons to respond.
// Return true on success and on "Continue" to continue with the process (for example installation of presets).
bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message);
} // namespace Config
} // namespace GUI
} // namespace Slic3r
#endif /* slic3r_GUI_Snapshot_ */

View File

@@ -0,0 +1,333 @@
#include "Version.hpp"
#include <cctype>
#include <boost/filesystem/operations.hpp>
#include <boost/nowide/fstream.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Config.hpp"
#include "libslic3r/FileParserError.hpp"
#include "libslic3r/Utils.hpp"
namespace Slic3r {
namespace GUI {
namespace Config {
// Optimized lexicographic compare of two pre-release versions, ignoring the numeric suffix.
static int compare_prerelease(const char *p1, const char *p2)
{
for (;;) {
char c1 = *p1 ++;
char c2 = *p2 ++;
bool a1 = std::isalpha(c1) && c1 != 0;
bool a2 = std::isalpha(c2) && c2 != 0;
if (a1) {
if (a2) {
if (c1 != c2)
return (c1 < c2) ? -1 : 1;
} else
return 1;
} else {
if (a2)
return -1;
else
return 0;
}
}
// This shall never happen.
return 0;
}
bool Version::is_slic3r_supported(const Semver &slic3r_version) const
{
if (! slic3r_version.in_range(min_slic3r_version, max_slic3r_version))
return false;
// Now verify, whether the configuration pre-release status is compatible with the Slic3r's pre-release status.
// Alpha Slic3r will happily load any configuration, while beta Slic3r will ignore alpha configurations etc.
const char *prerelease_slic3r = slic3r_version.prerelease();
const char *prerelease_config = this->config_version.prerelease();
if (prerelease_config == nullptr)
// Released config is always supported.
return true;
else if (prerelease_slic3r == nullptr)
// Released slic3r only supports released configs.
return false;
// Compare the pre-release status of Slic3r against the config.
// If the prerelease status of slic3r is lexicographically lower or equal
// to the prerelease status of the config, accept it.
return compare_prerelease(prerelease_slic3r, prerelease_config) != 1;
}
bool Version::is_current_slic3r_supported() const
{
return this->is_slic3r_supported(Slic3r::SEMVER);
}
bool Version::is_current_slic3r_downgrade() const
{
return Slic3r::SEMVER < min_slic3r_version;
}
#if 0
//TODO: This test should be moved to a unit test, once we have C++ unit tests in place.
static int version_test()
{
Version v;
v.config_version = *Semver::parse("1.1.2");
v.min_slic3r_version = *Semver::parse("1.38.0");
v.max_slic3r_version = Semver::inf();
assert(v.is_slic3r_supported(*Semver::parse("1.38.0")));
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha")));
assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha")));
// Test the prerelease status.
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-alpha");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-alpha1");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-beta");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-rc");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-rc2");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
// Test the upper boundary.
v.config_version = *Semver::parse("1.1.2");
v.max_slic3r_version = *Semver::parse("1.39.3-beta1");
assert(v.is_slic3r_supported(*Semver::parse("1.38.0")));
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha")));
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha")));
return 0;
}
static int version_test_run = version_test();
#endif
inline char* left_trim(char *c)
{
for (; *c == ' ' || *c == '\t'; ++ c);
return c;
}
inline char* right_trim(char *start)
{
char *end = start + strlen(start) - 1;
for (; end >= start && (*end == ' ' || *end == '\t'); -- end);
*(++ end) = 0;
return end;
}
inline std::string unquote_value(char *value, char *end, const std::string &path, int idx_line)
{
std::string svalue;
if (value == end) {
// Empty string is a valid string.
} else if (*value == '"') {
if (++ value > -- end || *end != '"')
throw file_parser_error("String not enquoted correctly", path, idx_line);
*end = 0;
if (! unescape_string_cstyle(value, svalue))
throw file_parser_error("Invalid escape sequence inside a quoted string", path, idx_line);
} else
svalue.assign(value, end);
return svalue;
}
inline std::string unquote_version_comment(char *value, char *end, const std::string &path, int idx_line)
{
std::string svalue;
if (value == end) {
// Empty string is a valid string.
} else if (*value == '"') {
if (++ value > -- end || *end != '"')
throw file_parser_error("Version comment not enquoted correctly", path, idx_line);
*end = 0;
if (! unescape_string_cstyle(value, svalue))
throw file_parser_error("Invalid escape sequence inside a quoted version comment", path, idx_line);
} else
svalue.assign(value, end);
return svalue;
}
size_t Index::load(const boost::filesystem::path &path)
{
m_configs.clear();
m_vendor = path.stem().string();
m_path = path;
boost::nowide::ifstream ifs(path.string());
std::string line;
size_t idx_line = 0;
Version ver;
while (std::getline(ifs, line)) {
#ifndef _MSVCVER
// On a Unix system, getline does not remove the trailing carriage returns, if the index is shared over a Windows filesystem. Remove them manually.
while (! line.empty() && line.back() == '\r')
line.pop_back();
#endif
++ idx_line;
// Skip the initial white spaces.
char *key = left_trim(line.data());
if (*key == '#')
// Skip a comment line.
continue;
// Right trim the line.
char *end = right_trim(key);
if (key == end)
// Skip an empty line.
continue;
// Keyword may only contain alphanumeric characters. Semantic version may in addition contain "+.-".
char *key_end = key;
bool maybe_semver = true;
for (; *key_end != 0; ++ key_end) {
if (std::isalnum(*key_end) || strchr("+.-", *key_end) != nullptr) {
// It may be a semver.
} else if (*key_end == '_') {
// Cannot be a semver, but it may be a key.
maybe_semver = false;
} else
// End of semver or keyword.
break;
}
if (*key_end != 0 && *key_end != ' ' && *key_end != '\t' && *key_end != '=')
throw file_parser_error("Invalid keyword or semantic version", path, idx_line);
char *value = left_trim(key_end);
bool key_value_pair = *value == '=';
if (key_value_pair)
value = left_trim(value + 1);
*key_end = 0;
boost::optional<Semver> semver;
if (maybe_semver)
semver = Semver::parse(key);
if (key_value_pair) {
if (semver)
throw file_parser_error("Key cannot be a semantic version", path, idx_line);\
// Verify validity of the key / value pair.
std::string svalue = unquote_value(value, end, path.string(), idx_line);
if (strcmp(key, "min_slic3r_version") == 0 || strcmp(key, "max_slic3r_version") == 0) {
if (! svalue.empty())
semver = Semver::parse(svalue);
if (! semver)
throw file_parser_error(std::string(key) + " must referece a valid semantic version", path, idx_line);
if (strcmp(key, "min_slic3r_version") == 0)
ver.min_slic3r_version = *semver;
else
ver.max_slic3r_version = *semver;
} else {
// Ignore unknown keys, as there may come new keys in the future.
}
continue;
}
if (! semver)
throw file_parser_error("Invalid semantic version", path, idx_line);
ver.config_version = *semver;
ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path.string(), idx_line);
m_configs.emplace_back(ver);
}
// Sort the configs by their version.
std::sort(m_configs.begin(), m_configs.end(), [](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; });
return m_configs.size();
}
Semver Index::version() const
{
Semver ver = Semver::zero();
for (const Version &cv : m_configs)
if (cv.config_version >= ver)
ver = cv.config_version;
return ver;
}
Index::const_iterator Index::find(const Semver &ver) const
{
Version key;
key.config_version = ver;
auto it = std::lower_bound(m_configs.begin(), m_configs.end(), key,
[](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; });
return (it == m_configs.end() || it->config_version == ver) ? it : m_configs.end();
}
Index::const_iterator Index::recommended(const Semver &slic3r_version) const
{
const_iterator highest = this->end();
for (const_iterator it = this->begin(); it != this->end(); ++ it)
if (it->is_slic3r_supported(slic3r_version) &&
(highest == this->end() || highest->config_version < it->config_version))
highest = it;
return highest;
}
Index::const_iterator Index::recommended() const
{
return this->recommended(Slic3r::SEMVER);
}
std::vector<Index> Index::load_db()
{
boost::filesystem::path cache_dir = boost::filesystem::path(Slic3r::data_dir()) / "ota";
std::vector<Index> index_db;
std::string errors_cummulative;
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_dir))
if (Slic3r::is_idx_file(dir_entry)) {
Index idx;
try {
idx.load(dir_entry.path());
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
errors_cummulative += "\n";
continue;
}
index_db.emplace_back(std::move(idx));
}
if (! errors_cummulative.empty())
throw Slic3r::RuntimeError(errors_cummulative);
return index_db;
}
} // namespace Config
} // namespace GUI
} // namespace Slic3r

View File

@@ -0,0 +1,95 @@
#ifndef slic3r_GUI_ConfigIndex_
#define slic3r_GUI_ConfigIndex_
#include <string>
#include <vector>
#include <boost/filesystem/path.hpp>
#include "libslic3r/FileParserError.hpp"
#include "libslic3r/Semver.hpp"
namespace Slic3r {
namespace GUI {
namespace Config {
// Configuration bundle version.
struct Version
{
// Version of this config.
Semver config_version = Semver::invalid();
// Minimum Slic3r version, for which this config is applicable.
Semver min_slic3r_version = Semver::zero();
// Maximum Slic3r version, for which this config is recommended.
// Slic3r should read older configuration and upgrade to a newer format,
// but likely there has been a better configuration published, using the new features.
Semver max_slic3r_version = Semver::inf();
// Single comment line.
std::string comment;
bool is_slic3r_supported(const Semver &slicer_version) const;
bool is_current_slic3r_supported() const;
bool is_current_slic3r_downgrade() const;
};
// Index of vendor specific config bundle versions and Slic3r compatibilities.
// The index is being downloaded from the internet, also an initial version of the index
// is contained in the Slic3r installation.
//
// The index has a simple format:
//
// min_sic3r_version =
// max_slic3r_version =
// config_version "comment"
// config_version "comment"
// ...
// min_slic3r_version =
// max_slic3r_version =
// config_version comment
// config_version comment
// ...
//
// The min_slic3r_version, max_slic3r_version keys are applied to the config versions below,
// empty slic3r version means an open interval.
class Index
{
public:
typedef std::vector<Version>::const_iterator const_iterator;
// Read a config index file in the simple format described in the Index class comment.
// Throws Slic3r::file_parser_error and the standard std file access exceptions.
size_t load(const boost::filesystem::path &path);
const std::string& vendor() const { return m_vendor; }
// Returns version of the index as the highest version of all the configs.
// If there is no config, Semver::zero() is returned.
Semver version() const;
const_iterator begin() const { return m_configs.begin(); }
const_iterator end() const { return m_configs.end(); }
const_iterator find(const Semver &ver) const;
const std::vector<Version>& configs() const { return m_configs; }
// Finds a recommended config to be installed for the current Slic3r version.
// Returns configs().end() if such version does not exist in the index. This shall never happen
// if the index is valid.
const_iterator recommended() const;
// Recommended config for a provided slic3r version. Used when checking for slic3r update (slic3r_version is the old one read out from PrusaSlicer.ini)
const_iterator recommended(const Semver &slic3r_version) const;
// Returns the filesystem path from which this index has originally been loaded
const boost::filesystem::path& path() const { return m_path; }
// Load all vendor specific indices.
// Throws Slic3r::file_parser_error and the standard std file access exceptions.
static std::vector<Index> load_db();
private:
std::string m_vendor;
std::vector<Version> m_configs;
boost::filesystem::path m_path;
};
} // namespace Config
} // namespace GUI
} // namespace Slic3r
#endif /* slic3r_GUI_ConfigIndex_ */

175
src/slic3r/GUI/2DBed.cpp Normal file
View File

@@ -0,0 +1,175 @@
#include "2DBed.hpp"
#include "GUI_App.hpp"
#include <wx/dcbuffer.h>
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/ClipperUtils.hpp"
namespace Slic3r {
namespace GUI {
Bed_2D::Bed_2D(wxWindow* parent) :
wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(25 * wxGetApp().em_unit(), -1), wxTAB_TRAVERSAL)
{
#ifdef __APPLE__
m_user_drawn_background = false;
#else
SetBackgroundStyle(wxBG_STYLE_PAINT); // to avoid assert message after wxAutoBufferedPaintDC
#endif /*__APPLE__*/
}
void Bed_2D::repaint(const std::vector<Vec2d>& shape)
{
wxAutoBufferedPaintDC dc(this);
auto cw = GetSize().GetWidth();
auto ch = GetSize().GetHeight();
// when canvas is not rendered yet, size is 0, 0
if (cw == 0) return ;
if (m_user_drawn_background) {
// On all systems the AutoBufferedPaintDC() achieves double buffering.
// On MacOS the background is erased, on Windows the background is not erased
// and on Linux / GTK the background is erased to gray color.
// Fill DC with the background on Windows & Linux / GTK.
wxColour color;
if (wxGetApp().dark_mode()) {// SetBackgroundColour
color = wxColour(45, 45, 49);
} else {
color = *wxWHITE;
}
dc.SetPen(*new wxPen(color, 1, wxPENSTYLE_SOLID));
dc.SetBrush(*new wxBrush(color, wxBRUSHSTYLE_SOLID));
auto rect = GetUpdateRegion().GetBox();
dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight());
}
if (shape.empty())
return;
// reduce size to have some space around the drawn shape
cw -= (2 * Border);
ch -= (2 * Border);
auto cbb = BoundingBoxf(Vec2d(0, 0),Vec2d(cw, ch));
auto ccenter = cbb.center();
// get bounding box of bed shape in G - code coordinates
auto bed_polygon = Polygon::new_scale(shape);
auto bb = BoundingBoxf(shape);
bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area
auto bw = bb.size()(0);
auto bh = bb.size()(1);
auto bcenter = bb.center();
// calculate the scaling factor for fitting bed shape in canvas area
auto sfactor = std::min(cw/bw, ch/bh);
auto shift = Vec2d(
ccenter(0) - bcenter(0) * sfactor,
ccenter(1) - bcenter(1) * sfactor
);
m_scale_factor = sfactor;
m_shift = Vec2d(shift(0) + cbb.min(0), shift(1) - (cbb.max(1) - ch));
// draw bed fill
dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID));
wxPointList pt_list;
for (auto pt : shape)
{
Point pt_pix = to_pixels(pt, ch);
pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1)));
}
dc.DrawPolygon(&pt_list, 0, 0);
// draw grid
auto step = 10; // 1cm grid
Polylines polylines;
for (auto x = bb.min(0) - fmod(bb.min(0), step) + step; x < bb.max(0); x += step) {
polylines.push_back(Polyline::new_scale({ Vec2d(x, bb.min(1)), Vec2d(x, bb.max(1)) }));
}
for (auto y = bb.min(1) - fmod(bb.min(1), step) + step; y < bb.max(1); y += step) {
polylines.push_back(Polyline::new_scale({ Vec2d(bb.min(0), y), Vec2d(bb.max(0), y) }));
}
polylines = intersection_pl(polylines, bed_polygon);
dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxPENSTYLE_SOLID));
for (auto pl : polylines)
{
for (size_t i = 0; i < pl.points.size()-1; i++) {
Point pt1 = to_pixels(unscale(pl.points[i]), ch);
Point pt2 = to_pixels(unscale(pl.points[i + 1]), ch);
dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1));
}
}
// draw bed contour
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxPENSTYLE_SOLID));
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT));
dc.DrawPolygon(&pt_list, 0, 0);
auto origin_px = to_pixels(Vec2d(0, 0), ch);
// draw axes
auto axes_len = 50;
auto arrow_len = 6;
auto arrow_angle = Geometry::deg2rad(45.0);
dc.SetPen(wxPen(wxColour(255, 0, 0), 2, wxPENSTYLE_SOLID)); // red
auto x_end = Vec2d(origin_px(0) + axes_len, origin_px(1));
dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(x_end(0), x_end(1)));
for (auto angle : { -arrow_angle, arrow_angle }) {
auto end = Eigen::Translation2d(x_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- x_end) * Eigen::Vector2d(x_end(0) - arrow_len, x_end(1));
dc.DrawLine(wxPoint(x_end(0), x_end(1)), wxPoint(end(0), end(1)));
}
dc.SetPen(wxPen(wxColour(0, 255, 0), 2, wxPENSTYLE_SOLID)); // green
auto y_end = Vec2d(origin_px(0), origin_px(1) - axes_len);
dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(y_end(0), y_end(1)));
for (auto angle : { -arrow_angle, arrow_angle }) {
auto end = Eigen::Translation2d(y_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- y_end) * Eigen::Vector2d(y_end(0), y_end(1) + arrow_len);
dc.DrawLine(wxPoint(y_end(0), y_end(1)), wxPoint(end(0), end(1)));
}
// draw origin
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxPENSTYLE_SOLID));
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_SOLID));
dc.DrawCircle(origin_px(0), origin_px(1), 3);
static const auto origin_label = wxString("(0,0)");
dc.SetTextForeground(wxColour(0, 0, 0));
dc.SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
auto extent = dc.GetTextExtent(origin_label);
const auto origin_label_x = origin_px(0) <= cw / 2 ? origin_px(0) + 1 : origin_px(0) - 1 - extent.GetWidth();
const auto origin_label_y = origin_px(1) <= ch / 2 ? origin_px(1) + 1 : origin_px(1) - 1 - extent.GetHeight();
dc.DrawText(origin_label, origin_label_x, origin_label_y);
// draw current position
if (m_pos!= Vec2d(0, 0)) {
auto pos_px = to_pixels(m_pos, ch);
dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxPENSTYLE_SOLID));
dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxBRUSHSTYLE_TRANSPARENT));
dc.DrawCircle(pos_px(0), pos_px(1), 5);
dc.DrawLine(pos_px(0) - 15, pos_px(1), pos_px(0) + 15, pos_px(1));
dc.DrawLine(pos_px(0), pos_px(1) - 15, pos_px(0), pos_px(1) + 15);
}
}
// convert G - code coordinates into pixels
Point Bed_2D::to_pixels(const Vec2d& point, int height)
{
auto p = point * m_scale_factor + m_shift;
return Point(p(0) + Border, height - p(1) + Border);
}
void Bed_2D::set_pos(const Vec2d& pos)
{
m_pos = pos;
Refresh();
}
} // GUI
} // Slic3r

33
src/slic3r/GUI/2DBed.hpp Normal file
View File

@@ -0,0 +1,33 @@
#ifndef slic3r_2DBed_hpp_
#define slic3r_2DBed_hpp_
#include <wx/wx.h>
#include "libslic3r/Config.hpp"
namespace Slic3r {
namespace GUI {
class Bed_2D : public wxPanel
{
static const int Border = 10;
bool m_user_drawn_background = true;
double m_scale_factor;
Vec2d m_shift = Vec2d::Zero();
Vec2d m_pos = Vec2d::Zero();
Point to_pixels(const Vec2d& point, int height);
void set_pos(const Vec2d& pos);
public:
explicit Bed_2D(wxWindow* parent);
void repaint(const std::vector<Vec2d>& shape);
};
} // GUI
} // Slic3r
#endif /* slic3r_2DBed_hpp_ */

751
src/slic3r/GUI/3DBed.cpp Normal file
View File

@@ -0,0 +1,751 @@
#include "libslic3r/libslic3r.h"
#include "3DBed.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Geometry/Circle.hpp"
#include "libslic3r/Tesselate.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "GUI_App.hpp"
#include "GUI_Colors.hpp"
#include "GLCanvas3D.hpp"
#include <GL/glew.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
//B
// #include <boost/timer.hpp>
static const float GROUND_Z = -0.04f;
static const std::array<float, 4> DEFAULT_MODEL_COLOR = { 0.3255f, 0.337f, 0.337f, 1.0f };
static const std::array<float, 4> DEFAULT_MODEL_COLOR_DARK = { 0.255f, 0.255f, 0.283f, 1.0f };
static const std::array<float, 4> PICKING_MODEL_COLOR = { 0.0f, 0.0f, 0.0f, 1.0f };
namespace Slic3r {
namespace GUI {
bool GeometryBuffer::set_from_triangles(const std::vector<Vec2f> &triangles, float z)
{
if (triangles.empty()) {
m_vertices.clear();
return false;
}
m_vertices.clear();
assert(triangles.size() % 3 == 0);
m_vertices = std::vector<Vertex>(triangles.size(), Vertex());
Vec2f min = triangles.front();
Vec2f max = min;
for (size_t v_count = 0; v_count < triangles.size(); ++ v_count) {
const Vec2f &p = triangles[v_count];
Vertex &v = m_vertices[v_count];
v.position = Vec3f(p.x(), p.y(), z);
v.tex_coords = p;
min = min.cwiseMin(p).eval();
max = max.cwiseMax(p).eval();
}
Vec2f size = max - min;
if (size.x() != 0.f && size.y() != 0.f) {
Vec2f inv_size = size.cwiseInverse();
inv_size.y() *= -1;
for (Vertex& v : m_vertices) {
v.tex_coords -= min;
v.tex_coords.x() *= inv_size.x();
v.tex_coords.y() *= inv_size.y();
}
}
return true;
}
bool GeometryBuffer::set_from_lines(const Lines& lines, float z)
{
m_vertices.clear();
unsigned int v_size = 2 * (unsigned int)lines.size();
if (v_size == 0)
return false;
m_vertices = std::vector<Vertex>(v_size, Vertex());
unsigned int v_count = 0;
for (const Line& l : lines) {
Vertex& v1 = m_vertices[v_count];
v1.position[0] = unscale<float>(l.a(0));
v1.position[1] = unscale<float>(l.a(1));
v1.position[2] = z;
++v_count;
Vertex& v2 = m_vertices[v_count];
v2.position[0] = unscale<float>(l.b(0));
v2.position[1] = unscale<float>(l.b(1));
v2.position[2] = z;
++v_count;
}
return true;
}
//QDS: set from 3d lines
bool GeometryBuffer::set_from_3d_Lines(const Lines3& lines)
{
m_vertices.clear();
unsigned int v_size = 2 * (unsigned int)lines.size();
if (v_size == 0)
return false;
m_vertices = std::vector<Vertex>(v_size, Vertex());
unsigned int v_count = 0;
for (const Line3& l : lines) {
Vertex& v1 = m_vertices[v_count];
v1.position[0] = unscale<float>(l.a(0));
v1.position[1] = unscale<float>(l.a(1));
v1.position[2] = unscale<float>(l.a(2));
++v_count;
Vertex& v2 = m_vertices[v_count];
v2.position[0] = unscale<float>(l.b(0));
v2.position[1] = unscale<float>(l.b(1));
v2.position[2] = unscale<float>(l.b(2));
++v_count;
}
return true;
}
const float* GeometryBuffer::get_vertices_data() const
{
return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr;
}
const float Bed3D::Axes::DefaultStemRadius = 0.5f;
const float Bed3D::Axes::DefaultStemLength = 25.0f;
const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius;
const float Bed3D::Axes::DefaultTipLength = 5.0f;
std::array<float, 4> Bed3D::AXIS_X_COLOR = decode_color_to_float_array("#FF0000");
std::array<float, 4> Bed3D::AXIS_Y_COLOR = decode_color_to_float_array("#00FF00");
std::array<float, 4> Bed3D::AXIS_Z_COLOR = decode_color_to_float_array("#0000FF");
void Bed3D::update_render_colors()
{
Bed3D::AXIS_X_COLOR = GLColor(RenderColor::colors[RenderCol_Axis_X]);
Bed3D::AXIS_Y_COLOR = GLColor(RenderColor::colors[RenderCol_Axis_Y]);
Bed3D::AXIS_Z_COLOR = GLColor(RenderColor::colors[RenderCol_Axis_Z]);
}
void Bed3D::load_render_colors()
{
RenderColor::colors[RenderCol_Axis_X] = IMColor(Bed3D::AXIS_X_COLOR);
RenderColor::colors[RenderCol_Axis_Y] = IMColor(Bed3D::AXIS_Y_COLOR);
RenderColor::colors[RenderCol_Axis_Z] = IMColor(Bed3D::AXIS_Z_COLOR);
}
void Bed3D::Axes::render() const
{
auto render_axis = [this](const Transform3f& transform) {
glsafe(::glPushMatrix());
glsafe(::glMultMatrixf(transform.data()));
m_arrow.render();
glsafe(::glPopMatrix());
};
if (!m_arrow.is_initialized())
const_cast<GLModel*>(&m_arrow)->init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length));
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
glsafe(::glEnable(GL_DEPTH_TEST));
shader->start_using();
shader->set_uniform("emission_factor", 0.0f);
// x axis
const_cast<GLModel*>(&m_arrow)->set_color(-1, AXIS_X_COLOR);
render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast<float>());
// y axis
const_cast<GLModel*>(&m_arrow)->set_color(-1, AXIS_Y_COLOR);
render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast<float>());
// z axis
const_cast<GLModel*>(&m_arrow)->set_color(-1, AXIS_Z_COLOR);
render_axis(Geometry::assemble_transform(m_origin).cast<float>());
shader->stop_using();
glsafe(::glDisable(GL_DEPTH_TEST));
}
//QDS: add part plate logic
bool Bed3D::set_shape(const Pointfs& printable_area, const double printable_height, const std::string& custom_model, bool force_as_custom,
const Vec2d position, bool with_reset)
{
/*auto check_texture = [](const std::string& texture) {
boost::system::error_code ec; // so the exists call does not throw (e.g. after a permission problem)
return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(texture, ec);
};*/
auto check_model = [](const std::string& model) {
boost::system::error_code ec;
return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model, ec);
};
Type type;
std::string model;
std::string texture;
if (force_as_custom)
type = Type::Custom;
else {
auto [new_type, system_model, system_texture] = detect_type(printable_area);
type = new_type;
model = system_model;
texture = system_texture;
}
/*std::string texture_filename = custom_texture.empty() ? texture : custom_texture;
if (! texture_filename.empty() && ! check_texture(texture_filename)) {
BOOST_LOG_TRIVIAL(error) << "Unable to load bed texture: " << texture_filename;
texture_filename.clear();
}*/
std::string model_filename = custom_model.empty() ? model : custom_model;
if (! model_filename.empty() && ! check_model(model_filename)) {
BOOST_LOG_TRIVIAL(error) << "Unable to load bed model: " << model_filename;
model_filename.clear();
}
//QDS: add position related logic
if (m_bed_shape == printable_area && m_build_volume.printable_height() == printable_height && m_type == type && m_model_filename == model_filename && position == m_position)
// No change, no need to update the UI.
return false;
//QDS: add part plate logic, apply position to bed shape
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":current position {%1%,%2%}, new position {%3%, %4%}") % m_position.x() % m_position.y() % position.x() % position.y();
m_position = position;
m_bed_shape = printable_area;
if ((position(0) != 0) || (position(1) != 0)) {
Pointfs new_bed_shape;
for (const Vec2d& p : m_bed_shape) {
Vec2d point(p(0) + m_position.x(), p(1) + m_position.y());
new_bed_shape.push_back(point);
}
m_build_volume = BuildVolume { new_bed_shape, printable_height };
}
else
m_build_volume = BuildVolume { printable_area, printable_height };
m_type = type;
//m_texture_filename = texture_filename;
m_model_filename = model_filename;
//QDS: add part plate logic
m_extended_bounding_box = this->calc_extended_bounding_box(false);
//QDS: add part plate logic
//QDS add default bed
#if 1
ExPolygon poly{ Polygon::new_scale(printable_area) };
#else
ExPolygon poly;
for (const Vec2d& p : printable_area) {
poly.contour.append(Point(scale_(p(0) + m_position.x()), scale_(p(1) + m_position.y())));
}
#endif
calc_triangles(poly);
//no need gridline for 3dbed
//const BoundingBox& bed_bbox = poly.contour.bounding_box();
//calc_gridlines(poly, bed_bbox);
//m_polygon = offset(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0];
if (with_reset) {
this->release_VBOs();
//m_texture.reset();
m_model.reset();
}
//QDS: add part plate logic, always update model offset
//else {
update_model_offset();
//}
// Set the origin and size for rendering the coordinate system axes.
m_axes.set_origin({ 0.0, 0.0, static_cast<double>(GROUND_Z) });
m_axes.set_stem_length(0.1f * static_cast<float>(m_build_volume.bounding_volume().max_size()));
// Let the calee to update the UI.
return true;
}
//QDS: add api to set position for partplate related bed
void Bed3D::set_position(Vec2d& position)
{
set_shape(m_bed_shape, m_build_volume.printable_height(), m_model_filename, false, position, false);
}
void Bed3D::set_axes_mode(bool origin)
{
if (origin) {
m_axes.set_origin({ 0.0, 0.0, static_cast<double>(GROUND_Z) });
}
else {
m_axes.set_origin({ m_position.x(), m_position.y(), static_cast<double>(GROUND_Z) });
}
}
/*bool Bed3D::contains(const Point& point) const
{
return m_polygon.contains(point);
}
Point Bed3D::point_projection(const Point& point) const
{
return m_polygon.point_projection(point);
}*/
void Bed3D::on_change_color_mode(bool is_dark)
{
m_is_dark = is_dark;
}
void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes)
{
render_internal(canvas, bottom, scale_factor, show_axes);
}
/*void Bed3D::render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_factor)
{
render_internal(canvas, bottom, scale_factor, false, false, true);
}*/
void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor,
bool show_axes)
{
float* factor = const_cast<float*>(&m_scale_factor);
*factor = scale_factor;
if (show_axes)
render_axes();
glsafe(::glEnable(GL_DEPTH_TEST));
m_model.set_color(-1, m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR);
switch (m_type)
{
case Type::System: { render_system(canvas, bottom); break; }
default:
case Type::Custom: { render_custom(canvas, bottom); break; }
}
glsafe(::glDisable(GL_DEPTH_TEST));
}
//QDS: add partplate related logic
// Calculate an extended bounding box from axes and current model for visualization purposes.
BoundingBoxf3 Bed3D::calc_extended_bounding_box(bool consider_model_offset) const
{
BoundingBoxf3 out { m_build_volume.bounding_volume() };
const Vec3d size = out.size();
// ensures that the bounding box is set as defined or the following calls to merge() will not work as intented
if (size.x() > 0.0 && size.y() > 0.0 && !out.defined)
out.defined = true;
// Reset the build volume Z, we don't want to zoom to the top of the build volume if it is empty.
out.min.z() = 0.0;
out.max.z() = 0.0;
// extend to contain axes
//QDS: add part plate related logic.
Vec3d offset{ m_position.x(), m_position.y(), 0.f };
//out.merge(m_axes.get_origin() + offset + m_axes.get_total_length() * Vec3d::Ones());
out.merge(Vec3d(0.f, 0.f, GROUND_Z) + offset + m_axes.get_total_length() * Vec3d::Ones());
out.merge(out.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, out.max.z()));
//QDS: add part plate related logic.
if (consider_model_offset) {
// extend to contain model, if any
BoundingBoxf3 model_bb = m_model.get_bounding_box();
if (model_bb.defined) {
model_bb.translate(m_model_offset);
out.merge(model_bb);
}
}
return out;
}
void Bed3D::calc_triangles(const ExPolygon& poly)
{
if (! m_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z))
BOOST_LOG_TRIVIAL(error) << "Unable to create bed triangles";
}
void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox)
{
/*Polylines axes_lines;
for (coord_t x = bed_bbox.min.x(); x <= bed_bbox.max.x(); x += scale_(10.0)) {
Polyline line;
line.append(Point(x, bed_bbox.min.y()));
line.append(Point(x, bed_bbox.max.y()));
axes_lines.push_back(line);
}
for (coord_t y = bed_bbox.min.y(); y <= bed_bbox.max.y(); y += scale_(10.0)) {
Polyline line;
line.append(Point(bed_bbox.min.x(), y));
line.append(Point(bed_bbox.max.x(), y));
axes_lines.push_back(line);
}
// clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped
Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, (float)SCALED_EPSILON)));
// append bed contours
Lines contour_lines = to_lines(poly);
std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines));
if (!m_gridlines.set_from_lines(gridlines, GROUND_Z))
BOOST_LOG_TRIVIAL(error) << "Unable to create bed grid lines\n";*/
}
// Try to match the print bed shape with the shape of an active profile. If such a match exists,
// return the print bed model.
std::tuple<Bed3D::Type, std::string, std::string> Bed3D::detect_type(const Pointfs& shape)
{
auto bundle = wxGetApp().preset_bundle;
if (bundle != nullptr) {
const Preset* curr = &bundle->printers.get_selected_preset();
while (curr != nullptr) {
if (curr->config.has("printable_area")) {
std::string texture_filename, model_filename;
if (shape == dynamic_cast<const ConfigOptionPoints*>(curr->config.option("printable_area"))->values) {
if (curr->is_system)
model_filename = PresetUtils::system_printer_bed_model(*curr);
else {
auto *printer_model = curr->config.opt<ConfigOptionString>("printer_model");
if (printer_model != nullptr && ! printer_model->value.empty()) {
model_filename = bundle->get_stl_model_for_printer_model(printer_model->value);
}
}
//std::string model_filename = PresetUtils::system_printer_bed_model(*curr);
//std::string texture_filename = PresetUtils::system_printer_bed_texture(*curr);
if (!model_filename.empty())
return { Type::System, model_filename, texture_filename };
}
}
curr = bundle->printers.get_preset_parent(*curr);
}
}
return { Type::Custom, {}, {} };
}
void Bed3D::render_axes() const
{
if (m_build_volume.valid())
m_axes.render();
}
void Bed3D::render_system(GLCanvas3D& canvas, bool bottom) const
{
if (!bottom)
render_model();
/*if (show_texture)
render_texture(bottom, canvas);*/
}
/*void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
{
GLTexture* texture = const_cast<GLTexture*>(&m_texture);
GLTexture* temp_texture = const_cast<GLTexture*>(&m_temp_texture);
if (m_texture_filename.empty()) {
texture->reset();
render_default(bottom, false);
return;
}
if (texture->get_id() == 0 || texture->get_source() != m_texture_filename) {
texture->reset();
if (boost::algorithm::iends_with(m_texture_filename, ".svg")) {
// use higher resolution images if graphic card and opengl version allow
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) {
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
if (!temp_texture->load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) {
render_default(bottom, false);
return;
}
canvas.request_extra_frame();
}
// starts generating the main texture, compression will run asynchronously
if (!texture->load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) {
render_default(bottom, false);
return;
}
}
else if (boost::algorithm::iends_with(m_texture_filename, ".png")) {
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) {
if (!temp_texture->load_from_file(m_texture_filename, false, GLTexture::None, false)) {
render_default(bottom, false);
return;
}
canvas.request_extra_frame();
}
// starts generating the main texture, compression will run asynchronously
if (!texture->load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) {
render_default(bottom, false);
return;
}
}
else {
render_default(bottom, false);
return;
}
}
else if (texture->unsent_compressed_data_available()) {
// sends to gpu the already available compressed levels of the main texture
texture->send_compressed_data_to_gpu();
// the temporary texture is not needed anymore, reset it
if (temp_texture->get_id() != 0)
temp_texture->reset();
canvas.request_extra_frame();
}
if (m_triangles.get_vertices_count() > 0) {
GLShaderProgram* shader = wxGetApp().get_shader("printbed");
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("transparent_background", bottom);
shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg"));
unsigned int* vbo_id = const_cast<unsigned int*>(&m_vbo_id);
if (*vbo_id == 0) {
glsafe(::glGenBuffers(1, vbo_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
glsafe(::glEnable(GL_DEPTH_TEST));
if (bottom)
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
if (bottom)
glsafe(::glFrontFace(GL_CW));
unsigned int stride = m_triangles.get_vertex_data_size();
GLint position_id = shader->get_attrib_location("v_position");
GLint tex_coords_id = shader->get_attrib_location("v_tex_coords");
// show the temporary texture while no compressed data is available
GLuint tex_id = (GLuint)temp_texture->get_id();
if (tex_id == 0)
tex_id = (GLuint)texture->get_id();
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id));
if (position_id != -1) {
glsafe(::glEnableVertexAttribArray(position_id));
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset()));
}
if (tex_coords_id != -1) {
glsafe(::glEnableVertexAttribArray(tex_coords_id));
glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset()));
}
glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count()));
if (tex_coords_id != -1)
glsafe(::glDisableVertexAttribArray(tex_coords_id));
if (position_id != -1)
glsafe(::glDisableVertexAttribArray(position_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
if (bottom)
glsafe(::glFrontFace(GL_CCW));
glsafe(::glDisable(GL_BLEND));
if (bottom)
glsafe(::glDepthMask(GL_TRUE));
shader->stop_using();
}
}
}*/
//QDS: add part plate related logic
void Bed3D::update_model_offset() const
{
// move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad
Vec3d shift = m_build_volume.bounding_volume().center();
shift(2) = -0.03;
Vec3d* model_offset_ptr = const_cast<Vec3d*>(&m_model_offset);
*model_offset_ptr = shift;
//QDS: TODO: hack for current stl for QDT printer
if (std::string::npos != m_model_filename.find("qdt-3dp-"))
{
(*model_offset_ptr)(0) -= m_bed_shape[2].x() / 2.0f;
(*model_offset_ptr)(1) -= m_bed_shape[2].y() / 2.0f;
(*model_offset_ptr)(2) = -0.41 + GROUND_Z;
}
// update extended bounding box
const_cast<BoundingBoxf3&>(m_extended_bounding_box) = calc_extended_bounding_box();
}
GeometryBuffer Bed3D::update_bed_triangles() const
{
GeometryBuffer new_triangles;
Vec3d shift = m_extended_bounding_box.center();
shift(2) = -0.03;
Vec3d* model_offset_ptr = const_cast<Vec3d*>(&m_model_offset);
*model_offset_ptr = shift;
//QDS: TODO: hack for default bed
BoundingBoxf3 build_volume;
if (!m_build_volume.valid()) return new_triangles;
(*model_offset_ptr)(0) = m_build_volume.bounding_volume2d().min.x();
(*model_offset_ptr)(1) = m_build_volume.bounding_volume2d().min.y();
(*model_offset_ptr)(2) = -0.41 + GROUND_Z;
std::vector<Vec2d> origin_bed_shape;
for (size_t i = 0; i < m_bed_shape.size(); i++) {
origin_bed_shape.push_back(m_bed_shape[i] - m_bed_shape[0]);
}
std::vector<Vec2d> new_bed_shape; // offset to correct origin
for (auto point : origin_bed_shape) {
Vec2d new_point(point.x() + model_offset_ptr->x(), point.y() + model_offset_ptr->y());
new_bed_shape.push_back(new_point);
}
ExPolygon poly{ Polygon::new_scale(new_bed_shape) };
if (!new_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z)) {
;
}
// update extended bounding box
const_cast<BoundingBoxf3&>(m_extended_bounding_box) = calc_extended_bounding_box();
return new_triangles;
}
void Bed3D::render_model() const
{
if (m_model_filename.empty())
return;
GLModel* model = const_cast<GLModel*>(&m_model);
if (model->get_filename() != m_model_filename && model->init_from_file(m_model_filename)) {
model->set_color(-1, m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR);
update_model_offset();
}
if (!model->get_filename().empty()) {
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("emission_factor", 0.0f);
glsafe(::glPushMatrix());
glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z()));
model->render();
glsafe(::glPopMatrix());
shader->stop_using();
}
}
}
void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const
{
if (m_model_filename.empty()) {
render_default(bottom);
return;
}
if (!bottom)
render_model();
/*if (show_texture)
render_texture(bottom, canvas);*/
}
void Bed3D::render_default(bool bottom) const
{
bool picking = false;
const_cast<GLTexture*>(&m_texture)->reset();
unsigned int triangles_vcount = m_triangles.get_vertices_count();
GeometryBuffer default_triangles = update_bed_triangles();
if (triangles_vcount > 0) {
bool has_model = !m_model.get_filename().empty();
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
if (!has_model && !bottom) {
// draw background
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glColor4fv(picking ? PICKING_MODEL_COLOR.data() : (m_is_dark ? DEFAULT_MODEL_COLOR_DARK.data() : DEFAULT_MODEL_COLOR.data())));
glsafe(::glNormal3d(0.0f, 0.0f, 1.0f));
glsafe(::glVertexPointer(3, GL_FLOAT, default_triangles.get_vertex_data_size(), (GLvoid*)default_triangles.get_vertices_data()));
glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount));
glsafe(::glDepthMask(GL_TRUE));
}
/*if (!picking) {
// draw grid
glsafe(::glLineWidth(1.5f * m_scale_factor));
if (has_model && !bottom)
glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 1.0f));
else
glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.6f));
glsafe(::glVertexPointer(3, GL_FLOAT, default_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data()));
glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count()));
}*/
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glDisable(GL_BLEND));
}
}
void Bed3D::release_VBOs()
{
if (m_vbo_id > 0) {
glsafe(::glDeleteBuffers(1, &m_vbo_id));
m_vbo_id = 0;
}
}
} // GUI
} // Slic3r

171
src/slic3r/GUI/3DBed.hpp Normal file
View File

@@ -0,0 +1,171 @@
#ifndef slic3r_3DBed_hpp_
#define slic3r_3DBed_hpp_
#include "GLTexture.hpp"
#include "3DScene.hpp"
#include "GLModel.hpp"
#include <libslic3r/BuildVolume.hpp>
#include <tuple>
#include <array>
namespace Slic3r {
namespace GUI {
class GLCanvas3D;
class GeometryBuffer
{
struct Vertex
{
Vec3f position{ Vec3f::Zero() };
Vec2f tex_coords{ Vec2f::Zero() };
};
std::vector<Vertex> m_vertices;
public:
bool set_from_triangles(const std::vector<Vec2f> &triangles, float z);
bool set_from_lines(const Lines& lines, float z);
//QDS: add APi to set from 3d lines
bool set_from_3d_Lines(const Lines3& lines);
const float* get_vertices_data() const;
unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); }
unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); }
size_t get_position_offset() const { return 0; }
size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); }
unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); }
};
class Bed3D
{
public:
static std::array<float, 4> AXIS_X_COLOR;
static std::array<float, 4> AXIS_Y_COLOR;
static std::array<float, 4> AXIS_Z_COLOR;
static void update_render_colors();
static void load_render_colors();
class Axes
{
public:
static const float DefaultStemRadius;
static const float DefaultStemLength;
static const float DefaultTipRadius;
static const float DefaultTipLength;
private:
Vec3d m_origin{ Vec3d::Zero() };
float m_stem_length{ DefaultStemLength };
GLModel m_arrow;
public:
const Vec3d& get_origin() const { return m_origin; }
void set_origin(const Vec3d& origin) { m_origin = origin; }
void set_stem_length(float length) {
m_stem_length = length;
m_arrow.reset();
}
float get_total_length() const { return m_stem_length + DefaultTipLength; }
void render() const;
};
public:
enum class Type : unsigned char
{
// The print bed model and texture are available from some printer preset.
System,
// The print bed model is unknown, thus it is rendered procedurally.
Custom
};
private:
BuildVolume m_build_volume;
Type m_type{ Type::System };
//std::string m_texture_filename;
std::string m_model_filename;
// Print volume bounding box exteded with axes and model.
BoundingBoxf3 m_extended_bounding_box;
// Slightly expanded print bed polygon, for collision detection.
//Polygon m_polygon;
GeometryBuffer m_triangles;
//GeometryBuffer m_gridlines;
GLTexture m_texture;
// temporary texture shown until the main texture has still no levels compressed
//GLTexture m_temp_texture;
GLModel m_model;
Vec3d m_model_offset{ Vec3d::Zero() };
unsigned int m_vbo_id{ 0 };
Axes m_axes;
float m_scale_factor{ 1.0f };
//QDS: add part plate related logic
Vec2d m_position{ Vec2d::Zero() };
std::vector<Vec2d> m_bed_shape;
bool m_is_dark = false;
public:
Bed3D() = default;
~Bed3D() { release_VBOs(); }
// Update print bed model from configuration.
// Return true if the bed shape changed, so the calee will update the UI.
//FIXME if the build volume max print height is updated, this function still returns zero
// as this class does not use it, thus there is no need to update the UI.
// QDS
bool set_shape(const Pointfs& printable_area, const double printable_height, const std::string& custom_model, bool force_as_custom = false,
const Vec2d position = Vec2d::Zero(), bool with_reset = true);
void set_position(Vec2d& position);
void set_axes_mode(bool origin);
const Vec2d& get_position() const { return m_position; }
// Build volume geometry for various collision detection tasks.
const BuildVolume& build_volume() const { return m_build_volume; }
// Was the model provided, or was it generated procedurally?
Type get_type() const { return m_type; }
// Was the model generated procedurally?
bool is_custom() const { return m_type == Type::Custom; }
// Bounding box around the print bed, axes and model, for rendering.
const BoundingBoxf3& extended_bounding_box() const { return m_extended_bounding_box; }
// Check against an expanded 2d bounding box.
//FIXME shall one check against the real build volume?
bool contains(const Point& point) const;
Point point_projection(const Point& point) const;
void render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes);
//void render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_factor);
void on_change_color_mode(bool is_dark);
private:
//QDS: add partplate related logic
// Calculate an extended bounding box from axes and current model for visualization purposes.
BoundingBoxf3 calc_extended_bounding_box(bool consider_model_offset = true) const;
void calc_triangles(const ExPolygon& poly);
void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox);
void update_model_offset() const;
//QDS: with offset
GeometryBuffer update_bed_triangles() const;
static std::tuple<Type, std::string, std::string> detect_type(const Pointfs& shape);
void render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor,
bool show_axes);
void render_axes() const;
void render_system(GLCanvas3D& canvas, bool bottom) const;
//void render_texture(bool bottom, GLCanvas3D& canvas) const;
void render_model() const;
void render_custom(GLCanvas3D& canvas, bool bottom) const;
void render_default(bool bottom) const;
void release_VBOs();
};
} // GUI
} // Slic3r
#endif // slic3r_3DBed_hpp_

2395
src/slic3r/GUI/3DScene.cpp Normal file

File diff suppressed because it is too large Load Diff

750
src/slic3r/GUI/3DScene.hpp Normal file
View File

@@ -0,0 +1,750 @@
#ifndef slic3r_3DScene_hpp_
#define slic3r_3DScene_hpp_
#include "libslic3r/libslic3r.h"
#include "libslic3r/Point.hpp"
#include "libslic3r/Line.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Geometry.hpp"
// QDS
#include "libslic3r/ObjectID.hpp"
#include "GLModel.hpp"
#include "GLShader.hpp"
#include <functional>
#include <optional>
#ifndef NDEBUG
#define HAS_GLSAFE
#endif // NDEBUG
#ifdef HAS_GLSAFE
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
#else // HAS_GLSAFE
inline void glAssertRecentCall() { }
#define glsafe(cmd) cmd
#define glcheck()
#endif // HAS_GLSAFE
extern std::vector<std::array<float, 4>> get_extruders_colors();
extern float FullyTransparentMaterialThreshold;
extern float FullTransparentModdifiedToFixAlpha;
extern std::array<float, 4> adjust_color_for_rendering(const std::array<float, 4> &colors);
namespace Slic3r {
class SLAPrintObject;
enum SLAPrintObjectStep : unsigned int;
class BuildVolume;
class DynamicPrintConfig;
class ExtrusionPath;
class ExtrusionMultiPath;
class ExtrusionLoop;
class ExtrusionEntity;
class ExtrusionEntityCollection;
class ModelObject;
class ModelVolume;
class GLShaderProgram;
enum ModelInstanceEPrintVolumeState : unsigned char;
using ModelObjectPtrs = std::vector<ModelObject*>;
// Return appropriate color based on the ModelVolume.
std::array<float, 4> color_from_model_volume(const ModelVolume& model_volume);
// A container for interleaved arrays of 3D vertices and normals,
// possibly indexed by triangles and / or quads.
class GLIndexedVertexArray {
public:
// Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized.
static_assert(sizeof(Eigen::AlignedBox<float, 3>) == 24, "Eigen::AlignedBox<float, 3> is not being vectorized, thus it does not need to be aligned");
using BoundingBox = Eigen::AlignedBox<float, 3>;
GLIndexedVertexArray() { m_bounding_box.setEmpty(); }
GLIndexedVertexArray(const GLIndexedVertexArray &rhs) :
vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved),
triangle_indices(rhs.triangle_indices),
quad_indices(rhs.quad_indices),
m_bounding_box(rhs.m_bounding_box)
{ assert(!rhs.has_VBOs()); m_bounding_box.setEmpty(); }
GLIndexedVertexArray(GLIndexedVertexArray &&rhs) :
vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)),
triangle_indices(std::move(rhs.triangle_indices)),
quad_indices(std::move(rhs.quad_indices)),
m_bounding_box(rhs.m_bounding_box)
{ assert(! rhs.has_VBOs()); }
~GLIndexedVertexArray() { release_geometry(); }
GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs)
{
assert(vertices_and_normals_interleaved_VBO_id == 0);
assert(triangle_indices_VBO_id == 0);
assert(quad_indices_VBO_id == 0);
assert(rhs.vertices_and_normals_interleaved_VBO_id == 0);
assert(rhs.triangle_indices_VBO_id == 0);
assert(rhs.quad_indices_VBO_id == 0);
this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved;
this->triangle_indices = rhs.triangle_indices;
this->quad_indices = rhs.quad_indices;
this->m_bounding_box = rhs.m_bounding_box;
this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size;
this->triangle_indices_size = rhs.triangle_indices_size;
this->quad_indices_size = rhs.quad_indices_size;
return *this;
}
GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs)
{
assert(vertices_and_normals_interleaved_VBO_id == 0);
assert(triangle_indices_VBO_id == 0);
assert(quad_indices_VBO_id == 0);
assert(rhs.vertices_and_normals_interleaved_VBO_id == 0);
assert(rhs.triangle_indices_VBO_id == 0);
assert(rhs.quad_indices_VBO_id == 0);
this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved);
this->triangle_indices = std::move(rhs.triangle_indices);
this->quad_indices = std::move(rhs.quad_indices);
this->m_bounding_box = rhs.m_bounding_box;
this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size;
this->triangle_indices_size = rhs.triangle_indices_size;
this->quad_indices_size = rhs.quad_indices_size;
return *this;
}
// Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x)
std::vector<float> vertices_and_normals_interleaved;
std::vector<int> triangle_indices;
std::vector<int> quad_indices;
// When the geometry data is loaded into the graphics card as Vertex Buffer Objects,
// the above mentioned std::vectors are cleared and the following variables keep their original length.
size_t vertices_and_normals_interleaved_size{ 0 };
size_t triangle_indices_size{ 0 };
size_t quad_indices_size{ 0 };
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not sent to GPU yet.
unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
unsigned int triangle_indices_VBO_id{ 0 };
unsigned int quad_indices_VBO_id{ 0 };
#if ENABLE_SMOOTH_NORMALS
void load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals = false);
void load_mesh(const TriangleMesh& mesh, bool smooth_normals = false) { this->load_mesh_full_shading(mesh, smooth_normals); }
#else
void load_mesh_full_shading(const TriangleMesh& mesh);
void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); }
#endif // ENABLE_SMOOTH_NORMALS
void load_its_flat_shading(const indexed_triangle_set &its);
inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; }
inline void reserve(size_t sz) {
this->vertices_and_normals_interleaved.reserve(sz * 6);
this->triangle_indices.reserve(sz * 3);
this->quad_indices.reserve(sz * 4);
}
inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) {
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
if (this->vertices_and_normals_interleaved_VBO_id != 0)
return;
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
this->vertices_and_normals_interleaved.emplace_back(nx);
this->vertices_and_normals_interleaved.emplace_back(ny);
this->vertices_and_normals_interleaved.emplace_back(nz);
this->vertices_and_normals_interleaved.emplace_back(x);
this->vertices_and_normals_interleaved.emplace_back(y);
this->vertices_and_normals_interleaved.emplace_back(z);
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
m_bounding_box.extend(Vec3f(x, y, z));
};
inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) {
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
}
template<typename Derived, typename Derived2>
inline void push_geometry(const Eigen::MatrixBase<Derived>& p, const Eigen::MatrixBase<Derived2>& n) {
push_geometry(float(p(0)), float(p(1)), float(p(2)), float(n(0)), float(n(1)), float(n(2)));
}
inline void push_triangle(int idx1, int idx2, int idx3) {
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
if (this->vertices_and_normals_interleaved_VBO_id != 0)
return;
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
this->triangle_indices.emplace_back(idx1);
this->triangle_indices.emplace_back(idx2);
this->triangle_indices.emplace_back(idx3);
this->triangle_indices_size = this->triangle_indices.size();
};
inline void push_quad(int idx1, int idx2, int idx3, int idx4) {
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
if (this->vertices_and_normals_interleaved_VBO_id != 0)
return;
if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity())
this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4));
this->quad_indices.emplace_back(idx1);
this->quad_indices.emplace_back(idx2);
this->quad_indices.emplace_back(idx3);
this->quad_indices.emplace_back(idx4);
this->quad_indices_size = this->quad_indices.size();
};
// Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects
// and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
void finalize_geometry(bool opengl_initialized);
// Release the geometry data, release OpenGL VBOs.
void release_geometry();
void render() const;
void render(const std::pair<size_t, size_t>& tverts_range, const std::pair<size_t, size_t>& qverts_range) const;
// Is there any geometry data stored?
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
void clear() {
this->vertices_and_normals_interleaved.clear();
this->triangle_indices.clear();
this->quad_indices.clear();
vertices_and_normals_interleaved_size = 0;
triangle_indices_size = 0;
quad_indices_size = 0;
m_bounding_box.setEmpty();
}
// Shrink the internal storage to tighly fit the data stored.
void shrink_to_fit() {
this->vertices_and_normals_interleaved.shrink_to_fit();
this->triangle_indices.shrink_to_fit();
this->quad_indices.shrink_to_fit();
}
const BoundingBox& bounding_box() const { return m_bounding_box; }
// Return an estimate of the memory consumed by this class.
size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); }
// Return an estimate of the memory held by GPU vertex buffers.
size_t gpu_memory_used() const
{
size_t memsize = 0;
if (this->vertices_and_normals_interleaved_VBO_id != 0)
memsize += this->vertices_and_normals_interleaved_size * 4;
if (this->triangle_indices_VBO_id != 0)
memsize += this->triangle_indices_size * 4;
if (this->quad_indices_VBO_id != 0)
memsize += this->quad_indices_size * 4;
return memsize;
}
size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
private:
BoundingBox m_bounding_box;
};
class GLVolume {
public:
std::string name;
bool is_text_shape{false};
static std::array<float, 4> DISABLED_COLOR;
static std::array<float, 4> SLA_SUPPORT_COLOR;
static std::array<float, 4> SLA_PAD_COLOR;
static std::array<float, 4> NEUTRAL_COLOR;
static std::array<float, 4> UNPRINTABLE_COLOR;
static std::array<std::array<float, 4>, 5> MODEL_COLOR;
static std::array<float, 4> MODEL_MIDIFIER_COL;
static std::array<float, 4> MODEL_NEGTIVE_COL;
static std::array<float, 4> SUPPORT_ENFORCER_COL;
static std::array<float, 4> SUPPORT_BLOCKER_COL;
static std::array<float, 4> MODEL_HIDDEN_COL;
static void update_render_colors();
static void load_render_colors();
static float explosion_ratio;
static float last_explosion_ratio;
enum EHoverState : unsigned char
{
HS_None,
HS_Hover,
HS_Select,
HS_Deselect
};
GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f, bool create_index_data = true);
GLVolume(const std::array<float, 4>& rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
virtual ~GLVolume() = default;
// QDS
protected:
Geometry::Transformation m_instance_transformation;
Geometry::Transformation m_volume_transformation;
// QDS
Vec3d m_offset_to_assembly{ 0.0, 0.0, 0.0 };
// Shift in z required by sla supports+pad
double m_sla_shift_z;
// Bounding box of this volume, in unscaled coordinates.
std::optional<BoundingBoxf3> m_transformed_bounding_box;
// Convex hull of the volume, if any.
std::shared_ptr<const TriangleMesh> m_convex_hull;
// Bounding box of this volume, in unscaled coordinates.
std::optional<BoundingBoxf3> m_transformed_convex_hull_bounding_box;
// Bounding box of the non sinking part of this volume, in unscaled coordinates.
std::optional<BoundingBoxf3> m_transformed_non_sinking_bounding_box;
class SinkingContours
{
static const float HalfWidth;
GLVolume& m_parent;
GUI::GLModel m_model;
BoundingBoxf3 m_old_box;
Vec3d m_shift{ Vec3d::Zero() };
public:
SinkingContours(GLVolume& volume) : m_parent(volume) {}
void render();
private:
void update();
};
SinkingContours m_sinking_contours;
public:
// Color of the triangles / quads held by this volume.
std::array<float, 4> color;
// Color used to render this volume.
std::array<float, 4> render_color;
struct CompositeID {
CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {}
CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {}
// Object ID, which is equal to the index of the respective ModelObject in Model.objects array.
int object_id;
// Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array.
// If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject,
// and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports.
// Volume with a negative volume_id cannot be picked independently, it will pick the associated instance.
int volume_id;
// Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array.
int instance_id;
bool operator==(const CompositeID &rhs) const { return object_id == rhs.object_id && volume_id == rhs.volume_id && instance_id == rhs.instance_id; }
bool operator!=(const CompositeID &rhs) const { return ! (*this == rhs); }
bool operator< (const CompositeID &rhs) const
{ return object_id < rhs.object_id || (object_id == rhs.object_id && (volume_id < rhs.volume_id || (volume_id == rhs.volume_id && instance_id < rhs.instance_id))); }
};
CompositeID composite_id;
// Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID,
// for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(),
// and the associated ModelInstanceID.
// Valid geometry_id should always be positive.
std::pair<size_t, size_t> geometry_id;
// An ID containing the extruder ID (used to select color).
int extruder_id;
size_t model_object_ID{0};
// Various boolean flags.
struct {
// Is this object selected?
bool selected : 1;
// Is this object disabled from selection?
bool disabled : 1;
// Is this object printable?
bool printable : 1;
// Is this object visible(in assemble view)?
bool visible : 1;
// Whether or not this volume is active for rendering
bool is_active : 1;
// Whether or not to use this volume when applying zoom_to_volumes()
bool zoom_to_volumes : 1;
// Wheter or not this volume is enabled for outside print volume detection in shader.
bool shader_outside_printer_detection_enabled : 1;
// Wheter or not this volume is outside print volume.
bool is_outside : 1;
bool partly_inside : 1;
// Wheter or not this volume has been generated from a modifier
bool is_modifier : 1;
// Wheter or not this volume has been generated from the wipe tower
bool is_wipe_tower : 1;
// Wheter or not this volume has been generated from an extrusion path
bool is_extrusion_path : 1;
// Wheter or not to always render this volume using its own alpha
bool force_transparent : 1;
// Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE)
bool force_native_color : 1;
// Whether or not render this volume in neutral
bool force_neutral_color : 1;
// Whether or not to force rendering of sinking contours
bool force_sinking_contours : 1;
};
// Is mouse or rectangle selection over this object to select/deselect it ?
EHoverState hover;
// Interleaved triangles & normals with indexed triangles & quads.
std::shared_ptr<GLIndexedVertexArray> indexed_vertex_array;
const TriangleMesh * ori_mesh{nullptr};
// QDS
mutable std::vector<GLIndexedVertexArray> mmuseg_ivas;
mutable ObjectBase::Timestamp mmuseg_ts;
// Ranges of triangle and quad indices to be rendered.
std::pair<size_t, size_t> tverts_range;
std::pair<size_t, size_t> qverts_range;
// If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts
// of the extrusions per layer.
std::vector<coordf_t> print_zs;
// Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
std::vector<size_t> offsets;
// Bounding box of this volume, in unscaled coordinates.
BoundingBoxf3 bounding_box() const {
BoundingBoxf3 out;
if (! this->indexed_vertex_array->bounding_box().isEmpty()) {
out.min = this->indexed_vertex_array->bounding_box().min().cast<double>();
out.max = this->indexed_vertex_array->bounding_box().max().cast<double>();
out.defined = true;
};
return out;
}
void set_color(const std::array<float, 4>& rgba);
void set_render_color(float r, float g, float b, float a);
void set_render_color(const std::array<float, 4>& rgba);
// Sets render color in dependence of current state
void set_render_color();
// set color according to model volume
void set_color_from_model_volume(const ModelVolume& model_volume);
const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; }
void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); }
void set_instance_transformation(const Transform3d &transform){ m_instance_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); }
const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); }
double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); }
void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); }
void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); }
const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); }
double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); }
void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); }
void set_instance_rotation(Axis axis, double rotation) { m_instance_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); }
Vec3d get_instance_scaling_factor() const { return m_instance_transformation.get_scaling_factor(); }
double get_instance_scaling_factor(Axis axis) const { return m_instance_transformation.get_scaling_factor(axis); }
void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); }
void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); }
const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); }
double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); }
void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); }
void set_instance_mirror(Axis axis, double mirror) { m_instance_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); }
const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; }
void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); }
void set_volume_transformation(const Transform3d &transform) { m_volume_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); }
const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); }
double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); }
void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); }
void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); }
const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); }
double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); }
void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); }
void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); }
const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); }
double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); }
void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); }
void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); }
const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); }
double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); }
void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); }
void set_volume_mirror(Axis axis, double mirror) { m_volume_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); }
double get_sla_shift_z() const { return m_sla_shift_z; }
void set_sla_shift_z(double z) { m_sla_shift_z = z; }
void set_convex_hull(std::shared_ptr<const TriangleMesh> convex_hull) { m_convex_hull = std::move(convex_hull); }
void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(convex_hull); }
void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(std::move(convex_hull)); }
void set_offset_to_assembly(const Vec3d& offset) { m_offset_to_assembly = offset; set_bounding_boxes_as_dirty(); }
const Vec3d& get_offset_to_assembly() { return m_offset_to_assembly; }
int object_idx() const { return this->composite_id.object_id; }
int volume_idx() const { return this->composite_id.volume_id; }
int instance_idx() const { return this->composite_id.instance_id; }
Transform3d world_matrix() const;
bool is_left_handed() const;
const BoundingBoxf3& transformed_bounding_box() const;
// non-caching variant
BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const;
// caching variant
const BoundingBoxf3& transformed_convex_hull_bounding_box() const;
// non-caching variant
BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const;
// caching variant
const BoundingBoxf3& transformed_non_sinking_bounding_box() const;
// convex hull
const TriangleMesh* convex_hull() const { return m_convex_hull.get(); }
bool empty() const { return this->indexed_vertex_array->empty(); }
void set_range(double low, double high);
//QDS: add outline related logic and add virtual specifier
virtual void render(bool with_outline = false,
const std::array<float, 4> &body_color = {1.0f, 1.0f, 1.0f, 1.0f} ) const;
//QDS: add simple render function for thumbnail
void simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector<std::array<float, 4>>& extruder_colors,bool ban_light =false) const;
void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array->finalize_geometry(opengl_initialized); }
void release_geometry() { this->indexed_vertex_array->release_geometry(); }
void set_bounding_boxes_as_dirty() {
m_transformed_bounding_box.reset();
m_transformed_convex_hull_bounding_box.reset();
m_transformed_non_sinking_bounding_box.reset();
}
bool is_sla_support() const;
bool is_sla_pad() const;
bool is_sinking() const;
bool is_below_printbed() const;
void render_sinking_contours();
// Return an estimate of the memory consumed by this class.
size_t cpu_memory_used() const {
//FIXME what to do wih m_convex_hull?
return sizeof(*this) - sizeof(*(this->indexed_vertex_array)) + this->indexed_vertex_array->cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t);
}
// Return an estimate of the memory held by GPU vertex buffers.
size_t gpu_memory_used() const { return this->indexed_vertex_array->gpu_memory_used(); }
size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
};
// QDS
class GLWipeTowerVolume : public GLVolume {
public:
GLWipeTowerVolume(const std::vector<std::array<float, 4>>& colors);
virtual void render(bool with_outline = false, const std::array<float, 4> &body_color = {1.0f, 1.0f, 1.0f, 1.0f}) const;
std::vector<GLIndexedVertexArray> iva_per_colors;
bool IsTransparent();
private:
std::vector<std::array<float, 4>> m_colors;
};
typedef std::vector<GLVolume*> GLVolumePtrs;
typedef std::pair<GLVolume*, std::pair<unsigned int, double>> GLVolumeWithIdAndZ;
typedef std::vector<GLVolumeWithIdAndZ> GLVolumeWithIdAndZList;
class GLVolumeCollection
{
public:
enum class ERenderType : unsigned char
{
Opaque,
Transparent,
All
};
struct PrintVolume
{
// see: Bed3D::EShapeType
int type{ 0 };
// data contains:
// Rectangle:
// [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y
// Circle:
// [0] = center.x, [1] = center.y, [3] = radius
std::array<float, 4> data;
// [0] = min z, [1] = max z
std::array<float, 2> zs;
};
private:
PrintVolume m_print_volume;
PrintVolume m_render_volume;
// z range for clipping in shaders
float m_z_range[2];
// plane coeffs for clipping in shaders
float m_clipping_plane[4];
struct Slope
{
// toggle for slope rendering
bool active{ false };//local active
bool isGlobalActive{false};
float normal_z;
};
Slope m_slope;
bool m_show_sinking_contours = false;
public:
GLVolumePtrs volumes;
GLVolumeCollection() {
set_default_slope_normal_z();
//QDS init render volume
m_render_volume.type = -1;
}
~GLVolumeCollection() { clear(); }
std::vector<int> load_object(
const ModelObject *model_object,
int obj_idx,
const std::vector<int> &instance_idxs,
const std::string &color_by,
bool opengl_initialized);
int load_object_volume(
const ModelObject *model_object,
int obj_idx,
int volume_idx,
int instance_idx,
const std::string &color_by,
bool opengl_initialized,
bool in_assemble_view = false,
bool use_loaded_id = false);
// Load SLA auxiliary GLVolumes (for support trees or pad).
void load_object_auxiliary(
const SLAPrintObject *print_object,
int obj_idx,
// pairs of <instance_idx, print_instance_idx>
const std::vector<std::pair<size_t, size_t>>& instances,
SLAPrintObjectStep milestone,
// Timestamp of the last change of the milestone
size_t timestamp,
bool opengl_initialized);
int load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized);
GLVolume* new_toolpath_volume(const std::array<float, 4>& rgba, size_t reserve_vbo_floats = 0);
GLVolume* new_nontoolpath_volume(const std::array<float, 4>& rgba, size_t reserve_vbo_floats = 0);
int get_selection_support_threshold_angle(bool&) const;
// Render the volumes by OpenGL.
//QDS: add outline drawing logic
void render(ERenderType type,
bool disable_cullface,
const Transform3d & view_matrix,
std::function<bool(const GLVolume &)> filter_func = std::function<bool(const GLVolume &)>(),
bool with_outline = true,
const std::array<float, 4>& body_color = {1.0f, 1.0f, 1.0f, 1.0f},
bool partly_inside_enable =true
) const;
// Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects
// and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
void finalize_geometry(bool opengl_initialized) { for (auto* v : volumes) v->finalize_geometry(opengl_initialized); }
// Release the geometry data assigned to the volumes.
// If OpenGL VBOs were allocated, an OpenGL context has to be active to release them.
void release_geometry() { for (auto *v : volumes) v->release_geometry(); }
// Clear the geometry
void clear();
void release_volume (GLVolume* volume);
bool empty() const { return volumes.empty(); }
void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); }
void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; }
void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; }
void set_clipping_plane(const double* coeffs) { m_clipping_plane[0] = coeffs[0]; m_clipping_plane[1] = coeffs[1]; m_clipping_plane[2] = coeffs[2]; m_clipping_plane[3] = coeffs[3]; }
bool is_slope_GlobalActive() const { return m_slope.isGlobalActive; }
bool is_slope_active() const { return m_slope.active; }
void set_slope_active(bool active) { m_slope.active = active; }
void set_slope_GlobalActive(bool active) { m_slope.isGlobalActive = active; }
float get_slope_normal_z() const { return m_slope.normal_z; }
void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; }
void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); }
void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; }
// returns true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null
bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const;
void reset_outside_state();
void update_colors_by_extruder(const DynamicPrintConfig *config, bool is_update_alpha = true);
// Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection
std::vector<double> get_current_print_zs(bool active_only) const;
// Return an estimate of the memory consumed by this class.
size_t cpu_memory_used() const;
// Return an estimate of the memory held by GPU vertex buffers.
size_t gpu_memory_used() const;
size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
// Return CPU, GPU and total memory log line.
std::string log_memory_info() const;
private:
GLVolumeCollection(const GLVolumeCollection &other);
GLVolumeCollection& operator=(const GLVolumeCollection &);
};
GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = nullptr);
struct _3DScene
{
static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
static void extrusionentity_to_verts(const Polyline &polyline, float width, float height, float print_z, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume);
};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,201 @@
#ifndef slic3r_AMSMaterialsSetting_hpp_
#define slic3r_AMSMaterialsSetting_hpp_
#include "libslic3r/Preset.hpp"
#include "wxExtensions.hpp"
#include "GUI_Utils.hpp"
#include "DeviceManager.hpp"
#include "wx/clrpicker.h"
#include "wx/colourdata.h"
#include "Widgets/RadioBox.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/CheckBox.hpp"
#include "Widgets/ComboBox.hpp"
#include "Widgets/TextInput.hpp"
#include "../slic3r/Utils/CalibUtils.hpp"
#define AMS_MATERIALS_SETTING_DEF_COLOUR wxColour(255, 255, 255)
#define AMS_MATERIALS_SETTING_GREY900 wxColour(38, 46, 48)
#define AMS_MATERIALS_SETTING_GREY800 wxColour(50, 58, 61)
#define AMS_MATERIALS_SETTING_GREY700 wxColour(107, 107, 107)
#define AMS_MATERIALS_SETTING_GREY300 wxColour(174,174,174)
#define AMS_MATERIALS_SETTING_GREY200 wxColour(248, 248, 248)
#define AMS_MATERIALS_SETTING_BODY_WIDTH FromDIP(380)
#define AMS_MATERIALS_SETTING_LABEL_WIDTH FromDIP(80)
#define AMS_MATERIALS_SETTING_COMBOX_WIDTH wxSize(FromDIP(250), FromDIP(30))
#define AMS_MATERIALS_SETTING_BUTTON_SIZE wxSize(FromDIP(90), FromDIP(24))
#define AMS_MATERIALS_SETTING_INPUT_SIZE wxSize(FromDIP(90), FromDIP(24))
namespace Slic3r { namespace GUI {
class ColorPicker : public wxWindow
{
public:
wxBitmap m_bitmap_border;
wxBitmap m_bitmap_border_dark;
wxBitmap m_bitmap_transparent;
wxColour m_colour;
std::vector<wxColour> m_cols;
bool m_selected{false};
bool m_show_full{false};
bool m_is_empty{false};
int ctype;
bool transparent_changed{false};
ColorPicker(wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize);
~ColorPicker();
void msw_rescale();
void set_color(wxColour col);
void set_colors(std::vector<wxColour> cols);
void set_selected(bool sel) {m_selected = sel;Refresh();};
void set_show_full(bool full) {m_show_full = full;Refresh();};
void is_empty(bool empty) {m_is_empty = empty;};
void paintEvent(wxPaintEvent& evt);
void render(wxDC& dc);
void doRender(wxDC& dc);
};
class ColorPickerPopup : public PopupWindow
{
public:
ScalableBitmap m_ts_bitmap_custom;
wxStaticBitmap* m_ts_stbitmap_custom;
StaticBox* m_custom_cp;
wxColourData* m_clrData;
StaticBox* m_def_color_box;
wxFlexGridSizer* m_ams_fg_sizer;
wxColour m_def_col;
std::vector<wxColour> m_def_colors;
std::vector<wxColour> m_ams_colors;
std::vector<ColorPicker*> m_color_pickers;
std::vector<ColorPicker*> m_ams_color_pickers;
public:
ColorPickerPopup(wxWindow* parent);
~ColorPickerPopup() {};
void on_custom_clr_picker(wxMouseEvent& event);
void set_ams_colours(std::vector<wxColour> ams);
void set_def_colour(wxColour col);
void paintEvent(wxPaintEvent& evt);
void Popup();
virtual void OnDismiss() wxOVERRIDE;
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
public:
};
class AMSMaterialsSetting : public DPIDialog
{
public:
AMSMaterialsSetting(wxWindow *parent, wxWindowID id);
~AMSMaterialsSetting();
void create();
void paintEvent(wxPaintEvent &evt);
void input_min_finish();
void input_max_finish();
void update();
void enable_confirm_button(bool en);
bool Show(bool show) override;
void Popup(wxString filament = wxEmptyString, wxString sn = wxEmptyString,
wxString temp_min = wxEmptyString, wxString temp_max = wxEmptyString,
wxString k = wxEmptyString, wxString n = wxEmptyString);
void post_select_event(int index);
void msw_rescale();
void set_color(wxColour color);
void set_empty_color(wxColour color);
void set_colors(std::vector<wxColour> colors);
void set_ctype(int ctype);
void on_picker_color(wxCommandEvent& color);
MachineObject* obj{ nullptr };
int ams_id { 0 }; /* 0 ~ 3 */
int tray_id { 0 }; /* 0 ~ 3 */
std::string ams_filament_id;
std::string ams_setting_id;
bool m_is_third;
wxString m_brand_filament;
wxString m_brand_sn;
wxString m_brand_tmp;
wxColour m_brand_colour;
std::string m_filament_type;
ColorPickerPopup m_color_picker_popup;
ColorPicker * m_clr_picker;
std::vector<PACalibResult> m_pa_profile_items;
protected:
void create_panel_normal(wxWindow* parent);
void create_panel_kn(wxWindow* parent);
void on_dpi_changed(const wxRect &suggested_rect) override;
void on_select_filament(wxCommandEvent& evt);
void on_select_cali_result(wxCommandEvent &evt);
void on_select_ok(wxCommandEvent &event);
void on_select_reset(wxCommandEvent &event);
void on_select_close(wxCommandEvent &event);
void on_clr_picker(wxMouseEvent &event);
bool is_virtual_tray();
void update_widgets();
protected:
StateColor m_btn_bg_green;
StateColor m_btn_bg_gray;
wxPanel * m_panel_normal;
wxPanel * m_panel_SN;
wxStaticText * m_sn_number;
wxStaticText * warning_text;
//wxPanel * m_panel_body;
wxStaticText * m_title_filament;
wxStaticText * m_title_pa_profile;
wxStaticText * m_title_colour;
wxStaticText * m_title_temperature;
TextInput * m_input_nozzle_min;
TextInput* m_input_nozzle_max;
ScalableBitmap * degree;
wxStaticBitmap * bitmap_max_degree;
wxStaticBitmap * bitmap_min_degree;
Button * m_button_reset;
Button * m_button_confirm;
Label* m_tip_readonly;
Button * m_button_close;
wxColourData * m_clrData;
wxPanel * m_panel_kn;
wxStaticText* m_ratio_text;
wxStaticText* m_k_param;
TextInput* m_input_k_val;
wxStaticText* m_n_param;
TextInput* m_input_n_val;
int m_filament_selection;
int m_pa_cali_select_id = 0;
#ifdef __APPLE__
wxComboBox *m_comboBox_filament;
#else
ComboBox *m_comboBox_filament;
#endif
ComboBox * m_comboBox_cali_result;
TextInput* m_readonly_filament;
struct FilamentInfos {
std::string filament_id;
std::string setting_id;
};
std::map<std::string, FilamentInfos> map_filament_items;
};
wxDECLARE_EVENT(EVT_SELECTED_COLOR, wxCommandEvent);
}} // namespace Slic3r::GUI
#endif

View File

@@ -0,0 +1,508 @@
#include "AMSSetting.hpp"
#include "GUI_App.hpp"
#include "I18N.hpp"
namespace Slic3r { namespace GUI {
AMSSetting::AMSSetting(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style)
: DPIDialog(parent, id, wxEmptyString, pos, size, style)
{
create();
wxGetApp().UpdateDlgDarkUI(this);
}
AMSSetting::~AMSSetting() {}
void AMSSetting::create()
{
wxBoxSizer *m_sizer_main;
m_sizer_main = new wxBoxSizer(wxVERTICAL);
SetBackgroundColour(*wxWHITE);
auto m_static_ams_settings = new wxStaticText(this, wxID_ANY, _L("AMS Settings"), wxDefaultPosition, wxDefaultSize, 0);
m_static_ams_settings->SetFont(::Label::Head_14);
m_static_ams_settings->SetForegroundColour(AMS_SETTING_GREY800);
m_panel_body = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, -1), wxTAB_TRAVERSAL);
m_panel_body->SetBackgroundColour(*wxWHITE);
wxBoxSizer *m_sizerl_body = new wxBoxSizer(wxVERTICAL);
m_panel_Insert_material = new wxPanel(m_panel_body, wxID_ANY, wxDefaultPosition, wxSize(-1, -1), wxTAB_TRAVERSAL);
m_panel_Insert_material->SetBackgroundColour(*wxWHITE);
wxBoxSizer* m_sizer_main_Insert_material = new wxBoxSizer(wxVERTICAL);
// checkbox area 1
wxBoxSizer *m_sizer_Insert_material = new wxBoxSizer(wxHORIZONTAL);
m_checkbox_Insert_material_auto_read = new ::CheckBox(m_panel_Insert_material);
m_checkbox_Insert_material_auto_read->Bind(wxEVT_TOGGLEBUTTON, &AMSSetting::on_insert_material_read, this);
m_sizer_Insert_material->Add(m_checkbox_Insert_material_auto_read, 0, wxTOP, 1);
m_sizer_Insert_material->Add(0, 0, 0, wxLEFT, 12);
m_title_Insert_material_auto_read = new wxStaticText(m_panel_Insert_material, wxID_ANY, _L("Insertion update"),
wxDefaultPosition, wxDefaultSize, 0);
m_title_Insert_material_auto_read->SetFont(::Label::Head_13);
m_title_Insert_material_auto_read->SetForegroundColour(AMS_SETTING_GREY800);
m_title_Insert_material_auto_read->Wrap(AMS_SETTING_BODY_WIDTH);
m_sizer_Insert_material->Add(m_title_Insert_material_auto_read, 0, wxALL | wxEXPAND, 0);
wxBoxSizer *m_sizer_Insert_material_tip = new wxBoxSizer(wxHORIZONTAL);
m_sizer_Insert_material_tip_inline = new wxBoxSizer(wxVERTICAL);
m_sizer_Insert_material_tip->Add(0, 0, 0, wxLEFT, 10);
// tip line1
m_tip_Insert_material_line1 = new Label(m_panel_Insert_material,
_L("The AMS will automatically read the filament information when inserting a new QIDI Lab filament. This takes about 20 seconds.")
);
m_tip_Insert_material_line1->SetFont(::Label::Body_13);
m_tip_Insert_material_line1->SetForegroundColour(AMS_SETTING_GREY700);
m_tip_Insert_material_line1->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
m_tip_Insert_material_line1->Wrap(AMS_SETTING_BODY_WIDTH);
m_tip_Insert_material_line1->Hide();
m_sizer_Insert_material_tip_inline->Add(m_tip_Insert_material_line1, 0, wxEXPAND, 0);
// tip line2
m_tip_Insert_material_line2 = new Label(m_panel_Insert_material,
_L("Note: if a new filament is inserted during printing, the AMS will not automatically read any information until printing is completed.")
);
m_tip_Insert_material_line2->SetFont(::Label::Body_13);
m_tip_Insert_material_line2->SetForegroundColour(AMS_SETTING_GREY700);
m_tip_Insert_material_line2->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
m_tip_Insert_material_line2->Wrap(AMS_SETTING_BODY_WIDTH);
m_tip_Insert_material_line2->Hide();
m_sizer_Insert_material_tip_inline->Add(m_tip_Insert_material_line2, 0, wxEXPAND | wxTOP, 8);
// tip line3
m_tip_Insert_material_line3 = new Label(m_panel_Insert_material,
_L("When inserting a new filament, the AMS will not automatically read its information, leaving it blank for you to enter manually.")
);
m_tip_Insert_material_line3->SetFont(::Label::Body_13);
m_tip_Insert_material_line3->SetForegroundColour(AMS_SETTING_GREY700);
m_tip_Insert_material_line3->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
m_tip_Insert_material_line3->Wrap(AMS_SETTING_BODY_WIDTH);
m_tip_Insert_material_line3->Hide();
m_sizer_Insert_material_tip_inline->Add(m_tip_Insert_material_line3, 0, wxEXPAND, 0);
m_sizer_Insert_material_tip->Add(m_sizer_Insert_material_tip_inline, 1, wxALIGN_CENTER, 0);
m_sizer_main_Insert_material->Add(m_sizer_Insert_material, 0, wxEXPAND | wxTOP, FromDIP(4));
m_sizer_main_Insert_material->Add(m_sizer_Insert_material_tip, 0, wxEXPAND | wxLEFT | wxTOP, 18);
m_panel_Insert_material->SetSizer(m_sizer_main_Insert_material);
// checkbox area 2
wxBoxSizer *m_sizer_starting = new wxBoxSizer(wxHORIZONTAL);
m_checkbox_starting_auto_read = new ::CheckBox(m_panel_body);
m_checkbox_starting_auto_read->Bind(wxEVT_TOGGLEBUTTON, &AMSSetting::on_starting_read, this);
m_sizer_starting->Add(m_checkbox_starting_auto_read, 0, wxTOP, 1);
m_sizer_starting->Add(0, 0, 0, wxLEFT, 12);
m_title_starting_auto_read = new wxStaticText(m_panel_body, wxID_ANY, _L("Power on update"), wxDefaultPosition,wxDefaultSize, 0);
m_title_starting_auto_read->SetFont(::Label::Head_13);
m_title_starting_auto_read->SetForegroundColour(AMS_SETTING_GREY800);
m_title_starting_auto_read->Wrap(AMS_SETTING_BODY_WIDTH);
m_sizer_starting->Add(m_title_starting_auto_read, 1, wxEXPAND, 0);
wxBoxSizer *m_sizer_starting_tip = new wxBoxSizer(wxHORIZONTAL);
m_sizer_starting_tip->Add(0, 0, 0, wxLEFT, 10);
// tip line
m_sizer_starting_tip_inline = new wxBoxSizer(wxVERTICAL);
m_tip_starting_line1 = new Label(m_panel_body,
_L("The AMS will automatically read the information of inserted filament on start-up. It will take about 1 minute.The reading process will roll filament spools.")
);
m_tip_starting_line1->SetFont(::Label::Body_13);
m_tip_starting_line1->SetForegroundColour(AMS_SETTING_GREY700);
m_tip_starting_line1->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
m_tip_starting_line1->Wrap(AMS_SETTING_BODY_WIDTH);
m_sizer_starting_tip_inline->Add(m_tip_starting_line1, 0, wxEXPAND, 0);
m_tip_starting_line2 = new Label(m_panel_body,
_L("The AMS will not automatically read information from inserted filament during startup and will continue to use the information recorded before the last shutdown.")
);
m_tip_starting_line2->SetFont(::Label::Body_13);
m_tip_starting_line2->SetForegroundColour(AMS_SETTING_GREY700);
m_tip_starting_line2->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
m_tip_starting_line2->Wrap(AMS_SETTING_BODY_WIDTH);
m_sizer_starting_tip_inline->Add(m_tip_starting_line2, 0, wxEXPAND,0);
m_sizer_starting_tip->Add(m_sizer_starting_tip_inline, 1, wxALIGN_CENTER, 0);
// checkbox area 3
wxBoxSizer* m_sizer_remain = new wxBoxSizer(wxHORIZONTAL);
m_checkbox_remain = new ::CheckBox(m_panel_body);
m_checkbox_remain->Bind(wxEVT_TOGGLEBUTTON, &AMSSetting::on_remain, this);
m_sizer_remain->Add(m_checkbox_remain, 0, wxTOP, 1);
m_sizer_remain->Add(0, 0, 0, wxLEFT, 12);
m_title_remain = new wxStaticText(m_panel_body, wxID_ANY, _L("Update remaining capacity"), wxDefaultPosition, wxDefaultSize, 0);
m_title_remain->SetFont(::Label::Head_13);
m_title_remain->SetForegroundColour(AMS_SETTING_GREY800);
m_title_remain->Wrap(AMS_SETTING_BODY_WIDTH);
m_sizer_remain->Add(m_title_remain, 1, wxEXPAND, 0);
wxBoxSizer* m_sizer_remain_tip = new wxBoxSizer(wxHORIZONTAL);
m_sizer_remain_tip->Add(0, 0, 0, wxLEFT, 10);
// tip line
m_sizer_remain_inline = new wxBoxSizer(wxVERTICAL);
m_tip_remain_line1 = new Label(m_panel_body,
_L("The AMS will estimate QIDI filament's remaining capacity after the filament info is updated. During printing, remaining capacity will be updated automatically.")
);
m_tip_remain_line1->SetFont(::Label::Body_13);
m_tip_remain_line1->SetForegroundColour(AMS_SETTING_GREY700);
m_tip_remain_line1->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
m_tip_remain_line1->Wrap(AMS_SETTING_BODY_WIDTH);
m_sizer_remain_inline->Add(m_tip_remain_line1, 0, wxEXPAND, 0);
m_sizer_remain_tip->Add(m_sizer_remain_inline, 1, wxALIGN_CENTER, 0);
// checkbox area 4
wxBoxSizer* m_sizer_switch_filament = new wxBoxSizer(wxHORIZONTAL);
m_checkbox_switch_filament = new ::CheckBox(m_panel_body);
m_checkbox_switch_filament->Bind(wxEVT_TOGGLEBUTTON, &AMSSetting::on_switch_filament, this);
m_sizer_switch_filament->Add(m_checkbox_switch_filament, 0, wxTOP, 1);
m_sizer_switch_filament->Add(0, 0, 0, wxLEFT, 12);
m_title_switch_filament = new wxStaticText(m_panel_body, wxID_ANY, _L("AMS filament backup"), wxDefaultPosition, wxDefaultSize, 0);
m_title_switch_filament->SetFont(::Label::Head_13);
m_title_switch_filament->SetForegroundColour(AMS_SETTING_GREY800);
m_title_switch_filament->Wrap(AMS_SETTING_BODY_WIDTH);
m_sizer_switch_filament->Add(m_title_switch_filament, 1, wxEXPAND, 0);
wxBoxSizer* m_sizer_switch_filament_tip = new wxBoxSizer(wxHORIZONTAL);
m_sizer_switch_filament_tip->Add(0, 0, 0, wxLEFT, 10);
// tip line
m_sizer_switch_filament_inline = new wxBoxSizer(wxVERTICAL);
m_tip_switch_filament_line1 = new Label(m_panel_body,
_L("AMS will continue to another spool with the same properties of filament automatically when current filament runs out")
);
m_tip_switch_filament_line1->SetFont(::Label::Body_13);
m_tip_switch_filament_line1->SetForegroundColour(AMS_SETTING_GREY700);
m_tip_switch_filament_line1->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
m_tip_switch_filament_line1->Wrap(AMS_SETTING_BODY_WIDTH);
m_sizer_switch_filament_inline->Add(m_tip_switch_filament_line1, 0, wxEXPAND, 0);
m_sizer_switch_filament_tip->Add(m_sizer_switch_filament_inline, 1, wxALIGN_CENTER, 0);
// checkbox area 5
wxBoxSizer* m_sizer_air_print = new wxBoxSizer(wxHORIZONTAL);
m_checkbox_air_print = new ::CheckBox(m_panel_body);
m_checkbox_air_print->Bind(wxEVT_TOGGLEBUTTON, &AMSSetting::on_air_print_detect, this);
m_sizer_air_print->Add(m_checkbox_air_print, 0, wxTOP, 1);
m_sizer_air_print->Add(0, 0, 0, wxLEFT, 12);
m_title_air_print = new wxStaticText(m_panel_body, wxID_ANY, _L("Air Printing Detection"), wxDefaultPosition, wxDefaultSize, 0);
m_title_air_print->SetFont(::Label::Head_13);
m_title_air_print->SetForegroundColour(AMS_SETTING_GREY800);
m_title_air_print->Wrap(AMS_SETTING_BODY_WIDTH);
m_sizer_air_print->Add(m_title_air_print, 1, wxEXPAND, 0);
wxBoxSizer* m_sizer_air_print_tip = new wxBoxSizer(wxHORIZONTAL);
m_sizer_air_print_tip->Add(0, 0, 0, wxLEFT, 10);
// tip line
auto m_sizer_air_print_inline = new wxBoxSizer(wxVERTICAL);
m_tip_air_print_line = new Label(m_panel_body,
_L("Detects clogging and filament grinding, halting printing immediately to conserve time and filament.")
);
m_tip_air_print_line->SetFont(::Label::Body_13);
m_tip_air_print_line->SetForegroundColour(AMS_SETTING_GREY700);
m_tip_air_print_line->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
m_tip_air_print_line->Wrap(AMS_SETTING_BODY_WIDTH);
m_sizer_air_print_inline->Add(m_tip_air_print_line, 0, wxEXPAND, 0);
m_sizer_air_print_tip->Add(m_sizer_air_print_inline, 1, wxALIGN_CENTER, 0);
m_checkbox_air_print->Hide();
m_title_air_print->Hide();
m_tip_air_print_line->Hide();
// panel img
wxPanel* m_panel_img = new wxPanel(m_panel_body, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_panel_img->SetBackgroundColour(AMS_SETTING_GREY200);
wxBoxSizer *m_sizer_img = new wxBoxSizer(wxVERTICAL);
m_am_img = new wxStaticBitmap(m_panel_img, wxID_ANY, create_scaled_bitmap("ams_icon", nullptr, 126), wxDefaultPosition, wxDefaultSize);
m_sizer_img->Add(m_am_img, 0, wxALIGN_CENTER | wxTOP, 26);
m_sizer_img->Add(0, 0, 0, wxTOP, 18);
m_panel_img->SetSizer(m_sizer_img);
m_panel_img->Layout();
m_sizer_img->Fit(m_panel_img);
m_sizer_remain_block = new wxBoxSizer(wxVERTICAL);
m_sizer_remain_block->Add(m_sizer_remain, 0, wxEXPAND | wxTOP, FromDIP(8));
m_sizer_remain_block->Add(0, 0, 0, wxTOP, 8);
m_sizer_remain_block->Add(m_sizer_remain_tip, 0, wxLEFT, 18);
m_sizer_remain_block->Add(0, 0, 0, wxTOP, 15);
m_sizerl_body->Add(m_panel_Insert_material, 0, 0, 0);
m_sizerl_body->Add(m_sizer_starting, 0, wxEXPAND | wxTOP, FromDIP(8));
m_sizerl_body->Add(0, 0, 0, wxTOP, 8);
m_sizerl_body->Add(m_sizer_starting_tip, 0, wxLEFT, 18);
m_sizerl_body->Add(0, 0, 0, wxTOP, 15);
m_sizerl_body->Add(m_sizer_remain_block, 0, wxEXPAND, 0);
m_sizerl_body->Add(m_sizer_switch_filament, 0, wxEXPAND | wxTOP, FromDIP(8));
m_sizerl_body->Add(0, 0, 0, wxTOP, 8);
m_sizerl_body->Add(m_sizer_switch_filament_tip, 0, wxLEFT, 18);
m_sizerl_body->Add(0, 0, 0, wxTOP, 6);
m_sizerl_body->Add(0, 0, 0, wxTOP, FromDIP(5));
m_sizerl_body->Add(m_sizer_air_print, 0, wxEXPAND | wxTOP, FromDIP(8));
m_sizerl_body->Add(0, 0, 0, wxTOP, 8);
m_sizerl_body->Add(m_sizer_air_print_tip, 0, wxLEFT, 18);
m_sizerl_body->Add(0, 0, 0, wxTOP, 6);
m_sizerl_body->Add(0, 0, 0, wxTOP, FromDIP(5));
m_sizerl_body->Add(m_panel_img, 1, wxEXPAND | wxALL, FromDIP(5));
m_panel_body->SetSizer(m_sizerl_body);
m_panel_body->Layout();
m_sizerl_body->Fit(m_panel_body);
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(10));
m_sizer_main->Add(m_static_ams_settings, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(24));
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(10));
m_sizer_main->Add(m_panel_body, 1, wxBottom | wxLEFT | wxRIGHT | wxEXPAND, FromDIP(24));
this->SetSizer(m_sizer_main);
this->Layout();
m_sizer_main->Fit(this);
this->Centre(wxBOTH);
wxGetApp().UpdateDlgDarkUI(this);
Bind(wxEVT_SHOW, [this](auto& e) {
if (this->IsShown()) {
if (ams_support_remain) {
m_sizer_remain_block->Show(true);
}
else {
m_sizer_remain_block->Show(false);
}
}
});
}
void AMSSetting::update_insert_material_read_mode(bool selected, std::string version)
{
if (!version.empty() && version >= AMS_F1_SUPPORT_INSERTION_UPDATE_DEFAULT) {
m_checkbox_Insert_material_auto_read->SetValue(true);
m_checkbox_Insert_material_auto_read->Hide();
m_title_Insert_material_auto_read->Hide();
m_tip_Insert_material_line1->Hide();
m_tip_Insert_material_line2->Hide();
m_tip_Insert_material_line3->Hide();
m_panel_Insert_material->Hide();
}
else {
m_panel_Insert_material->Show();
m_checkbox_Insert_material_auto_read->SetValue(selected);
m_checkbox_Insert_material_auto_read->Show();
m_title_Insert_material_auto_read->Show();
if (selected) {
m_tip_Insert_material_line1->Show();
m_tip_Insert_material_line2->Show();
m_tip_Insert_material_line3->Hide();
}
else {
m_tip_Insert_material_line1->Hide();
m_tip_Insert_material_line2->Hide();
m_tip_Insert_material_line3->Show();
}
}
m_panel_Insert_material->Layout();
m_sizer_Insert_material_tip_inline->Layout();
Layout();
Fit();
}
void AMSSetting::update_ams_img(std::string ams_icon_str)
{
if (wxGetApp().dark_mode()&& ams_icon_str=="extra_icon") {
ams_icon_str += "_dark";
}
m_am_img->SetBitmap(create_scaled_bitmap(ams_icon_str, nullptr, 126));
}
void AMSSetting::update_starting_read_mode(bool selected)
{
m_checkbox_starting_auto_read->SetValue(selected);
if (selected) { // selected
m_tip_starting_line1->Show();
m_tip_starting_line2->Hide();
} else { // unselected
m_tip_starting_line1->Hide();
m_tip_starting_line2->Show();
}
m_sizer_starting_tip_inline->Layout();
Layout();
Fit();
}
void AMSSetting::update_remain_mode(bool selected)
{
if (obj->is_support_update_remain) {
m_checkbox_remain->Show();
m_title_remain->Show();
m_tip_remain_line1->Show();
Layout();
}
else {
m_checkbox_remain->Hide();
m_title_remain->Hide();
m_tip_remain_line1->Hide();
Layout();
}
m_checkbox_remain->SetValue(selected);
}
void AMSSetting::update_switch_filament(bool selected)
{
if (obj->is_support_filament_backup) {
m_checkbox_switch_filament->Show();
m_title_switch_filament->Show();
m_tip_switch_filament_line1->Show();
Layout();
} else {
m_checkbox_switch_filament->Hide();
m_title_switch_filament->Hide();
m_tip_switch_filament_line1->Hide();
Layout();
}
m_checkbox_switch_filament->SetValue(selected);
}
void AMSSetting::update_air_printing_detection(bool selected)
{
if (false/*obj->is_support_air_print_detection*/) {
m_checkbox_air_print->Show();
m_title_air_print->Show();
m_tip_air_print_line->Show();
}
else {
m_checkbox_air_print->Hide();
m_title_air_print->Hide();
m_tip_air_print_line->Hide();
}
Layout();
m_checkbox_air_print->SetValue(selected);
}
void AMSSetting::on_select_ok(wxMouseEvent &event)
{
if (obj) {
obj->command_ams_calibrate(ams_id);
}
}
void AMSSetting::on_insert_material_read(wxCommandEvent &event)
{
// send command
if (m_checkbox_Insert_material_auto_read->GetValue()) {
// checked
m_tip_Insert_material_line1->Show();
m_tip_Insert_material_line2->Show();
m_tip_Insert_material_line3->Hide();
} else {
// unchecked
m_tip_Insert_material_line1->Hide();
m_tip_Insert_material_line2->Hide();
m_tip_Insert_material_line3->Show();
}
m_checkbox_Insert_material_auto_read->SetValue(event.GetInt());
bool start_read_opt = m_checkbox_starting_auto_read->GetValue();
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
bool remain_opt = m_checkbox_remain->GetValue();
obj->command_ams_user_settings(ams_id, start_read_opt, tray_read_opt, remain_opt);
m_sizer_Insert_material_tip_inline->Layout();
Layout();
Fit();
event.Skip();
}
void AMSSetting::on_starting_read(wxCommandEvent &event)
{
if (m_checkbox_starting_auto_read->GetValue()) {
// checked
m_tip_starting_line1->Show();
m_tip_starting_line2->Hide();
} else {
// unchecked
m_tip_starting_line1->Hide();
m_tip_starting_line2->Show();
}
m_checkbox_starting_auto_read->SetValue(event.GetInt());
bool start_read_opt = m_checkbox_starting_auto_read->GetValue();
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
bool remain_opt = m_checkbox_remain->GetValue();
obj->command_ams_user_settings(ams_id, start_read_opt, tray_read_opt, remain_opt);
m_sizer_starting_tip_inline->Layout();
Layout();
Fit();
event.Skip();
}
void AMSSetting::on_remain(wxCommandEvent& event)
{
bool start_read_opt = m_checkbox_starting_auto_read->GetValue();
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
bool remain_opt = m_checkbox_remain->GetValue();
obj->command_ams_user_settings(ams_id, start_read_opt, tray_read_opt, remain_opt);
event.Skip();
}
void AMSSetting::on_switch_filament(wxCommandEvent& event)
{
bool switch_filament = m_checkbox_switch_filament->GetValue();
obj->command_ams_switch_filament(switch_filament);
event.Skip();
}
void AMSSetting::on_air_print_detect(wxCommandEvent& event)
{
bool air_print_detect = m_checkbox_air_print->GetValue();
obj->command_ams_air_print_detect(air_print_detect);
event.Skip();
}
wxString AMSSetting::append_title(wxString text)
{
wxString lab;
auto * widget = new wxStaticText(m_panel_body, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
widget->SetForegroundColour(*wxBLACK);
widget->Wrap(AMS_SETTING_BODY_WIDTH);
widget->SetMinSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
lab = widget->GetLabel();
widget->Destroy();
return lab;
}
wxStaticText *AMSSetting::append_text(wxString text)
{
auto *widget = new wxStaticText(m_panel_body, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
widget->Wrap(250);
widget->SetMinSize(wxSize(250, -1));
return widget;
}
void AMSSetting::on_dpi_changed(const wxRect &suggested_rect)
{
//m_button_auto_demarcate->SetMinSize(AMS_SETTING_BUTTON_SIZE);
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,91 @@
#ifndef slic3r_AMSSettingDialog_hpp_
#define slic3r_AMSSettingDialog_hpp_
#include "libslic3r/Preset.hpp"
#include "wxExtensions.hpp"
#include "GUI_Utils.hpp"
#include "DeviceManager.hpp"
#include "Widgets/RadioBox.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/CheckBox.hpp"
#define AMS_SETTING_DEF_COLOUR wxColour(255, 255, 255)
#define AMS_SETTING_GREY800 wxColour(50, 58, 61)
#define AMS_SETTING_GREY700 wxColour(107, 107, 107)
#define AMS_SETTING_GREY200 wxColour(248, 248, 248)
#define AMS_SETTING_BODY_WIDTH FromDIP(380)
#define AMS_SETTING_BUTTON_SIZE wxSize(FromDIP(150), FromDIP(24))
#define AMS_F1_SUPPORT_INSERTION_UPDATE_DEFAULT std::string("00.00.07.89")
namespace Slic3r { namespace GUI {
class AMSSetting : public DPIDialog
{
public:
AMSSetting(wxWindow *parent, wxWindowID id, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE);
~AMSSetting();
void create();
void update_insert_material_read_mode(bool selected, std::string version);
void update_ams_img(std::string ams_icon_str);
void update_starting_read_mode(bool selected);
void update_remain_mode(bool selected);
void update_switch_filament(bool selected);
void update_air_printing_detection(bool selected);
void on_select_ok(wxMouseEvent& event);
void on_insert_material_read(wxCommandEvent &event);
void on_starting_read(wxCommandEvent &event);
void on_remain(wxCommandEvent& event);
void on_switch_filament(wxCommandEvent& event);
void on_air_print_detect(wxCommandEvent& event);
wxString append_title(wxString text);
wxStaticText *append_text(wxString text);
MachineObject *obj{nullptr};
bool ams_support_remain{false};
wxStaticBitmap* m_am_img;
int ams_id { 0 };
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
protected:
wxPanel * m_panel_body;
wxPanel* m_panel_Insert_material;
CheckBox * m_checkbox_Insert_material_auto_read;
wxStaticText *m_title_Insert_material_auto_read;
Label* m_tip_Insert_material_line1;
Label* m_tip_Insert_material_line2;
Label* m_tip_Insert_material_line3;
CheckBox * m_checkbox_starting_auto_read;
wxStaticText *m_title_starting_auto_read;
Label* m_tip_starting_line1;
Label* m_tip_starting_line2;
CheckBox * m_checkbox_remain;
wxStaticText *m_title_remain;
Label* m_tip_remain_line1;
CheckBox* m_checkbox_switch_filament;
wxStaticText* m_title_switch_filament;
Label* m_tip_switch_filament_line1;
CheckBox* m_checkbox_air_print;
wxStaticText* m_title_air_print;
Label* m_tip_air_print_line;
wxStaticText *m_tip_ams_img;
Button * m_button_auto_demarcate;
wxBoxSizer *m_sizer_Insert_material_tip_inline;
wxBoxSizer *m_sizer_starting_tip_inline;
wxBoxSizer *m_sizer_remain_inline;
wxBoxSizer *m_sizer_switch_filament_inline;
wxBoxSizer *m_sizer_remain_block;
};
}} // namespace Slic3r::GUI
#endif

View File

@@ -0,0 +1,440 @@
#include "AboutDialog.hpp"
#include "I18N.hpp"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "Widgets/Button.hpp"
#include <wx/clipbrd.h>
namespace Slic3r {
namespace GUI {
AboutDialogLogo::AboutDialogLogo(wxWindow* parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{
this->SetBackgroundColour(*wxWHITE);
this->logo = ScalableBitmap(this, Slic3r::var("QIDIStudio_192px.png"), wxBITMAP_TYPE_PNG);
this->SetMinSize(this->logo.GetBmpSize());
this->Bind(wxEVT_PAINT, &AboutDialogLogo::onRepaint, this);
}
void AboutDialogLogo::onRepaint(wxEvent &event)
{
wxPaintDC dc(this);
dc.SetBackgroundMode(wxTRANSPARENT);
wxSize size = this->GetSize();
int logo_w = this->logo.GetBmpWidth();
int logo_h = this->logo.GetBmpHeight();
dc.DrawBitmap(this->logo.bmp(), (size.GetWidth() - logo_w)/2, (size.GetHeight() - logo_h)/2, true);
event.Skip();
}
// -----------------------------------------
// CopyrightsDialog
// -----------------------------------------
CopyrightsDialog::CopyrightsDialog()
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, from_u8((boost::format("%1% - %2%")
% (wxGetApp().is_editor() ? SLIC3R_APP_FULL_NAME : GCODEVIEWER_APP_NAME)
% _utf8(L("Portions copyright"))).str()),
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
this->SetFont(wxGetApp().normal_font());
this->SetBackgroundColour(*wxWHITE);
std::string icon_path = (boost::format("%1%/images/QIDIStudioTitle.ico") % resources_dir()).str();
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
wxStaticLine *staticline1 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
auto sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add( staticline1, 0, wxEXPAND | wxALL, 5 );
fill_entries();
m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition,
wxSize(40 * em_unit(), 20 * em_unit()), wxHW_SCROLLBAR_AUTO);
m_html->SetMinSize(wxSize(FromDIP(870),FromDIP(520)));
m_html->SetBackgroundColour(*wxWHITE);
wxFont font = get_default_font(this);
const int fs = font.GetPointSize();
const int fs2 = static_cast<int>(1.2f*fs);
int size[] = { fs, fs, fs, fs, fs2, fs2, fs2 };
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
m_html->SetBorders(2);
m_html->SetPage(get_html_text());
sizer->Add(m_html, 1, wxEXPAND | wxALL, 15);
m_html->Bind(wxEVT_HTML_LINK_CLICKED, &CopyrightsDialog::onLinkClicked, this);
SetSizer(sizer);
sizer->SetSizeHints(this);
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
}
void CopyrightsDialog::fill_entries()
{
m_entries = {
{ "Admesh", "", "https://admesh.readthedocs.io/" },
{ "Anti-Grain Geometry", "", "http://antigrain.com" },
{ "ArcWelderLib", "", "https://plugins.octoprint.org/plugins/arc_welder" },
{ "Boost", "", "http://www.boost.org" },
{ "Cereal", "", "http://uscilab.github.io/cereal" },
{ "CGAL", "", "https://www.cgal.org" },
{ "Clipper", "", "http://www.angusj.co" },
{ "libcurl", "", "https://curl.se/libcurl" },
{ "Eigen3", "", "http://eigen.tuxfamily.org" },
{ "Expat", "", "http://www.libexpat.org" },
{ "fast_float", "", "https://github.com/fastfloat/fast_float" },
{ "GLEW (The OpenGL Extension Wrangler Library)", "", "http://glew.sourceforge.net" },
{ "GLFW", "", "https://www.glfw.org" },
{ "GNU gettext", "", "https://www.gnu.org/software/gettext" },
{ "ImGUI", "", "https://github.com/ocornut/imgui" },
{ "Libigl", "", "https://libigl.github.io" },
{ "libnest2d", "", "https://github.com/tamasmeszaros/libnest2d" },
{ "lib_fts", "", "https://www.forrestthewoods.com" },
{ "Mesa 3D", "", "https://mesa3d.org" },
{ "Miniz", "", "https://github.com/richgel999/miniz" },
{ "Nanosvg", "", "https://github.com/memononen/nanosvg" },
{ "nlohmann/json", "", "https://json.nlohmann.me" },
{ "Qhull", "", "http://qhull.org" },
{ "Open Cascade", "", "https://www.opencascade.com" },
{ "OpenGL", "", "https://www.opengl.org" },
{ "PoEdit", "", "https://poedit.net" },
{ "PrusaSlicer", "", "https://www.prusa3d.com" },
{ "Real-Time DXT1/DXT5 C compression library", "", "https://github.com/Cyan4973/RygsDXTc" },
{ "SemVer", "", "https://semver.org" },
{ "Shinyprofiler", "", "https://code.google.com/p/shinyprofiler" },
{ "SuperSlicer", "", "https://github.com/supermerill/SuperSlicer" },
{ "TBB", "", "https://www.intel.cn/content/www/cn/zh/developer/tools/oneapi/onetbb.html" },
{ "wxWidgets", "", "https://www.wxwidgets.org" },
{ "zlib", "", "http://zlib.net" },
};
}
wxString CopyrightsDialog::get_html_text()
{
wxColour bgr_clr = wxGetApp().get_window_default_clr();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
const auto text_clr = wxGetApp().get_label_clr_default();// wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
const wxString copyright_str = _(L("Copyright")) + "&copy; ";
wxString text = wxString::Format(
"<html>"
"<body bgcolor= %s link= %s>"
"<font color=%s>"
"<font size=\"5\">%s</font><br/>"
"<font size=\"5\">%s</font>"
"<a href=\"%s\">%s.</a><br/>"
"<font size=\"5\">%s.</font><br/>"
"<font size=\"5\">%s.</font><br/>"
"<br /><br />"
"<font size=\"5\">%s</font><br/>"
"<font size=\"5\">%s:</font><br/>"
"<br />"
"<font size=\"3\">",
bgr_clr_str, text_clr_str, text_clr_str,
_L("License"),
_L("QIDI Studio is licensed under "),
"https://www.gnu.org/licenses/agpl-3.0.html",_L("GNU Affero General Public License, version 3"),
// y7
_L("QIDI Studio is based on Bambu Studio by Bambu Lab"),
_L("Bambu Studio is based on PrusaSlicer by Prusa Research, which is from Slic3r by Alessandro Ranellucci and the RepRap community"),
_L("Libraries"),
_L("This software uses open source components whose copyright and other proprietary rights belong to their respective owners"));
for (auto& entry : m_entries) {
text += format_wxstr(
"%s<br/>"
, entry.lib_name);
text += wxString::Format(
"<a href=\"%s\">%s</a><br/><br/>"
, entry.link, entry.link);
}
text += wxString(
"</font>"
"</font>"
"</body>"
"</html>");
return text;
}
void CopyrightsDialog::on_dpi_changed(const wxRect &suggested_rect)
{
const wxFont& font = GetFont();
const int fs = font.GetPointSize();
const int fs2 = static_cast<int>(1.2f*fs);
int font_size[] = { fs, fs, fs, fs, fs2, fs2, fs2 };
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), font_size);
const int& em = em_unit();
msw_buttons_rescale(this, em, { wxID_CLOSE });
const wxSize& size = wxSize(40 * em, 20 * em);
m_html->SetMinSize(size);
m_html->Refresh();
SetMinSize(size);
Fit();
Refresh();
}
void CopyrightsDialog::onLinkClicked(wxHtmlLinkEvent &event)
{
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
event.Skip(false);
}
void CopyrightsDialog::onCloseDialog(wxEvent &)
{
this->EndModal(wxID_CLOSE);
}
AboutDialog::AboutDialog()
: DPIDialog(static_cast<wxWindow *>(wxGetApp().mainframe),wxID_ANY,from_u8((boost::format(_utf8(L("About %s"))) % (wxGetApp().is_editor() ? SLIC3R_APP_FULL_NAME : GCODEVIEWER_APP_NAME)).str()),wxDefaultPosition,
wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE)
{
SetFont(wxGetApp().normal_font());
SetBackgroundColour(*wxWHITE);
std::string icon_path = (boost::format("%1%/images/QIDIStudioTitle.ico") % resources_dir()).str();
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
wxPanel *m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(560), FromDIP(237)), wxTAB_TRAVERSAL);
wxBoxSizer *panel_versizer = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *vesizer = new wxBoxSizer(wxVERTICAL);
m_panel->SetSizer(panel_versizer);
wxBoxSizer *ver_sizer = new wxBoxSizer(wxVERTICAL);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_panel, 1, wxEXPAND | wxALL, 0);
main_sizer->Add(ver_sizer, 0, wxEXPAND | wxALL, 0);
// logo
m_logo_bitmap = ScalableBitmap(this, "QIDIStudio_about", 250);
m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp(), wxDefaultPosition,wxDefaultSize, 0);
m_logo->SetSizer(vesizer);
panel_versizer->Add(m_logo, 1, wxALL | wxEXPAND, 0);
//B
// version
{
vesizer->Add(0, FromDIP(165), 1, wxEXPAND, FromDIP(5));
auto version_text = GUI_App::format_display_version();
#if QDT_INTERNAL_TESTING
wxString versionText = QDT_INTERNAL_TESTING == 1 ? _L("Internal Version") : _L("Beta Version");
auto version_string = versionText + " " + std::string(version_text);
#else
auto version_string = _L("Version") + " " + std::string(version_text);
#endif
wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize);
wxFont version_font = GetFont();
#ifdef __WXMSW__
version_font.SetPointSize(version_font.GetPointSize()-1);
#else
version_font.SetPointSize(11);
#endif
version_font.SetPointSize(FromDIP(16));
version->SetFont(version_font);
version->SetForegroundColour(wxColour("#FFFFFD"));
version->SetBackgroundColour(wxColour("#4479FB"));
vesizer->Add(version, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, FromDIP(5));
#if QDT_INTERNAL_TESTING
wxString plugin_version = wxString::Format("Plugin Version: %s", wxGetApp().getAgent() ? wxGetApp().getAgent()->get_version() : "");
wxStaticText *plugin_version_text = new wxStaticText(this, wxID_ANY, plugin_version, wxDefaultPosition, wxDefaultSize);
plugin_version_text->SetForegroundColour(wxColour("#FFFFFE"));
plugin_version_text->SetBackgroundColour(wxColour("#4479FB"));
vesizer->Add(plugin_version_text, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, FromDIP(5));
wxString build_time = wxString::Format("Build Time: %s", std::string(SLIC3R_BUILD_TIME));
wxStaticText* build_time_text = new wxStaticText(this, wxID_ANY, build_time, wxDefaultPosition, wxDefaultSize);
build_time_text->SetForegroundColour(wxColour("#FFFFFE"));
build_time_text->SetBackgroundColour(wxColour("#4479FB"));
vesizer->Add(build_time_text, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, FromDIP(5));
#endif
vesizer->Add(0, 0, 1, wxEXPAND, FromDIP(5));
}
wxBoxSizer *text_sizer_horiz = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *text_sizer = new wxBoxSizer(wxVERTICAL);
text_sizer_horiz->Add( 0, 0, 0, wxLEFT, FromDIP(20));
std::vector<wxString> text_list;
// y7
text_list.push_back(_L("QIDI Studio is based on Bambu Studio by Bambu Lab."));
text_list.push_back(_L("Bambu Studio is based on PrusaSlicer by PrusaResearch and SuperSlicer by Merill(supermerill)."));
text_list.push_back(_L("PrusaSlicer is originally based on Slic3r by Alessandro Ranellucci."));
text_list.push_back(_L("Slic3r was created by Alessandro Ranellucci with the help of many other contributors."));
text_list.push_back(_L("QIDI Studio also referenced some ideas from Cura by Ultimaker."));
text_list.push_back(_L("There many parts of the software that come from community contributions, so we're unable to list them one-by-one, and instead, they'll be attributed in the corresponding code comments."));
text_sizer->Add( 0, 0, 0, wxTOP, FromDIP(33));
bool is_zh = wxGetApp().app_config->get("language") == "zh_CN";
for (int i = 0; i < text_list.size(); i++)
{
auto staticText = new wxStaticText( this, wxID_ANY, wxEmptyString,wxDefaultPosition,wxSize(FromDIP(520), -1), wxALIGN_LEFT );
staticText->SetForegroundColour(wxColour(107, 107, 107));
staticText->SetBackgroundColour(*wxWHITE);
staticText->SetMinSize(wxSize(FromDIP(520), -1));
staticText->SetFont(Label::Body_12);
if (is_zh) {
wxString find_txt = "";
wxString count_txt = "";
for (auto o = 0; o < text_list[i].length(); o++) {
auto size = staticText->GetTextExtent(count_txt);
if (size.x < FromDIP(506)) {
find_txt += text_list[i][o];
count_txt += text_list[i][o];
} else {
find_txt += std::string("\n") + text_list[i][o];
count_txt = text_list[i][o];
}
}
staticText->SetLabel(find_txt);
} else {
staticText->SetLabel(text_list[i]);
staticText->Wrap(FromDIP(520));
}
text_sizer->Add( staticText, 0, wxUP | wxDOWN, FromDIP(3));
}
text_sizer_horiz->Add(text_sizer, 1, wxALL,0);
ver_sizer->Add(text_sizer_horiz, 0, wxALL,0);
ver_sizer->Add( 0, 0, 0, wxTOP, FromDIP(43));
wxBoxSizer *copyright_ver_sizer = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *copyright_hor_sizer = new wxBoxSizer(wxHORIZONTAL);
copyright_hor_sizer->Add(copyright_ver_sizer, 0, wxLEFT, FromDIP(20));
wxStaticText *html_text = new wxStaticText(this, wxID_ANY, "Copyright(C) 2021-2024 Lunkuo All Rights Reserved", wxDefaultPosition, wxDefaultSize);
html_text->SetForegroundColour(wxColour(107, 107, 107));
copyright_ver_sizer->Add(html_text, 0, wxALL , 0);
m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER /*NEVER*/);
{
wxFont font = get_default_font(this);
const int fs = font.GetPointSize()-1;
int size[] = {fs,fs,fs,fs,fs,fs,fs};
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
m_html->SetMinSize(wxSize(FromDIP(-1), FromDIP(16)));
m_html->SetBorders(2);
const auto text = from_u8(
(boost::format(
"<html>"
"<body>"
"<p style=\"text-align:left\"><a href=\"www.qidi3d.com\">www.qidi3d.com</ a></p>"
"</body>"
"</html>")
).str());
m_html->SetPage(text);
copyright_ver_sizer->Add(m_html, 0, wxEXPAND, 0);
m_html->Bind(wxEVT_HTML_LINK_CLICKED, &AboutDialog::onLinkClicked, this);
}
//Add "Portions copyright" button
Button* button_portions = new Button(this,_L("Portions copyright"));
StateColor report_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled), std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Enabled),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
button_portions->SetBackgroundColor(report_bg);
StateColor report_bd(std::pair<wxColour, int>(wxColour(144, 144, 144), StateColor::Disabled), std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
button_portions->SetBorderColor(report_bd);
StateColor report_text(std::pair<wxColour, int>(wxColour(144, 144, 144), StateColor::Disabled), std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
button_portions->SetTextColor(report_text);
button_portions->SetFont(Label::Body_12);
button_portions->SetCornerRadius(FromDIP(12));
button_portions->SetMinSize(wxSize(FromDIP(120), FromDIP(24)));
wxBoxSizer *copyright_button_ver = new wxBoxSizer(wxVERTICAL);
copyright_button_ver->Add( 0, 0, 0, wxTOP, FromDIP(10));
copyright_button_ver->Add(button_portions, 0, wxALL,0);
copyright_hor_sizer->AddStretchSpacer();
copyright_hor_sizer->Add(copyright_button_ver, 0, wxRIGHT, FromDIP(20));
ver_sizer->Add(copyright_hor_sizer, 0, wxEXPAND ,0);
ver_sizer->Add( 0, 0, 0, wxTOP, FromDIP(30));
button_portions->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this);
wxGetApp().UpdateDlgDarkUI(this);
SetSizer(main_sizer);
Layout();
Fit();
CenterOnParent();
}
void AboutDialog::on_dpi_changed(const wxRect &suggested_rect)
{
m_logo_bitmap.msw_rescale();
m_logo->SetBitmap(m_logo_bitmap.bmp());
const wxFont& font = GetFont();
const int fs = font.GetPointSize() - 1;
int font_size[] = { fs, fs, fs, fs, fs, fs, fs };
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), font_size);
const int& em = em_unit();
msw_buttons_rescale(this, em, { wxID_CLOSE, m_copy_rights_btn_id });
m_html->SetMinSize(wxSize(-1, 16 * em));
m_html->Refresh();
const wxSize& size = wxSize(65 * em, 30 * em);
SetMinSize(size);
Fit();
Refresh();
}
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
{
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
event.Skip(false);
}
void AboutDialog::onCloseDialog(wxEvent &)
{
this->EndModal(wxID_CLOSE);
}
void AboutDialog::onCopyrightBtn(wxEvent &)
{
CopyrightsDialog dlg;
dlg.ShowModal();
}
void AboutDialog::onCopyToClipboard(wxEvent&)
{
wxTheClipboard->Open();
wxTheClipboard->SetData(new wxTextDataObject(_L("Version") + " " + GUI_App::format_display_version()));
wxTheClipboard->Close();
}
} // namespace GUI
} // namespace Slic3r

View File

@@ -0,0 +1,80 @@
#ifndef slic3r_GUI_AboutDialog_hpp_
#define slic3r_GUI_AboutDialog_hpp_
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/html/htmlwin.h>
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
namespace Slic3r {
namespace GUI {
class AboutDialogLogo : public wxPanel
{
public:
AboutDialogLogo(wxWindow* parent);
private:
ScalableBitmap logo;
void onRepaint(wxEvent &event);
};
class CopyrightsDialog : public DPIDialog
{
public:
CopyrightsDialog();
~CopyrightsDialog() {}
struct Entry {
Entry(const std::string &lib_name, const std::string &copyright, const std::string &link) :
lib_name(lib_name), copyright(copyright), link(link) {}
std::string lib_name;
std::string copyright;
std::string link;
};
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
private:
wxHtmlWindow* m_html;
std::vector<Entry> m_entries;
void onLinkClicked(wxHtmlLinkEvent &event);
void onCloseDialog(wxEvent &);
void fill_entries();
wxString get_html_text();
};
class AboutDialog : public DPIDialog
{
ScalableBitmap m_logo_bitmap;
wxHtmlWindow* m_html;
wxStaticBitmap* m_logo;
int m_copy_rights_btn_id { wxID_ANY };
int m_copy_version_btn_id { wxID_ANY };
public:
AboutDialog();
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
private:
void onLinkClicked(wxHtmlLinkEvent &event);
void onCloseDialog(wxEvent &);
void onCopyrightBtn(wxEvent &);
void onCopyToClipboard(wxEvent&);
};
} // namespace GUI
} // namespace Slic3r
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,345 @@
#ifndef slic3r_GUI_AmsMappingPopup_hpp_
#define slic3r_GUI_AmsMappingPopup_hpp_
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/collpane.h>
#include <wx/dataview.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/dataview.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/hyperlink.h>
#include <wx/button.h>
#include <wx/dialog.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/wrapsizer.h>
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
#include "DeviceManager.hpp"
#include "Plater.hpp"
#include "QDTStatusBar.hpp"
#include "QDTStatusBarSend.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/CheckBox.hpp"
#include "Widgets/ComboBox.hpp"
#include "Widgets/ScrolledWindow.hpp"
#include "Widgets/PopupWindow.hpp"
#include <wx/simplebook.h>
#include <wx/hashmap.h>
namespace Slic3r { namespace GUI {
#define MATERIAL_ITEM_SIZE wxSize(FromDIP(64), FromDIP(34))
#define MATERIAL_ITEM_REAL_SIZE wxSize(FromDIP(62), FromDIP(32))
#define MAPPING_ITEM_REAL_SIZE wxSize(FromDIP(48), FromDIP(45))
#define AMS_TOTAL_COUNT 4
enum TrayType {
NORMAL,
THIRD,
EMPTY
};
struct TrayData
{
TrayType type;
int id;
int ctype = 0;
std::string name;
std::string filament_type;
wxColour colour;
std::vector<wxColour> material_cols = std::vector<wxColour>();
};
class MaterialItem: public wxPanel
{
public:
MaterialItem(wxWindow *parent,wxColour mcolour, wxString mname);
~MaterialItem();
wxPanel* m_main_panel;
wxColour m_material_coloul;
wxString m_material_name;
wxColour m_ams_coloul;
wxString m_ams_name;
int m_ams_ctype = 0;
std::vector<wxColour> m_ams_cols = std::vector<wxColour>();
ScalableBitmap m_arraw_bitmap_gray;
ScalableBitmap m_arraw_bitmap_white;
ScalableBitmap m_transparent_mitem;
bool m_selected {false};
bool m_warning{false};
void msw_rescale();
void set_ams_info(wxColour col, wxString txt, int ctype=0, std::vector<wxColour> cols= std::vector<wxColour>());
void disable();
void enable();
void on_normal();
void on_selected();
void on_warning();
void on_left_down(wxMouseEvent &evt);
void paintEvent(wxPaintEvent &evt);
void render(wxDC &dc);
void doRender(wxDC &dc);
};
class MappingItem : public wxPanel
{
public:
MappingItem(wxWindow *parent);
~MappingItem();
void update_data(TrayData data);
void send_event(int fliament_id);
void set_tray_index(wxString t_index) {m_tray_index = t_index;};
wxString m_tray_index;
wxColour m_coloul;
wxString m_name;
TrayData m_tray_data;
ScalableBitmap m_transparent_mapping_item;
bool m_unmatch{false};
void msw_rescale();
void paintEvent(wxPaintEvent &evt);
void render(wxDC &dc);
void set_data(wxColour colour, wxString name, TrayData data, bool unmatch = false);
void doRender(wxDC &dc);
};
class MappingContainer : public wxPanel
{
public:
wxBitmap ams_mapping_item_container;
MappingContainer(wxWindow* parent);
~MappingContainer();
void paintEvent(wxPaintEvent& evt);
void render(wxDC& dc);
void doRender(wxDC& dc);
};
class AmsMapingPopup : public PopupWindow
{
public:
AmsMapingPopup(wxWindow *parent);
~AmsMapingPopup(){};
wxStaticText * m_warning_text{nullptr};
std::vector<std::string> m_materials_list;
std::vector<wxBoxSizer*> m_amsmapping_container_sizer_list;
std::vector<wxWindow*> m_amsmapping_container_list;
std::vector<MappingItem*> m_mapping_item_list;
bool m_has_unmatch_filament {false};
int m_current_filament_id;
std::string m_tag_material;
wxBoxSizer *m_sizer_main{nullptr};
wxBoxSizer *m_sizer_list{nullptr};
wxWindow *m_parent_item{nullptr};
wxString format_text(wxString &m_msg);
void update_materials_list(std::vector<std::string> list);
void set_tag_texture(std::string texture);
void update_ams_data(std::map<std::string, Ams *> amsList);
void update_ams_data_multi_machines();
void add_ams_mapping(std::vector<TrayData> tray_data, wxWindow* container, wxBoxSizer* sizer);
void set_current_filament_id(int id){m_current_filament_id = id;};
int get_current_filament_id(){return m_current_filament_id;};
bool is_match_material(std::string material);
void on_left_down(wxMouseEvent &evt);
virtual void OnDismiss() wxOVERRIDE;
virtual bool ProcessLeftDown(wxMouseEvent &event) wxOVERRIDE;
void paintEvent(wxPaintEvent &evt);
void set_parent_item(wxWindow* item) {m_parent_item = item;};
std::vector<TrayData> parse_ams_mapping(std::map<std::string, Ams*> amsList);
};
class AmsMapingTipPopup : public PopupWindow
{
public:
AmsMapingTipPopup(wxWindow *parent);
~AmsMapingTipPopup(){};
void paintEvent(wxPaintEvent &evt);
virtual void OnDismiss() wxOVERRIDE;
virtual bool ProcessLeftDown(wxMouseEvent &event) wxOVERRIDE;
public:
wxPanel * m_panel_enable_ams;
wxStaticText * m_title_enable_ams;
wxStaticText * m_tip_enable_ams;
wxPanel * m_split_lines;
wxPanel * m_panel_disable_ams;
wxStaticText * m_title_disable_ams;
wxStaticText * m_tip_disable_ams;
};
class AmsHumidityLevelList : public wxPanel
{
public:
AmsHumidityLevelList(wxWindow* parent);
~AmsHumidityLevelList() {};
ScalableBitmap background_img;
ScalableBitmap hum_level1_img;
ScalableBitmap hum_level2_img;
ScalableBitmap hum_level3_img;
ScalableBitmap hum_level4_img;
std::vector<ScalableBitmap> hum_level_img_light;
std::vector<ScalableBitmap> hum_level_img_dark;
wxStaticBitmap* hum_level1_bitmap;
wxStaticBitmap* hum_level2_bitmap;
wxStaticBitmap* hum_level3_bitmap;
wxStaticBitmap* hum_level4_bitmap;
wxStaticBitmap* hum_level5_bitmap;
void msw_rescale();
void paintEvent(wxPaintEvent& evt);
void render(wxDC& dc);
void doRender(wxDC& dc);
};
class AmsHumidityTipPopup : public PopupWindow
{
public:
AmsHumidityTipPopup(wxWindow* parent);
~AmsHumidityTipPopup() {};
void paintEvent(wxPaintEvent& evt);
virtual void OnDismiss() wxOVERRIDE;
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
void set_humidity_level(int level);
void render(wxDC& dc);
void doRender(wxDC& dc);
public:
int current_humidity_level;
ScalableBitmap close_img;
wxStaticBitmap* curr_humidity_img;
AmsHumidityLevelList* humidity_level_list{nullptr};
wxStaticBitmap* m_img;
Label* m_staticText;;
Label* m_staticText_note;
};
class AmsTutorialPopup : public PopupWindow
{
public:
Label* text_title;
wxStaticBitmap* img_top;
wxStaticBitmap* arrows_top;
wxStaticText* tip_top;
wxStaticBitmap* arrows_bottom;
wxStaticText* tip_bottom;
wxStaticBitmap* img_middle;
wxStaticText* tip_middle;
wxStaticBitmap* img_botton;
AmsTutorialPopup(wxWindow* parent);
~AmsTutorialPopup() {};
void paintEvent(wxPaintEvent& evt);
virtual void OnDismiss() wxOVERRIDE;
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
};
class AmsIntroducePopup : public PopupWindow
{
public:
bool is_enable_ams = {false};
Label* m_staticText_top;
Label* m_staticText_bottom;
wxStaticBitmap* m_img_enable_ams;
wxStaticBitmap* m_img_disable_ams;
AmsIntroducePopup(wxWindow* parent);
~AmsIntroducePopup() {};
void set_mode(bool enable_ams);
void paintEvent(wxPaintEvent& evt);
virtual void OnDismiss() wxOVERRIDE;
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
};
class AmsRMGroup : public wxWindow
{
public:
AmsRMGroup(wxWindow* parent, std::map<std::string, wxColour> group_info, wxString mname, wxString group_index);
~AmsRMGroup() {};
public:
void set_index(std::string index) {m_selected_index = index;};
void paintEvent(wxPaintEvent& evt);
void render(wxDC& dc);
void doRender(wxDC& dc);
void on_mouse_move(wxMouseEvent& evt);
double GetAngle(wxPoint pointA, wxPoint pointB);
wxPoint CalculateEndpoint(const wxPoint& startPoint, int angle, int length);
private:
std::map<std::string, wxColour> m_group_info;
std::string m_selected_index;
ScalableBitmap backup_current_use_white;
ScalableBitmap backup_current_use_black;
ScalableBitmap bitmap_backup_tips_0;
ScalableBitmap bitmap_backup_tips_1;
ScalableBitmap bitmap_editable;
ScalableBitmap bitmap_bg;
ScalableBitmap bitmap_editable_light;
wxString m_material_name;
wxString m_group_index;
};
class AmsReplaceMaterialDialog : public DPIDialog
{
public:
AmsReplaceMaterialDialog(wxWindow* parent);
~AmsReplaceMaterialDialog() {};
public:
AmsRMGroup* create_backup_group(wxString gname, std::map<std::string, wxColour> group_info, wxString material, std::vector<bool> status_list);
void create();
void update_machine_obj(MachineObject* obj);
void update_mapping_result(std::vector<FilamentInfo> result);
void paintEvent(wxPaintEvent& evt);
void on_dpi_changed(const wxRect& suggested_rect) override;
std::vector<bool> GetStatus(unsigned int status);
public:
wxScrolledWindow* m_scrollview_groups{ nullptr };
wxBoxSizer* m_scrollview_sizer{ nullptr };
wxBoxSizer* m_main_sizer{ nullptr };
wxWrapSizer* m_groups_sizer{ nullptr };
MachineObject* m_obj{ nullptr };
std::vector<std::string> m_tray_used;
Label* label_txt{nullptr};
};
wxDECLARE_EVENT(EVT_SET_FINISH_MAPPING, wxCommandEvent);
}} // namespace Slic3r::GUI
#endif

View File

@@ -0,0 +1,199 @@
#include "AmsWidgets.hpp"
#include <wx/button.h>
#include <wx/sizer.h>
#include <wx/dataview.h>
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "Plater.hpp"
#include "MainFrame.hpp"
#include "Widgets/Label.hpp"
#include "format.hpp"
namespace Slic3r {
namespace GUI {
TrayListModel::TrayListModel() :
wxDataViewVirtualListModel(0)
{
;
}
void TrayListModel::GetValueByRow(wxVariant& variant,
unsigned int row, unsigned int col) const
{
switch (col) {
case Col_TrayTitle:
if (row >= m_titleColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_titleColValues[row];
break;
case Col_TrayColor:
if (row >= m_colorColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_colorColValues[row];
break;
case Col_TrayMeterial:
if (row >= m_meterialColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_meterialColValues[row];
break;
case Col_TrayWeight:
if (row >= m_weightColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_weightColValues[row];
break;
case Col_TrayDiameter:
if (row >= m_diameterColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_diameterColValues[row];
break;
case Col_TrayTime:
if (row >= m_timeColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_timeColValues[row];
break;
case Col_TraySN:
if (row >= m_snColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_snColValues[row];
break;
case Col_TrayManufacturer:
if (row >= m_manufacturerColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_manufacturerColValues[row];
break;
case Col_TraySaturability:
if (row >= m_saturabilityColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_saturabilityColValues[row];
break;
case Col_TrayTransmittance:
if (row >= m_transmittanceColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_transmittanceColValues[row];
break;
case Col_TraySmooth:
if (row >= m_smoothColValues.GetCount())
variant = wxString::Format("N/A", row);
else
variant = m_smoothColValues[row];
break;
default:
break;
}
}
bool TrayListModel::GetAttrByRow(unsigned int row, unsigned int col,
wxDataViewItemAttr& attr) const
{
return true;
}
bool TrayListModel::SetValueByRow(const wxVariant& variant,
unsigned int row, unsigned int col)
{
switch (col)
{
case Col_TrayTitle:
case Col_TrayColor:
case Col_TrayMeterial:
case Col_TrayWeight:
case Col_TrayDiameter:
case Col_TrayTime:
case Col_TraySN:
case Col_TrayManufacturer:
case Col_TraySaturability:
case Col_TrayTransmittance:
case Col_TraySmooth:
return true;
default:
break;
}
return false;
}
void TrayListModel::update(MachineObject* obj)
{
if (!obj) return;
m_titleColValues.clear();
m_colorColValues.clear();
m_meterialColValues.clear();
m_weightColValues.clear();
m_diameterColValues.clear();
m_timeColValues.clear();
m_snColValues.clear();
m_manufacturerColValues.clear();
m_saturabilityColValues.clear();
m_transmittanceColValues.clear();
std::map<std::string, Ams*>::iterator ams_it;
std::map<std::string, AmsTray*>::iterator tray_it;
int tray_index = 0;
for (ams_it = obj->amsList.begin(); ams_it != obj->amsList.end(); ams_it++) {
if (ams_it->second) {
for (tray_it = ams_it->second->trayList.begin(); tray_it != ams_it->second->trayList.end(); tray_it++) {
AmsTray* tray = tray_it->second;
if (tray) {
tray_index++;
wxString title_text = wxString::Format("tray %s(ams %s)", tray->id, ams_it->second->id);
m_titleColValues.push_back(title_text);
wxString color_text = wxString::Format("%s", tray->wx_color.GetAsString());
m_colorColValues.push_back(color_text);
wxString meterial_text = wxString::Format("%s", tray->type);
m_meterialColValues.push_back(meterial_text);
wxString weight_text = wxString::Format("%sg", tray->weight);
m_weightColValues.push_back(weight_text);
wxString diameter_text = wxString::Format("%0.2f", tray->diameter);
m_diameterColValues.push_back(diameter_text);
wxString time_text = wxString::Format("%s", tray->time);
m_timeColValues.push_back(time_text);
wxString sn_text = wxString::Format("%s", tray->uuid);
m_snColValues.push_back(sn_text);
wxString manufacturer_text = wxString::Format("%s", tray->sub_brands);
m_manufacturerColValues.push_back(manufacturer_text);
// TODO:
//wxString saturability_text = wxString::Format("%s", tray->saturability);
//m_saturabilityColValues.push_back(saturability_text);
//wxString transmittance_text = wxString::Format("%s", tray->transmittance);
//m_transmittanceColValues.push_back(transmittance_text);
//wxString smooth_text = wxString::Format("%s", tray->smooth);
//m_smoothColValues.push_back(smooth_text);
}
}
}
}
Reset(m_titleColValues.GetCount());
}
void TrayListModel::clear_data()
{
m_titleColValues.clear();
m_colorColValues.clear();
m_meterialColValues.clear();
m_weightColValues.clear();
m_diameterColValues.clear();
m_timeColValues.clear();
m_snColValues.clear();
m_manufacturerColValues.clear();
m_saturabilityColValues.clear();
m_transmittanceColValues.clear();
m_smoothColValues.clear();
Reset(0);
}
} // GUI
} // Slic3r

View File

@@ -0,0 +1,106 @@
#ifndef slic3r_AmsWidgets_hpp_
#define slic3r_AmsWidgets_hpp_
#include <wx/notebook.h>
#include <wx/scrolwin.h>
#include <wx/sizer.h>
#include <wx/bmpcbox.h>
#include <wx/bmpbuttn.h>
#include <wx/treectrl.h>
#include <wx/imaglist.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/stattext.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/grid.h>
#include <wx/dataview.h>
#include <wx/panel.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/bmpbuttn.h>
#include <wx/button.h>
#include <wx/statbox.h>
#include <wx/tglbtn.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <map>
#include <vector>
#include <memory>
#include "Event.hpp"
#include "libslic3r/ProjectTask.hpp"
#include "wxExtensions.hpp"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r {
namespace GUI {
class TrayListModel : public wxDataViewVirtualListModel
{
public:
enum
{
Col_TrayTitle,
Col_TrayColor,
Col_TrayMeterial,
Col_TrayWeight,
Col_TrayDiameter,
Col_TrayTime,
Col_TraySN,
Col_TrayManufacturer,
Col_TraySaturability,
Col_TrayTransmittance,
Col_TraySmooth,
Col_Max,
};
TrayListModel();
virtual unsigned int GetColumnCount() const wxOVERRIDE
{
return Col_Max;
}
virtual wxString GetColumnType(unsigned int col) const wxOVERRIDE
{
return "string";
}
virtual void GetValueByRow(wxVariant& variant,
unsigned int row, unsigned int col) const wxOVERRIDE;
virtual bool GetAttrByRow(unsigned int row, unsigned int col,
wxDataViewItemAttr& attr) const wxOVERRIDE;
virtual bool SetValueByRow(const wxVariant& variant,
unsigned int row, unsigned int col) wxOVERRIDE;
void update(MachineObject* obj);
void clear_data();
private:
wxArrayString m_titleColValues;
wxArrayString m_colorColValues;
wxArrayString m_meterialColValues;
wxArrayString m_weightColValues;
wxArrayString m_diameterColValues;
wxArrayString m_timeColValues;
wxArrayString m_snColValues;
wxArrayString m_manufacturerColValues;
wxArrayString m_saturabilityColValues;
wxArrayString m_transmittanceColValues;
wxArrayString m_smoothColValues;
};
} // GUI
} // Slic3r
#endif /* slic3r_Tab_hpp_ */

1233
src/slic3r/GUI/Auxiliary.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,247 @@
#ifndef slic3r_Auxiliary_hpp_
#define slic3r_Auxiliary_hpp_
#include "Tabbook.hpp"
#include <wx/notebook.h>
#include <wx/scrolwin.h>
#include <wx/sizer.h>
#include <wx/bmpcbox.h>
#include <wx/bmpbuttn.h>
#include <wx/treectrl.h>
#include <wx/imaglist.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/stattext.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/grid.h>
#include <wx/dataview.h>
#include <wx/panel.h>
#include <wx/statline.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/bmpbuttn.h>
#include <wx/button.h>
#include <wx/gbsizer.h>
#include <wx/statbox.h>
#include <wx/tglbtn.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/webrequest.h>
#include <map>
#include <vector>
#include <memory>
#include "Event.hpp"
#include "libslic3r/ProjectTask.hpp"
#include "wxExtensions.hpp"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/MonitorBasePanel.h"
#include "slic3r/GUI/StatusPanel.hpp"
#include "slic3r/GUI/UpgradePanel.hpp"
#include "slic3r/GUI/AmsWidgets.hpp"
#include "Widgets/SideTools.hpp"
#define AUFILE_GREY700 wxColour(107, 107, 107)
#define AUFILE_GREY500 wxColour(158, 158, 158)
#define AUFILE_GREY300 wxColour(238, 238, 238)
#define AUFILE_GREY200 wxColour(248, 248, 248)
#define AUFILE_BRAND wxColour(68, 121, 251)
#define AUFILE_BRAND_TRANSPARENT wxColour(215, 232, 222)
//#define AUFILE_PICTURES_SIZE wxSize(FromDIP(300), FromDIP(300))
//#define AUFILE_PICTURES_PANEL_SIZE wxSize(FromDIP(300), FromDIP(340))
#define AUFILE_PICTURES_SIZE wxSize(FromDIP(168), FromDIP(168))
#define AUFILE_PICTURES_PANEL_SIZE wxSize(FromDIP(168), FromDIP(208))
#define AUFILE_SIZE wxSize(FromDIP(168), FromDIP(168))
#define AUFILE_PANEL_SIZE wxSize(FromDIP(168), FromDIP(208))
#define AUFILE_TEXT_HEIGHT FromDIP(40)
#define AUFILE_ROUNDING FromDIP(5)
enum AuxiliaryFolderType {
MODEL_PICTURE,
BILL_OF_MATERIALS,
ASSEMBLY_GUIDE,
OTHERS,
THUMBNAILS,
DESIGNER,
AddFileButton,
};
const static std::array<wxString, 5> s_default_folders = {("Model Pictures"), ("Bill of Materials"), ("Assembly Guide"), ("Others"), (".thumbnails")};
enum ValidationType { Valid, NoValid, Warning };
namespace Slic3r { namespace GUI {
class AuFile : public wxPanel
{
public:
AuxiliaryFolderType m_type;
bool m_hover{false};
bool m_cover{false};
wxStaticText* m_text_name {nullptr};
::TextInput* m_input_name {nullptr};
fs::path m_file_path;
wxString m_add_file;
wxString m_file_name;
wxString cover_text_left;
wxString cover_text_right;
wxString cover_text_cover;
ScalableBitmap m_file_bitmap;
ScalableBitmap m_file_cover;
ScalableBitmap m_file_edit_mask;
ScalableBitmap m_file_delete;
wxStaticBitmap* m_file_exit_rename;
ScalableBitmap m_bitmap_excel;
ScalableBitmap m_bitmap_pdf;
ScalableBitmap m_bitmap_txt;
public:
AuFile(wxWindow *parent, fs::path file_path, wxString file_name, AuxiliaryFolderType type, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
void enter_rename_mode();
void exit_rename_mode();
void OnPaint(wxPaintEvent &evt);
void PaintBackground(wxDC &dc);
void OnEraseBackground(wxEraseEvent &evt);
void PaintForeground(wxDC &dc);
void on_mouse_enter(wxMouseEvent &evt);
void on_mouse_leave(wxMouseEvent &evt);
void on_input_enter(wxCommandEvent& evt);
void on_dclick(wxMouseEvent &evt);
void on_mouse_left_up(wxMouseEvent &evt);
void on_set_cover();
void on_set_delete();
void on_set_rename();
void on_set_open();
void set_cover(bool cover);
void msw_rescale();
~AuFile();
};
class AuFiles
{
public:
wxString path;
AuFile * file;
};
WX_DEFINE_ARRAY(AuFiles *, AuFilesHash);
class AuFolderPanel : public wxPanel
{
public:
AuFolderPanel(wxWindow * parent,
AuxiliaryFolderType type,
wxWindowID id = wxID_ANY,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
~AuFolderPanel();
void clear();
void update_cover();
void update(std::vector<fs::path> paths);
void msw_rescale();
public:
AuxiliaryFolderType m_type;
wxScrolledWindow * m_scrolledWindow{nullptr};
wxWrapSizer * m_gsizer_content{nullptr};
//AuFile * m_button_add{nullptr};
Button * m_button_del{nullptr};
AuFile * m_big_button_add{ nullptr };
AuFilesHash m_aufiles_list;
void on_add(wxMouseEvent& event);
void on_delete(wxCommandEvent &event);
};
class DesignerPanel : public wxPanel
{
public:
DesignerPanel(wxWindow * parent,
AuxiliaryFolderType type,
wxWindowID id = wxID_ANY,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
~DesignerPanel();
::TextInput* m_input_designer {nullptr};
::TextInput* m_imput_model_name {nullptr};
//wxComboBox* m_combo_license {nullptr};
bool Show(bool show) override;
void init_license_list();
void on_input_enter_designer(wxCommandEvent &evt);
void on_input_enter_model(wxCommandEvent &evt);
void on_select_license(wxCommandEvent& evt);
void update_info();
void msw_rescale();
};
class AuxiliaryPanel : public wxPanel
{
private:
Tabbook *m_tabpanel = {nullptr};
wxSizer *m_main_sizer = {nullptr};
AuFolderPanel *m_pictures_panel= {nullptr};
AuFolderPanel *m_bill_of_materials_panel= {nullptr};
AuFolderPanel *m_assembly_panel= {nullptr};
AuFolderPanel *m_others_panel= {nullptr};
DesignerPanel * m_designer_panel= {nullptr};
/* images */
wxBitmap m_signal_strong_img;
wxBitmap m_signal_middle_img;
wxBitmap m_signal_weak_img;
wxBitmap m_signal_no_img;
wxBitmap m_printer_img;
wxBitmap m_arrow_img;
wxWindow *create_side_tools();
public:
AuxiliaryPanel(wxWindow *parent, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
~AuxiliaryPanel();
void init_bitmap();
void init_tabpanel();
void Split(const std::string &src, const std::string &separator, std::vector<std::string> &dest);
void msw_rescale();
void on_size(wxSizeEvent &event);
bool Show(bool show);
// core logic
std::map<std::string, std::vector<fs::path>> m_paths_list;
wxString m_root_dir;
void init_auxiliary();
void create_folder(wxString name = wxEmptyString);
std::string replaceSpace(std::string s, std::string ts, std::string ns);
void on_import_file(wxCommandEvent &event);
void Reload(wxString aux_path);
void update_all_panel();
void update_all_cover();
};
wxDECLARE_EVENT(EVT_AUXILIARY_IMPORT, wxCommandEvent);
wxDECLARE_EVENT(EVT_AUXILIARY_UPDATE_COVER, wxCommandEvent);
wxDECLARE_EVENT(EVT_AUXILIARY_UPDATE_DELETE, wxCommandEvent);
wxDECLARE_EVENT(EVT_AUXILIARY_UPDATE_RENAME, wxCommandEvent);
}} // namespace Slic3r::GUI
#endif

View File

@@ -0,0 +1,493 @@
#include "AuxiliaryDataViewModel.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/qds_3mf.hpp"
#include <boost/log/trivial.hpp>
#include <wx/log.h>
const static std::array<wxString, 4> s_default_folders = {
_L("Model Pictures"),
_L("Bill of Materials"),
_L("Assembly Guide"),
_L("Others")
};
AuxiliaryModel::AuxiliaryModel()
{
m_root = nullptr;
}
void AuxiliaryModel::Init(wxString aux_path)
{
m_root = new AuxiliaryModelNode();
m_root_dir = aux_path;
if (wxDirExists(m_root_dir)) {
fs::path path_to_del(m_root_dir.ToStdWstring());
try {
fs::remove_all(path_to_del);
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the auxiliary directory " << m_root_dir.c_str();
}
}
fs::path top_dir_path(m_root_dir.ToStdWstring());
fs::create_directory(top_dir_path);
for (auto folder : s_default_folders)
CreateFolder(folder);
}
AuxiliaryModel::~AuxiliaryModel()
{
if (wxDirExists(m_root_dir)) {
fs::path path_to_del(m_root_dir.ToStdWstring());
try {
fs::remove_all(path_to_del);
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the auxiliary directory " << m_root_dir.c_str();
}
m_root_dir = "";
}
delete m_root;
m_root = nullptr;
}
void AuxiliaryModel::Reload(wxString aux_path)
{
fs::path new_aux_path(aux_path.ToStdWstring());
// Clean
try {
fs::remove_all(fs::path(m_root_dir.ToStdWstring()));
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the auxiliary directory " << m_root_dir.c_str();
}
if (m_root) {
delete m_root;
m_root = nullptr;
}
Cleared();
// Create new root.
m_root = new AuxiliaryModelNode();
m_root_dir = aux_path;
// Check new path. If not exist, create a new one.
if (!fs::exists(new_aux_path)) {
fs::create_directory(new_aux_path);
// Create default folders if they are not loaded
wxDataViewItemArray default_items;
for (auto folder : s_default_folders) {
wxString folder_path = aux_path + "\\" + folder;
if (fs::exists(folder_path.ToStdWstring())) continue;
fs::create_directory(folder_path.ToStdWstring());
AuxiliaryModelNode *node = new AuxiliaryModelNode(m_root,
folder_path,
true);
default_items.Add(wxDataViewItem(node));
}
ItemsAdded(wxDataViewItem(nullptr), default_items);
return;
}
// Load from new path
std::map<fs::path, AuxiliaryModelNode *> dir_cache;
fs::directory_iterator iter_end;
wxDataViewItemArray items;
for (fs::directory_iterator iter(new_aux_path); iter != iter_end; iter++) {
wxString path = iter->path().generic_wstring();
AuxiliaryModelNode* node = new AuxiliaryModelNode(m_root, path, fs::is_directory(iter->path()));
items.Add(wxDataViewItem(node));
if (node->IsContainer()) {
dir_cache.insert({ iter->path(), node });
}
}
ItemsAdded(wxDataViewItem(nullptr), items);
items.Clear();
for (auto dir : dir_cache) {
for (fs::directory_iterator iter(dir.first); iter != iter_end; iter++) {
if (fs::is_directory(iter->path()))
continue;
wxString file_path = iter->path().generic_wstring();
AuxiliaryModelNode* file = new AuxiliaryModelNode(dir.second, file_path, false);
items.Add(wxDataViewItem(file));
}
ItemsAdded(wxDataViewItem(dir.second), items);
}
// Create default folders if they are not loaded
wxDataViewItemArray default_items;
for (auto folder : s_default_folders) {
wxString folder_path = aux_path + "\\" + folder;
if (fs::exists(folder_path.ToStdWstring()))
continue;
fs::create_directory(folder_path.ToStdWstring());
AuxiliaryModelNode* node = new AuxiliaryModelNode(m_root, folder_path, true);
default_items.Add(wxDataViewItem(node));
}
ItemsAdded(wxDataViewItem(nullptr), default_items);
}
int AuxiliaryModel::Compare(const wxDataViewItem& item1, const wxDataViewItem& item2,
unsigned int column, bool ascending) const
{
wxASSERT(item1.IsOk() && item2.IsOk());
// should never happen
if (IsContainer(item1) && IsContainer(item2))
{
wxVariant value1, value2;
GetValue(value1, item1, 0);
GetValue(value2, item2, 0);
wxString str1 = value1.GetString();
wxString str2 = value2.GetString();
int res = str1.Cmp(str2);
if (res) return res;
// items must be different
wxUIntPtr litem1 = (wxUIntPtr)item1.GetID();
wxUIntPtr litem2 = (wxUIntPtr)item2.GetID();
return litem1 - litem2;
}
return wxDataViewModel::Compare(item1, item2, column, ascending);
}
void AuxiliaryModel::GetValue(wxVariant& variant,
const wxDataViewItem& item, unsigned int col) const
{
wxASSERT(item.IsOk());
AuxiliaryModelNode* node = (AuxiliaryModelNode*)item.GetID();
switch (col)
{
case 0:
variant = node->name;
break;
default:
wxLogError("AuxiliaryModel::GetValue: wrong column %d", col);
}
}
bool AuxiliaryModel::SetValue(const wxVariant& variant,
const wxDataViewItem& item, unsigned int col)
{
wxASSERT(item.IsOk());
AuxiliaryModelNode* node = (AuxiliaryModelNode*)item.GetID();
switch (col)
{
case 0:
node->name = variant.GetString();
return true;
default:
wxLogError("AuxiliaryModel::SetValue: wrong column");
}
return false;
}
bool AuxiliaryModel::IsEnabled(const wxDataViewItem& item,
unsigned int col) const
{
return true;
}
wxDataViewItem AuxiliaryModel::GetParent(const wxDataViewItem& item) const
{
AuxiliaryModelNode* node = (AuxiliaryModelNode*)item.GetID();
return wxDataViewItem(GetParent(node));
}
bool AuxiliaryModel::IsContainer(const wxDataViewItem& item) const
{
// the invisible root node can have children
// (in our model always "MyMusic")
if (!item.IsOk())
return true;
AuxiliaryModelNode* node = (AuxiliaryModelNode*)item.GetID();
return node->IsContainer();
}
static unsigned int count = 0;
unsigned int AuxiliaryModel::GetChildren(const wxDataViewItem& parent,
wxDataViewItemArray& array) const
{
if (m_root == nullptr)
return 0;
AuxiliaryModelNode* node = (AuxiliaryModelNode*)parent.GetID();
if (!node)
{
node = m_root;
}
count = node->GetChildren().GetCount();
for (unsigned int pos = 0; pos < count; pos++)
{
AuxiliaryModelNode* child = node->GetChildren().Item(pos);
array.Add(wxDataViewItem((void*)child));
}
return count;
}
wxDataViewItem AuxiliaryModel::CreateFolder(wxString name)
{
wxString folder_name = name;
if (folder_name == wxEmptyString) {
folder_name = _L("New Folder");
for (int i = 1; i <= 1000; i++) {
bool exist = false;
for (AuxiliaryModelNode* node : m_root->GetChildren()) {
if (!node->IsContainer())
continue;
if (node->name == folder_name) {
exist = true;
break;
}
}
if (!exist)
break;
folder_name = _L("New Folder");
folder_name << "(" << i << ")";
}
}
else {
for (AuxiliaryModelNode* node : m_root->GetChildren()) {
if (!node->IsContainer())
continue;
if (node->name == folder_name) {
return wxDataViewItem(nullptr);
}
}
}
// Create folder in file system
fs::path bfs_path((m_root_dir + "\\" + folder_name).ToStdWstring());
if (fs::exists(bfs_path)) {
try {
bool is_done = fs::remove_all(bfs_path);
if (!is_done)
return wxDataViewItem(nullptr);
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the auxiliary directory " << m_root_dir.c_str();
}
}
fs::create_directory(bfs_path);
// Create model node
AuxiliaryModelNode* folder = new AuxiliaryModelNode(m_root, bfs_path.generic_wstring(), true);
// Notify wxDataViewCtrl to update ui
wxDataViewItem folder_item(folder);
ItemAdded(wxDataViewItem(NULL), folder_item);
return folder_item;
}
wxDataViewItemArray AuxiliaryModel::ImportFile(AuxiliaryModelNode* sel, wxArrayString file_paths)
{
if (sel == nullptr) {
sel = m_root;
}
wxDataViewItemArray added_items;
AuxiliaryModelNode* parent = sel->IsContainer() ? sel : sel->GetParent();
for (wxString file_path : file_paths) {
bool exists = false;
for (AuxiliaryModelNode* node : parent->GetChildren()) {
if (node->path == file_path) {
exists = true;
break;
}
}
if (exists)
continue;
// Copy imported file to project temp directory
fs::path src_bfs_path(file_path.ToStdWstring());
wxString dir_path = m_root_dir;
if (sel != m_root)
dir_path += "\\" + sel->name;
dir_path += "\\" + src_bfs_path.filename().generic_wstring();
boost::system::error_code ec;
if (!fs::copy_file(src_bfs_path, fs::path(dir_path.ToStdWstring()), fs::copy_option::overwrite_if_exists, ec))
continue;
// Update model data
AuxiliaryModelNode* file = new AuxiliaryModelNode(parent, dir_path, false);
// Notify wxDataViewCtrl to update ui
wxDataViewItem file_item(file);
if (parent == m_root)
parent = nullptr;
Slic3r::put_other_changes();
wxDataViewItem parent_item(parent);
ItemAdded(parent_item, file_item);
added_items.push_back(file_item);
}
return added_items;
}
void AuxiliaryModel::Delete(const wxDataViewItem& item)
{
AuxiliaryModelNode* node = (AuxiliaryModelNode*)item.GetID();
if (!node) // happens if item.IsOk()==false
return;
bool is_done = false;
if (node->IsContainer()) {
fs::path bfs_path((m_root_dir + "\\" + node->name).ToStdWstring());
try {
is_done = fs::remove_all(bfs_path);
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the auxiliary directory " << m_root_dir.c_str();
}
}
else {
fs::path bfs_path(node->path.ToStdWstring());
is_done = fs::remove(bfs_path);
}
if (!is_done)
return;
node->GetParent()->GetChildren().Remove(node);
Slic3r::put_other_changes();
wxDataViewItem parent_item = GetParent(item);
ItemDeleted(parent_item, item);
delete node;
}
void AuxiliaryModel::MoveItem(const wxDataViewItem& dropped_item, const wxDataViewItem& dragged_item)
{
AuxiliaryModelNode* dropped = (AuxiliaryModelNode*)dropped_item.GetID();
AuxiliaryModelNode* dragged = (AuxiliaryModelNode*)dragged_item.GetID();
if (dragged == nullptr || dragged->IsContainer())
return;
AuxiliaryModelNode* target_folder = nullptr;
if (dropped == nullptr) {
target_folder = m_root;
}
else if (dropped->IsContainer()) {
target_folder = dropped;
}
else {
target_folder = dropped->GetParent();
}
if (dragged->GetParent() == target_folder)
return;
for (AuxiliaryModelNode* node : target_folder->GetChildren()) {
if (node->path == dragged->path)
return;
}
// Generate new path
wxString new_path = m_root_dir;
if (target_folder != m_root)
new_path += "\\" + target_folder->name;
new_path += "\\" + dragged->name;
// Perform file movement in file system
fs::path bfs_new_path(new_path.ToStdWstring());
fs::path bfs_old_path(dragged->path.ToStdWstring());
boost::system::error_code err;
fs::rename(bfs_old_path, bfs_new_path, err);
if (err.failed())
return;
// Reparent dragged node
wxDataViewItem old_parent_item = this->GetParent(dragged_item);
this->Reparent(dragged, target_folder);
dragged->path = new_path;
// Notify wxDataViewCtrl to update ui
Slic3r::put_other_changes();
ItemDeleted(old_parent_item, wxDataViewItem(dragged));
ItemAdded(wxDataViewItem(target_folder == m_root ? nullptr : target_folder), wxDataViewItem(dragged));
}
bool AuxiliaryModel::IsOrphan(const wxDataViewItem& item)
{
AuxiliaryModelNode* node = (AuxiliaryModelNode*)item.GetID();
return node->GetParent() != m_root;
}
bool AuxiliaryModel::Rename(const wxDataViewItem& item, const wxString& name)
{
AuxiliaryModelNode* node = (AuxiliaryModelNode*)item.GetID();
AuxiliaryModelNode* parent = node->GetParent();
if (node->IsContainer())
return false;
if (!parent->IsContainer())
return false;
for (AuxiliaryModelNode* cur_node : parent->GetChildren()) {
if (cur_node->name == name)
return false;
}
boost::system::error_code err;
fs::path old_path((m_root_dir + "\\" + parent->name + "\\" + node->name).ToStdWstring());
fs::path new_path((m_root_dir + "\\" + parent->name + "\\" + name).ToStdWstring());
fs::rename(old_path, new_path, err);
if (err.failed())
return false;
Slic3r::put_other_changes();
node->name = name;
node->path = m_root_dir + "\\" + parent->name + "\\" + name;
return true;
}
AuxiliaryModelNode* AuxiliaryModel::GetParent(AuxiliaryModelNode* node) const
{
if (node == m_root || node->GetParent() == m_root)
return nullptr;
return node->GetParent();
}
void AuxiliaryModel::Reparent(AuxiliaryModelNode* node, AuxiliaryModelNode* new_parent)
{
if (node->IsContainer())
return;
node->GetParent()->GetChildren().Remove(node);
node->SetParent(new_parent);
new_parent->Append(node);
}

View File

@@ -0,0 +1,155 @@
#ifndef slic3r_GUI_AuxiliaryDataViewModel_hpp_
#define slic3r_GUI_AuxiliaryDataViewModel_hpp_
#include "wx/wxprec.h"
#include "wx/dataview.h"
#include "wx/hashmap.h"
#include "wx/vector.h"
#include "I18N.hpp"
#include <boost/filesystem.hpp>
class AuxiliaryModelNode;
WX_DEFINE_ARRAY_PTR(AuxiliaryModelNode*, AuxiliaryModelNodePtrArray);
namespace fs = boost::filesystem;
class AuxiliaryModelNode
{
public:
AuxiliaryModelNode()
{
m_parent = NULL;
name = "";
m_container = true;
m_root = true;
}
AuxiliaryModelNode(AuxiliaryModelNode* parent, const wxString& abs_path, bool is_container)
{
m_parent = parent;
m_container = is_container;
m_root = false;
path = abs_path;
fs::path path_obj(path.c_str());
name = path_obj.filename().generic_wstring();
parent->Append(this);
}
~AuxiliaryModelNode()
{
// free all our children nodes
size_t count = m_children.GetCount();
for (size_t i = 0; i < count; i++)
{
AuxiliaryModelNode* child = m_children[i];
delete child;
}
}
bool IsContainer() const
{
return m_container;
}
AuxiliaryModelNode* GetParent()
{
return m_parent;
}
void SetParent(AuxiliaryModelNode* parent)
{
m_parent = parent;
}
AuxiliaryModelNodePtrArray& GetChildren()
{
return m_children;
}
AuxiliaryModelNode* GetNthChild(unsigned int n)
{
return m_children.Item(n);
}
void Insert(AuxiliaryModelNode* child, unsigned int n)
{
m_children.Insert(child, n);
}
void Append(AuxiliaryModelNode* child)
{
m_children.Add(child);
}
unsigned int GetChildCount() const
{
return m_children.GetCount();
}
public:
wxString name;
wxString path;
private:
AuxiliaryModelNode* m_parent;
AuxiliaryModelNodePtrArray m_children;
bool m_container;
bool m_root;
};
class AuxiliaryModel : public wxDataViewModel
{
public:
AuxiliaryModel();
~AuxiliaryModel();
// helper methods to change the model
wxDataViewItem CreateFolder(wxString name = wxEmptyString);
wxDataViewItemArray ImportFile(AuxiliaryModelNode* sel, wxArrayString file_paths);
void Delete(const wxDataViewItem& item);
void MoveItem(const wxDataViewItem& dropped_item, const wxDataViewItem& dragged_item);
bool IsOrphan(const wxDataViewItem& item);
bool Rename(const wxDataViewItem& item, const wxString& name);
AuxiliaryModelNode* GetParent(AuxiliaryModelNode* node) const;
void Reparent(AuxiliaryModelNode* node, AuxiliaryModelNode* new_parent);
void Init(wxString aux_path);
void Reload(wxString aux_path);
// override sorting to always sort branches ascendingly
int Compare(const wxDataViewItem& item1, const wxDataViewItem& item2,
unsigned int column, bool ascending) const wxOVERRIDE;
// implementation of base class virtuals to define model
virtual unsigned int GetColumnCount() const wxOVERRIDE
{
return 1;
}
virtual wxString GetColumnType(unsigned int col) const wxOVERRIDE
{
return "string";
}
virtual void GetValue(wxVariant& variant,
const wxDataViewItem& item, unsigned int col) const wxOVERRIDE;
virtual bool SetValue(const wxVariant& variant,
const wxDataViewItem& item, unsigned int col) wxOVERRIDE;
virtual bool IsEnabled(const wxDataViewItem& item,
unsigned int col) const wxOVERRIDE;
virtual wxDataViewItem GetParent(const wxDataViewItem& item) const wxOVERRIDE;
virtual bool IsContainer(const wxDataViewItem& item) const wxOVERRIDE;
virtual unsigned int GetChildren(const wxDataViewItem& parent,
wxDataViewItemArray& array) const wxOVERRIDE;
private:
AuxiliaryModelNode* m_root;
wxString m_root_dir;
};
#endif // slic3r_GUI_AuxiliaryDataViewModel_hpp_

View File

@@ -0,0 +1,38 @@
#include "AuxiliaryDialog.hpp"
#include "I18N.hpp"
#include "GUI_AuxiliaryList.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/property_tree/ptree.hpp>
namespace pt = boost::property_tree;
typedef pt::ptree JSON;
namespace Slic3r {
namespace GUI {
AuxiliaryDialog::AuxiliaryDialog(wxWindow * parent)
: DPIDialog(parent, wxID_ANY, _L("Auxiliaryies"), wxDefaultPosition,
wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
m_aux_list = new AuxiliaryList(this);
SetSizerAndFit(m_aux_list->get_top_sizer());
SetSize({80 * em_unit(), 50 * em_unit()});
Layout();
Center();
}
void AuxiliaryDialog::on_dpi_changed(const wxRect& suggested_rect)
{
Fit();
SetSize({80 * em_unit(), 50 * em_unit()});
//m_aux_list->msw_rescale();
Refresh();
}
} // namespace GUI
} // namespace Slic3r

View File

@@ -0,0 +1,33 @@
#ifndef slic3r_GUI_AuxiliaryDialog_hpp_
#define slic3r_GUI_AuxiliaryDialog_hpp_
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/collpane.h>
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
class AuxiliaryList;
namespace Slic3r {
namespace GUI {
class AuxiliaryDialog : public DPIDialog
{
public:
AuxiliaryDialog(wxWindow * parent);
AuxiliaryList * aux_list() { return m_aux_list; }
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
private:
AuxiliaryList * m_aux_list;
};
} // namespace GUI
} // namespace Slic3r
#endif

View File

@@ -0,0 +1,901 @@
#include "BackgroundSlicingProcess.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include <wx/app.h>
#include <wx/panel.h>
#include <wx/stdpaths.h>
// For zipped archive creation
#include <wx/stdstream.h>
#include <wx/wfstream.h>
#include <wx/zipstrm.h>
#include <miniz.h>
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "libslic3r/Thread.hpp"
#include "libslic3r/libslic3r.h"
#include <cassert>
#include <stdexcept>
#include <cctype>
#include <boost/format/format_fwd.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp>
#include "I18N.hpp"
//#include "RemovableDriveManager.hpp"
#include "slic3r/GUI/Plater.hpp"
namespace Slic3r {
bool SlicingProcessCompletedEvent::critical_error() const
{
try {
this->rethrow_exception();
} catch (const Slic3r::SlicingError &) {
// Exception derived from SlicingError is non-critical.
return false;
} catch (const Slic3r::SlicingErrors &) {
return false;
} catch (...) {}
return true;
}
bool SlicingProcessCompletedEvent::invalidate_plater() const
{
if (critical_error())
{
try {
this->rethrow_exception();
}
catch (const Slic3r::ExportError&) {
// Exception thrown by copying file does not ivalidate plater
return false;
}
catch (...) {
}
return true;
}
return false;
}
std::pair<std::string, std::vector<size_t>> SlicingProcessCompletedEvent::format_error_message() const
{
std::string error;
size_t monospace = 0;
try {
this->rethrow_exception();
} catch (const std::bad_alloc &ex) {
wxString errmsg = GUI::from_u8(boost::format(_utf8(L("An error occurred. Maybe memory of system is not enough or it's a bug "
"of the program"))).str());
error = std::string(errmsg.ToUTF8()) + "\n" + std::string(ex.what());
} catch (const HardCrash &ex) {
error = GUI::format("A fatal error occurred: \"%1%\"", ex.what()) + "\n" +
_u8L("Please save project and restart the program. ");
} catch (PlaceholderParserError &ex) {
error = ex.what();
monospace = 1;
} catch (SlicingError &ex) {
error = ex.what();
monospace = ex.objectId();
} catch (SlicingErrors &exs) {
std::vector<size_t> ids;
for (auto &ex : exs.errors_) {
error = ex.what();
monospace = ex.objectId();
ids.push_back(monospace);
}
return std::make_pair(std::move(error), ids);
} catch (std::exception &ex) {
error = ex.what();
} catch (...) {
error = "Unknown C++ exception.";
}
return std::make_pair(std::move(error), std::vector<size_t>{monospace});
}
BackgroundSlicingProcess::BackgroundSlicingProcess()
{
//QDS: move this logic to part plate
#if 0
boost::filesystem::path temp_path(wxStandardPaths::Get().GetTempDir().utf8_str().data());
temp_path /= (boost::format(".%1%.gcode") % get_current_pid()).str();
m_temp_output_path = temp_path.string();
#endif
}
BackgroundSlicingProcess::~BackgroundSlicingProcess()
{
this->stop();
this->join_background_thread();
//QDS: move this logic to part plate
//boost::nowide::remove(m_temp_output_path.c_str());
}
//QDS: switch the print in background slicing process
bool BackgroundSlicingProcess::switch_print_preprocess()
{
bool result = true;
/*switch (m_printer_tech) {
case ptFFF: m_print = m_fff_print; break;
case ptSLA: m_print = m_sla_print; break;
default: assert(false); break;
}*/
return result;
}
//QDS: judge whether can switch the print
bool BackgroundSlicingProcess::can_switch_print()
{
bool result = true;
if (m_state == STATE_RUNNING)
{
//currently it is on slicing, judge whether the slice result is valid or not
//if (m_current_plate->is_slice_result_valid())
{
result = false;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": slicing plate's plate_id %1%, on slicing, can not switch print") % m_current_plate->get_index();
}
}
return result;
}
//QDS: select the printer technology
bool BackgroundSlicingProcess::select_technology(PrinterTechnology tech)
{
bool changed = false;
if (m_printer_tech != tech) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change the printer technology from %1% to %2%") % m_printer_tech % tech;
m_printer_tech = tech;
if (m_print != nullptr)
this->reset();
changed = true;
}
switch (tech) {
case ptFFF: m_print = m_fff_print; break;
case ptSLA: m_print = m_sla_print; break;
default: assert(false); break;
}
assert(m_print != nullptr);
return changed;
}
PrinterTechnology BackgroundSlicingProcess::current_printer_technology() const
{
//QDS: as the m_printer is changed frequently when switch plates, use m_printer_tech directly
return m_printer_tech;
//return m_print->technology();
}
std::string BackgroundSlicingProcess::output_filepath_for_project(const boost::filesystem::path &project_path)
{
assert(m_print != nullptr);
if (project_path.empty())
return m_print->output_filepath("");
return m_print->output_filepath(project_path.parent_path().string(), project_path.stem().string());
}
// This function may one day be merged into the Print, but historically the print was separated
// from the G-code generator.
// y13
void BackgroundSlicingProcess::process_fff()
{
// y18
// assert(m_print == m_fff_print);
//m_gcode_result->reset();
//PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
//m_fff_print->set_QDT_Printer(preset_bundle.printers.get_edited_preset().is_qdt_vendor_preset(&preset_bundle));
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: gcode_result reseted, will start print::process")%__LINE__;
//m_print->process();
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: after print::process, send slicing complete event to gui...")%__LINE__;
//wxCommandEvent evt(m_event_slicing_completed_id);
//// Post the Slicing Finished message for the G-code viewer to update.
//// Passing the timestamp
//evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psSlicingFinished).timestamp));
//wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
//boost::filesystem::path temp_path(wxStandardPaths::Get().GetTempDir().utf8_str().data());
// temp_path /= (boost::format(".%1%.gcode") % get_current_pid()).str();
//m_temp_output_path = temp_path.string();
//m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
assert(m_print == m_fff_print);
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
m_fff_print->set_QDT_Printer(true);
//QDS: add the logic to process from an existed gcode file
if (m_print->finished()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: skip slicing, to process previous gcode file")%__LINE__;
m_fff_print->set_status(80, _utf8(L("Processing G-Code from Previous file...")));
wxCommandEvent evt(m_event_slicing_completed_id);
// Post the Slicing Finished message for the G-code viewer to update.
// Passing the timestamp
evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psSlicingFinished).timestamp));
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
m_temp_output_path = this->get_current_plate()->get_tmp_gcode_path();
// y21
if (!m_temp_output_path.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: export gcode from %2% directly to %3%")%__LINE__%m_temp_output_path %m_export_path;
}
else {
m_fff_print->export_gcode_from_previous_file(m_temp_output_path, m_gcode_result, [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: export_gcode_from_previous_file from %2% finished")%__LINE__ % m_temp_output_path;
}
}
else {
//QDS: reset the gcode before reload_print in slicing_completed event processing
//FIX the gcode rename failed issue
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: will start slicing, reset gcode_result %2% firstly")%__LINE__%m_gcode_result;
m_gcode_result->reset();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: gcode_result reseted, will start print::process")%__LINE__;
m_print->process();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: after print::process, send slicing complete event to gui...")%__LINE__;
wxCommandEvent evt(m_event_slicing_completed_id);
// Post the Slicing Finished message for the G-code viewer to update.
// Passing the timestamp
evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psSlicingFinished).timestamp));
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
//QDS: add plate index into render params
m_temp_output_path = this->get_current_plate()->get_tmp_gcode_path();
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
finalize_gcode();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": export gcode finished");
}
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
export_gcode();
} else if (! m_upload_job.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
prepare_upload();
} else {
m_print->set_status(100, _utf8(L("Slicing complete")));
}
this->set_step_done(bspsGCodeFinalize);
}
}
static void write_thumbnail(Zipper& zipper, const ThumbnailData& data)
{
size_t png_size = 0;
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
if (png_data != nullptr)
{
zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) + "x" + std::to_string(data.height) + ".png", (const std::uint8_t*)png_data, png_size);
mz_free(png_data);
}
}
void BackgroundSlicingProcess::process_sla()
{
assert(m_print == m_sla_print);
m_print->process();
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
//QDS: add plate id for thumbnail generation
ThumbnailsList thumbnails = this->render_thumbnails(
ThumbnailsParams{ current_print()->full_print_config().option<ConfigOptionPoints>("thumbnail_size")->values, true, true, true, true, 0 });
Zipper zipper(export_path);
m_sla_archive.export_print(zipper, *m_sla_print); // true, false, true, true); // renders also supports and pad
for (const ThumbnailData& data : thumbnails)
if (data.is_valid())
write_thumbnail(zipper, data);
zipper.finalize();
//m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
m_print->set_status(100, (boost::format(_utf8("Masked SLA file exported to %1%")) % export_path).str());
} else {
//m_print->set_status(100, _utf8(L("Slicing complete")));
m_print->set_status(100, _utf8("Slicing complete"));
}
this->set_step_done(bspsGCodeFinalize);
}
}
void BackgroundSlicingProcess::thread_proc()
{
//QDS: thread name
set_current_thread_name("qdt_BgSlcPcs");
name_tbb_thread_pool_threads_set_locale();
assert(m_print != nullptr);
assert(m_print == m_fff_print || m_print == m_sla_print);
std::unique_lock<std::mutex> lck(m_mutex);
// Let the caller know we are ready to run the background processing task.
m_state = STATE_IDLE;
lck.unlock();
m_condition.notify_one();
for (;;) {
//QDS: sometimes the state has already been set in the start function
assert(m_state == STATE_IDLE || m_state == STATE_CANCELED || m_state == STATE_FINISHED);
// Wait until a new task is ready to be executed, or this thread should be finished.
lck.lock();
m_condition.wait(lck, [this](){ return m_state == STATE_STARTED || m_state == STATE_EXIT; });
if (m_state == STATE_EXIT)
// Exiting this thread.
break;
// Process the background slicing task.
m_state = STATE_RUNNING;
//QDS: internal cancel
// m_internal_cancelled = false;
lck.unlock();
std::exception_ptr exception;
#ifdef _WIN32
this->call_process_seh_throw(exception);
#else
this->call_process(exception);
#endif
m_print->finalize();
lck.lock();
m_state = m_print->canceled() ? STATE_CANCELED : STATE_FINISHED;
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": process finished, state %1%, print cancel_status %2%")%m_state %m_print->cancel_status();
if (m_print->cancel_status() != Print::CANCELED_INTERNAL) {
// Only post the canceled event, if canceled by user.
// Don't post the canceled event, if canceled from Print::apply().
SlicingProcessCompletedEvent evt(m_event_finished_id, 0,
(m_state == STATE_CANCELED) ? SlicingProcessCompletedEvent::Cancelled :
exception ? SlicingProcessCompletedEvent::Error : SlicingProcessCompletedEvent::Finished, exception);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": send SlicingProcessCompletedEvent to main, status %1%")%evt.status();
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
}
else {
//QDS: internal cancel
m_internal_cancelled = true;
}
m_print->restart();
lck.unlock();
// Let the UI thread wake up if it is waiting for the background task to finish.
m_condition.notify_one();
// Let the UI thread see the result.
}
m_state = STATE_EXITED;
lck.unlock();
// End of the background processing thread. The UI thread should join m_thread now.
}
#ifdef _WIN32
// Only these SEH exceptions will be catched and turned into Slic3r::HardCrash C++ exceptions.
static bool is_win32_seh_harware_exception(unsigned long ex) throw() {
return
ex == STATUS_ACCESS_VIOLATION ||
ex == STATUS_DATATYPE_MISALIGNMENT ||
ex == STATUS_FLOAT_DIVIDE_BY_ZERO ||
ex == STATUS_FLOAT_OVERFLOW ||
ex == STATUS_FLOAT_UNDERFLOW ||
#ifdef STATUS_FLOATING_RESEVERED_OPERAND
ex == STATUS_FLOATING_RESEVERED_OPERAND ||
#endif // STATUS_FLOATING_RESEVERED_OPERAND
ex == STATUS_ILLEGAL_INSTRUCTION ||
ex == STATUS_PRIVILEGED_INSTRUCTION ||
ex == STATUS_INTEGER_DIVIDE_BY_ZERO ||
ex == STATUS_INTEGER_OVERFLOW ||
ex == STATUS_STACK_OVERFLOW;
}
// Rethrow some SEH exceptions as Slic3r::HardCrash C++ exceptions.
static void rethrow_seh_exception(unsigned long win32_seh_catched)
{
if (win32_seh_catched) {
// Rethrow SEH exception as Slicer::HardCrash.
if (win32_seh_catched == STATUS_ACCESS_VIOLATION || win32_seh_catched == STATUS_DATATYPE_MISALIGNMENT)
throw Slic3r::HardCrash(_u8L("Access violation"));
if (win32_seh_catched == STATUS_ILLEGAL_INSTRUCTION || win32_seh_catched == STATUS_PRIVILEGED_INSTRUCTION)
throw Slic3r::HardCrash(_u8L("Illegal instruction"));
if (win32_seh_catched == STATUS_FLOAT_DIVIDE_BY_ZERO || win32_seh_catched == STATUS_INTEGER_DIVIDE_BY_ZERO)
throw Slic3r::HardCrash(_u8L("Divide by zero"));
if (win32_seh_catched == STATUS_FLOAT_OVERFLOW || win32_seh_catched == STATUS_INTEGER_OVERFLOW)
throw Slic3r::HardCrash(_u8L("Overflow"));
if (win32_seh_catched == STATUS_FLOAT_UNDERFLOW)
throw Slic3r::HardCrash(_u8L("Underflow"));
#ifdef STATUS_FLOATING_RESEVERED_OPERAND
if (win32_seh_catched == STATUS_FLOATING_RESEVERED_OPERAND)
throw Slic3r::HardCrash(_u8L("Floating reserved operand"));
#endif // STATUS_FLOATING_RESEVERED_OPERAND
if (win32_seh_catched == STATUS_STACK_OVERFLOW)
throw Slic3r::HardCrash(_u8L("Stack overflow"));
}
}
// Wrapper for Win32 structured exceptions. Win32 structured exception blocks and C++ exception blocks cannot be mixed in the same function.
unsigned long BackgroundSlicingProcess::call_process_seh(std::exception_ptr &ex) throw()
{
unsigned long win32_seh_catched = 0;
__try {
this->call_process(ex);
} __except (is_win32_seh_harware_exception(GetExceptionCode())) {
win32_seh_catched = GetExceptionCode();
}
return win32_seh_catched;
}
void BackgroundSlicingProcess::call_process_seh_throw(std::exception_ptr &ex) throw()
{
unsigned long win32_seh_catched = this->call_process_seh(ex);
if (win32_seh_catched) {
// Rethrow SEH exception as Slicer::HardCrash.
try {
rethrow_seh_exception(win32_seh_catched);
} catch (...) {
ex = std::current_exception();
}
}
}
#endif // _WIN32
void BackgroundSlicingProcess::call_process(std::exception_ptr &ex) throw()
{
try {
assert(m_print != nullptr);
switch (m_print->technology()) {
case ptFFF: this->process_fff(); break;
case ptSLA: this->process_sla(); break;
default: m_print->process(); break;
}
} catch (CanceledException& /* ex */) {
// Canceled, this is all right.
assert(m_print->canceled());
ex = std::current_exception();
BOOST_LOG_TRIVIAL(error) <<__FUNCTION__ << ":got cancelled exception" << std::endl;
} catch (...) {
ex = std::current_exception();
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":got other exception" << std::endl;
}
}
#ifdef _WIN32
unsigned long BackgroundSlicingProcess::thread_proc_safe_seh() throw()
{
unsigned long win32_seh_catched = 0;
__try {
this->thread_proc_safe();
} __except (is_win32_seh_harware_exception(GetExceptionCode())) {
win32_seh_catched = GetExceptionCode();
}
return win32_seh_catched;
}
void BackgroundSlicingProcess::thread_proc_safe_seh_throw() throw()
{
unsigned long win32_seh_catched = this->thread_proc_safe_seh();
if (win32_seh_catched) {
// Rethrow SEH exception as Slicer::HardCrash.
try {
rethrow_seh_exception(win32_seh_catched);
} catch (...) {
wxTheApp->OnUnhandledException();
}
}
}
#endif // _WIN32
void BackgroundSlicingProcess::thread_proc_safe() throw()
{
try {
this->thread_proc();
} catch (...) {
wxTheApp->OnUnhandledException();
}
}
void BackgroundSlicingProcess::join_background_thread()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// Worker thread has not been started yet.
assert(! m_thread.joinable());
} else {
assert(m_state == STATE_IDLE);
assert(m_thread.joinable());
// Notify the worker thread to exit.
m_state = STATE_EXIT;
lck.unlock();
m_condition.notify_one();
// Wait until the worker thread exits.
m_thread.join();
}
}
bool BackgroundSlicingProcess::start()
{
if (m_print->empty()) {
if (!m_current_plate || !m_current_plate->is_slice_result_valid())
// The print is empty (no object in Model, or all objects are out of the print bed).
return false;
}
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// The worker thread is not running yet. Start it.
assert(! m_thread.joinable());
m_thread = create_thread([this]{
#ifdef _WIN32
this->thread_proc_safe_seh_throw();
#else // _WIN32
this->thread_proc_safe();
#endif // _WIN32
});
// Wait until the worker thread is ready to execute the background processing task.
m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; });
}
assert(m_state == STATE_IDLE || this->running());
//if (this->running())
// //The background processing thread is already running.
// return false;
if (this->running())
return false;
if (! this->idle())
throw Slic3r::RuntimeError("Cannot start a background task, the worker thread is not idle.");
m_state = STATE_STARTED;
m_print->set_cancel_callback([this](){ this->stop_internal(); });
lck.unlock();
m_condition.notify_one();
return true;
}
// To be called on the UI thread.
bool BackgroundSlicingProcess::stop()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ", enter"<<std::endl;
// m_print->state_mutex() shall NOT be held. Unfortunately there is no interface to test for it.
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// m_export_path.clear();
return false;
}
// assert(this->running());
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
// Cancel any task planned by the background thread on UI thread.
cancel_ui_task(m_ui_task);
m_print->cancel();
// Wait until the background processing stops by being canceled.
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
// In the "Canceled" state. Reset the state to "Idle".
m_state = STATE_IDLE;
m_print->set_cancel_callback([](){});
} else if (m_state == STATE_FINISHED || m_state == STATE_CANCELED) {
// In the "Finished" or "Canceled" state. Reset the state to "Idle".
m_state = STATE_IDLE;
m_print->set_cancel_callback([](){});
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ", exit"<<std::endl;
// m_export_path.clear();
return true;
}
bool BackgroundSlicingProcess::reset()
{
bool stopped = this->stop();
this->reset_export();
//QDS: don't clear print for print is not owned by background slicing process anymore
//do it in the part_plate
//m_print->clear();
this->invalidate_all_steps();
return stopped;
}
// To be called by Print::apply() on the UI thread through the Print::m_cancel_callback to stop the background
// processing before changing any data of running or finalized milestones.
// This function shall not trigger any UI update through the wxWidgets event.
void BackgroundSlicingProcess::stop_internal()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ", enter"<<std::endl;
// m_print->state_mutex() shall be held. Unfortunately there is no interface to test for it.
if (m_state == STATE_IDLE)
// The worker thread is waiting on m_mutex/m_condition for wake up. The following lock of the mutex would block.
return;
std::unique_lock<std::mutex> lck(m_mutex);
assert(m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED);
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
// Cancel any task planned by the background thread on UI thread.
cancel_ui_task(m_ui_task);
// At this point of time the worker thread may be blocking on m_print->state_mutex().
// Set the print state to canceled before unlocking the state_mutex(), so when the worker thread wakes up,
// it throws the CanceledException().
m_print->cancel_internal();
// Allow the worker thread to wake up if blocking on a milestone.
m_print->state_mutex().unlock();
// Wait until the background processing stops by being canceled.
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
// Lock it back to be in a consistent state.
m_print->state_mutex().lock();
}
// In the "Canceled" state. Reset the state to "Idle".
m_state = STATE_IDLE;
m_print->set_cancel_callback([](){});
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ", exit"<<std::endl;
}
// Execute task from background thread on the UI thread. Returns true if processed, false if cancelled.
bool BackgroundSlicingProcess::execute_ui_task(std::function<void()> task)
{
bool running = false;
if (m_mutex.try_lock()) {
// Cancellation is either not in process, or already canceled and waiting for us to finish.
// There must be no UI task planned.
assert(! m_ui_task);
if (! m_print->canceled()) {
running = true;
m_ui_task = std::make_shared<UITask>();
}
m_mutex.unlock();
} else {
// Cancellation is in process.
}
bool result = false;
if (running) {
std::shared_ptr<UITask> ctx = m_ui_task;
GUI::wxGetApp().mainframe->m_plater->CallAfter([task, ctx]() {
// Running on the UI thread, thus ctx->state does not need to be guarded with mutex against ::cancel_ui_task().
assert(ctx->state == UITask::Planned || ctx->state == UITask::Canceled);
if (ctx->state == UITask::Planned) {
task();
std::unique_lock<std::mutex> lck(ctx->mutex);
ctx->state = UITask::Finished;
}
// Wake up the worker thread from the UI thread.
ctx->condition.notify_all();
});
{
std::unique_lock<std::mutex> lock(ctx->mutex);
ctx->condition.wait(lock, [&ctx]{ return ctx->state == UITask::Finished || ctx->state == UITask::Canceled; });
}
result = ctx->state == UITask::Finished;
m_ui_task.reset();
}
return result;
}
// To be called on the UI thread from ::stop() and ::stop_internal().
void BackgroundSlicingProcess::cancel_ui_task(std::shared_ptr<UITask> task)
{
if (task) {
std::unique_lock<std::mutex> lck(task->mutex);
task->state = UITask::Canceled;
lck.unlock();
task->condition.notify_all();
}
}
bool BackgroundSlicingProcess::empty() const
{
assert(m_print != nullptr);
return m_print->empty();
}
StringObjectException BackgroundSlicingProcess::validate(StringObjectException *warning, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons)
{
assert(m_print != nullptr);
return m_print->validate(warning, collison_polygons, height_polygons);
}
// Apply config over the print. Returns false, if the new config values caused any of the already
// processed steps to be invalidated, therefore the task will need to be restarted.
Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const DynamicPrintConfig &config)
{
assert(m_print != nullptr);
assert(config.opt_enum<PrinterTechnology>("printer_technology") == m_print->technology());
// TODO: add partplate config
DynamicPrintConfig new_config = config;
new_config.apply(*m_current_plate->config());
Print::ApplyStatus invalidated = m_print->apply(model, new_config);
if ((invalidated & PrintBase::APPLY_STATUS_INVALIDATED) != 0 && m_print->technology() == ptFFF &&
!m_fff_print->is_step_done(psGCodeExport)) {
// Some FFF status was invalidated, and the G-code was not exported yet.
// Let the G-code preview UI know that the final G-code preview is not valid.
// In addition, this early memory deallocation reduces memory footprint.
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": invalide gcode result %1%, will reset soon")%m_gcode_result;
if (m_gcode_result != nullptr)
m_gcode_result->reset();
}
return invalidated;
}
void BackgroundSlicingProcess::set_task(const PrintBase::TaskParams &params)
{
assert(m_print != nullptr);
m_print->set_task(params);
}
// Set the output path of the G-code.
void BackgroundSlicingProcess::schedule_export(const std::string &path, bool export_path_on_removable_media)
{
assert(m_export_path.empty());
if (! m_export_path.empty())
return;
// Guard against entering the export step before changing the export path.
std::scoped_lock<std::mutex> lock(m_print->state_mutex());
this->invalidate_step(bspsGCodeFinalize);
m_export_path = path;
m_export_path_on_removable_media = export_path_on_removable_media;
}
void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job)
{
assert(m_export_path.empty());
if (! m_export_path.empty())
return;
std::scoped_lock<std::mutex> lock(m_print->state_mutex());
this->invalidate_step(bspsGCodeFinalize);
m_export_path.clear();
m_upload_job = std::move(upload_job);
}
void BackgroundSlicingProcess::reset_export()
{
assert(! this->running());
if (! this->running()) {
m_export_path.clear();
m_export_path_on_removable_media = false;
// invalidate_step expects the mutex to be locked.
std::scoped_lock<std::mutex> lock(m_print->state_mutex());
this->invalidate_step(bspsGCodeFinalize);
}
}
bool BackgroundSlicingProcess::set_step_started(BackgroundSlicingProcessStep step)
{
return m_step_state.set_started(step, m_print->state_mutex(), [this](){ this->throw_if_canceled(); });
}
void BackgroundSlicingProcess::set_step_done(BackgroundSlicingProcessStep step)
{
m_step_state.set_done(step, m_print->state_mutex(), [this](){ this->throw_if_canceled(); });
}
bool BackgroundSlicingProcess::is_step_done(BackgroundSlicingProcessStep step) const
{
return m_step_state.is_done(step, m_print->state_mutex());
}
bool BackgroundSlicingProcess::invalidate_step(BackgroundSlicingProcessStep step)
{
bool invalidated = m_step_state.invalidate(step, [this](){ this->stop_internal(); });
return invalidated;
}
bool BackgroundSlicingProcess::invalidate_all_steps()
{
return m_step_state.invalidate_all([this](){ this->stop_internal(); });
}
//Call post-processing script for the last step during slicing
void BackgroundSlicingProcess::finalize_gcode()
{
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config());
m_print->set_status(100, _utf8(L("Successfully executed post-processing script")));
}
// G-code is generated in m_temp_output_path.
// Optionally run a post-processing script on a copy of m_temp_output_path.
// Copy the final G-code to target location (possibly a SD card, if it is a removable media, then verify that the file was written without an error).
void BackgroundSlicingProcess::export_gcode()
{
// Perform the final post-processing of the export path by applying the print statistics over the file name.
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
std::string output_path = m_temp_output_path;
//FIXME localize the messages
std::string error_message;
int copy_ret_val = CopyFileResult::SUCCESS;
try
{
copy_ret_val = copy_file(output_path, export_path, error_message, m_export_path_on_removable_media);
}
catch (...)
{
throw Slic3r::ExportError(_utf8(L("Unknown error when export G-code.")));
}
switch (copy_ret_val) {
case CopyFileResult::SUCCESS: break; // no error
case CopyFileResult::FAIL_COPY_FILE:
//throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"))) % error_message).str());
//break;
case CopyFileResult::FAIL_FILES_DIFFERENT:
//throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
//break;
case CopyFileResult::FAIL_RENAMING:
//throw Slic3r::ExportError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
//break;
case CopyFileResult::FAIL_CHECK_ORIGIN_NOT_OPENED:
//throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % output_path % export_path).str());
//break;
case CopyFileResult::FAIL_CHECK_TARGET_NOT_OPENED:
//throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
//break;
default:
BOOST_LOG_TRIVIAL(error) << "Fail code(" << (int)copy_ret_val << ") when copy "<<output_path<<" to " << export_path << ".";
throw Slic3r::ExportError((boost::format(_utf8(L("Failed to save gcode file.\nError message: %1%.\nSource file %2%."))) % error_message % output_path).str());
//throw Slic3r::ExportError(_utf8(L("Unknown error when export G-code.")));
break;
}
// QDS
auto evt = new wxCommandEvent(m_event_export_finished_id, GUI::wxGetApp().mainframe->m_plater->GetId());
wxString output_gcode_str = wxString::FromUTF8(export_path.c_str(), export_path.length());
evt->SetString(output_gcode_str);
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt);
// QDS: to be checked. Whether use export_path or output_path.
gcode_add_line_number(export_path, m_fff_print->full_print_config());
}
// A print host upload job has been scheduled, enqueue it to the printhost job queue
void BackgroundSlicingProcess::prepare_upload()
{
// Generate a unique temp path to which the gcode/zip file is copied/exported
boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
/ boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%");
if (m_print == m_fff_print) {
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
std::string error_message;
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS)
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
// Make a copy of the source path, as run_post_process_scripts() is allowed to change it when making a copy of the source file
// (not here, but when the final target is a file).
std::string source_path_str = source_path.string();
std::string output_name_str = m_upload_job.upload_data.upload_path.string();
if (run_post_process_scripts(source_path_str, false, m_upload_job.printhost->get_name(), output_name_str, m_fff_print->full_print_config()))
m_upload_job.upload_data.upload_path = output_name_str;
} else {
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
ThumbnailsList thumbnails = this->render_thumbnails(
ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnail_size")->values, true, true, true, true});
// true, false, true, true); // renders also supports and pad
Zipper zipper{source_path.string()};
m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string());
for (const ThumbnailData& data : thumbnails)
if (data.is_valid())
write_thumbnail(zipper, data);
zipper.finalize();
}
m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str());
m_upload_job.upload_data.source_path = std::move(source_path);
GUI::wxGetApp().printhost_job_queue().enqueue(std::move(m_upload_job));
}
// Executed by the background thread, to start a task on the UI thread.
ThumbnailsList BackgroundSlicingProcess::render_thumbnails(const ThumbnailsParams &params)
{
ThumbnailsList thumbnails;
if (m_thumbnail_cb)
this->execute_ui_task([this, &params, &thumbnails](){ thumbnails = m_thumbnail_cb(params); });
return thumbnails;
}
}; // namespace Slic3r

View File

@@ -0,0 +1,312 @@
#ifndef slic3r_GUI_BackgroundSlicingProcess_hpp_
#define slic3r_GUI_BackgroundSlicingProcess_hpp_
#include <string>
#include <condition_variable>
#include <mutex>
#include <boost/thread.hpp>
#include <wx/event.h>
#include "libslic3r/PrintBase.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "slic3r/Utils/PrintHost.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "PartPlate.hpp"
namespace boost { namespace filesystem { class path; } }
namespace Slic3r {
class DynamicPrintConfig;
class Model;
class SLAPrint;
class SlicingStatusEvent : public wxEvent
{
public:
SlicingStatusEvent(wxEventType eventType, int winid, const PrintBase::SlicingStatus &status) :
wxEvent(winid, eventType), status(std::move(status)) {}
virtual wxEvent *Clone() const { return new SlicingStatusEvent(*this); }
PrintBase::SlicingStatus status;
};
class SlicingProcessCompletedEvent : public wxEvent
{
public:
enum StatusType {
Finished,
Cancelled,
Error
};
SlicingProcessCompletedEvent(wxEventType eventType, int winid, StatusType status, std::exception_ptr exception) :
wxEvent(winid, eventType), m_status(status), m_exception(exception) {}
virtual wxEvent* Clone() const { return new SlicingProcessCompletedEvent(*this); }
StatusType status() const { return m_status; }
bool finished() const { return m_status == Finished; }
bool success() const { return m_status == Finished; }
bool cancelled() const { return m_status == Cancelled; }
bool error() const { return m_status == Error; }
// Unhandled error produced by stdlib or a Win32 structured exception, or unhandled Slic3r's own critical exception.
bool critical_error() const;
// Critical errors does invalidate plater except CopyFileError.
bool invalidate_plater() const;
// Only valid if error()
void rethrow_exception() const { assert(this->error()); assert(m_exception); std::rethrow_exception(m_exception); }
// Produce a human readable message to be displayed by a notification or a message box.
// 2nd parameter defines whether the output should be displayed with a monospace font.
std::pair<std::string, std::vector<size_t>> format_error_message() const;
private:
StatusType m_status;
std::exception_ptr m_exception;
};
//QDS: move it to plater.hpp
//wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent);
// Print step IDs for keeping track of the print state.
enum BackgroundSlicingProcessStep {
bspsGCodeFinalize, bspsCount,
};
// Support for the GUI background processing (Slicing and G-code generation).
// As of now this class is not declared in Slic3r::GUI due to the Perl bindings limits.
class BackgroundSlicingProcess
{
public:
BackgroundSlicingProcess();
// Stop the background processing and finalize the bacgkround processing thread, remove temp files.
~BackgroundSlicingProcess();
void set_fff_print(Print *print) { m_fff_print = print; }
void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); }
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; }
//QDS: add partplate related logic
bool switch_print_preprocess();
bool can_switch_print();
void set_current_plate(GUI::PartPlate* plate) { m_current_plate = plate; }
GUI::PartPlate* get_current_plate() { return m_current_plate; }
GCodeProcessorResult* get_current_gcode_result() { return m_gcode_result;}
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished
// and the background processing will transition into G-code export.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
void set_slicing_completed_event(int event_id) { m_event_slicing_completed_id = event_id; }
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
void set_finished_event(int event_id) { m_event_finished_id = event_id; }
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code is being exported to
// specified path or uploaded.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
void set_export_began_event(int event_id) { m_event_export_began_id = event_id; }
// QDS
void set_export_finished_event(int event_id) { m_event_export_finished_id = event_id; }
// Activate either m_fff_print or m_sla_print.
// Return true if changed.
bool select_technology(PrinterTechnology tech);
// Get the currently active printer technology.
PrinterTechnology current_printer_technology() const;
// Get the current print. It is either m_fff_print or m_sla_print.
const PrintBase* current_print() const { return m_print; }
const Print* fff_print() const { return m_fff_print; }
Print * fff_print() { return m_fff_print; }
const SLAPrint* sla_print() const { return m_sla_print; }
// Take the project path (if provided), extract the name of the project, run it through the macro processor and save it next to the project file.
// If the project_path is empty, just run output_filepath().
std::string output_filepath_for_project(const boost::filesystem::path &project_path);
// Start the background processing. Returns false if the background processing was already running.
bool start();
// Cancel the background processing. Returns false if the background processing was not running.
// A stopped background processing may be restarted with start().
bool stop();
// Cancel the background processing and reset the print. Returns false if the background processing was not running.
// Useful when the Model or configuration is being changed drastically.
bool reset();
// Apply config over the print. Returns false, if the new config values caused any of the already
// processed steps to be invalidated, therefore the task will need to be restarted.
PrintBase::ApplyStatus apply(const Model &model, const DynamicPrintConfig &config);
// After calling the apply() function, set_task() may be called to limit the task to be processed by process().
// This is useful for calculating SLA supports for a single object only.
void set_task(const PrintBase::TaskParams &params);
// After calling apply, the empty() call will report whether there is anything to slice.
bool empty() const;
// Validate the print. Returns an empty string if valid, returns an error message if invalid.
// Call validate before calling start().
StringObjectException validate(StringObjectException *warning = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr);
// Set the export path of the G-code.
// Once the path is set, the G-code
void schedule_export(const std::string &path, bool export_path_on_removable_media);
// Set print host upload job data to be enqueued to the PrintHostJobQueue
// after current print slicing is complete
void schedule_upload(Slic3r::PrintHostJob upload_job);
// Clear m_export_path.
void reset_export();
// Once the G-code export is scheduled, the apply() methods will do nothing.
bool is_export_scheduled() const { return ! m_export_path.empty(); }
bool is_upload_scheduled() const { return ! m_upload_job.empty(); }
enum State {
// m_thread is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet).
STATE_INITIAL = 0,
// m_thread is waiting for the task to execute.
STATE_IDLE,
STATE_STARTED,
// m_thread is executing a task.
STATE_RUNNING,
// m_thread finished executing a task, and it is waiting until the UI thread picks up the results.
STATE_FINISHED,
// m_thread finished executing a task, the task has been canceled by the UI thread, therefore the UI thread will not be notified.
STATE_CANCELED,
// m_thread exited the loop and it is going to finish. The UI thread should join on m_thread.
STATE_EXIT,
STATE_EXITED,
};
State state() const { return m_state; }
bool idle() const { return m_state == STATE_IDLE; }
bool running() const { return m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED; }
// Returns true if the last step of the active print was finished with success.
// The "finished" flag is reset by the apply() method, if it changes the state of the print.
// This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs),
// and it does not account for the OctoPrint scheduling.
//QDS: improve the finished logic, also judge the m_gcode_result
//bool finished() const { return m_print->finished(); }
bool finished() const { return m_print->finished() && !m_gcode_result->moves.empty(); }
bool is_internal_cancelled() { return m_internal_cancelled; }
//QDS: add Plater to friend class
//need to call stop_internal in ui thread
friend class GUI::Plater;
private:
void thread_proc();
// Calls thread_proc(), catches all C++ exceptions and shows them using wxApp::OnUnhandledException().
void thread_proc_safe() throw();
#ifdef _WIN32
// Wrapper for Win32 structured exceptions. Win32 structured exception blocks and C++ exception blocks cannot be mixed in the same function.
// Catch a SEH exception and return its ID or zero if no SEH exception has been catched.
unsigned long thread_proc_safe_seh() throw();
// Calls thread_proc_safe_seh(), rethrows a Slic3r::HardCrash exception based on SEH exception
// returned by thread_proc_safe_seh() and lets wxApp::OnUnhandledException() display it.
void thread_proc_safe_seh_throw() throw();
#endif // _WIN32
void join_background_thread();
// To be called by Print::apply() through the Print::m_cancel_callback to stop the background
// processing before changing any data of running or finalized milestones.
// This function shall not trigger any UI update through the wxWidgets event.
void stop_internal();
// Helper to wrap the FFF slicing & G-code generation.
void process_fff();
// Temporary: for mimicking the fff file export behavior with the raster output
void process_sla();
// Call Print::process() and catch all exceptions into ex, thus no exception could be thrown
// by this method. This exception behavior is required to combine C++ exceptions with Win32 SEH exceptions
// on the same thread.
void call_process(std::exception_ptr &ex) throw();
#ifdef _WIN32
// Wrapper for Win32 structured exceptions. Win32 structured exception blocks and C++ exception blocks cannot be mixed in the same function.
// Catch a SEH exception and return its ID or zero if no SEH exception has been catched.
unsigned long call_process_seh(std::exception_ptr &ex) throw();
// Calls call_process_seh(), rethrows a Slic3r::HardCrash exception based on SEH exception
// returned by call_process_seh().
void call_process_seh_throw(std::exception_ptr &ex) throw();
#endif // _WIN32
// Currently active print. It is one of m_fff_print and m_sla_print.
PrintBase *m_print = nullptr;
// Non-owned pointers to Print instances.
Print *m_fff_print = nullptr;
SLAPrint *m_sla_print = nullptr;
// Data structure, to which the G-code export writes its annotations.
GCodeProcessorResult *m_gcode_result = nullptr;
// Callback function, used to write thumbnails into gcode.
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
SL1Archive m_sla_archive;
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
std::string m_temp_output_path;
// Output path provided by the user. The output path may be set even if the slicing is running,
// but once set, it cannot be re-set.
std::string m_export_path;
bool m_export_path_on_removable_media = false;
// Print host upload job to schedule after slicing is complete, used by schedule_upload(),
// empty by default (ie. no upload to schedule)
PrintHostJob m_upload_job;
// Thread, on which the background processing is executed. The thread will always be present
// and ready to execute the slicing process.
boost::thread m_thread;
// Mutex and condition variable to synchronize m_thread with the UI thread.
std::mutex m_mutex;
std::condition_variable m_condition;
State m_state = STATE_INITIAL;
// For executing tasks from the background thread on UI thread synchronously (waiting for result) using wxWidgets CallAfter().
// When the background proces is canceled, the UITask has to be invalidated as well, so that it will not be
// executed on the UI thread referencing invalid data.
struct UITask {
enum State {
Planned,
Finished,
Canceled,
};
State state = Planned;
std::mutex mutex;
std::condition_variable condition;
};
// Only one UI task may be planned by the background thread to be executed on the UI thread, as the background
// thread is blocking until the UI thread calculation finishes.
std::shared_ptr<UITask> m_ui_task;
//QDS: partplate related
GUI::PartPlate* m_current_plate;
PrinterTechnology m_printer_tech = ptUnknown;
bool m_internal_cancelled = false;
PrintState<BackgroundSlicingProcessStep, bspsCount> m_step_state;
bool set_step_started(BackgroundSlicingProcessStep step);
void set_step_done(BackgroundSlicingProcessStep step);
bool is_step_done(BackgroundSlicingProcessStep step) const;
bool invalidate_step(BackgroundSlicingProcessStep step);
bool invalidate_all_steps();
// If the background processing stop was requested, throw CanceledException.
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
void finalize_gcode();
void export_gcode();
void prepare_upload();
// To be executed at the background thread.
ThumbnailsList render_thumbnails(const ThumbnailsParams &params);
// Execute task from background thread on the UI thread synchronously. Returns true if processed, false if cancelled before executing the task.
bool execute_ui_task(std::function<void()> task);
// To be called from inside m_mutex to cancel a planned UI task.
static void cancel_ui_task(std::shared_ptr<BackgroundSlicingProcess::UITask> task);
// wxWidgets command ID to be sent to the plater to inform that the slicing is finished, and the G-code export will continue.
int m_event_slicing_completed_id = 0;
// wxWidgets command ID to be sent to the plater to inform that the task finished.
int m_event_finished_id = 0;
// wxWidgets command ID to be sent to the plater to inform that the G-code is being exported.
int m_event_export_began_id = 0;
// wxWidgets command ID to be sent to the plater to inform that the G-code is exported end.
int m_event_export_finished_id = 0;
};
}; // namespace Slic3r
#endif /* slic3r_GUI_BackgroundSlicingProcess_hpp_ */

View File

@@ -0,0 +1,655 @@
#include "BedShapeDialog.hpp"
#include "GUI_App.hpp"
#include "OptionsGroup.hpp"
#include <wx/wx.h>
#include <wx/numformatter.h>
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/tooltip.h>
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Polygon.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include "FileHelp.hpp"
#include <wx/dcgraph.h>
#include <algorithm>
namespace Slic3r {
namespace GUI {
BedShape::BedShape(const ConfigOptionPoints& points)
{
m_build_volume = { points.values, 0. };
}
static std::string get_option_label(BedShape::Parameter param)
{
switch (param) {
case BedShape::Parameter::RectSize : return L("Size");
case BedShape::Parameter::RectOrigin: return L("Origin");
case BedShape::Parameter::Diameter : return L("Diameter");
default: assert(false); return {};
}
}
void BedShape::append_option_line(ConfigOptionsGroupShp optgroup, Parameter param, bool can_edit)
{
ConfigOptionDef def;
t_config_option_key key;
switch (param) {
case Parameter::RectSize:
def.type = coPoints;
def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) });
def.min = 0;
def.max = 1200;
def.label = get_option_label(param);
def.tooltip = L("Size in X and Y of the rectangular plate.");
def.readonly = !can_edit;
key = "rect_size";
break;
case Parameter::RectOrigin:
def.type = coPoints;
def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) });
def.min = -600;
def.max = 600;
def.label = get_option_label(param);
def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
def.readonly = !can_edit;
key = "rect_origin";
break;
case Parameter::Diameter:
def.type = coFloat;
def.set_default_value(new ConfigOptionFloat(200));
def.sidetext = L("mm");
def.label = get_option_label(param);
def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
def.readonly = !can_edit;
key = "diameter";
break;
default:
assert(false);
}
optgroup->append_single_option_line({ def, std::move(key) });
}
wxString BedShape::get_name(PageType type)
{
switch (type) {
case PageType::Rectangle: return _L("Rectangular");
case PageType::Circle: return _L("Circular");
case PageType::Custom: return _L("Custom");
}
// make visual studio happy
assert(false);
return {};
}
BedShape::PageType BedShape::get_page_type()
{
switch (m_build_volume.type()) {
case BuildVolume::Type::Rectangle:
case BuildVolume::Type::Invalid: return PageType::Rectangle;
case BuildVolume::Type::Circle: return PageType::Circle;
case BuildVolume::Type::Convex:
case BuildVolume::Type::Custom: return PageType::Custom;
}
// make visual studio happy
assert(false);
return PageType::Rectangle;
}
wxString BedShape::get_full_name_with_params()
{
wxString out = _L("Shape") + ": " + get_name(this->get_page_type());
switch (m_build_volume.type()) {
case BuildVolume::Type::Circle:
out += "\n" + _L(get_option_label(Parameter::Diameter)) + ": [" + double_to_string(2. * unscaled<double>(m_build_volume.circle().radius)) + "]";
break;
default:
// rectangle, convex, concave...
out += "\n" + _(get_option_label(Parameter::RectSize)) + ": [" + ConfigOptionPoint(to_2d(m_build_volume.bounding_volume().size())).serialize() + "]";
out += "\n" + _(get_option_label(Parameter::RectOrigin)) + ": [" + ConfigOptionPoint(- to_2d(m_build_volume.bounding_volume().min)).serialize() + "]";
break;
}
return out;
}
void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup)
{
switch (m_build_volume.type()) {
case BuildVolume::Type::Circle:
optgroup->set_value("diameter", double_to_string(2. * unscaled<double>(m_build_volume.circle().radius)));
break;
default:
// rectangle, convex, concave...
optgroup->set_value("rect_size" , new ConfigOptionPoints{ to_2d(m_build_volume.bounding_volume().size()) });
optgroup->set_value("rect_origin" , new ConfigOptionPoints{ - to_2d(m_build_volume.bounding_volume().min) });
}
}
void BedShapeDialog::build_dialog(const ConfigOptionPoints &default_pt, const ConfigOptionString &custom_texture, const ConfigOptionString &custom_model, bool can_edit)
{
SetFont(wxGetApp().normal_font());
this->SetBackgroundColour(*wxWHITE);
m_panel = new BedShapePanel(this);
m_panel->set_edit_state(can_edit);
m_panel->build_panel(default_pt, custom_texture, custom_model);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_panel, 1, wxEXPAND);
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
wxGetApp().UpdateDlgDarkUI(this);
SetSizer(main_sizer);
SetMinSize(GetSize());
main_sizer->SetSizeHints(this);
this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent& evt) {
EndModal(wxID_CANCEL);
}));
}
void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect)
{
const int& em = em_unit();
m_panel->m_shape_options_book->SetMinSize(wxSize(25 * em, -1));
for (auto og : m_panel->m_optgroups)
og->msw_rescale();
const wxSize& size = wxSize(50 * em, -1);
SetMinSize(size);
SetSize(size);
Refresh();
}
const std::string BedShapePanel::NONE = "None";
const std::string BedShapePanel::EMPTY_STRING = "";
void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model)
{
wxGetApp().UpdateDarkUI(this);
m_shape = default_pt.values;
m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value;
m_custom_model = custom_model.value.empty() ? NONE : custom_model.value;
auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _L("Shape"));
sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font());
sbsizer->GetStaticBox()->Enable(m_can_edit);
wxGetApp().UpdateDarkUI(sbsizer->GetStaticBox());
// shape options
m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP);
wxGetApp().UpdateDarkUI(m_shape_options_book->GetChoiceCtrl());
sbsizer->Add(m_shape_options_book);
auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::PageType::Rectangle));
BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize,m_can_edit);
// QDS hide
BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin, m_can_edit);
activate_options_page(optgroup);
// QDS hide
/* optgroup = init_shape_options_page(BedShape::get_name(BedShape::PageType::Circle));
BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter);
activate_options_page(optgroup);
optgroup = init_shape_options_page(BedShape::get_name(BedShape::PageType::Custom));
Line line{ "", "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
Button* shape_btn = new Button(parent, _L("Load shape from STL..."));
wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL);
shape_sizer->Add(shape_btn, 1, wxEXPAND);
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(shape_sizer, 1, wxEXPAND);
shape_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
load_stl();
});
return sizer;
};
optgroup->append_line(line);
activate_options_page(optgroup);*/
wxPanel* texture_panel = init_texture_panel();
wxPanel* model_panel = init_model_panel();
Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) { update_shape(); }));
// right pane with preview canvas
m_canvas = new Bed_2D(this);
m_canvas->SetMinSize({ FromDIP(320), FromDIP(320) });
m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent& e) { m_canvas->repaint(m_shape); });
m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { m_canvas->Refresh(); });
wxSizer* left_sizer = new wxBoxSizer(wxVERTICAL);
left_sizer->Add(sbsizer, 0, wxEXPAND);
left_sizer->Add(texture_panel, 1, wxEXPAND);
left_sizer->Add(model_panel, 1, wxEXPAND);
wxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL);
top_sizer->Add(left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10);
top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10);
SetSizerAndFit(top_sizer);
set_shape(default_pt);
update_preview();
}
// Called from the constructor.
// Create a panel for a rectangular / circular / custom bed shape.
ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title)
{
wxPanel* panel = new wxPanel(m_shape_options_book);
panel->SetBackgroundColour(*wxWHITE);
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _L("Settings"));
optgroup->label_width = 10;
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
update_shape();
};
m_optgroups.push_back(optgroup);
// panel->SetSizerAndFit(optgroup->sizer);
m_shape_options_book->AddPage(panel, title);
return optgroup;
}
void BedShapePanel::activate_options_page(ConfigOptionsGroupShp options_group)
{
options_group->activate();
options_group->parent()->SetSizerAndFit(options_group->sizer);
}
wxPanel *BedShapePanel::init_texture_panel()
{
wxPanel *panel = new wxPanel(this);
wxGetApp().UpdateDarkUI(panel, true);
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _L("Texture"));
optgroup->label_width = 10;
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { update_shape(); };
Line line{"", ""};
line.full_width = 1;
line.widget = [this](wxWindow *parent) {
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Disabled), std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Hovered),
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
StateColor btn_bd_white(std::pair<wxColour, int>(*wxWHITE, StateColor::Disabled), std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
Button* load_btn = new Button(parent, _L("Load..."));
load_btn->SetBackgroundColor(btn_bg_white);
load_btn->SetBorderColor(btn_bd_white);
load_btn->SetBackgroundColour(*wxWHITE);
load_btn->Enable(m_can_edit);
wxSizer * load_sizer = new wxBoxSizer(wxHORIZONTAL);
load_sizer->Add(load_btn, 1, wxEXPAND);
wxStaticText *filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE));
wxSizer *filename_sizer = new wxBoxSizer(wxHORIZONTAL);
filename_sizer->Add(filename_lbl, 1, wxEXPAND);
Button* remove_btn = new Button(parent, _L("Remove"));
remove_btn->SetBackgroundColor(btn_bg_white);
remove_btn->SetBorderColor(btn_bd_white);
remove_btn->SetBackgroundColour(*wxWHITE);
wxSizer * remove_sizer = new wxBoxSizer(wxHORIZONTAL);
remove_sizer->Add(remove_btn, 1, wxEXPAND);
wxSizer *sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(filename_sizer, 1, wxEXPAND);
sizer->Add(load_sizer, 1, wxEXPAND);
sizer->Add(remove_sizer, 1, wxEXPAND | wxTOP, 2);
load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent &e) { load_texture(); }));
remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent &e) {
m_custom_texture = NONE;
update_shape();
}));
filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent &e) {
wxGCDC dc;
auto text = wxControl::Ellipsize(_L(boost::filesystem::path(m_custom_texture).filename().string()), dc, wxELLIPSIZE_END, FromDIP(150));
e.SetText(text);
wxStaticText *lbl = dynamic_cast<wxStaticText *>(e.GetEventObject());
if (lbl != nullptr) {
bool exists = (m_custom_texture == NONE) || boost::filesystem::exists(m_custom_texture);
lbl->SetForegroundColour(exists ? wxGetApp().get_label_clr_default() : wxColor(*wxRED));
wxString tooltip_text = "";
if (m_custom_texture != NONE) {
if (!exists) tooltip_text += _L("Not found:") + " ";
tooltip_text += _(m_custom_texture);
}
wxToolTip *tooltip = lbl->GetToolTip();
if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text)) lbl->SetToolTip(tooltip_text);
}
}));
remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent &e) { e.Enable(m_custom_texture != NONE); }));
parent->SetBackgroundColour(*wxWHITE);
return sizer;
};
optgroup->append_line(line);
optgroup->activate();
panel->SetSizerAndFit(optgroup->sizer);
return panel;
}
wxPanel *BedShapePanel::init_model_panel()
{
wxPanel *panel = new wxPanel(this);
wxGetApp().UpdateDarkUI(panel, true);
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _L("Model"));
optgroup->label_width = 10;
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { update_shape(); };
Line line{"", ""};
line.full_width = 1;
line.widget = [this](wxWindow *parent) {
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Disabled), std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Hovered),
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
StateColor btn_bd_white(std::pair<wxColour, int>(*wxWHITE, StateColor::Disabled), std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
Button* load_btn = new Button(parent, _L("Load..."));
load_btn->SetBackgroundColor(btn_bg_white);
load_btn->SetBorderColor(btn_bd_white);
load_btn->SetBackgroundColour(*wxWHITE);
load_btn->Enable(m_can_edit);
wxSizer * load_sizer = new wxBoxSizer(wxHORIZONTAL);
load_sizer->Add(load_btn, 1, wxEXPAND);
wxStaticText *filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE));
wxSizer * filename_sizer = new wxBoxSizer(wxHORIZONTAL);
filename_sizer->Add(filename_lbl, 1, wxEXPAND);
Button* remove_btn = new Button(parent, _L("Remove"));
remove_btn->SetBackgroundColor(btn_bg_white);
remove_btn->SetBorderColor(btn_bd_white);
remove_btn->SetBackgroundColour(*wxWHITE);
wxSizer * remove_sizer = new wxBoxSizer(wxHORIZONTAL);
remove_sizer->Add(remove_btn, 1, wxEXPAND);
wxSizer *sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(filename_sizer, 1, wxEXPAND);
sizer->Add(load_sizer, 1, wxEXPAND);
sizer->Add(remove_sizer, 1, wxEXPAND | wxTOP, 2);
load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent &e) { load_model(); }));
remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent &e) {
m_custom_model = NONE;
update_shape();
}));
filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent &e) {
wxGCDC dc;
auto text = wxControl::Ellipsize(_L(boost::filesystem::path(m_custom_model).filename().string()), dc, wxELLIPSIZE_END, FromDIP(150));
e.SetText(text);
wxStaticText *lbl = dynamic_cast<wxStaticText *>(e.GetEventObject());
if (lbl != nullptr) {
bool exists = (m_custom_model == NONE) || boost::filesystem::exists(m_custom_model);
lbl->SetForegroundColour(exists ? wxGetApp().get_label_clr_default() : wxColor(*wxRED));
wxString tooltip_text = "";
if (m_custom_model != NONE) {
if (!exists) tooltip_text += _L("Not found:") + " ";
tooltip_text += _(m_custom_model);
}
wxToolTip *tooltip = lbl->GetToolTip();
if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text)) lbl->SetToolTip(tooltip_text);
}
}));
remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent &e) { e.Enable(m_custom_model != NONE); }));
parent->SetBackgroundColour(*wxWHITE);
return sizer;
};
optgroup->append_line(line);
optgroup->activate();
panel->SetSizerAndFit(optgroup->sizer);
return panel;
}
// Called from the constructor.
// Set the initial bed shape from a list of points.
// Deduce the bed shape type(rect, circle, custom)
// This routine shall be smart enough if the user messes up
// with the list of points in the ini file directly.
void BedShapePanel::set_shape(const ConfigOptionPoints& points)
{
BedShape shape(points);
m_shape_options_book->SetSelection(int(shape.get_page_type()));
shape.apply_optgroup_values(m_optgroups[int(shape.get_page_type())]);
// Copy the polygon to the canvas, make a copy of the array, if custom shape is selected
if (shape.is_custom())
m_loaded_shape = points.values;
update_shape();
return;
}
void BedShapePanel::update_preview()
{
if (m_canvas) m_canvas->Refresh();
Refresh();
}
// Update the bed shape from the dialog fields.
void BedShapePanel::update_shape()
{
auto page_idx = m_shape_options_book->GetSelection();
auto opt_group = m_optgroups[page_idx];
switch (static_cast<BedShape::PageType>(page_idx)) {
case BedShape::PageType::Rectangle: {
m_is_valid = true;
Vec2d rect_size(Vec2d::Zero());
Vec2d rect_origin(Vec2d::Zero());
try {
rect_size = boost::any_cast<Vec2d>(opt_group->get_value("rect_size"));
} catch (const std::exception & /* e */) {
return;
}
// QDS
try {
rect_origin = boost::any_cast<Vec2d>(opt_group->get_value("rect_origin"));
} catch (const std::exception & /* e */) {
return;
}
auto x = rect_size(0);
auto y = rect_size(1);
// empty strings or '-' or other things
if (x == 0 || y == 0) {
m_is_valid = false;
return;
}
double x0 = 0.0;
double y0 = 0.0;
double x1 = x;
double y1 = y;
auto dx = rect_origin(0);
auto dy = rect_origin(1);
if (dx >= x || dy >= y) {
m_is_valid = false;
return;
}
x0 -= dx;
x1 -= dx;
y0 -= dy;
y1 -= dy;
m_shape = { Vec2d(x0, y0),
Vec2d(x1, y0),
Vec2d(x1, y1),
Vec2d(x0, y1) };
break;
}
case BedShape::PageType::Circle:
{
double diameter;
try { diameter = boost::any_cast<double>(opt_group->get_value("diameter")); }
catch (const std::exception & /* e */) { return; }
if (diameter == 0.0) return ;
auto r = diameter / 2;
auto twopi = 2 * PI;
// Don't change this value without adjusting BuildVolume constructor detecting circle diameter!
auto edges = 72;
std::vector<Vec2d> points;
for (int i = 1; i <= edges; ++i) {
auto angle = i * twopi / edges;
points.push_back(Vec2d(r*cos(angle), r*sin(angle)));
}
m_shape = points;
break;
}
case BedShape::PageType::Custom:
m_shape = m_loaded_shape;
break;
}
update_preview();
}
// Loads an stl file, projects it to the XY plane and calculates a polygon.
void BedShapePanel::load_stl()
{
wxFileDialog dialog(this, _L("Choose an STL file to import bed shape from:"), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
return;
std::string file_name = dialog.GetPath().ToUTF8().data();
if (!boost::algorithm::iends_with(file_name, ".stl")) {
show_error(this, _L("Invalid file format."));
return;
}
wxBusyCursor wait;
Model model;
try {
model = Model::read_from_file(file_name);
}
catch (std::exception &) {
show_error(this, _L("Error! Invalid model"));
return;
}
auto mesh = model.mesh();
auto expolygons = mesh.horizontal_projection();
if (expolygons.size() == 0) {
show_error(this, _L("The selected file contains no geometry."));
return;
}
if (expolygons.size() > 1) {
show_error(this, _L("The selected file contains several disjoint areas. This is not supported."));
return;
}
auto polygon = expolygons[0].contour;
std::vector<Vec2d> points;
for (auto pt : polygon.points)
points.push_back(unscale(pt));
m_loaded_shape = points;
update_shape();
}
void BedShapePanel::load_texture()
{
wxFileDialog dialog(this, _L("Choose a file to import bed texture from (PNG/SVG):"), "", "",
file_wildcards(FT_TEX), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
return;
m_custom_texture = NONE;
std::string file_name = dialog.GetPath().ToUTF8().data();
if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg")) {
show_error(this, _L("Invalid file format."));
return;
}
bool try_ok;
if (Utils::is_file_too_large(file_name, try_ok)) {
if (try_ok) {
wxMessageBox(wxString::Format(_L("The file exceeds %d MB, please import again."), STL_SVG_MAX_FILE_SIZE_MB), "Error", wxOK | wxICON_ERROR);
} else {
wxMessageBox(_L("Exception in obtaining file size, please import again."));
}
return;
}
wxBusyCursor wait;
m_custom_texture = file_name;
Utils::slash_to_back_slash(m_custom_texture);
update_shape();
}
void BedShapePanel::load_model()
{
wxFileDialog dialog(this, _L("Choose an STL file to import bed model from:"), "", "",
file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
return;
m_custom_model = NONE;
std::string file_name = dialog.GetPath().ToUTF8().data();
if (!boost::algorithm::iends_with(file_name, ".stl")) {
show_error(this, _L("Invalid file format."));
return;
}
bool try_ok;
if (Utils::is_file_too_large(file_name, try_ok)) {
if (try_ok) {
wxMessageBox(wxString::Format(_L("The file exceeds %d MB, please import again."), STL_SVG_MAX_FILE_SIZE_MB), "Error", wxOK | wxICON_ERROR);
} else {
wxMessageBox(_L("Exception in obtaining file size, please import again."));
}
return;
}
wxBusyCursor wait;
m_custom_model = file_name;
Utils::slash_to_back_slash(m_custom_model);
update_shape();
}
} // GUI
} // Slic3r

View File

@@ -0,0 +1,118 @@
#ifndef slic3r_BedShapeDialog_hpp_
#define slic3r_BedShapeDialog_hpp_
// The bed shape dialog.
// The dialog opens from Print Settins tab->Bed Shape : Set...
#include "GUI_Utils.hpp"
#include "2DBed.hpp"
#include "I18N.hpp"
#include <libslic3r/BuildVolume.hpp>
#include <wx/dialog.h>
#include <wx/choicebk.h>
namespace Slic3r {
namespace GUI {
class ConfigOptionsGroup;
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
struct BedShape
{
enum class PageType {
Rectangle,
Circle,
Custom
};
enum class Parameter {
RectSize,
RectOrigin,
Diameter
};
BedShape(const ConfigOptionPoints& points);
bool is_custom() { return m_build_volume.type() == BuildVolume::Type::Convex || m_build_volume.type() == BuildVolume::Type::Custom; }
static void append_option_line(ConfigOptionsGroupShp optgroup, Parameter param,bool can_edit);
static wxString get_name(PageType type);
PageType get_page_type();
wxString get_full_name_with_params();
void apply_optgroup_values(ConfigOptionsGroupShp optgroup);
private:
BuildVolume m_build_volume;
};
class BedShapePanel : public wxPanel
{
static const std::string NONE;
static const std::string EMPTY_STRING;
Bed_2D* m_canvas;
std::vector<Vec2d> m_shape;
std::vector<Vec2d> m_loaded_shape;
std::string m_custom_texture;
std::string m_custom_model;
bool m_is_valid{true};
bool m_can_edit{true};
public:
BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY), m_custom_texture(NONE), m_custom_model(NONE) {}
void build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model);
// Returns the resulting bed shape polygon. This value will be stored to the ini file.
const std::vector<Vec2d>& get_shape() const { return m_shape; }
const std::string& get_custom_texture() const { return (m_custom_texture != NONE) ? m_custom_texture : EMPTY_STRING; }
const std::string& get_custom_model() const { return (m_custom_model != NONE) ? m_custom_model : EMPTY_STRING; }
bool get_valid() { return m_is_valid; }
void set_edit_state(bool flag) { m_can_edit = flag; }
private:
ConfigOptionsGroupShp init_shape_options_page(const wxString& title);
void activate_options_page(ConfigOptionsGroupShp options_group);
wxPanel* init_texture_panel();
wxPanel* init_model_panel();
void set_shape(const ConfigOptionPoints& points);
void update_preview();
void update_shape();
void load_stl();
void load_texture();
void load_model();
wxChoicebook* m_shape_options_book;
std::vector <ConfigOptionsGroupShp> m_optgroups;
friend class BedShapeDialog;
};
class BedShapeDialog : public DPIDialog
{
BedShapePanel* m_panel;
public:
BedShapeDialog(wxWindow* parent) : DPIDialog(parent, wxID_ANY, _(L("Bed Shape")),
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {}
void build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model,bool can_edit);
const std::vector<Vec2d>& get_shape() const { return m_panel->get_shape(); }
const std::string& get_custom_texture() const { return m_panel->get_custom_texture(); }
const std::string& get_custom_model() const { return m_panel->get_custom_model(); }
bool get_valid() { return m_panel->get_valid(); }
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
};
} // GUI
} // Slic3r
#endif /* slic3r_BedShapeDialog_hpp_ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,169 @@
#ifndef slic3r_BindDialog_hpp_
#define slic3r_BindDialog_hpp_
#include "I18N.hpp"
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/gauge.h>
#include <wx/button.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/dialog.h>
#include <curl/curl.h>
#include <wx/webrequest.h>
#include <wx/hyperlink.h>
#include "wxExtensions.hpp"
#include "Plater.hpp"
#include "Widgets/StepCtrl.hpp"
#include "Widgets/ProgressDialog.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/ProgressBar.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Jobs/BindJob.hpp"
#include "QDTStatusBar.hpp"
#include "QDTStatusBarBind.hpp"
#define BIND_DIALOG_GREY200 wxColour(248, 248, 248)
#define BIND_DIALOG_GREY800 wxColour(50, 58, 61)
#define BIND_DIALOG_GREY900 wxColour(38, 46, 48)
#define BIND_DIALOG_BUTTON_SIZE wxSize(FromDIP(68), FromDIP(24))
#define BIND_DIALOG_BUTTON_PANEL_SIZE wxSize(FromDIP(450), FromDIP(30))
#define PING_CODE_LENGTH 6
namespace Slic3r { namespace GUI {
struct MemoryStruct
{
char * memory;
size_t read_pos;
size_t size;
};
class PingCodeBindDialog : public DPIDialog
{
private:
Label* m_status_text;
wxStaticText* m_text_input_title;
wxStaticText* m_link_show_ping_code_wiki;
TextInput* m_text_input_single_code[PING_CODE_LENGTH];
Button* m_button_bind;
Button* m_button_cancel;
Button* m_button_close;
wxSimplebook* m_simplebook;
wxPanel* request_bind_panel;
wxPanel* binding_panel;
wxScrolledWindow* m_sw_bind_failed_info;
Label* m_bind_failed_info;
Label* m_st_txt_error_code{ nullptr };
Label* m_st_txt_error_desc{ nullptr };
Label* m_st_txt_extra_info{ nullptr };
wxHyperlinkCtrl* m_link_network_state{ nullptr };
wxString m_result_info;
wxString m_result_extra;
wxString m_ping_code_wiki;
bool m_show_error_info_state = true;
int m_result_code;
std::shared_ptr<QDTStatusBarBind> m_status_bar;
public:
PingCodeBindDialog(Plater* plater = nullptr);
~PingCodeBindDialog();
void on_key_input(wxKeyEvent& evt);
void on_text_changed(wxCommandEvent& event);
void on_key_backspace(wxKeyEvent& event);
void on_cancel(wxCommandEvent& event);
void on_bind_printer(wxCommandEvent& event);
void on_dpi_changed(const wxRect& suggested_rect) override;
};
class BindMachineDialog : public DPIDialog
{
private:
wxWindow* m_panel_agreement;
wxStaticText * m_printer_name;
wxStaticText * m_user_name;
StaticBox * m_panel_left;
StaticBox * m_panel_right;
wxStaticText *m_status_text;
wxStaticText* m_link_show_error;
Button * m_button_bind;
Button * m_button_cancel;
wxSimplebook *m_simplebook;
wxStaticBitmap *m_avatar;
wxStaticBitmap *m_printer_img;
wxStaticBitmap *m_static_bitmap_show_error;
wxBitmap m_bitmap_show_error_close;
wxBitmap m_bitmap_show_error_open;
wxScrolledWindow* m_sw_bind_failed_info;
Label* m_bind_failed_info;
Label* m_st_txt_error_code{ nullptr };
Label* m_st_txt_error_desc{ nullptr };
Label* m_st_txt_extra_info{ nullptr };
wxHyperlinkCtrl* m_link_network_state{ nullptr };
wxString m_result_info;
wxString m_result_extra;
bool m_show_error_info_state = true;
bool m_allow_privacy{false};
bool m_allow_notice{false};
int m_result_code;
std::shared_ptr<int> m_tocken;
MachineObject * m_machine_info{nullptr};
std::shared_ptr<BindJob> m_bind_job;
std::shared_ptr<QDTStatusBarBind> m_status_bar;
public:
BindMachineDialog(Plater *plater = nullptr);
~BindMachineDialog();
void show_bind_failed_info(bool show, int code = 0, wxString description = wxEmptyString, wxString extra = wxEmptyString);
void on_cancel(wxCommandEvent& event);
void on_bind_fail(wxCommandEvent &event);
void on_update_message(wxCommandEvent &event);
void on_bind_success(wxCommandEvent &event);
void on_bind_printer(wxCommandEvent &event);
void on_dpi_changed(const wxRect &suggested_rect) override;
void update_machine_info(MachineObject *info);
void on_show(wxShowEvent &event);
void on_close(wxCloseEvent& event);
void on_destroy();
wxString get_print_error(wxString str);
};
class UnBindMachineDialog : public DPIDialog
{
protected:
wxStaticText * m_printer_name;
wxStaticText * m_user_name;
wxStaticText *m_status_text;
Button * m_button_unbind;
Button * m_button_cancel;
MachineObject *m_machine_info{nullptr};
wxStaticBitmap *m_avatar;
wxStaticBitmap *m_printer_img;
std::shared_ptr<int> m_tocken;
public:
UnBindMachineDialog(Plater *plater = nullptr);
~UnBindMachineDialog();
void on_cancel(wxCommandEvent &event);
void on_unbind_printer(wxCommandEvent &event);
void on_dpi_changed(const wxRect &suggested_rect) override;
void update_machine_info(MachineObject *info) { m_machine_info = info; };
void on_show(wxShowEvent &event);
};
}} // namespace Slic3r::GUI
#endif /* slic3r_BindDialog_hpp_ */

View File

@@ -0,0 +1,545 @@
#include "BitmapCache.hpp"
#include "libslic3r/Utils.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "GUI.hpp"
#include "GUI_Utils.hpp"
#include <boost/filesystem.hpp>
#ifdef __WXGTK2__
// Broken alpha workaround
#include <wx/mstream.h>
#include <wx/rawbmp.h>
#endif /* __WXGTK2__ */
#define NANOSVG_IMPLEMENTATION
#include "nanosvg/nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvg/nanosvgrast.h"
namespace Slic3r { namespace GUI {
BitmapCache::BitmapCache()
{
#ifdef __APPLE__
// Note: win->GetContentScaleFactor() is not used anymore here because it tends to
// return bogus results quite often (such as 1.0 on Retina or even 0.0).
// We're using the max scaling factor across all screens because it's very likely to be good enough.
m_scale = mac_max_scaling_factor();
#endif
}
void BitmapCache::clear()
{
for (std::pair<const std::string, wxBitmap*> &bitmap : m_map)
delete bitmap.second;
m_map.clear();
}
static wxBitmap wxImage_to_wxBitmap_with_alpha(wxImage &&image, float scale = 1.0f)
{
#ifdef __WXGTK2__
// Broken alpha workaround
wxMemoryOutputStream stream;
image.SaveFile(stream, wxBITMAP_TYPE_PNG);
wxStreamBuffer *buf = stream.GetOutputStreamBuffer();
return wxBitmap::NewFromPNGData(buf->GetBufferStart(), buf->GetBufferSize());
#else
#ifdef __APPLE__
// This is a c-tor native to Mac OS. We need to let the Mac OS wxBitmap implementation
// know that the image may already be scaled appropriately for Retina,
// and thereby that it's not supposed to upscale it.
// Contrary to intuition, the `scale` argument isn't "please scale this to such and such"
// but rather "the wxImage is sized for backing scale such and such".
return wxBitmap(std::move(image), -1, scale);
#else
return wxBitmap(std::move(image));
#endif
#endif
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height)
{
wxBitmap *bitmap = nullptr;
auto it = m_map.find(bitmap_key);
if (it == m_map.end()) {
bitmap = new wxBitmap(width, height
#ifdef __WXGTK3__
, 32
#endif
);
#ifdef __APPLE__
// Contrary to intuition, the `scale` argument isn't "please scale this to such and such"
// but rather "the wxImage is sized for backing scale such and such".
// So, We need to let the Mac OS wxBitmap implementation
// know that the image may already be scaled appropriately for Retina,
// and thereby that it's not supposed to upscale it.
bitmap->CreateScaled(width, height, -1, m_scale);
#endif
m_map[bitmap_key] = bitmap;
} else {
bitmap = it->second;
if (size_t(bitmap->GetWidth()) != width || size_t(bitmap->GetHeight()) != height)
bitmap->Create(width, height);
}
#if defined(WIN32) || defined(__APPLE__)
// Not needed or harmful for GTK2 and GTK3.
bitmap->UseAlpha();
#endif
return bitmap;
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp)
{
wxBitmap *bitmap = nullptr;
auto it = m_map.find(bitmap_key);
if (it == m_map.end()) {
bitmap = new wxBitmap(bmp);
m_map[bitmap_key] = bitmap;
} else {
bitmap = it->second;
*bitmap = bmp;
}
return bitmap;
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2)
{
// Copying the wxBitmaps is cheap as the bitmap's content is reference counted.
const wxBitmap bmps[2] = { bmp, bmp2 };
return this->insert(bitmap_key, bmps, bmps + 2);
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3)
{
// Copying the wxBitmaps is cheap as the bitmap's content is reference counted.
const wxBitmap bmps[3] = { bmp, bmp2, bmp3 };
return this->insert(bitmap_key, bmps, bmps + 3);
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *begin, const wxBitmap *end)
{
size_t width = 0;
size_t height = 0;
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
#ifdef __APPLE__
width += bmp->GetScaledWidth();
height = std::max<size_t>(height, bmp->GetScaledHeight());
#else
width += bmp->GetWidth();
height = std::max<size_t>(height, bmp->GetHeight());
#endif
}
#ifdef __WXGTK2__
// Broken alpha workaround
wxImage image(width, height);
image.InitAlpha();
// Fill in with a white color.
memset(image.GetData(), 0x0ff, width * height * 3);
// Fill in with full transparency.
memset(image.GetAlpha(), 0, width * height);
size_t x = 0;
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
if (bmp->GetWidth() > 0) {
if (bmp->GetDepth() == 32) {
wxAlphaPixelData data(*const_cast<wxBitmap*>(bmp));
//FIXME The following method is missing from wxWidgets 3.1.1.
// It looks like the wxWidgets 3.0.3 called the wrapped bitmap's UseAlpha().
//data.UseAlpha();
if (data) {
for (int r = 0; r < bmp->GetHeight(); ++ r) {
wxAlphaPixelData::Iterator src(data);
src.Offset(data, 0, r);
unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3;
unsigned char *dst_alpha = image.GetAlpha() + x + r * width;
for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) {
*dst_pixels ++ = src.Red();
*dst_pixels ++ = src.Green();
*dst_pixels ++ = src.Blue();
*dst_alpha ++ = src.Alpha();
}
}
}
} else if (bmp->GetDepth() == 24) {
wxNativePixelData data(*const_cast<wxBitmap*>(bmp));
if (data) {
for (int r = 0; r < bmp->GetHeight(); ++ r) {
wxNativePixelData::Iterator src(data);
src.Offset(data, 0, r);
unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3;
unsigned char *dst_alpha = image.GetAlpha() + x + r * width;
for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) {
*dst_pixels ++ = src.Red();
*dst_pixels ++ = src.Green();
*dst_pixels ++ = src.Blue();
*dst_alpha ++ = wxALPHA_OPAQUE;
}
}
}
}
}
x += bmp->GetWidth();
}
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
#else
wxBitmap *bitmap = this->insert(bitmap_key, width, height);
wxMemoryDC memDC;
memDC.SelectObject(*bitmap);
memDC.SetBackground(*wxTRANSPARENT_BRUSH);
memDC.Clear();
size_t x = 0;
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
if (bmp->GetWidth() > 0)
memDC.DrawBitmap(*bmp, x, 0, true);
#ifdef __APPLE__
// we should "move" with step equal to non-scaled width
x += bmp->GetScaledWidth();
#else
x += bmp->GetWidth();
#endif
}
memDC.SelectObject(wxNullBitmap);
return bitmap;
#endif
}
wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale/* = false*/)
{
wxImage image(width, height);
image.InitAlpha();
unsigned char *rgb = image.GetData();
unsigned char *alpha = image.GetAlpha();
unsigned int pixels = width * height;
for (unsigned int i = 0; i < pixels; ++ i) {
*rgb ++ = *raw_data ++;
*rgb ++ = *raw_data ++;
*rgb ++ = *raw_data ++;
*alpha ++ = *raw_data ++;
}
if (grayscale)
image = image.ConvertToGreyscale(m_gs, m_gs, m_gs);
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image), m_scale));
}
wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width, unsigned height,
const bool grayscale/* = false*/, const float scale_in_center/* = 0*/) // QDS: support resize by fill border
{
std::string bitmap_key = bitmap_name + ( height !=0 ?
"-h" + std::to_string(height) :
"-w" + std::to_string(width))
+ (grayscale ? "-gs" : "");
auto it = m_map.find(bitmap_key);
if (it != m_map.end())
return it->second;
wxImage image;
if (! image.LoadFile(Slic3r::GUI::from_u8(Slic3r::var(bitmap_name + ".png")), wxBITMAP_TYPE_PNG) ||
image.GetWidth() == 0 || image.GetHeight() == 0)
return nullptr;
if (height == 0 && width == 0)
height = image.GetHeight();
if (height != 0 && unsigned(image.GetHeight()) != height)
width = unsigned(0.5f + float(image.GetWidth()) * height / image.GetHeight());
else if (width != 0 && unsigned(image.GetWidth()) != width)
height = unsigned(0.5f + float(image.GetHeight()) * width / image.GetWidth());
if (height != 0 && width != 0) {
// QDS: support resize by fill border
if (scale_in_center > 0)
image.Resize({ (int)width, (int)height }, { (int)(width - image.GetWidth()) / 2, (int)(height - image.GetHeight()) / 2 });
else
image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
}
if (grayscale)
image = image.ConvertToGreyscale(m_gs, m_gs, m_gs);
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
}
NSVGimage* BitmapCache::nsvgParseFromFileWithReplace(const char* filename, const char* units, float dpi, const std::map<std::string, std::string>& replaces)
{
std::string str;
FILE* fp = NULL;
size_t size;
char* data = NULL;
NSVGimage* image = NULL;
fp = boost::nowide::fopen(filename, "rb");
if (!fp) goto error;
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
data = (char*)malloc(size + 1);
if (data == NULL) goto error;
if (fread(data, 1, size, fp) != size) goto error;
data[size] = '\0'; // Must be null terminated.
fclose(fp);
if (replaces.empty())
image = nsvgParse(data, units, dpi);
else {
str.assign(data);
for (auto val : replaces)
boost::replace_all(str, val.first, val.second);
image = nsvgParse(str.data(), units, dpi);
}
free(data);
return image;
error:
if (fp) fclose(fp);
if (data) free(data);
if (image) nsvgDelete(image);
return NULL;
}
wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height,
const bool grayscale/* = false*/, const bool dark_mode/* = false*/, const std::string& new_color /*= ""*/, const float scale_in_center/* = 0*/)
{
std::string bitmap_key = bitmap_name + ( target_height !=0 ?
"-h" + std::to_string(target_height) :
"-w" + std::to_string(target_width))
+ (m_scale != 1.0f ? "-s" + float_to_string_decimal_point(m_scale) : "")
+ (dark_mode ? "-dm" : "")
+ (grayscale ? "-gs" : "")
+ new_color;
auto it = m_map.find(bitmap_key);
if (it != m_map.end())
return it->second;
// map of color replaces
std::map<std::string, std::string> replaces;
if (dark_mode) {
replaces["\"#262E30\""] = "\"#EFEFF0\"";
replaces["\"#323A3D\""] = "\"#B3B3B5\"";
replaces["\"#808080\""] = "\"#818183\"";
//replaces["\"#ACACAC\""] = "\"#54545A\"";
replaces["\"#CECECE\""] = "\"#54545B\"";
replaces["\"#6B6B6B\""] = "\"#818182\"";
replaces["\"#909090\""] = "\"#FFFFFF\"";
replaces["\"#00FF00\""] = "\"#FF0000\"";
}
//if (!new_color.empty())
// replaces["\"#ED6B21\""] = "\"" + new_color + "\"";
NSVGimage *image = nullptr;
if (strstr(bitmap_name.c_str(), "printer_thumbnail") == NULL) {
image = nsvgParseFromFileWithReplace(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f, replaces);
}
else {
std::map<std::string, std::string> temp_replaces;
image = nsvgParseFromFileWithReplace(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f, temp_replaces);
}
if (image == nullptr)
return nullptr;
if (target_height == 0 && target_width == 0)
target_height = image->height;
target_height != 0 ? target_height *= m_scale : target_width *= m_scale;
float svg_scale = target_height != 0 ?
(float)target_height / image->height : target_width != 0 ?
(float)target_width / image->width : 1;
int width = (int)(svg_scale * image->width + 0.5f);
int height = (int)(svg_scale * image->height + 0.5f);
int n_pixels = width * height;
if (n_pixels <= 0) {
::nsvgDelete(image);
return nullptr;
}
NSVGrasterizer *rast = ::nsvgCreateRasterizer();
if (rast == nullptr) {
::nsvgDelete(image);
return nullptr;
}
std::vector<unsigned char> data(n_pixels * 4, 0);
// QDS: support resize by fill border
if (scale_in_center > 0 && scale_in_center < svg_scale) {
int w = (int)(image->width * scale_in_center);
int h = (int)(image->height * scale_in_center);
::nsvgRasterize(rast, image, 0, 0, scale_in_center, data.data() + int(height - h) / 2 * width * 4 + int(width - w) / 2 * 4, w, h, width * 4);
} else
::nsvgRasterize(rast, image, 0, 0, svg_scale, data.data(), width, height, width * 4);
::nsvgDeleteRasterizer(rast);
::nsvgDelete(image);
return this->insert_raw_rgba(bitmap_key, width, height, data.data(), grayscale);
}
wxBitmap* BitmapCache::load_svg2(const std::string& bitmap_name, unsigned target_width, unsigned target_height,
const bool grayscale/* = false*/, const bool dark_mode/* = false*/, const std::vector<std::string>& array_new_color /*= vector<std::string>()*/, const float scale_in_center/* = 0*/)
{
std::map<std::string, std::string> replaces;
if (array_new_color.size() == 2) {
replaces["#D9D9D9"] = array_new_color[0];
replaces["fill-opacity=\"1.0"] = array_new_color[1];
}
NSVGimage* image = nullptr;
image = nsvgParseFromFileWithReplace(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f, replaces);
if (image == nullptr)
return nullptr;
if (target_height == 0 && target_width == 0)
target_height = image->height;
target_height != 0 ? target_height *= m_scale : target_width *= m_scale;
float svg_scale = target_height != 0 ?
(float)target_height / image->height : target_width != 0 ?
(float)target_width / image->width : 1;
int width = (int)(svg_scale * image->width + 0.5f);
int height = (int)(svg_scale * image->height + 0.5f);
int n_pixels = width * height;
if (n_pixels <= 0) {
::nsvgDelete(image);
return nullptr;
}
NSVGrasterizer* rast = ::nsvgCreateRasterizer();
if (rast == nullptr) {
::nsvgDelete(image);
return nullptr;
}
std::vector<unsigned char> data(n_pixels * 4, 0);
// QDS: support resize by fill border
if (scale_in_center > 0 && scale_in_center < svg_scale) {
int w = (int)(image->width * scale_in_center);
int h = (int)(image->height * scale_in_center);
::nsvgRasterize(rast, image, 0, 0, scale_in_center, data.data() + int(height - h) / 2 * width * 4 + int(width - w) / 2 * 4, w, h, width * 4);
}
else
::nsvgRasterize(rast, image, 0, 0, svg_scale, data.data(), width, height, width * 4);
::nsvgDeleteRasterizer(rast);
::nsvgDelete(image);
const unsigned char * raw_data = data.data();
wxImage wx_image(width, height);
wx_image.InitAlpha();
unsigned char* rgb = wx_image.GetData();
unsigned char* alpha = wx_image.GetAlpha();
unsigned int pixels = width * height;
for (unsigned int i = 0; i < pixels; ++i) {
*rgb++ = *raw_data++;
*rgb++ = *raw_data++;
*rgb++ = *raw_data++;
*alpha++ = *raw_data++;
}
if (grayscale)
wx_image = wx_image.ConvertToGreyscale(m_gs, m_gs, m_gs);
auto result = new wxBitmap(wxImage_to_wxBitmap_with_alpha(std::move(wx_image), m_scale));
return result;
}
//we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap
wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling/* = false*/, size_t border_width /*= 0*/, bool dark_mode/* = false*/)
{
double scale = suppress_scaling ? 1.0f : m_scale;
width *= scale;
height *= scale;
wxImage image(width, height);
image.InitAlpha();
unsigned char* imgdata = image.GetData();
unsigned char* imgalpha = image.GetAlpha();
for (size_t i = 0; i < width * height; ++ i) {
*imgdata ++ = r;
*imgdata ++ = g;
*imgdata ++ = b;
*imgalpha ++ = transparency;
}
// Add border, make white/light spools easier to see
if (border_width > 0) {
// Restrict to width of image
if (border_width > height) border_width = height - 1;
if (border_width > width) border_width = width - 1;
auto px_data = (uint8_t*)image.GetData();
auto a_data = (uint8_t*)image.GetAlpha();
for (size_t x = 0; x < width; ++x) {
for (size_t y = 0; y < height; ++y) {
if (x < border_width || y < border_width ||
x >= (width - border_width) || y >= (height - border_width)) {
const size_t idx = (x + y * width);
const size_t idx_rgb = (x + y * width) * 3;
px_data[idx_rgb] = px_data[idx_rgb + 1] = px_data[idx_rgb + 2] = dark_mode ? 245u : 110u;
a_data[idx] = 255u;
}
}
}
}
return wxImage_to_wxBitmap_with_alpha(std::move(image), scale);
}
bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out)
{
if (scolor.size() == 9) {
unsigned char rgba[4];
parse_color4(scolor, rgba);
rgb_out[0] = rgba[0];
rgb_out[1] = rgba[1];
rgb_out[2] = rgba[2];
return true;
}
rgb_out[0] = rgb_out[1] = rgb_out[2] = 0;
if (scolor.size() != 7 || scolor.front() != '#')
return false;
const char* c = scolor.data() + 1;
for (size_t i = 0; i < 3; ++i) {
int digit1 = hex_digit_to_int(*c++);
int digit2 = hex_digit_to_int(*c++);
if (digit1 == -1 || digit2 == -1)
return false;
rgb_out[i] = (unsigned char)(digit1 * 16 + digit2);
}
return true;
}
bool BitmapCache::parse_color4(const std::string& scolor, unsigned char* rgba_out)
{
rgba_out[0] = rgba_out[1] = rgba_out[2] = 0; rgba_out[3] = 255;
if ((scolor.size() != 7 && scolor.size() != 9) || scolor.front() != '#')
return false;
const char* c = scolor.data() + 1;
for (size_t i = 0; i < scolor.size() / 2; ++i) {
int digit1 = hex_digit_to_int(*c++);
int digit2 = hex_digit_to_int(*c++);
if (digit1 == -1 || digit2 == -1)
return false;
rgba_out[i] = (unsigned char)(digit1 * 16 + digit2);
}
return true;
}
} // namespace GUI
} // namespace Slic3r

View File

@@ -0,0 +1,64 @@
#ifndef SLIC3R_GUI_BITMAP_CACHE_HPP
#define SLIC3R_GUI_BITMAP_CACHE_HPP
#include <map>
#include <vector>
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
struct NSVGimage;
namespace Slic3r { namespace GUI {
class BitmapCache
{
public:
BitmapCache();
~BitmapCache() { clear(); }
void clear();
double scale() { return m_scale; }
wxBitmap* find(const std::string &name) { auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; }
const wxBitmap* find(const std::string &name) const { return const_cast<BitmapCache*>(this)->find(name); }
wxBitmap* insert(const std::string &name, size_t width, size_t height);
wxBitmap* insert(const std::string &name, const wxBitmap &bmp);
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2);
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3);
wxBitmap* insert(const std::string &name, const std::vector<wxBitmap> &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); }
wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end);
wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale = false);
// QDS: support resize by fill border (scale_in_center)
// Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero.
wxBitmap* load_png(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const float scale_in_center = 0.f);
// Parses SVG file from a file, returns SVG image as paths.
// And makes replases befor parsing
// replace_map containes old_value->new_value
static NSVGimage* nsvgParseFromFileWithReplace(const char* filename, const char* units, float dpi, const std::map<std::string, std::string>& replaces);
// Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width.
wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = "", const float scale_in_center = 0.f);
//Load background image of semi transparent material with color,
wxBitmap* load_svg2(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::vector<std::string>& array_new_color = std::vector<std::string>(), const float scale_in_center = 0.0f);
wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false);
wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3], bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); }
wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); }
static bool parse_color(const std::string& scolor, unsigned char* rgb_out);
static bool parse_color4(const std::string& scolor, unsigned char* rgba_out);
private:
std::map<std::string, wxBitmap*> m_map;
double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs)
double m_scale = 1.0; // value, used for correct scaling of SVG icons on Retina display
};
} // GUI
} // Slic3r
#endif /* SLIC3R_GUI_BITMAP_CACHE_HPP */

View File

@@ -0,0 +1,286 @@
#include "BitmapComboBox.hpp"
#include <cstddef>
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/button.h>
#include <wx/statbox.h>
#include <wx/colordlg.h>
#include <wx/wupdlock.h>
#include <wx/menu.h>
#include <wx/odcombo.h>
#include <wx/listbook.h>
#include <wx/window.h>
#ifdef __WINDOWS__
#include <wx/msw/dcclient.h>
#include <wx/msw/private.h>
#ifdef _MSW_DARK_MODE
#include "dark_mode.hpp"
#endif //_MSW_DARK_MODE
#endif //__WINDOWS__
#include "libslic3r/libslic3r.h"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
// A workaround for a set of issues related to text fitting into gtk widgets:
// See e.g.: https://github.com/prusa3d/PrusaSlicer/issues/4584
#if defined(__WXGTK20__) || defined(__WXGTK3__)
#include <glib-2.0/glib-object.h>
#include <pango-1.0/pango/pango-layout.h>
#include <gtk/gtk.h>
#endif
using Slic3r::GUI::format_wxstr;
#define BORDER_W 10
// ---------------------------------
// *** BitmapComboBox ***
// ---------------------------------
namespace Slic3r {
namespace GUI {
/* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina
* (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean
* "please scale this to such and such" but rather
* "the wxImage is already sized for backing scale such and such". )
* Unfortunately, the constructor changes the size of wxBitmap too.
* Thus We need to use unscaled size value for bitmaps that we use
* to avoid scaled size of control items.
* For this purpose control drawing methods and
* control size calculation methods (virtual) are overridden.
**/
BitmapComboBox::BitmapComboBox(wxWindow* parent,
wxWindowID id/* = wxID_ANY*/,
const wxString& value/* = wxEmptyString*/,
const wxPoint& pos/* = wxDefaultPosition*/,
const wxSize& size/* = wxDefaultSize*/,
int n/* = 0*/,
const wxString choices[]/* = NULL*/,
long style/* = 0*/) :
wxBitmapComboBox(parent, id, value, pos, size, n, choices, style)
{
SetFont(Slic3r::GUI::wxGetApp().normal_font());
#ifdef _WIN32
// Workaround for ignoring CBN_EDITCHANGE events, which are processed after the content of the combo box changes, so that
// the index of the item inside CBN_EDITCHANGE may no more be valid.
EnableTextChangedEvents(false);
wxGetApp().UpdateDarkUI(this);
if (!HasFlag(wxCB_READONLY))
wxTextEntry::SetMargins(0,0);
#endif /* _WIN32 */
}
BitmapComboBox::~BitmapComboBox()
{
}
#ifdef __APPLE__
bool BitmapComboBox::OnAddBitmap(const wxBitmap& bitmap)
{
if (bitmap.IsOk())
{
// we should use scaled! size values of bitmap
int width = (int)bitmap.GetScaledWidth();
int height = (int)bitmap.GetScaledHeight();
if (m_usedImgSize.x < 0)
{
// If size not yet determined, get it from this image.
m_usedImgSize.x = width;
m_usedImgSize.y = height;
// Adjust control size to vertically fit the bitmap
wxWindow* ctrl = GetControl();
ctrl->InvalidateBestSize();
wxSize newSz = ctrl->GetBestSize();
wxSize sz = ctrl->GetSize();
if (newSz.y > sz.y)
ctrl->SetSize(sz.x, newSz.y);
else
DetermineIndent();
}
wxCHECK_MSG(width == m_usedImgSize.x && height == m_usedImgSize.y,
false,
"you can only add images of same size");
return true;
}
return false;
}
void BitmapComboBox::OnDrawItem(wxDC& dc,
const wxRect& rect,
int item,
int flags) const
{
const wxBitmap& bmp = *(static_cast<wxBitmap*>(m_bitmaps[item]));
if (bmp.IsOk())
{
// we should use scaled! size values of bitmap
wxCoord w = bmp.GetScaledWidth();
wxCoord h = bmp.GetScaledHeight();
const int imgSpacingLeft = 4;
// Draw the image centered
dc.DrawBitmap(bmp,
rect.x + (m_usedImgSize.x - w) / 2 + imgSpacingLeft,
rect.y + (rect.height - h) / 2,
true);
}
wxString text = GetString(item);
if (!text.empty())
dc.DrawText(text,
rect.x + m_imgAreaWidth + 1,
rect.y + (rect.height - dc.GetCharHeight()) / 2);
}
#endif
#ifdef _WIN32
int BitmapComboBox::Append(const wxString& item)
{
// Workaround for a correct rendering of the control without Bitmap (under MSW):
//1. We should create small Bitmap to fill Bitmaps RefData,
// ! in this case wxBitmap.IsOK() return true.
//2. But then set width to 0 value for no using of bitmap left and right spacing
//3. Set this empty bitmap to the at list one item and BitmapCombobox will be recreated correct
wxBitmap bitmap(1, int(1.6 * wxGetApp().em_unit() + 1));
{
// bitmap.SetWidth(0); is depricated now
// so, use next code
bitmap.UnShare();// AllocExclusive();
bitmap.GetGDIImageData()->m_width = 0;
}
OnAddBitmap(bitmap);
const int n = wxComboBox::Append(item);
if (n != wxNOT_FOUND)
DoSetItemBitmap(n, bitmap);
return n;
}
enum OwnerDrawnComboBoxPaintingFlags
{
ODCB_PAINTING_DISABLED = 0x0004,
};
bool BitmapComboBox::MSWOnDraw(WXDRAWITEMSTRUCT* item)
{
LPDRAWITEMSTRUCT lpDrawItem = (LPDRAWITEMSTRUCT)item;
int pos = lpDrawItem->itemID;
// Draw default for item -1, which means 'focus rect only'
if (pos == -1)
return false;
int flags = 0;
if (lpDrawItem->itemState & ODS_COMBOBOXEDIT)
flags |= wxODCB_PAINTING_CONTROL;
if (lpDrawItem->itemState & ODS_SELECTED)
flags |= wxODCB_PAINTING_SELECTED;
if (lpDrawItem->itemState & ODS_DISABLED)
flags |= ODCB_PAINTING_DISABLED;
wxPaintDCEx dc(this, lpDrawItem->hDC);
wxRect rect = wxRectFromRECT(lpDrawItem->rcItem);
DrawBackground_(dc, rect, pos, flags);
wxString text;
if (flags & wxODCB_PAINTING_CONTROL)
{
// Don't draw anything in the editable selection field.
//if (!HasFlag(wxCB_READONLY))
// return true;
pos = GetSelection();
// Skip drawing if there is nothing selected.
if (pos < 0)
return true;
text = GetValue();
}
else
{
text = GetString(pos);
}
wxBitmapComboBoxBase::DrawItem(dc, rect, pos, text, flags);
return true;
}
void BitmapComboBox::DrawBackground_(wxDC& dc, const wxRect& rect, int WXUNUSED(item), int flags) const
{
if (flags & wxODCB_PAINTING_SELECTED)
{
const int vSizeDec = 0; // Vertical size reduction of selection rectangle edges
dc.SetTextForeground(wxGetApp().get_label_highlight_clr());
wxColour selCol = wxGetApp().get_highlight_default_clr();
dc.SetPen(selCol);
dc.SetBrush(selCol);
dc.DrawRectangle(rect.x,
rect.y + vSizeDec,
rect.width,
rect.height - (vSizeDec * 2));
}
else
{
dc.SetTextForeground(flags & ODCB_PAINTING_DISABLED ? wxColour(108,108,108) : wxGetApp().get_label_clr_default());
wxColour selCol = flags & ODCB_PAINTING_DISABLED ?
//#ifdef _MSW_DARK_MODE
//wxRGBToColour(NppDarkMode::GetSofterBackgroundColor()) :
//#else
wxGetApp().get_highlight_default_clr() :
//#endif
wxGetApp().get_window_default_clr();
dc.SetPen(selCol);
dc.SetBrush(selCol);
dc.DrawRectangle(rect);
}
}
void BitmapComboBox::Rescale()
{
// Next workaround: To correct scaling of a BitmapCombobox
// we need to refill control with new bitmaps
const wxString selection = this->GetValue();
std::vector<wxString> items;
for (size_t i = 0; i < GetCount(); i++)
items.push_back(GetString(i));
this->Clear();
for (const wxString& item : items)
Append(item);
this->SetValue(selection);
}
#endif
}}

View File

@@ -0,0 +1,64 @@
#ifndef slic3r_BitmapComboBox_hpp_
#define slic3r_BitmapComboBox_hpp_
#include <wx/bmpcbox.h>
#include <wx/gdicmn.h>
#include "GUI_Utils.hpp"
// ---------------------------------
// *** BitmapComboBox ***
// ---------------------------------
namespace Slic3r {
namespace GUI {
// BitmapComboBox used to presets list on Sidebar and Tabs
class BitmapComboBox : public wxBitmapComboBox
{
public:
BitmapComboBox(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxString& value = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
int n = 0,
const wxString choices[] = NULL,
long style = 0);
~BitmapComboBox();
#ifdef _WIN32
int Append(const wxString& item);
#endif
int Append(const wxString& item, const wxBitmap& bitmap)
{
return wxBitmapComboBox::Append(item, bitmap);
}
protected:
#ifdef __APPLE__
/* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina
* (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean
* "please scale this to such and such" but rather
* "the wxImage is already sized for backing scale such and such". )
* Unfortunately, the constructor changes the size of wxBitmap too.
* Thus We need to use unscaled size value for bitmaps that we use
* to avoid scaled size of control items.
* For this purpose control drawing methods and
* control size calculation methods (virtual) are overridden.
**/
bool OnAddBitmap(const wxBitmap& bitmap) override;
void OnDrawItem(wxDC& dc, const wxRect& rect, int item, int flags) const override;
#endif
#ifdef _WIN32
bool MSWOnDraw(WXDRAWITEMSTRUCT* item) override;
void DrawBackground_(wxDC& dc, const wxRect& rect, int WXUNUSED(item), int flags) const;
public:
void Rescale();
#endif
};
}}
#endif

View File

@@ -0,0 +1,394 @@
#include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers
#include "slic3r/Utils/Udp.hpp"
#include "BonjourDialog.hpp"
#include <set>
#include <mutex>
#include <boost/nowide/convert.hpp>
#include <wx/sizer.h>
#include <wx/button.h>
#include <wx/listctrl.h>
#include <wx/stattext.h>
#include <wx/timer.h>
#include <wx/wupdlock.h>
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/Utils/Bonjour.hpp"
#include "Widgets/Button.hpp"
#include "slic3r/Utils/Udp.hpp"
namespace Slic3r {
class BonjourReplyEvent : public wxEvent
{
public:
BonjourReply reply;
BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) :
wxEvent(winid, eventType),
reply(std::move(reply))
{}
virtual wxEvent *Clone() const
{
return new BonjourReplyEvent(*this);
}
};
// y3 add udp
// B29
class UdpReplyEvent : public wxEvent
{
public:
UdpReply reply;
UdpReplyEvent(wxEventType eventType, int winid, UdpReply &&reply) : wxEvent(winid, eventType), reply(std::move(reply)) {}
virtual wxEvent *Clone() const { return new UdpReplyEvent(*this); }
};
// B29
wxDEFINE_EVENT(EVT_UDP_REPLY, UdpReplyEvent);
wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent);
// y3
wxDECLARE_EVENT(EVT_UDP_COMPLETE, wxCommandEvent);
wxDEFINE_EVENT(EVT_UDP_COMPLETE, wxCommandEvent);
wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
class ReplySet: public std::set<BonjourReply> {};
// y3
class UdpReplySet : public std::set<UdpReply> {};
struct LifetimeGuard
{
std::mutex mutex;
BonjourDialog *dialog;
LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {}
};
// y3
BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
: wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
, list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSIMPLE_BORDER))
, replies(new ReplySet)
, udp_replies(new UdpReplySet)
, label(new wxStaticText(this, wxID_ANY, ""))
, timer(new wxTimer())
, timer_state(0)
, tech(tech)
{
const int em = GUI::wxGetApp().em_unit();
list->SetMinSize(wxSize(40 * em, 30 * em));
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em);
list->SetSingleStyle(wxLC_SINGLE_SEL);
list->SetSingleStyle(wxLC_SORT_DESCENDING);
//B29
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT);
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT);
// list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 20 * em);
// if (tech == ptFFF) {
// list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 5 * em);
// }
vsizer->Add(list, 1, wxEXPAND | wxALL, em);
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em);
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em);
// ^ Note: The Ok/Cancel labels are translated by wxWidgets
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
SetSizerAndFit(vsizer);
Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this);
Bind(EVT_UDP_REPLY, &BonjourDialog::on_udp_reply, this);
// B29
Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) {
this->timer_state = 0;
});
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
GUI::wxGetApp().UpdateDlgDarkUI(this);
}
BonjourDialog::~BonjourDialog()
{
// Needed bacuse of forward defs
}
bool BonjourDialog::show_and_lookup()
{
Show(); // Because we need GetId() to work before ShowModal()
timer->Stop();
timer->SetOwner(this);
timer_state = 1;
timer->Start(1000);
on_timer_process();
// The background thread needs to queue messages for this dialog
// and for that it needs a valid pointer to it (mandated by the wxWidgets API).
// Here we put the pointer under a shared_ptr and protect it by a mutex,
// so that both threads can access it safely.
auto dguard = std::make_shared<LifetimeGuard>(this);
// y3
// B29
Udp::TxtKeys udp_txt_keys{"version", "model"};
udp = Udp("octoprint")
.set_txt_keys(std::move(udp_txt_keys))
.set_retries(3)
.set_timeout(4)
.on_udp_reply([dguard](UdpReply &&reply) {
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
auto dialog = dguard->dialog;
if (dialog != nullptr) {
auto evt = new UdpReplyEvent(EVT_UDP_REPLY, dialog->GetId(), std::move(reply));
wxQueueEvent(dialog, evt);
}
})
.on_complete([dguard]() {
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
auto dialog = dguard->dialog;
if (dialog != nullptr) {
auto evt = new wxCommandEvent(EVT_UDP_COMPLETE, dialog->GetId());
wxQueueEvent(dialog, evt);
}
})
.lookup();
// Note: More can be done here when we support discovery of hosts other than Octoprint and SL1
Bonjour::TxtKeys txt_keys{"version", "model"};
bonjour = Bonjour("octoprint")
.set_txt_keys(std::move(txt_keys))
.set_retries(3)
.set_timeout(4)
.on_reply([dguard](BonjourReply &&reply) {
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
auto dialog = dguard->dialog;
if (dialog != nullptr) {
auto evt = new BonjourReplyEvent(EVT_BONJOUR_REPLY, dialog->GetId(), std::move(reply));
wxQueueEvent(dialog, evt);
}
})
.on_complete([dguard]() {
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
auto dialog = dguard->dialog;
if (dialog != nullptr) {
auto evt = new wxCommandEvent(EVT_BONJOUR_COMPLETE, dialog->GetId());
wxQueueEvent(dialog, evt);
}
})
.lookup();
bool res = ShowModal() == wxID_OK && list->GetFirstSelected() >= 0;
{
// Tell the background thread the dialog is going away...
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
dguard->dialog = nullptr;
}
return res;
}
wxString BonjourDialog::get_selected() const
{
auto sel = list->GetFirstSelected();
return sel >= 0 ? list->GetItemText(sel) : wxString();
}
// Private
void BonjourDialog::on_reply(BonjourReplyEvent &e)
{
if (replies->find(e.reply) != replies->end()) {
// We already have this reply
return;
}
// Filter replies based on selected technology
const auto model = e.reply.txt_data.find("model");
const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1";
if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) {
return;
}
replies->insert(std::move(e.reply));
auto selected = get_selected();
wxWindowUpdateLocker freeze_guard(this);
(void)freeze_guard;
list->DeleteAllItems();
// The whole list is recreated so that we benefit from it already being sorted in the set.
// (And also because wxListView's sorting API is bananas.)
for (const auto &reply : *replies) {
auto item = list->InsertItem(0, reply.full_address);
list->SetItem(item, 1, reply.hostname);
list->SetItem(item, 2, reply.service_name);
if (tech == ptFFF) {
const auto it = reply.txt_data.find("version");
if (it != reply.txt_data.end()) {
list->SetItem(item, 3, GUI::from_u8(it->second));
}
}
}
const int em = GUI::wxGetApp().em_unit();
for (int i = 0; i < list->GetColumnCount(); i++) {
list->SetColumnWidth(i, wxLIST_AUTOSIZE);
if (list->GetColumnWidth(i) < 10 * em) {
list->SetColumnWidth(i, 10 * em);
}
}
if (!selected.IsEmpty()) {
// Attempt to preserve selection
auto hit = list->FindItem(-1, selected);
if (hit >= 0) {
list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
}
}
}
// B29
void BonjourDialog::on_udp_reply(UdpReplyEvent &e)
{
if (udp_replies->find(e.reply) != udp_replies->end()) {
// We already have this reply
return;
}
//// Filter replies based on selected technology
// const auto model = e.reply.txt_data.find("model");
// const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1";
// if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) {
// return;
//}
udp_replies->insert(std::move(e.reply));
auto selected = get_selected();
wxWindowUpdateLocker freeze_guard(this);
(void) freeze_guard;
list->DeleteAllItems();
// The whole list is recreated so that we benefit from it already being sorted in the set.
// (And also because wxListView's sorting API is bananas.)
for (const auto &reply : *udp_replies) {
auto item = list->InsertItem(0, reply.service_name);
list->SetItem(item, 1, reply.hostname);
}
const int em = GUI::wxGetApp().em_unit();
for (int i = 0; i < list->GetColumnCount(); i++) {
list->SetColumnWidth(i, wxLIST_AUTOSIZE);
if (list->GetColumnWidth(i) < 10 * em) {
list->SetColumnWidth(i, 10 * em);
}
}
if (!selected.IsEmpty()) {
// Attempt to preserve selection
auto hit = list->FindItem(-1, selected);
if (hit >= 0) {
list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
}
}
}
void BonjourDialog::on_timer(wxTimerEvent &)
{
on_timer_process();
}
// This is here so the function can be bound to wxEVT_TIMER and also called
// explicitly (wxTimerEvent should not be created by user code).
void BonjourDialog::on_timer_process()
{
const auto search_str = _L("Searching for devices");
if (timer_state > 0) {
const std::string dots(timer_state, '.');
label->SetLabel(search_str + dots);
timer_state = (timer_state) % 3 + 1;
} else {
label->SetLabel(search_str + ": " + _L("Finished") + ".");
timer->Stop();
}
}
IPListDialog::IPListDialog(wxWindow* parent, const wxString& hostname, const std::vector<boost::asio::ip::address>& ips, size_t& selected_index)
: wxDialog(parent, wxID_ANY, _(L("Multiple resolved IP addresses")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, m_list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxSIMPLE_BORDER))
, m_selected_index (selected_index)
{
const int em = GUI::wxGetApp().em_unit();
m_list->SetMinSize(wxSize(40 * em, 30 * em));
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
auto* label = new wxStaticText(this, wxID_ANY, GUI::format_wxstr(_L("There are several IP addresses resolving to hostname %1%.\nPlease select one that should be used."), hostname));
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em);
m_list->SetSingleStyle(wxLC_SINGLE_SEL);
m_list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 40 * em);
for (size_t i = 0; i < ips.size(); i++)
m_list->InsertItem(i, boost::nowide::widen(ips[i].to_string()));
m_list->Select(0);
vsizer->Add(m_list, 1, wxEXPAND | wxALL, em);
wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL);
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em);
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em);
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
SetSizerAndFit(vsizer);
GUI::wxGetApp().UpdateDlgDarkUI(this);
}
IPListDialog::~IPListDialog()
{
}
void IPListDialog::EndModal(int retCode)
{
if (retCode == wxID_OK) {
m_selected_index = (size_t)m_list->GetFirstSelected();
}
wxDialog::EndModal(retCode);
}
}

View File

@@ -0,0 +1,82 @@
#ifndef slic3r_BonjourDialog_hpp_
#define slic3r_BonjourDialog_hpp_
#include <cstddef>
#include <memory>
#include <boost/asio/ip/address.hpp>
#include <wx/dialog.h>
#include <wx/string.h>
#include "libslic3r/PrintConfig.hpp"
class wxListView;
class wxStaticText;
class wxTimer;
class wxTimerEvent;
class address;
namespace Slic3r {
class Bonjour;
//B29
class Udp;
class BonjourReplyEvent;
//B29
class UdpReplyEvent;
class ReplySet;
//B29
class UdpReplySet;
class BonjourDialog: public wxDialog
{
public:
BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology);
BonjourDialog(BonjourDialog &&) = delete;
BonjourDialog(const BonjourDialog &) = delete;
BonjourDialog &operator=(BonjourDialog &&) = delete;
BonjourDialog &operator=(const BonjourDialog &) = delete;
~BonjourDialog();
bool show_and_lookup();
wxString get_selected() const;
private:
wxListView *list;
std::unique_ptr<ReplySet> replies;
//B29
std::unique_ptr<UdpReplySet> udp_replies;
wxStaticText *label;
std::shared_ptr<Bonjour> bonjour;
//B29
std::shared_ptr<Udp> udp;
std::unique_ptr<wxTimer> timer;
unsigned timer_state;
Slic3r::PrinterTechnology tech;
virtual void on_reply(BonjourReplyEvent &);
//B29
virtual void on_udp_reply(UdpReplyEvent &);
void on_timer(wxTimerEvent &);
void on_timer_process();
};
class IPListDialog : public wxDialog
{
public:
IPListDialog(wxWindow* parent, const wxString& hostname, const std::vector<boost::asio::ip::address>& ips, size_t& selected_index);
IPListDialog(IPListDialog&&) = delete;
IPListDialog(const IPListDialog&) = delete;
IPListDialog& operator=(IPListDialog&&) = delete;
IPListDialog& operator=(const IPListDialog&) = delete;
~IPListDialog();
virtual void EndModal(int retCode) wxOVERRIDE;
private:
wxListView* m_list;
size_t& m_selected_index;
};
}
#endif

View File

@@ -0,0 +1,751 @@
#include "CaliHistoryDialog.hpp"
#include "I18N.hpp"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "format.hpp"
#include "MsgDialog.hpp"
#include "slic3r/Utils/CalibUtils.hpp"
namespace Slic3r {
namespace GUI {
#define HISTORY_WINDOW_SIZE wxSize(FromDIP(700), FromDIP(600))
#define EDIT_HISTORY_DIALOG_INPUT_SIZE wxSize(FromDIP(160), FromDIP(24))
#define NEW_HISTORY_DIALOG_INPUT_SIZE wxSize(FromDIP(250), FromDIP(24))
#define HISTORY_WINDOW_ITEMS_COUNT 5
static wxString get_preset_name_by_filament_id(std::string filament_id)
{
auto preset_bundle = wxGetApp().preset_bundle;
auto collection = &preset_bundle->filaments;
wxString preset_name = "";
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
if (filament_id.compare(it->filament_id) == 0) {
auto preset_parent = collection->get_preset_parent(*it);
if (preset_parent) {
if (preset_parent->is_system) {
if (!preset_parent->alias.empty())
preset_name = from_u8(preset_parent->alias);
else
preset_name = from_u8(preset_parent->name);
}
else { // is custom created filament
std::string name_str = preset_parent->name;
preset_name = from_u8(name_str.substr(0, name_str.find(" @")));
}
}
else {
if (it->is_system) {
if (!it->alias.empty())
preset_name = from_u8(it->alias);
else
preset_name = from_u8(it->name);
}
else { // is custom created filament
std::string name_str = it->name;
preset_name = from_u8(name_str.substr(0, name_str.find(" @")));
}
}
}
}
return preset_name;
}
HistoryWindow::HistoryWindow(wxWindow* parent, const std::vector<PACalibResult>& calib_results_history, bool& show)
: DPIDialog(parent, wxID_ANY, _L("Flow Dynamics Calibration Result"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
, m_calib_results_history(calib_results_history)
, m_show_history_dialog(show)
{
this->SetBackgroundColour(*wxWHITE);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
auto scroll_window = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
scroll_window->SetScrollRate(5, 5);
scroll_window->SetBackgroundColour(*wxWHITE);
scroll_window->SetMinSize(HISTORY_WINDOW_SIZE);
scroll_window->SetSize(HISTORY_WINDOW_SIZE);
scroll_window->SetMaxSize(HISTORY_WINDOW_SIZE);
auto scroll_sizer = new wxBoxSizer(wxVERTICAL);
scroll_window->SetSizer(scroll_sizer);
Button * mew_btn = new Button(scroll_window, _L("New"));
// y96
StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(95, 82, 253), StateColor::Pressed), std::pair<wxColour, int>(wxColour(129, 150, 255), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Normal));
mew_btn->SetBackgroundColour(*wxWHITE);
mew_btn->SetBackgroundColor(btn_bg_green);
mew_btn->SetBorderColor(wxColour(68, 121, 251));
mew_btn->SetTextColor(wxColour("#FFFFFE"));
mew_btn->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
mew_btn->SetMaxSize(wxSize(FromDIP(100), FromDIP(24)));
mew_btn->SetCornerRadius(FromDIP(12));
mew_btn->Bind(wxEVT_BUTTON, &HistoryWindow::on_click_new_button, this);
scroll_sizer->Add(mew_btn, 0, wxLEFT, FromDIP(20));
scroll_sizer->AddSpacer(FromDIP(15));
wxPanel* comboBox_panel = new wxPanel(scroll_window);
comboBox_panel->SetBackgroundColour(wxColour(238, 238, 238));
auto comboBox_sizer = new wxBoxSizer(wxVERTICAL);
comboBox_panel->SetSizer(comboBox_sizer);
comboBox_sizer->AddSpacer(10);
auto nozzle_dia_title = new Label(comboBox_panel, _L("Nozzle Diameter"));
nozzle_dia_title->SetFont(Label::Head_14);
comboBox_sizer->Add(nozzle_dia_title, 0, wxLEFT | wxRIGHT, FromDIP(15));
comboBox_sizer->AddSpacer(10);
m_comboBox_nozzle_dia = new ComboBox(comboBox_panel, wxID_ANY, "", wxDefaultPosition, wxSize(-1, FromDIP(24)), 0, nullptr, wxCB_READONLY);
comboBox_sizer->Add(m_comboBox_nozzle_dia, 0, wxLEFT | wxEXPAND | wxRIGHT, FromDIP(15));
comboBox_sizer->AddSpacer(10);
scroll_sizer->Add(comboBox_panel, 0, wxEXPAND | wxRIGHT, FromDIP(10));
scroll_sizer->AddSpacer(FromDIP(15));
wxPanel* tips_panel = new wxPanel(scroll_window, wxID_ANY);
tips_panel->SetBackgroundColour(*wxWHITE);
auto tips_sizer = new wxBoxSizer(wxVERTICAL);
tips_panel->SetSizer(tips_sizer);
m_tips = new Label(tips_panel, "");
m_tips->SetForegroundColour({ 145, 145, 145 });
tips_sizer->Add(m_tips, 0, wxEXPAND);
scroll_sizer->Add(tips_panel, 0, wxEXPAND);
scroll_sizer->AddSpacer(FromDIP(15));
m_history_data_panel = new wxPanel(scroll_window);
m_history_data_panel->SetBackgroundColour(*wxWHITE);
scroll_sizer->Add(m_history_data_panel, 1, wxEXPAND);
main_sizer->Add(scroll_window, 1, wxEXPAND | wxALL, FromDIP(10));
SetSizer(main_sizer);
Layout();
main_sizer->Fit(this);
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
m_comboBox_nozzle_dia->Bind(wxEVT_COMBOBOX, &HistoryWindow::on_select_nozzle, this);
m_refresh_timer = new wxTimer();
m_refresh_timer->SetOwner(this);
m_refresh_timer->Start(200);
Bind(wxEVT_TIMER, &HistoryWindow::on_timer, this);
m_show_history_dialog = true;
}
HistoryWindow::~HistoryWindow()
{
m_refresh_timer->Stop();
m_show_history_dialog = false;
}
void HistoryWindow::sync_history_result(MachineObject* obj)
{
BOOST_LOG_TRIVIAL(info) << "sync_history_result";
m_calib_results_history.clear();
if (obj)
m_calib_results_history = obj->pa_calib_tab;
if (m_calib_results_history.empty()) {
m_tips->SetLabel(_L("No History Result"));
return;
}
else {
m_tips->SetLabel(_L("Success to get history result"));
}
m_tips->Refresh();
sync_history_data();
}
void HistoryWindow::on_device_connected(MachineObject* obj)
{
if (!obj) {
return;
}
curr_obj = obj;
// init nozzle value
static std::array<float, 4> nozzle_diameter_list = { 0.2f, 0.4f, 0.6f, 0.8f };
int selection = 1;
for (int i = 0; i < nozzle_diameter_list.size(); i++) {
m_comboBox_nozzle_dia->AppendString(wxString::Format("%1.1f mm", nozzle_diameter_list[i]));
if (abs(curr_obj->nozzle_diameter - nozzle_diameter_list[i]) < 1e-3) {
selection = i;
}
}
m_comboBox_nozzle_dia->SetSelection(selection);
// trigger on_select nozzle
wxCommandEvent evt(wxEVT_COMBOBOX);
evt.SetEventObject(m_comboBox_nozzle_dia);
wxPostEvent(m_comboBox_nozzle_dia, evt);
}
void HistoryWindow::on_timer(wxTimerEvent& event)
{
update(curr_obj);
}
void HistoryWindow::update(MachineObject* obj)
{
if (!obj) return;
if (obj->cali_version != history_version) {
if (obj->has_get_pa_calib_tab) {
history_version = obj->cali_version;
reqeust_history_result(obj);
}
}
// sync when history is not empty
if (obj->has_get_pa_calib_tab && m_calib_results_history.empty()) {
sync_history_result(curr_obj);
}
}
void HistoryWindow::on_select_nozzle(wxCommandEvent& evt)
{
reqeust_history_result(curr_obj);
}
void HistoryWindow::reqeust_history_result(MachineObject* obj)
{
if (curr_obj) {
// reset
curr_obj->reset_pa_cali_history_result();
m_calib_results_history.clear();
sync_history_data();
float nozzle_value = get_nozzle_value();
if (nozzle_value > 0) {
CalibUtils::emit_get_PA_calib_infos(nozzle_value);
m_tips->SetLabel(_L("Refreshing the historical Flow Dynamics Calibration records"));
BOOST_LOG_TRIVIAL(info) << "request calib history";
}
}
}
void HistoryWindow::enbale_action_buttons(bool enable) {
auto childern = m_history_data_panel->GetChildren();
for (auto child : childern) {
auto button = dynamic_cast<Button*>(child);
if (button) {
button->Enable(enable);
}
}
}
void HistoryWindow::sync_history_data() {
Freeze();
m_history_data_panel->DestroyChildren();
m_history_data_panel->Enable();
wxGridBagSizer* gbSizer;
gbSizer = new wxGridBagSizer(FromDIP(0), FromDIP(50));
gbSizer->SetFlexibleDirection(wxBOTH);
gbSizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
m_history_data_panel->SetSizer(gbSizer, true);
auto title_name = new Label(m_history_data_panel, _L("Name"));
title_name->SetFont(Label::Head_14);
gbSizer->Add(title_name, { 0, 0 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
auto title_preset_name = new Label(m_history_data_panel, _L("Filament"));
title_preset_name->SetFont(Label::Head_14);
gbSizer->Add(title_preset_name, { 0, 1 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
auto title_k = new Label(m_history_data_panel, _L("Factor K"));
title_k->SetFont(Label::Head_14);
gbSizer->Add(title_k, { 0, 2 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
// Hide
//auto title_n = new Label(m_history_data_panel, wxID_ANY, _L("N"));
//title_n->SetFont(Label::Head_14);
//gbSizer->Add(title_n, { 0, 3 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
auto title_action = new Label(m_history_data_panel, _L("Action"));
title_action->SetFont(Label::Head_14);
gbSizer->Add(title_action, { 0, 3 }, { 1, 1 });
int i = 1;
for (auto& result : m_calib_results_history) {
auto name_value = new Label(m_history_data_panel, from_u8(result.name));
wxString preset_name = get_preset_name_by_filament_id(result.filament_id);
auto preset_name_value = new Label(m_history_data_panel, preset_name);
auto k_str = wxString::Format("%.3f", result.k_value);
auto n_str = wxString::Format("%.3f", result.n_coef);
auto k_value = new Label(m_history_data_panel, k_str);
auto n_value = new Label(m_history_data_panel, n_str);
n_value->Hide();
auto delete_button = new Button(m_history_data_panel, _L("Delete"));
delete_button->SetBackgroundColour(*wxWHITE);
delete_button->SetMinSize(wxSize(-1, FromDIP(24)));
delete_button->SetCornerRadius(FromDIP(12));
delete_button->Bind(wxEVT_BUTTON, [this, gbSizer, i, &result](auto& e) {
for (int j = 0; j < HISTORY_WINDOW_ITEMS_COUNT; j++) {
auto item = gbSizer->FindItemAtPosition({ i, j });
item->GetWindow()->Hide();
}
gbSizer->SetEmptyCellSize({ 0,0 });
m_history_data_panel->Layout();
m_history_data_panel->Fit();
CalibUtils::delete_PA_calib_result({ result.tray_id, result.cali_idx, result.nozzle_diameter, result.filament_id });
});
auto edit_button = new Button(m_history_data_panel, _L("Edit"));
StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(95, 82, 253), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(129, 150, 255), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Normal));
edit_button->SetBackgroundColour(*wxWHITE);
edit_button->SetBackgroundColor(btn_bg_green);
edit_button->SetBorderColor(wxColour(68, 121, 251));
edit_button->SetTextColor(wxColour("#FFFFFE"));
edit_button->SetMinSize(wxSize(-1, FromDIP(24)));
edit_button->SetCornerRadius(FromDIP(12));
edit_button->Bind(wxEVT_BUTTON, [this, result, k_value, name_value, edit_button](auto& e) {
PACalibResult result_buffer = result;
result_buffer.k_value = stof(k_value->GetLabel().ToStdString());
result_buffer.name = name_value->GetLabel().ToUTF8().data();
EditCalibrationHistoryDialog dlg(this, result_buffer);
if (dlg.ShowModal() == wxID_OK) {
auto new_result = dlg.get_result();
wxString new_k_str = wxString::Format("%.3f", new_result.k_value);
k_value->SetLabel(new_k_str);
name_value->SetLabel(from_u8(new_result.name));
new_result.tray_id = -1;
CalibUtils::set_PA_calib_result({ new_result }, true);
enbale_action_buttons(false);
}
});
gbSizer->Add(name_value, { i, 0 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
gbSizer->Add(preset_name_value, { i, 1 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
gbSizer->Add(k_value, { i, 2 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
//gbSizer->Add(n_value, { i, 3 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
gbSizer->Add(delete_button, { i, 3 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
gbSizer->Add(edit_button, { i, 4 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
i++;
}
wxGetApp().UpdateDlgDarkUI(this);
Layout();
Fit();
Thaw();
}
float HistoryWindow::get_nozzle_value()
{
double nozzle_value = 0.0;
wxString nozzle_value_str = m_comboBox_nozzle_dia->GetValue();
try {
nozzle_value_str.ToDouble(&nozzle_value);
}
catch (...) {
;
}
return nozzle_value;
}
void HistoryWindow::on_click_new_button(wxCommandEvent& event)
{
if (curr_obj && curr_obj->get_printer_series() == PrinterSeries::SERIES_P1P && m_calib_results_history.size() >= 16) {
MessageDialog msg_dlg(nullptr, wxString::Format(_L("This machine type can only hold %d history results per nozzle."), 16), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
NewCalibrationHistoryDialog dlg(this, m_calib_results_history);
dlg.ShowModal();
}
EditCalibrationHistoryDialog::EditCalibrationHistoryDialog(wxWindow* parent, const PACalibResult& result)
: DPIDialog(parent, wxID_ANY, _L("Edit Flow Dynamics Calibration"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
, m_new_result(result)
{
this->SetBackgroundColour(*wxWHITE);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
auto top_panel = new wxPanel(this);
top_panel->SetBackgroundColour(*wxWHITE);
auto panel_sizer = new wxBoxSizer(wxVERTICAL);
top_panel->SetSizer(panel_sizer);
auto flex_sizer = new wxFlexGridSizer(0, 2, FromDIP(15), FromDIP(30));
flex_sizer->SetFlexibleDirection(wxBOTH);
flex_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
Label* name_title = new Label(top_panel, _L("Name"));
m_name_value = new TextInput(top_panel, from_u8(m_new_result.name), "", "", wxDefaultPosition, EDIT_HISTORY_DIALOG_INPUT_SIZE, wxTE_PROCESS_ENTER);
flex_sizer->Add(name_title);
flex_sizer->Add(m_name_value);
Label* preset_name_title = new Label(top_panel, _L("Filament"));
wxString preset_name = get_preset_name_by_filament_id(result.filament_id);
Label* preset_name_value = new Label(top_panel, preset_name);
flex_sizer->Add(preset_name_title);
flex_sizer->Add(preset_name_value);
Label* k_title = new Label(top_panel, _L("Factor K"));
auto k_str = wxString::Format("%.3f", m_new_result.k_value);
m_k_value = new TextInput(top_panel, k_str, "", "", wxDefaultPosition, EDIT_HISTORY_DIALOG_INPUT_SIZE, wxTE_PROCESS_ENTER);
flex_sizer->Add(k_title);
flex_sizer->Add(m_k_value);
// Hide:
//Label* n_title = new Label(top_panel, _L("Factor N"));
//TextInput* n_value = new TextInput(top_panel, n, "", "", wxDefaultPosition, EDIT_HISTORY_DIALOG_INPUT_SIZE, wxTE_PROCESS_ENTER);
//flex_sizer->Add(n_title);
//flex_sizer->Add(n_value);
panel_sizer->Add(flex_sizer);
panel_sizer->AddSpacer(FromDIP(25));
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
Button* save_btn = new Button(top_panel, _L("Save"));
StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(95, 82, 253), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(129, 150, 255), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Normal));
save_btn->SetBackgroundColour(*wxWHITE);
save_btn->SetBackgroundColor(btn_bg_green);
save_btn->SetBorderColor(wxColour(68, 121, 251));
save_btn->SetTextColor(wxColour("#FFFFFE"));
save_btn->SetMinSize(wxSize(-1, FromDIP(24)));
save_btn->SetCornerRadius(FromDIP(12));
Button* cancel_btn = new Button(top_panel, _L("Cancel"));
cancel_btn->SetBackgroundColour(*wxWHITE);
cancel_btn->SetMinSize(wxSize(-1, FromDIP(24)));
cancel_btn->SetCornerRadius(FromDIP(12));
save_btn->Bind(wxEVT_BUTTON, &EditCalibrationHistoryDialog::on_save, this);
cancel_btn->Bind(wxEVT_BUTTON, &EditCalibrationHistoryDialog::on_cancel, this);
btn_sizer->AddStretchSpacer();
btn_sizer->Add(save_btn);
btn_sizer->AddSpacer(FromDIP(20));
btn_sizer->Add(cancel_btn);
panel_sizer->Add(btn_sizer, 0, wxEXPAND, 0);
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
SetSizer(main_sizer);
Layout();
Fit();
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
}
EditCalibrationHistoryDialog::~EditCalibrationHistoryDialog() {
}
PACalibResult EditCalibrationHistoryDialog::get_result() {
return m_new_result;
}
void EditCalibrationHistoryDialog::on_save(wxCommandEvent& event) {
wxString name = m_name_value->GetTextCtrl()->GetValue();
if (!CalibUtils::validate_input_name(name))
return;
m_new_result.name = m_name_value->GetTextCtrl()->GetValue().ToUTF8().data();
float k = 0.0f;
if (!CalibUtils::validate_input_k_value(m_k_value->GetTextCtrl()->GetValue(), &k)) {
MessageDialog msg_dlg(nullptr, wxString::Format(_L("Please input a valid value (K in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
wxString k_str = wxString::Format("%.3f", k);
m_k_value->GetTextCtrl()->SetValue(k_str);
m_new_result.k_value = k;
EndModal(wxID_OK);
}
void EditCalibrationHistoryDialog::on_cancel(wxCommandEvent& event) {
EndModal(wxID_CANCEL);
}
void EditCalibrationHistoryDialog::on_dpi_changed(const wxRect& suggested_rect)
{
}
wxArrayString NewCalibrationHistoryDialog::get_all_filaments(const MachineObject *obj)
{
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
wxArrayString filament_items;
std::set<std::string> filament_id_set;
std::set<std::string> printer_names;
std::ostringstream stream;
stream << std::fixed << std::setprecision(1) << obj->nozzle_diameter;
std::string nozzle_diameter_str = stream.str();
for (auto printer_it = preset_bundle->printers.begin(); printer_it != preset_bundle->printers.end(); printer_it++) {
// filter by system preset
if (!printer_it->is_system)
continue;
// get printer_model
ConfigOption * printer_model_opt = printer_it->config.option("printer_model");
ConfigOptionString *printer_model_str = dynamic_cast<ConfigOptionString *>(printer_model_opt);
if (!printer_model_str)
continue;
// use printer_model as printer type
if (printer_model_str->value != MachineObject::get_preset_printer_model_name(obj->printer_type))
continue;
if (printer_it->name.find(nozzle_diameter_str) != std::string::npos)
printer_names.insert(printer_it->name);
}
if (preset_bundle) {
BOOST_LOG_TRIVIAL(trace) << "system_preset_bundle filament number=" << preset_bundle->filaments.size();
for (auto filament_it = preset_bundle->filaments.begin(); filament_it != preset_bundle->filaments.end(); filament_it++) {
// filter by system preset
Preset &preset = *filament_it;
/*The situation where the user preset is not displayed is as follows:
1. Not a root preset
2. Not system preset and the printer firmware does not support user preset */
if (preset_bundle->filaments.get_preset_base(*filament_it) != &preset || (!filament_it->is_system && ! obj->is_support_user_preset)) { continue; }
ConfigOption * printer_opt = filament_it->config.option("compatible_printers");
ConfigOptionStrings *printer_strs = dynamic_cast<ConfigOptionStrings *>(printer_opt);
for (auto printer_str : printer_strs->values) {
if (printer_names.find(printer_str) != printer_names.end()) {
if (filament_id_set.find(filament_it->filament_id) != filament_id_set.end()) {
continue;
} else {
filament_id_set.insert(filament_it->filament_id);
// name matched
if (filament_it->is_system) {
filament_items.push_back(filament_it->alias);
FilamentInfos filament_infos;
filament_infos.filament_id = filament_it->filament_id;
filament_infos.setting_id = filament_it->setting_id;
map_filament_items[filament_it->alias] = filament_infos;
} else {
char target = '@';
size_t pos = filament_it->name.find(target);
if (pos != std::string::npos) {
std::string user_preset_alias = filament_it->name.substr(0, pos - 1);
wxString wx_user_preset_alias = wxString(user_preset_alias.c_str(), wxConvUTF8);
user_preset_alias = wx_user_preset_alias.ToStdString();
filament_items.push_back(user_preset_alias);
FilamentInfos filament_infos;
filament_infos.filament_id = filament_it->filament_id;
filament_infos.setting_id = filament_it->setting_id;
map_filament_items[user_preset_alias] = filament_infos;
}
}
}
}
}
}
}
return filament_items;
}
NewCalibrationHistoryDialog::NewCalibrationHistoryDialog(wxWindow *parent, const std::vector<PACalibResult> history_results)
: DPIDialog(parent, wxID_ANY, _L("New Flow Dynamic Calibration"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
, m_history_results(history_results)
{
Slic3r::DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev)
return;
MachineObject *obj = dev->get_selected_machine();
if (!obj)
return;
curr_obj = obj;
this->SetBackgroundColour(*wxWHITE);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
auto top_panel = new wxPanel(this);
top_panel->SetBackgroundColour(*wxWHITE);
auto panel_sizer = new wxBoxSizer(wxVERTICAL);
top_panel->SetSizer(panel_sizer);
auto flex_sizer = new wxFlexGridSizer(0, 2, FromDIP(15), FromDIP(30));
flex_sizer->SetFlexibleDirection(wxBOTH);
flex_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
Label *name_title = new Label(top_panel, _L("Name"));
m_name_value = new TextInput(top_panel, "", "", "", wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, wxTE_PROCESS_ENTER);
// Name
flex_sizer->Add(name_title);
flex_sizer->Add(m_name_value);
Label * preset_name_title = new Label(top_panel, _L("Filament"));
m_comboBox_filament = new ::ComboBox(top_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, 0, nullptr, wxCB_READONLY);
wxArrayString filament_items = get_all_filaments(obj);
m_comboBox_filament->Set(filament_items);
m_comboBox_filament->SetSelection(-1);
// Filament
flex_sizer->Add(preset_name_title);
flex_sizer->Add(m_comboBox_filament);
Label *nozzle_diameter_title = new Label(top_panel, _L("Nozzle Diameter"));
m_comboBox_nozzle_diameter = new ::ComboBox(top_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, 0, nullptr, wxCB_READONLY);
static std::array<float, 4> nozzle_diameter_list = {0.2f, 0.4f, 0.6f, 0.8f};
for (int i = 0; i < nozzle_diameter_list.size(); i++) {
m_comboBox_nozzle_diameter->AppendString(wxString::Format("%1.1f mm", nozzle_diameter_list[i]));
if (abs(obj->nozzle_diameter - nozzle_diameter_list[i]) < 1e-3) {
m_comboBox_nozzle_diameter->SetSelection(i);
}
}
// Nozzle Diameter
flex_sizer->Add(nozzle_diameter_title);
flex_sizer->Add(m_comboBox_nozzle_diameter);
Label *k_title = new Label(top_panel, _L("Factor K"));
auto k_str = wxString::Format("%.3f", m_new_result.k_value);
m_k_value = new TextInput(top_panel, k_str, "", "", wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, wxTE_PROCESS_ENTER);
// Factor K
flex_sizer->Add(k_title);
flex_sizer->Add(m_k_value);
panel_sizer->Add(flex_sizer);
panel_sizer->AddSpacer(FromDIP(25));
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
Button * ok_btn = new Button(top_panel, _L("Ok"));
StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(95, 82, 253), StateColor::Pressed), std::pair<wxColour, int>(wxColour(129, 150, 255), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Normal));
ok_btn->SetBackgroundColour(*wxWHITE);
ok_btn->SetBackgroundColor(btn_bg_green);
ok_btn->SetBorderColor(wxColour(68, 121, 251));
ok_btn->SetTextColor(wxColour("#FFFFFE"));
ok_btn->SetMinSize(wxSize(-1, FromDIP(24)));
ok_btn->SetCornerRadius(FromDIP(12));
Button *cancel_btn = new Button(top_panel, _L("Cancel"));
cancel_btn->SetBackgroundColour(*wxWHITE);
cancel_btn->SetMinSize(wxSize(-1, FromDIP(24)));
cancel_btn->SetCornerRadius(FromDIP(12));
ok_btn->Bind(wxEVT_BUTTON, &NewCalibrationHistoryDialog::on_ok, this);
cancel_btn->Bind(wxEVT_BUTTON, &NewCalibrationHistoryDialog::on_cancel, this);
btn_sizer->AddStretchSpacer();
btn_sizer->Add(ok_btn);
btn_sizer->AddSpacer(FromDIP(20));
btn_sizer->Add(cancel_btn);
panel_sizer->Add(btn_sizer, 0, wxEXPAND, 0);
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
SetSizer(main_sizer);
Layout();
Fit();
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
}
void NewCalibrationHistoryDialog::on_ok(wxCommandEvent &event)
{
wxString name = m_name_value->GetTextCtrl()->GetValue();
if (!CalibUtils::validate_input_name(name))
return;
float k = 0.0f;
if (!CalibUtils::validate_input_k_value(m_k_value->GetTextCtrl()->GetValue(), &k)) {
MessageDialog msg_dlg(nullptr, wxString::Format(_L("Please input a valid value (K in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
wxString k_str = wxString::Format("%.3f", k);
m_k_value->GetTextCtrl()->SetValue(k_str);
double nozzle_value = 0.0;
wxString nozzle_value_str = m_comboBox_nozzle_diameter->GetValue();
nozzle_value_str.ToDouble(&nozzle_value);
std::string filament_name = m_comboBox_filament->GetValue().ToStdString();
if (filament_name.empty()) {
MessageDialog msg_dlg(nullptr, _L("The filament must be selected."), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
std::string filament_id = filament_item.filament_id;
std::string setting_id = filament_item.setting_id;
m_new_result.name = name.ToUTF8().data();
m_new_result.k_value = k;
m_new_result.tray_id = -1;
m_new_result.cali_idx = -1;
m_new_result.nozzle_diameter = nozzle_value;
m_new_result.filament_id = filament_id;
m_new_result.setting_id = setting_id;
// Check for duplicate names from history
{
auto iter = std::find_if(m_history_results.begin(), m_history_results.end(), [this](const PACalibResult &item) {
return item.name == m_new_result.name && item.filament_id == m_new_result.filament_id;
});
if (iter != m_history_results.end()) {
MessageDialog msg_dlg(nullptr,
wxString::Format(_L("There is already a historical calibration result with the same name: %s. Only one of the results with the same name "
"is saved. Are you sure you want to override the historical result?"),
m_new_result.name),
wxEmptyString, wxICON_WARNING | wxYES_NO);
if (msg_dlg.ShowModal() != wxID_YES)
return;
}
}
try {
json js;
js["cali_type"] = "cali_new_pa";
js["nozzle_diameter"] = m_new_result.nozzle_diameter;
js["filament_id"] = m_new_result.filament_id;
js["printer_type"] = curr_obj->printer_type;
NetworkAgent *agent = GUI::wxGetApp().getAgent();
if (agent) agent->track_event("cali", js.dump());
} catch (...) {}
CalibUtils::set_PA_calib_result({m_new_result}, true);
EndModal(wxID_OK);
}
void NewCalibrationHistoryDialog::on_cancel(wxCommandEvent &event)
{
EndModal(wxID_CANCEL);
}
} // namespace GUI
} // namespace Slic3r

View File

@@ -0,0 +1,97 @@
#ifndef slic3r_GUI_CaliHistory_hpp_
#define slic3r_GUI_CaliHistory_hpp_
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
#include "Widgets/ComboBox.hpp"
#include "DeviceManager.hpp"
namespace Slic3r {
namespace GUI {
class HistoryWindow : public DPIDialog {
public:
HistoryWindow(wxWindow* parent, const std::vector<PACalibResult>& calib_results_history, bool& show);
~HistoryWindow();
void on_dpi_changed(const wxRect& suggested_rect) {}
void on_select_nozzle(wxCommandEvent& evt);
void reqeust_history_result(MachineObject* obj);
void sync_history_result(MachineObject* obj);
void on_device_connected(MachineObject* obj);
void on_timer(wxTimerEvent& event);
void update(MachineObject* obj);
protected:
void sync_history_data();
void enbale_action_buttons(bool enable);
float get_nozzle_value();
void on_click_new_button(wxCommandEvent &event);
wxPanel* m_history_data_panel;
ComboBox* m_comboBox_nozzle_dia;
Label* m_tips;
wxTimer* m_refresh_timer { nullptr };
bool& m_show_history_dialog;
std::vector<PACalibResult> m_calib_results_history;
MachineObject* curr_obj { nullptr };
int history_version = -1;
};
class EditCalibrationHistoryDialog : public DPIDialog
{
public:
EditCalibrationHistoryDialog(wxWindow* parent, const PACalibResult& result);
~EditCalibrationHistoryDialog();
void on_dpi_changed(const wxRect& suggested_rect) override;
PACalibResult get_result();
protected:
virtual void on_save(wxCommandEvent& event);
virtual void on_cancel(wxCommandEvent& event);
protected:
PACalibResult m_new_result;
TextInput* m_name_value{ nullptr };
TextInput* m_k_value{ nullptr };
};
class NewCalibrationHistoryDialog : public DPIDialog
{
public:
NewCalibrationHistoryDialog(wxWindow *parent, const std::vector<PACalibResult> history_results);
~NewCalibrationHistoryDialog(){};
void on_dpi_changed(const wxRect &suggested_rect) override{};
protected:
virtual void on_ok(wxCommandEvent &event);
virtual void on_cancel(wxCommandEvent &event);
wxArrayString get_all_filaments(const MachineObject *obj);
protected:
PACalibResult m_new_result;
std::vector<PACalibResult> m_history_results;
MachineObject * curr_obj;
TextInput *m_name_value{nullptr};
TextInput *m_k_value{nullptr};
ComboBox *m_comboBox_nozzle_diameter;
ComboBox *m_comboBox_filament;
struct FilamentInfos
{
std::string filament_id;
std::string setting_id;
};
std::map<std::string, FilamentInfos> map_filament_items;
};
} // namespace GUI
} // namespace Slic3r
#endif

View File

@@ -0,0 +1,329 @@
#include "Calibration.hpp"
#include "I18N.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Thread.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_Preview.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/StaticBox.hpp"
static wxColour FG_COLOR = wxColour(0x32, 0x3A, 0x3D);
static wxColour BG_COLOR = wxColour(0xF8, 0xF8, 0xF8);
namespace Slic3r { namespace GUI {
CalibrationDialog::CalibrationDialog(Plater *plater)
: DPIDialog(static_cast<wxWindow *>(wxGetApp().mainframe), wxID_ANY, _L("Calibration"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
{
this->SetDoubleBuffered(true);
std::string icon_path = (boost::format("%1%/images/QIDIStudioTitle.ico") % resources_dir()).str();
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
SetBackgroundColour(*wxWHITE);
wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL);
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
m_sizer_main->Add(m_line_top, 0, wxEXPAND, 0);
wxBoxSizer *sizer_body = new wxBoxSizer(wxHORIZONTAL);
auto body_panel = new wxPanel(this, wxID_ANY);
body_panel->SetBackgroundColour(*wxWHITE);
auto cali_left_panel = new StaticBox(body_panel, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(303), -1));
cali_left_panel->SetBackgroundColor(BG_COLOR);
cali_left_panel->SetBorderColor(BG_COLOR);
wxBoxSizer *cali_left_sizer = new wxBoxSizer(wxVERTICAL);
cali_left_sizer->Add(0, 0, 0, wxTOP, FromDIP(25));
// calibration step selection
auto cali_step_select_title = new wxStaticText(cali_left_panel, wxID_ANY, _L("Calibration step selection"), wxDefaultPosition, wxDefaultSize, 0);
cali_step_select_title->SetFont(::Label::Head_14);
cali_step_select_title->Wrap(-1);
cali_step_select_title->SetForegroundColour(FG_COLOR);
cali_step_select_title->SetBackgroundColour(BG_COLOR);
cali_left_sizer->Add(cali_step_select_title, 0, wxLEFT, FromDIP(15));
select_xcam_cali = create_check_option(_L("Micro lidar calibration"), cali_left_panel, _L("Micro lidar calibration"), "xcam_cali");
select_bed_leveling = create_check_option(_L("Bed leveling"), cali_left_panel, _L("Bed leveling"), "bed_leveling");
select_vibration = create_check_option(_L("Vibration compensation"), cali_left_panel, _L("Vibration compensation"), "vibration");
select_motor_noise = create_check_option(_L("Motor noise cancellation"), cali_left_panel, _L("Motor noise cancellation"), "motor_noise");
cali_left_sizer->Add(0, FromDIP(18), 0, wxEXPAND, 0);
cali_left_sizer->Add(select_xcam_cali, 0, wxLEFT, FromDIP(15));
cali_left_sizer->Add(select_bed_leveling, 0, wxLEFT, FromDIP(15));
cali_left_sizer->Add(select_vibration, 0, wxLEFT, FromDIP(15));
cali_left_sizer->Add(select_motor_noise, 0, wxLEFT, FromDIP(15));
cali_left_sizer->Add(0, FromDIP(30), 0, wxEXPAND, 0);
auto cali_left_text_top = new wxStaticText(cali_left_panel, wxID_ANY, _L("Calibration program"), wxDefaultPosition, wxDefaultSize, 0);
cali_left_text_top->SetFont(::Label::Head_14);
cali_left_text_top->Wrap(-1);
cali_left_text_top->SetForegroundColour(FG_COLOR);
cali_left_text_top->SetBackgroundColour(BG_COLOR);
cali_left_sizer->Add(cali_left_text_top, 0, wxLEFT, FromDIP(15));
cali_left_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
auto cali_left_text_body =
new Label(cali_left_panel, _L("The calibration program detects the status of your device automatically to minimize deviation.\nIt keeps the device performing optimally."));
cali_left_text_body->Wrap(FromDIP(260));
cali_left_text_body->SetForegroundColour(wxColour(0x6B, 0x6B, 0x6B));
cali_left_text_body->SetBackgroundColour(BG_COLOR);
cali_left_text_body->SetFont(::Label::Body_13);
cali_left_sizer->Add(cali_left_text_body, 0, wxLEFT, FromDIP(15));
cali_left_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
/* auto cali_left_text_top_prepar = new wxStaticText(cali_left_panel, wxID_ANY, _L("Preparation before calibration"), wxDefaultPosition, wxDefaultSize, 0);
cali_left_text_top_prepar->SetFont(::Label::Head_14);
cali_left_text_top_prepar->SetForegroundColour(wxColour(0x32, 0x3A, 0x3D));
cali_left_text_top_prepar->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
cali_left_text_top_prepar->Wrap(-1);
cali_left_sizer->Add(cali_left_text_top_prepar, 0, wxLEFT, FromDIP(15));
cali_left_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
auto cali_left_text_body_prepar =
new wxStaticText(cali_left_panel, wxID_ANY,
_L("Before calibration, please make sure a filament is loaded and its nozzle temperature and bed temperature is set in Feeding lab."),
wxDefaultPosition, wxSize(FromDIP(260), -1), 0); cali_left_text_body_prepar->Wrap(FromDIP(260)); cali_left_text_body_prepar->SetFont(::Label::Body_13);
cali_left_text_body_prepar->SetForegroundColour(wxColour(0x6B, 0x6B, 0x6B));
cali_left_text_body_prepar->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
cali_left_sizer->Add(cali_left_text_body_prepar, 0, wxLEFT, FromDIP(15));*/
cali_left_panel->SetSizer(cali_left_sizer);
cali_left_panel->Layout();
sizer_body->Add(cali_left_panel, 0, wxALIGN_CENTER, 0);
sizer_body->Add(0, 0, 0, wxLEFT, FromDIP(8));
wxBoxSizer *cali_right_sizer_h = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *cali_right_sizer_v = new wxBoxSizer(wxVERTICAL);
auto cali_right_panel = new StaticBox(body_panel, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(182), FromDIP(160)));
cali_right_panel->SetBackgroundColor(BG_COLOR);
cali_right_panel->SetBorderColor(BG_COLOR);
auto cali_text_right_top = new wxStaticText(cali_right_panel, wxID_ANY, _L("Calibration Flow"), wxDefaultPosition, wxDefaultSize, 0);
cali_text_right_top->Wrap(-1);
cali_text_right_top->SetFont(::Label::Head_14);
cali_text_right_top->SetForegroundColour(wxColour(0x44, 0x79, 0xFB)); // y96
cali_text_right_top->SetBackgroundColour(BG_COLOR);
auto staticline = new ::StaticLine(cali_right_panel);
staticline->SetLineColour(wxColour(0x44, 0x79, 0xFB)); // y96
auto calibration_panel = new wxPanel(cali_right_panel);
calibration_panel->SetBackgroundColour(BG_COLOR);
auto calibration_sizer = new wxBoxSizer(wxVERTICAL);
calibration_panel->SetMinSize(wxSize(FromDIP(170), FromDIP(160)));
calibration_panel->SetSize(wxSize(FromDIP(170), FromDIP(160)));
m_calibration_flow = new StepIndicator(calibration_panel, wxID_ANY);
StateColor bg_color(std::pair<wxColour, int>(BG_COLOR, StateColor::Normal));
m_calibration_flow->SetBackgroundColor(bg_color);
m_calibration_flow->SetFont(Label::Body_12);
m_calibration_flow->SetMinSize(wxSize(FromDIP(170), FromDIP(160)));
m_calibration_flow->SetSize(wxSize(FromDIP(170), FromDIP(160)));
calibration_panel->SetSizer(calibration_sizer);
calibration_panel->Layout();
calibration_sizer->Add(m_calibration_flow, 0, wxEXPAND, 0);
// y96
StateColor btn_bg_green(std::pair<wxColour, int>(AMS_CONTROL_DISABLE_COLOUR, StateColor::Disabled), std::pair<wxColour, int>(wxColour(95, 82, 253), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(129, 150, 255), StateColor::Hovered), std::pair<wxColour, int>(AMS_CONTROL_BRAND_COLOUR, StateColor::Normal));
StateColor btn_bd_green(std::pair<wxColour, int>(AMS_CONTROL_WHITE_COLOUR, StateColor::Disabled), std::pair<wxColour, int>(AMS_CONTROL_BRAND_COLOUR, StateColor::Enabled));
m_calibration_btn = new Button(cali_right_panel, _L("Start Calibration"));
m_calibration_btn->SetBackgroundColor(btn_bg_green);
m_calibration_btn->SetBorderColor(btn_bd_green);
m_calibration_btn->SetTextColor(wxColour("#FFFFFE"));
m_calibration_btn->SetSize(wxSize(FromDIP(128), FromDIP(26)));
m_calibration_btn->SetMinSize(wxSize(FromDIP(128), FromDIP(26)));
cali_right_sizer_v->Add(cali_text_right_top, 0, wxALIGN_CENTER, 0);
cali_right_sizer_v->Add(0, 0, 0, wxTOP, FromDIP(7));
cali_right_sizer_v->Add(staticline, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(10));
cali_right_sizer_v->Add(0, 0, 0, wxTOP, FromDIP(3));
cali_right_sizer_v->Add(calibration_panel, 0, wxALIGN_CENTER_HORIZONTAL | wxLEFT | wxRIGHT, FromDIP(6));
cali_right_sizer_v->Add(0, 0, 1, wxEXPAND, 5);
cali_right_sizer_v->Add(m_calibration_btn, 0, wxALIGN_CENTER_HORIZONTAL, 0);
cali_right_sizer_h->Add(cali_right_sizer_v, 0, wxALIGN_CENTER, 0);
cali_right_panel->SetSizer(cali_right_sizer_h);
cali_right_panel->Layout();
sizer_body->Add(cali_right_panel, 0, wxEXPAND, 0);
body_panel->SetSizer(sizer_body);
body_panel->Layout();
m_sizer_main->Add(body_panel, 0, wxEXPAND | wxALL, FromDIP(25));
SetSizer(m_sizer_main);
Layout();
Fit();
m_calibration_btn->Bind(wxEVT_LEFT_DOWN, &CalibrationDialog::on_start_calibration, this);
}
CalibrationDialog::~CalibrationDialog() {}
void CalibrationDialog::on_dpi_changed(const wxRect &suggested_rect) {}
wxWindow* CalibrationDialog::create_check_option(wxString title, wxWindow* parent, wxString tooltip, std::string param)
{
auto checkbox = new wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
checkbox->SetBackgroundColour(BG_COLOR);
wxBoxSizer* sizer_checkbox = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer* sizer_check = new wxBoxSizer(wxVERTICAL);
auto check = new ::CheckBox(checkbox);
sizer_check->Add(check, 0, wxBOTTOM | wxEXPAND | wxTOP, FromDIP(5));
sizer_checkbox->Add(sizer_check, 0, wxEXPAND, FromDIP(5));
sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(11));
auto text = new wxStaticText(checkbox, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
text->SetFont(::Label::Body_13);
text->SetForegroundColour(wxColour(107, 107, 107));
text->Wrap(-1);
sizer_checkbox->Add(text, 0, wxBOTTOM | wxEXPAND | wxTOP, FromDIP(5));
checkbox->SetSizer(sizer_checkbox);
checkbox->Layout();
sizer_checkbox->Fit(checkbox);
checkbox->SetToolTip(tooltip);
text->SetToolTip(tooltip);
text->Bind(wxEVT_LEFT_DOWN, [this, check](wxMouseEvent&) { check->SetValue(check->GetValue() ? false : true); });
m_checkbox_list[param] = check;
m_checkbox_list[param]->SetValue(true);
return checkbox;
}
void CalibrationDialog::update_cali(MachineObject *obj)
{
if (!obj) return;
if (obj->is_support_ai_monitoring && obj->is_support_lidar_calibration) {
select_xcam_cali->Show();
} else {
select_xcam_cali->Hide();
m_checkbox_list["xcam_cali"]->SetValue(false);
}
if(obj->is_support_auto_leveling){
select_bed_leveling->Show();
}else{
select_bed_leveling->Hide();
m_checkbox_list["bed_leveling"]->SetValue(false);
}
if (obj->is_support_motor_noise_cali) {
select_motor_noise->Show();
} else {
select_motor_noise->Hide();
m_checkbox_list["motor_noise"]->SetValue(false);
}
if (obj->is_calibration_running() || obj->is_calibration_done()) {
if (obj->is_calibration_done()) {
m_calibration_btn->Enable();
m_calibration_btn->SetLabel(_L("Completed"));
} else {
// RUNNING && IDLE
m_calibration_btn->Disable();
m_calibration_btn->SetLabel(_L("Calibrating"));
}
auto size = wxSize(-1, obj->stage_list_info.size() * FromDIP(44));
if (m_calibration_flow->GetSize().y != size.y) {
m_calibration_flow->SetSize(size);
m_calibration_flow->SetMinSize(size);
m_calibration_flow->SetMaxSize(size);
Layout();
}
if (is_stage_list_info_changed(obj)) {
// change items if stage_list_info changed
m_calibration_flow->DeleteAllItems();
for (int i = 0; i < obj->stage_list_info.size(); i++) {
m_calibration_flow->AppendItem(get_stage_string(obj->stage_list_info[i]));
}
}
int index = obj->get_curr_stage_idx();
m_calibration_flow->SelectItem(index);
} else {
// IDLE
if (obj->is_in_printing()) {
m_calibration_btn->Disable();
}
else {
m_calibration_btn->Enable();
}
m_calibration_flow->DeleteAllItems();
m_calibration_btn->SetLabel(_L("Start Calibration"));
}
if (!obj->is_calibration_running() && !m_checkbox_list["vibration"]->GetValue() && !m_checkbox_list["bed_leveling"]->GetValue() &&
!m_checkbox_list["xcam_cali"]->GetValue() && !m_checkbox_list["motor_noise"]->GetValue()) {
m_calibration_btn->Disable();
m_calibration_btn->SetLabel(_L("No step selected"));
}
else if(!obj->is_calibration_running()){
m_calibration_btn->Enable();
}
}
bool CalibrationDialog::is_stage_list_info_changed(MachineObject *obj)
{
if (!obj) return true;
if (last_stage_list_info.size() != obj->stage_list_info.size()) return true;
for (int i = 0; i < last_stage_list_info.size(); i++) {
if (last_stage_list_info[i] != obj->stage_list_info[i]) return true;
}
last_stage_list_info = obj->stage_list_info;
return false;
}
void CalibrationDialog::on_start_calibration(wxMouseEvent &event)
{
if (m_obj) {
if (m_obj->is_calibration_done()) {
m_obj->calibration_done = false;
EndModal(wxID_CANCEL);
Close();
} else {
BOOST_LOG_TRIVIAL(info) << "on_start_calibration";
m_obj->command_start_calibration(
m_checkbox_list["vibration"]->GetValue(),
m_checkbox_list["bed_leveling"]->GetValue(),
m_checkbox_list["xcam_cali"]->GetValue(),
m_checkbox_list["motor_noise"]->GetValue()
);
}
}
}
void CalibrationDialog::update_machine_obj(MachineObject *obj) { m_obj = obj; }
bool CalibrationDialog::Show(bool show)
{
if (show) {
wxGetApp().UpdateDlgDarkUI(this);
CentreOnParent();
}
return DPIDialog::Show(show);
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,69 @@
#ifndef slic3r_GUI_Calibration_hpp_
#define slic3r_GUI_Calibration_hpp_
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/collpane.h>
#include <wx/dataview.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/dataview.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/hyperlink.h>
#include <wx/button.h>
#include <wx/dialog.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/wrapsizer.h>
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
#include "DeviceManager.hpp"
#include "Plater.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/StepCtrl.hpp"
#include "Widgets/CheckBox.hpp"
namespace Slic3r { namespace GUI {
class CalibrationDialog : public DPIDialog
{
private:
std::map<std::string, ::CheckBox*> m_checkbox_list;
wxWindow* select_xcam_cali { nullptr };
wxWindow* select_bed_leveling { nullptr };
wxWindow* select_vibration { nullptr };
wxWindow* select_motor_noise { nullptr };
wxWindow* create_check_option(wxString title, wxWindow *parent, wxString tooltip, std::string param);
public:
CalibrationDialog(Plater *plater = nullptr);
~CalibrationDialog();
void on_dpi_changed(const wxRect &suggested_rect) override;
StepIndicator *m_calibration_flow;
Button * m_calibration_btn;
MachineObject *m_obj;
std::vector<int> last_stage_list_info;
int m_state{0};
void update_cali(MachineObject *obj);
bool is_stage_list_info_changed(MachineObject *obj);
void on_start_calibration(wxMouseEvent &event);
void update_machine_obj(MachineObject *obj);
bool Show(bool show) override;
};
}} // namespace Slic3r::GUI
#endif

View File

@@ -0,0 +1,289 @@
#include <wx/dcgraph.h>
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "CalibrationPanel.hpp"
#include "I18N.hpp"
#include "Tab.hpp"
namespace Slic3r { namespace GUI {
#define REFRESH_INTERVAL 1000
#define INITIAL_NUMBER_OF_MACHINES 0
#define LIST_REFRESH_INTERVAL 200
#define MACHINE_LIST_REFRESH_INTERVAL 2000
wxDEFINE_EVENT(EVT_FINISHED_UPDATE_MLIST, wxCommandEvent);
wxDEFINE_EVENT(EVT_UPDATE_USER_MLIST, wxCommandEvent);
wxString get_calibration_type_name(CalibMode cali_mode)
{
switch (cali_mode) {
case CalibMode::Calib_PA_Line:
//w29
return _L("Pressure Advance");
case CalibMode::Calib_Flow_Rate:
return _L("Flow Rate");
case CalibMode::Calib_Vol_speed_Tower:
return _L("Max Volumetric Speed");
case CalibMode::Calib_Temp_Tower:
return _L("Temperature");
case CalibMode::Calib_Retraction_tower:
return _L("Retraction");
default:
return "";
}
}
//w29
CalibrationPanel::CalibrationPanel(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
//w29
: wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
init_tabpanel();
wxBoxSizer* sizer_main = new wxBoxSizer(wxVERTICAL);
sizer_main->Add(m_tabpanel, 1, wxEXPAND, 0);
SetSizerAndFit(sizer_main);
Layout();
init_timer();
//w29
}
//w29
void CalibrationPanel::create_preset_box(wxWindow* parent, wxBoxSizer* sizer_side_tools) {
PresetBundle* m_preset_bundle = wxGetApp().preset_bundle;
auto panel = this;
m_printer_choice = new TabPresetComboBox(panel, Slic3r::Preset::TYPE_PRINTER);
m_printer_choice->set_selection_changed_function([this](int selection) {
if (!m_printer_choice->selection_is_changed_according_to_physical_printers())
{
if (!m_printer_choice->is_selected_physical_printer())
wxGetApp().preset_bundle->physical_printers.unselect_printer();
std::string preset_name = m_printer_choice->GetString(selection).ToUTF8().data();
Slic3r::GUI::Tab* printer_tab = Slic3r::GUI::wxGetApp().get_tab(Preset::Type::TYPE_PRINTER);
printer_tab->select_preset(Preset::remove_suffix_modified(preset_name));
m_filament_choice->update();
m_print_choice->update();
}
});
m_filament_choice = new TabPresetComboBox(panel, Slic3r::Preset::TYPE_FILAMENT);
m_filament_choice->set_selection_changed_function([this](int selection) {
if (!m_filament_choice->selection_is_changed_according_to_physical_printers())
{
if (!m_filament_choice->is_selected_physical_printer())
wxGetApp().preset_bundle->physical_printers.unselect_printer();
std::string preset_name = m_filament_choice->GetString(selection).ToUTF8().data();
Slic3r::GUI::Tab* filament_tab = Slic3r::GUI::wxGetApp().get_tab(Preset::Type::TYPE_FILAMENT);
filament_tab->select_preset(preset_name);
const std::string& name = wxGetApp().preset_bundle->filaments.get_selected_preset_name();
{
Preset* preset = wxGetApp().preset_bundle->filaments.find_preset(name, false);
if (preset)
{
if (preset->is_compatible) wxGetApp().preset_bundle->set_filament_preset(0, name);
}
wxGetApp().sidebar().combos_filament()[0]->update();
}
}
});
m_print_choice = new TabPresetComboBox(panel, Slic3r::Preset::TYPE_PRINT);
m_print_choice->set_selection_changed_function([this](int selection) {
if (!m_print_choice->selection_is_changed_according_to_physical_printers())
{
if (!m_print_choice->is_selected_physical_printer())
wxGetApp().preset_bundle->physical_printers.unselect_printer();
std::string preset_name = m_print_choice->GetString(selection).ToUTF8().data();
Slic3r::GUI::Tab* print_tab = Slic3r::GUI::wxGetApp().get_tab(Preset::Type::TYPE_PRINT);
print_tab->select_preset(Preset::remove_suffix_modified(preset_name));
}
});
wxPanel* m_printer_panel = new wxPanel(parent);
ScalableButton* m_printer_icon = new ScalableButton(m_printer_panel, wxID_ANY, "printer");
Label* m_printer_title = new Label(m_printer_panel, _L("Printer"), LB_PROPAGATE_MOUSE_EVENT);
m_printer_icon->SetForegroundColour(m_printer_icon->GetForegroundColour());
wxBoxSizer* h_printer_sizer = new wxBoxSizer(wxHORIZONTAL);
m_printer_panel->SetSizer(h_printer_sizer);
h_printer_sizer->Add(FromDIP(10), 0, 0, 0, 0);
h_printer_sizer->Add(m_printer_icon, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
h_printer_sizer->Add(m_printer_title, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
h_printer_sizer->AddStretchSpacer(1);
wxPanel* m_filament_panel = new wxPanel(parent);
ScalableButton* m_filament_icon = new ScalableButton(m_filament_panel, wxID_ANY, "filament");
Label* m_filament_title = new Label(m_filament_panel, _L("Filament"), LB_PROPAGATE_MOUSE_EVENT);
m_filament_icon->SetForegroundColour(m_filament_icon->GetForegroundColour());
wxBoxSizer* h_filament_sizer = new wxBoxSizer(wxHORIZONTAL);
m_filament_panel->SetSizer(h_filament_sizer);
h_filament_sizer->Add(FromDIP(10), 0, 0, 0, 0);
h_filament_sizer->Add(m_filament_icon, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
h_filament_sizer->Add(m_filament_title, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
h_filament_sizer->AddStretchSpacer(1);
wxPanel* m_process_panel = new wxPanel(parent);
ScalableButton* m_process_icon = new ScalableButton(m_process_panel, wxID_ANY, "process");
Label* m_process_title = new Label(m_process_panel, _L("Process"), LB_PROPAGATE_MOUSE_EVENT);
m_process_icon->SetForegroundColour(m_process_icon->GetForegroundColour());
wxBoxSizer* h_process_sizer = new wxBoxSizer(wxHORIZONTAL);
m_process_panel->SetSizer(h_process_sizer);
h_process_sizer->Add(FromDIP(10), 0, 0, 0, 0);
h_process_sizer->Add(m_process_icon, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
h_process_sizer->Add(m_process_title, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
h_process_sizer->AddStretchSpacer(1);
wxColour bg(243, 243, 243);
m_printer_panel->SetBackgroundColour(bg);
m_filament_panel->SetBackgroundColour(bg);
m_process_panel->SetBackgroundColour(bg);
sizer_side_tools->Add(m_printer_panel, 0, wxEXPAND);
sizer_side_tools->AddSpacer(10);
sizer_side_tools->Add(m_printer_choice, 0, wxEXPAND | wxLEFT | wxRIGHT, 5);
sizer_side_tools->Add(m_filament_panel, 0, wxEXPAND | wxTOP | wxBottom, 10);
sizer_side_tools->AddSpacer(10);
sizer_side_tools->Add(m_filament_choice, 0, wxEXPAND | wxLEFT | wxRIGHT, 5);
sizer_side_tools->Add(m_process_panel, 0, wxEXPAND | wxTOP | wxBottom, 10);
sizer_side_tools->AddSpacer(10);
sizer_side_tools->Add(m_print_choice, 0, wxEXPAND | wxLEFT | wxRIGHT, 5);
wxStaticLine* line = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
line->SetBackgroundColour(wxColour(192, 192, 192));
sizer_side_tools->Add(line, 0, wxEXPAND | wxTOP, 10);
sizer_side_tools->Layout();
this->Layout();
this->Refresh();
m_printer_choice->update();
m_filament_choice->update();
m_print_choice->update();
Layout();
Refresh();
}
void CalibrationPanel::init_tabpanel() {
//w29
wxBoxSizer* sizer_side_tools = new wxBoxSizer(wxVERTICAL);
sizer_side_tools->SetMinSize(wxSize(250, -1));
//w29
create_preset_box(this, sizer_side_tools);
m_tabpanel = new Tabbook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, sizer_side_tools, wxNB_LEFT | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
m_printer_choice->update();
m_filament_choice->update();
m_print_choice->update();
//w29
m_tabpanel->SetBackgroundColour(*wxWHITE);
m_cali_panels[0] = new FlowRateWizard(m_tabpanel);
m_cali_panels[1] = new PressureAdvanceWizard(m_tabpanel);
//w29
m_cali_panels[2] = new MaxVolumetricSpeedWizard(m_tabpanel);
for (int i = 0; i < (int)CALI_MODE_COUNT; i++) {
bool selected = false;
if (i == 0)
selected = true;
//w29
/*m_tabpanel->AddPage(m_cali_panels[i],
get_calibration_type_name(m_cali_panels[i]->get_calibration_mode()),
"",
selected);*/
if (i == 1) {
m_tabpanel->AddPage(m_cali_panels[i],
get_calibration_type_name(CalibMode::Calib_PA_Line),
"",
selected);
}
else if( i == 0){
selected = true;
m_tabpanel->AddPage(m_cali_panels[i],
get_calibration_type_name(CalibMode::Calib_Flow_Rate),
"",
selected);
}
else {
m_tabpanel->AddPage(m_cali_panels[i],
get_calibration_type_name(m_cali_panels[i]->get_calibration_mode()),
"",
selected);
}
}
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()
{
m_refresh_timer = new wxTimer();
m_refresh_timer->SetOwner(this);
m_refresh_timer->Start(REFRESH_INTERVAL);
wxPostEvent(this, wxTimerEvent());
}
//w29
bool CalibrationPanel::Show(bool show) {
if (show) {
m_refresh_timer->Stop();
m_refresh_timer->SetOwner(this);
m_refresh_timer->Start(REFRESH_INTERVAL);
wxPostEvent(this, wxTimerEvent());
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (dev) {
//set a default machine when obj is null
obj = dev->get_selected_machine();
if (obj == nullptr) {
dev->load_last_machine();
obj = dev->get_selected_machine();
if (obj)
GUI::wxGetApp().sidebar().load_ams_list(obj->dev_id, obj);
}
else {
obj->reset_update_time();
}
}
}
else {
m_refresh_timer->Stop();
}
return wxPanel::Show(show);
}
//w29
void CalibrationPanel::msw_rescale()
{
for (int i = 0; i < (int)CALI_MODE_COUNT; i++) {
m_cali_panels[i]->msw_rescale();
}
}
CalibrationPanel::~CalibrationPanel() {
if (m_refresh_timer)
m_refresh_timer->Stop();
delete m_refresh_timer;
}
}}

View File

@@ -0,0 +1,55 @@
#ifndef slic3r_GUI_CalibrationPanel_hpp_
#define slic3r_GUI_CalibrationPanel_hpp_
#include "CalibrationWizard.hpp"
#include "Tabbook.hpp"
//#include "Widgets/SideTools.hpp"
namespace Slic3r { namespace GUI {
#define SELECT_MACHINE_GREY900 wxColour(38, 46, 48)
#define SELECT_MACHINE_GREY600 wxColour(144,144,144)
#define SELECT_MACHINE_GREY400 wxColour(206, 206, 206)
#define SELECT_MACHINE_BRAND wxColour(68, 121, 251)
#define SELECT_MACHINE_REMIND wxColour(255,111,0)
#define SELECT_MACHINE_LIGHT_GREEN wxColour(10, 255, 255) // y96
//w29
#define CALI_MODE_COUNT 3
wxString get_calibration_type_name(CalibMode cali_mode);
//w29
class CalibrationPanel : public wxPanel
{
public:
CalibrationPanel(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
~CalibrationPanel();
Tabbook* get_tabpanel() { return m_tabpanel; };
//w29
bool Show(bool show);
//w29
void msw_rescale();
//w29
TabPresetComboBox* m_printer_choice{ nullptr };
TabPresetComboBox* m_filament_choice{ nullptr };
TabPresetComboBox* m_print_choice{ nullptr };
void create_preset_box(wxWindow* parent, wxBoxSizer* sizer_side_tools);
protected:
void init_tabpanel();
void init_timer();
//w29
//w29
bool m_initialized { false };
MachineObject* obj{ nullptr };
Tabbook* m_tabpanel{ nullptr };
CalibrationWizard* m_cali_panels[CALI_MODE_COUNT];
wxTimer* m_refresh_timer = nullptr;
};
}} // namespace Slic3r::GUI
#endif

View File

@@ -0,0 +1,491 @@
#include "CalibrationWizard.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "MsgDialog.hpp"
#include "CalibrationWizardPage.hpp"
#include "../../libslic3r/Calib.hpp"
#include "Tabbook.hpp"
#include "CaliHistoryDialog.hpp"
#include "CalibUtils.hpp"
//w29
#include "MainFrame.hpp"
namespace Slic3r { namespace GUI {
#define CALIBRATION_DEBUG
wxDEFINE_EVENT(EVT_DEVICE_CHANGED, wxCommandEvent);
wxDEFINE_EVENT(EVT_CALIBRATION_JOB_FINISHED, wxCommandEvent);
static const wxString NA_STR = _L("N/A");
static const float MIN_PA_K_VALUE_STEP = 0.001;
static const int MAX_PA_HISTORY_RESULTS_NUMS = 16;
//w29
CalibrationWizard::CalibrationWizard(wxWindow* parent, CalibMode mode, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxPanel(parent, id, pos, size, style)
, m_mode(mode)
{
SetBackgroundColour(wxColour(0xEEEEEE));
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
//w29
m_scrolledWindow->SetScrollRate(20, 20);
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
wxBoxSizer* padding_sizer = new wxBoxSizer(wxHORIZONTAL);
padding_sizer->Add(0, 0, 1);
m_all_pages_sizer = new wxBoxSizer(wxVERTICAL);
padding_sizer->Add(m_all_pages_sizer, 0);
padding_sizer->Add(0, 0, 1);
m_scrolledWindow->SetSizer(padding_sizer);
main_sizer->Add(m_scrolledWindow, 1, wxEXPAND | wxALL, FromDIP(10));
this->SetSizer(main_sizer);
this->Layout();
main_sizer->Fit(this);
//w29
#if !QDT_RELEASE_TO_PUBLIC
this->Bind(wxEVT_CHAR_HOOK, [this](auto& evt) {
const int keyCode = evt.GetKeyCode();
switch (keyCode)
{
case WXK_PAGEUP:
{
show_step(m_curr_step->prev);
break;
}
case WXK_PAGEDOWN:
{
show_step(m_curr_step->next);
break;
}
default:
evt.Skip();
break;
}
});
#endif
}
CalibrationWizard::~CalibrationWizard()
{
;
}
//w29
void CalibrationWizard::show_step(CalibrationWizardPageStep* step)
{
if (!step)
return;
if (m_curr_step) {
m_curr_step->page->Hide();
}
m_curr_step = step;
if (m_curr_step) {
m_curr_step->page->Show();
}
Layout();
}
void CalibrationWizard::update(MachineObject* obj)
{
curr_obj = obj;
/* only update curr step
if (m_curr_step) {
m_curr_step->page->update(obj);
}
*/
//w29
// update all page steps
for (int i = 0; i < m_page_steps.size(); i++) {
if (m_page_steps[i]->page)
m_page_steps[i]->page->update(obj);
}
}
//w29
void CalibrationWizard::msw_rescale()
{
for (int i = 0; i < m_page_steps.size(); i++) {
if (m_page_steps[i]->page)
m_page_steps[i]->page->msw_rescale();
}
}
//w29
void CalibrationWizard::on_cali_go_home()
{
// can go home? confirm to continue
CalibrationMethod method;
int cali_stage = 0;
CalibMode obj_cali_mode = get_obj_calibration_mode(curr_obj, method, cali_stage);
bool double_confirm = false;
CaliPageType page_type = get_curr_step()->page->get_page_type();
//w29
if (!m_page_steps.empty()) {
show_step(m_page_steps.front());
}
}
PressureAdvanceWizard::PressureAdvanceWizard(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: CalibrationWizard(parent, m_mode, id, pos, size, style)//w29
// : CalibrationWizard(parent, CalibMode::Calib_PA_Line, id, pos, size, style)
{
create_pages();
}
//w29
void PressureAdvanceWizard::create_pages()
{
start_step = new CalibrationWizardPageStep(new CalibrationPAStartPage(m_scrolledWindow));
preset_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, CalibMode::Calib_PA_Line, false));
//w29
pa_line = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, CalibMode::Calib_PA_Line, false));
pa_pattern = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, CalibMode::Calib_PA_Pattern, false));
pa_tower = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, CalibMode::Calib_PA_Tower, false));
//w29
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));
//w29
m_all_pages_sizer->Add(pa_line->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(pa_pattern->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(pa_tower->page, 1, wxEXPAND | wxALL, FromDIP(25));
//w29
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
//w29
m_page_steps.push_back(pa_line);
m_page_steps.push_back(pa_pattern);
m_page_steps.push_back(pa_tower);
//w29
pa_line->page->set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
pa_pattern->page->set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
pa_tower->page->set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
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());
}
void PressureAdvanceWizard::on_cali_action(wxCommandEvent& evt)
{
CaliPageActionType action = static_cast<CaliPageActionType>(evt.GetInt());
//w29
if (action == CaliPageActionType::CALI_ACTION_NEXT) {
show_step(m_curr_step->next);
}
else if (action == CaliPageActionType::CALI_ACTION_CALI_NEXT) {
show_step(m_curr_step->next);
}
else if (action == CaliPageActionType::CALI_ACTION_PREV) {
show_step(m_curr_step->prev);
}
else if (action == CaliPageActionType::CALI_ACTION_CALI) {
on_cali_start();
}
//w29
else if (action == CaliPageActionType::CALI_ACTION_PA_LINE) {
show_step(pa_line);
}
else if (action == CaliPageActionType::CALI_ACTION_PA_PATTERN) {
show_step(pa_pattern);
}
else if (action == CaliPageActionType::CALI_ACTION_PA_TOWER) {
show_step(pa_tower);
}
else if (action == CaliPageActionType::CALI_ACTION_GO_HOME) {
on_cali_go_home();
}
}
void PressureAdvanceWizard::update(MachineObject* obj)
{
CalibrationWizard::update(obj);
if (!obj)
return;
if (!m_show_result_dialog) {
if (obj->cali_version != -1 && obj->cali_version != cali_version) {
cali_version = obj->cali_version;
CalibUtils::emit_get_PA_calib_info(obj->nozzle_diameter, "");
}
}
}
//w29
void PressureAdvanceWizard::on_cali_start()
{
//w29
{
CalibrationPresetPage* preset_page = nullptr;// = (static_cast<CalibrationPresetPage*>(preset_step->page));
if (m_curr_step == pa_line)
preset_page = (static_cast<CalibrationPresetPage*>(pa_line->page));
if (m_curr_step == pa_pattern)
preset_page = (static_cast<CalibrationPresetPage*>(pa_pattern->page));
if (m_curr_step == pa_tower)
preset_page = (static_cast<CalibrationPresetPage*>(pa_tower->page));
CalibInfo calib_info;
wxArrayString values = preset_page->get_custom_range_values();
if (values.size() != 3) {
MessageDialog msg_dlg(nullptr, _L("The input value size must be 3."), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
else {
values[0].ToDouble(&calib_info.params.start);
values[1].ToDouble(&calib_info.params.end);
values[2].ToDouble(&calib_info.params.step);
}
calib_info.params.print_numbers = true;
if (calib_info.params.start < 0 || calib_info.params.step < 0.001 || calib_info.params.end < calib_info.params.start + calib_info.params.step) {
MessageDialog msg_dlg(nullptr, _L("Please input valid values:\nStart PA: >= 0.0\nEnd PA: > Start PA\nPA step: >= 0.001)"), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
Calib_Params m_paramas;
m_paramas.start = calib_info.params.start;
m_paramas.end = calib_info.params.end;
m_paramas.step = calib_info.params.step;
m_paramas.mode = calib_info.params.mode;
if (m_curr_step == pa_line)
m_paramas.mode = CalibMode::Calib_PA_Line;
if (m_curr_step == pa_pattern)
m_paramas.mode = CalibMode::Calib_PA_Pattern;
if (m_curr_step == pa_tower)
m_paramas.mode = CalibMode::Calib_PA_Tower;
wxGetApp().plater_->calib_pa(m_paramas);
}
}
//w29
FlowRateWizard::FlowRateWizard(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
//w29
: CalibrationWizard(parent, m_mode, id, pos, size, style)//: CalibrationWizard(parent, CalibMode::Calib_Flow_Rate, id, pos, size, style)
{
create_pages();
}
void FlowRateWizard::create_pages()
{
start_step = new CalibrationWizardPageStep(new CalibrationFlowRateStartPage(m_scrolledWindow));
preset_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, m_mode, false));
coarse_calib_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, CalibMode::Calib_Flow_Rate_Coarse, false));
fine_calib_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, CalibMode::Calib_Flow_Rate_Fine, false));
//w29
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(coarse_calib_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(fine_calib_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
//w29
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(coarse_calib_step);
m_page_steps.push_back(fine_calib_step);
//w29
coarse_calib_step->page->set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
fine_calib_step->page->set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
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, &FlowRateWizard::on_cali_action, this);
}
if (!m_page_steps.empty())
show_step(m_page_steps.front());
}
void FlowRateWizard::on_cali_action(wxCommandEvent& evt)
{
//w29
CaliPageActionType action = static_cast<CaliPageActionType>(evt.GetInt());
if (action == CaliPageActionType::CALI_ACTION_NEXT) {
show_step(m_curr_step->next);
}
else if (action == CaliPageActionType::CALI_ACTION_CALI_NEXT) {
show_step(m_curr_step->next);
}
else if (action == CaliPageActionType::CALI_ACTION_PREV) {
show_step(m_curr_step->prev);
}
else if (action == CaliPageActionType::CALI_ACTION_CALI) {
if (m_curr_step == coarse_calib_step)
cali_start_pass(1);
else if(m_curr_step == fine_calib_step)
cali_start_pass(2);
}
else if (action == CaliPageActionType::CALI_ACTION_GO_HOME) {
on_cali_go_home();
}
//w29
else if (action == CaliPageActionType::CALI_ACTION_FLOW_COARSE) {
show_step(coarse_calib_step);
}
else if (action == CaliPageActionType::CALI_ACTION_FLOW_FINE) {
//this->set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
show_step(fine_calib_step);
}
}
void FlowRateWizard::cali_start_pass(int road) {
CalibrationPresetPage* preset_page = nullptr;// = (static_cast<CalibrationPresetPage*>(preset_step->page));
if (m_curr_step == coarse_calib_step)
preset_page = (static_cast<CalibrationPresetPage*>(coarse_calib_step->page));
if (m_curr_step == fine_calib_step)
preset_page = (static_cast<CalibrationPresetPage*>(fine_calib_step->page));
wxArrayString values = preset_page->get_custom_range_values();
double input_value;
if (values.size() != 1) {
MessageDialog msg_dlg(nullptr, _L("The input value size must be 1."), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
else {
values[0].ToDouble(&input_value);
}
wxGetApp().plater_->calib_flowrate(road, input_value);
}
//w29
void FlowRateWizard::update(MachineObject* obj)
{
CalibrationWizard::update(obj);
}
//w29
MaxVolumetricSpeedWizard::MaxVolumetricSpeedWizard(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: CalibrationWizard(parent, CalibMode::Calib_Vol_speed_Tower, id, pos, size, style)
{
create_pages();
}
void MaxVolumetricSpeedWizard::create_pages()
{
start_step = new CalibrationWizardPageStep(new CalibrationMaxVolumetricSpeedStartPage(m_scrolledWindow));
preset_step = new CalibrationWizardPageStep(new MaxVolumetricSpeedPresetPage(m_scrolledWindow, m_mode, true));
//w29
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));
//w29
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
preset_step->page->set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
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, &MaxVolumetricSpeedWizard::on_cali_action, this);
}
for (auto page_step : m_page_steps) {
page_step->page->Hide();
}
if (!m_page_steps.empty())
show_step(m_page_steps.front());
return;
}
void MaxVolumetricSpeedWizard::on_cali_action(wxCommandEvent& evt)
{
CaliPageActionType action = static_cast<CaliPageActionType>(evt.GetInt());
if (action == CaliPageActionType::CALI_ACTION_START) {
show_step(m_curr_step->next);
}
else if (action == CaliPageActionType::CALI_ACTION_PREV) {
show_step(m_curr_step->prev);
}
else if (action == CaliPageActionType::CALI_ACTION_CALI) {
on_cali_start();
}
else if (action == CaliPageActionType::CALI_ACTION_NEXT) {
show_step(m_curr_step->next);
}
else if (action == CaliPageActionType::CALI_ACTION_GO_HOME) {
on_cali_go_home();
}
}
void MaxVolumetricSpeedWizard::on_cali_start()
{
//w29
CalibrationPresetPage* preset_page = (static_cast<CalibrationPresetPage*>(preset_step->page));
wxArrayString values = preset_page->get_custom_range_values();
Calib_Params params;
if (values.size() != 3) {
MessageDialog msg_dlg(nullptr, _L("The input value size must be 3."), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
else {
values[0].ToDouble(&params.start);
values[1].ToDouble(&params.end);
values[2].ToDouble(&params.step);
}
params.mode = m_mode;
CalibInfo calib_info;
calib_info.params = params;
Calib_Params m_paramas;
m_paramas.start = calib_info.params.start;
m_paramas.end = calib_info.params.end;
m_paramas.step = calib_info.params.step;
if (m_paramas.start <= 0 || m_paramas.step < 0.01 || m_paramas.end < (m_paramas.start + m_paramas.step)) {
MessageDialog msg_dlg(nullptr, _L("Please input valid values:\nstart > 0 \nstep >= 0\nend > start + step)"), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
m_paramas.mode = CalibMode::Calib_Vol_speed_Tower;
wxGetApp().plater_->calib_max_vol_speed(m_paramas);
wxGetApp().mainframe->select_tab(size_t(MainFrame::tpPreview));
}
//w29
}}

View File

@@ -0,0 +1,144 @@
#ifndef slic3r_GUI_CalibrationWizard_hpp_
#define slic3r_GUI_CalibrationWizard_hpp_
#include "../slic3r/Utils/CalibUtils.hpp"
#include "DeviceManager.hpp"
#include "CalibrationWizardPage.hpp"
#include "CalibrationWizardStartPage.hpp"
#include "CalibrationWizardPresetPage.hpp"
namespace Slic3r { namespace GUI {
class CalibrationWizardPageStep
{
public:
CalibrationWizardPageStep(CalibrationWizardPage* data) {
page = data;
}
CalibrationWizardPageStep* prev { nullptr };
CalibrationWizardPageStep* next { nullptr };
CalibrationWizardPage* page { nullptr };
void chain(CalibrationWizardPageStep* step) {
if (!step) return;
this->next = step;
step->prev = this;
}
};
class CalibrationWizard : public wxPanel {
public:
CalibrationWizard(wxWindow* parent, CalibMode mode,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
~CalibrationWizard();
//w29
CalibrationWizardPageStep* get_curr_step() { return m_curr_step; }
void show_step(CalibrationWizardPageStep* step);
virtual void update(MachineObject* obj);
//w29
CalibMode get_calibration_mode() { return m_mode; }
//w29
void msw_rescale();
protected:
void on_cali_go_home();
protected:
/* wx widgets*/
wxScrolledWindow* m_scrolledWindow;
wxBoxSizer* m_all_pages_sizer;
CalibMode m_mode;
//w29
CalibrationMethod m_cali_method { CalibrationMethod::CALI_METHOD_MANUAL };
MachineObject* curr_obj { nullptr };
//w29
CalibrationWizardPageStep* m_curr_step { nullptr };
CalibrationWizardPageStep* start_step { nullptr };
CalibrationWizardPageStep* preset_step { nullptr };
//w29
CalibrationWizardPageStep* pa_line{ nullptr };
CalibrationWizardPageStep* pa_pattern{ nullptr };
CalibrationWizardPageStep* pa_tower{ nullptr };
CalibrationWizardPageStep* coarse_calib_step{ nullptr };
//w29
CalibrationWizardPageStep* fine_calib_step{ nullptr };
/* save steps of calibration pages */
std::vector<CalibrationWizardPageStep*> m_page_steps;
SecondaryCheckDialog *go_home_dialog = nullptr;
};
class PressureAdvanceWizard : public CalibrationWizard {
public:
PressureAdvanceWizard(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
~PressureAdvanceWizard() {};
//w29
protected:
void create_pages();
void on_cali_start();
//w29
void on_cali_action(wxCommandEvent& evt);
void update(MachineObject* obj) override;
//w29
bool m_show_result_dialog = false;
std::vector<PACalibResult> m_calib_results_history;
int cali_version = -1;
};
class FlowRateWizard : public CalibrationWizard {
public:
FlowRateWizard(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
~FlowRateWizard() {};
//w29
protected:
void create_pages();
void on_cali_action(wxCommandEvent& evt);
//w29
void cali_start_pass(int road);
void update(MachineObject* obj) override;
};
class MaxVolumetricSpeedWizard : public CalibrationWizard {
public:
MaxVolumetricSpeedWizard(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
~MaxVolumetricSpeedWizard() {};
protected:
void create_pages();
void on_cali_action(wxCommandEvent& evt);
void on_cali_start();
};
// save printer_type in command event
wxDECLARE_EVENT(EVT_DEVICE_CHANGED, wxCommandEvent);
wxDECLARE_EVENT(EVT_CALIBRATION_JOB_FINISHED, wxCommandEvent);
}} // namespace Slic3r::GUI
#endif

View File

@@ -0,0 +1,569 @@
#include "CalibrationWizardPage.hpp"
#include "I18N.hpp"
#include "Widgets/Label.hpp"
#include "MsgDialog.hpp"
namespace Slic3r { namespace GUI {
wxDEFINE_EVENT(EVT_CALI_ACTION, wxCommandEvent);
wxDEFINE_EVENT(EVT_CALI_TRAY_CHANGED, wxCommandEvent);
//w29
wxString get_cali_mode_caption_string(CalibMode mode)
{
//w29
if (mode == CalibMode::Calib_PA_Line|| mode == CalibMode::Calib_PA_Pattern|| mode == CalibMode::Calib_PA_Tower)
return _L("Pressure Advance Calibration");
if (mode == CalibMode::Calib_Flow_Rate)
return _L("Flow Rate Calibration");
if (mode == CalibMode::Calib_Vol_speed_Tower)
return _L("Max Volumetric Speed Calibration");
//w29
//if (mode == CalibMode::Calib_Flow_Rate_Coarse || mode == CalibMode::Calib_Flow_Rate_Fine)
// return _L("Flow Rate Calibration");
if (mode == CalibMode::Calib_Flow_Rate_Coarse)
return _L("Flow Rate Coarse Calibration");
if (mode == CalibMode::Calib_Flow_Rate_Fine)
return _L("Flow Rate Fine Calibration");
return "no cali_mode_caption";
}
//w29
CalibrationFilamentMode get_cali_filament_mode(MachineObject* obj, CalibMode mode)
{
//w29
return CalibrationFilamentMode::CALI_MODEL_SINGLE;
}
CalibMode get_obj_calibration_mode(const MachineObject* obj)
{
CalibrationMethod method;
int cali_stage;
return get_obj_calibration_mode(obj, method, cali_stage);
}
CalibMode get_obj_calibration_mode(const MachineObject* obj, int& cali_stage)
{
CalibrationMethod method;
return get_obj_calibration_mode(obj, method, cali_stage);
}
CalibMode get_obj_calibration_mode(const MachineObject* obj, CalibrationMethod& method, int& cali_stage)
{
method = CalibrationMethod::CALI_METHOD_MANUAL;
if (!obj) return CalibMode::Calib_None;
if (boost::contains(obj->m_gcode_file, "auto_filament_cali")) {
method = CalibrationMethod::CALI_METHOD_AUTO;
return CalibMode::Calib_PA_Line;
}
if (boost::contains(obj->m_gcode_file, "user_cali_manual_pa")) {
method = CalibrationMethod::CALI_METHOD_MANUAL;
return CalibMode::Calib_PA_Line;
}
if (boost::contains(obj->m_gcode_file, "extrusion_cali")) {
method = CalibrationMethod::CALI_METHOD_MANUAL;
return CalibMode::Calib_PA_Line;
}
if (boost::contains(obj->m_gcode_file, "abs_flowcalib_cali")) {
method = CalibrationMethod::CALI_METHOD_AUTO;
return CalibMode::Calib_Flow_Rate;
}
//w29
CalibMode cali_mode = CalibUtils::get_calib_mode_by_name(obj->subtask_name, cali_stage);
if (cali_mode != CalibMode::Calib_None) {
method = CalibrationMethod::CALI_METHOD_MANUAL;
}
return cali_mode;
}
CaliPageButton::CaliPageButton(wxWindow* parent, CaliPageActionType type, wxString text)
: m_action_type(type),
Button(parent, text)
{
// y96
StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(95, 82, 253), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(129, 150, 255), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Normal));
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
StateColor btn_bd_green(std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Enabled));
StateColor btn_bd_white(std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
StateColor btn_text_green(std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Enabled));
StateColor btn_text_white(std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
switch (m_action_type)
{
//w29
case CaliPageActionType::CALI_ACTION_START:
//w29
this->SetLabel(_L("Calibrate"));
break;
case CaliPageActionType::CALI_ACTION_PREV:
this->SetLabel(_L("Prev"));
break;
case CaliPageActionType::CALI_ACTION_NEXT:
this->SetLabel(_L("Next"));
break;
case CaliPageActionType::CALI_ACTION_CALI_NEXT:
this->SetLabel(_L("Next"));
break;
case CaliPageActionType::CALI_ACTION_CALI:
this->SetLabel(_L("Calibrate"));
break;
//w29
case CaliPageActionType::CALI_ACTION_FLOW_COARSE:
this->SetLabel(_L("Coarse Calibration"));
break;
case CaliPageActionType::CALI_ACTION_FLOW_FINE:
this->SetLabel(_L("Fine Calibration"));
break;
//w29
case CaliPageActionType::CALI_ACTION_PA_LINE:
this->SetLabel(_L("PA Line"));
break;
case CaliPageActionType::CALI_ACTION_PA_PATTERN:
this->SetLabel(_L("PA Pattern"));
break;
case CaliPageActionType::CALI_ACTION_PA_TOWER:
this->SetLabel(_L("PA Tower"));
break;
default:
this->SetLabel("Unknown");
break;
}
switch (m_action_type)
{
//w29
case CaliPageActionType::CALI_ACTION_PREV:
case CaliPageActionType::CALI_ACTION_START:
case CaliPageActionType::CALI_ACTION_NEXT:
case CaliPageActionType::CALI_ACTION_CALI:
case CaliPageActionType::CALI_ACTION_CALI_NEXT:
case CaliPageActionType::CALI_ACTION_PA_LINE:
case CaliPageActionType::CALI_ACTION_PA_PATTERN:
case CaliPageActionType::CALI_ACTION_PA_TOWER:
case CaliPageActionType::CALI_ACTION_FLOW_COARSE:
case CaliPageActionType::CALI_ACTION_FLOW_FINE:
SetBackgroundColor(btn_bg_green);
SetBorderColor(btn_bd_green);
SetTextColor(btn_text_green);
break;
default:
break;
}
//w29
SetBackgroundColour(*wxWHITE);
SetFont(Label::Body_16);
SetMinSize(wxSize(-1, FromDIP(30)));
SetCornerRadius(FromDIP(16));
}
void CaliPageButton::msw_rescale()
{
SetMinSize(wxSize(-1, FromDIP(24)));
SetCornerRadius(FromDIP(12));
Rescale();
}
FilamentComboBox::FilamentComboBox(wxWindow* parent, const wxPoint& pos, const wxSize& size)
: wxPanel(parent, wxID_ANY, pos, size, wxTAB_TRAVERSAL)
{
SetBackgroundColour(*wxWHITE);
wxBoxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL);
m_comboBox = new CalibrateFilamentComboBox(this);
m_comboBox->SetSize(CALIBRATION_FILAMENT_COMBOX_SIZE);
m_comboBox->SetMinSize(CALIBRATION_FILAMENT_COMBOX_SIZE);
main_sizer->Add(m_comboBox->clr_picker, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(8));
main_sizer->Add(m_comboBox, 0, wxALIGN_CENTER);
this->SetSizer(main_sizer);
this->Layout();
main_sizer->Fit(this);
}
void FilamentComboBox::set_select_mode(CalibrationFilamentMode mode)
{
m_mode = mode;
if (m_checkBox)
m_checkBox->Show(m_mode == CalibrationFilamentMode::CALI_MODEL_MULITI);
if (m_radioBox)
m_radioBox->Show(m_mode == CalibrationFilamentMode::CALI_MODEL_SINGLE);
Layout();
}
//w29
bool FilamentComboBox::Show(bool show)
{
return wxPanel::Show(show);
}
bool FilamentComboBox::Enable(bool enable) {
if (!enable)
SetValue(false);
if (m_radioBox)
m_radioBox->Enable(enable);
if (m_checkBox)
m_checkBox->Enable(enable);
return wxPanel::Enable(enable);
}
void FilamentComboBox::SetValue(bool value, bool send_event) {
if (m_radioBox) {
if (value == m_radioBox->GetValue()) {
if (m_checkBox) {
if (value == m_checkBox->GetValue())
return;
}
else {
return;
}
}
}
if (m_radioBox)
m_radioBox->SetValue(value);
if (m_checkBox)
m_checkBox->SetValue(value);
}
void FilamentComboBox::msw_rescale()
{
//m_checkBox->Rescale();
m_comboBox->SetSize(CALIBRATION_FILAMENT_COMBOX_SIZE);
m_comboBox->SetMinSize(CALIBRATION_FILAMENT_COMBOX_SIZE);
m_comboBox->msw_rescale();
}
CaliPageCaption::CaliPageCaption(wxWindow* parent, CalibMode cali_mode,
wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxPanel(parent, id, pos, size, style)
{
init_bitmaps();
SetBackgroundColour(*wxWHITE);
auto top_sizer = new wxBoxSizer(wxVERTICAL);
auto caption_sizer = new wxBoxSizer(wxHORIZONTAL);
m_prev_btn = new ScalableButton(this, wxID_ANY, "cali_page_caption_prev",
wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 30);
m_prev_btn->SetBackgroundColour(*wxWHITE);
caption_sizer->Add(m_prev_btn, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(10));
wxString title = get_cali_mode_caption_string(cali_mode);
Label* title_text = new Label(this, title);
title_text->SetFont(Label::Head_20);
title_text->Wrap(-1);
caption_sizer->Add(title_text, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(10));
caption_sizer->AddStretchSpacer();
//w29
top_sizer->Add(caption_sizer, 1, wxEXPAND);
top_sizer->AddSpacer(FromDIP(35));
this->SetSizer(top_sizer);
top_sizer->Fit(this);
// hover effect
//m_prev_btn->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {
// m_prev_btn->SetBitmap(m_prev_bmp_hover.bmp());
//});
//m_prev_btn->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {
// m_prev_btn->SetBitmap(m_prev_bmp_normal.bmp());
//});
// send event
m_prev_btn->Bind(wxEVT_BUTTON, [this](auto& e) {
wxCommandEvent event(EVT_CALI_ACTION);
event.SetEventObject(m_parent);
event.SetInt((int)(CaliPageActionType::CALI_ACTION_GO_HOME));
wxPostEvent(m_parent, event);
});
#ifdef __linux__
wxGetApp().CallAfter([this, title_text]() {
title_text->SetMinSize(title_text->GetSize() + wxSize{ FromDIP(150), title_text->GetCharHeight() / 2 });
Layout();
Fit();
});
#endif
}
void CaliPageCaption::init_bitmaps() {
m_prev_bmp_normal = ScalableBitmap(this, "cali_page_caption_prev", 30);
m_prev_bmp_hover = ScalableBitmap(this, "cali_page_caption_prev_hover", 30);
m_help_bmp_normal = ScalableBitmap(this, "cali_page_caption_help", 30);
m_help_bmp_hover = ScalableBitmap(this, "cali_page_caption_help_hover", 30);
}
//w29
void CaliPageCaption::show_prev_btn(bool show)
{
m_prev_btn->Show(show);
}
//w29
void CaliPageCaption::msw_rescale()
{
m_prev_btn->msw_rescale();
}
CaliPageStepGuide::CaliPageStepGuide(wxWindow* parent, wxArrayString steps,
wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxPanel(parent, id, pos, size, style),
m_steps(steps)
{
SetBackgroundColour(*wxWHITE);
auto top_sizer = new wxBoxSizer(wxVERTICAL);
m_step_sizer = new wxBoxSizer(wxHORIZONTAL);
m_step_sizer->AddSpacer(FromDIP(90));
for (int i = 0; i < m_steps.size(); i++) {
Label* step_text = new Label(this, m_steps[i]);
step_text->SetForegroundColour(wxColour(206, 206, 206));
m_text_steps.push_back(step_text);
m_step_sizer->Add(step_text, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(15));
if (i != m_steps.size() - 1) {
auto line = new wxPanel(this, wxID_ANY, wxDefaultPosition);
line->SetBackgroundColour(*wxBLACK);
m_step_sizer->Add(line, 1, wxALIGN_CENTER);
}
}
m_step_sizer->AddSpacer(FromDIP(90));
top_sizer->Add(m_step_sizer, 0, wxEXPAND);
top_sizer->AddSpacer(FromDIP(30));
this->SetSizer(top_sizer);
top_sizer->Fit(this);
wxGetApp().UpdateDarkUIWin(this);
}
void CaliPageStepGuide::set_steps(int index)
{
for (Label* text_step : m_text_steps) {
text_step->SetForegroundColour(wxColour(206, 206, 206));
}
m_text_steps[index]->SetForegroundColour(*wxBLACK);
wxGetApp().UpdateDarkUIWin(this);
}
void CaliPageStepGuide::set_steps_string(wxArrayString steps)
{
m_steps.Clear();
m_text_steps.clear();
m_step_sizer->Clear(true);
m_steps = steps;
m_step_sizer->AddSpacer(FromDIP(90));
for (int i = 0; i < m_steps.size(); i++) {
Label* step_text = new Label(this, m_steps[i]);
step_text->SetForegroundColour(wxColour(206, 206, 206));
m_text_steps.push_back(step_text);
m_step_sizer->Add(step_text, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(15));
if (i != m_steps.size() - 1) {
auto line = new wxPanel(this, wxID_ANY, wxDefaultPosition);
line->SetBackgroundColour(*wxBLACK);
m_step_sizer->Add(line, 1, wxALIGN_CENTER);
}
}
m_step_sizer->AddSpacer(FromDIP(90));
wxGetApp().UpdateDarkUIWin(this);
Layout();
}
CaliPagePicture::CaliPagePicture(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(wxColour(0xCECECE));
auto top_sizer = new wxBoxSizer(wxHORIZONTAL);
top_sizer->AddStretchSpacer();
m_img = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
top_sizer->Add(m_img);
top_sizer->AddStretchSpacer();
this->SetSizer(top_sizer);
top_sizer->Fit(this);
}
void CaliPagePicture::set_bmp(const ScalableBitmap& bmp)
{
m_bmp = bmp;
m_img->SetBitmap(m_bmp.bmp());
}
void CaliPagePicture::msw_rescale()
{
m_bmp.msw_rescale();
m_img->SetBitmap(m_bmp.bmp());
}
//w29
CaliPageActionPanel::CaliPageActionPanel(wxWindow* parent,
CalibMode cali_mode,
CaliPageType page_type,
wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxPanel(parent, id, pos, size, style)
{
m_parent = parent;
wxWindow* btn_parent = this;
//w29
if (cali_mode == CalibMode::Calib_PA_Line) {
if (page_type == CaliPageType::CALI_PAGE_START) {
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_PA_LINE));
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_PA_PATTERN));
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_PA_TOWER));
}
else if (page_type == CaliPageType::CALI_PAGE_PRESET) {
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_CALI));
}
else if (page_type == CaliPageType::CALI_PAGE_CALI) {
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_CALI_NEXT));
}
}
else if (cali_mode == CalibMode::Calib_Flow_Rate) {
if (page_type == CaliPageType::CALI_PAGE_START) {
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_FLOW_COARSE));
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_FLOW_FINE));
}
else if (page_type == CaliPageType::CALI_PAGE_PRESET) {
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_CALI));
}
else if (page_type == CaliPageType::CALI_PAGE_CALI) {
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_CALI_NEXT));
}
}
else {
if (page_type == CaliPageType::CALI_PAGE_START) {
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_START));
}
else if (page_type == CaliPageType::CALI_PAGE_PRESET) {
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_CALI));
}
else if (page_type == CaliPageType::CALI_PAGE_CALI) {
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_CALI_NEXT));
}
else {
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_PREV));
m_action_btns.push_back(new CaliPageButton(btn_parent, CaliPageActionType::CALI_ACTION_NEXT));
}
}
auto top_sizer = new wxBoxSizer(wxHORIZONTAL);
top_sizer->Add(0, 0, 1, wxEXPAND, 0);
for (int i = 0; i < m_action_btns.size(); i++) {
top_sizer->Add(m_action_btns[i], 0, wxALL, FromDIP(5));
m_action_btns[i]->Bind(wxEVT_BUTTON,
[this, i](wxCommandEvent& evt) {
wxCommandEvent event(EVT_CALI_ACTION);
event.SetEventObject(m_parent);
event.SetInt((int)m_action_btns[i]->get_action_type());
wxPostEvent(m_parent, event);
});
}
top_sizer->Add(0, 0, 1, wxEXPAND, 0);
this->SetSizer(top_sizer);
top_sizer->Fit(this);
}
void CaliPageActionPanel::bind_button(CaliPageActionType action_type, bool is_block)
{
for (int i = 0; i < m_action_btns.size(); i++) {
if (m_action_btns[i]->get_action_type() == action_type) {
if (is_block) {
m_action_btns[i]->Bind(wxEVT_BUTTON,
[this](wxCommandEvent& evt) {
MessageDialog msg(nullptr, _L("The current firmware version of the printer does not support calibration.\nPlease upgrade the printer firmware."), _L("Calibration not supported"), wxOK | wxICON_WARNING);
msg.ShowModal();
});
}
else {
m_action_btns[i]->Bind(wxEVT_BUTTON,
[this, i](wxCommandEvent& evt) {
wxCommandEvent event(EVT_CALI_ACTION);
event.SetEventObject(m_parent);
event.SetInt((int)m_action_btns[i]->get_action_type());
wxPostEvent(m_parent, event);
});
}
}
}
}
void CaliPageActionPanel::show_button(CaliPageActionType action_type, bool show)
{
for (int i = 0; i < m_action_btns.size(); i++) {
if (m_action_btns[i]->get_action_type() == action_type) {
m_action_btns[i]->Show(show);
}
}
Layout();
}
void CaliPageActionPanel::enable_button(CaliPageActionType action_type, bool enable)
{
for (int i = 0; i < m_action_btns.size(); i++) {
if (m_action_btns[i]->get_action_type() == action_type) {
m_action_btns[i]->Enable(enable);
}
}
}
void CaliPageActionPanel::msw_rescale()
{
for (int i = 0; i < m_action_btns.size(); i++) {
m_action_btns[i]->msw_rescale();
}
}
//w29
CalibrationWizardPage::CalibrationWizardPage(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxPanel(parent, id, pos, size, style)
, m_parent(parent)
{
SetBackgroundColour(*wxWHITE);
SetMinSize({ MIN_CALIBRATION_PAGE_WIDTH, -1 });
}
void CalibrationWizardPage::msw_rescale()
{
m_page_caption->msw_rescale();
m_action_panel->msw_rescale();
}
//w29
}
}

View File

@@ -0,0 +1,260 @@
#ifndef slic3r_GUI_CalibrationWizardPage_hpp_
#define slic3r_GUI_CalibrationWizardPage_hpp_
#include "wx/event.h"
#include "Widgets/Button.hpp"
#include "Widgets/ComboBox.hpp"
#include "Widgets/TextInput.hpp"
#include "Widgets/AMSControl.hpp"
#include "Widgets/ProgressBar.hpp"
#include "wxExtensions.hpp"
#include "PresetComboBoxes.hpp"
#include "../slic3r/Utils/CalibUtils.hpp"
#include "../../libslic3r/Calib.hpp"
namespace Slic3r { namespace GUI {
#define MIN_CALIBRATION_PAGE_WIDTH FromDIP(1100)
#define PRESET_GAP FromDIP(25)
#define CALIBRATION_COMBOX_SIZE wxSize(FromDIP(500), FromDIP(24))
#define CALIBRATION_FILAMENT_COMBOX_SIZE wxSize(FromDIP(250), FromDIP(24))
#define CALIBRATION_OPTIMAL_INPUT_SIZE wxSize(FromDIP(300), FromDIP(24))
#define CALIBRATION_FROM_TO_INPUT_SIZE wxSize(FromDIP(160), FromDIP(24))
#define CALIBRATION_FGSIZER_HGAP FromDIP(50)
#define CALIBRATION_TEXT_MAX_LENGTH FromDIP(90) + CALIBRATION_FGSIZER_HGAP + 2 * CALIBRATION_FILAMENT_COMBOX_SIZE.x
#define CALIBRATION_PROGRESSBAR_LENGTH FromDIP(690)
class CalibrationWizard;
//w29
wxString get_cali_mode_caption_string(CalibMode mode);
enum CalibrationFilamentMode {
/* calibration single filament at once */
CALI_MODEL_SINGLE = 0,
/* calibration multi filament at once */
CALI_MODEL_MULITI,
};
enum CalibrationMethod {
CALI_METHOD_MANUAL = 0,
CALI_METHOD_AUTO,
CALI_METHOD_NONE,
};
//w29
CalibrationFilamentMode get_cali_filament_mode(MachineObject* obj, CalibMode mode);
CalibMode get_obj_calibration_mode(const MachineObject* obj);
CalibMode get_obj_calibration_mode(const MachineObject* obj, int& cali_stage);
CalibMode get_obj_calibration_mode(const MachineObject* obj, CalibrationMethod& method, int& cali_stage);
//w29
enum class CaliPageType {
CALI_PAGE_START = 0,
CALI_PAGE_PRESET,
CALI_PAGE_CALI
};
class FilamentComboBox : public wxPanel
{
public:
FilamentComboBox(wxWindow* parent, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize);
~FilamentComboBox() {};
void set_select_mode(CalibrationFilamentMode mode);
//w29
virtual bool Show(bool show = true);
virtual bool Enable(bool enable);
virtual void SetValue(bool value, bool send_event = true);
void msw_rescale();
protected:
//w29
CheckBox* m_checkBox{ nullptr };
wxRadioButton* m_radioBox{ nullptr };
CalibrateFilamentComboBox* m_comboBox{ nullptr };
CalibrationFilamentMode m_mode { CalibrationFilamentMode::CALI_MODEL_SINGLE };
};
typedef std::vector<FilamentComboBox*> FilamentComboBoxList;
class CaliPageCaption : public wxPanel
{
public:
CaliPageCaption(wxWindow* parent,
CalibMode cali_mode,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void show_prev_btn(bool show = true);
//w29
void msw_rescale();
protected:
ScalableButton* m_prev_btn;
//w29
private:
void init_bitmaps();
//w29
ScalableBitmap m_prev_bmp_normal;
ScalableBitmap m_prev_bmp_hover;
ScalableBitmap m_help_bmp_normal;
ScalableBitmap m_help_bmp_hover;
};
class CaliPageStepGuide : public wxPanel
{
public:
CaliPageStepGuide(wxWindow* parent,
wxArrayString steps,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void set_steps(int index);
void set_steps_string(wxArrayString steps);
protected:
wxArrayString m_steps;
wxBoxSizer* m_step_sizer;
std::vector<Label*> m_text_steps;
};
class CaliPagePicture : public wxPanel
{
public:
CaliPagePicture(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void set_bmp(const ScalableBitmap& bmp);
//w29
void msw_rescale();
protected:
ScalableBitmap m_bmp;
wxStaticBitmap* m_img;
};
//w29
enum class CaliPageActionType : int
{
CALI_ACTION_START = 0,
CALI_ACTION_CALI,
CALI_ACTION_PREV,
CALI_ACTION_NEXT,
CALI_ACTION_CALI_NEXT,
CALI_ACTION_GO_HOME,
//w29
CALI_ACTION_FLOW_COARSE,
CALI_ACTION_FLOW_FINE,
CALI_ACTION_PA_LINE,
CALI_ACTION_PA_PATTERN,
CALI_ACTION_PA_TOWER
};
class CaliPageButton : public Button
{
public:
CaliPageButton(wxWindow* parent, CaliPageActionType type, wxString text = wxEmptyString);
CaliPageActionType get_action_type() { return m_action_type; }
void msw_rescale();
private:
CaliPageActionType m_action_type;
};
//w29
class CaliPageActionPanel : public wxPanel
{
public:
CaliPageActionPanel(wxWindow* parent,
CalibMode cali_mode,
CaliPageType page_type,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void bind_button(CaliPageActionType action_type, bool is_block);
void show_button(CaliPageActionType action_type, bool show = true);
void enable_button(CaliPageActionType action_type, bool enable = true);
void msw_rescale();
protected:
std::vector<CaliPageButton*> m_action_btns;
};
class CalibrationWizardPage : public wxPanel
{
public:
CalibrationWizardPage(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
~CalibrationWizardPage() {};
CaliPageType get_page_type() { return m_page_type; }
CalibrationWizardPage* get_prev_page() { return m_prev_page; }
CalibrationWizardPage* get_next_page() { return m_next_page; }
void set_prev_page(CalibrationWizardPage* prev) { m_prev_page = prev; }
void set_next_page(CalibrationWizardPage* next) { m_next_page = next; }
CalibrationWizardPage* chain(CalibrationWizardPage* next)
{
set_next_page(next);
next->set_prev_page(this);
return next;
}
virtual void update(MachineObject* obj) { curr_obj = obj; }
//w29
virtual void set_cali_filament_mode(CalibrationFilamentMode mode) {
m_cali_filament_mode = mode;
}
virtual void set_cali_method(CalibrationMethod method) {
m_cali_method = method;
if (method == CalibrationMethod::CALI_METHOD_MANUAL) {
set_cali_filament_mode(CalibrationFilamentMode::CALI_MODEL_SINGLE);
}
}
virtual void msw_rescale();
//w29
protected:
CalibMode m_cali_mode;
CaliPageType m_page_type;
CalibrationFilamentMode m_cali_filament_mode;
CalibrationMethod m_cali_method{ CalibrationMethod::CALI_METHOD_MANUAL };
MachineObject* curr_obj { nullptr };
wxWindow* m_parent { nullptr };
CaliPageCaption* m_page_caption { nullptr };
CaliPageActionPanel* m_action_panel { nullptr };
Label* m_statictext_printer_msg{ nullptr };
private:
CalibrationWizardPage* m_prev_page {nullptr};
CalibrationWizardPage* m_next_page {nullptr};
};
wxDECLARE_EVENT(EVT_CALI_ACTION, wxCommandEvent);
wxDECLARE_EVENT(EVT_CALI_TRAY_CHANGED, wxCommandEvent);
}} // namespace Slic3r::GUI
#endif

View File

@@ -0,0 +1,817 @@
#include <regex>
#include "CalibrationWizardPresetPage.hpp"
#include "I18N.hpp"
#include "Widgets/Label.hpp"
#include "MsgDialog.hpp"
#include "libslic3r/Print.hpp"
#include "Tab.hpp"
namespace Slic3r { namespace GUI {
static int PA_LINE = 0;
static int PA_PATTERN = 1;
//w29
static int PA_TOWER = 2;
CaliPresetCaliStagePanel::CaliPresetCaliStagePanel(
wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style)
: wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
m_top_sizer = new wxBoxSizer(wxVERTICAL);
create_panel(this);
this->SetSizer(m_top_sizer);
m_top_sizer->Fit(this);
}
void CaliPresetCaliStagePanel::create_panel(wxWindow* parent)
{
//w29
auto title = new Label(parent, _L("Calibration Type"));
title->SetFont(Label::Head_14);
title->Hide();
m_top_sizer->Add(title);
m_top_sizer->AddSpacer(FromDIP(15));
m_top_sizer->AddSpacer(FromDIP(10));
input_panel = new wxPanel(parent);
input_panel->Hide();
auto input_sizer = new wxBoxSizer(wxHORIZONTAL);
input_panel->SetSizer(input_sizer);
input_sizer->AddSpacer(FromDIP(18));
m_top_sizer->Add(input_panel);
m_top_sizer->AddSpacer(PRESET_GAP);
Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
SetFocusIgnoringChildren();
});
}
//w29
CaliPresetWarningPanel::CaliPresetWarningPanel(
wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style)
: wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
m_top_sizer = new wxBoxSizer(wxHORIZONTAL);
create_panel(this);
this->SetSizer(m_top_sizer);
m_top_sizer->Fit(this);
}
void CaliPresetWarningPanel::create_panel(wxWindow* parent)
{
m_warning_text = new Label(parent, wxEmptyString);
m_warning_text->SetFont(Label::Body_13);
m_warning_text->SetForegroundColour(wxColour(230, 92, 92));
m_warning_text->Wrap(CALIBRATION_TEXT_MAX_LENGTH);
m_top_sizer->Add(m_warning_text, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(5));
}
void CaliPresetWarningPanel::set_warning(wxString text)
{
m_warning_text->SetLabel(text);
}
CaliPresetCustomRangePanel::CaliPresetCustomRangePanel(
wxWindow* parent,
int input_value_nums,
//w29
bool scale,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style)
: wxPanel(parent, id, pos, size, style)
, m_input_value_nums(input_value_nums)
{
SetBackgroundColour(*wxWHITE);
m_title_texts.resize(input_value_nums);
m_value_inputs.resize(input_value_nums);
m_top_sizer = new wxBoxSizer(wxHORIZONTAL);
//w29
create_panel(this, scale);
this->SetSizer(m_top_sizer);
m_top_sizer->Fit(this);
}
void CaliPresetCustomRangePanel::set_unit(wxString unit)
{
for (size_t i = 0; i < m_input_value_nums; ++i) {
m_value_inputs[i]->SetLabel(unit);
}
}
void CaliPresetCustomRangePanel::set_titles(wxArrayString titles)
{
if (titles.size() != m_input_value_nums)
return;
for (size_t i = 0; i < m_input_value_nums; ++i) {
m_title_texts[i]->SetLabel(titles[i]);
}
}
void CaliPresetCustomRangePanel::set_values(wxArrayString values) {
if (values.size() != m_input_value_nums)
return;
for (size_t i = 0; i < m_input_value_nums; ++i) {
m_value_inputs[i]->GetTextCtrl()->SetValue(values[i]);
}
}
wxArrayString CaliPresetCustomRangePanel::get_values()
{
wxArrayString result;
for (size_t i = 0; i < m_input_value_nums; ++i) {
result.push_back(m_value_inputs[i]->GetTextCtrl()->GetValue());
}
return result;
}
//w29
void CaliPresetCustomRangePanel::create_panel(wxWindow* parent , bool scale)
{
wxBoxSizer* horiz_sizer;
horiz_sizer = new wxBoxSizer(wxHORIZONTAL);
for (size_t i = 0; i < m_input_value_nums; ++i) {
if (i > 0) {
horiz_sizer->Add(FromDIP(10), 0, 0, wxEXPAND, 0);
}
wxBoxSizer *item_sizer;
item_sizer = new wxBoxSizer(wxVERTICAL);
m_title_texts[i] = new Label(parent, _L("Title"));
m_title_texts[i]->Wrap(-1);
//w29
m_title_texts[i]->SetFont(::Label::Head_14);
item_sizer->Add(m_title_texts[i], 0, wxALL, 0);
//w29
if (m_input_value_nums == 1 && scale)
m_value_inputs[i] = new TextInput(parent, wxEmptyString, wxString::FromUTF8("°C"), "", wxDefaultPosition, wxSize(20 * wxGetApp().em_unit(), 30 * wxGetApp().em_unit() / 10), 0);
else
m_value_inputs[i] = new TextInput(parent, wxEmptyString, wxString::FromUTF8("°C"), "", wxDefaultPosition, CALIBRATION_FROM_TO_INPUT_SIZE, 0);
m_value_inputs[i]->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
m_value_inputs[i]->GetTextCtrl()->Bind(wxEVT_TEXT, [this, i](wxCommandEvent& event) {
std::string number = m_value_inputs[i]->GetTextCtrl()->GetValue().ToStdString();
std::string decimal_point;
std::string expression = "^[-+]?[0-9]+([,.][0-9]+)?$";
std::regex decimalRegex(expression);
int decimal_number;
if (std::regex_match(number, decimalRegex)) {
std::smatch match;
if (std::regex_search(number, match, decimalRegex)) {
std::string decimalPart = match[1].str();
if (decimalPart != "")
decimal_number = decimalPart.length() - 1;
else
decimal_number = 0;
}
int max_decimal_length;
if (i <= 1)
max_decimal_length = 3;
else if (i >= 2)
max_decimal_length = 3;
if (decimal_number > max_decimal_length) {
int allowed_length = number.length() - decimal_number + max_decimal_length;
number = number.substr(0, allowed_length);
m_value_inputs[i]->GetTextCtrl()->SetValue(number);
m_value_inputs[i]->GetTextCtrl()->SetInsertionPointEnd();
}
}
// input is not a number, invalid.
else
BOOST_LOG_TRIVIAL(trace) << "The K input string is not a valid number when calibrating. ";
});
item_sizer->Add(m_value_inputs[i], 0, wxALL, 0);
horiz_sizer->Add(item_sizer, 0, wxEXPAND, 0);
}
m_top_sizer->Add(horiz_sizer, 0, wxEXPAND, 0);
}
CaliPresetTipsPanel::CaliPresetTipsPanel(
wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style)
: wxPanel(parent, id, pos, size, style)
{
this->SetBackgroundColour(wxColour(238, 238, 238));
this->SetMinSize(wxSize(MIN_CALIBRATION_PAGE_WIDTH, -1));
m_top_sizer = new wxBoxSizer(wxVERTICAL);
create_panel(this);
this->SetSizer(m_top_sizer);
m_top_sizer->Fit(this);
}
void CaliPresetTipsPanel::create_panel(wxWindow* parent)
{
m_top_sizer->AddSpacer(FromDIP(10));
auto preset_panel_tips = new Label(parent, _L("A test model will be printed. Please clear the build plate and place it back to the hot bed before calibration."));
preset_panel_tips->SetFont(Label::Body_14);
preset_panel_tips->Wrap(CALIBRATION_TEXT_MAX_LENGTH * 1.5f);
m_top_sizer->Add(preset_panel_tips, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(20));
m_top_sizer->AddSpacer(FromDIP(10));
auto info_sizer = new wxFlexGridSizer(0, 3, 0, FromDIP(10));
info_sizer->SetFlexibleDirection(wxBOTH);
info_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
auto nozzle_temp_sizer = new wxBoxSizer(wxVERTICAL);
auto nozzle_temp_text = new Label(parent, _L("Nozzle temperature"));
nozzle_temp_text->SetFont(Label::Body_12);
m_nozzle_temp = new TextInput(parent, wxEmptyString, wxString::FromUTF8("°C"), "", wxDefaultPosition, CALIBRATION_FROM_TO_INPUT_SIZE, wxTE_READONLY);
m_nozzle_temp->SetBorderWidth(0);
nozzle_temp_sizer->Add(nozzle_temp_text, 0, wxALIGN_LEFT);
nozzle_temp_sizer->Add(m_nozzle_temp, 0, wxEXPAND);
nozzle_temp_text->Hide();
m_nozzle_temp->Hide();
auto bed_temp_sizer = new wxBoxSizer(wxHORIZONTAL);
auto printing_param_text = new Label(parent, _L("Printing Parameters"));
printing_param_text->SetFont(Label::Head_12);
printing_param_text->Wrap(CALIBRATION_TEXT_MAX_LENGTH);
bed_temp_sizer->Add(printing_param_text, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(20));
auto bed_temp_text = new Label(parent, _L("Bed temperature"));
bed_temp_text->SetFont(Label::Body_12);
m_bed_temp = new Label(parent, wxString::FromUTF8("- °C"));
m_bed_temp->SetFont(Label::Body_12);
bed_temp_sizer->Add(bed_temp_text, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(10));
bed_temp_sizer->Add(m_bed_temp, 0, wxALIGN_CENTER);
auto max_flow_sizer = new wxBoxSizer(wxVERTICAL);
auto max_flow_text = new Label(parent, _L("Max volumetric speed"));
max_flow_text->SetFont(Label::Body_12);
m_max_volumetric_speed = new TextInput(parent, wxEmptyString, wxString::FromUTF8("mm³"), "", wxDefaultPosition, CALIBRATION_FROM_TO_INPUT_SIZE, wxTE_READONLY);
m_max_volumetric_speed->SetBorderWidth(0);
max_flow_sizer->Add(max_flow_text, 0, wxALIGN_LEFT);
max_flow_sizer->Add(m_max_volumetric_speed, 0, wxEXPAND);
max_flow_text->Hide();
m_max_volumetric_speed->Hide();
m_nozzle_temp->GetTextCtrl()->Bind(wxEVT_SET_FOCUS, [](auto&) {});
m_max_volumetric_speed->GetTextCtrl()->Bind(wxEVT_SET_FOCUS, [](auto&) {});
info_sizer->Add(nozzle_temp_sizer);
info_sizer->Add(bed_temp_sizer);
info_sizer->Add(max_flow_sizer);
m_top_sizer->Add(info_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(20));
m_top_sizer->AddSpacer(FromDIP(10));
}
void CaliPresetTipsPanel::set_params(int nozzle_temp, int bed_temp, float max_volumetric)
{
wxString text_nozzle_temp = wxString::Format("%d", nozzle_temp);
m_nozzle_temp->GetTextCtrl()->SetValue(text_nozzle_temp);
std::string bed_temp_text = bed_temp==0 ? "-": std::to_string(bed_temp);
m_bed_temp->SetLabel(wxString::FromUTF8(bed_temp_text + "°C"));
wxString flow_val_text = wxString::Format("%0.2f", max_volumetric);
m_max_volumetric_speed->GetTextCtrl()->SetValue(flow_val_text);
}
void CaliPresetTipsPanel::get_params(int& nozzle_temp, int& bed_temp, float& max_volumetric)
{
try {
nozzle_temp = stoi(m_nozzle_temp->GetTextCtrl()->GetValue().ToStdString());
}
catch (...) {
nozzle_temp = 0;
}
try {
bed_temp = stoi(m_bed_temp->GetLabel().ToStdString());
}
catch (...) {
bed_temp = 0;
}
try {
max_volumetric = stof(m_max_volumetric_speed->GetTextCtrl()->GetValue().ToStdString());
}
catch (...) {
max_volumetric = 0.0f;
}
}
CalibrationPresetPage::CalibrationPresetPage(
wxWindow* parent,
CalibMode cali_mode,
bool custom_range,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style)
: CalibrationWizardPage(parent, id, pos, size, style)
, m_show_custom_range(custom_range)
{
SetBackgroundColour(*wxWHITE);
m_cali_mode = cali_mode;
m_page_type = CaliPageType::CALI_PAGE_PRESET;
m_cali_filament_mode = CalibrationFilamentMode::CALI_MODEL_SINGLE;
m_top_sizer = new wxBoxSizer(wxVERTICAL);
create_page(this);
this->SetSizer(m_top_sizer);
m_top_sizer->Fit(this);
}
void CalibrationPresetPage::msw_rescale()
{
CalibrationWizardPage::msw_rescale();
//w29
//m_ams_sync_button->msw_rescale();
for (auto& comboBox : m_filament_comboBox_list) {
comboBox->msw_rescale();
}
}
//w29
#define NOZZLE_LIST_COUNT 4
#define NOZZLE_LIST_DEFAULT 1
float nozzle_diameter_list[NOZZLE_LIST_COUNT] = {0.2, 0.4, 0.6, 0.8 };
//w29
void CalibrationPresetPage::add_bitmap(wxWindow* parent, wxBoxSizer* m_top_sizer, std::string img,const bool custom_cut , const int px_cnt) {
wxBitmap before_bmp;// = create_scaled_bitmap(img, this, 350);
if (custom_cut)
before_bmp = create_scaled_bitmap(img, this, px_cnt);
else
before_bmp = create_scaled_bitmap(img, this, 350);
wxStaticBitmap* m_bmp_intro_temp{ nullptr };
wxBoxSizer* m_images_sizer_temp{ nullptr };
if (!m_bmp_intro_temp)
m_bmp_intro_temp = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0);
m_bmp_intro_temp->SetBitmap(before_bmp);
m_images_sizer_temp = new wxBoxSizer(wxHORIZONTAL);
m_images_sizer_temp->Add(m_bmp_intro_temp, 0, wxALL, 0);
m_top_sizer->Add(m_images_sizer_temp, 0, wxALIGN_CENTER, 0);
m_bmp_intro_temp = { nullptr };
m_images_sizer_temp = { nullptr };
}
//w29
void CalibrationPresetPage::create_gif_images(wxWindow* parent, wxBoxSizer* m_top_sizer, const std::string image) {
wxAnimation animation;
if (animation.LoadFile(Slic3r::resources_dir() + "/images/" + image + ".gif")) {
wxAnimationCtrl* gifCtrl = new wxAnimationCtrl(parent, wxID_ANY, animation);
gifCtrl->Play();
m_top_sizer->Add(gifCtrl, 0, wxALIGN_CENTER | wxALL, 10);
}
}
//w29
void CalibrationPresetPage::create_page_flow_coarse(wxWindow* parent, wxBoxSizer* m_top_sizer) {
create_paph(parent, _L("Step 1"), _L("You only need to click the \"Calibrate\" button below and wait for a short time.After successful slicing, you have three ways to print:\
\n1. Directly send the sliced file and print it;\
\n2. Send the sliced file to the printer via the network and manually select the sliced file for printing;.\
\n3. Send the sliced file to a storage medium and print it through the storage medium.\
\nAfter successful printing, you will receive the model as shown in the picture. Choose the number with the smoothest surface. \
\nThe value of the number \"0\" in the figure has the smoothest surface, so the value obtained from coarse calibration is \"1 + 0.00 = 1\", which can be used as the intermediate value for fine calibration."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
add_bitmap(parent, m_top_sizer, "flowcoarseresult", true, 400);
m_top_sizer->AddSpacer(PRESET_GAP);
m_custom_range_panel = new CaliPresetCustomRangePanel(parent, 1);
//m_value_inputs
m_top_sizer->Add(m_custom_range_panel, 0, wxALIGN_CENTER);
create_paph(parent, _L("Step 2"), _L("You can also directly apply this value to your printing configuration, return to the \"Prepare\" interface, enter the filaments parameters to make modifications, and then click the save button to save your configuration."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
//add_bitmap(parent, m_top_sizer, "flowratiocoarseset", true, 180);
create_gif_images(parent, m_top_sizer, "flowratiocoarseset");
}
//w29
void CalibrationPresetPage::create_page_flow_fine(wxWindow* parent, wxBoxSizer* m_top_sizer) {
create_paph(parent, _L("Step 1"), _L("After passing the \"coarse calibration\", the intermediate value \"1\" was obtained. Enter this value into the text box below and follow the steps in the \"coarse calibration\" to print."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
m_custom_range_panel = new CaliPresetCustomRangePanel(parent, 1,true);
m_top_sizer->Add(m_custom_range_panel, 0, wxALIGN_CENTER);
m_top_sizer->AddSpacer(PRESET_GAP);
create_paph(parent, _L("Step 2"), _L("After printing, select a number with the smoothest and smoothest surface, as shown in the figure below as \" - 1\". The optimal flow rate for obtaining current filaments is \"1.00 - 0.01 = 0.99\"."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
add_bitmap(parent, m_top_sizer, "flowfineresult", true, 430);
m_top_sizer->AddSpacer(PRESET_GAP);
create_paph(parent, _L("Step 3"), _L("Fill in the value obtained in step 2 into the \"Flow ratio\" in the filaments settings, and you have completed the flow calibration here"));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
//add_bitmap(parent, m_top_sizer, "flowratioset", true, 180);
create_gif_images(parent, m_top_sizer, "flowratioset");
}
//w29
void CalibrationPresetPage::create_page_pa_line(wxWindow* parent, wxBoxSizer* m_top_sizer) {
create_paph(parent, _L("Step 1"), _L("Enter the minimum pressure advance value, maximum pressure advance value, and step size at the bottom of the current page, click the \"Calibrate\" button at the bottom of the page, and wait for a little time.The software will automatically set the calibration configuration."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
m_custom_range_panel = new CaliPresetCustomRangePanel(parent);
m_top_sizer->Add(m_custom_range_panel, 0, wxALIGN_CENTER);
m_top_sizer->AddSpacer(PRESET_GAP);
auto extra_text = new Label(parent, _L("After successful slicing, you have three methods to perform the operation:\
\n1. Directly send the sliced file and print it;\
\n2. Send the sliced file to the printer via the network and manually select the sliced file for printing;\
\n3. Send the sliced file to a storage medium and print it through the storage medium.\
\nReferring to the process in the \"PA Line\", you will print the calibration model as shown in the following figure."));
extra_text->SetFont(Label::Body_14);
extra_text->Wrap(FromDIP(1000));
extra_text->SetMinSize({ FromDIP(1000), -1 });
m_top_sizer->Add(extra_text);
m_top_sizer->AddSpacer(PRESET_GAP);
add_bitmap(parent, m_top_sizer, "PressureAdvanceLine", true, 400);
m_top_sizer->AddSpacer(PRESET_GAP);
create_paph(parent, _L("Step 2"), _L("After printing is completed, select the smoothest line, enter its corresponding value into the software and save it."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
create_gif_images(parent, m_top_sizer, "pavalue03");
}
//w29
void CalibrationPresetPage::create_page_pa_pattern(wxWindow* parent, wxBoxSizer* m_top_sizer) {
create_paph(parent, _L("Step 1"), _L("Enter the minimum pressure advance value, maximum pressure advance value, and step size at the bottom of the current page, click the \"Calibrate\" button at the bottom of the page, and wait for a little time.The software will automatically set the calibration configuration."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
m_custom_range_panel = new CaliPresetCustomRangePanel(parent);
m_top_sizer->Add(m_custom_range_panel, 0, wxALIGN_CENTER);
m_top_sizer->AddSpacer(PRESET_GAP);
auto extra_text = new Label(parent, _L("After successful slicing, you have three methods to perform the operation:\
\n1. Directly send the sliced file and print it;\
\n2. Send the sliced file to the printer via the network and manually select the sliced file for printing;\
\n3. Send the sliced file to a storage medium and print it through the storage medium.\
\nReferring to the process in the \"PA Pattern\", you will print the calibration model as shown in the following figure."));
extra_text->SetFont(Label::Body_14);
extra_text->Wrap(FromDIP(1000));
extra_text->SetMinSize({ FromDIP(1000), -1 });
m_top_sizer->Add(extra_text);
m_top_sizer->AddSpacer(PRESET_GAP);
add_bitmap(parent, m_top_sizer, "PressureAdvancePattern", true, 350);
m_top_sizer->AddSpacer(PRESET_GAP);
create_paph(parent, _L("Step 2"), _L("There are three feature regions in this model that need to be observed:\
\n1. In regions 1 and 3 of the figure, when the pressure advance value is too small, material stacking will occur and the endpoints will exceed the bounding box. When the pressure advance value is too high, there may be a shortage of wire and the endpoint has not reached the bounding box.\
\n2. In region 2 of the figure, when the pressure advance value is too small, material stacking may occur, which can cause excessive overflow at corners during actual printing. When the pressure value is too high, there may be missing threads. In actual printing, it can cause corners to become rounded and lead to missing threads.\
\nFinally, save the value with the best surface effect."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
create_gif_images(parent, m_top_sizer, "pavalue03");
}
//w29
void CalibrationPresetPage::create_page_pa_tower(wxWindow* parent, wxBoxSizer* m_top_sizer) {
create_paph(parent, _L("Step 1"), _L("Enter the minimum pressure advance value, maximum pressure advance value, and step size at the bottom of the current page, click the \"Calibrate\" button at the bottom of the page, and wait for a little time. The software will automatically set the calibration configuration."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
m_custom_range_panel = new CaliPresetCustomRangePanel(parent);
m_top_sizer->Add(m_custom_range_panel, 0, wxALIGN_CENTER);
m_top_sizer->AddSpacer(PRESET_GAP);
auto extra_text = new Label(parent, _L("After successful slicing, you have three methods to perform the operation:\
\n1. Directly send the sliced file and print it;\
\n2. Send the sliced file to the printer via the network and manually select the sliced file for printing;\
\n3. Send the sliced file to a storage medium and print it through the storage medium.\
\nReferring to the process in the \"PA Tower\", you will print the calibration model as shown in the following figure."));
extra_text->SetFont(Label::Body_14);
extra_text->Wrap(FromDIP(1000));
extra_text->SetMinSize({ FromDIP(1000), -1 });
m_top_sizer->Add(extra_text);
m_top_sizer->AddSpacer(PRESET_GAP);
add_bitmap(parent, m_top_sizer, "patowermodel", true, 350);
m_top_sizer->AddSpacer(PRESET_GAP);
create_paph(parent, _L("Step 2"), _L("Observe each corner of the model and calibrate it. The pressure advance increases by a step value gradient with every 5mm increase in height. If the pressure advance value is too small, there will be excessive extrusion at the corner. If the pressure advance value is too large, there will be a right angle becoming rounded or even missing threads at the corner. Determine the optimal position for the effect and use a scale to determine the height."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
create_paph(parent, _L("Step 3"), _L("Calculate the optimal pressure advance value using the given formula:\
\nPressure Advance = k_Start + floor(height ÷ 5) × k_Step\
\nNOTICE: floor() represents rounding downwards\
\nAccording to the measured values, the pressure advance value in the figure is : 0.00 + floor(22.7 ÷ 5) × 0.005 = 0.02\
\nFinally, save the value with the best surface effect."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
create_gif_images(parent, m_top_sizer, "pavalue02");
}
void CalibrationPresetPage::create_page_max_volumetric_speed(wxWindow* parent, wxBoxSizer* m_top_sizer) {
create_paph(parent, _L("Step 1"), _L("Enter the minimum volumetric speed value, maximum volumetric speed value, and step size at the bottom of the current page, click the \"Calibrate\" button at the bottom of the page, and wait for a little time. The software will automatically set the calibration configuration."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
m_custom_range_panel = new CaliPresetCustomRangePanel(parent);
m_top_sizer->Add(m_custom_range_panel, 0, wxALIGN_CENTER);
m_top_sizer->AddSpacer(PRESET_GAP);
auto extra_text = new Label(parent, _L("After successful slicing, you have three methods to perform the operation:\
\n1. Directly send the sliced file and print it;\
\n2. Send the sliced file to the printer via the network and manually select the sliced file for printing;\
\n3. Send the sliced file to a storage medium and print it through the storage medium.\
\nReferring to the process in the \"Max Volumetric Speed Calibration\", you will print the calibration model as shown in the following figure."));
extra_text->SetFont(Label::Body_14);
extra_text->Wrap(FromDIP(1000));
extra_text->SetMinSize({ FromDIP(1000), -1 });
m_top_sizer->Add(extra_text);
m_top_sizer->AddSpacer(PRESET_GAP);
add_bitmap(parent, m_top_sizer, "Volumetricspeedmodel", true, 350);
create_paph(parent, _L("Step 2"), _L("It can be observed that at a certain height, the model begins to show missing fibers. There are two methods to measure the maximum volumetric velocity:\
\n1. Observing the number of notches nums on the right side, you can use StartV + (step * 2) = Max Volumetric Speed.\
\n2. In the \"Preview\" interface, view the Gcode of the model, find the \"Flow\" value corresponding to the missing part, and save it."));
m_top_sizer->Add(m_txt_title);
m_top_sizer->Add(m_txt_content);
m_top_sizer->AddSpacer(PRESET_GAP);
create_gif_images(parent, m_top_sizer, "Volumetricspeedset");
}
void CalibrationPresetPage::create_page(wxWindow* parent)
{
m_page_caption = new CaliPageCaption(parent, m_cali_mode);
m_page_caption->show_prev_btn(true);
m_top_sizer->Add(m_page_caption, 0, wxEXPAND, 0);
if (m_cali_mode == CalibMode::Calib_Flow_Rate) {
wxArrayString steps;
steps.Add(_L("Preset"));
steps.Add(_L("Calibration1"));
steps.Add(_L("Calibration2"));
steps.Add(_L("Record Factor"));
m_step_panel = new CaliPageStepGuide(parent, steps);
m_step_panel->set_steps(0);
}
else {
wxArrayString steps;
steps.Add(_L("Preset"));
steps.Add(_L("Calibration"));
//w29
//steps.Add(_L("Record Factor"));
m_step_panel = new CaliPageStepGuide(parent, steps);
m_step_panel->set_steps(0);
}
if (m_step_panel)
m_step_panel->Show(false);
//w29
if (m_cali_mode == CalibMode::Calib_Flow_Rate_Coarse)
create_page_flow_coarse(parent, m_top_sizer);
if (m_cali_mode == CalibMode::Calib_Flow_Rate_Fine)
create_page_flow_fine(parent, m_top_sizer);
if (m_cali_mode == CalibMode::Calib_PA_Line)
create_page_pa_line(parent, m_top_sizer);
if (m_cali_mode == CalibMode::Calib_PA_Pattern)
create_page_pa_pattern(parent, m_top_sizer);
if (m_cali_mode == CalibMode::Calib_PA_Tower)
create_page_pa_tower(parent, m_top_sizer);
if (m_cali_mode == CalibMode::Calib_Vol_speed_Tower)
create_page_max_volumetric_speed(parent, m_top_sizer);
m_top_sizer->AddSpacer(PRESET_GAP);
m_action_panel = new CaliPageActionPanel(parent, m_cali_mode, CaliPageType::CALI_PAGE_PRESET);
m_top_sizer->Add(m_action_panel, 0, wxEXPAND, 0);
m_top_sizer->Add(m_step_panel, 0, wxEXPAND, 0);
m_cali_stage_panel = new CaliPresetCaliStagePanel(parent);
m_cali_stage_panel->set_parent(this);
m_top_sizer->Add(m_cali_stage_panel, 0);
//w29
m_warning_panel = new CaliPresetWarningPanel(parent);
m_tips_panel = new CaliPresetTipsPanel(parent);
m_tips_panel->Hide();
m_statictext_printer_msg = new Label(this, wxEmptyString, wxALIGN_CENTER_HORIZONTAL);
m_statictext_printer_msg->SetFont(::Label::Body_13);
m_statictext_printer_msg->Hide();
}
wxString CalibrationPresetPage::format_text(wxString& m_msg)
{
if (wxGetApp().app_config->get("language") != "zh_CN") { return m_msg; }
wxString out_txt = m_msg;
wxString count_txt = "";
int new_line_pos = 0;
for (int i = 0; i < m_msg.length(); i++) {
auto text_size = m_statictext_printer_msg->GetTextExtent(count_txt);
if (text_size.x < (FromDIP(600))) {
count_txt += m_msg[i];
}
else {
out_txt.insert(i - 1, '\n');
count_txt = "";
}
}
return out_txt;
}
void CalibrationPresetPage::stripWhiteSpace(std::string& str)
{
if (str == "") { return; }
string::iterator cur_it;
cur_it = str.begin();
while (cur_it != str.end()) {
if ((*cur_it) == '\n' || (*cur_it) == ' ') {
cur_it = str.erase(cur_it);
}
else {
cur_it++;
}
}
}
//w29
void CalibrationPresetPage::update(MachineObject* obj)
{
curr_obj = obj;
}
void CalibrationPresetPage::set_cali_filament_mode(CalibrationFilamentMode mode)
{
CalibrationWizardPage::set_cali_filament_mode(mode);
for (int i = 0; i < m_filament_comboBox_list.size(); i++) {
m_filament_comboBox_list[i]->set_select_mode(mode);
}
}
void CalibrationPresetPage::set_cali_method(CalibrationMethod method)
{
if (method == CalibrationMethod::CALI_METHOD_MANUAL) {
if (m_cali_mode == CalibMode::Calib_PA_Line || m_cali_mode == CalibMode::Calib_PA_Pattern || m_cali_mode == CalibMode::Calib_PA_Tower) {
if (m_cali_stage_panel)
m_cali_stage_panel->Show(false);
if (m_custom_range_panel) {
wxArrayString titles;
titles.push_back(_L("From k Value"));
titles.push_back(_L("To k Value"));
titles.push_back(_L("Value step"));
m_custom_range_panel->set_titles(titles);
wxArrayString values;
{
values.push_back(wxString::Format(wxT("%.0f"), 0));
values.push_back(wxString::Format(wxT("%.2f"), 0.04));
values.push_back(wxString::Format(wxT("%.3f"), 0.002));
}
m_custom_range_panel->set_values(values);
m_custom_range_panel->set_unit("");
m_custom_range_panel->Show();
}
}
//w29
else if ( m_cali_mode == CalibMode::Calib_Flow_Rate_Fine || m_cali_mode == CalibMode::Calib_Flow_Rate) {
if (m_cali_stage_panel)
m_cali_stage_panel->Hide();
if (m_custom_range_panel) {
wxArrayString titles;
titles.push_back(_L("Flow Ratio"));
m_custom_range_panel->set_titles(titles);
wxArrayString values;
//w29
values.push_back(wxString::Format(wxT("%.0f"), 1.0));
m_custom_range_panel->set_values(values);
m_custom_range_panel->set_unit("");
m_custom_range_panel->Show();
}
}
else if (m_cali_mode == CalibMode::Calib_Flow_Rate_Coarse) {
if (m_cali_stage_panel)
m_cali_stage_panel->Show(false);
if (m_custom_range_panel) {
m_custom_range_panel->Show(false);
}
}
}
else {
wxArrayString steps;
steps.Add(_L("Preset"));
steps.Add(_L("Calibration"));
steps.Add(_L("Record Factor"));
m_step_panel->set_steps_string(steps);
m_step_panel->set_steps(0);
if (m_cali_stage_panel)
m_cali_stage_panel->Show(false);
if (m_custom_range_panel)
m_custom_range_panel->Show(false);
}
}
//w29
wxArrayString CalibrationPresetPage::get_custom_range_values()
{
if (m_custom_range_panel) {
return m_custom_range_panel->get_values();
}
return wxArrayString();
}
//w29
MaxVolumetricSpeedPresetPage::MaxVolumetricSpeedPresetPage(
wxWindow *parent, CalibMode cali_mode, bool custom_range, wxWindowID id, const wxPoint &pos, const wxSize &size, long style)
: CalibrationPresetPage(parent, cali_mode, custom_range, id, pos, size, style)
{
if (custom_range && m_custom_range_panel) {
wxArrayString titles;
titles.push_back(_L("From Volumetric Speed"));
titles.push_back(_L("To Volumetric Speed"));
titles.push_back(_L("Step"));
//w29
wxArrayString values;
values.push_back(wxString::Format(wxT("%.1f"), 5.0));
values.push_back(wxString::Format(wxT("%.1f"), 15.0));
values.push_back(wxString::Format(wxT("%.1f"), 0.1));
m_custom_range_panel->set_titles(titles);
m_custom_range_panel->set_values(values);
//w29
m_custom_range_panel->set_unit("");
}
}
}}

View File

@@ -0,0 +1,179 @@
#ifndef slic3r_GUI_CalibrationWizardPresetPage_hpp_
#define slic3r_GUI_CalibrationWizardPresetPage_hpp_
#include "CalibrationWizardPage.hpp"
namespace Slic3r { namespace GUI {
//w29
class CalibrationPresetPage;
class CaliPresetCaliStagePanel : public wxPanel
{
public:
CaliPresetCaliStagePanel(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void create_panel(wxWindow* parent);
//w29
void set_parent(CalibrationPresetPage* parent) { m_stage_panel_parent = parent; }
//w29
protected:
//w29
wxBoxSizer* m_top_sizer;
wxPanel* input_panel;
CalibrationPresetPage* m_stage_panel_parent;
};
//w29
class CaliPresetWarningPanel : public wxPanel
{
public:
CaliPresetWarningPanel(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void create_panel(wxWindow* parent);
void set_warning(wxString text);
protected:
wxBoxSizer* m_top_sizer;
Label* m_warning_text;
};
class CaliPresetTipsPanel : public wxPanel
{
public:
CaliPresetTipsPanel(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void create_panel(wxWindow* parent);
void set_params(int nozzle_temp, int bed_temp, float max_volumetric);
void get_params(int& nozzle_temp, int& bed_temp, float& max_volumetric);
protected:
wxBoxSizer* m_top_sizer;
TextInput* m_nozzle_temp;
Label* m_bed_temp;
TextInput* m_max_volumetric_speed;
};
class CaliPresetCustomRangePanel : public wxPanel
{
public:
CaliPresetCustomRangePanel(wxWindow* parent,
int input_value_nums = 3,
//w29
bool scale = false,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
//w29
void create_panel(wxWindow* parent , bool scale = false);
void set_unit(wxString unit);
void set_titles(wxArrayString titles);
void set_values(wxArrayString values);
wxArrayString get_values();
protected:
wxBoxSizer* m_top_sizer;
int m_input_value_nums;
std::vector<Label*> m_title_texts;
std::vector<TextInput*> m_value_inputs;
};
//w29
class CalibrationPresetPage : public CalibrationWizardPage
{
public:
CalibrationPresetPage(wxWindow* parent,
CalibMode cali_mode,
bool custom_range = false,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
//w29
void create_page(wxWindow* parent);
wxString format_text(wxString& m_msg);
void stripWhiteSpace(std::string& str);
void update(MachineObject* obj) override;
void set_cali_filament_mode(CalibrationFilamentMode mode) override;
void set_cali_method(CalibrationMethod method) override;
wxArrayString get_custom_range_values();
void msw_rescale() override;
//w29
void create_paph(wxWindow* parent, wxString title, wxString content)
{
m_txt_title = new Label(this, title);
m_txt_title->SetFont(Label::Head_14);
m_txt_title->Wrap(FromDIP(1000));
m_txt_title->SetMinSize({ FromDIP(1000), -1 });
m_txt_content = new Label(this, content);;
m_txt_content->SetFont(Label::Body_14);
m_txt_content->Wrap(FromDIP(1000));
m_txt_content->SetMinSize({ FromDIP(1000), -1 });
}
void create_page_flow_coarse(wxWindow* parent, wxBoxSizer* m_top_sizer);
void create_page_flow_fine(wxWindow* parent, wxBoxSizer* m_top_sizer);
void create_page_pa_line(wxWindow* parent, wxBoxSizer* m_top_sizer);
void create_page_pa_pattern(wxWindow* parent, wxBoxSizer* m_top_sizer);
void create_page_pa_tower(wxWindow* parent, wxBoxSizer* m_top_sizer);
void create_page_max_volumetric_speed(wxWindow* parent, wxBoxSizer* m_top_sizer);
void add_bitmap(wxWindow* parent, wxBoxSizer* m_top_sizer, std::string img, const bool custom_cut = false, const int px_cnt = 350);
void create_gif_images(wxWindow* parent, wxBoxSizer* m_top_sizer,const std::string image);
protected:
//w29
CaliPageStepGuide* m_step_panel{ nullptr };
CaliPresetCaliStagePanel* m_cali_stage_panel { nullptr };
CaliPresetWarningPanel* m_warning_panel { nullptr };
CaliPresetCustomRangePanel* m_custom_range_panel { nullptr };
CaliPresetTipsPanel* m_tips_panel { nullptr };
wxBoxSizer* m_top_sizer;
//w29
FilamentComboBoxList m_filament_comboBox_list;
bool m_show_custom_range { false };
MachineObject* curr_obj { nullptr };
//w29
Label* m_txt_title{ nullptr };
Label* m_txt_content{ nullptr };
};
class MaxVolumetricSpeedPresetPage : public CalibrationPresetPage
{
public:
MaxVolumetricSpeedPresetPage(wxWindow * parent,
CalibMode cali_mode,
bool custom_range = false,
wxWindowID id = wxID_ANY,
const wxPoint &pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
};
}} // namespace Slic3r::GUI
#endif

View File

@@ -0,0 +1,431 @@
#include "CalibrationWizardStartPage.hpp"
#include "I18N.hpp"
#include "Widgets/Label.hpp"
namespace Slic3r { namespace GUI {
#define CALIBRATION_START_PAGE_TEXT_MAX_LENGTH FromDIP(1000)
CalibrationStartPage::CalibrationStartPage(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
:CalibrationWizardPage(parent, id, pos, size, style)
{
m_top_sizer = new wxBoxSizer(wxVERTICAL);
}
void CalibrationStartPage::create_when(wxWindow* parent, wxString title, wxString content)
{
m_when_title = new Label(this, title);
m_when_title->SetFont(Label::Head_14);
m_when_title->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
m_when_title->SetMinSize({CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1});
m_when_content = new Label(this, content);;
m_when_content->SetFont(Label::Body_14);
m_when_content->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
m_when_content->SetMinSize({CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1});
}
void CalibrationStartPage::create_about(wxWindow* parent, wxString title, wxString content)
{
m_about_title = new Label(this, title);
m_about_title->SetFont(Label::Head_14);
m_about_title->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
m_about_title->SetMinSize({CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1});
m_about_content = new Label(this, content);
m_about_content->SetFont(Label::Body_14);
m_about_content->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
m_about_content->SetMinSize({CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1});
}
void CalibrationStartPage::create_bitmap(wxWindow* parent, const wxBitmap& before_img, const wxBitmap& after_img)
{
if (!m_before_bmp)
m_before_bmp = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0);
m_before_bmp->SetBitmap(before_img);
if (!m_after_bmp)
m_after_bmp = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0);
m_after_bmp->SetBitmap(after_img);
if (!m_images_sizer) {
m_images_sizer = new wxBoxSizer(wxHORIZONTAL);
m_images_sizer->Add(m_before_bmp, 0, wxALL, 0);
m_images_sizer->AddSpacer(FromDIP(20));
m_images_sizer->Add(m_after_bmp, 0, wxALL, 0);
}
}
void CalibrationStartPage::create_bitmap(wxWindow* parent, std::string before_img, std::string after_img)
{
wxBitmap before_bmp = create_scaled_bitmap(before_img, this, 350);
wxBitmap after_bmp = create_scaled_bitmap(after_img, this, 350);
create_bitmap(parent, before_bmp, after_bmp);
}
void CalibrationStartPage::create_bitmap(wxWindow* parent, std::string img) {
wxBitmap before_bmp = create_scaled_bitmap(img, this, 350);
if (!m_bmp_intro)
m_bmp_intro = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0);
m_bmp_intro->SetBitmap(before_bmp);
if (!m_images_sizer) {
m_images_sizer = new wxBoxSizer(wxHORIZONTAL);
m_images_sizer->Add(m_bmp_intro, 0, wxALL, 0);
}
}
//w29
void CalibrationStartPage::add_bitmap(wxWindow* parent, wxBoxSizer* m_top_sizer, std::string img , bool can_modify ,int modify_size) {
wxBitmap before_bmp; //= create_scaled_bitmap(img, this, 350);
if(can_modify)
before_bmp = create_scaled_bitmap(img, this, modify_size);
else
before_bmp = create_scaled_bitmap(img, this, 350);
wxStaticBitmap* m_bmp_intro_temp{ nullptr };
wxBoxSizer* m_images_sizer_temp{ nullptr };
if (!m_bmp_intro_temp)
m_bmp_intro_temp = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0);
m_bmp_intro_temp->SetBitmap(before_bmp);
m_images_sizer_temp = new wxBoxSizer(wxHORIZONTAL);
m_images_sizer_temp->Add(m_bmp_intro_temp, 0, wxALL, 0);
if(can_modify)
m_top_sizer->Add(m_images_sizer_temp, 0, wxALIGN_CENTER, 0);
else
m_top_sizer->Add(m_images_sizer_temp, 0, wxALL, 0);
if (!can_modify)
m_top_sizer->AddSpacer(PRESET_GAP);
m_bmp_intro_temp = { nullptr };
m_images_sizer_temp = { nullptr };
}
CalibrationPAStartPage::CalibrationPAStartPage(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: CalibrationStartPage(parent, id, pos, size, style)
{
m_cali_mode = CalibMode::Calib_PA_Line;
m_page_type = CaliPageType::CALI_PAGE_START;
create_page(this);
this->SetSizer(m_top_sizer);
Layout();
m_top_sizer->Fit(this);
}
CaliPresetTipsstartPanel::CaliPresetTipsstartPanel(
wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style)
: CalibrationStartPage(parent, id, pos, size, style)
{
this->SetBackgroundColour(wxColour(238, 238, 238));
this->SetMinSize(wxSize(MIN_CALIBRATION_PAGE_WIDTH, -1));
m_top_sizer = new wxBoxSizer(wxVERTICAL);
create_panel(this);
this->SetSizer(m_top_sizer);
m_top_sizer->Fit(this);
}
void CaliPresetTipsstartPanel::create_panel(wxWindow* parent)
{
m_top_sizer->AddSpacer(FromDIP(10));
auto preset_panel_tips = new Label(parent, _L("After completing the pressure pre calibration process, please create a new project before printing."));
preset_panel_tips->SetFont(Label::Body_14);
preset_panel_tips->Wrap(CALIBRATION_TEXT_MAX_LENGTH * 1.5f);
m_top_sizer->Add(preset_panel_tips, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(20));
add_bitmap(parent, m_top_sizer, "newProject",true,95);
//m_top_sizer->AddSpacer(FromDIP(10));
auto info_sizer = new wxFlexGridSizer(0, 3, 0, FromDIP(10));
info_sizer->SetFlexibleDirection(wxBOTH);
info_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
m_top_sizer->Add(info_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(20));
m_top_sizer->AddSpacer(FromDIP(10));
}
void CalibrationPAStartPage::create_page(wxWindow* parent)
{
m_page_caption = new CaliPageCaption(parent, CalibMode::Calib_PA_Line);
m_page_caption->show_prev_btn(false);
m_top_sizer->Add(m_page_caption, 0, wxALIGN_CENTER, 0);
create_when(parent,
_L("What is Pressure Advance Calibration ?"),
_L("From fluid mechanics, when a newtonian fluid flow through a hole, it needs pressure, and the pressure is proportional to the flow rate.\
\nAs the filament is not rigid body, when the extruder starts to extrude, the filament will be compressed to generate the pressure. The compression process will delay the response of the real flow, as the extruder only provides the amount of the filament that needs to extrude, no extra."));
m_top_sizer->Add(m_when_title);
m_top_sizer->Add(m_when_content);
m_top_sizer->AddSpacer(PRESET_GAP);
add_bitmap(parent, m_top_sizer, "PressureAdvanceCompare",true, 346);
m_top_sizer->AddSpacer(PRESET_GAP);
create_when(parent,
_L("When to Calibrate Pressure in Advance"),
_L("1.Use different brands of filaments, or the filaments are damp;\
\n2.The nozzle is worn or replaced with a different size nozzle;\
\n3.Use different printing parameters such as temperature and line width;\
\n4.PA calibration does not work with PETG."));
m_top_sizer->Add(m_when_title);
m_top_sizer->Add(m_when_content);
m_top_sizer->AddSpacer(PRESET_GAP);
auto introduce_text = new Label(parent, _L("We have provided 3 calibration modes. Click the button below to enter the corresponding calibration page.\
\nBefore calibration, you need to select the printer you are using, the consumables that need to be calibrated, and the process. You can directly select them in the upper left corner of the current page."));
introduce_text->SetFont(Label::Body_14);
introduce_text->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
introduce_text->SetMinSize({ CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1 });
m_top_sizer->Add(introduce_text);
m_top_sizer->AddSpacer(PRESET_GAP);
//w29
m_action_panel = new CaliPageActionPanel(parent, CalibMode::Calib_PA_Line, CaliPageType::CALI_PAGE_START);
m_top_sizer->Add(m_action_panel, 0, wxEXPAND, 0);
CaliPresetTipsstartPanel* m_tips_panel = new CaliPresetTipsstartPanel(parent);
m_top_sizer->Add(m_tips_panel, 0);
m_top_sizer->AddSpacer(PRESET_GAP);
// #ifdef __linux__
// wxGetApp().CallAfter([this]() {
// m_when_content->SetMinSize(m_when_content->GetSize() + wxSize{ 0, wxWindow::GetCharHeight() });
// m_about_content->SetMinSize(m_about_content->GetSize() + wxSize{ 0, wxWindow::GetCharHeight() });
// Layout();
// Fit();
// });
// #endif
}
//w29
void CalibrationPAStartPage::msw_rescale()
{
CalibrationWizardPage::msw_rescale();
//w29
if (wxGetApp().app_config->get_language_code() == "zh-cn") {
create_bitmap(this, "cali_page_before_pa_CN", "cali_page_after_pa_CN");
} else {
create_bitmap(this, "cali_page_before_pa", "cali_page_after_pa");
}
}
CalibrationFlowRateStartPage::CalibrationFlowRateStartPage(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: CalibrationStartPage(parent, id, pos, size, style)
{
m_cali_mode = CalibMode::Calib_Flow_Rate;
create_page(this);
this->SetSizer(m_top_sizer);
Layout();
m_top_sizer->Fit(this);
}
void CalibrationFlowRateStartPage::create_page(wxWindow* parent)
{
//w29
m_page_caption = new CaliPageCaption(parent, CalibMode::Calib_Flow_Rate);
m_page_caption->show_prev_btn(false);
m_top_sizer->Add(m_page_caption, 0, wxALIGN_CENTER, 0);
Label* title_1{ nullptr };
Label* content_1{ nullptr };
Label* title_2{ nullptr };
Label* content_2{ nullptr };
auto introduce_text = new Label(parent, _L("When using official filaments, the default values of the software are obtained through our testing, and usually perform well in the vast majority of printing situations."));
introduce_text->SetFont(Label::Body_14);
introduce_text->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
introduce_text->SetMinSize({ CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1 });
m_top_sizer->Add(introduce_text);
m_top_sizer->AddSpacer(PRESET_GAP);
title_1 = new Label(this, _L("When do you need Flowrate Calibration"));
title_1->SetFont(Label::Head_14);
title_1->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
title_1->SetMinSize({ CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1 });
content_1 = new Label(this, _L("If you notice the following signs and other uncertain reasons during printing, you may consider performing flowrate calibration:\
\n1. Over-Extrusion: If you see excess material on your printed object, forming blobs or zits, or the layers seem too thick, it could be a sign of over-extrusion;\
\n2. Under-Extrusion: This is the opposite of over - extrusion.Signs include missing layers, weak infill, or gaps in the print.This could mean that your printer isn't extruding enough filament;\
\n3. Poor Surface Quality : If the surface of your prints seems rough or uneven, this could be a result of an incorrect flow rate;\
\n4. Weak Structural Integrity : If your prints break easily or don't seem as sturdy as they should be, this might be due to under-extrusion or poor layer adhesion, which can be improved by flow rate calibration;\
\n5. When using third-party filaments"));;
content_1->SetFont(Label::Body_14);
content_1->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
content_1->SetMinSize({ CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1 });
m_top_sizer->Add(title_1);
m_top_sizer->Add(content_1);
m_top_sizer->AddSpacer(PRESET_GAP);
add_bitmap(parent, m_top_sizer, "FlowrateCompare",true,290);
title_2 = new Label(this, _L("Calibration process"));
title_2->SetFont(Label::Head_14);
title_2->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
title_2->SetMinSize({ CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1 });
content_2 = new Label(this, _L("The calibration process includes two types: coarse calibration and fine calibration.\
\nUsually, we first use coarse calibration to obtain a range, and then perform fine calibration to obtain precise values. You can also directly use the values of coarse calibration.\
\nBefore calibration, you need to select the printer you are using, the consumables that need to be calibrated, and the process. You can directly select them in the upper left corner of the current page."));
content_2->SetFont(Label::Body_14);
content_2->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
content_2->SetMinSize({ CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1 });
m_top_sizer->Add(title_2);
m_top_sizer->Add(content_2);
m_top_sizer->AddSpacer(PRESET_GAP);
m_action_panel = new CaliPageActionPanel(parent, CalibMode::Calib_Flow_Rate, CaliPageType::CALI_PAGE_START);
m_top_sizer->Add(m_action_panel, 0, wxEXPAND, 0);
#ifdef __linux__
wxGetApp().CallAfter([this, title_1, title_2, content_1, content_2, introduce_text]() {
title_1->SetMinSize(title_1->GetSize() + wxSize{ 0, wxWindow::GetCharHeight() });
title_2->SetMinSize(title_2->GetSize() + wxSize{ 0, wxWindow::GetCharHeight() });
content_1->SetMinSize(content_1->GetSize() + wxSize{ 0, wxWindow::GetCharHeight() });
content_2->SetMinSize(content_2->GetSize() + wxSize{ 0, wxWindow::GetCharHeight() });
introduce_text->SetMinSize(introduce_text->GetSize() + wxSize{ 0, wxWindow::GetCharHeight() });
Layout();
Fit();
});
#endif
}
//w29
void CalibrationFlowRateStartPage::msw_rescale()
{
CalibrationWizardPage::msw_rescale();
if (wxGetApp().app_config->get_language_code() == "zh-cn") {
create_bitmap(this, "cali_page_flow_introduction_CN");
} else {
create_bitmap(this, "cali_page_flow_introduction");
}
}
CalibrationMaxVolumetricSpeedStartPage::CalibrationMaxVolumetricSpeedStartPage(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: CalibrationStartPage(parent, id, pos, size, style)
{
m_cali_mode = CalibMode::Calib_Vol_speed_Tower;
create_page(this);
this->SetSizer(m_top_sizer);
Layout();
m_top_sizer->Fit(this);
}
void CalibrationMaxVolumetricSpeedStartPage::create_page(wxWindow* parent)
{
//w29
m_page_caption = new CaliPageCaption(parent, m_cali_mode);
m_page_caption->show_prev_btn(false);
m_top_sizer->Add(m_page_caption, 0, wxALIGN_CENTER, 0);
create_when(parent,
_L("What is Max Volumetric Speed Calibration ?"),
_L("Different filaments have different maximum volume speed.\
\nNozzle material, caliber, printing temperature, etc., will affect the maximum volume speed.\
\nWhen the maximum volume velocity is set too high and does not match the filament properties, there may be missing threads during the printing process, resulting in a deterioration of the surface texture of the model.\
\nThis is a test designed to calibrate the maximum volumetric speed of the specific filament. The generic or 3rd party filament types may not have the correct volumetric flow rate set in the filament. This test will help you to find the maximum volumetric speed of the filament."));
m_top_sizer->Add(m_when_title);
m_top_sizer->Add(m_when_content);
m_top_sizer->AddSpacer(PRESET_GAP);
create_when(parent,
_L("When to Calibrate Max Volumetric Speed ?"),
_L("We have configured corresponding values for our official consumables in the software. When you have the following situations, you need to calibrate the Max Volumetric Speed:\
\n1.Use different brands of filaments;\
\n2.Replaced nozzles with different materials and diameters;\
\n3.You have changed the printing temperature;\
\n4.During the printing process, it was found that there were missing threads, insufficient extrusion, or broken filling.\
\nBefore calibration, you need to select the printer you are using, the consumables that need to be calibrated, and the process. You can directly select them in the upper left corner of the current page."));
m_top_sizer->Add(m_when_title);
m_top_sizer->Add(m_when_content);
m_top_sizer->AddSpacer(PRESET_GAP);
add_bitmap(parent, m_top_sizer, "maxvolumetricspeedmodel", true, 300);
m_top_sizer->AddSpacer(PRESET_GAP);
/*
auto extra_text = new Label(parent, _L("Different filaments have different maximum volume speed.\
\nNozzle material, caliber, printing temperature, etc., will affect the maximum volume speed.\
\nWhen the maximum volume velocity is set too high and does not match the filament properties, there may be missing threads during the printing process, resulting in a deterioration of the surface texture of the model.\
\nThis is a test designed to calibrate the maximum volumetric speed of the specific filament. The generic or 3rd party filament types may not have the correct volumetric flow rate set in the filament. This test will help you to find the maximum volumetric speed of the filament."));
extra_text->SetFont(Label::Body_14);
extra_text->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
extra_text->SetMinSize({ CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1 });
m_top_sizer->Add(extra_text);
m_top_sizer->AddSpacer(PRESET_GAP);*/
/*
auto end_text = new Label(parent, _L("During the test, the printing speed will increase layer by layer. When there is a hole or missing wire on the surface of the model, it indicates that the layer has reached the maximum volume speed, and the maximum volume speed is calculated according to the ratio of height."));
end_text->SetFont(Label::Body_14);
end_text->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
end_text->SetMinSize({ CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1 });
m_top_sizer->Add(end_text);
m_top_sizer->AddSpacer(PRESET_GAP);*/
/*
create_when(parent, _L("When you need Max Volumetric Speed Calibration"), _L("Over-extrusion or under extrusion"));
m_top_sizer->Add(m_when_title);
m_top_sizer->Add(m_when_content);
m_top_sizer->AddSpacer(PRESET_GAP);
auto recommend_title = new Label(parent, _L("Max Volumetric Speed calibration is recommended when you print with:"));
recommend_title->SetFont(Label::Head_14);
recommend_title->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
m_top_sizer->Add(recommend_title);
auto recommend_text1 = new Label(parent, _L("material with significant thermal shrinkage/expansion, such as..."));
recommend_text1->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
recommend_text1->SetFont(Label::Body_14);
m_top_sizer->Add(recommend_text1);
auto recommend_text2 = new Label(parent, _L("materials with inaccurate filament diameter"));
recommend_text2->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
recommend_text2->SetFont(Label::Body_14);
m_top_sizer->Add(recommend_text2);
m_top_sizer->AddSpacer(PRESET_GAP);
if (wxGetApp().app_config->get_language_code() == "zh-cn") {
create_bitmap(parent, "cali_page_before_pa_CN", "cali_page_after_pa_CN");
} else {
create_bitmap(parent, "cali_page_before_pa", "cali_page_after_pa");
}
m_top_sizer->Add(m_images_sizer, 0, wxALL, 0);
m_top_sizer->AddSpacer(PRESET_GAP);*/
m_action_panel = new CaliPageActionPanel(parent, m_cali_mode, CaliPageType::CALI_PAGE_START);
m_top_sizer->Add(m_action_panel, 0, wxEXPAND, 0);
CaliPresetTipsstartPanel* m_tips_panel = new CaliPresetTipsstartPanel(parent);
m_top_sizer->Add(m_tips_panel, 0);
m_top_sizer->AddSpacer(PRESET_GAP);
}
void CalibrationMaxVolumetricSpeedStartPage::msw_rescale()
{
CalibrationWizardPage::msw_rescale();
if (wxGetApp().app_config->get_language_code() == "zh-cn") {
create_bitmap(this, "cali_page_before_pa_CN", "cali_page_after_pa_CN");
} else {
create_bitmap(this, "cali_page_before_pa", "cali_page_after_pa");
}
}
}}

View File

@@ -0,0 +1,98 @@
#ifndef slic3r_GUI_CalibrationWizardStartPage_hpp_
#define slic3r_GUI_CalibrationWizardStartPage_hpp_
#include "CalibrationWizardPage.hpp"
namespace Slic3r { namespace GUI {
class CalibrationStartPage : public CalibrationWizardPage
{
public:
CalibrationStartPage(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
protected:
CalibMode m_cali_mode;
wxBoxSizer* m_top_sizer{ nullptr };
wxBoxSizer* m_images_sizer{ nullptr };
Label* m_when_title{ nullptr };
Label* m_when_content{ nullptr };
Label* m_about_title{ nullptr };
Label* m_about_content{ nullptr };
wxStaticBitmap* m_before_bmp{ nullptr };
wxStaticBitmap* m_after_bmp{ nullptr };
wxStaticBitmap* m_bmp_intro{ nullptr };
//w29
void create_when(wxWindow* parent, wxString title, wxString content);
void create_about(wxWindow* parent, wxString title, wxString content);
void create_bitmap(wxWindow* parent, const wxBitmap& before_img, const wxBitmap& after_img);
void create_bitmap(wxWindow* parent, std::string before_img, std::string after_img);
void create_bitmap(wxWindow* parent, std::string img);
//w29
void add_bitmap(wxWindow* parent, wxBoxSizer* m_top_sizer, std::string img,bool can_modify = false , int modify_size =350);
};
class CalibrationPAStartPage : public CalibrationStartPage
{
public:
CalibrationPAStartPage(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void create_page(wxWindow* parent);
//w29
void msw_rescale() override;
};
class CalibrationFlowRateStartPage : public CalibrationStartPage
{
public:
CalibrationFlowRateStartPage(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void create_page(wxWindow* parent);
//w29
void msw_rescale() override;
};
class CalibrationMaxVolumetricSpeedStartPage : public CalibrationStartPage
{
public:
CalibrationMaxVolumetricSpeedStartPage(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void create_page(wxWindow* parent);
void msw_rescale() override;
};
class CaliPresetTipsstartPanel : public CalibrationStartPage
{
public:
CaliPresetTipsstartPanel(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
void create_panel(wxWindow* parent);
};
}} // namespace Slic3r::GUI
#endif

622
src/slic3r/GUI/Camera.cpp Normal file
View File

@@ -0,0 +1,622 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/AppConfig.hpp"
#include "Camera.hpp"
#include "GUI_App.hpp"
#if ENABLE_CAMERA_STATISTICS
#include "Mouse3DController.hpp"
#include "Plater.hpp"
#endif // ENABLE_CAMERA_STATISTICS
#include <GL/glew.h>
namespace Slic3r {
namespace GUI {
const double Camera::DefaultDistance = 1000.0;
const double Camera::DefaultZoomToBoxMarginFactor = 1.025;
const double Camera::DefaultZoomToVolumesMarginFactor = 1.025;
double Camera::FrustrumMinZRange = 50.0;
double Camera::FrustrumMinNearZ = 100.0;
double Camera::FrustrumZMargin = 10.0;
double Camera::MaxFovDeg = 60.0;
double Camera::ZoomUnit = 0.1;
std::string Camera::get_type_as_string() const
{
switch (m_type)
{
case EType::Unknown: return "unknown";
case EType::Perspective: return "perspective";
default:
case EType::Ortho: return "orthographic";
};
}
void Camera::set_type(EType type)
{
if (m_type != type && (type == EType::Ortho || type == EType::Perspective)) {
m_type = type;
if (m_update_config_on_type_change_enabled) {
wxGetApp().app_config->set_bool("use_perspective_camera", m_type == EType::Perspective);
wxGetApp().app_config->save();
}
}
}
void Camera::select_next_type()
{
unsigned char next = (unsigned char)m_type + 1;
if (next == (unsigned char)EType::Num_types)
next = 1;
set_type((EType)next);
}
void Camera::translate(const Vec3d& displacement) {
if (!displacement.isApprox(Vec3d::Zero())) {
m_view_matrix.translate(-displacement);
update_target();
}
}
void Camera::set_target(const Vec3d& target)
{
//QDS do not check validation
//const Vec3d new_target = validate_target(target);
update_target();
const Vec3d new_target = target;
const Vec3d new_displacement = new_target - m_target;
if (!new_displacement.isApprox(Vec3d::Zero())) {
m_target = new_target;
m_view_matrix.translate(-new_displacement);
}
}
void Camera::set_zoom(double zoom)
{
// Don't allow to zoom too far outside the scene.
const double zoom_min = min_zoom();
if (zoom_min > 0.0)
zoom = std::max(zoom, zoom_min);
// Don't allow to zoom too close to the scene.
m_zoom = std::min(zoom, max_zoom());
}
void Camera::select_view(const std::string& direction)
{
if (direction == "iso")
set_iso_orientation();
else if (direction == "left")
look_at(m_target - m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ());
else if (direction == "right")
look_at(m_target + m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ());
else if (direction == "top")
look_at(m_target + m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY());
else if (direction == "bottom")
look_at(m_target - m_distance * Vec3d::UnitZ(), m_target, -Vec3d::UnitY());
else if (direction == "front")
look_at(m_target - m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ());
else if (direction == "rear")
look_at(m_target + m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ());
else if (direction == "topfront")
look_at(m_target - 0.707 * m_distance * Vec3d::UnitY() + 0.707 * m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY() + Vec3d::UnitZ());
else if (direction == "plate") {
look_at(m_target - 0.707 * m_distance * Vec3d::UnitY() + 0.707 * m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY() + Vec3d::UnitZ());
}
}
double Camera::get_fov() const
{
switch (m_type)
{
case EType::Perspective:
return 2.0 * Geometry::rad2deg(std::atan(1.0 / m_projection_matrix.matrix()(1, 1)));
default:
case EType::Ortho:
return 0.0;
};
}
void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h)
{
glsafe(::glViewport(0, 0, w, h));
glsafe(::glGetIntegerv(GL_VIEWPORT, m_viewport.data()));
}
void Camera::apply_view_matrix()
{
glsafe(::glMatrixMode(GL_MODELVIEW));
glsafe(::glLoadIdentity());
glsafe(::glMultMatrixd(m_view_matrix.data()));
}
void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z)
{
double w = 0.0;
double h = 0.0;
const double old_distance = m_distance;
m_frustrum_zs = calc_tight_frustrum_zs_around(box);
if (m_distance != old_distance)
// the camera has been moved re-apply view matrix
apply_view_matrix();
if (near_z > 0.0)
m_frustrum_zs.first = std::max(std::min(m_frustrum_zs.first, near_z), FrustrumMinNearZ);
if (far_z > 0.0)
m_frustrum_zs.second = std::max(m_frustrum_zs.second, far_z);
w = 0.5 * (double)m_viewport[2];
h = 0.5 * (double)m_viewport[3];
const double inv_zoom = get_inv_zoom();
w *= inv_zoom;
h *= inv_zoom;
switch (m_type)
{
default:
case EType::Ortho:
{
m_gui_scale = 1.0;
break;
}
case EType::Perspective:
{
// scale near plane to keep w and h constant on the plane at z = m_distance
const double scale = m_frustrum_zs.first / m_distance;
w *= scale;
h *= scale;
m_gui_scale = scale;
break;
}
}
glsafe(::glMatrixMode(GL_PROJECTION));
glsafe(::glLoadIdentity());
switch (m_type)
{
default:
case EType::Ortho:
{
glsafe(::glOrtho(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second));
break;
}
case EType::Perspective:
{
glsafe(::glFrustum(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second));
break;
}
}
glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, m_projection_matrix.data()));
glsafe(::glMatrixMode(GL_MODELVIEW));
}
void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor)
{
// Calculate the zoom factor needed to adjust the view around the given box.
const double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor);
if (zoom > 0.0) {
m_zoom = zoom;
// center view around box center
set_target(box.center());
}
}
void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor)
{
Vec3d center;
const double zoom = calc_zoom_to_volumes_factor(volumes, center, margin_factor);
if (zoom > 0.0) {
m_zoom = zoom;
// center view around the calculated center
set_target(center);
}
}
#if ENABLE_CAMERA_STATISTICS
void Camera::debug_render() const
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
std::string type = get_type_as_string();
if (wxGetApp().plater()->get_mouse3d_controller().connected()
#ifdef SUPPORT_FREE_CAMERA
|| (wxGetApp().app_config->get("use_free_camera") == "1")
#endif
)
type += "/free";
else
type += "/constrained";
Vec3f position = get_position().cast<float>();
Vec3f target = m_target.cast<float>();
float distance = (float)get_distance();
float zenit = (float)m_zenit;
Vec3f forward = get_dir_forward().cast<float>();
Vec3f right = get_dir_right().cast<float>();
Vec3f up = get_dir_up().cast<float>();
float nearZ = (float)m_frustrum_zs.first;
float farZ = (float)m_frustrum_zs.second;
float deltaZ = farZ - nearZ;
float zoom = (float)m_zoom;
float fov = (float)get_fov();
std::array<int, 4>viewport = get_viewport();
float gui_scale = (float)get_gui_scale();
ImGui::InputText("Type", type.data(), type.length(), ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat3("Position", position.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Target", target.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Distance", &distance, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("Zenit", &zenit, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat3("Forward", forward.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Right", right.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Up", up.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("Near Z", &nearZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Far Z", &farZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Delta Z", &deltaZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("Zoom", &zoom, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputInt4("Viewport", viewport.data(), ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
imgui.end();
}
#endif // ENABLE_CAMERA_STATISTICS
void Camera::rotate_on_sphere_with_target(double delta_azimut_rad, double delta_zenit_rad, bool apply_limits, Vec3d target)
{
m_zenit += Geometry::rad2deg(delta_zenit_rad);
if (apply_limits) {
if (m_zenit > 90.0f) {
delta_zenit_rad -= Geometry::deg2rad(m_zenit - 90.0f);
m_zenit = 90.0f;
}
else if (m_zenit < -90.0f) {
delta_zenit_rad -= Geometry::deg2rad(m_zenit + 90.0f);
m_zenit = -90.0f;
}
}
Vec3d translation = m_view_matrix.translation() + m_view_rotation * target;
auto rot_z = Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ());
m_view_rotation *= rot_z * Eigen::AngleAxisd(delta_zenit_rad, rot_z.inverse() * get_dir_right());
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
}
void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, bool apply_limits)
{
m_zenit += Geometry::rad2deg(delta_zenit_rad);
if (apply_limits) {
if (m_zenit > 90.0f) {
delta_zenit_rad -= Geometry::deg2rad(m_zenit - 90.0f);
m_zenit = 90.0f;
}
else if (m_zenit < -90.0f) {
delta_zenit_rad -= Geometry::deg2rad(m_zenit + 90.0f);
m_zenit = -90.0f;
}
}
const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target;
const auto rot_z = Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ());
m_view_rotation *= rot_z * Eigen::AngleAxisd(delta_zenit_rad, rot_z.inverse() * get_dir_right());
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (- m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
}
//QDS rotate with target
void Camera::rotate_local_with_target(const Vec3d& rotation_rad, Vec3d target)
{
double angle = rotation_rad.norm();
if (std::abs(angle) > EPSILON) {
Vec3d translation = m_view_matrix.translation() + m_view_rotation * target;
Vec3d axis = m_view_rotation.conjugate() * rotation_rad.normalized();
m_view_rotation *= Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis));
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
update_zenit();
}
}
// Virtual trackball, rotate around an axis, where the eucledian norm of the axis gives the rotation angle in radians.
void Camera::rotate_local_around_target(const Vec3d& rotation_rad)
{
const double angle = rotation_rad.norm();
if (std::abs(angle) > EPSILON) {
const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target;
const Vec3d axis = m_view_rotation.conjugate() * rotation_rad.normalized();
m_view_rotation *= Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis));
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
update_zenit();
}
}
std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box)
{
std::pair<double, double> ret;
auto& [near_z, far_z] = ret;
// box in eye space
const BoundingBoxf3 eye_box = box.transformed(m_view_matrix);
near_z = -eye_box.max(2);
far_z = -eye_box.min(2);
// apply margin
near_z -= FrustrumZMargin;
far_z += FrustrumZMargin;
// ensure min size
if (far_z - near_z < FrustrumMinZRange) {
const double mid_z = 0.5 * (near_z + far_z);
const double half_size = 0.5 * FrustrumMinZRange;
near_z = mid_z - half_size;
far_z = mid_z + half_size;
}
if (near_z < FrustrumMinNearZ) {
const double delta = FrustrumMinNearZ - near_z;
set_distance(m_distance + delta);
near_z += delta;
far_z += delta;
}
// The following is commented out because it causes flickering of the 3D scene GUI
// when the bounding box of the scene gets large enough
// We need to introduce some smarter code to move the camera back and forth in such case
// else if (near_z > 2.0 * FrustrumMinNearZ && m_distance > DefaultDistance) {
// float delta = m_distance - DefaultDistance;
// set_distance(DefaultDistance);
// near_z -= delta;
// far_z -= delta;
// }
return ret;
}
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor) const
{
const double max_bb_size = box.max_size();
if (max_bb_size == 0.0)
return -1.0;
// project the box vertices on a plane perpendicular to the camera forward axis
// then calculates the vertices coordinate on this plane along the camera xy axes
const Vec3d right = get_dir_right();
const Vec3d up = get_dir_up();
const Vec3d forward = get_dir_forward();
const Vec3d bb_center = box.center();
// box vertices in world space
const std::vector<Vec3d> vertices = {
box.min,
{ box.max(0), box.min(1), box.min(2) },
{ box.max(0), box.max(1), box.min(2) },
{ box.min(0), box.max(1), box.min(2) },
{ box.min(0), box.min(1), box.max(2) },
{ box.max(0), box.min(1), box.max(2) },
box.max,
{ box.min(0), box.max(1), box.max(2) }
};
double min_x = DBL_MAX;
double min_y = DBL_MAX;
double max_x = -DBL_MAX;
double max_y = -DBL_MAX;
for (const Vec3d& v : vertices) {
// project vertex on the plane perpendicular to camera forward axis
const Vec3d pos = v - bb_center;
const Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
// calculates vertex coordinate along camera xy axes
const double x_on_plane = proj_on_plane.dot(right);
const double y_on_plane = proj_on_plane.dot(up);
min_x = std::min(min_x, x_on_plane);
min_y = std::min(min_y, y_on_plane);
max_x = std::max(max_x, x_on_plane);
max_y = std::max(max_y, y_on_plane);
}
double dx = max_x - min_x;
double dy = max_y - min_y;
if (dx <= 0.0 || dy <= 0.0)
return -1.0f;
dx *= margin_factor;
dy *= margin_factor;
return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy);
}
double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor) const
{
if (volumes.empty())
return -1.0;
// project the volumes vertices on a plane perpendicular to the camera forward axis
// then calculates the vertices coordinate on this plane along the camera xy axes
const Vec3d right = get_dir_right();
const Vec3d up = get_dir_up();
const Vec3d forward = get_dir_forward();
BoundingBoxf3 box;
for (const GLVolume* volume : volumes) {
box.merge(volume->transformed_bounding_box());
}
center = box.center();
double min_x = DBL_MAX;
double min_y = DBL_MAX;
double max_x = -DBL_MAX;
double max_y = -DBL_MAX;
for (const GLVolume* volume : volumes) {
const Transform3d& transform = volume->world_matrix();
const TriangleMesh* hull = volume->convex_hull();
if (hull == nullptr)
continue;
for (const Vec3f& vertex : hull->its.vertices) {
const Vec3d v = transform * vertex.cast<double>();
// project vertex on the plane perpendicular to camera forward axis
const Vec3d pos = v - center;
const Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
// calculates vertex coordinate along camera xy axes
const double x_on_plane = proj_on_plane.dot(right);
const double y_on_plane = proj_on_plane.dot(up);
min_x = std::min(min_x, x_on_plane);
min_y = std::min(min_y, y_on_plane);
max_x = std::max(max_x, x_on_plane);
max_y = std::max(max_y, y_on_plane);
}
}
center += 0.5 * (max_x + min_x) * right + 0.5 * (max_y + min_y) * up;
const double dx = margin_factor * (max_x - min_x);
const double dy = margin_factor * (max_y - min_y);
if (dx <= 0.0 || dy <= 0.0)
return -1.0f;
return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy);
}
void Camera::set_distance(double distance)
{
if (m_distance != distance) {
m_view_matrix.translate((distance - m_distance) * get_dir_forward());
m_distance = distance;
update_target();
}
}
void Camera::load_camera_view(Camera& cam)
{
m_target = cam.get_target();
m_zoom = cam.get_zoom();
m_scene_box = cam.get_scene_box();
m_viewport = cam.get_viewport();
m_view_matrix = cam.get_view_matrix();
m_projection_matrix = cam.get_projection_matrix();
m_view_rotation = cam.get_view_rotation();
m_frustrum_zs = cam.get_z_range();
m_zenit = cam.get_zenit();
}
void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up)
{
const Vec3d unit_z = (position - target).normalized();
const Vec3d unit_x = up.cross(unit_z).normalized();
const Vec3d unit_y = unit_z.cross(unit_x).normalized();
m_target = target;
m_distance = (position - target).norm();
const Vec3d new_position = m_target + m_distance * unit_z;
m_view_matrix(0, 0) = unit_x(0);
m_view_matrix(0, 1) = unit_x(1);
m_view_matrix(0, 2) = unit_x(2);
m_view_matrix(0, 3) = -unit_x.dot(new_position);
m_view_matrix(1, 0) = unit_y(0);
m_view_matrix(1, 1) = unit_y(1);
m_view_matrix(1, 2) = unit_y(2);
m_view_matrix(1, 3) = -unit_y.dot(new_position);
m_view_matrix(2, 0) = unit_z(0);
m_view_matrix(2, 1) = unit_z(1);
m_view_matrix(2, 2) = unit_z(2);
m_view_matrix(2, 3) = -unit_z.dot(new_position);
m_view_matrix(3, 0) = 0.0;
m_view_matrix(3, 1) = 0.0;
m_view_matrix(3, 2) = 0.0;
m_view_matrix(3, 3) = 1.0;
// Initialize the rotation quaternion from the rotation submatrix of of m_view_matrix.
m_view_rotation = Eigen::Quaterniond(m_view_matrix.matrix().template block<3, 3>(0, 0));
m_view_rotation.normalize();
update_zenit();
}
void Camera::set_default_orientation()
{
// QDS modify default orientation
look_at(m_target - 0.707 * m_distance * Vec3d::UnitY() + 0.707 * m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY() + Vec3d::UnitZ());
/*m_zenit = 45.0f;
const double theta_rad = Geometry::deg2rad(-(double)m_zenit);
const double phi_rad = Geometry::deg2rad(45.0);
const double sin_theta = ::sin(theta_rad);
const Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad));
m_view_rotation = Eigen::AngleAxisd(theta_rad, Vec3d::UnitX()) * Eigen::AngleAxisd(phi_rad, Vec3d::UnitZ());
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-camera_pos), m_view_rotation, Vec3d::Ones());*/
}
void Camera::set_iso_orientation()
{
m_zenit = 45.0f;
const double theta_rad = Geometry::deg2rad(-(double)m_zenit);
const double phi_rad = Geometry::deg2rad(45.0);
const double sin_theta = ::sin(theta_rad);
const Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad));
m_view_rotation = Eigen::AngleAxisd(theta_rad, Vec3d::UnitX()) * Eigen::AngleAxisd(phi_rad, Vec3d::UnitZ());
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-camera_pos), m_view_rotation, Vec3d::Ones());
}
Vec3d Camera::validate_target(const Vec3d& target) const
{
BoundingBoxf3 test_box = m_scene_box;
test_box.translate(-m_scene_box.center());
// We may let this factor be customizable
//QDS enlarge scene box factor
static const double ScaleFactor = 3.0;
test_box.scale(ScaleFactor);
test_box.translate(m_scene_box.center());
return { std::clamp(target(0), test_box.min(0), test_box.max(0)),
std::clamp(target(1), test_box.min(1), test_box.max(1)),
std::clamp(target(2), test_box.min(2), test_box.max(2)) };
}
void Camera::update_zenit()
{
m_zenit = Geometry::rad2deg(0.5 * M_PI - std::acos(std::clamp(-get_dir_forward().dot(Vec3d::UnitZ()), -1.0, 1.0))); }
void Camera::update_target() {
Vec3d temptarget = get_position() + m_distance * get_dir_forward();
if (!(temptarget-m_target).isApprox(Vec3d::Zero())){
m_target = temptarget;
}
}
} // GUI
} // Slic3r

176
src/slic3r/GUI/Camera.hpp Normal file
View File

@@ -0,0 +1,176 @@
#ifndef slic3r_Camera_hpp_
#define slic3r_Camera_hpp_
#include "libslic3r/BoundingBox.hpp"
#include "3DScene.hpp"
#include <array>
namespace Slic3r {
namespace GUI {
#define REQUIRES_ZOOM_TO_CUR_PLATE -1
#define REQUIRES_ZOOM_TO_ALL_PLATE -2
#define REQUIRES_ZOOM_TO_PLATE_IDLE -100
struct Camera
{
static const double DefaultDistance;
static const double DefaultZoomToBoxMarginFactor;
static const double DefaultZoomToVolumesMarginFactor;
static double FrustrumMinZRange;
static double FrustrumMinNearZ;
static double FrustrumZMargin;
static double MaxFovDeg;
static double ZoomUnit;
enum class EType : unsigned char
{
Unknown,
Ortho,
Perspective,
Num_types
};
bool requires_zoom_to_bed{ false };
//QDS
bool requires_zoom_to_volumes{ false };
int requires_zoom_to_plate{ REQUIRES_ZOOM_TO_PLATE_IDLE };
private:
EType m_type{ EType::Perspective };
bool m_update_config_on_type_change_enabled{ false };
Vec3d m_target{ Vec3d::Zero() };
float m_zenit{ 45.0f };
double m_zoom{ 1.0 };
// Distance between camera position and camera target measured along the camera Z axis
double m_distance{ DefaultDistance };
double m_gui_scale{ 1.0 };
std::array<int, 4> m_viewport;
Transform3d m_view_matrix{ Transform3d::Identity() };
// We are calculating the rotation part of the m_view_matrix from m_view_rotation.
Eigen::Quaterniond m_view_rotation{ 1.0, 0.0, 0.0, 0.0 };
Transform3d m_projection_matrix{ Transform3d::Identity() };
std::pair<double, double> m_frustrum_zs;
BoundingBoxf3 m_scene_box;
public:
Camera() { set_default_orientation(); }
EType get_type() const { return m_type; }
std::string get_type_as_string() const;
void set_type(EType type);
// valid values for type: "false" -> ortho, "true" -> perspective
void set_type(const std::string& type) { set_type((type == "true") ? EType::Perspective : EType::Ortho); }
void select_next_type();
void enable_update_config_on_type_change(bool enable) { m_update_config_on_type_change_enabled = enable; }
void translate(const Vec3d& displacement);
const Vec3d& get_target() {
update_target();
return m_target; }
void set_target(const Vec3d& target);
double get_distance() { return (get_position() - get_target()).norm(); }
double get_gui_scale() const { return m_gui_scale; }
float get_zenit() const { return m_zenit; }
double get_zoom() const { return m_zoom; }
double get_inv_zoom() const { assert(m_zoom != 0.0); return 1.0 / m_zoom; }
void update_zoom(double delta_zoom) { set_zoom(m_zoom / (1.0 - std::max(std::min(delta_zoom, 4.0), -4.0) * ZoomUnit)); }
void set_zoom(double zoom);
const BoundingBoxf3& get_scene_box() const { return m_scene_box; }
void set_scene_box(const BoundingBoxf3& box) { m_scene_box = box; }
void select_view(const std::string& direction);
const std::array<int, 4>& get_viewport() const { return m_viewport; }
const Transform3d& get_view_matrix() const { return m_view_matrix; }
const Transform3d& get_projection_matrix() const { return m_projection_matrix; }
//QDS
const Eigen::Quaterniond& get_view_rotation() const {return m_view_rotation; }
Vec3d get_dir_right() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(0); }
Vec3d get_dir_up() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(1); }
Vec3d get_dir_forward() const { return -m_view_matrix.matrix().block(0, 0, 3, 3).row(2); }
Vec3d get_position() const { return m_view_matrix.matrix().inverse().block(0, 3, 3, 1); }
double get_near_z() const { return m_frustrum_zs.first; }
double get_far_z() const { return m_frustrum_zs.second; }
const std::pair<double, double>& get_z_range() const { return m_frustrum_zs; }
double get_fov() const;
void apply_viewport(int x, int y, unsigned int w, unsigned int h);
void apply_view_matrix();
// Calculates and applies the projection matrix tighting the frustrum z range around the given box.
// If larger z span is needed, pass the desired values of near and far z (negative values are ignored)
void apply_projection(const BoundingBoxf3& box, double near_z = -1.0, double far_z = -1.0);
void zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor);
void zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor = DefaultZoomToVolumesMarginFactor);
#if ENABLE_CAMERA_STATISTICS
void debug_render() const;
#endif // ENABLE_CAMERA_STATISTICS
// translate the camera in world space
void translate_world(const Vec3d& displacement) { set_target(m_target + displacement); }
// QDS rotate the camera on a sphere having center == target
void rotate_on_sphere_with_target(double delta_azimut_rad, double delta_zenit_rad, bool apply_limits, Vec3d target);
void rotate_local_with_target(const Vec3d& rotation_rad, Vec3d target);
// rotate the camera on a sphere having center == m_target and radius == m_distance
// using the given variations of spherical coordinates
// if apply_limits == true the camera stops rotating when its forward vector is parallel to the world Z axis
void rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, bool apply_limits);
// rotate the camera around three axes parallel to the camera local axes and passing through m_target
void rotate_local_around_target(const Vec3d& rotation_rad);
// returns true if the camera z axis (forward) is pointing in the negative direction of the world z axis
bool is_looking_downward() const { return get_dir_forward().dot(Vec3d::UnitZ()) < 0.0; }
bool is_looking_front() const { return abs(get_dir_up().dot(Vec3d::UnitZ())-1) < 0.001; }
// forces camera right vector to be parallel to XY plane
void recover_from_free_camera() {
if (std::abs(get_dir_right()(2)) > EPSILON)
look_at(get_position(), m_target, Vec3d::UnitZ());
}
//QDS store and load camera view
void load_camera_view(Camera& cam);
void look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up);
double max_zoom() const { return 250.0; }
double min_zoom() const { return 0.2 * calc_zoom_to_bounding_box_factor(m_scene_box); }
private:
// returns tight values for nearZ and farZ plane around the given bounding box
// the camera MUST be outside of the bounding box in eye coordinate of the given box
std::pair<double, double> calc_tight_frustrum_zs_around(const BoundingBoxf3& box);
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor) const;
double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const;
void set_distance(double distance);
void set_default_orientation();
void set_iso_orientation();
Vec3d validate_target(const Vec3d& target) const;
void update_zenit();
void update_target();
};
} // GUI
} // Slic3r
#endif // slic3r_Camera_hpp_

View File

@@ -0,0 +1,548 @@
#include "CameraPopup.hpp"
#include "I18N.hpp"
#include "Widgets/Label.hpp"
#include "libslic3r/Utils.hpp"
#include "BitmapCache.hpp"
#include <wx/progdlg.h>
#include <wx/clipbrd.h>
#include <wx/dcgraph.h>
#include "GUI_App.hpp"
#include <slic3r/GUI/StatusPanel.hpp>
namespace Slic3r {
namespace GUI {
wxIMPLEMENT_CLASS(CameraPopup, PopupWindow);
wxBEGIN_EVENT_TABLE(CameraPopup, PopupWindow)
EVT_MOUSE_EVENTS(CameraPopup::OnMouse )
EVT_SIZE(CameraPopup::OnSize)
EVT_SET_FOCUS(CameraPopup::OnSetFocus )
EVT_KILL_FOCUS(CameraPopup::OnKillFocus )
wxEND_EVENT_TABLE()
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
#define CAMERAPOPUP_CLICK_INTERVAL 20
const wxColour TEXT_COL = wxColour(43, 52, 54);
CameraPopup::CameraPopup(wxWindow *parent)
: PopupWindow(parent, wxBORDER_NONE | wxPU_CONTAINS_CONTROLS)
{
#ifdef __WINDOWS__
SetDoubleBuffered(true);
#endif
m_panel = new wxScrolledWindow(this, wxID_ANY);
m_panel->SetBackgroundColour(*wxWHITE);
m_panel->SetMinSize(wxSize(FromDIP(180),-1));
m_panel->Bind(wxEVT_MOTION, &CameraPopup::OnMouse, this);
main_sizer = new wxBoxSizer(wxVERTICAL);
wxFlexGridSizer* top_sizer = new wxFlexGridSizer(0, 2, 0, FromDIP(50));
top_sizer->AddGrowableCol(0);
top_sizer->SetFlexibleDirection(wxBOTH);
top_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
//recording
m_text_recording = new wxStaticText(m_panel, wxID_ANY, _L("Auto-record Monitoring"));
m_text_recording->Wrap(-1);
m_text_recording->SetFont(Label::Head_14);
m_text_recording->SetForegroundColour(TEXT_COL);
m_switch_recording = new SwitchButton(m_panel);
//vcamera
m_text_vcamera = new wxStaticText(m_panel, wxID_ANY, _L("Go Live"));
m_text_vcamera->Wrap(-1);
m_text_vcamera->SetFont(Label::Head_14);
m_text_vcamera->SetForegroundColour(TEXT_COL);
m_switch_vcamera = new SwitchButton(m_panel);
top_sizer->Add(m_text_recording, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
top_sizer->Add(m_switch_recording, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
top_sizer->Add(m_text_vcamera, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
top_sizer->Add(m_switch_vcamera, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
#if !QDT_RELEASE_TO_PUBLIC
m_text_liveview_retry = new wxStaticText(m_panel, wxID_ANY, _L("Liveview Retry"));
m_text_liveview_retry->Wrap(-1);
m_text_liveview_retry->SetFont(Label::Head_14);
m_text_liveview_retry->SetForegroundColour(TEXT_COL);
m_switch_liveview_retry = new SwitchButton(m_panel);
bool auto_retry = wxGetApp().app_config->get("liveview", "auto_retry") != "false";
m_switch_liveview_retry->SetValue(auto_retry);
top_sizer->Add(m_text_liveview_retry, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
top_sizer->Add(m_switch_liveview_retry, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
m_switch_liveview_retry->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &e) {
wxGetApp().app_config->set("liveview", "auto_retry", e.IsChecked());
e.Skip();
});
#endif
//resolution
m_text_resolution = new wxStaticText(m_panel, wxID_ANY, _L("Resolution"));
m_text_resolution->Wrap(-1);
m_text_resolution->SetFont(Label::Head_14);
m_text_resolution->SetForegroundColour(TEXT_COL);
top_sizer->Add(m_text_resolution, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
top_sizer->Add(0, 0, wxALL, 0);
for (int i = 0; i < (int)RESOLUTION_OPTIONS_NUM; ++i)
{
m_resolution_options[i] = create_item_radiobox(to_resolution_label_string(CameraResolution(i)), m_panel, wxEmptyString, FromDIP(10));
top_sizer->Add(m_resolution_options[i], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
top_sizer->Add(0, 0, wxALL, 0);
}
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
auto url = wxString::Format(L"https://wiki.qidilab.com/%s/software/qidi-studio/virtual-camera", L"en");
auto text = _L("Show \"Live Video\" guide page.");
wxBoxSizer* link_sizer = new wxBoxSizer(wxVERTICAL);
vcamera_guide_link = new Label(m_panel, text);
vcamera_guide_link->Wrap(-1);
vcamera_guide_link->SetForegroundColour(wxColour(0x1F, 0x8E, 0xEA));
auto text_size = vcamera_guide_link->GetTextExtent(text);
vcamera_guide_link->Bind(wxEVT_LEFT_DOWN, [this, url](wxMouseEvent& e) {wxLaunchDefaultBrowser(url); });
link_underline = new wxPanel(m_panel, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
link_underline->SetBackgroundColour(wxColour(0x1F, 0x8E, 0xEA));
link_underline->SetSize(wxSize(text_size.x, 1));
link_underline->SetMinSize(wxSize(text_size.x, 1));
vcamera_guide_link->Hide();
link_underline->Hide();
link_sizer->Add(vcamera_guide_link, 0, wxALL, 0);
link_sizer->Add(link_underline, 0, wxALL, 0);
main_sizer->Add(link_sizer, 0, wxALL, FromDIP(15));
m_panel->SetSizer(main_sizer);
m_panel->Layout();
main_sizer->Fit(m_panel);
SetClientSize(m_panel->GetSize());
m_switch_recording->Connect(wxEVT_LEFT_DOWN, wxCommandEventHandler(CameraPopup::on_switch_recording), NULL, this);
m_switch_vcamera->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) {
wxMouseEvent evt(EVT_VCAMERA_SWITCH);
evt.SetEventObject(this);
GetEventHandler()->ProcessEvent(evt);
});
#ifdef __APPLE__
m_panel->Bind(wxEVT_LEFT_UP, &CameraPopup::OnLeftUp, this);
#endif //APPLE
this->Bind(wxEVT_TIMER, &CameraPopup::stop_interval, this);
m_interval_timer = new wxTimer();
m_interval_timer->SetOwner(this);
wxGetApp().UpdateDarkUIWin(this);
}
void CameraPopup::sdcard_absent_hint()
{
wxCommandEvent evt(EVT_SDCARD_ABSENT_HINT);
evt.SetEventObject(this);
GetEventHandler()->ProcessEvent(evt);
}
void CameraPopup::on_switch_recording(wxCommandEvent& event)
{
if (!m_obj) return;
if (m_obj->sdcard_state != MachineObject::SdcardState::HAS_SDCARD_NORMAL) {
sdcard_absent_hint();
return;
}
bool value = m_switch_recording->GetValue();
m_switch_recording->SetValue(!value);
m_obj->command_ipcam_record(!value);
}
void CameraPopup::on_set_resolution()
{
if (!m_obj) return;
m_obj->command_ipcam_resolution_set(to_resolution_msg_string(curr_sel_resolution));
}
void CameraPopup::Popup(wxWindow *WXUNUSED(focus))
{
wxPoint curr_position = this->GetPosition();
wxSize win_size = this->GetSize();
curr_position.x -= win_size.x;
this->SetPosition(curr_position);
if (!m_is_in_interval)
PopupWindow::Popup();
}
wxWindow* CameraPopup::create_item_radiobox(wxString title, wxWindow* parent, wxString tooltip, int padding_left)
{
wxWindow *item = new wxWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(20)));
item->SetBackgroundColour(*wxWHITE);
RadioBox *radiobox = new RadioBox(item);
radiobox->SetPosition(wxPoint(padding_left, (item->GetSize().GetHeight() - radiobox->GetSize().GetHeight()) / 2));
resolution_rbtns.push_back(radiobox);
int btn_idx = resolution_rbtns.size() - 1;
radiobox->Bind(wxEVT_LEFT_DOWN, [this, btn_idx](wxMouseEvent &e) {
if (m_obj && allow_alter_resolution) {
select_curr_radiobox(btn_idx);
on_set_resolution();
}
});
wxStaticText *text = new wxStaticText(item, wxID_ANY, title, wxDefaultPosition, wxDefaultSize);
text->SetForegroundColour(*wxBLACK);
resolution_texts.push_back(text);
text->SetPosition(wxPoint(padding_left + radiobox->GetSize().GetWidth() + 10, (item->GetSize().GetHeight() - text->GetSize().GetHeight()) / 2));
text->SetFont(Label::Body_13);
text->SetForegroundColour(0x6B6B6B);
text->Bind(wxEVT_LEFT_DOWN, [this, btn_idx](wxMouseEvent &e) {
if (m_obj && allow_alter_resolution) {
select_curr_radiobox(btn_idx);
on_set_resolution();
}
});
radiobox->SetToolTip(tooltip);
text->SetToolTip(tooltip);
return item;
}
void CameraPopup::select_curr_radiobox(int btn_idx)
{
if (!m_obj) return;
int len = resolution_rbtns.size();
for (int i = 0; i < len; ++i) {
if (i == btn_idx) {
curr_sel_resolution = CameraResolution(i);
resolution_rbtns[i]->SetValue(true);
}
else {
resolution_rbtns[i]->SetValue(false);
}
}
}
void CameraPopup::sync_resolution_setting(std::string resolution)
{
if (resolution == "") {
reset_resolution_setting();
return;
}
int res = 0;
for (CameraResolution i = RESOLUTION_720P; i < RESOLUTION_OPTIONS_NUM; i = CameraResolution(i+1)){
if (resolution == to_resolution_msg_string(i)) {
res = int(i);
break;
}
}
select_curr_radiobox(res);
}
void CameraPopup::reset_resolution_setting()
{
int len = resolution_rbtns.size();
for (int i = 0; i < len; ++i) {
resolution_rbtns[i]->SetValue(false);
}
curr_sel_resolution = RESOLUTION_OPTIONS_NUM;
}
void CameraPopup::sync_vcamera_state(bool show_vcamera)
{
is_vcamera_show = show_vcamera;
if (is_vcamera_show) {
m_switch_vcamera->SetValue(true);
vcamera_guide_link->Show();
link_underline->Show();
}
else {
m_switch_vcamera->SetValue(false);
vcamera_guide_link->Hide();
link_underline->Hide();
}
rescale();
}
void CameraPopup::check_func_supported(MachineObject *obj2)
{
m_obj = obj2;
if (m_obj == nullptr)
return;
// function supported
if (m_obj->has_ipcam) {
m_text_recording->Show();
m_switch_recording->Show();
} else {
m_text_recording->Hide();
m_switch_recording->Hide();
}
if (m_obj->virtual_camera && m_obj->has_ipcam) {
m_text_vcamera->Show();
m_switch_vcamera->Show();
if (is_vcamera_show) {
vcamera_guide_link->Show();
link_underline->Show();
}
} else {
m_text_vcamera->Hide();
m_switch_vcamera->Hide();
vcamera_guide_link->Hide();
link_underline->Hide();
}
allow_alter_resolution = ( (m_obj->camera_resolution_supported.size() > 1?true:false) && m_obj->has_ipcam);
//check u2 version
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return;
MachineObject* obj = dev->get_selected_machine();
if (!obj) return;
//resolution supported
std::vector<std::string> resolution_supported = m_obj->get_resolution_supported();
auto support_count = resolution_supported.size();
for (int i = 0; i < (int)RESOLUTION_OPTIONS_NUM; ++i){
auto curr_res = to_resolution_msg_string(CameraResolution(i));
std::vector <std::string> ::iterator it = std::find(resolution_supported.begin(), resolution_supported.end(), curr_res);
if ((it == resolution_supported.end())||(support_count <= 1) || !obj->is_support_1080dpi)
m_resolution_options[i]->Hide();
else {
m_resolution_options[i]->Show();
if (m_obj->camera_resolution == curr_res) {
resolution_rbtns[i]->SetValue(true);
}
}
}
//hide resolution if there is only one choice
if (support_count <= 1 || !obj->is_support_1080dpi) {
m_text_resolution->Hide();
}
else {
m_text_resolution->Show();
}
}
void CameraPopup::update(bool vcamera_streaming)
{
if (!m_obj) return;
m_switch_recording->SetValue(m_obj->camera_recording_when_printing);
sync_resolution_setting(m_obj->camera_resolution);
sync_vcamera_state(vcamera_streaming);
rescale();
}
wxString CameraPopup::to_resolution_label_string(CameraResolution resolution) {
switch (resolution) {
case RESOLUTION_720P:
return "720p";
case RESOLUTION_1080P:
return "1080p";
default:
return "";
}
return "";
}
std::string CameraPopup::to_resolution_msg_string(CameraResolution resolution) {
switch (resolution) {
case RESOLUTION_720P:
return std::string("720p");
case RESOLUTION_1080P:
return std::string("1080p");
default:
return "";
}
return "";
}
void CameraPopup::rescale()
{
m_panel->Layout();
main_sizer->Fit(m_panel);
SetClientSize(m_panel->GetSize());
PopupWindow::Update();
}
void CameraPopup::OnLeftUp(wxMouseEvent &event)
{
auto mouse_pos = ClientToScreen(event.GetPosition());
auto wxscroll_win_pos = m_panel->ClientToScreen(wxPoint(0, 0));
if (mouse_pos.x > wxscroll_win_pos.x && mouse_pos.y > wxscroll_win_pos.y && mouse_pos.x < (wxscroll_win_pos.x + m_panel->GetSize().x) && mouse_pos.y < (wxscroll_win_pos.y + m_panel->GetSize().y)) {
//recording
auto recording_rect = m_switch_recording->ClientToScreen(wxPoint(0, 0));
if (mouse_pos.x > recording_rect.x && mouse_pos.y > recording_rect.y && mouse_pos.x < (recording_rect.x + m_switch_recording->GetSize().x) && mouse_pos.y < (recording_rect.y + m_switch_recording->GetSize().y)) {
wxMouseEvent recording_evt(wxEVT_LEFT_DOWN);
m_switch_recording->GetEventHandler()->ProcessEvent(recording_evt);
return;
}
//vcamera
auto vcamera_rect = m_switch_vcamera->ClientToScreen(wxPoint(0, 0));
if (mouse_pos.x > vcamera_rect.x && mouse_pos.y > vcamera_rect.y && mouse_pos.x < (vcamera_rect.x + m_switch_vcamera->GetSize().x) && mouse_pos.y < (vcamera_rect.y + m_switch_vcamera->GetSize().y)) {
wxMouseEvent vcamera_evt(wxEVT_LEFT_DOWN);
m_switch_vcamera->GetEventHandler()->ProcessEvent(vcamera_evt);
return;
}
//resolution
for (int i = 0; i < (int)RESOLUTION_OPTIONS_NUM; ++i){
auto resolution_rbtn = resolution_rbtns[i];
auto rbtn_rect = resolution_rbtn->ClientToScreen(wxPoint(0, 0));
if (mouse_pos.x > rbtn_rect.x && mouse_pos.y > rbtn_rect.y && mouse_pos.x < (rbtn_rect.x + resolution_rbtn->GetSize().x) && mouse_pos.y < (rbtn_rect.y + resolution_rbtn->GetSize().y)) {
wxMouseEvent resolution_evt(wxEVT_LEFT_DOWN);
resolution_rbtn->GetEventHandler()->ProcessEvent(resolution_evt);
return;
}
auto resolution_txt = resolution_texts[i];
auto txt_rect = resolution_txt->ClientToScreen(wxPoint(0, 0));
if (mouse_pos.x > txt_rect.x && mouse_pos.y > txt_rect.y && mouse_pos.x < (txt_rect.x + resolution_txt->GetSize().x) && mouse_pos.y < (txt_rect.y + resolution_txt->GetSize().y)) {
wxMouseEvent resolution_evt(wxEVT_LEFT_DOWN);
resolution_txt->GetEventHandler()->ProcessEvent(resolution_evt);
return;
}
}
//hyper link
auto h_rect = vcamera_guide_link->ClientToScreen(wxPoint(0, 0));
if (mouse_pos.x > h_rect.x && mouse_pos.y > h_rect.y && mouse_pos.x < (h_rect.x + vcamera_guide_link->GetSize().x) && mouse_pos.y < (h_rect.y + vcamera_guide_link->GetSize().y)) {
auto url = wxString::Format(L"https://wiki.qidilab.com/%s/software/qidi-studio/virtual-camera", L"en");
wxLaunchDefaultBrowser(url);
}
}
}
void CameraPopup::start_interval()
{
m_interval_timer->Start(CAMERAPOPUP_CLICK_INTERVAL);
m_is_in_interval = true;
}
void CameraPopup::stop_interval(wxTimerEvent& event)
{
m_is_in_interval = false;
m_interval_timer->Stop();
}
void CameraPopup::OnDismiss() {
PopupWindow::OnDismiss();
this->start_interval();
}
bool CameraPopup::ProcessLeftDown(wxMouseEvent &event)
{
return PopupWindow::ProcessLeftDown(event);
}
bool CameraPopup::Show(bool show)
{
return PopupWindow::Show(show);
}
void CameraPopup::OnSize(wxSizeEvent &event)
{
event.Skip();
}
void CameraPopup::OnSetFocus(wxFocusEvent &event)
{
event.Skip();
}
void CameraPopup::OnKillFocus(wxFocusEvent &event)
{
event.Skip();
}
void CameraPopup::OnMouse(wxMouseEvent &event)
{
event.Skip();
}
CameraItem::CameraItem(wxWindow *parent, std::string normal, std::string hover)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{
#ifdef __WINDOWS__
SetDoubleBuffered(true);
#endif //__WINDOWS__
m_bitmap_normal = ScalableBitmap(this, normal, 20);
m_bitmap_hover = ScalableBitmap(this, hover, 20);
SetSize(wxSize(FromDIP(20), FromDIP(20)));
SetMinSize(wxSize(FromDIP(20), FromDIP(20)));
SetMaxSize(wxSize(FromDIP(20), FromDIP(20)));
Bind(wxEVT_PAINT, &CameraItem::paintEvent, this);
Bind(wxEVT_ENTER_WINDOW, &CameraItem::on_enter_win, this);
Bind(wxEVT_LEAVE_WINDOW, &CameraItem::on_level_win, this);
}
CameraItem::~CameraItem() {}
void CameraItem::msw_rescale() {
m_bitmap_normal.msw_rescale();
m_bitmap_hover.msw_rescale();
}
void CameraItem::on_enter_win(wxMouseEvent &evt)
{
m_hover = true;
Refresh();
}
void CameraItem::on_level_win(wxMouseEvent &evt)
{
m_hover = false;
Refresh();
}
void CameraItem::paintEvent(wxPaintEvent &evt)
{
wxPaintDC dc(this);
render(dc);
// PrepareDC(buffdc);
// PrepareDC(dc);
}
void CameraItem::render(wxDC &dc)
{
#ifdef __WXMSW__
wxSize size = GetSize();
wxMemoryDC memdc;
wxBitmap bmp(size.x, size.y);
memdc.SelectObject(bmp);
memdc.Blit({0, 0}, size, &dc, {0, 0});
{
wxGCDC dc2(memdc);
doRender(dc2);
}
memdc.SelectObject(wxNullBitmap);
dc.DrawBitmap(bmp, 0, 0);
#else
doRender(dc);
#endif
}
void CameraItem::doRender(wxDC &dc)
{
if (m_hover) {
dc.DrawBitmap(m_bitmap_hover.bmp(), wxPoint((GetSize().x - m_bitmap_hover.GetBmpSize().x) / 2, (GetSize().y - m_bitmap_hover.GetBmpSize().y) / 2));
} else {
dc.DrawBitmap(m_bitmap_normal.bmp(), wxPoint((GetSize().x - m_bitmap_normal.GetBmpSize().x) / 2, (GetSize().y - m_bitmap_normal.GetBmpSize().y) / 2));
}
}
}
}

View File

@@ -0,0 +1,119 @@
#ifndef slic3r_CameraPopup_hpp_
#define slic3r_CameraPopup_hpp_
#include "slic3r/GUI/MonitorBasePanel.h"
#include "DeviceManager.hpp"
#include "GUI.hpp"
#include <wx/panel.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/sizer.h>
#include <wx/timer.h>
#include <wx/gbsizer.h>
#include <wx/webrequest.h>
#include <wx/hyperlink.h>
#include "Widgets/SwitchButton.hpp"
#include "Widgets/RadioBox.hpp"
#include "Widgets/PopupWindow.hpp"
namespace Slic3r {
namespace GUI {
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
class CameraPopup : public PopupWindow
{
public:
CameraPopup(wxWindow *parent);
virtual ~CameraPopup() {}
// PopupWindow virtual methods are all overridden to log them
virtual void Popup(wxWindow *focus = NULL) wxOVERRIDE;
virtual void OnDismiss() wxOVERRIDE;
virtual bool ProcessLeftDown(wxMouseEvent &event) wxOVERRIDE;
virtual bool Show(bool show = true) wxOVERRIDE;
void sync_vcamera_state(bool show_vcamera);
void check_func_supported(MachineObject* obj);
void update(bool vcamera_streaming);
enum CameraResolution
{
RESOLUTION_720P = 0,
RESOLUTION_1080P = 1,
RESOLUTION_OPTIONS_NUM = 2
};
void rescale();
protected:
void on_switch_recording(wxCommandEvent& event);
void on_set_resolution();
void sdcard_absent_hint();
wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left);
void select_curr_radiobox(int btn_idx);
void sync_resolution_setting(std::string resolution);
void reset_resolution_setting();
wxString to_resolution_label_string(CameraResolution resolution);
std::string to_resolution_msg_string(CameraResolution resolution);
private:
MachineObject* m_obj { nullptr };
wxTimer* m_interval_timer{nullptr};
bool m_is_in_interval{ false };
wxStaticText* m_text_recording;
SwitchButton* m_switch_recording;
wxStaticText* m_text_vcamera;
SwitchButton* m_switch_vcamera;
wxStaticText* m_text_liveview_retry;
SwitchButton* m_switch_liveview_retry;
wxStaticText* m_text_resolution;
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
wxScrolledWindow *m_panel;
wxBoxSizer* main_sizer;
std::vector<RadioBox*> resolution_rbtns;
std::vector<wxStaticText*> resolution_texts;
CameraResolution curr_sel_resolution = RESOLUTION_1080P;
Label* vcamera_guide_link { nullptr };
wxPanel* link_underline{ nullptr };
bool is_vcamera_show = false;
bool allow_alter_resolution = false;
void start_interval();
void stop_interval(wxTimerEvent& event);
void OnMouse(wxMouseEvent &event);
void OnSize(wxSizeEvent &event);
void OnSetFocus(wxFocusEvent &event);
void OnKillFocus(wxFocusEvent &event);
void OnLeftUp(wxMouseEvent& event);
private:
wxDECLARE_ABSTRACT_CLASS(CameraPopup);
wxDECLARE_EVENT_TABLE();
};
class CameraItem : public wxPanel
{
public:
CameraItem(wxWindow *parent, std::string normal, std::string hover);
~CameraItem();
MachineObject *m_obj{nullptr};
bool m_hover{false};
ScalableBitmap m_bitmap_normal;
ScalableBitmap m_bitmap_hover;
void msw_rescale();
void on_enter_win(wxMouseEvent &evt);
void on_level_win(wxMouseEvent &evt);
void paintEvent(wxPaintEvent &evt);
void render(wxDC &dc);
void doRender(wxDC &dc);
};
}
}
#endif

View File

@@ -0,0 +1,131 @@
#include "CameraUtils.hpp"
#include <igl/project.h> // projecting points
#include <igl/unproject.h>
#include "slic3r/GUI/3DScene.hpp" // GLVolume
#include "libslic3r/Geometry/ConvexHull.hpp"
using namespace Slic3r;
using namespace GUI;
Points CameraUtils::project(const Camera & camera,
const std::vector<Vec3d> &points)
{
Vec4i viewport(camera.get_viewport().data());
// Convert our std::vector to Eigen dynamic matrix.
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign>
pts(points.size(), 3);
for (size_t i = 0; i < points.size(); ++i)
pts.block<1, 3>(i, 0) = points[i];
// Get the projections.
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign> projections;
igl::project(pts, camera.get_view_matrix().matrix(),
camera.get_projection_matrix().matrix(), viewport, projections);
Points result;
result.reserve(points.size());
int window_height = viewport[3];
// convert to points --> loss precision
for (int i = 0; i < projections.rows(); ++i) {
double x = projections(i, 0);
double y = projections(i, 1);
// opposit direction o Y
result.emplace_back(x, window_height - y);
}
return result;
}
Point CameraUtils::project(const Camera &camera, const Vec3d &point)
{
// IMPROVE: do it faster when you need it (inspire in project multi point)
return project(camera, std::vector{point}).front();
}
Slic3r::Polygon CameraUtils::create_hull2d(const Camera & camera,
const GLVolume &volume)
{
std::vector<Vec3d> vertices;
const TriangleMesh *hull = volume.convex_hull();
if (hull != nullptr) {
const indexed_triangle_set &its = hull->its;
vertices.reserve(its.vertices.size());
// cast vector
for (const Vec3f &vertex : its.vertices)
vertices.emplace_back(vertex.cast<double>());
} else {
// Negative volume doesn't have convex hull so use bounding box
auto bb = volume.bounding_box();
Vec3d &min = bb.min;
Vec3d &max = bb.max;
vertices = {min,
Vec3d(min.x(), min.y(), max.z()),
Vec3d(min.x(), max.y(), min.z()),
Vec3d(min.x(), max.y(), max.z()),
Vec3d(max.x(), min.y(), min.z()),
Vec3d(max.x(), min.y(), max.z()),
Vec3d(max.x(), max.y(), min.z()),
max};
}
const Transform3d &trafoMat =
volume.get_instance_transformation().get_matrix() *
volume.get_volume_transformation().get_matrix();
for (Vec3d &vertex : vertices)
vertex = trafoMat * vertex.cast<double>();
Points vertices_2d = project(camera, vertices);
return Geometry::convex_hull(vertices_2d);
}
void CameraUtils::ray_from_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction) {
switch (camera.get_type()) {
case Camera::EType::Ortho: return ray_from_ortho_screen_pos(camera, position, point, direction);
case Camera::EType::Perspective: return ray_from_persp_screen_pos(camera, position, point, direction);
default: break;
}
}
Vec3d CameraUtils::screen_point(const Camera &camera, const Vec2d &position)
{
double height = camera.get_viewport().data()[3];
// Y coordinate has opposit direction
return Vec3d(position.x(), height - position.y(), 0.);
}
void CameraUtils::ray_from_ortho_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction)
{
assert(camera.get_type() == Camera::EType::Ortho);
Matrix4d modelview = camera.get_view_matrix().matrix();
Matrix4d projection = camera.get_projection_matrix().matrix();
Vec4i viewport(camera.get_viewport().data());
igl::unproject(screen_point(camera,position), modelview, projection, viewport, point);
direction = camera.get_dir_forward();
}
void CameraUtils::ray_from_persp_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction)
{
assert(camera.get_type() == Camera::EType::Perspective);
Matrix4d modelview = camera.get_view_matrix().matrix();
Matrix4d projection = camera.get_projection_matrix().matrix();
Vec4i viewport(camera.get_viewport().data());
igl::unproject(screen_point(camera, position), modelview, projection, viewport, point);
direction = point - camera.get_position();
}
Vec2d CameraUtils::get_z0_position(const Camera &camera, const Vec2d & coor)
{
Vec3d p0, dir;
ray_from_screen_pos(camera, coor, p0, dir);
// is approx zero
if ((fabs(dir.z()) - 1e-4) < 0)
return Vec2d(std::numeric_limits<double>::max(),
std::numeric_limits<double>::max());
// find position of ray cross plane(z = 0)
double t = p0.z() / dir.z();
Vec3d p = p0 - t * dir;
return Vec2d(p.x(), p.y());
}

View File

@@ -0,0 +1,69 @@
#ifndef slic3r_CameraUtils_hpp_
#define slic3r_CameraUtils_hpp_
#include "Camera.hpp"
#include "libslic3r/Point.hpp"
namespace Slic3r {
class GLVolume;
}
namespace Slic3r::GUI {
/// <summary>
/// Help divide camera data and camera functions
/// This utility work with camera data by static funtions
/// </summary>
class CameraUtils
{
public:
CameraUtils() = delete; // only static functions
/// <summary>
/// Project point throw camera to 2d coordinate into imgui window
/// </summary>
/// <param name="camera">Projection params</param>
/// <param name="points">Point to project.</param>
/// <returns>projected points by camera into coordinate of camera.
/// x(from left to right), y(from top to bottom)</returns>
static Points project(const Camera& camera, const std::vector<Vec3d> &points);
static Point project(const Camera& camera, const Vec3d &point);
/// <summary>
/// Create hull around GLVolume in 2d space of camera
/// </summary>
/// <param name="camera">Projection params</param>
/// <param name="volume">Outline by 3d object</param>
/// <returns>Polygon around object</returns>
static Polygon create_hull2d(const Camera &camera, const GLVolume &volume);
/// <summary>
/// Create ray(point and direction) for screen coordinate
/// </summary>
/// <param name="camera">Definition of camera</param>
/// <param name="position">Position on screen(aka mouse position) </param>
/// <param name="point">OUT start of ray</param>
/// <param name="direction">OUT direction of ray</param>
static void ray_from_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction);
static void ray_from_ortho_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction);
static void ray_from_persp_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction);
/// <summary>
/// Unproject mouse coordinate to get position in space where z coor is zero
/// Platter surface should be in z == 0
/// </summary>
/// <param name="camera">Projection params</param>
/// <param name="coor">Mouse position</param>
/// <returns>Position on platter under mouse</returns>
static Vec2d get_z0_position(const Camera &camera, const Vec2d &coor);
/// <summary>
/// Create 3d screen point from 2d position
/// </summary>
/// <param name="camera">Define camera viewport</param>
/// <param name="position">Position on screen(aka mouse position)</param>
/// <returns>Point represented screen coor in 3d</returns>
static Vec3d screen_point(const Camera &camera, const Vec2d &position);
};
} // Slic3r::GUI
#endif /* slic3r_CameraUtils_hpp_ */

View File

@@ -0,0 +1,15 @@
#include <exception>
namespace Slic3r {
class ConfigError : public Slic3r::RuntimeError {
using Slic3r::RuntimeError::RuntimeError;
};
namespace GUI {
class ConfigGUITypeError : public ConfigError {
using ConfigError::ConfigError;
};
} // namespace GUI
} // namespace Slic3r

View File

@@ -0,0 +1,878 @@
// #include "libslic3r/GCodeSender.hpp"
#include "ConfigManipulation.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "format.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "MsgDialog.hpp"
#include <wx/msgdlg.h>
namespace Slic3r {
namespace GUI {
void ConfigManipulation::apply(DynamicPrintConfig* config, DynamicPrintConfig* new_config)
{
bool modified = false;
m_applying_keys = config->diff(*new_config);
for (auto opt_key : m_applying_keys) {
config->set_key_value(opt_key, new_config->option(opt_key)->clone());
modified = true;
}
if (modified && load_config != nullptr)
load_config();
m_applying_keys.clear();
}
bool ConfigManipulation::is_applying() const { return is_msg_dlg_already_exist; }
t_config_option_keys const &ConfigManipulation::applying_keys() const
{
return m_applying_keys;
}
void ConfigManipulation::toggle_field(const std::string &opt_key, const bool toggle, int opt_index /* = -1*/)
{
if (local_config) {
if (local_config->option(opt_key) == nullptr) return;
}
cb_toggle_field(opt_key, toggle, opt_index);
}
void ConfigManipulation::toggle_line(const std::string& opt_key, const bool toggle)
{
if (local_config) {
if (local_config->option(opt_key) == nullptr)
return;
}
if (cb_toggle_line)
cb_toggle_line(opt_key, toggle);
}
void ConfigManipulation::check_nozzle_recommended_temperature_range(DynamicPrintConfig *config) {
if (is_msg_dlg_already_exist)
return;
int temperature_range_low, temperature_range_high;
if (!get_temperature_range(config, temperature_range_low, temperature_range_high)) return;
wxString msg_text;
bool need_check = false;
if (temperature_range_low < 190 || temperature_range_high > 300) {
msg_text += _L("The recommended minimum temperature is less than 190 degree or the recommended maximum temperature is greater than 300 degree.\n");
need_check = true;
}
if (temperature_range_low > temperature_range_high) {
msg_text += _L("The recommended minimum temperature cannot be higher than the recommended maximum temperature.\n");
need_check = true;
}
if (need_check) {
msg_text += _L("Please check.\n");
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
is_msg_dlg_already_exist = true;
dialog.ShowModal();
is_msg_dlg_already_exist = false;
}
}
void ConfigManipulation::check_nozzle_temperature_range(DynamicPrintConfig *config)
{
if (is_msg_dlg_already_exist)
return;
int temperature_range_low, temperature_range_high;
if (!get_temperature_range(config, temperature_range_low, temperature_range_high)) return;
if (config->has("nozzle_temperature")) {
if (config->opt_int("nozzle_temperature", 0) < temperature_range_low || config->opt_int("nozzle_temperature", 0) > temperature_range_high) {
wxString msg_text = _(L("Nozzle may be blocked when the temperature is out of recommended range.\n"
"Please confirm whether to use the temperature for printing.\n\n"));
msg_text += wxString::Format(_L("Recommended nozzle temperature of this filament type is [%d, %d] degree centigrade"), temperature_range_low, temperature_range_high);
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
is_msg_dlg_already_exist = true;
dialog.ShowModal();
is_msg_dlg_already_exist = false;
}
}
}
void ConfigManipulation::check_nozzle_temperature_initial_layer_range(DynamicPrintConfig* config)
{
if (is_msg_dlg_already_exist)
return;
int temperature_range_low, temperature_range_high;
if (!get_temperature_range(config, temperature_range_low, temperature_range_high)) return;
if (config->has("nozzle_temperature_initial_layer")) {
if (config->opt_int("nozzle_temperature_initial_layer", 0) < temperature_range_low ||
config->opt_int("nozzle_temperature_initial_layer", 0) > temperature_range_high)
{
wxString msg_text = _(L("Nozzle may be blocked when the temperature is out of recommended range.\n"
"Please confirm whether to use the temperature for printing.\n\n"));
msg_text += wxString::Format(_L("Recommended nozzle temperature of this filament type is [%d, %d] degree centigrade"), temperature_range_low, temperature_range_high);
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
is_msg_dlg_already_exist = true;
dialog.ShowModal();
is_msg_dlg_already_exist = false;
}
}
}
void ConfigManipulation::check_filament_max_volumetric_speed(DynamicPrintConfig *config)
{
//if (is_msg_dlg_already_exist) return;
//float max_volumetric_speed = config->opt_float("filament_max_volumetric_speed");
float max_volumetric_speed = config->has("filament_max_volumetric_speed") ? config->opt_float("filament_max_volumetric_speed", (float) 0.5) : 0.5;
// QDS: limite the min max_volumetric_speed
if (max_volumetric_speed < 0.5) {
const wxString msg_text = _(L("Too small max volumetric speed.\nReset to 0.5"));
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("filament_max_volumetric_speed", new ConfigOptionFloats({0.5}));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
}
void ConfigManipulation::check_chamber_temperature(DynamicPrintConfig* config)
{
const static std::map<std::string, int>recommend_temp_map = {
{"PLA",45},
{"PLA-CF",45},
{"PVA",45},
{"TPU",50},
{"PETG",55},
{"PCTG",55},
{"PETG-CF",55}
};
bool support_chamber_temp_control=GUI::wxGetApp().preset_bundle->printers.get_selected_preset().config.opt_bool("support_chamber_temp_control");
if (support_chamber_temp_control&&config->has("chamber_temperatures")) {
std::string filament_type = config->option<ConfigOptionStrings>("filament_type")->get_at(0);
auto iter = recommend_temp_map.find(filament_type);
if (iter!=recommend_temp_map.end()) {
if (iter->second < config->option<ConfigOptionInts>("chamber_temperatures")->get_at(0)) {
wxString msg_text = wxString::Format(_L("Current chamber temperature is higher than the material's safe temperature,it may result in material softening and clogging.The maximum safe temperature for the material is %d"), iter->second);
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
is_msg_dlg_already_exist = true;
dialog.ShowModal();
is_msg_dlg_already_exist = false;
}
}
}
}
void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config, const bool is_plate_config)
{
// #ys_FIXME_to_delete
//! Temporary workaround for the correct updates of the TextCtrl (like "layer_height"):
// KillFocus() for the wxSpinCtrl use CallAfter function. So,
// to except the duplicate call of the update() after dialog->ShowModal(),
// let check if this process is already started.
if (is_msg_dlg_already_exist)
return;
bool is_object_config = (!is_global_config && !is_plate_config);
// layer_height shouldn't be equal to zero
if (config->opt_float("layer_height") < EPSILON)
{
const wxString msg_text = _(L("Too small layer height.\nReset to 0.2"));
MessageDialog dialog(m_msg_dlg_parent, msg_text,"", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.2));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
//QDS: limite the max layer_herght
if (config->opt_float("layer_height") > 0.6 + EPSILON)
{
const wxString msg_text = _(L("Too large layer height.\nReset to 0.2"));
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.2));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
//QDS: limit scarf seam start height range
bool apply_scarf_seam = config->opt_enum<SeamScarfType>("seam_slope_type") != SeamScarfType::None;
if (apply_scarf_seam) {
// scarf seam start height shouldn't small than zero
double layer_height = config->opt_float("layer_height");
double scarf_seam_slope_height = config->option<ConfigOptionFloatOrPercent>("seam_slope_start_height")->get_abs_value(layer_height);
if (scarf_seam_slope_height < EPSILON) {
const wxString msg_text = _(L("Too small scarf start height.\nReset to 50%"));
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("seam_slope_start_height", new ConfigOptionFloatOrPercent(50, true));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
// scarf seam start height shouldn't bigger than layer height
if (scarf_seam_slope_height > config->opt_float("layer_height") + EPSILON) {
const wxString msg_text = _(L("Too big scarf start height.\nReset to 50%"));
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("seam_slope_start_height", new ConfigOptionFloatOrPercent(50, true));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
}
//QDS: top_area_threshold showed if the top one wall function be applyed
bool top_one_wall_apply = config->opt_enum<TopOneWallType>("top_one_wall_type") == TopOneWallType::None;
toggle_line("top_area_threshold", !top_one_wall_apply);
//QDS: ironing_spacing shouldn't be too small or equal to zero
if (config->opt_float("ironing_spacing") < 0.05)
{
const wxString msg_text = _(L("Too small ironing spacing.\nReset to 0.1"));
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("ironing_spacing", new ConfigOptionFloat(0.1));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
if (config->option<ConfigOptionFloat>("initial_layer_print_height")->value < EPSILON)
{
const wxString msg_text = _(L("Zero initial layer height is invalid.\n\nThe first layer height will be reset to 0.2."));
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("initial_layer_print_height", new ConfigOptionFloat(0.2));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
if (abs(config->option<ConfigOptionFloat>("xy_hole_compensation")->value) > 2)
{
const wxString msg_text = _(L("This setting is only used for model size tuning with small value in some cases.\n"
"For example, when the model size has slight errors and is difficult be assembled.\n"
"For large size tuning, please use model scaling function.\n\n"
"The value will be reset to 0."));
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("xy_hole_compensation", new ConfigOptionFloat(0));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
if (abs(config->option<ConfigOptionFloat>("xy_contour_compensation")->value) > 2)
{
const wxString msg_text = _(L("This setting is only used for model size tuning with small value in some cases.\n"
"For example, when the model size has slight errors and is difficult be assembled.\n"
"For large size tuning, please use model scaling function.\n\n"
"The value will be reset to 0."));
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("xy_contour_compensation", new ConfigOptionFloat(0));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
if (config->option<ConfigOptionFloat>("elefant_foot_compensation")->value > 1)
{
const wxString msg_text = _(L("Too large elephant foot compensation is unreasonable.\n"
"If really have serious elephant foot effect, please check other settings.\n"
"For example, whether bed temperature is too high.\n\n"
"The value will be reset to 0."));
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("elefant_foot_compensation", new ConfigOptionFloat(0));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
double sparse_infill_density = config->option<ConfigOptionPercent>("sparse_infill_density")->value;
auto timelapse_type = config->opt_enum<TimelapseType>("timelapse_type");
if (!is_plate_config &&
config->opt_bool("spiral_mode") &&
!(config->opt_int("wall_loops") == 1 &&
config->opt_int("top_shell_layers") == 0 &&
sparse_infill_density == 0 &&
!config->opt_bool("enable_support") &&
config->opt_int("enforce_support_layers") == 0 &&
config->opt_bool("ensure_vertical_shell_thickness") &&
!config->opt_bool("detect_thin_wall") &&
config->opt_enum<TimelapseType>("timelapse_type") == TimelapseType::tlTraditional))
{
DynamicPrintConfig new_conf = *config;
auto answer = show_spiral_mode_settings_dialog(is_object_config);
bool support = true;
if (answer == wxID_YES) {
new_conf.set_key_value("wall_loops", new ConfigOptionInt(1));
new_conf.set_key_value("top_shell_layers", new ConfigOptionInt(0));
new_conf.set_key_value("sparse_infill_density", new ConfigOptionPercent(0));
new_conf.set_key_value("enable_support", new ConfigOptionBool(false));
new_conf.set_key_value("enforce_support_layers", new ConfigOptionInt(0));
new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(true));
new_conf.set_key_value("detect_thin_wall", new ConfigOptionBool(false));
new_conf.set_key_value("timelapse_type", new ConfigOptionEnum<TimelapseType>(tlTraditional));
sparse_infill_density = 0;
timelapse_type = TimelapseType::tlTraditional;
support = false;
}
else {
new_conf.set_key_value("spiral_mode", new ConfigOptionBool(false));
}
apply(config, &new_conf);
if (cb_value_change) {
cb_value_change("sparse_infill_density", sparse_infill_density);
int timelapse_type_int = (int)timelapse_type;
cb_value_change("timelapse_type", timelapse_type_int);
if (!support)
cb_value_change("enable_support", false);
}
is_msg_dlg_already_exist = false;
}
//QDS
//if (config->opt_enum<PerimeterGeneratorType>("wall_generator") == PerimeterGeneratorType::Arachne &&
// config->opt_bool("enable_overhang_speed"))
//{
// wxString msg_text = _(L("Arachne engine only works when overhang slowing down is disabled.\n"
// "This may cause decline in the quality of overhang surface when print fastly")) + "\n";
// if (is_global_config)
// msg_text += "\n" + _(L("Disable overhang slowing down automatically? \n"
// "Yes - Enable arachne and disable overhang slowing down\n"
// "No - Give up using arachne this time"));
// MessageDialog dialog(m_msg_dlg_parent, msg_text, "",
// wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
// DynamicPrintConfig new_conf = *config;
// is_msg_dlg_already_exist = true;
// auto answer = dialog.ShowModal();
// bool enable_overhang_slow_down = true;
// if (!is_global_config || answer == wxID_YES) {
// new_conf.set_key_value("enable_overhang_speed", new ConfigOptionBool(false));
// enable_overhang_slow_down = false;
// }
// else {
// new_conf.set_key_value("wall_generator", new ConfigOptionEnum<PerimeterGeneratorType>(PerimeterGeneratorType::Classic));
// }
// apply(config, &new_conf);
// if (cb_value_change) {
// if (!enable_overhang_slow_down)
// cb_value_change("enable_overhang_speed", false);
// }
// is_msg_dlg_already_exist = false;
//}
// QDS
int filament_cnt = wxGetApp().preset_bundle->filament_presets.size();
#if 0
bool has_wipe_tower = filament_cnt > 1 && config->opt_bool("enable_prime_tower");
if (has_wipe_tower && (config->opt_bool("adaptive_layer_height") || config->opt_bool("independent_support_layer_height"))) {
wxString msg_text;
if (config->opt_bool("adaptive_layer_height") && config->opt_bool("independent_support_layer_height")) {
msg_text = _(L("Prime tower does not work when Adaptive Layer Height or Independent Support Layer Height is on.\n"
"Which do you want to keep?\n"
"YES - Keep Prime Tower\n"
"NO - Keep Adaptive Layer Height and Independent Support Layer Height"));
}
else if (config->opt_bool("adaptive_layer_height")) {
msg_text = _(L("Prime tower does not work when Adaptive Layer Height is on.\n"
"Which do you want to keep?\n"
"YES - Keep Prime Tower\n"
"NO - Keep Adaptive Layer Height"));
}
else {
msg_text = _(L("Prime tower does not work when Independent Support Layer Height is on.\n"
"Which do you want to keep?\n"
"YES - Keep Prime Tower\n"
"NO - Keep Independent Support Layer Height"));
}
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxYES | wxNO);
is_msg_dlg_already_exist = true;
auto answer = dialog.ShowModal();
DynamicPrintConfig new_conf = *config;
if (answer == wxID_YES) {
if (config->opt_bool("adaptive_layer_height"))
new_conf.set_key_value("adaptive_layer_height", new ConfigOptionBool(false));
if (config->opt_bool("independent_support_layer_height"))
new_conf.set_key_value("independent_support_layer_height", new ConfigOptionBool(false));
}
else
new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(false));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
// QDS
if (has_wipe_tower && config->opt_bool("enable_support") && !config->opt_bool("independent_support_layer_height")) {
double layer_height = config->opt_float("layer_height");
double top_gap_raw = config->opt_float("support_top_z_distance");
//double bottom_gap_raw = config->opt_float("support_bottom_z_distance");
double top_gap = std::round(top_gap_raw / layer_height) * layer_height;
//double bottom_gap = std::round(bottom_gap_raw / layer_height) * layer_height;
if (top_gap != top_gap_raw /* || bottom_gap != bottom_gap_raw*/) {
DynamicPrintConfig new_conf = *config;
new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(top_gap));
//new_conf.set_key_value("support_bottom_z_distance", new ConfigOptionFloat(bottom_gap));
apply(config, &new_conf);
//wxMessageBox(_L("Support top/bottom Z distance is automatically changed to multiple of layer height."));
}
}
#endif
// Check "enable_support" and "overhangs" relations only on global settings level
if (is_global_config && config->opt_bool("enable_support")) {
// Ask only once.
if (!m_support_material_overhangs_queried) {
m_support_material_overhangs_queried = true;
if (!config->opt_bool("detect_overhang_wall")/* != 1*/) {
//QDS: detect_overhang_wall is setting in develop mode. Enable it directly.
DynamicPrintConfig new_conf = *config;
new_conf.set_key_value("detect_overhang_wall", new ConfigOptionBool(true));
apply(config, &new_conf);
}
}
}
else {
m_support_material_overhangs_queried = false;
}
if (config->opt_bool("enable_support")) {
auto support_type = config->opt_enum<SupportType>("support_type");
auto support_style = config->opt_enum<SupportMaterialStyle>("support_style");
std::set<int> enum_set_normal = { smsDefault, smsGrid, smsSnug };
std::set<int> enum_set_tree = { smsDefault, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsTreeOrganic };
auto & set = is_tree(support_type) ? enum_set_tree : enum_set_normal;
if (set.find(support_style) == set.end()) {
DynamicPrintConfig new_conf = *config;
new_conf.set_key_value("support_style", new ConfigOptionEnum<SupportMaterialStyle>(smsDefault));
apply(config, &new_conf);
}
}
if (config->option<ConfigOptionPercent>("sparse_infill_density")->value == 100) {
std::string sparse_infill_pattern = config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->serialize();
const auto &top_fill_pattern_values = config->def()->get("top_surface_pattern")->enum_values;
bool correct_100p_fill = std::find(top_fill_pattern_values.begin(), top_fill_pattern_values.end(), sparse_infill_pattern) != top_fill_pattern_values.end();
if (!correct_100p_fill) {
// get sparse_infill_pattern name from enum_labels for using this one at dialog_msg
const ConfigOptionDef *fill_pattern_def = config->def()->get("sparse_infill_pattern");
assert(fill_pattern_def != nullptr);
auto it_pattern = std::find(fill_pattern_def->enum_values.begin(), fill_pattern_def->enum_values.end(), sparse_infill_pattern);
assert(it_pattern != fill_pattern_def->enum_values.end());
if (it_pattern != fill_pattern_def->enum_values.end()) {
wxString msg_text = GUI::format_wxstr(_L("%1% infill pattern doesn't support 100%% density."),
_(fill_pattern_def->enum_labels[it_pattern - fill_pattern_def->enum_values.begin()]));
if (is_global_config)
msg_text += "\n" + _L("Switch to rectilinear pattern?\n"
"Yes - switch to rectilinear pattern automatically\n"
"No - reset density to default non 100% value automatically") + "\n";
MessageDialog dialog(m_msg_dlg_parent, msg_text, "",
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK) );
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
auto answer = dialog.ShowModal();
if (is_object_config || answer == wxID_YES) {
new_conf.set_key_value("sparse_infill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinear));
sparse_infill_density = 100;
}
else
sparse_infill_density = wxGetApp().preset_bundle->prints.get_selected_preset().config.option<ConfigOptionPercent>("sparse_infill_density")->value;
new_conf.set_key_value("sparse_infill_density", new ConfigOptionPercent(sparse_infill_density));
apply(config, &new_conf);
if (cb_value_change)
cb_value_change("sparse_infill_density", sparse_infill_density);
is_msg_dlg_already_exist = false;
}
}
}
// QDS
static const char* keys[] = { "support_filament", "support_interface_filament"};
for (int i = 0; i < sizeof(keys) / sizeof(keys[0]); i++) {
std::string key = std::string(keys[i]);
auto* opt = dynamic_cast<ConfigOptionInt*>(config->option(key, false));
if (opt != nullptr) {
if (opt->getInt() > filament_cnt) {
DynamicPrintConfig new_conf = *config;
new_conf.set_key_value(key, new ConfigOptionInt(0));
apply(config, &new_conf);
}
}
}
if (config->opt_enum<PrintSequence>("print_sequence") == PrintSequence::ByObject && config->opt_int("skirt_height") > 1 && config->opt_int("skirt_loops") > 0) {
const wxString msg_text = _(L("While printing by Object, the extruder may collide skirt.\nThus, reset the skirt layer to 1 to avoid that."));
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("skirt_height", new ConfigOptionInt(1));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
}
void ConfigManipulation::apply_null_fff_config(DynamicPrintConfig *config, std::vector<std::string> const &keys, std::map<ObjectBase *, ModelConfig *> const &configs)
{
for (auto &k : keys) {
if (/*k == "adaptive_layer_height" || */k == "independent_support_layer_height" || k == "enable_support" || k == "detect_thin_wall")
config->set_key_value(k, new ConfigOptionBool(true));
else if (k == "wall_loops")
config->set_key_value(k, new ConfigOptionInt(0));
else if (k == "top_shell_layers" || k == "enforce_support_layers")
config->set_key_value(k, new ConfigOptionInt(1));
else if (k == "sparse_infill_density") {
double v = config->option<ConfigOptionPercent>(k)->value;
for (auto &c : configs) {
auto o = c.second->get().option<ConfigOptionPercent>(k);
if (o && o->value > v) v = o->value;
}
config->set_key_value(k, new ConfigOptionPercent(v)); // sparse_infill_pattern
}
else if (k == "detect_overhang_wall")
config->set_key_value(k, new ConfigOptionBool(false));
else if (k == "sparse_infill_pattern")
config->set_key_value(k, new ConfigOptionEnum<InfillPattern>(ipGrid));
}
}
void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, const bool is_global_config)
{
bool have_perimeters = config->opt_int("wall_loops") > 0;
for (auto el : { "ensure_vertical_shell_thickness", "detect_thin_wall", "detect_overhang_wall",
"seam_position","seam_gap","wipe_speed", "wall_sequence", "outer_wall_line_width",
"inner_wall_speed", "outer_wall_speed","small_perimeter_speed", "small_perimeter_threshold" })
toggle_field(el, have_perimeters);
bool have_infill = config->option<ConfigOptionPercent>("sparse_infill_density")->value > 0;
// sparse_infill_filament uses the same logic as in Print::extruders()
for (auto el : { "sparse_infill_pattern", "sparse_infill_anchor_max", "infill_combination",
"minimum_sparse_infill_area", "sparse_infill_filament"})
toggle_line(el, have_infill);
// Only allow configuration of open anchors if the anchoring is enabled.
bool has_infill_anchors = have_infill && config->option<ConfigOptionFloatOrPercent>("sparse_infill_anchor_max")->value > 0;
toggle_line("sparse_infill_anchor", has_infill_anchors);
bool has_spiral_vase = config->opt_bool("spiral_mode");
toggle_line("spiral_mode_smooth", has_spiral_vase);
toggle_line("spiral_mode_max_xy_smoothing", config->opt_bool("spiral_mode_smooth"));
bool has_top_solid_infill = config->opt_int("top_shell_layers") > 0;
bool has_bottom_solid_infill = config->opt_int("bottom_shell_layers") > 0;
bool has_solid_infill = has_top_solid_infill || has_bottom_solid_infill;
// solid_infill_filament uses the same logic as in Print::extruders()
for (auto el : { "top_surface_pattern", "bottom_surface_pattern", "internal_solid_infill_pattern", "solid_infill_filament"})
toggle_field(el, has_solid_infill);
for (auto el : { "infill_direction", "sparse_infill_line_width", "bridge_angle",
"sparse_infill_speed", "bridge_speed" })
toggle_field(el, have_infill || has_solid_infill);
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_solid_infill);
toggle_field("bottom_shell_thickness", ! has_spiral_vase && has_bottom_solid_infill);
// Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476).
toggle_field("gap_infill_speed", have_perimeters);
for (auto el : { "top_surface_line_width", "top_surface_speed" })
toggle_field(el, has_top_solid_infill || (has_spiral_vase && has_bottom_solid_infill));
bool have_default_acceleration = config->opt_float("default_acceleration") > 0;
//QDS
for (auto el : { "initial_layer_acceleration", "outer_wall_acceleration", "top_surface_acceleration", "inner_wall_acceleration", "sparse_infill_acceleration" })
toggle_field(el, have_default_acceleration);
//w20
for (auto el : { "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk" })
toggle_line(el, false);
/*if (is_QDT_Printer) {
for (auto el : {"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk"})
toggle_line(el, false);
} else {
for (auto el : {"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk"})
toggle_line(el, true);
bool quality_default_jerk = config->opt_float("default_jerk") > 0;
for (auto el : {"outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk"})
toggle_field(el, quality_default_jerk);
}*/
bool have_skirt = config->opt_int("skirt_loops") > 0;
toggle_field("skirt_height", have_skirt && config->opt_enum<DraftShield>("draft_shield") != dsEnabled);
for (auto el : { "skirt_distance", "draft_shield"})
toggle_field(el, have_skirt);
bool have_brim = (config->opt_enum<BrimType>("brim_type") != btNoBrim);
toggle_field("brim_object_gap", have_brim);
bool have_brim_width = (config->opt_enum<BrimType>("brim_type") != btNoBrim) && config->opt_enum<BrimType>("brim_type") != btAutoBrim;
toggle_field("brim_width", have_brim_width);
// wall_filament uses the same logic as in Print::extruders()
toggle_field("wall_filament", have_perimeters || have_brim);
bool have_raft = config->opt_int("raft_layers") > 0;
bool have_support_material = config->opt_bool("enable_support") || have_raft;
// QDS
SupportType support_type = config->opt_enum<SupportType>("support_type");
bool have_support_interface = config->opt_int("support_interface_top_layers") > 0 || config->opt_int("support_interface_bottom_layers") > 0;
bool have_support_soluble = have_support_material && config->opt_float("support_top_z_distance") == 0;
auto support_style = config->opt_enum<SupportMaterialStyle>("support_style");
for (auto el : { "support_style", "support_base_pattern",
"support_base_pattern_spacing", "support_expansion", "support_angle",
"support_interface_pattern", "support_interface_top_layers", "support_interface_bottom_layers",
"bridge_no_support", "max_bridge_length", "support_top_z_distance", "support_bottom_z_distance",
//QDS: add more support params to dependent of enable_support
"support_type", "support_on_build_plate_only",
"support_remove_small_overhang","support_interface_not_for_body",
"support_object_xy_distance","support_object_first_layer_gap"/*, "independent_support_layer_height"*/})
toggle_field(el, have_support_material);
toggle_field("support_threshold_angle", have_support_material && is_auto(support_type));
//toggle_field("support_closing_radius", have_support_material && support_style == smsSnug);
bool support_is_tree = config->opt_bool("enable_support") && is_tree(support_type);
for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter","tree_support_brim_width"})
toggle_field(el, support_is_tree);
// hide tree support settings when normal is selected
for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter", "max_bridge_length","tree_support_brim_width" })
toggle_line(el, support_is_tree);
toggle_line("support_critical_regions_only", is_auto(support_type) && support_is_tree);
// tree support use max_bridge_length instead of bridge_no_support
toggle_line("bridge_no_support", !support_is_tree);
for (auto el : { "support_interface_spacing", "support_interface_filament",
"support_interface_loop_pattern", "support_bottom_interface_spacing" })
toggle_field(el, have_support_material && have_support_interface);
//QDS
bool have_skirt_height = have_skirt &&
(config->opt_int("skirt_height") > 1 || config->opt_enum<DraftShield>("draft_shield") != dsEnabled);
toggle_line("support_speed", have_support_material || have_skirt_height);
toggle_line("support_interface_speed", have_support_material && have_support_interface);
// QDS
//toggle_field("support_material_synchronize_layers", have_support_soluble);
toggle_field("inner_wall_line_width", have_perimeters || have_skirt || have_brim);
toggle_field("support_filament", have_support_material || have_skirt);
toggle_line("raft_contact_distance", have_raft && !have_support_soluble);
for (auto el : { "raft_first_layer_expansion", "raft_first_layer_density"})
toggle_line(el, have_raft);
bool has_ironing = (config->opt_enum<IroningType>("ironing_type") != IroningType::NoIroning);
for (auto el : {
"ironing_pattern","ironing_speed", "ironing_flow", "ironing_spacing", "ironing_direction"})
toggle_line(el, has_ironing);
// bool have_sequential_printing = (config->opt_enum<PrintSequence>("print_sequence") == PrintSequence::ByObject);
// for (auto el : { "extruder_clearance_radius", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid" })
// toggle_field(el, have_sequential_printing);
bool have_ooze_prevention = config->opt_bool("ooze_prevention");
toggle_field("standby_temperature_delta", have_ooze_prevention);
bool have_prime_tower = config->opt_bool("enable_prime_tower");
for (auto el : { "prime_tower_width", "prime_volume", "prime_tower_brim_width"})
toggle_line(el, have_prime_tower);
for (auto el : {"flush_into_infill", "flush_into_support", "flush_into_objects"})
toggle_field(el, have_prime_tower);
bool have_avoid_crossing_perimeters = config->opt_bool("reduce_crossing_wall");
toggle_line("max_travel_detour_distance", have_avoid_crossing_perimeters);
bool has_overhang_speed = config->opt_bool("enable_overhang_speed");
for (auto el : { "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed"})
toggle_line(el, has_overhang_speed);
toggle_line("flush_into_objects", !is_global_config);
toggle_line("support_interface_not_for_body",config->opt_int("support_interface_filament")&&!config->opt_int("support_filament"));
bool has_fuzzy_skin = (config->opt_enum<FuzzySkinType>("fuzzy_skin") != FuzzySkinType::None);
for (auto el : { "fuzzy_skin_thickness", "fuzzy_skin_point_distance"})
toggle_line(el, has_fuzzy_skin);
bool have_arachne = config->opt_enum<PerimeterGeneratorType>("wall_generator") == PerimeterGeneratorType::Arachne;
for (auto el : { "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
"min_feature_size", "min_bead_width", "wall_distribution_count" })
toggle_line(el, have_arachne);
toggle_field("detect_thin_wall", !have_arachne);
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
// OrcaSlicer
auto gcflavor = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value;
if( gcflavor != gcfKlipper )
{
for (auto el : {"accel_to_decel_enable", "accel_to_decel_factor"})
toggle_line(el, false);
}
else {
for (auto el : {"accel_to_decel_enable", "accel_to_decel_factor"})
toggle_line(el, true);
toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable"));
}
toggle_line("exclude_object", gcflavor == gcfKlipper);
toggle_field("seam_slope_type", !has_spiral_vase);
bool has_seam_slope = !has_spiral_vase && config->opt_enum<SeamScarfType>("seam_slope_type") != SeamScarfType::None;
toggle_line("seam_slope_conditional", has_seam_slope);
toggle_line("scarf_angle_threshold", has_seam_slope && config->opt_bool("seam_slope_conditional"));
toggle_line("seam_slope_start_height", has_seam_slope);
toggle_line("seam_slope_entire_loop", has_seam_slope);
toggle_line("seam_slope_min_length", has_seam_slope);
toggle_line("seam_slope_steps", has_seam_slope);
toggle_line("seam_slope_inner_walls", has_seam_slope);
toggle_field("seam_slope_min_length", !config->opt_bool("seam_slope_entire_loop"));
//w16
bool is_resonance_avoidance = config->opt_bool("resonance_avoidance");
toggle_line("min_resonance_avoidance_speed", is_resonance_avoidance);
toggle_line("max_resonance_avoidance_speed", is_resonance_avoidance);
}
void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)
{
double head_penetration = config->opt_float("support_head_penetration");
double head_width = config->opt_float("support_head_width");
if (head_penetration > head_width) {
//wxString msg_text = _(L("Head penetration should not be greater than the head width."));
wxString msg_text = "Head penetration should not be greater than the head width.";
//MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK);
MessageDialog dialog(m_msg_dlg_parent, msg_text, "Invalid Head penetration", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
if (dialog.ShowModal() == wxID_OK) {
new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width));
apply(config, &new_conf);
}
}
double pinhead_d = config->opt_float("support_head_front_diameter");
double pillar_d = config->opt_float("support_pillar_diameter");
if (pinhead_d > pillar_d) {
//wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter."));
wxString msg_text = "Pinhead diameter should be smaller than the pillar diameter.";
//MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK);
MessageDialog dialog(m_msg_dlg_parent, msg_text, "Invalid pinhead diameter", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
if (dialog.ShowModal() == wxID_OK) {
new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0));
apply(config, &new_conf);
}
}
}
void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
{
bool supports_en = config->opt_bool("supports_enable");
toggle_field("support_head_front_diameter", supports_en);
toggle_field("support_head_penetration", supports_en);
toggle_field("support_head_width", supports_en);
toggle_field("support_pillar_diameter", supports_en);
toggle_field("support_small_pillar_diameter_percent", supports_en);
toggle_field("support_max_bridges_on_pillar", supports_en);
toggle_field("support_pillar_connection_mode", supports_en);
toggle_field("support_buildplate_only", supports_en);
toggle_field("support_base_diameter", supports_en);
toggle_field("support_base_height", supports_en);
toggle_field("support_base_safety_distance", supports_en);
toggle_field("support_critical_angle", supports_en);
toggle_field("support_max_bridge_length", supports_en);
toggle_field("support_max_pillar_link_distance", supports_en);
toggle_field("support_points_density_relative", supports_en);
toggle_field("support_points_minimal_distance", supports_en);
bool pad_en = config->opt_bool("pad_enable");
toggle_field("pad_wall_thickness", pad_en);
toggle_field("pad_wall_height", pad_en);
toggle_field("pad_brim_size", pad_en);
toggle_field("pad_max_merge_distance", pad_en);
// toggle_field("pad_edge_radius", supports_en);
toggle_field("pad_wall_slope", pad_en);
toggle_field("pad_around_object", pad_en);
toggle_field("pad_around_object_everywhere", pad_en);
bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
toggle_field("support_object_elevation", supports_en && !zero_elev);
toggle_field("pad_object_gap", zero_elev);
toggle_field("pad_around_object_everywhere", zero_elev);
toggle_field("pad_object_connector_stride", zero_elev);
toggle_field("pad_object_connector_width", zero_elev);
toggle_field("pad_object_connector_penetration", zero_elev);
}
int ConfigManipulation::show_spiral_mode_settings_dialog(bool is_object_config)
{
wxString msg_text = _(L("Spiral mode only works when wall loops is 1, support is disabled, top shell layers is 0, sparse infill density is 0 and timelapse type is traditional."));
auto printer_structure_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure");
if (printer_structure_opt && printer_structure_opt->value == PrinterStructure::psI3) {
msg_text += _(L(" But machines with I3 structure will not generate timelapse videos."));
}
if (!is_object_config)
msg_text += "\n\n" + _(L("Change these settings automatically? \n"
"Yes - Change these settings and enable spiral mode automatically\n"
"No - Give up using spiral mode this time"));
MessageDialog dialog(m_msg_dlg_parent, msg_text, "",
wxICON_WARNING | (!is_object_config ? wxYES | wxNO : wxOK));
is_msg_dlg_already_exist = true;
auto answer = dialog.ShowModal();
is_msg_dlg_already_exist = false;
if (is_object_config)
answer = wxID_YES;
return answer;
}
bool ConfigManipulation::get_temperature_range(DynamicPrintConfig *config, int &range_low, int &range_high)
{
bool range_low_exist = false, range_high_exist = false;
if (config->has("nozzle_temperature_range_low")) {
range_low = config->opt_int("nozzle_temperature_range_low", (unsigned int) 0);
range_low_exist = true;
}
if (config->has("nozzle_temperature_range_high")) {
range_high = config->opt_int("nozzle_temperature_range_high", (unsigned int) 0);
range_high_exist = true;
}
return range_low_exist && range_high_exist;
}
} // GUI
} // Slic3r

View File

@@ -0,0 +1,102 @@
#ifndef slic3r_ConfigManipulation_hpp_
#define slic3r_ConfigManipulation_hpp_
/* Class for validation config options
* and update (enable/disable) IU components
*
* Used for config validation for global config (Print Settings Tab)
* and local config (overrides options on sidebar)
* */
#include "libslic3r/PrintConfig.hpp"
#include "Field.hpp"
namespace Slic3r {
class ModelConfig;
class ObjectBase;
namespace GUI {
class ConfigManipulation
{
bool is_msg_dlg_already_exist{ false };
bool m_is_initialized_support_material_overhangs_queried{ false };
bool m_support_material_overhangs_queried{ false };
bool is_QDT_Printer{false};
// function to loading of changed configuration
std::function<void()> load_config = nullptr;
std::function<void (const std::string&, bool toggle, int opt_index)> cb_toggle_field = nullptr;
std::function<void (const std::string&, bool toggle)> cb_toggle_line = nullptr;
// callback to propagation of changed value, if needed
std::function<void(const std::string&, const boost::any&)> cb_value_change = nullptr;
//QDS: change local config to const DynamicPrintConfig
const DynamicPrintConfig* local_config = nullptr;
//ModelConfig* local_config = nullptr;
wxWindow* m_msg_dlg_parent {nullptr};
t_config_option_keys m_applying_keys;
public:
ConfigManipulation(std::function<void()> load_config,
std::function<void(const std::string&, bool toggle, int opt_index)> cb_toggle_field,
std::function<void(const std::string&, bool toggle)> cb_toggle_line,
std::function<void(const std::string&, const boost::any&)> cb_value_change,
//QDS: change local config to DynamicPrintConfig
const DynamicPrintConfig* local_config = nullptr,
wxWindow* msg_dlg_parent = nullptr) :
load_config(load_config),
cb_toggle_field(cb_toggle_field),
cb_toggle_line(cb_toggle_line),
cb_value_change(cb_value_change),
m_msg_dlg_parent(msg_dlg_parent),
local_config(local_config) {}
ConfigManipulation() {}
~ConfigManipulation() {
load_config = nullptr;
cb_toggle_field = nullptr;
cb_toggle_line = nullptr;
cb_value_change = nullptr;
}
bool is_applying() const;
void apply(DynamicPrintConfig* config, DynamicPrintConfig* new_config);
t_config_option_keys const &applying_keys() const;
void toggle_field(const std::string& field_key, const bool toggle, int opt_index = -1);
void toggle_line(const std::string& field_key, const bool toggle);
// FFF print
void update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config = false, const bool is_plate_config = false);
void toggle_print_fff_options(DynamicPrintConfig* config, const bool is_global_config = false);
void apply_null_fff_config(DynamicPrintConfig *config, std::vector<std::string> const &keys, std::map<ObjectBase*, ModelConfig*> const & configs);
//QDS: FFF filament nozzle temperature range
void check_nozzle_recommended_temperature_range(DynamicPrintConfig *config);
void check_nozzle_temperature_range(DynamicPrintConfig* config);
void check_nozzle_temperature_initial_layer_range(DynamicPrintConfig* config);
void check_filament_max_volumetric_speed(DynamicPrintConfig *config);
void check_chamber_temperature(DynamicPrintConfig* config);
void set_is_QDT_Printer(bool is_qdt_printer) { is_QDT_Printer = is_qdt_printer; };
// SLA print
void update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config = false);
void toggle_print_sla_options(DynamicPrintConfig* config);
bool is_initialized_support_material_overhangs_queried() { return m_is_initialized_support_material_overhangs_queried; }
void initialize_support_material_overhangs_queried(bool queried)
{
m_is_initialized_support_material_overhangs_queried = true;
m_support_material_overhangs_queried = queried;
}
int show_spiral_mode_settings_dialog(bool is_object_config = false);
private:
bool get_temperature_range(DynamicPrintConfig *config, int &range_low, int &range_high);
};
} // GUI
} // Slic3r
#endif /* slic3r_ConfigManipulation_hpp_ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
#ifndef slic3r_ConfigWizard_hpp_
#define slic3r_ConfigWizard_hpp_
#include <memory>
#include <wx/dialog.h>
#include "GUI_Utils.hpp"
namespace Slic3r {
class PresetBundle;
class PresetUpdater;
namespace GUI {
class ConfigWizard: public DPIDialog
{
public:
// Why is the Wizard run
enum RunReason {
RR_DATA_EMPTY, // No or empty datadir
RR_DATA_LEGACY, // Pre-updating datadir
RR_DATA_INCOMPAT, // Incompatible datadir - Slic3r downgrade situation
RR_USER, // User requested the Wizard from the menus
};
// What page should wizard start on
enum StartPage {
SP_WELCOME,
SP_PRINTERS,
SP_FILAMENTS,
SP_MATERIALS,
SP_CUSTOM,
};
ConfigWizard(wxWindow *parent);
ConfigWizard(ConfigWizard &&) = delete;
ConfigWizard(const ConfigWizard &) = delete;
ConfigWizard &operator=(ConfigWizard &&) = delete;
ConfigWizard &operator=(const ConfigWizard &) = delete;
~ConfigWizard();
// Run the Wizard. Return whether it was completed.
bool run(RunReason reason, StartPage start_page = SP_WELCOME);
static const wxString& name(const bool from_menu = false);
protected:
void on_dpi_changed(const wxRect &suggested_rect) override ;
void on_sys_color_changed() override;
private:
struct priv;
std::unique_ptr<priv> p;
friend struct ConfigWizardPage;
};
}
}
#endif

View File

@@ -0,0 +1,620 @@
#ifndef slic3r_ConfigWizard_private_hpp_
#define slic3r_ConfigWizard_private_hpp_
#include "ConfigWizard.hpp"
#include <vector>
#include <set>
#include <unordered_map>
#include <functional>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <wx/sizer.h>
#include <wx/panel.h>
#include <wx/button.h>
#include <wx/choice.h>
#include <wx/spinctrl.h>
#include <wx/textctrl.h>
#include <wx/listbox.h>
#include <wx/checklst.h>
#include <wx/radiobut.h>
#include <wx/html/htmlwin.h>
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "BedShapeDialog.hpp"
#include "GUI.hpp"
#include "wxExtensions.hpp"
#include "Widgets/Button.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
enum {
WRAP_WIDTH = 500,
MODEL_MIN_WRAP = 150,
DIALOG_MARGIN = 15,
INDEX_MARGIN = 40,
BTN_SPACING = 10,
INDENT_SPACING = 30,
VERTICAL_SPACING = 10,
MAX_COLS = 4,
ROW_SPACING = 75,
};
// Configuration data structures extensions needed for the wizard
enum Technology {
// Bitflag equivalent of PrinterTechnology
T_FFF = 0x1,
T_SLA = 0x2,
T_ANY = ~0,
};
struct Bundle
{
std::unique_ptr<PresetBundle> preset_bundle;
VendorProfile* vendor_profile{ nullptr };
bool is_in_resources{ false };
//QDS: set QDT as default
bool is_qdt_bundle{ false };
Bundle() = default;
Bundle(Bundle&& other);
// Returns false if not loaded. Reason for that is logged as boost::log error.
//QDS: set QDT as default
bool load(fs::path source_path, bool is_in_resources, bool is_qdt_bundle = false);
const std::string& vendor_id() const { return vendor_profile->id; }
};
struct BundleMap : std::unordered_map<std::string /* = vendor ID */, Bundle>
{
static BundleMap load();
//QDS: add QDT as default
Bundle& qdt_bundle();
const Bundle& qdt_bundle() const;
};
struct Materials
{
Technology technology;
// use vector for the presets to purpose of save of presets sorting in the bundle
std::vector<const Preset*> presets;
// String is alias of material, size_t number of compatible counters
std::vector<std::pair<std::string, size_t>> compatibility_counter;
std::set<std::string> types;
std::set<const Preset*> printers;
Materials(Technology technology) : technology(technology) {}
void push(const Preset *preset);
void add_printer(const Preset* preset);
void clear();
bool containts(const Preset *preset) const {
//return std::find(presets.begin(), presets.end(), preset) != presets.end();
return std::find_if(presets.begin(), presets.end(),
[preset](const Preset* element) { return element == preset; }) != presets.end();
}
bool get_omnipresent(const Preset* preset) {
return get_printer_counter(preset) == printers.size();
}
const std::vector<const Preset*> get_presets_by_alias(const std::string name) {
std::vector<const Preset*> ret_vec;
for (auto it = presets.begin(); it != presets.end(); ++it) {
if ((*it)->alias == name)
ret_vec.push_back((*it));
}
return ret_vec;
}
size_t get_printer_counter(const Preset* preset) {
for (auto it : compatibility_counter) {
if (it.first == preset->alias)
return it.second;
}
return 0;
}
const std::string& appconfig_section() const;
const std::string& get_type(const Preset *preset) const;
const std::string& get_vendor(const Preset *preset) const;
template<class F> void filter_presets(const Preset* printer, const std::string& type, const std::string& vendor, F cb) {
for (auto preset : presets) {
const Preset& prst = *(preset);
const Preset& prntr = *printer;
if ((printer == nullptr || is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) &&
(type.empty() || get_type(preset) == type) &&
(vendor.empty() || get_vendor(preset) == vendor)) {
cb(preset);
}
}
}
static const std::string UNKNOWN;
static const std::string& get_filament_type(const Preset *preset);
static const std::string& get_filament_vendor(const Preset *preset);
static const std::string& get_material_type(const Preset *preset);
static const std::string& get_material_vendor(const Preset *preset);
};
struct PrinterPickerEvent;
// GUI elements
typedef std::function<bool(const VendorProfile::PrinterModel&)> ModelFilter;
struct PrinterPicker: wxPanel //TO check
{
struct Checkbox : wxCheckBox
{
Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) :
wxCheckBox(parent, wxID_ANY, label),
model(model),
variant(variant)
{}
std::string model;
std::string variant;
};
const std::string vendor_id;
std::vector<Checkbox*> cboxes;
std::vector<Checkbox*> cboxes_alt;
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter);
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig);
void select_all(bool select, bool alternates = false);
void select_one(size_t i, bool select);
bool any_selected() const;
std::set<std::string> get_selected_models() const ;
int get_width() const { return width; }
const std::vector<int>& get_button_indexes() { return m_button_indexes; }
static const std::string PRINTER_PLACEHOLDER;
private:
int width;
std::vector<int> m_button_indexes;
void on_checkbox(const Checkbox *cbox, bool checked);
};
struct ConfigWizardPage: wxPanel
{
ConfigWizard *parent;
const wxString shortname;
wxBoxSizer *content;
const unsigned indent;
ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent = 0);
virtual ~ConfigWizardPage();
template<class T>
T* append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
{
content->Add(thing, proportion, flag, border);
return thing;
}
wxStaticText* append_text(wxString text);
void append_spacer(int space);
ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
virtual void apply_custom_config(DynamicPrintConfig &config) {}
virtual void set_run_reason(ConfigWizard::RunReason run_reason) {}
virtual void on_activate() {}
};
struct PageWelcome: ConfigWizardPage
{
wxStaticText *welcome_text;
wxCheckBox *cbox_reset;
wxCheckBox *cbox_integrate;
PageWelcome(ConfigWizard *parent);
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
bool integrate_desktop() const { return cbox_integrate != nullptr ? cbox_integrate->GetValue() : false; }
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
};
struct PagePrinters: ConfigWizardPage //TO check
{
std::vector<PrinterPicker *> printer_pickers;
Technology technology;
bool install;
PagePrinters(ConfigWizard *parent,
wxString title,
wxString shortname,
const VendorProfile &vendor,
unsigned indent, Technology technology);
void select_all(bool select, bool alternates = false);
int get_width() const;
bool any_selected() const;
std::set<std::string> get_selected_models();
std::string get_vendor_id() const { return printer_pickers.empty() ? "" : printer_pickers[0]->vendor_id; }
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
bool has_printers { false };
bool is_primary_printer_page { false };
};
// Here we extend wxListBox and wxCheckListBox
// to make the client data API much easier to use.
template<class T, class D> struct DataList : public T
{
DataList(wxWindow *parent) : T(parent, wxID_ANY) {}
DataList(wxWindow* parent, int style) : T(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, style) {}
// Note: We're _not_ using wxLB_SORT here because it doesn't do the right thing,
// eg. "ABS" is sorted before "(All)"
int append(const std::string &label, const D *data) {
void *ptr = reinterpret_cast<void*>(const_cast<D*>(data));
return this->Append(from_u8(label), ptr);
}
int append(const wxString &label, const D *data) {
void *ptr = reinterpret_cast<void*>(const_cast<D*>(data));
return this->Append(label, ptr);
}
const D& get_data(int n) {
return *reinterpret_cast<const D*>(this->GetClientData(n));
}
int find(const D &data) {
for (unsigned i = 0; i < this->GetCount(); i++) {
if (get_data(i) == data) { return i; }
}
return wxNOT_FOUND;
}
int size() { return this->GetCount(); }
void on_mouse_move(const wxPoint& position) {
int item = T::HitTest(position);
if(item == wxHitTest::wxHT_WINDOW_INSIDE)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_INSIDE";
else if (item == wxHitTest::wxHT_WINDOW_OUTSIDE)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_OUTSIDE";
else if(item == wxHitTest::wxHT_WINDOW_CORNER)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_CORNER";
else if (item == wxHitTest::wxHT_WINDOW_VERT_SCROLLBAR)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_VERT_SCROLLBAR";
else if (item == wxHitTest::wxHT_NOWHERE)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_NOWHERE";
else if (item == wxHitTest::wxHT_MAX)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_MAX";
else
BOOST_LOG_TRIVIAL(error) << "hit test: " << item;
}
};
typedef DataList<wxListBox, std::string> StringList;
typedef DataList<wxCheckListBox, std::string> PresetList;
struct ProfilePrintData
{
std::reference_wrapper<const std::string> name;
bool omnipresent;
bool checked;
ProfilePrintData(const std::string& n, bool o, bool c) : name(n), omnipresent(o), checked(c) {}
};
struct PageMaterials: ConfigWizardPage
{
Materials *materials;
StringList *list_printer, *list_type, *list_vendor;
PresetList *list_profile;
wxArrayInt sel_printers_prev;
int sel_type_prev, sel_vendor_prev;
bool presets_loaded;
wxFlexGridSizer *grid;
wxHtmlWindow* html_window;
int compatible_printers_width = { 100 };
std::string empty_printers_label;
bool first_paint = { false };
static const std::string EMPTY;
int last_hovered_item = { -1 } ;
PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name);
void reload_presets();
void update_lists(int sel_type, int sel_vendor, int last_selected_printer = -1);
void on_material_highlighted(int sel_material);
void on_material_hovered(int sel_material);
void select_material(int i);
void select_all(bool select);
void clear();
void set_compatible_printers_html_window(const std::vector<std::string>& printer_names, bool all_printers = false);
void clear_compatible_printers_label();
void sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering);
void sort_list_data(PresetList* list, const std::vector<ProfilePrintData>& data);
void on_paint();
void on_mouse_move_on_profiles(wxMouseEvent& evt);
void on_mouse_enter_profiles(wxMouseEvent& evt);
void on_mouse_leave_profiles(wxMouseEvent& evt);
virtual void on_activate() override;
};
struct PageCustom: ConfigWizardPage
{
PageCustom(ConfigWizard *parent);
bool custom_wanted() const { return cb_custom->GetValue(); }
std::string profile_name() const { return into_u8(tc_profile_name->GetValue()); }
private:
static const char* default_profile_name;
wxCheckBox *cb_custom;
wxTextCtrl *tc_profile_name;
wxString profile_name_prev;
};
//struct PageUpdate: ConfigWizardPage
//{
// bool version_check;
// bool preset_update;
//
// PageUpdate(ConfigWizard *parent);
//};
//struct PageReloadFromDisk : ConfigWizardPage
//{
// bool full_pathnames;
//
// PageReloadFromDisk(ConfigWizard* parent);
//};
//#ifdef _WIN32
//struct PageFilesAssociation : ConfigWizardPage
//{
//private:
// wxCheckBox* cb_3mf{ nullptr };
// wxCheckBox* cb_stl{ nullptr };
//// wxCheckBox* cb_gcode;
//
//public:
// PageFilesAssociation(ConfigWizard* parent);
//
// bool associate_3mf() const { return cb_3mf->IsChecked(); }
// bool associate_stl() const { return cb_stl->IsChecked(); }
//// bool associate_gcode() const { return cb_gcode->IsChecked(); }
//};
//#endif // _WIN32
//struct PageVendors: ConfigWizardPage
//{
// PageVendors(ConfigWizard *parent);
//};
struct PageFirmware: ConfigWizardPage
{
const ConfigOptionDef &gcode_opt;
wxChoice *gcode_picker;
PageFirmware(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
struct PageBedShape : ConfigWizardPage
{
BedShapePanel* shape_panel;
PageBedShape(ConfigWizard* parent);
virtual void apply_custom_config(DynamicPrintConfig& config);
};
struct PageDiameters: ConfigWizardPage
{
wxTextCtrl *diam_nozzle;
wxTextCtrl *diam_filam;
PageDiameters(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
struct PageTemperatures: ConfigWizardPage
{
wxSpinCtrlDouble *spin_extr;
wxSpinCtrlDouble *spin_bed;
PageTemperatures(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
// hypothetically, each vendor can has printers both of technologies (FFF and SLA)
typedef std::map<std::string /* = vendor ID */,
std::pair<PagePrinters* /* = FFF page */,
PagePrinters* /* = SLA page */>> Pages3rdparty;
class ConfigWizardIndex: public wxPanel
{
public:
ConfigWizardIndex(wxWindow *parent);
void add_page(ConfigWizardPage *page);
void add_label(wxString label, unsigned indent = 0);
size_t active_item() const { return item_active; }
ConfigWizardPage* active_page() const;
bool active_is_last() const { return item_active < items.size() && item_active == last_page; }
void go_prev();
void go_next();
void go_to(size_t i);
void go_to(const ConfigWizardPage *page);
void clear();
void msw_rescale();
int em() const { return em_w; }
static const size_t NO_ITEM = size_t(-1);
private:
struct Item
{
wxString label;
unsigned indent;
ConfigWizardPage *page; // nullptr page => label-only item
bool operator==(ConfigWizardPage *page) const { return this->page == page; }
};
int em_w;
int em_h;
ScalableBitmap bg;
ScalableBitmap bullet_black;
ScalableBitmap bullet_blue;
ScalableBitmap bullet_white;
std::vector<Item> items;
size_t item_active;
ssize_t item_hover;
size_t last_page;
int item_height() const { return std::max(bullet_black.bmp().GetSize().GetHeight(), em_w) + em_w; }
void on_paint(wxPaintEvent &evt);
void on_mouse_move(wxMouseEvent &evt);
};
wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
// ConfigWizard private data
typedef std::map<std::string, std::set<std::string>> PresetAliases;
struct ConfigWizard::priv
{
ConfigWizard *q;
ConfigWizard::RunReason run_reason = RR_USER;
AppConfig appconfig_new; // Backing for vendor/model/variant and material selections in the GUI
BundleMap bundles; // Holds all loaded config bundles, the key is the vendor names.
// Materials refers to Presets in those bundles by pointers.
// Also we update the is_visible flag in printer Presets according to the
// PrinterPickers state.
Materials filaments; // Holds available filament presets and their types & vendors
Materials sla_materials; // Ditto for SLA materials
PresetAliases aliases_fff; // Map of aliase to preset names
PresetAliases aliases_sla; // Map of aliase to preset names
std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition
bool any_fff_selected; // Used to decide whether to display Filaments page
bool any_sla_selected; // Used to decide whether to display SLA Materials page
bool custom_printer_selected { false };
// Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers)
bool only_sla_mode { false };
wxScrolledWindow *hscroll = nullptr;
wxBoxSizer *hscroll_sizer = nullptr;
wxBoxSizer *btnsizer = nullptr;
ConfigWizardPage *page_current = nullptr;
ConfigWizardIndex *index = nullptr;
//wxButton *btn_sel_all = nullptr;
Button *btn_prev = nullptr;
Button *btn_next = nullptr;
Button *btn_finish = nullptr;
Button *btn_cancel = nullptr;
//PagePrinters *page_fff = nullptr;
//PagePrinters *page_msla = nullptr;
PageMaterials *page_filaments = nullptr;
PageMaterials *page_sla_materials = nullptr;
PageCustom *page_custom = nullptr;
//PageReloadFromDisk *page_reload_from_disk = nullptr;
//#ifdef _WIN32
// PageFilesAssociation* page_files_association = nullptr;
//#endif // _WIN32
//PageVendors *page_vendors = nullptr;
//Pages3rdparty pages_3rdparty;
// Custom setup pages
PageFirmware *page_firmware = nullptr;
PageBedShape *page_bed = nullptr;
PageDiameters *page_diams = nullptr;
PageTemperatures *page_temps = nullptr;
// Pointers to all pages (regardless or whether currently part of the ConfigWizardIndex)
std::vector<ConfigWizardPage*> all_pages;
priv(ConfigWizard *q)
: q(q)
, appconfig_new()
, filaments(T_FFF)
, sla_materials(T_SLA)
{}
void load_pages();
void init_dialog_size();
void load_vendors();
void add_page(ConfigWizardPage *page);
void enable_next(bool enable);
void set_start_page(ConfigWizard::StartPage start_page);
void create_3rdparty_pages();
void set_run_reason(RunReason run_reason);
void update_materials(Technology technology);
void on_custom_setup(const bool custom_wanted);
void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt);
void select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology);
void select_default_materials_for_printer_models(Technology technology, const std::set<const VendorProfile::PrinterModel*> &printer_models);
void on_3rdparty_install(const VendorProfile *vendor, bool install);
bool on_bnt_finish();
bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string());
bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes);
// #ys_FIXME_alise
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
#ifdef __linux__
void perform_desktop_integration() const;
#endif
bool check_fff_selected(); // Used to decide whether to display Filaments page
bool check_sla_selected(); // Used to decide whether to display SLA Materials page
int em() const { return 10; }
};
}
}
#endif

View File

@@ -0,0 +1,191 @@
#include "ConnectPrinter.hpp"
#include "GUI_App.hpp"
#include <slic3r/GUI/I18N.hpp>
#include <slic3r/GUI/Widgets/Label.hpp>
#include "libslic3r/AppConfig.hpp"
namespace Slic3r { namespace GUI {
ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos, const wxSize &size, long style)
: DPIDialog(parent, id, _L("ConnectPrinter(LAN)"), pos, size, style)
{
SetBackgroundColour(*wxWHITE);
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
wxBoxSizer *main_sizer;
main_sizer = new wxBoxSizer(wxHORIZONTAL);
main_sizer->Add(FromDIP(40), 0);
wxBoxSizer *sizer_top;
sizer_top = new wxBoxSizer(wxVERTICAL);
sizer_top->Add(0, FromDIP(40));
m_staticText_connection_code = new wxStaticText(this, wxID_ANY, _L("Please input the printer access code:"), wxDefaultPosition, wxDefaultSize, 0);
m_staticText_connection_code->SetFont(Label::Body_15);
m_staticText_connection_code->SetForegroundColour(wxColour(50, 58, 61));
m_staticText_connection_code->Wrap(-1);
sizer_top->Add(m_staticText_connection_code, 0, wxALL, 0);
sizer_top->Add(0, FromDIP(10));
wxBoxSizer *sizer_connect;
sizer_connect = new wxBoxSizer(wxHORIZONTAL);
m_textCtrl_code = new TextInput(this, wxEmptyString);
m_textCtrl_code->GetTextCtrl()->SetMaxLength(10);
m_textCtrl_code->SetFont(Label::Body_14);
m_textCtrl_code->SetCornerRadius(FromDIP(5));
m_textCtrl_code->SetSize(wxSize(FromDIP(330), FromDIP(40)));
m_textCtrl_code->SetMinSize(wxSize(FromDIP(330), FromDIP(40)));
m_textCtrl_code->GetTextCtrl()->SetSize(wxSize(-1, FromDIP(22)));
m_textCtrl_code->GetTextCtrl()->SetMinSize(wxSize(-1, FromDIP(22)));
m_textCtrl_code->SetBackgroundColour(*wxWHITE);
m_textCtrl_code->GetTextCtrl()->SetForegroundColour(wxColour(107, 107, 107));
sizer_connect->Add(m_textCtrl_code, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0);
sizer_connect->Add(FromDIP(20), 0);
m_button_confirm = new Button(this, _L("Confirm"));
m_button_confirm->SetFont(Label::Body_12);
m_button_confirm->SetMinSize(wxSize(-1, FromDIP(24)));
m_button_confirm->SetCornerRadius(FromDIP(12));
m_button_confirm->SetTextColor(wxColour("#FFFFFE"));
StateColor btn_bg(
// y96
std::pair<wxColour, int>(wxColour(95, 82, 253), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(129, 150, 255), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Normal)
);
StateColor btn_bd(std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Normal));
StateColor btn_text(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
m_button_confirm->SetBackgroundColor(btn_bg);
m_button_confirm->SetBorderColor(btn_bd);
m_button_confirm->SetTextColor(btn_text);
sizer_connect->Add(m_button_confirm, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0);
sizer_connect->Add(FromDIP(60), 0);
sizer_top->Add(sizer_connect);
sizer_top->Add(0, FromDIP(35));
m_staticText_hints = new wxStaticText(this, wxID_ANY, _L("You can find it in \"Settings > Network > Connection code\"\non the printer, as shown in the figure:"), wxDefaultPosition, wxDefaultSize, 0);
m_staticText_hints->SetFont(Label::Body_15);
m_staticText_hints->SetForegroundColour(wxColour(50, 58, 61));
m_staticText_hints->Wrap(-1);
sizer_top->Add(m_staticText_hints, 0, wxALL, 0);
sizer_top->Add(0, FromDIP(25));
wxBoxSizer *sizer_diagram;
sizer_diagram = new wxBoxSizer(wxHORIZONTAL);
m_bitmap_diagram = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(340), -1), 0);
m_bitmap_diagram->SetBitmap(m_diagram_img);
sizer_diagram->Add(m_bitmap_diagram);
sizer_top->Add(sizer_diagram);
sizer_top->Add(0, FromDIP(40), 0, wxEXPAND, 0);
main_sizer->Add(sizer_top);
this->SetSizer(main_sizer);
this->Layout();
this->Fit();
CentreOnParent();
m_textCtrl_code->Bind(wxEVT_TEXT, &ConnectPrinterDialog::on_input_enter, this);
m_button_confirm->Bind(wxEVT_BUTTON, &ConnectPrinterDialog::on_button_confirm, this);
wxGetApp().UpdateDlgDarkUI(this);
}
ConnectPrinterDialog::~ConnectPrinterDialog() {}
void ConnectPrinterDialog::end_modal(wxStandardID id)
{
EndModal(id);
}
void ConnectPrinterDialog::init_bitmap()
{
AppConfig *config = get_app_config();
std::string language = config->get("language");
if (m_obj) {
std::string img_str = DeviceManager::get_printer_diagram_img(m_obj->printer_type);
if(img_str.empty()){img_str = "input_access_code_x1"; }
if (language == "zh_CN") {
m_diagram_bmp = create_scaled_bitmap(img_str+"_cn", nullptr, 190);
}
else {
m_diagram_bmp = create_scaled_bitmap(img_str+"_en", nullptr, 190);
}
}
else{
if (language == "zh_CN") {
m_diagram_bmp = create_scaled_bitmap("input_access_code_x1_cn", nullptr, 190);
}
else {
m_diagram_bmp = create_scaled_bitmap("input_access_code_x1_en", nullptr, 190);
}
}
m_diagram_img = m_diagram_bmp.ConvertToImage();
auto bmp_size = m_diagram_bmp.GetSize();
float scale = (float)FromDIP(340) / (float)bmp_size.x;
m_diagram_img.Rescale(FromDIP(340), bmp_size.y * scale);
m_bitmap_diagram->SetBitmap(m_diagram_img);
Fit();
}
void ConnectPrinterDialog::set_machine_object(MachineObject* obj)
{
m_obj = obj;
init_bitmap();
}
void ConnectPrinterDialog::on_input_enter(wxCommandEvent& evt)
{
m_input_access_code = evt.GetString();
}
void ConnectPrinterDialog::on_button_confirm(wxCommandEvent &event)
{
wxString code = m_textCtrl_code->GetTextCtrl()->GetValue();
for (char c : code) {
if (!('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z')) {
show_error(this, _L("Invalid input."));
return;
}
}
if (m_obj) {
m_obj->set_user_access_code(code.ToStdString());
if (m_need_connect) {
wxGetApp().getDeviceManager()->set_selected_machine(m_obj->dev_id);
}
}
EndModal(wxID_OK);
}
void ConnectPrinterDialog::on_dpi_changed(const wxRect &suggested_rect)
{
init_bitmap();
m_bitmap_diagram->SetBitmap(m_diagram_img);
m_textCtrl_code->GetTextCtrl()->SetSize(wxSize(-1, FromDIP(22)));
m_textCtrl_code->GetTextCtrl()->SetMinSize(wxSize(-1, FromDIP(22)));
m_button_confirm->SetCornerRadius(FromDIP(12));
m_button_confirm->Rescale();
Layout();
this->Refresh();
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,59 @@
#ifndef slic3r_GUI_ConnectPrinter_hpp_
#define slic3r_GUI_ConnectPrinter_hpp_
#include "GUI.hpp"
#include "GUI_Utils.hpp"
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/stattext.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/statbmp.h>
#include "Widgets/Button.hpp"
#include "Widgets/TextInput.hpp"
#include "DeviceManager.hpp"
namespace Slic3r { namespace GUI {
class ConnectPrinterDialog : public DPIDialog
{
private:
protected:
bool m_need_connect{true};
wxStaticText * m_staticText_connection_code;
TextInput * m_textCtrl_code;
Button * m_button_confirm;
wxStaticText* m_staticText_hints;
wxStaticBitmap* m_bitmap_diagram;
wxBitmap m_diagram_bmp;
wxImage m_diagram_img;
MachineObject* m_obj;
wxString m_input_access_code;
public:
ConnectPrinterDialog(wxWindow * parent,
wxWindowID id = wxID_ANY,
const wxString &title = wxEmptyString,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = wxCLOSE_BOX | wxCAPTION);
~ConnectPrinterDialog();
void go_connect_printer(bool need) {m_need_connect = need;};
void end_modal(wxStandardID id);
void init_bitmap();
void set_machine_object(MachineObject* obj);
void on_input_enter(wxCommandEvent& evt);
void on_button_confirm(wxCommandEvent &event);
void on_dpi_changed(const wxRect &suggested_rect) override;
};
}} // namespace Slic3r::GUI
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,401 @@
#ifndef slic3r_CreatePresetsDialog_hpp_
#define slic3r_CreatePresetsDialog_hpp_
#include "libslic3r/Preset.hpp"
#include "wxExtensions.hpp"
#include "GUI_Utils.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/TextInput.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/RadioBox.hpp"
#include "Widgets/CheckBox.hpp"
#include "Widgets/ComboBox.hpp"
#include "miniz.h"
#include "ParamsDialog.hpp"
namespace Slic3r {
namespace GUI {
class CreateFilamentPresetDialog : public DPIDialog
{
public:
CreateFilamentPresetDialog(wxWindow *parent);
~CreateFilamentPresetDialog();
protected:
enum FilamentOptionType {
VENDOR = 0,
TYPE,
SERIAL,
FILAMENT_PRESET,
PRESET_FOR_PRINTER,
FILAMENT_NAME_COUNT
};
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
bool is_check_box_selected();
wxBoxSizer *create_item(FilamentOptionType option_type);
wxBoxSizer *create_vendor_item();
wxBoxSizer *create_type_item();
wxBoxSizer *create_serial_item();
wxBoxSizer *create_filament_preset_item();
wxBoxSizer *create_filament_preset_for_printer_item();
wxBoxSizer *create_button_item();
private:
void clear_filament_preset_map();
wxArrayString get_filament_preset_choices();
wxBoxSizer * create_radio_item(wxString title, wxWindow *parent, wxString tooltip, std::vector<std::pair<RadioBox *, wxString>> &radiobox_list);
void select_curr_radiobox(std::vector<std::pair<RadioBox *, wxString>> &radiobox_list, int btn_idx);
wxString curr_create_filament_type();
void get_filament_presets_by_machine();
void get_all_filament_presets();
void get_all_visible_printer_name();
void update_dialog_size();
template<typename T>
void sort_printer_by_nozzle(std::vector<std::pair<std::string, T>> &printer_name_to_filament_preset);
private:
struct CreateType
{
wxString base_filament;
wxString base_filament_preset;
};
private:
std::vector<std::pair<RadioBox *, wxString>> m_create_type_btns;
std::unordered_map<::CheckBox *, std::pair<std::string, Preset *>> m_filament_preset;
std::unordered_map<::CheckBox *, std::pair<std::string, Preset *>> m_machint_filament_preset;
std::unordered_map<std::string, std::vector<Preset *>> m_filament_choice_map;
std::unordered_map<wxString, std::string> m_public_name_to_filament_id_map;
std::unordered_map<std::string, Preset *> m_all_presets_map;
std::unordered_set<std::string> m_system_filament_types_set;
std::set<std::string> m_visible_printers;
CreateType m_create_type;
Button * m_button_create = nullptr;
Button * m_button_cancel = nullptr;
ComboBox * m_filament_vendor_combobox = nullptr;
::CheckBox * m_can_not_find_vendor_checkbox = nullptr;
ComboBox * m_filament_type_combobox = nullptr;
ComboBox * m_exist_vendor_combobox = nullptr;
ComboBox * m_filament_preset_combobox = nullptr;
TextInput * m_filament_custom_vendor_input = nullptr;
wxGridSizer * m_filament_presets_sizer = nullptr;
wxPanel * m_filament_preset_panel = nullptr;
wxScrolledWindow * m_scrolled_preset_panel = nullptr;
TextInput * m_filament_serial_input = nullptr;
wxBoxSizer * m_scrolled_sizer = nullptr;
wxStaticText * m_filament_preset_text = nullptr;
};
class CreatePrinterPresetDialog : public DPIDialog
{
public:
CreatePrinterPresetDialog(wxWindow *parent);
~CreatePrinterPresetDialog();
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
/******************************************************** Control Construction *****************************************************/
wxBoxSizer *create_step_switch_item();
//Create Printer Page1
void create_printer_page1(wxWindow *parent);
wxBoxSizer *create_type_item(wxWindow *parent);
wxBoxSizer *create_printer_item(wxWindow *parent);
wxBoxSizer *create_nozzle_diameter_item(wxWindow *parent);
wxBoxSizer *create_bed_shape_item(wxWindow *parent);
wxBoxSizer *create_bed_size_item(wxWindow *parent);
wxBoxSizer *create_origin_item(wxWindow *parent);
wxBoxSizer *create_hot_bed_stl_item(wxWindow *parent);
wxBoxSizer *create_hot_bed_svg_item(wxWindow *parent);
wxBoxSizer *create_max_print_height_item(wxWindow *parent);
wxBoxSizer *create_page1_btns_item(wxWindow *parent);
//Improt Presets Page2
void create_printer_page2(wxWindow *parent);
wxBoxSizer *create_printer_preset_item(wxWindow *parent);
wxBoxSizer *create_presets_item(wxWindow *parent);
wxBoxSizer *create_presets_template_item(wxWindow *parent);
wxBoxSizer *create_page2_btns_item(wxWindow *parent);
void show_page1();
void show_page2();
/********************************************************** Data Interaction *******************************************************/
bool data_init();
void set_current_visible_printer();
void select_curr_radiobox(std::vector<std::pair<RadioBox *, wxString>> &radiobox_list, int btn_idx);
void select_all_preset_template(std::vector<std::pair<::CheckBox *, Preset *>> &preset_templates);
void deselect_all_preset_template(std::vector<std::pair<::CheckBox *, Preset *>> &preset_templates);
void update_presets_list(bool jast_template = false);
void on_preset_model_value_change(wxCommandEvent &e);
void clear_preset_combobox();
bool save_printable_area_config(Preset *preset);
bool check_printable_area();
bool validate_input_valid();
void load_texture();
void load_model_stl();
bool load_system_and_user_presets_with_curr_model(PresetBundle &temp_preset_bundle, bool just_template = false);
void generate_process_presets_data(std::vector<Preset const *> presets, std::string nozzle);
void update_preset_list_size();
wxArrayString printer_preset_sort_with_nozzle_diameter(const VendorProfile &vendor_profile, float nozzle_diameter);
wxBoxSizer *create_radio_item(wxString title, wxWindow *parent, wxString tooltip, std::vector<std::pair<RadioBox *, wxString>> &radiobox_list);
wxString curr_create_preset_type();
wxString curr_create_printer_type();
private:
struct CreatePrinterType
{
wxString create_printer;
wxString create_nozzle;
wxString base_template;
wxString base_curr_printer;
};
CreatePrinterType m_create_type;
std::vector<std::pair<RadioBox *, wxString>> m_create_type_btns;
std::vector<std::pair<RadioBox *, wxString>> m_create_presets_btns;
std::vector<std::pair<::CheckBox *, Preset *>> m_filament_preset;
std::vector<std::pair<::CheckBox *, Preset *>> m_process_preset;
std::unordered_map<std::string, std::shared_ptr<Preset>> m_printer_name_to_preset;
VendorProfile m_printer_preset_vendor_selected;
Slic3r::VendorProfile::PrinterModel m_printer_preset_model_selected;
bool rewritten = false;
Preset * m_printer_preset = nullptr;
wxStaticBitmap * m_step_1 = nullptr;
wxStaticBitmap * m_step_2 = nullptr;
Button * m_button_OK = nullptr;
Button * m_button_create = nullptr;
Button * m_button_page1_cancel = nullptr;
Button * m_button_page2_cancel = nullptr;
Button * m_button_page2_back = nullptr;
Button * m_button_bed_stl = nullptr;
Button * m_button_bed_svg = nullptr;
wxScrolledWindow * m_page1 = nullptr;
wxPanel * m_page2 = nullptr;
wxScrolledWindow * m_scrolled_preset_window = nullptr;
wxBoxSizer * m_scrooled_preset_sizer = nullptr;
ComboBox * m_select_vendor = nullptr;
ComboBox * m_select_model = nullptr;
ComboBox * m_select_printer = nullptr;
::CheckBox * m_can_not_find_vendor_combox = nullptr;
wxStaticText * m_can_not_find_vendor_text = nullptr;
wxTextCtrl * m_custom_vendor_text_ctrl = nullptr;
wxTextCtrl * m_custom_model_text_ctrl = nullptr;
ComboBox * m_nozzle_diameter = nullptr;
ComboBox * m_printer_vendor = nullptr;
ComboBox * m_printer_model = nullptr;
TextInput * m_bed_size_x_input = nullptr;
TextInput * m_bed_size_y_input = nullptr;
TextInput * m_bed_origin_x_input = nullptr;
TextInput * m_bed_origin_y_input = nullptr;
TextInput * m_print_height_input = nullptr;
wxGridSizer * m_filament_preset_template_sizer = nullptr;
wxGridSizer * m_process_preset_template_sizer = nullptr;
wxPanel * m_filament_preset_panel = nullptr;
wxPanel * m_process_preset_panel = nullptr;
wxPanel * m_preset_template_panel = nullptr;
wxBoxSizer * m_filament_sizer = nullptr;
wxPanel * m_printer_info_panel = nullptr;
wxBoxSizer * m_page1_sizer = nullptr;
wxBoxSizer * m_printer_info_sizer = nullptr;
wxBoxSizer * m_page2_sizer = nullptr;
wxStaticText * m_upload_stl_tip_text = nullptr;
wxStaticText * m_upload_svg_tip_text = nullptr;
std::string m_custom_texture;
std::string m_custom_model;
};
enum SuccessType {
PRINTER = 0,
FILAMENT
};
class CreatePresetSuccessfulDialog : public DPIDialog
{
public:
CreatePresetSuccessfulDialog(wxWindow *parent, const SuccessType &create_success_type);
~CreatePresetSuccessfulDialog();
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
private:
Button *m_button_ok = nullptr;
Button *m_button_cancel = nullptr;
};
class ExportConfigsDialog : public DPIDialog
{
public:
ExportConfigsDialog(wxWindow *parent);
~ExportConfigsDialog();//to do: delete preset
protected:
struct ExportType
{
wxString preset_bundle;
wxString filament_bundle;
wxString printer_preset;
wxString filament_preset;
wxString process_preset;
};
enum ExportCase {
INITIALIZE_FAIL = 0,
ADD_FILE_FAIL,
ADD_BUNDLE_STRUCTURE_FAIL,
FINALIZE_FAIL,
OPEN_ZIP_WRITTEN_FILE,
EXPORT_CANCEL,
EXPORT_SUCCESS,
CASE_COUNT,
};
private:
void data_init();
void select_curr_radiobox(std::vector<std::pair<RadioBox *, wxString>> &radiobox_list, int btn_idx);
void on_dpi_changed(const wxRect &suggested_rect) override;
void show_export_result(const ExportCase &export_case);
bool has_check_box_selected();
bool preset_is_not_compatible_qdt_printer(Preset *preset);
bool earse_preset_fields_for_safe(Preset *preset);
std::string initial_file_path(const wxString &path, const std::string &sub_file_path);
std::string initial_file_name(const wxString &path, const std::string file_name);
wxBoxSizer *create_export_config_item(wxWindow *parent);
wxBoxSizer *create_button_item(wxWindow *parent);
wxBoxSizer *create_select_printer(wxWindow *parent);
wxBoxSizer *create_radio_item(wxString title, wxWindow *parent, wxString tooltip, std::vector<std::pair<RadioBox *, wxString>> &radiobox_list);
int initial_zip_archive(mz_zip_archive &zip_archive, const std::string &file_path);
ExportCase save_zip_archive_to_file(mz_zip_archive &zip_archive);
ExportCase save_presets_to_zip(const std::string &export_file, const std::vector<std::pair<std::string, std::string>> &config_paths);
ExportCase archive_preset_bundle_to_file(const wxString &path);
ExportCase archive_filament_bundle_to_file(const wxString &path);
ExportCase archive_printer_preset_to_file(const wxString &path);
ExportCase archive_filament_preset_to_file(const wxString &path);
ExportCase archive_process_preset_to_file(const wxString &path);
private:
std::vector<std::pair<RadioBox *, wxString>> m_export_type_btns;
std::vector<std::pair<::CheckBox *, Preset *>> m_preset; // for printer preset bundle,printer preset, process preset export
std::vector<std::pair<::CheckBox *, std::string>> m_printer_name; // for filament and peocess preset export, collaborate with m_filament_name_to_presets
std::unordered_map<std::string, Preset *> m_printer_presets;//first: printer name, second: printer presets have same printer name
std::unordered_map<std::string, std::vector<Preset *>> m_filament_presets;//first: printer name, second: filament presets have same printer name
std::unordered_map<std::string, std::vector<Preset *>> m_process_presets;//first: printer name, second: filament presets have same printer name
std::unordered_map<std::string, std::vector<std::pair<std::string, Preset *>>> m_filament_name_to_presets;//first: filament name, second presets have same filament name and printer name in vector
ExportType m_exprot_type;
wxBoxSizer * m_main_sizer = nullptr;
wxScrolledWindow * m_scrolled_preset_window = nullptr;
wxGridSizer * m_preset_sizer = nullptr;
wxPanel * m_presets_window = nullptr;
Button * m_button_ok = nullptr;
Button * m_button_cancel = nullptr;
wxStaticText * m_serial_text = nullptr;
};
class CreatePresetForPrinterDialog : public DPIDialog
{
public:
CreatePresetForPrinterDialog(wxWindow *parent, std::string filament_type, std::string filament_id, std::string filament_vendor, std::string filament_name);
~CreatePresetForPrinterDialog();
private:
void on_dpi_changed(const wxRect &suggested_rect) override;
void get_visible_printer_and_compatible_filament_presets();
wxBoxSizer *create_selected_printer_preset_sizer();
wxBoxSizer *create_selected_filament_preset_sizer();
wxBoxSizer *create_button_sizer();
private:
std::string m_filament_id;
std::string m_filament_name;
std::string m_filament_vendor;
std::string m_filament_type;
std::shared_ptr<PresetBundle> m_preset_bundle;
ComboBox * m_selected_printer = nullptr;
ComboBox * m_selected_filament = nullptr;
Button * m_ok_btn = nullptr;
Button * m_cancel_btn = nullptr;
std::unordered_map<wxString, std::shared_ptr<Preset>> filament_choice_to_filament_preset;
std::unordered_map<std::string, std::vector<std::shared_ptr<Preset>>> m_printer_compatible_filament_presets; // need be used when add presets
};
class EditFilamentPresetDialog;
class PresetTree
{
public:
PresetTree(EditFilamentPresetDialog *dialog);
wxPanel *get_preset_tree(std::pair<std::string, std::vector<std::shared_ptr<Preset>>> printer_and_presets);
private:
wxPanel *get_root_item(wxPanel *parent, const std::string &printer_name);
wxPanel *get_child_item(wxPanel *parent, std::shared_ptr<Preset> preset, std::string printer_name, int preset_index, bool is_last = false);
void delete_preset(std::string printer_name, int need_delete_preset_index);
void edit_preset(std::string printer_name, int need_edit_preset_index);
private:
EditFilamentPresetDialog * m_parent_dialog = nullptr;
std::pair<std::string, std::vector<std::shared_ptr<Preset>>> m_printer_and_presets;
};
class EditFilamentPresetDialog : public DPIDialog
{
public:
EditFilamentPresetDialog(wxWindow *parent, FilamentInfomation *filament_info);
~EditFilamentPresetDialog();
wxPanel *get_preset_tree_panel() { return m_preset_tree_panel; }
std::shared_ptr<Preset> get_need_edit_preset() { return m_need_edit_preset; }
void set_printer_name(const std::string &printer_name) { m_selected_printer = printer_name; }
void set_need_delete_preset_index(int need_delete_preset_index) { m_need_delete_preset_index = need_delete_preset_index; }
void set_need_edit_preset_index(int need_edit_preset_index) { m_need_edit_preset_index = need_edit_preset_index; }
void delete_preset();
void edit_preset();
private:
void on_dpi_changed(const wxRect &suggested_rect) override;
bool get_same_filament_id_presets(std::string filament_id);
void update_preset_tree();
wxBoxSizer *create_filament_basic_info();
wxBoxSizer *create_add_filament_btn();
wxBoxSizer *create_preset_tree_sizer();
wxBoxSizer *create_button_sizer();
private:
PresetTree * m_preset_tree_creater = nullptr;
std::string m_filament_id;
std::string m_filament_name;
std::string m_vendor_name;
std::string m_filament_type;
std::string m_filament_serial;
Button * m_add_filament_btn = nullptr;
Button * m_del_filament_btn = nullptr;
Button * m_ok_btn = nullptr;
wxBoxSizer * m_preset_tree_sizer = nullptr;
wxPanel * m_preset_tree_panel = nullptr;
wxScrolledWindow * m_preset_tree_window = nullptr;
wxBoxSizer * m_main_sizer = nullptr;
wxStaticText * m_note_text = nullptr;
int m_need_delete_preset_index = -1;
int m_need_edit_preset_index = -1;
std::shared_ptr<Preset> m_need_edit_preset;
std::string m_selected_printer = "";
std::unordered_map<std::string, std::vector<std::shared_ptr<Preset>>> m_printer_compatible_presets;
};
}
}
#endif

View File

@@ -0,0 +1,620 @@
#include "DailyTips.hpp"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <imgui/imgui_internal.h>
namespace Slic3r { namespace GUI {
struct DailyTipsData {
std::string main_text;
std::string wiki_url;
std::string img_url;
std::string additional_text; // currently not used
std::string hyper_text; // currently not used
std::function<void(void)> hypertext_callback; // currently not used
};
class DailyTipsDataRenderer {
public:
DailyTipsDataRenderer(DailyTipsLayout layout);
~DailyTipsDataRenderer();
void update_data(const DailyTipsData& data);
void render(const ImVec2& pos, const ImVec2& size) const;
bool has_image() const;
void on_change_color_mode(bool is_dark);
void set_fade_opacity(float opacity);
protected:
void load_texture_from_img_url(const std::string url);
void open_wiki() const;
// relative to the window's upper-left position
void render_img(const ImVec2& start_pos, const ImVec2& size) const;
void render_text(const ImVec2& start_pos, const ImVec2& size) const;
private:
DailyTipsData m_data;
GLTexture* m_texture{ nullptr };
GLTexture* m_placeholder_texture{ nullptr };
bool m_is_dark{ false };
DailyTipsLayout m_layout;
float m_fade_opacity{ 1.0f };
};
DailyTipsDataRenderer::DailyTipsDataRenderer(DailyTipsLayout layout)
: m_layout(layout)
{
}
DailyTipsDataRenderer::~DailyTipsDataRenderer() {
if (m_texture)
delete m_texture;
if (m_placeholder_texture)
delete m_placeholder_texture;
}
void DailyTipsDataRenderer::update_data(const DailyTipsData& data)
{
m_data = data;
load_texture_from_img_url(m_data.img_url);
}
void DailyTipsDataRenderer::load_texture_from_img_url(const std::string url)
{
if (m_texture) {
delete m_texture;
m_texture = nullptr;
}
if (!url.empty()) {
m_texture = new GLTexture();
m_texture->load_from_file(Slic3r::resources_dir() + "/" + url, true, GLTexture::None, false);
}
else {
if (!m_placeholder_texture) {
m_placeholder_texture = new GLTexture();
m_placeholder_texture->load_from_file(Slic3r::resources_dir() + "/images/dailytips_placeholder.png", true, GLTexture::None, false);
}
}
}
void DailyTipsDataRenderer::open_wiki() const
{
if (!m_data.wiki_url.empty())
{
wxGetApp().open_browser_with_warning_dialog(m_data.wiki_url);
NetworkAgent* agent = wxGetApp().getAgent();
if (agent) {
json j;
j["dayil_tips"] = m_data.wiki_url;
agent->track_event("dayil_tips", j.dump());
}
}
}
void DailyTipsDataRenderer::render(const ImVec2& pos, const ImVec2& size) const
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
ImGuiWindow* parent_window = ImGui::GetCurrentWindow();
int window_flags = parent_window->Flags;
window_flags &= ~ImGuiWindowFlags_NoScrollbar;
window_flags &= ~ImGuiWindowFlags_NoScrollWithMouse;
std::string name = "##DailyTipsDataRenderer" + std::to_string(parent_window->ID);
ImGui::SetNextWindowPos(pos);
if (ImGui::BeginChild(name.c_str(), size, false, window_flags)) {
if (m_layout == DailyTipsLayout::Vertical) {
ImVec2 img_size = ImVec2(size.x, 9.0f / 16.0f * size.x);
render_img({ 0, 0 }, img_size);
float img_text_gap = ImGui::CalcTextSize("A").y;
render_text({ 0, img_size.y + img_text_gap }, size);
}
if (m_layout == DailyTipsLayout::Horizontal) {
ImVec2 img_size = ImVec2(16.0f / 9.0f * size.y, size.y);
render_img({ 0, 0 }, img_size);
float img_text_gap = ImGui::CalcTextSize("A").y;
render_text({ img_size.x + img_text_gap, 0 }, { size.x - img_size.x - img_text_gap, size.y });
}
}
ImGui::EndChild();
}
bool DailyTipsDataRenderer::has_image() const
{
return !m_data.img_url.empty();
}
void DailyTipsDataRenderer::on_change_color_mode(bool is_dark)
{
m_is_dark = is_dark;
}
void DailyTipsDataRenderer::set_fade_opacity(float opacity)
{
m_fade_opacity = opacity;
}
void DailyTipsDataRenderer::render_img(const ImVec2& start_pos, const ImVec2& size) const
{
if (has_image())
ImGui::Image((ImTextureID)(intptr_t)m_texture->get_id(), size, ImVec2(0, 0), ImVec2(1, 1), m_is_dark ? ImVec4(0.8, 0.8, 0.8, m_fade_opacity) : ImVec4(1, 1, 1, m_fade_opacity));
else {
ImGui::Image((ImTextureID)(intptr_t)m_placeholder_texture->get_id(), size, ImVec2(0, 0), ImVec2(1, 1), m_is_dark ? ImVec4(0.8, 0.8, 0.8, m_fade_opacity) : ImVec4(1, 1, 1, m_fade_opacity));
}
}
void DailyTipsDataRenderer::render_text(const ImVec2& start_pos, const ImVec2& size) const
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
ImGui::PushStyleColor(ImGuiCol_Text, m_is_dark ? ImVec4(1.0f, 1.0f, 1.0f, 0.88f * m_fade_opacity) : ImVec4(38 / 255.0f, 46 / 255.0f, 48 / 255.0f, m_fade_opacity));
// main text
// first line is headline (for hint notification it must be divided by \n)
std::string title_line;
std::string content_lines;
size_t end_pos = m_data.main_text.find_first_of('\n');
if (end_pos != std::string::npos) {
title_line = m_data.main_text.substr(0, end_pos);
title_line = ImGui::ColorMarkerStart + title_line + ImGui::ColorMarkerEnd;
content_lines = m_data.main_text.substr(end_pos + 1);
}
ImGui::SetCursorPos(start_pos);
imgui.text(title_line);
bool is_zh = false;
for (int i = 0; i < content_lines.size() - 1; i += 2) {
if ((content_lines[i] & 0x80) && (content_lines[i + 1] & 0x80))
is_zh = true;
}
if (!is_zh) {
// problem in Chinese with spaces
ImGui::SetCursorPosX(start_pos.x);
imgui.text_wrapped(content_lines, size.x);
}
else {
Label* wrapped_text = new Label(wxGetApp().GetTopWindow());
wrapped_text->Hide();
wrapped_text->SetLabelText(wxString::FromUTF8(content_lines));
wrapped_text->Wrap(size.x + ImGui::CalcTextSize("A").x * 5.0f);
std::string wrapped_content_lines = wrapped_text->GetLabel().ToUTF8().data();
wrapped_text->Destroy();
ImGui::SetCursorPosX(start_pos.x);
imgui.text(wrapped_content_lines);
}
// wiki //B //y6
if (!m_data.wiki_url.empty()) {
std::string tips_line = _u8L("For more information, please check out Wiki");
std::string wiki_part_text = _u8L("Wiki");
std::string first_part_text = tips_line.substr(0, tips_line.find(wiki_part_text));
ImVec2 wiki_part_size = ImGui::CalcTextSize(wiki_part_text.c_str());
ImVec2 first_part_size = ImGui::CalcTextSize(first_part_text.c_str());
//text
ImGui::SetCursorPosX(start_pos.x);
ImVec2 link_start_pos = ImGui::GetCursorScreenPos();
imgui.text(first_part_text);
ImColor HyperColor = ImColor(31, 142, 234, (int)(255 * m_fade_opacity)).Value;
ImVec2 wiki_part_rect_min = ImVec2(link_start_pos.x + first_part_size.x, link_start_pos.y);
ImVec2 wiki_part_rect_max = wiki_part_rect_min + wiki_part_size;
ImGui::PushStyleColor(ImGuiCol_Text, HyperColor.Value);
ImGui::SetCursorScreenPos(wiki_part_rect_min);
imgui.text(wiki_part_text.c_str());
ImGui::PopStyleColor();
//click behavior
if (ImGui::IsMouseHoveringRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), true))
{
//underline
ImVec2 lineEnd = ImGui::GetItemRectMax();
lineEnd.y -= 2.0f;
ImVec2 lineStart = lineEnd;
lineStart.x = ImGui::GetItemRectMin().x;
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, HyperColor);
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
open_wiki();
}
}
ImGui::PopStyleColor();
}
int DailyTipsPanel::uid = 0;
DailyTipsPanel::DailyTipsPanel(bool can_expand, DailyTipsLayout layout)
: m_pos(ImVec2(0, 0)),
m_width(0),
m_height(0),
m_can_expand(can_expand),
m_layout(layout),
m_uid(DailyTipsPanel::uid++),
m_dailytips_renderer(std::make_unique<DailyTipsDataRenderer>(layout))
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
float scale = imgui.get_font_size() / 15.0f;
m_footer_height = 58.0f * scale;
m_is_expanded = wxGetApp().app_config->get("show_hints") == "true";
}
void DailyTipsPanel::set_position(const ImVec2& pos)
{
m_pos = pos;
}
void DailyTipsPanel::set_size(const ImVec2& size)
{
m_width = size.x;
m_height = size.y;
m_content_height = m_height - m_footer_height;
}
void DailyTipsPanel::set_can_expand(bool can_expand)
{
m_can_expand = can_expand;
}
ImVec2 DailyTipsPanel::get_size()
{
return ImVec2(m_width, m_height);
}
void DailyTipsPanel::render()
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
float scale = imgui.get_font_size() / 15.0f;
if (!m_first_enter) {
retrieve_data_from_hint_database(HintDataNavigation::Curr);
m_first_enter = true;
}
push_styles();
if (m_can_expand) {
if (m_is_expanded) {
m_height = m_content_height + m_footer_height;
}
else {
m_height = m_footer_height;
}
}
ImGui::SetNextWindowPos(m_pos);
ImGui::SetNextWindowSizeConstraints(ImVec2(m_width, m_height), ImVec2(m_width, m_height));
int window_flags = ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoScrollWithMouse;
if (ImGui::BeginChild((std::string("##DailyTipsPanel") + std::to_string(m_uid)).c_str(), ImVec2(m_width, m_height), false, window_flags)) {
if (m_can_expand) {
if (m_is_expanded) {
m_dailytips_renderer->render({ m_pos.x, m_pos.y }, { m_width, m_content_height });
render_controller_buttons({ m_pos.x, m_pos.y + m_height - m_footer_height }, { m_width, m_footer_height });
}
else {
render_controller_buttons({ m_pos.x, m_pos.y + m_height - m_footer_height }, { m_width, m_footer_height });
}
}
else {
m_dailytips_renderer->render({ m_pos.x, m_pos.y }, { m_width, m_content_height });
render_controller_buttons({ m_pos.x, m_pos.y + m_height - m_footer_height }, { m_width, m_footer_height });
}
//{// for debug
// ImVec2 vMin = ImGui::GetWindowContentRegionMin();
// ImVec2 vMax = ImGui::GetWindowContentRegionMax();
// vMin += ImGui::GetWindowPos();
// vMax += ImGui::GetWindowPos();
// ImGui::GetForegroundDrawList()->AddRect(vMin, vMax, IM_COL32(180, 180, 255, 255));
//}
}
ImGui::EndChild();
pop_styles();
}
void DailyTipsPanel::retrieve_data_from_hint_database(HintDataNavigation nav)
{
HintData* hint_data = HintDatabase::get_instance().get_hint(nav);
if (hint_data != nullptr)
{
DailyTipsData data{ hint_data->text,
hint_data->documentation_link,
hint_data->image_url,
hint_data->follow_text,
hint_data->hypertext,
hint_data->callback
};
m_dailytips_renderer->update_data(data);
}
}
void DailyTipsPanel::expand(bool expand)
{
if (!m_can_expand)
return;
m_is_expanded = expand;
wxGetApp().app_config->set_bool("show_hints", expand);
}
void DailyTipsPanel::collapse()
{
if (!m_can_expand)
return;
m_is_expanded = false;
wxGetApp().app_config->set_bool("show_hints", false);
}
bool DailyTipsPanel::is_expanded()
{
return m_is_expanded;
}
void DailyTipsPanel::on_change_color_mode(bool is_dark)
{
m_is_dark = is_dark;
m_dailytips_renderer->on_change_color_mode(is_dark);
}
void DailyTipsPanel::set_fade_opacity(float opacity)
{
m_fade_opacity = opacity;
m_dailytips_renderer->set_fade_opacity(opacity);
}
//void DailyTipsPanel::render_header(const ImVec2& pos, const ImVec2& size)
//{
// ImGuiWrapper& imgui = *wxGetApp().imgui();
// ImGuiWindow* parent_window = ImGui::GetCurrentWindow();
// int window_flags = parent_window->Flags;
// std::string name = "##DailyTipsPanelHeader" + std::to_string(parent_window->ID);
// ImGui::SetNextWindowPos(pos);
// if (ImGui::BeginChild(name.c_str(), size, false, window_flags)) {
// ImVec2 text_pos = pos + ImVec2(0, (size.y - ImGui::CalcTextSize("A").y) / 2);
// ImGui::SetCursorScreenPos(text_pos);
// imgui.push_bold_font();
// imgui.text(_u8L("Daily Tips"));
// imgui.pop_bold_font();
// }
// ImGui::EndChild();
//}
void DailyTipsPanel::render_controller_buttons(const ImVec2& pos, const ImVec2& size)
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
float scale = imgui.get_font_size() / 15.0f;
ImGuiWindow* parent_window = ImGui::GetCurrentWindow();
int window_flags = parent_window->Flags;
std::string name = "##DailyTipsPanelControllers" + std::to_string(parent_window->ID);
ImGui::SetNextWindowPos(pos);
if (ImGui::BeginChild(name.c_str(), size, false, window_flags)) {
ImVec2 button_size = ImVec2(38.0f, 38.0f) * scale;
float button_margin_x = 8.0f * scale;
std::wstring button_text;
// collapse / expand
ImVec2 btn_pos = pos + ImVec2(0, (size.y - ImGui::CalcTextSize("A").y) / 2);
ImGui::SetCursorScreenPos(btn_pos);
if (m_can_expand) {
if (m_is_expanded) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_Text, ImColor(144, 144, 144, (int)(255 * m_fade_opacity)).Value);
button_text = ImGui::CollapseArrowIcon;
imgui.button(_L("Collapse") + button_text);
ImVec2 collapse_btn_size = ImGui::CalcTextSize((_u8L("Collapse")).c_str());
collapse_btn_size.x += button_size.x / 2.0f;
if (ImGui::IsMouseHoveringRect(btn_pos, btn_pos + collapse_btn_size, true))
{
//underline
ImVec2 lineEnd = ImGui::GetItemRectMax();
lineEnd.x -= ImGui::CalcTextSize("A").x / 2;
lineEnd.y -= 2;
ImVec2 lineStart = lineEnd;
lineStart.x = ImGui::GetItemRectMin().x;
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, ImColor(144, 144, 144));
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
collapse();
}
ImGui::PopStyleColor(4);
}
else {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_Text, m_is_dark ? ImColor(230, 230, 230, (int)(255 * m_fade_opacity)).Value : ImColor(38, 46, 48, (int)(255 * m_fade_opacity)).Value);
// for bold font text, split text and icon-font button
imgui.push_bold_font();
imgui.button(_L("Daily Tips"));
imgui.pop_bold_font();
ImVec2 expand_btn_size = ImGui::CalcTextSize((_u8L("Daily Tips")).c_str());
ImGui::SetCursorScreenPos(ImVec2(btn_pos.x + expand_btn_size.x + ImGui::CalcTextSize(" ").x, btn_pos.y));
button_text = ImGui::ExpandArrowIcon;
imgui.button(button_text);
expand_btn_size.x += 19.0f * scale;
if (ImGui::IsMouseHoveringRect(btn_pos, btn_pos + expand_btn_size, true))
{
//underline
ImVec2 lineEnd = ImGui::GetItemRectMax();
lineEnd.x -= ImGui::CalcTextSize("A").x / 2;
lineEnd.y -= 2;
ImVec2 lineStart = lineEnd;
lineStart.x = ImGui::GetItemRectMin().x - expand_btn_size.x;
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, m_is_dark ? ImColor(230, 230, 230, (int)(255 * m_fade_opacity)) : ImColor(38, 46, 48, (int)(255 * m_fade_opacity)));
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
expand();
}
ImGui::PopStyleColor(4);
ImGui::EndChild();
return;
}
}
// page index
m_page_index = HintDatabase::get_instance().get_index() + 1;
m_pages_count = HintDatabase::get_instance().get_count();
std::string text_str = std::to_string(m_page_index) + "/" + std::to_string(m_pages_count);
float text_item_width = ImGui::CalcTextSize(text_str.c_str()).x;
ImGui::PushItemWidth(text_item_width);
float text_pos_x = (pos + size).x - button_margin_x * 2 - button_size.x * 2 - text_item_width;
float text_pos_y = pos.y + (size.y - ImGui::CalcTextSize("A").y) / 2;
ImGui::SetCursorScreenPos(ImVec2(text_pos_x, text_pos_y));
ImGui::PushStyleColor(ImGuiCol_Text, m_is_dark ? ImColor(230, 230, 230, (int)(255 * m_fade_opacity)).Value : ImColor(38, 46, 48, (int)(255 * m_fade_opacity)).Value);
imgui.text(text_str);
ImGui::PopStyleColor();
ImGui::PopItemWidth();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(.0f, .0f, .0f, .0f));
// prev button
ImColor button_text_color = m_is_dark ? ImColor(228, 228, 228, (int)(255 * m_fade_opacity)) : ImColor(38, 46, 48, (int)(255 * m_fade_opacity));
ImVec2 prev_button_pos = pos + size + ImVec2(-button_margin_x - button_size.x * 2, -size.y + (size.y - button_size.y) / 2);
ImGui::SetCursorScreenPos(prev_button_pos);
button_text = ImGui::PrevArrowBtnIcon;
if (ImGui::IsMouseHoveringRect(prev_button_pos, prev_button_pos + button_size, true))
{
button_text_color = ImColor(68, 121, 251, (int)(255 * m_fade_opacity)); // y96
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
retrieve_data_from_hint_database(HintDataNavigation::Prev);
}
ImGui::PushStyleColor(ImGuiCol_Text, button_text_color.Value);// for icon-font button
imgui.button(button_text);
ImGui::PopStyleColor();
// next button
button_text_color = m_is_dark ? ImColor(228, 228, 228, (int)(255 * m_fade_opacity)) : ImColor(38, 46, 48, (int)(255 * m_fade_opacity));
ImVec2 next_button_pos = pos + size + ImVec2(-button_size.x, -size.y + (size.y - button_size.y) / 2);
ImGui::SetCursorScreenPos(next_button_pos);
button_text = ImGui::NextArrowBtnIcon;
if (ImGui::IsMouseHoveringRect(next_button_pos, next_button_pos + button_size, true))
{
button_text_color = ImColor(68, 121, 251, (int)(255 * m_fade_opacity));
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
retrieve_data_from_hint_database(HintDataNavigation::Next);
}
ImGui::PushStyleColor(ImGuiCol_Text, button_text_color.Value);// for icon-font button
imgui.button(button_text);
ImGui::PopStyleColor();
ImGui::PopStyleColor(4);
}
ImGui::EndChild();
}
void DailyTipsPanel::push_styles()
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
float scale = imgui.get_font_size() / 15.0f;
imgui.push_common_window_style(scale);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
// framePadding cannot be zero. Otherwise, there is a problem with icon font button display
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 4.0f * scale);
ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, m_is_dark ? ImGuiWrapper::COL_WINDOW_BG_DARK : ImGuiWrapper::COL_WINDOW_BG);
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
}
void DailyTipsPanel::pop_styles()
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
imgui.pop_common_window_style();
ImGui::PopStyleVar(6);
ImGui::PopStyleColor(4);
}
DailyTipsWindow::DailyTipsWindow()
{
m_panel = new DailyTipsPanel(false, DailyTipsLayout::Vertical);
}
void DailyTipsWindow::open()
{
m_show = true;
m_panel->retrieve_data_from_hint_database(HintDataNavigation::Curr);
}
void DailyTipsWindow::close()
{
m_show = false;
}
void DailyTipsWindow::render()
{
if (!m_show)
return;
//if (m_show)
// ImGui::OpenPopup((_u8L("Daily Tips")).c_str());
ImGuiWrapper& imgui = *wxGetApp().imgui();
float scale = imgui.get_font_size() / 15.0f;
const Size& cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size();
ImVec2 center = ImVec2(cnv_size.get_width() * 0.5f, cnv_size.get_height() * 0.5f);
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
ImVec2 padding = ImVec2(25, 25) * scale;
imgui.push_menu_style(scale);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 12.f * scale);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 3) * scale);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10, 7) * scale);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, m_is_dark ? ImVec4(54 / 255.0f, 54 / 255.0f, 60 / 255.0f, 1.00f) : ImVec4(245 / 255.0f, 245 / 255.0f, 245 / 255.0f, 1.00f));
ImGui::GetCurrentContext()->DimBgRatio = 1.0f;
int windows_flag =
ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_AlwaysAutoResize
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoScrollWithMouse;
imgui.push_bold_font();
//if (ImGui::BeginPopupModal((_u8L("Daily Tips")).c_str(), NULL, windows_flag))
if (ImGui::Begin((_u8L("Daily Tips")).c_str(), &m_show, windows_flag))
{
imgui.pop_bold_font();
ImVec2 panel_pos = ImGui::GetWindowPos() + ImGui::GetWindowContentRegionMin();
ImVec2 panel_size = ImVec2(400.0f, 435.0f) * scale;
m_panel->set_position(panel_pos);
m_panel->set_size(panel_size);
m_panel->render();
if (ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_Escape))) {
m_show = false;
ImGui::CloseCurrentPopup();
}
ImGui::End();
//ImGui::EndPopup();
}
else {
imgui.pop_bold_font();
}
ImGui::PopStyleVar(4);
ImGui::PopStyleColor();
imgui.pop_menu_style();
}
void DailyTipsWindow::on_change_color_mode(bool is_dark)
{
m_is_dark = is_dark;
m_panel->on_change_color_mode(is_dark);
}
}}

View File

@@ -0,0 +1,74 @@
#ifndef slic3r_GUI_DailyTips_hpp_
#define slic3r_GUI_DailyTips_hpp_
#include "HintNotification.hpp"
//#include <wx/time.h>
#include <string>
#include <vector>
#include <memory>
namespace Slic3r { namespace GUI {
enum class DailyTipsLayout{
Horizontal,
Vertical
};
class DailyTipsDataRenderer;
class DailyTipsPanel {
static int uid;
public:
DailyTipsPanel(bool can_expand = true, DailyTipsLayout layout = DailyTipsLayout::Vertical);
void set_position(const ImVec2& pos);
void set_size(const ImVec2& size);
void set_can_expand(bool can_expand);
ImVec2 get_size();
void render();
void retrieve_data_from_hint_database(HintDataNavigation nav);
void expand(bool expand = true);
void collapse();
bool is_expanded();
void on_change_color_mode(bool is_dark);
void set_fade_opacity(float opacity);
protected:
void render_controller_buttons(const ImVec2& pos, const ImVec2& size);
void push_styles();
void pop_styles();
private:
std::unique_ptr<DailyTipsDataRenderer> m_dailytips_renderer;
size_t m_page_index{ 0 };
int m_pages_count;
bool m_is_expanded{ true };
bool m_can_expand{ true };
ImVec2 m_pos;
float m_width;
float m_height;
float m_content_height;
float m_footer_height;
int m_uid;
bool m_first_enter{ false };
bool m_is_dark{ false };
DailyTipsLayout m_layout{ DailyTipsLayout::Vertical };
float m_fade_opacity{ 1.0f };
};
class DailyTipsWindow {
public:
DailyTipsWindow();
void open();
void close();
void render();
void on_change_color_mode(bool is_dark);
private:
DailyTipsPanel* m_panel{ nullptr };
bool m_show{ false };
bool m_is_dark{ false };
};
}}
#endif

View File

@@ -0,0 +1,506 @@
#ifdef __linux__
#include "DesktopIntegrationDialog.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "format.hpp"
#include "I18N.hpp"
#include "NotificationManager.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Platform.hpp"
#include "libslic3r/Config.hpp"
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/dll/runtime_symbol_info.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <wx/filename.h>
#include <wx/stattext.h>
namespace Slic3r {
namespace GUI {
namespace {
// escaping of path string according to
// https://cgit.freedesktop.org/xdg/xdg-specs/tree/desktop-entry/desktop-entry-spec.xml
std::string escape_string(const std::string& str)
{
// The buffer needs to be bigger if escaping <,>,&
std::vector<char> out(str.size() * 4, 0);
char *outptr = out.data();
for (size_t i = 0; i < str.size(); ++ i) {
char c = str[i];
// must be escaped
if (c == '\"') { //double quote
(*outptr ++) = '\\';
(*outptr ++) = '\"';
} else if (c == '`') { // backtick character
(*outptr ++) = '\\';
(*outptr ++) = '`';
} else if (c == '$') { // dollar sign
(*outptr ++) = '\\';
(*outptr ++) = '$';
} else if (c == '\\') { // backslash character
(*outptr ++) = '\\';
(*outptr ++) = '\\';
(*outptr ++) = '\\';
(*outptr ++) = '\\';
// Reserved characters
// At Ubuntu, all these characters must NOT be escaped for desktop integration to work
/*
} else if (c == ' ') { // space
(*outptr ++) = '\\';
(*outptr ++) = ' ';
} else if (c == '\t') { // tab
(*outptr ++) = '\\';
(*outptr ++) = '\t';
} else if (c == '\n') { // newline
(*outptr ++) = '\\';
(*outptr ++) = '\n';
} else if (c == '\'') { // single quote
(*outptr ++) = '\\';
(*outptr ++) = '\'';
} else if (c == '>') { // greater-than sign
(*outptr ++) = '\\';
(*outptr ++) = '&';
(*outptr ++) = 'g';
(*outptr ++) = 't';
(*outptr ++) = ';';
} else if (c == '<') { //less-than sign
(*outptr ++) = '\\';
(*outptr ++) = '&';
(*outptr ++) = 'l';
(*outptr ++) = 't';
(*outptr ++) = ';';
} else if (c == '~') { // tilde
(*outptr ++) = '\\';
(*outptr ++) = '~';
} else if (c == '|') { // vertical bar
(*outptr ++) = '\\';
(*outptr ++) = '|';
} else if (c == '&') { // ampersand
(*outptr ++) = '\\';
(*outptr ++) = '&';
(*outptr ++) = 'a';
(*outptr ++) = 'm';
(*outptr ++) = 'p';
(*outptr ++) = ';';
} else if (c == ';') { // semicolon
(*outptr ++) = '\\';
(*outptr ++) = ';';
} else if (c == '*') { //asterisk
(*outptr ++) = '\\';
(*outptr ++) = '*';
} else if (c == '?') { // question mark
(*outptr ++) = '\\';
(*outptr ++) = '?';
} else if (c == '#') { // hash mark
(*outptr ++) = '\\';
(*outptr ++) = '#';
} else if (c == '(') { // parenthesis
(*outptr ++) = '\\';
(*outptr ++) = '(';
} else if (c == ')') {
(*outptr ++) = '\\';
(*outptr ++) = ')';
*/
} else
(*outptr ++) = c;
}
return std::string(out.data(), outptr - out.data());
}
// Disects path strings stored in system variable divided by ':' and adds into vector
void resolve_path_from_var(const std::string& var, std::vector<std::string>& paths)
{
wxString wxdirs;
if (! wxGetEnv(boost::nowide::widen(var), &wxdirs) || wxdirs.empty() )
return;
std::string dirs = boost::nowide::narrow(wxdirs);
for (size_t i = dirs.find(':'); i != std::string::npos; i = dirs.find(':'))
{
paths.push_back(dirs.substr(0, i));
if (dirs.size() > i+1)
dirs = dirs.substr(i+1);
}
paths.push_back(dirs);
}
// Return true if directory in path p+dir_name exists
bool contains_path_dir(const std::string& p, const std::string& dir_name)
{
if (p.empty() || dir_name.empty())
return false;
boost::filesystem::path path(p + (p[p.size()-1] == '/' ? "" : "/") + dir_name);
if (boost::filesystem::exists(path) && boost::filesystem::is_directory(path)) {
//BOOST_LOG_TRIVIAL(debug) << path.string() << " " << std::oct << boost::filesystem::status(path).permissions();
return true; //boost::filesystem::status(path).permissions() & boost::filesystem::owner_write;
} else
BOOST_LOG_TRIVIAL(debug) << path.string() << " doesnt exists";
return false;
}
// Creates directory in path if not exists yet
void create_dir(const boost::filesystem::path& path)
{
if (boost::filesystem::exists(path))
return;
BOOST_LOG_TRIVIAL(debug)<< "creating " << path.string();
boost::system::error_code ec;
boost::filesystem::create_directory(path, ec);
if (ec)
BOOST_LOG_TRIVIAL(error)<< "create directory failed: " << ec.message();
}
// Starts at basic_path (excluded) and creates all directories in dir_path
void create_path(const std::string& basic_path, const std::string& dir_path)
{
if (basic_path.empty() || dir_path.empty())
return;
boost::filesystem::path path(basic_path);
std::string dirs = dir_path;
for (size_t i = dirs.find('/'); i != std::string::npos; i = dirs.find('/'))
{
std::string dir = dirs.substr(0, i);
path = boost::filesystem::path(path.string() +"/"+ dir);
create_dir(path);
dirs = dirs.substr(i+1);
}
path = boost::filesystem::path(path.string() +"/"+ dirs);
create_dir(path);
}
// Calls our internal copy_file function to copy file at icon_path to dest_path
bool copy_icon(const std::string& icon_path, const std::string& dest_path)
{
BOOST_LOG_TRIVIAL(debug) <<"icon from "<< icon_path;
BOOST_LOG_TRIVIAL(debug) <<"icon to "<< dest_path;
std::string error_message;
auto cfr = copy_file(icon_path, dest_path, error_message, false);
if (cfr) {
BOOST_LOG_TRIVIAL(debug) << "Copy icon fail(" << cfr << "): " << error_message;
return false;
}
BOOST_LOG_TRIVIAL(debug) << "Copy icon success.";
return true;
}
// Creates new file filled with data.
bool create_desktop_file(const std::string& path, const std::string& data)
{
BOOST_LOG_TRIVIAL(debug) <<".desktop to "<< path;
std::ofstream output(path);
output << data;
struct stat buffer;
if (stat(path.c_str(), &buffer) == 0)
{
BOOST_LOG_TRIVIAL(debug) << "Desktop file created.";
return true;
}
BOOST_LOG_TRIVIAL(debug) << "Desktop file NOT created.";
return false;
}
} // namespace integratec_desktop_internal
// methods that actually do / undo desktop integration. Static to be accesible from anywhere.
bool DesktopIntegrationDialog::is_integrated()
{
const AppConfig *app_config = wxGetApp().app_config;
std::string path(app_config->get("desktop_integration_app_path"));
BOOST_LOG_TRIVIAL(debug) << "Desktop integration desktop file path: " << path;
if (path.empty())
return false;
// confirmation that QIDIStudio.desktop exists
struct stat buffer;
return (stat (path.c_str(), &buffer) == 0);
}
bool DesktopIntegrationDialog::integration_possible()
{
return true;
}
void DesktopIntegrationDialog::perform_desktop_integration()
{
BOOST_LOG_TRIVIAL(debug) << "performing desktop integration";
// Path to appimage
const char *appimage_env = std::getenv("APPIMAGE");
std::string excutable_path;
if (appimage_env) {
try {
excutable_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
} catch (std::exception &) {
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - boost::filesystem::canonical did not return appimage path.";
show_error(nullptr, _L("Performing desktop integration failed - boost::filesystem::canonical did not return appimage path."));
return;
}
} else {
// not appimage - find executable
excutable_path = boost::dll::program_location().string();
//excutable_path = wxStandardPaths::Get().GetExecutablePath().string();
BOOST_LOG_TRIVIAL(debug) << "non-appimage path to executable: " << excutable_path;
if (excutable_path.empty())
{
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - no executable found.";
show_error(nullptr, _L("Performing desktop integration failed - Could not find executable."));
return;
}
}
// Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path'
//boost::replace_all(excutable_path, "'", "'\\''");
excutable_path = escape_string(excutable_path);
// Find directories icons and applications
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition to the $XDG_DATA_HOME base directory.
// The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
std::vector<std::string>target_candidates;
resolve_path_from_var("XDG_DATA_HOME", target_candidates);
resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
AppConfig *app_config = wxGetApp().app_config;
// suffix string to create different desktop file for alpha, beta.
std::string version_suffix;
std::string name_suffix;
std::string version(SLIC3R_VERSION);
if (version.find("alpha") != std::string::npos)
{
version_suffix = "-alpha";
name_suffix = " - alpha";
}else if (version.find("beta") != std::string::npos)
{
version_suffix = "-beta";
name_suffix = " - beta";
}
// theme path to icon destination
std::string icon_theme_path;
std::string icon_theme_dirs;
if (platform_flavor() == PlatformFlavor::LinuxOnChromium) {
icon_theme_path = "hicolor/96x96/apps/";
icon_theme_dirs = "/hicolor/96x96/apps";
}
std::string target_dir_icons;
std::string target_dir_desktop;
// slicer icon
// iterate thru target_candidates to find icons folder
for (size_t i = 0; i < target_candidates.size(); ++i) {
// Copy icon QIDIStudio.png from resources_dir()/icons to target_dir_icons/icons/
if (contains_path_dir(target_candidates[i], "images")) {
target_dir_icons = target_candidates[i];
std::string icon_path = GUI::format("%1%/images/QIDIStudio.png",resources_dir());
std::string dest_path = GUI::format("%1%/images/%2%QIDIStudio%3%.png", target_dir_icons, icon_theme_path, version_suffix);
if (copy_icon(icon_path, dest_path))
break; // success
else
target_dir_icons.clear(); // copying failed
// if all failed - try creating default home folder
if (i == target_candidates.size() - 1) {
// create $HOME/.local/share
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs);
// copy icon
target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
std::string icon_path = GUI::format("%1%/images/QIDIStudio.png",resources_dir());
std::string dest_path = GUI::format("%1%/images/%2%QIDIStudio%3%.png", target_dir_icons, icon_theme_path, version_suffix);
if (!contains_path_dir(target_dir_icons, "images")
|| !copy_icon(icon_path, dest_path)) {
// every attempt failed - icon wont be present
target_dir_icons.clear();
}
}
}
}
if(target_dir_icons.empty()) {
BOOST_LOG_TRIVIAL(error) << "Copying QIDIStudio icon to icons directory failed.";
} else
// save path to icon
app_config->set("desktop_integration_icon_slicer_path", GUI::format("%1%/images/%2%QIDIStudio%3%.png", target_dir_icons, icon_theme_path, version_suffix));
// desktop file
// iterate thru target_candidates to find applications folder
for (size_t i = 0; i < target_candidates.size(); ++i)
{
if (contains_path_dir(target_candidates[i], "applications")) {
target_dir_desktop = target_candidates[i];
// Write slicer desktop file
std::string desktop_file = GUI::format(
"[Desktop Entry]\n"
"Name=QIDIStudio%1%\n"
"GenericName=3D Printing Software\n"
"Icon=QIDIStudio%2%\n"
"Exec=\"%3%\" %%F\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n"
"Categories=Graphics;3DGraphics;Engineering;\n"
"Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n"
"StartupNotify=false\n"
"StartupWMClass=qidi-studio\n", name_suffix, version_suffix, excutable_path);
std::string path = GUI::format("%1%/applications/QIDIStudio%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(path, desktop_file)){
BOOST_LOG_TRIVIAL(debug) << "QIDIStudio.desktop file installation success.";
break;
} else {
// write failed - try another path
BOOST_LOG_TRIVIAL(debug) << "Attempt to QIDIStudio.desktop file installation failed. failed path: " << target_candidates[i];
target_dir_desktop.clear();
}
// if all failed - try creating default home folder
if (i == target_candidates.size() - 1) {
// create $HOME/.local/share
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
// create desktop file
target_dir_desktop = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
std::string path = GUI::format("%1%/applications/QIDIStudio%2%.desktop", target_dir_desktop, version_suffix);
if (contains_path_dir(target_dir_desktop, "applications")) {
if (!create_desktop_file(path, desktop_file)) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create desktop file";
return;
}
} else {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found.";
return;
}
}
}
}
if(target_dir_desktop.empty()) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found.";
show_error(nullptr, _L("Performing desktop integration failed because the application directory was not found."));
return;
}
// save path to desktop file
app_config->set("desktop_integration_app_path", GUI::format("%1%/applications/QIDIStudio%2%.desktop", target_dir_desktop, version_suffix));
// Repeat for Gcode viewer - use same paths as for slicer files
// Do NOT add gcode viewer desktop file on ChromeOS
if (platform_flavor() != PlatformFlavor::LinuxOnChromium) {
// Icon
if (!target_dir_icons.empty())
{
std::string icon_path = GUI::format("%1%/images/QIDIStudio-gcodeviewer_192px.png",resources_dir());
std::string dest_path = GUI::format("%1%/images/%2%QIDIStudio-gcodeviewer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
if (copy_icon(icon_path, dest_path))
// save path to icon
app_config->set("desktop_integration_icon_viewer_path", dest_path);
else
BOOST_LOG_TRIVIAL(error) << "Copying Gcode Viewer icon to icons directory failed.";
}
// Desktop file
std::string desktop_file = GUI::format(
"[Desktop Entry]\n"
"Name=QIDI Gcode Viewer%1%\n"
"GenericName=3D Printing Software\n"
"Icon=QIDIStudio-gcodeviewer%2%\n"
"Exec=\"%3%\" --gcodeviewer %%F\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=text/x.gcode;\n"
"Categories=Graphics;3DGraphics;\n"
"Keywords=3D;Printing;Slicer;\n"
"StartupNotify=false\n", name_suffix, version_suffix, excutable_path);
std::string desktop_path = GUI::format("%1%/applications/QIDIStudioGcodeViewer%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(desktop_path, desktop_file))
// save path to desktop file
app_config->set("desktop_integration_app_viewer_path", desktop_path);
else {
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create Gcodeviewer desktop file";
show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. QIDIStudio desktop file was probably created successfully."));
}
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
}
void DesktopIntegrationDialog::undo_desktop_intgration()
{
const AppConfig *app_config = wxGetApp().app_config;
// slicer .desktop
std::string path = std::string(app_config->get("desktop_integration_app_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
// slicer icon
path = std::string(app_config->get("desktop_integration_icon_slicer_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
// No gcode viewer at ChromeOS
if (platform_flavor() != PlatformFlavor::LinuxOnChromium) {
// gcode viewer .desktop
path = std::string(app_config->get("desktop_integration_app_viewer_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
// gcode viewer icon
path = std::string(app_config->get("desktop_integration_icon_viewer_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess);
}
DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
: wxDialog(parent, wxID_ANY, _(L("Desktop Integration")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
{
bool can_undo = DesktopIntegrationDialog::is_integrated();
wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
wxString text = _L("Desktop Integration sets this binary to be searchable by the system.\n\nPress \"Perform\" to proceed.");
if (can_undo)
text += "\nPress \"Undo\" to remove previous integration.";
vbox->Add(
new wxStaticText( this, wxID_ANY, text),
// , wxDefaultPosition, wxSize(100,50), wxTE_MULTILINE),
1, // make vertically stretchable
wxEXPAND | // make horizontally stretchable
wxALL, // and make border all around
10 ); // set border width to 10
wxBoxSizer *btn_szr = new wxBoxSizer(wxHORIZONTAL);
wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Perform"));
btn_szr->Add(btn_perform, 0, wxALL, 10);
btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(); EndModal(wxID_ANY); });
if (can_undo){
wxButton *btn_undo = new wxButton(this, wxID_ANY, _L("Undo"));
btn_szr->Add(btn_undo, 0, wxALL, 10);
btn_undo->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::undo_desktop_intgration(); EndModal(wxID_ANY); });
}
wxButton *btn_cancel = new wxButton(this, wxID_ANY, _L("Cancel"));
btn_szr->Add(btn_cancel, 0, wxALL, 10);
btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { EndModal(wxID_ANY); });
vbox->Add(btn_szr, 0, wxALIGN_CENTER);
SetSizerAndFit(vbox);
}
DesktopIntegrationDialog::~DesktopIntegrationDialog()
{
}
} // namespace GUI
} // namespace Slic3r
#endif // __linux__

View File

@@ -0,0 +1,39 @@
#ifdef __linux__
#ifndef slic3r_DesktopIntegrationDialog_hpp_
#define slic3r_DesktopIntegrationDialog_hpp_
#include <wx/dialog.h>
namespace Slic3r {
namespace GUI {
class DesktopIntegrationDialog : public wxDialog
{
public:
DesktopIntegrationDialog(wxWindow *parent);
DesktopIntegrationDialog(DesktopIntegrationDialog &&) = delete;
DesktopIntegrationDialog(const DesktopIntegrationDialog &) = delete;
DesktopIntegrationDialog &operator=(DesktopIntegrationDialog &&) = delete;
DesktopIntegrationDialog &operator=(const DesktopIntegrationDialog &) = delete;
~DesktopIntegrationDialog();
// methods that actually do / undo desktop integration. Static to be accesible from anywhere.
// returns true if path to QIDIStudio.desktop is stored in App Config and existence of desktop file.
// Does not check if desktop file leads to this binary or existence of icons and viewer desktop file.
static bool is_integrated();
// true if appimage
static bool integration_possible();
// Creates Desktop files and icons for both PrusaSlicer and GcodeViewer.
// Stores paths into App Config.
// Rewrites if files already existed.
static void perform_desktop_integration();
// Deletes Desktop files and icons for both PrusaSlicer and GcodeViewer at paths stored in App Config.
static void undo_desktop_intgration();
private:
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_DesktopIntegrationDialog_hpp_
#endif // __linux__

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,215 @@
#include "DownloadProgressDialog.hpp"
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/statbmp.h>
#include <wx/scrolwin.h>
#include <wx/clipbrd.h>
#include <wx/checkbox.h>
#include <wx/html/htmlwin.h>
#include <boost/algorithm/string/replace.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
//#include "ConfigWizard.hpp"
#include "wxExtensions.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "GUI_App.hpp"
#define DESIGN_INPUT_SIZE wxSize(FromDIP(100), -1)
namespace Slic3r {
namespace GUI {
DownloadProgressDialog::DownloadProgressDialog(wxString title)
: DPIDialog(static_cast<wxWindow *>(wxGetApp().mainframe), wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
{
wxString download_failed_url = wxT("https://wiki.qidilab.com/en/software/qidi-studio/failed-to-get-network-plugin");
wxString install_failed_url = wxT("https://wiki.qidilab.com/en/software/qidi-studio/failed-to-get-network-plugin");
wxString download_failed_msg = _L("Failed to download the plug-in. Please check your firewall settings and vpn software, check and retry.");
wxString install_failed_msg = _L("Failed to install the plug-in. Please check whether it is blocked or deleted by anti-virus software.");
std::string icon_path = (boost::format("%1%/images/QIDIStudioTitle.ico") % resources_dir()).str();
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
SetBackgroundColour(*wxWHITE);
wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL);
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
m_sizer_main->Add(m_line_top, 0, wxEXPAND, 0);
m_simplebook_status = new wxSimplebook(this);
m_simplebook_status->SetSize(wxSize(FromDIP(400), -1));
m_simplebook_status->SetMinSize(wxSize(FromDIP(400), -1));
m_simplebook_status->SetMaxSize(wxSize(FromDIP(400), -1));
//mode normal
m_status_bar = std::make_shared<QDTStatusBarSend>(m_simplebook_status);
m_panel_download = m_status_bar->get_panel();
m_panel_download->SetSize(wxSize(FromDIP(400), FromDIP(70)));
m_panel_download->SetMinSize(wxSize(FromDIP(400), FromDIP(70)));
m_panel_download->SetMaxSize(wxSize(FromDIP(400), FromDIP(70)));
//mode Download Failed
auto m_panel_download_failed = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
wxBoxSizer* sizer_download_failed = new wxBoxSizer(wxVERTICAL);
auto m_statictext_download_failed = new wxStaticText(m_panel_download_failed, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
m_statictext_download_failed->SetForegroundColour(*wxBLACK);
m_statictext_download_failed->SetLabel(format_text(m_statictext_download_failed, download_failed_msg, FromDIP(360)));
m_statictext_download_failed->Wrap(FromDIP(360));
sizer_download_failed->Add(m_statictext_download_failed, 0, wxALIGN_CENTER | wxALL, 5);
auto m_download_hyperlink = new wxHyperlinkCtrl(m_panel_download_failed, wxID_ANY, _L("click here to see more info"), download_failed_url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE);
sizer_download_failed->Add(m_download_hyperlink, 0, wxALIGN_CENTER | wxALL, 5);
m_panel_download_failed->SetSizer(sizer_download_failed);
m_panel_download_failed->Layout();
sizer_download_failed->Fit(m_panel_download_failed);
//mode Installed failed
auto m_panel_install_failed = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
wxBoxSizer* sizer_install_failed = new wxBoxSizer(wxVERTICAL);
auto m_statictext_install_failed = new wxStaticText(m_panel_install_failed, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
m_statictext_install_failed->SetForegroundColour(*wxBLACK);
m_statictext_install_failed->SetLabel(format_text(m_statictext_install_failed, install_failed_msg,FromDIP(360)));
m_statictext_install_failed->Wrap(FromDIP(360));
sizer_install_failed->Add(m_statictext_install_failed, 0, wxALIGN_CENTER | wxALL, 5);
auto m_install_hyperlink = new wxHyperlinkCtrl(m_panel_install_failed, wxID_ANY, _L("click here to see more info"), install_failed_url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE);
sizer_install_failed->Add(m_install_hyperlink, 0, wxALIGN_CENTER | wxALL, 5);
m_panel_install_failed->SetSizer(sizer_install_failed);
m_panel_install_failed->Layout();
sizer_install_failed->Fit(m_panel_install_failed);
m_sizer_main->Add(m_simplebook_status, 0, wxALL, FromDIP(20));
m_sizer_main->Add(0, 0, 1, wxBOTTOM, 10);
m_simplebook_status->AddPage(m_status_bar->get_panel(), wxEmptyString, true);
m_simplebook_status->AddPage(m_panel_download_failed, wxEmptyString, false);
m_simplebook_status->AddPage(m_panel_install_failed, wxEmptyString, false);
SetSizer(m_sizer_main);
Layout();
Fit();
CentreOnParent();
Bind(wxEVT_CLOSE_WINDOW, &DownloadProgressDialog::on_close, this);
wxGetApp().UpdateDlgDarkUI(this);
}
wxString DownloadProgressDialog::format_text(wxStaticText* st, wxString str, int warp)
{
if (wxGetApp().app_config->get("language") != "zh_CN") { return str; }
wxString out_txt = str;
wxString count_txt = "";
int new_line_pos = 0;
for (int i = 0; i < str.length(); i++) {
auto text_size = st->GetTextExtent(count_txt);
if (text_size.x < warp) {
count_txt += str[i];
}
else {
out_txt.insert(i - 1, '\n');
count_txt = "";
}
}
return out_txt;
}
bool DownloadProgressDialog::Show(bool show)
{
if (show) {
m_simplebook_status->SetSelection(0);
m_upgrade_job = make_job(m_status_bar);
m_upgrade_job->set_event_handle(this);
m_status_bar->set_progress(0);
Bind(EVT_UPGRADE_NETWORK_SUCCESS, [this](wxCommandEvent& evt) {
m_status_bar->change_button_label(_L("Close"));
on_finish();
m_status_bar->set_cancel_callback_fina(
[this]() {
this->Close();
}
);
});
//download failed
Bind(EVT_DOWNLOAD_NETWORK_FAILED, [this](wxCommandEvent& evt) {
m_status_bar->change_button_label(_L("Close"));
m_status_bar->set_progress(0);
this->m_simplebook_status->SetSelection(1);
m_status_bar->set_cancel_callback_fina(
[this]() {
this->Close();
}
);
});
//install failed
Bind(EVT_INSTALL_NETWORK_FAILED, [this](wxCommandEvent& evt) {
m_status_bar->change_button_label(_L("Close"));
m_status_bar->set_progress(0);
this->m_simplebook_status->SetSelection(2);
m_status_bar->set_cancel_callback_fina(
[this]() {
this->Close();
}
);
});
m_status_bar->set_cancel_callback_fina([this]() {
if (m_upgrade_job) {
m_upgrade_job->cancel();
//EndModal(wxID_CLOSE);
}
});
m_upgrade_job->start();
}
return DPIDialog::Show(show);
}
void DownloadProgressDialog::on_close(wxCloseEvent& event)
{
if (m_upgrade_job) {
m_upgrade_job->cancel();
m_upgrade_job->join();
}
event.Skip();
}
DownloadProgressDialog::~DownloadProgressDialog() {}
void DownloadProgressDialog::on_dpi_changed(const wxRect &suggested_rect) {}
void DownloadProgressDialog::update_release_note(std::string release_note, std::string version) {}
std::shared_ptr<UpgradeNetworkJob> DownloadProgressDialog::make_job(std::shared_ptr<ProgressIndicator> pri) { return std::make_shared<UpgradeNetworkJob>(pri); }
void DownloadProgressDialog::on_finish() { wxGetApp().restart_networking(); }
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,62 @@
#ifndef slic3r_DownloadProgressDialog_hpp_
#define slic3r_DownloadProgressDialog_hpp_
#include <string>
#include <unordered_map>
#include "GUI_Utils.hpp"
#include <wx/dialog.h>
#include <wx/font.h>
#include <wx/bitmap.h>
#include <wx/msgdlg.h>
#include <wx/richmsgdlg.h>
#include <wx/textctrl.h>
#include <wx/statline.h>
#include <wx/simplebook.h>
#include "Widgets/Button.hpp"
#include "QDTStatusBar.hpp"
#include "QDTStatusBarSend.hpp"
#include "Jobs/UpgradeNetworkJob.hpp"
class wxBoxSizer;
class wxCheckBox;
class wxStaticBitmap;
#define MSG_DIALOG_BUTTON_SIZE wxSize(FromDIP(58), FromDIP(24))
#define MSG_DIALOG_MIDDLE_BUTTON_SIZE wxSize(FromDIP(76), FromDIP(24))
#define MSG_DIALOG_LONG_BUTTON_SIZE wxSize(FromDIP(90), FromDIP(24))
namespace Slic3r {
namespace GUI {
class DownloadProgressDialog : public DPIDialog
{
protected:
bool Show(bool show) override;
void on_close(wxCloseEvent& event);
public:
DownloadProgressDialog(wxString title);
wxString format_text(wxStaticText* st, wxString str, int warp);
~DownloadProgressDialog();
void on_dpi_changed(const wxRect &suggested_rect) override;
void update_release_note(std::string release_note, std::string version);
wxSimplebook* m_simplebook_status{nullptr};
std::shared_ptr<QDTStatusBarSend> m_status_bar;
std::shared_ptr<UpgradeNetworkJob> m_upgrade_job { nullptr };
wxPanel * m_panel_download;
protected:
virtual std::shared_ptr<UpgradeNetworkJob> make_job(std::shared_ptr<ProgressIndicator> pri);
virtual void on_finish();
};
}
}
#endif

View File

@@ -0,0 +1,250 @@
#include "DragCanvas.hpp"
#include "wxExtensions.hpp"
#include "GUI_App.hpp"
namespace Slic3r { namespace GUI {
#define CANVAS_WIDTH FromDIP(240)
#define SHAPE_SIZE FromDIP(20)
#define SHAPE_GAP (2 * SHAPE_SIZE)
#define LINE_HEIGHT (SHAPE_SIZE + FromDIP(5))
static const wxColour CANVAS_BORDER_COLOR = wxColour(0xCECECE);
DragCanvas::DragCanvas(wxWindow* parent, const std::vector<std::string>& colors, const std::vector<int>& order)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
, m_drag_mode(DragMode::NONE)
, m_max_shape_pos(wxPoint(0, 0))
{
SetBackgroundColour(*wxWHITE);
m_arrow_bmp = create_scaled_bitmap("plate_settings_arrow", this, 16);
set_shape_list(colors, order);
Bind(wxEVT_PAINT, &DragCanvas::on_paint, this);
Bind(wxEVT_ERASE_BACKGROUND, &DragCanvas::on_erase, this);
Bind(wxEVT_LEFT_DOWN, &DragCanvas::on_mouse, this);
Bind(wxEVT_LEFT_UP, &DragCanvas::on_mouse, this);
Bind(wxEVT_MOTION, &DragCanvas::on_mouse, this);
Bind(wxEVT_ENTER_WINDOW, &DragCanvas::on_mouse, this);
Bind(wxEVT_LEAVE_WINDOW, &DragCanvas::on_mouse, this);
}
DragCanvas::~DragCanvas()
{
for (int i = 0; i < m_dragshape_list.size(); i++) {
delete m_dragshape_list[i];
}
m_dragshape_list.clear();
if (m_drag_image)
delete m_drag_image;
}
void DragCanvas::set_shape_list(const std::vector<std::string>& colors, const std::vector<int>& order)
{
m_dragshape_list.clear();
for (int i = 0; i < order.size(); i++) {
wxBitmap* bmp = get_extruder_color_icon(colors[order[i] - 1], std::to_string(order[i]), SHAPE_SIZE, SHAPE_SIZE);
DragShape* shape = new DragShape(*bmp, order[i]);
m_dragshape_list.push_back(shape);
}
// wrapping lines
for (int i = 0; i < order.size(); i++) {
int shape_pos_x = FromDIP(10) + i * SHAPE_GAP;
int shape_pos_y = FromDIP(5);
while (shape_pos_x + SHAPE_SIZE > CANVAS_WIDTH) {
shape_pos_x -= CANVAS_WIDTH;
shape_pos_y += LINE_HEIGHT;
int row = shape_pos_y / LINE_HEIGHT + 1;
if (row > 1) {
if (row % 2 == 0) {
shape_pos_x += (SHAPE_GAP - SHAPE_SIZE);
}
else {
shape_pos_x -= (SHAPE_GAP - SHAPE_SIZE);
shape_pos_x += SHAPE_GAP;
}
}
}
m_max_shape_pos.x = std::max(m_max_shape_pos.x, shape_pos_x);
m_max_shape_pos.y = std::max(m_max_shape_pos.y, shape_pos_y);
m_dragshape_list[i]->SetPosition(wxPoint(shape_pos_x, shape_pos_y));
}
int rows = m_max_shape_pos.y / LINE_HEIGHT + 1;
SetMinSize(wxSize(CANVAS_WIDTH, LINE_HEIGHT * rows + FromDIP(5)));
}
std::vector<int> DragCanvas::get_shape_list_order()
{
std::vector<int> res;
std::vector<DragShape*> ordered_list = get_ordered_shape_list();
res.reserve(ordered_list.size());
for (auto& item : ordered_list) {
res.push_back(item->get_index());
}
return res;
}
std::vector<DragShape*> DragCanvas::get_ordered_shape_list()
{
std::vector<DragShape*> ordered_list = m_dragshape_list;
std::sort(ordered_list.begin(), ordered_list.end(), [](const DragShape* l, const DragShape* r) {
if (l->GetPosition().y < r->GetPosition().y)
return true;
else if (l->GetPosition().y == r->GetPosition().y) {
return l->GetPosition().x < r->GetPosition().x;
}
else {
return false;
}
});
return ordered_list;
}
void DragCanvas::on_paint(wxPaintEvent& event)
{
wxPaintDC dc(this);
for (int i = 0; i < m_dragshape_list.size(); i++) {
m_dragshape_list[i]->paint(dc, m_dragshape_list[i] == m_slot_shape);
auto arrow_pos = m_dragshape_list[i]->GetPosition() - wxSize(SHAPE_GAP - SHAPE_SIZE, 0);
if (arrow_pos.x < 0) {
arrow_pos.x = m_max_shape_pos.x;
arrow_pos.y -= LINE_HEIGHT;
}
arrow_pos += wxSize((SHAPE_GAP - SHAPE_SIZE - m_arrow_bmp.GetWidth() / dc.GetContentScaleFactor()) / 2, (SHAPE_SIZE - m_arrow_bmp.GetHeight() / dc.GetContentScaleFactor()) / 2);
dc.DrawBitmap(m_arrow_bmp, arrow_pos);
}
}
void DragCanvas::on_erase(wxEraseEvent& event)
{
wxSize size = GetSize();
if (event.GetDC())
{
auto& dc = *(event.GetDC());
dc.SetPen(CANVAS_BORDER_COLOR);
dc.SetBrush(*wxWHITE_BRUSH);
dc.DrawRectangle({ 0,0 }, size);
}
else
{
wxClientDC dc(this);
dc.SetPen(CANVAS_BORDER_COLOR);
dc.SetBrush(*wxWHITE_BRUSH);
dc.DrawRectangle({ 0,0 }, size);
}
}
void DragCanvas::on_mouse(wxMouseEvent& event)
{
if (event.LeftDown())
{
DragShape* shape = find_shape(event.GetPosition());
if (shape)
{
m_drag_mode = DragMode::DRAGGING;
m_drag_start_pos = event.GetPosition();
m_dragging_shape = shape;
if (m_drag_image) {
delete m_drag_image;
m_drag_image = nullptr;
}
m_drag_image = new wxDragImage(m_dragging_shape->GetBitmap());
wxPoint offset = m_drag_start_pos - m_dragging_shape->GetPosition();
bool success = m_drag_image->BeginDrag(offset, this);
if (!success)
{
delete m_drag_image;
m_drag_image = nullptr;
m_drag_mode = DragMode::NONE;
}
}
}
else if (event.Dragging() && m_drag_mode == DragMode::DRAGGING)
{
DragShape* shape = find_shape(event.GetPosition());
if (shape) {
if (shape != m_dragging_shape) {
m_slot_shape = shape;
Refresh();
Update();
}
}
else {
if (m_slot_shape) {
m_slot_shape = nullptr;
Refresh();
Update();
}
}
m_drag_image->Move(event.GetPosition());
m_drag_image->Show();
}
else if (event.LeftUp() && m_drag_mode != DragMode::NONE)
{
m_drag_mode = DragMode::NONE;
if (m_drag_image) {
m_drag_image->Hide();
m_drag_image->EndDrag();
// swap position
if (m_slot_shape && m_dragging_shape) {
auto highlighted_pos = m_slot_shape->GetPosition();
m_slot_shape->SetPosition(m_dragging_shape->GetPosition());
m_dragging_shape->SetPosition(highlighted_pos);
m_slot_shape = nullptr;
m_dragging_shape = nullptr;
}
}
Refresh();
Update();
}
}
DragShape* DragCanvas::find_shape(const wxPoint& pt) const
{
for (auto& shape : m_dragshape_list) {
if (shape->hit_test(pt))
return shape;
}
return nullptr;
}
DragShape::DragShape(const wxBitmap& bitmap, int index)
: m_bitmap(bitmap)
, m_pos(wxPoint(0,0))
, m_index(index)
{
}
bool DragShape::hit_test(const wxPoint& pt) const
{
wxRect rect(wxRect(m_pos.x, m_pos.y, m_bitmap.GetWidth(), m_bitmap.GetHeight()));
return rect.Contains(pt.x, pt.y);
}
void DragShape::paint(wxDC& dc, bool highlight)
{
dc.DrawBitmap(m_bitmap, m_pos);
if (highlight)
{
dc.SetPen(*wxWHITE_PEN);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(m_pos.x, m_pos.y, m_bitmap.GetWidth(), m_bitmap.GetHeight());
}
}
}}

View File

@@ -0,0 +1,65 @@
#ifndef slic3r_GUI_DragCanvas_hpp_
#define slic3r_GUI_DragCanvas_hpp_
#include "wx/bitmap.h"
#include "wx/dragimag.h"
namespace Slic3r { namespace GUI {
class DragShape : public wxObject
{
public:
DragShape(const wxBitmap& bitmap, int index);
~DragShape() {}
wxPoint GetPosition() const { return m_pos; }
void SetPosition(const wxPoint& pos) { m_pos = pos; }
const wxBitmap& GetBitmap() const { return m_bitmap; }
void SetBitmap(const wxBitmap& bitmap) { m_bitmap = bitmap; }
int get_index() { return m_index; }
bool hit_test(const wxPoint& pt) const;
void paint(wxDC& dc, bool highlight = false);
protected:
wxPoint m_pos;
wxBitmap m_bitmap;
int m_index;
};
enum class DragMode {
NONE,
DRAGGING,
};
class DragCanvas : public wxPanel
{
public:
DragCanvas(wxWindow* parent, const std::vector<std::string>& colors, const std::vector<int>& order);
~DragCanvas();
void set_shape_list(const std::vector<std::string>& colors, const std::vector<int>& order);
std::vector<int> get_shape_list_order();
std::vector<DragShape*> get_ordered_shape_list();
protected:
void on_paint(wxPaintEvent& event);
void on_erase(wxEraseEvent& event);
void on_mouse(wxMouseEvent& event);
DragShape* find_shape(const wxPoint& pt) const;
private:
std::vector<DragShape*> m_dragshape_list;
DragMode m_drag_mode;
DragShape* m_dragging_shape{ nullptr };
DragShape* m_slot_shape{ nullptr }; // The shape that's being highlighted
wxDragImage* m_drag_image{ nullptr };
wxPoint m_drag_start_pos;
wxBitmap m_arrow_bmp;
wxPoint m_max_shape_pos;
};
}}
#endif

92
src/slic3r/GUI/Event.hpp Normal file
View File

@@ -0,0 +1,92 @@
#ifndef slic3r_Events_hpp_
#define slic3r_Events_hpp_
#include <array>
#include <wx/event.h>
namespace Slic3r {
namespace GUI {
struct SimpleEvent : public wxEvent
{
SimpleEvent(wxEventType type, wxObject* origin = nullptr) : wxEvent(0, type)
{
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
SetEventObject(origin);
}
virtual wxEvent* Clone() const
{
return new SimpleEvent(GetEventType(), GetEventObject());
}
};
struct IntEvent : public wxEvent
{
public:
IntEvent(wxEventType type, int data, wxObject* origin = nullptr) : wxEvent(0, type)
{
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
SetEventObject(origin);
m_data = data;
}
virtual wxEvent* Clone() const
{
return new IntEvent(GetEventType(), m_data, GetEventObject());
}
int get_data() { return m_data; }
private:
int m_data;
};
template<class T, size_t N> struct ArrayEvent : public wxEvent
{
std::array<T, N> data;
ArrayEvent(wxEventType type, std::array<T, N> data, wxObject* origin = nullptr)
: wxEvent(0, type), data(std::move(data))
{
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
SetEventObject(origin);
}
virtual wxEvent* Clone() const
{
return new ArrayEvent<T, N>(GetEventType(), data, GetEventObject());
}
};
template<class T> struct Event : public wxEvent
{
T data;
Event(wxEventType type, const T &data, wxObject* origin = nullptr)
: wxEvent(0, type), data(std::move(data))
{
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
SetEventObject(origin);
}
Event(wxEventType type, T&& data, wxObject* origin = nullptr)
: wxEvent(0, type), data(std::move(data))
{
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
SetEventObject(origin);
}
virtual wxEvent* Clone() const
{
return new Event<T>(GetEventType(), data, GetEventObject());
}
};
}
}
#endif // slic3r_Events_hpp_

View File

@@ -0,0 +1,395 @@
#include "ExtraRenderers.hpp"
#include "wxExtensions.hpp"
#include "GUI.hpp"
#include "BitmapComboBox.hpp"
#include "Plater.hpp"
#include "Widgets/ComboBox.hpp"
#include <wx/dc.h>
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
#include "wx/generic/private/markuptext.h"
#include "wx/generic/private/rowheightcache.h"
#include "wx/generic/private/widthcalc.h"
#endif
/*
#ifdef __WXGTK__
#include "wx/gtk/private.h"
#include "wx/gtk/private/value.h"
#endif
*/
#if wxUSE_ACCESSIBILITY
#include "wx/private/markupparser.h"
#endif // wxUSE_ACCESSIBILITY
using Slic3r::GUI::from_u8;
using Slic3r::GUI::into_u8;
//-----------------------------------------------------------------------------
// DataViewBitmapText
//-----------------------------------------------------------------------------
wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject)
IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText)
// ---------------------------------------------------------
// BitmapTextRenderer
// ---------------------------------------------------------
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
BitmapTextRenderer::BitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/,
int align /*= wxDVR_DEFAULT_ALIGNMENT*/):
wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align)
{
SetMode(mode);
SetAlignment(align);
}
#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
BitmapTextRenderer::~BitmapTextRenderer()
{
#ifdef SUPPORTS_MARKUP
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
delete m_markupText;
#endif //wxHAS_GENERIC_DATAVIEWCTRL
#endif // SUPPORTS_MARKUP
}
void BitmapTextRenderer::EnableMarkup(bool enable)
{
#ifdef SUPPORTS_MARKUP
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
if (enable) {
if (!m_markupText)
m_markupText = new wxItemMarkupText(wxString());
}
else {
if (m_markupText) {
delete m_markupText;
m_markupText = nullptr;
}
}
#else
is_markupText = enable;
#endif //wxHAS_GENERIC_DATAVIEWCTRL
#endif // SUPPORTS_MARKUP
}
bool BitmapTextRenderer::SetValue(const wxVariant &value)
{
m_value << value;
#ifdef SUPPORTS_MARKUP
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
if (m_markupText)
m_markupText->SetMarkup(m_value.GetText());
/*
#else
#if defined(__WXGTK__)
GValue gvalue = G_VALUE_INIT;
g_value_init(&gvalue, G_TYPE_STRING);
g_value_set_string(&gvalue, wxGTK_CONV_FONT(str.GetText(), GetOwner()->GetOwner()->GetFont()));
g_object_set_property(G_OBJECT(m_renderer/ *.GetText()* /), is_markupText ? "markup" : "text", &gvalue);
g_value_unset(&gvalue);
#endif // __WXGTK__
*/
#endif // wxHAS_GENERIC_DATAVIEWCTRL
#endif // SUPPORTS_MARKUP
return true;
}
bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const
{
return false;
}
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY
wxString BitmapTextRenderer::GetAccessibleDescription() const
{
#ifdef SUPPORTS_MARKUP
if (m_markupText)
return wxMarkupParser::Strip(m_text);
#endif // SUPPORTS_MARKUP
return m_value.GetText();
}
#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state)
{
int xoffset = 0;
const wxBitmap& icon = m_value.GetBitmap();
if (icon.IsOk())
{
#ifdef __APPLE__
wxSize icon_sz = icon.GetScaledSize();
#else
wxSize icon_sz = icon.GetSize();
#endif
dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.y) / 2);
xoffset = icon_sz.x + 4;
}
#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL)
if (m_markupText)
{
rect.x += xoffset;
m_markupText->Render(GetView(), *dc, rect, 0, GetEllipsizeMode());
}
else
#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL
#ifdef _WIN32
// workaround for Windows DarkMode : Don't respect to the state & wxDATAVIEW_CELL_SELECTED to avoid update of the text color
RenderText(m_value.GetText(), xoffset, rect, dc, state & wxDATAVIEW_CELL_SELECTED ? 0 :state);
#else
RenderText(m_value.GetText(), xoffset, rect, dc, state);
#endif
return true;
}
wxSize BitmapTextRenderer::GetSize() const
{
if (!m_value.GetText().empty())
{
wxSize size;
#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL)
if (m_markupText)
{
wxDataViewCtrl* const view = GetView();
wxClientDC dc(view);
if (GetAttr().HasFont())
dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont()));
size = m_markupText->Measure(dc);
int lines = m_value.GetText().Freq('\n') + 1;
size.SetHeight(size.GetHeight() * lines);
}
else
#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL
{
size = GetTextExtent(m_value.GetText());
size.x = size.x * 9 / 8;
}
if (m_value.GetBitmap().IsOk())
size.x += m_value.GetBitmap().GetWidth() + 4;
return size;
}
return wxSize(80, 20);
}
wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value)
{
if (can_create_editor_ctrl && !can_create_editor_ctrl())
return nullptr;
DataViewBitmapText data;
data << value;
m_was_unusable_symbol = false;
wxPoint position = labelRect.GetPosition();
if (data.GetBitmap().IsOk()) {
const int bmp_width = data.GetBitmap().GetWidth();
position.x += bmp_width;
labelRect.SetWidth(labelRect.GetWidth() - bmp_width);
}
#ifdef __WXMSW__
// Case when from some reason we try to create next EditorCtrl till old one was not deleted
if (auto children = parent->GetChildren(); children.GetCount() > 0)
for (auto child : children)
if (dynamic_cast<wxTextCtrl*>(child)) {
parent->RemoveChild(child);
child->Destroy();
break;
}
#endif // __WXMSW__
wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(),
position, labelRect.GetSize(), wxTE_PROCESS_ENTER);
text_editor->SetInsertionPointEnd();
text_editor->SelectAll();
text_editor->SetBackgroundColour(parent->GetBackgroundColour());
text_editor->SetForegroundColour(parent->GetForegroundColour());
return text_editor;
}
bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value)
{
wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl);
auto item = GetView()->GetModel()->GetParent(m_item);
if (!text_editor || (item.IsOk() && text_editor->GetValue().IsEmpty()))
return false;
m_was_unusable_symbol = Slic3r::GUI::Plater::has_illegal_filename_characters(text_editor->GetValue());
if (m_was_unusable_symbol)
return false;
// The icon can't be edited so get its old value and reuse it.
wxVariant valueOld;
GetView()->GetModel()->GetValue(valueOld, m_item, /*colName*/0);
DataViewBitmapText bmpText;
bmpText << valueOld;
// But replace the text with the value entered by user.
bmpText.SetText(text_editor->GetValue());
value << bmpText;
return true;
}
// ----------------------------------------------------------------------------
// BitmapChoiceRenderer
// ----------------------------------------------------------------------------
bool BitmapChoiceRenderer::SetValue(const wxVariant& value)
{
m_value << value;
return true;
}
bool BitmapChoiceRenderer::GetValue(wxVariant& value) const
{
value << m_value;
return true;
}
bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state)
{
// int xoffset = 0;
const wxBitmap& icon = m_value.GetBitmap();
if (icon.IsOk())
{
dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2);
// xoffset = icon.GetWidth() + 4;
if (rect.height == 0)
rect.height = icon.GetHeight();
}
#ifdef _WIN32
// workaround for Windows DarkMode : Don't respect to the state & wxDATAVIEW_CELL_SELECTED to avoid update of the text color
// RenderText(m_value.GetText(), xoffset, rect, dc, state & wxDATAVIEW_CELL_SELECTED ? 0 : state);
#else
// RenderText(m_value.GetText(), xoffset, rect, dc, state);
#endif
return true;
}
wxSize BitmapChoiceRenderer::GetSize() const
{
wxSize sz;// = GetTextExtent(m_value.GetText());
if (m_value.GetBitmap().IsOk()) {
sz.x += m_value.GetBitmap().GetWidth() + 4;
sz.y = m_value.GetBitmap().GetHeight() + 4;
}
return sz;
}
wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value)
{
if (can_create_editor_ctrl && !can_create_editor_ctrl())
return nullptr;
std::vector<wxBitmap*> icons = get_extruder_color_icons();
if (icons.empty())
return nullptr;
DataViewBitmapText data;
data << value;
::ComboBox *c_editor = new ::ComboBox(parent, wxID_ANY, wxEmptyString,
labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1),
0, nullptr, wxCB_READONLY | CB_NO_DROP_ICON | CB_NO_TEXT);
c_editor->GetDropDown().SetUseContentWidth(true);
if (has_default_extruder && has_default_extruder())
c_editor->Append(_L("default"), *get_default_extruder_color_icon());
for (size_t i = 0; i < icons.size(); i++)
c_editor->Append(wxString::Format("%d", i+1), *icons[i]);
if (has_default_extruder && has_default_extruder())
c_editor->SetSelection(atoi(data.GetText().c_str()));
else
c_editor->SetSelection(atoi(data.GetText().c_str()) - 1);
#ifdef __linux__
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
// to avoid event propagation to other sidebar items
evt.StopPropagation();
// FinishEditing grabs new selection and triggers config update. We better call
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
this->FinishEditing();
});
#else
// to avoid event propagation to other sidebar items
c_editor->Bind(wxEVT_COMBOBOX, [](wxCommandEvent& evt) { evt.StopPropagation(); });
#endif
return c_editor;
}
bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value)
{
::ComboBox*c = static_cast<::ComboBox *>(ctrl);
int selection = c->GetSelection();
if (selection < 0)
return false;
DataViewBitmapText bmpText;
bmpText.SetText(c->GetString(selection));
bmpText.SetBitmap(c->GetItemBitmap(selection));
value << bmpText;
return true;
}
// ----------------------------------------------------------------------------
// TextRenderer
// ----------------------------------------------------------------------------
bool TextRenderer::SetValue(const wxVariant& value)
{
m_value = value.GetString();
return true;
}
bool TextRenderer::GetValue(wxVariant& value) const
{
return false;
}
bool TextRenderer::Render(wxRect rect, wxDC* dc, int state)
{
#ifdef _WIN32
// workaround for Windows DarkMode : Don't respect to the state & wxDATAVIEW_CELL_SELECTED to avoid update of the text color
RenderText(m_value, 0, rect, dc, state & wxDATAVIEW_CELL_SELECTED ? 0 : state);
#else
RenderText(m_value, 0, rect, dc, state);
#endif
return true;
}
wxSize TextRenderer::GetSize() const
{
return GetTextExtent(m_value);
}

View File

@@ -0,0 +1,191 @@
#ifndef slic3r_GUI_ExtraRenderers_hpp_
#define slic3r_GUI_ExtraRenderers_hpp_
#include <functional>
#include <wx/dataview.h>
#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1)
#define SUPPORTS_MARKUP
#endif
// ----------------------------------------------------------------------------
// DataViewBitmapText: helper class used by BitmapTextRenderer
// ----------------------------------------------------------------------------
class DataViewBitmapText : public wxObject
{
public:
DataViewBitmapText( const wxString &text = wxEmptyString,
const wxBitmap& bmp = wxNullBitmap) :
m_text(text),
m_bmp(bmp)
{ }
DataViewBitmapText(const DataViewBitmapText &other)
: wxObject(),
m_text(other.m_text),
m_bmp(other.m_bmp)
{ }
void SetText(const wxString &text) { m_text = text; }
wxString GetText() const { return m_text; }
void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; }
const wxBitmap &GetBitmap() const { return m_bmp; }
bool IsSameAs(const DataViewBitmapText& other) const {
return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp);
}
bool operator==(const DataViewBitmapText& other) const {
return IsSameAs(other);
}
bool operator!=(const DataViewBitmapText& other) const {
return !IsSameAs(other);
}
private:
wxString m_text;
wxBitmap m_bmp;
wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText);
};
DECLARE_VARIANT_OBJECT(DataViewBitmapText)
// ----------------------------------------------------------------------------
// BitmapTextRenderer
// ----------------------------------------------------------------------------
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
class BitmapTextRenderer : public wxDataViewRenderer
#else
class BitmapTextRenderer : public wxDataViewCustomRenderer
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
{
public:
BitmapTextRenderer(bool use_markup = false,
wxDataViewCellMode mode =
//#ifdef __WXOSX__
// wxDATAVIEW_CELL_INERT
//#else
wxDATAVIEW_CELL_EDITABLE
//#endif
, int align = wxDVR_DEFAULT_ALIGNMENT
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
);
#else
) :
wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align)
{
EnableMarkup(use_markup);
}
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
~BitmapTextRenderer();
void EnableMarkup(bool enable = true);
bool SetValue(const wxVariant& value) override;
bool GetValue(wxVariant& value) const override;
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY
virtual wxString GetAccessibleDescription() const override;
#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
virtual wxSize GetSize() const override;
bool HasEditorCtrl() const override
{
//#ifdef __WXOSX__
// return false;
//#else
return true;
//#endif
}
wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override;
bool GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override;
bool WasCanceled() const { return m_was_unusable_symbol; }
void set_can_create_editor_ctrl_function(std::function<bool()> can_create_fn) { can_create_editor_ctrl = can_create_fn; }
private:
DataViewBitmapText m_value;
bool m_was_unusable_symbol{ false };
std::function<bool()> can_create_editor_ctrl { nullptr };
#ifdef SUPPORTS_MARKUP
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
class wxItemMarkupText* m_markupText { nullptr };;
#else
bool is_markupText {false};
#endif
#endif // SUPPORTS_MARKUP
};
// ----------------------------------------------------------------------------
// BitmapChoiceRenderer
// ----------------------------------------------------------------------------
class BitmapChoiceRenderer : public wxDataViewCustomRenderer
{
public:
BitmapChoiceRenderer(wxDataViewCellMode mode =
//#ifdef __WXOSX__
// wxDATAVIEW_CELL_INERT
//#else
wxDATAVIEW_CELL_EDITABLE
//#endif
, int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {}
bool SetValue(const wxVariant& value) override;
bool GetValue(wxVariant& value) const override;
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
virtual wxSize GetSize() const override;
bool HasEditorCtrl() const override { return true; }
wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override;
bool GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override;
void set_can_create_editor_ctrl_function(std::function<bool()> can_create_fn) { can_create_editor_ctrl = can_create_fn; }
void set_default_extruder_idx(std::function<int()> default_extruder_idx_fn) { get_default_extruder_idx = default_extruder_idx_fn; }
void set_has_default_extruder(std::function<bool()> has_default_extruder_fn) { has_default_extruder = has_default_extruder_fn; }
private:
DataViewBitmapText m_value;
std::function<bool()> can_create_editor_ctrl { nullptr };
std::function<int()> get_default_extruder_idx{ nullptr };
std::function<bool()> has_default_extruder{ nullptr };
};
// ----------------------------------------------------------------------------
// TextRenderer
// ----------------------------------------------------------------------------
class TextRenderer : public wxDataViewCustomRenderer
{
public:
TextRenderer(wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT
, int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
) : wxDataViewCustomRenderer(wxT("string"), mode, align) {}
bool SetValue(const wxVariant& value) override;
bool GetValue(wxVariant& value) const override;
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
virtual wxSize GetSize() const override;
bool HasEditorCtrl() const override { return false; }
private:
wxString m_value;
};
#endif // slic3r_GUI_ExtraRenderers_hpp_

View File

@@ -0,0 +1,850 @@
#include "ExtrusionCalibration.hpp"
#include "GUI_App.hpp"
#include "MsgDialog.hpp"
#include "libslic3r/Preset.hpp"
#include "I18N.hpp"
#include <boost/log/trivial.hpp>
#include <wx/dcgraph.h>
#include "CalibUtils.hpp"
namespace Slic3r { namespace GUI {
ExtrusionCalibration::ExtrusionCalibration(wxWindow *parent, wxWindowID id)
: DPIDialog(parent, id, _L("Dynamic flow calibration"), wxDefaultPosition, wxDefaultSize, (wxSYSTEM_MENU |
wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxCLOSE_BOX | wxCAPTION |wxCLIP_CHILDREN))
{
create();
wxGetApp().UpdateDlgDarkUI(this);
}
void ExtrusionCalibration::init_bitmaps()
{
auto lan = wxGetApp().app_config->get_language_code();
if (lan == "zh-cn") {
m_is_zh = true;
m_calibration_tips_bmp_zh = create_scaled_bitmap("extrusion_calibration_tips_zh", nullptr, 256);
}
else{
m_is_zh = false;
m_calibration_tips_bmp_en = create_scaled_bitmap("extrusion_calibration_tips_en", nullptr, 256);
}
m_calibration_tips_open_btn_bmp = create_scaled_bitmap("extrusion_calibrati_open_button", nullptr, 16);
}
void ExtrusionCalibration::create()
{
init_bitmaps();
SetBackgroundColour(*wxWHITE);
wxBoxSizer* sizer_main = new wxBoxSizer(wxVERTICAL);
m_step_1_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_step_1_panel->SetBackgroundColour(*wxWHITE);
wxBoxSizer* step_1_sizer = new wxBoxSizer(wxVERTICAL);
m_step_1_panel->SetSizer(step_1_sizer);
step_1_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
// filament title
wxString intro_text = _L("The nozzle temp and max volumetric speed will affect the calibration results. Please fill in the same values as the actual printing. They can be auto-filled by selecting a filament preset.");
m_filament_preset_title = new Label(m_step_1_panel, intro_text);
m_filament_preset_title->SetFont(Label::Body_12);
m_filament_preset_title->SetForegroundColour(EXTRUSION_CALIBRATION_GREY800);
m_filament_preset_title->Wrap(this->GetSize().x);
step_1_sizer->Add(m_filament_preset_title, 0, wxEXPAND);
step_1_sizer->AddSpacer(FromDIP(12));
auto select_sizer = new wxBoxSizer(wxVERTICAL);
auto nozzle_dia_sel_text = new wxStaticText(m_step_1_panel, wxID_ANY, _L("Nozzle Diameter"), wxDefaultPosition, wxDefaultSize, 0);
select_sizer->Add(nozzle_dia_sel_text, 0, wxALIGN_LEFT);
select_sizer->AddSpacer(FromDIP(4));
#ifdef __APPLE__
m_comboBox_nozzle_dia = new wxComboBox(m_step_1_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, EXTRUSION_CALIBRATION_BED_COMBOX, 0, nullptr, wxCB_READONLY);
#else
m_comboBox_nozzle_dia = new ComboBox(m_step_1_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, EXTRUSION_CALIBRATION_BED_COMBOX, 0, nullptr, wxCB_READONLY);
#endif
m_comboBox_nozzle_dia->AppendString(wxString::Format("%1.1f", 0.2));
m_comboBox_nozzle_dia->AppendString(wxString::Format("%1.1f", 0.4));
m_comboBox_nozzle_dia->AppendString(wxString::Format("%1.1f", 0.6));
m_comboBox_nozzle_dia->AppendString(wxString::Format("%1.1f", 0.8));
select_sizer->Add(m_comboBox_nozzle_dia, 0, wxEXPAND);
select_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
auto filament_sel_text = new wxStaticText(m_step_1_panel, wxID_ANY, _L("Filament"), wxDefaultPosition, wxDefaultSize, 0);
select_sizer->Add(filament_sel_text, 0, wxALIGN_LEFT);
select_sizer->AddSpacer(FromDIP(4));
#ifdef __APPLE__
m_comboBox_filament = new wxComboBox(m_step_1_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, EXTRUSION_CALIBRATION_BED_COMBOX, 0, nullptr, wxCB_READONLY);
#else
m_comboBox_filament = new ComboBox(m_step_1_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, EXTRUSION_CALIBRATION_BED_COMBOX, 0, nullptr, wxCB_READONLY);
#endif
select_sizer->Add(m_comboBox_filament, 0, wxEXPAND);
select_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
auto bed_type_sel_text = new wxStaticText(m_step_1_panel, wxID_ANY, _L("Bed Type"), wxDefaultPosition, wxDefaultSize, 0);
select_sizer->Add(bed_type_sel_text, 0, wxALIGN_LEFT);
select_sizer->AddSpacer(FromDIP(4));
#ifdef __APPLE__
m_comboBox_bed_type = new wxComboBox(m_step_1_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, EXTRUSION_CALIBRATION_BED_COMBOX, 0, nullptr, wxCB_READONLY);
#else
m_comboBox_bed_type = new ComboBox(m_step_1_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, EXTRUSION_CALIBRATION_BED_COMBOX, 0, nullptr, wxCB_READONLY);
#endif
select_sizer->Add(m_comboBox_bed_type, 0, wxEXPAND);
// get bed type
const ConfigOptionDef* bed_type_def = print_config_def.get("curr_bed_type");
if (bed_type_def && bed_type_def->enum_keys_map) {
for (auto item : *bed_type_def->enum_keys_map) {
if (item.first == "Default Plate")
continue;
m_comboBox_bed_type->AppendString(_L(item.first));
}
}
step_1_sizer->Add(select_sizer, 0, wxEXPAND);
// static line
step_1_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
wxPanel* static_line = new wxPanel(m_step_1_panel, wxID_ANY, wxDefaultPosition, { -1, FromDIP(1) });
static_line->SetBackgroundColour(EXTRUSION_CALIBRATION_GREY300);
step_1_sizer->Add(static_line, 0, wxEXPAND);
step_1_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
// filament info
auto info_sizer = new wxFlexGridSizer(0, 3, 0, FromDIP(16));
info_sizer->SetFlexibleDirection(wxBOTH);
info_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
auto nozzle_temp_sizer = new wxBoxSizer(wxVERTICAL);
auto nozzle_temp_text = new wxStaticText(m_step_1_panel, wxID_ANY, _L("Nozzle temperature"));
auto max_input_width = std::max(std::max(std::max(wxWindow::GetTextExtent(_L("Nozzle temperature")).x,
wxWindow::GetTextExtent(_L("Bed Temperature")).x),
wxWindow::GetTextExtent(_L("Max volumetric speed")).x),
EXTRUSION_CALIBRATION_INPUT_SIZE.x);
m_nozzle_temp = new TextInput(m_step_1_panel, wxEmptyString, "°C", "", wxDefaultPosition, { max_input_width, EXTRUSION_CALIBRATION_INPUT_SIZE.y }, wxTE_READONLY);
nozzle_temp_sizer->Add(nozzle_temp_text, 0, wxALIGN_LEFT);
nozzle_temp_sizer->AddSpacer(FromDIP(4));
nozzle_temp_sizer->Add(m_nozzle_temp, 0, wxEXPAND);
auto bed_temp_sizer = new wxBoxSizer(wxVERTICAL);
auto bed_temp_text = new wxStaticText(m_step_1_panel, wxID_ANY, _L("Bed temperature"));
m_bed_temp = new TextInput(m_step_1_panel, wxEmptyString, "°C", "", wxDefaultPosition, { max_input_width, EXTRUSION_CALIBRATION_INPUT_SIZE.y }, wxTE_READONLY);
bed_temp_sizer->Add(bed_temp_text, 0, wxALIGN_LEFT);
bed_temp_sizer->AddSpacer(FromDIP(4));
bed_temp_sizer->Add(m_bed_temp, 0, wxEXPAND);
auto max_flow_sizer = new wxBoxSizer(wxVERTICAL);
auto max_flow_text = new wxStaticText(m_step_1_panel, wxID_ANY, _L("Max volumetric speed"));
m_max_flow_ratio = new TextInput(m_step_1_panel, wxEmptyString, "mm³", "", wxDefaultPosition, { max_input_width, EXTRUSION_CALIBRATION_INPUT_SIZE.y }, wxTE_READONLY);
max_flow_sizer->Add(max_flow_text, 0, wxALIGN_LEFT);
max_flow_sizer->AddSpacer(FromDIP(4));
max_flow_sizer->Add(m_max_flow_ratio, 0, wxEXPAND);
info_sizer->Add(nozzle_temp_sizer);
info_sizer->Add(bed_temp_sizer);
info_sizer->Add(max_flow_sizer);
step_1_sizer->Add(info_sizer, 0, wxEXPAND);
// static line
step_1_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
wxPanel* static_line2 = new wxPanel(m_step_1_panel, wxID_ANY, wxDefaultPosition, { -1, FromDIP(1) });
static_line2->SetBackgroundColour(EXTRUSION_CALIBRATION_GREY300);
step_1_sizer->Add(static_line2, 0, wxEXPAND);
step_1_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
auto cali_sizer = new wxBoxSizer(wxHORIZONTAL);
m_info_text = new wxStaticText(m_step_1_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
m_info_text->SetFont(Label::Body_12);
m_info_text->Hide();
m_error_text = new wxStaticText(m_step_1_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
m_error_text->SetFont(Label::Body_12);
m_error_text->SetForegroundColour(wxColour(208, 27, 27));
m_error_text->Hide();
m_button_cali = new Button(m_step_1_panel, _L("Start calibration"));
// y96
m_btn_bg_green = StateColor(std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Disabled), std::pair<wxColour, int>(wxColour(95, 82, 253), StateColor::Pressed), std::pair<wxColour, int>(wxColour(129, 150, 255), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Normal));
m_button_cali->SetBackgroundColor(m_btn_bg_green);
m_button_cali->SetFont(Label::Body_13);
m_button_cali->SetBorderColor({ std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Disabled), std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Enabled) });
m_button_cali->SetTextColor({ std::pair<wxColour, int>(wxColour(172, 172, 172), StateColor::Disabled), std::pair<wxColour, int>(EXTRUSION_CALIBRATION_GREY200, StateColor::Enabled) });
m_button_cali->SetCornerRadius(FromDIP(12));
m_button_cali->SetMinSize(wxSize(-1, FromDIP(24)));
m_button_cali->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_cali, this);
m_cali_cancel = new Button(m_step_1_panel, _L("Cancel"));
m_btn_bg_green = StateColor(std::pair<wxColour, int>(wxColour(95, 82, 253), StateColor::Pressed), std::pair<wxColour, int>(wxColour(129, 150, 255), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Normal));
m_cali_cancel->SetBackgroundColor(m_btn_bg_green);
m_cali_cancel->SetBorderColor(wxColour(68, 121, 251));
m_cali_cancel->SetTextColor(EXTRUSION_CALIBRATION_GREY200);
m_cali_cancel->SetMinSize(EXTRUSION_CALIBRATION_BUTTON_SIZE);
m_cali_cancel->SetCornerRadius(FromDIP(12));
m_cali_cancel->Hide();
m_cali_cancel->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_cancel, this);
m_button_next_step = new Button(m_step_1_panel, _L("Next"));
m_btn_bg_gray = StateColor(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed), std::pair<wxColour, int>(*wxWHITE, StateColor::Focused),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
m_button_next_step->SetBackgroundColor(m_btn_bg_gray);
m_button_next_step->SetFont(Label::Body_13);
m_button_next_step->SetBorderColor(EXTRUSION_CALIBRATION_GREY900);
m_button_next_step->SetTextColor(EXTRUSION_CALIBRATION_GREY900);
m_button_next_step->SetMinSize(EXTRUSION_CALIBRATION_BUTTON_SIZE);
m_button_next_step->SetCornerRadius(FromDIP(12));
m_button_next_step->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_next, this);
m_button_next_step->Hide();
cali_sizer->Add(m_info_text, 10, wxALIGN_LEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL, FromDIP(10));
cali_sizer->Add(m_error_text, 10, wxALIGN_LEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL, FromDIP(10));
cali_sizer->AddStretchSpacer();
cali_sizer->Add(m_button_cali, 0, wxRIGHT | wxALIGN_CENTRE_VERTICAL, FromDIP(10));
cali_sizer->Add(m_cali_cancel, 0, wxRIGHT | wxALIGN_CENTRE_VERTICAL, FromDIP(10));
cali_sizer->Add(m_button_next_step, 0, wxRIGHT | wxALIGN_CENTRE_VERTICAL, FromDIP(10));
step_1_sizer->Add(cali_sizer, 0, wxEXPAND);
step_1_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
m_step_2_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_step_2_panel->SetBackgroundColour(*wxWHITE);
wxBoxSizer* step_2_sizer = new wxBoxSizer(wxVERTICAL);
m_step_2_panel->SetSizer(step_2_sizer);
step_2_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
// save result title and tips
wxBoxSizer* save_result_sizer = new wxBoxSizer(wxHORIZONTAL);
wxString fill_intro_text = _L("Calibration completed. Please find the most uniform extrusion line on your hot bed like the picture below, and fill the value on its left side into the factor K input box.");
m_save_cali_result_title = new Label(m_step_2_panel, fill_intro_text);
m_save_cali_result_title->SetFont(::Label::Body_12);
m_save_cali_result_title->SetForegroundColour(EXTRUSION_CALIBRATION_GREY800);
m_save_cali_result_title->Wrap(this->GetSize().x);
save_result_sizer->Add(m_save_cali_result_title, 0, wxEXPAND);
step_2_sizer->Add(save_result_sizer, 0, wxEXPAND);
step_2_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
auto content_sizer = new wxBoxSizer(wxHORIZONTAL);
m_calibration_tips_static_bmp = new wxStaticBitmap(m_step_2_panel, wxID_ANY, wxNullBitmap, wxDefaultPosition, EXTRUSION_CALIBRATION_BMP_SIZE, 0);
m_calibration_tips_static_bmp->SetMinSize(EXTRUSION_CALIBRATION_BMP_SIZE);
content_sizer->Add(m_calibration_tips_static_bmp, 1, wxEXPAND | wxSHAPED);
content_sizer->Add(EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0, 0);
// k/n input value
auto kn_sizer = new wxBoxSizer(wxVERTICAL);
auto k_val_text = new wxStaticText(m_step_2_panel, wxID_ANY, _L("Factor K"), wxDefaultPosition, wxDefaultSize, 0);
m_k_val = new TextInput(m_step_2_panel, wxEmptyString, "", "", wxDefaultPosition, wxDefaultSize);
auto n_val_text = new wxStaticText(m_step_2_panel, wxID_ANY, _L("Factor N"), wxDefaultPosition, wxDefaultSize, 0);
m_n_val = new TextInput(m_step_2_panel, wxEmptyString, "", "", wxDefaultPosition, wxDefaultSize);
// hide n
n_val_text->Hide();
m_n_val->Hide();
kn_sizer->Add(k_val_text, 0, wxALIGN_CENTER_VERTICAL);
kn_sizer->Add(m_k_val, 0, wxEXPAND);
kn_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
kn_sizer->Add(n_val_text, 0, wxALIGN_CENTER_VERTICAL);
kn_sizer->Add(m_n_val, 0, wxEXPAND);
// save button
m_button_save_result = new Button(m_step_2_panel, _L("Save"));
m_btn_bg_green = StateColor(std::pair<wxColour, int>(wxColour(95, 82, 253), StateColor::Pressed), std::pair<wxColour, int>(wxColour(129, 150, 255), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(68, 121, 251), StateColor::Normal));
m_button_save_result->SetBackgroundColor(m_btn_bg_green);
m_button_save_result->SetFont(Label::Body_13);
m_button_save_result->SetBorderColor(wxColour(68, 121, 251));
m_button_save_result->SetTextColor(EXTRUSION_CALIBRATION_GREY200);
m_button_save_result->SetMinSize(EXTRUSION_CALIBRATION_BUTTON_SIZE);
m_button_save_result->SetCornerRadius(FromDIP(12));
m_button_save_result->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_save, this);
m_button_last_step = new Button(m_step_2_panel, _L("Last Step")); // Back for english
m_button_last_step->SetBackgroundColor(m_btn_bg_gray);
m_button_last_step->SetFont(Label::Body_13);
m_button_last_step->SetBorderColor(EXTRUSION_CALIBRATION_GREY900);
m_button_last_step->SetTextColor(EXTRUSION_CALIBRATION_GREY900);
m_button_last_step->SetMinSize(EXTRUSION_CALIBRATION_BUTTON_SIZE);
m_button_last_step->SetCornerRadius(FromDIP(12));
m_button_last_step->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_last, this);
kn_sizer->AddStretchSpacer();
kn_sizer->Add(m_button_last_step, 0);
kn_sizer->AddSpacer(FromDIP(10));
kn_sizer->Add(m_button_save_result, 0);
content_sizer->Add(kn_sizer, 0, wxEXPAND);
step_2_sizer->Add(content_sizer, 0, wxEXPAND);
step_2_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
sizer_main->Add(m_step_1_panel, 1, wxEXPAND);
sizer_main->Add(m_step_2_panel, 1, wxEXPAND);
wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL);
top_sizer->Add(FromDIP(24), 0);
top_sizer->Add(sizer_main, 1, wxEXPAND);
top_sizer->Add(FromDIP(24), 0);
SetSizer(top_sizer);
// set default nozzle
m_comboBox_nozzle_dia->SetSelection(1);
// set a default bed type
m_comboBox_bed_type->SetSelection(0);
// set to step 1
set_step(1);
Layout();
Fit();
m_k_val->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent& e) {
input_value_finish();
e.Skip();
});
m_n_val->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent& e) {
input_value_finish();
e.Skip();
});
m_calibration_tips_static_bmp->Bind(wxEVT_PAINT, &ExtrusionCalibration::paint, this);
m_calibration_tips_static_bmp->Bind(wxEVT_LEFT_UP, &ExtrusionCalibration::open_bitmap, this);
m_comboBox_filament->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(ExtrusionCalibration::on_select_filament), NULL, this);
m_comboBox_bed_type->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(ExtrusionCalibration::on_select_bed_type), NULL, this);
m_comboBox_nozzle_dia->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(ExtrusionCalibration::on_select_nozzle_dia), NULL, this);
}
ExtrusionCalibration::~ExtrusionCalibration()
{
m_comboBox_filament->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(ExtrusionCalibration::on_select_filament), NULL, this);
m_comboBox_bed_type->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(ExtrusionCalibration::on_select_bed_type), NULL, this);
m_comboBox_nozzle_dia->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(ExtrusionCalibration::on_select_nozzle_dia), NULL, this);
}
void ExtrusionCalibration::paint(wxPaintEvent&) {
auto size = m_calibration_tips_static_bmp->GetSize();
wxPaintDC dc(m_calibration_tips_static_bmp);
wxGCDC gcdc(dc);
dc.DrawBitmap(m_is_zh ? m_calibration_tips_bmp_zh : m_calibration_tips_bmp_en, wxPoint(0, 0));
gcdc.SetPen(wxColour(0, 0, 0, 61));
gcdc.SetBrush(wxColour(0, 0, 0, 61));
gcdc.DrawRectangle(wxPoint(0, 0), EXTRUSION_CALIBRATION_BMP_TIP_BAR);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
int pos_offset = (EXTRUSION_CALIBRATION_BMP_TIP_BAR.y - EXTRUSION_CALIBRATION_BMP_BTN_SIZE.y) / 2;
wxPoint open_btn_pos = wxPoint(size.x - pos_offset - EXTRUSION_CALIBRATION_BMP_BTN_SIZE.x, pos_offset);
dc.DrawBitmap(m_calibration_tips_open_btn_bmp, open_btn_pos);
gcdc.SetFont(Label::Head_14);
gcdc.SetTextForeground(wxColour(255, 255, 255, 224));
wxSize text_size = wxWindow::GetTextExtent(_L("Example"));
gcdc.DrawText(_L("Example"), { (EXTRUSION_CALIBRATION_BMP_TIP_BAR.x - text_size.x) / 2, (EXTRUSION_CALIBRATION_BMP_TIP_BAR.y - text_size.y) / 2});
return;
}
void ExtrusionCalibration::open_bitmap(wxMouseEvent& event) {
auto pos = event.GetPosition();
auto size = m_calibration_tips_static_bmp->GetSize();
if (pos.x > size.x - EXTRUSION_CALIBRATION_BMP_TIP_BAR.y && pos.y > 0 &&
pos.x < size.x && pos.y < EXTRUSION_CALIBRATION_BMP_TIP_BAR.y) {
auto* popup = new wxDialog(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize);
auto bmp_sizer = new wxBoxSizer(wxVERTICAL);
wxStaticBitmap* zoomed_bitmap = new wxStaticBitmap(popup, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0);
zoomed_bitmap->SetBitmap(create_scaled_bitmap(m_is_zh ? "extrusion_calibration_tips_zh" : "extrusion_calibration_tips_en", nullptr, 720));
bmp_sizer->Add(zoomed_bitmap, 1, wxEXPAND);
popup->SetSizer(bmp_sizer);
popup->Layout();
popup->Fit();
popup->CenterOnParent();
wxGetApp().UpdateDlgDarkUI(popup);
popup->ShowModal();
}
return;
}
void ExtrusionCalibration::input_value_finish()
{
;
}
void ExtrusionCalibration::show_info(bool show, bool is_error, wxString text)
{
if (show && is_error) {
m_error_text->Show();
if (m_error_text->GetLabelText().compare(text) != 0)
m_error_text->SetLabelText(text);
m_info_text->Hide();
}
else if (show && !is_error) {
m_info_text->Show();
if (m_info_text->GetLabelText().compare(text) != 0)
m_info_text->SetLabelText(text);
m_error_text->Hide();
}
else {
if (is_error) {
m_info_text->Hide();
m_error_text->Show();
if (m_error_text->GetLabelText().compare(text) != 0)
m_error_text->SetLabelText(text);
} else {
m_info_text->Show();
if (m_info_text->GetLabelText().compare(text) != 0)
m_info_text->SetLabelText(text);
m_error_text->Hide();
}
}
}
void ExtrusionCalibration::update()
{
if (obj) {
if (obj->is_in_extrusion_cali()) {
show_info(true, false, wxString::Format(_L("Calibrating... %d%%"), obj->mc_print_percent));
m_cali_cancel->Show();
m_cali_cancel->Enable();
m_button_cali->Hide();
m_button_next_step->Hide();
} else if (obj->is_extrusion_cali_finished()) {
if (m_bed_temp->GetTextCtrl()->GetValue().compare("0") == 0) {
wxString tips = get_bed_type_incompatible(false);
show_info(true, true, tips);
}
else {
get_bed_type_incompatible(true);
show_info(true, false, _L("Calibration completed"));
}
m_cali_cancel->Hide();
m_button_cali->Show();
m_button_next_step->Show();
} else {
if (m_bed_temp->GetTextCtrl()->GetValue().compare("0") == 0) {
wxString tips = get_bed_type_incompatible(false);
show_info(true, true, tips);
} else {
get_bed_type_incompatible(true);
show_info(true, false, wxEmptyString);
}
m_cali_cancel->Hide();
m_button_cali->Show();
m_button_next_step->Hide();
}
Layout();
}
}
void ExtrusionCalibration::on_click_cali(wxCommandEvent& event)
{
if (obj) {
int nozzle_temp = -1;
int bed_temp = -1;
float max_volumetric_speed = -1;
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle) {
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
wxString filament_name = wxString::FromUTF8(it->name);
if (filament_name.compare(m_comboBox_filament->GetValue()) == 0) {
try {
bed_temp = get_bed_temp(&it->config);
const ConfigOptionInts* nozzle_temp_opt = it->config.option<ConfigOptionInts>("nozzle_temperature");
const ConfigOptionFloats* speed_opt = it->config.option<ConfigOptionFloats>("filament_max_volumetric_speed");
if (nozzle_temp_opt && speed_opt) {
nozzle_temp = nozzle_temp_opt->get_at(0);
max_volumetric_speed = speed_opt->get_at(0);
if (bed_temp >= 0 && nozzle_temp >= 0 && max_volumetric_speed >= 0) {
int curr_tray_id = ams_id * 4 + tray_id;
if (tray_id == VIRTUAL_TRAY_ID)
curr_tray_id = tray_id;
obj->command_start_extrusion_cali(curr_tray_id, nozzle_temp, bed_temp, max_volumetric_speed, it->setting_id);
return;
}
} else {
BOOST_LOG_TRIVIAL(error) << "cali parameters is invalid";
}
} catch(...) {
;
}
}
}
} else {
BOOST_LOG_TRIVIAL(error) << "extrusion_cali: preset_bundle is nullptr";
}
} else {
BOOST_LOG_TRIVIAL(error) << "cali obj parameters is invalid";
}
}
void ExtrusionCalibration::on_click_cancel(wxCommandEvent& event)
{
if (obj) {
BOOST_LOG_TRIVIAL(info) << "extrusion_cali: stop";
obj->command_stop_extrusion_cali();
}
}
bool ExtrusionCalibration::check_k_validation(wxString k_text)
{
if (k_text.IsEmpty())
return false;
double k = 0.0;
try {
k_text.ToDouble(&k);
}
catch (...) {
;
}
if (k < MIN_PA_K_VALUE || k > MAX_PA_K_VALUE)
return false;
return true;
}
bool ExtrusionCalibration::check_k_n_validation(wxString k_text, wxString n_text)
{
if (k_text.IsEmpty() || n_text.IsEmpty())
return false;
double k = 0.0;
try {
k_text.ToDouble(&k);
}
catch (...) {
;
}
double n = 0.0;
try {
n_text.ToDouble(&n);
}
catch (...) {
;
}
if (k < MIN_PA_K_VALUE || k > MAX_PA_K_VALUE)
return false;
if (n < 0.6 || n > 2.0)
return false;
return true;
}
void ExtrusionCalibration::on_click_save(wxCommandEvent &event)
{
wxString k_text = m_k_val->GetTextCtrl()->GetValue();
wxString n_text = m_n_val->GetTextCtrl()->GetValue();
if (!ExtrusionCalibration::check_k_validation(k_text)) {
wxString k_tips = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE);
wxString kn_tips = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE, 0.6, 2.0);
MessageDialog msg_dlg(nullptr, k_tips, wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
double k = 0.0;
try {
k_text.ToDouble(&k);
}
catch (...) {
;
}
double n = 0.0;
try {
n_text.ToDouble(&n);
}
catch (...) {
;
}
// set values
int nozzle_temp = -1;
int bed_temp = -1;
float max_volumetric_speed = -1;
std::string setting_id;
std::string name;
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle) {
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
wxString filament_name = wxString::FromUTF8(it->name);
if (filament_name.compare(m_comboBox_filament->GetValue()) == 0) {
if (obj) {
bed_temp = get_bed_temp(&it->config);
const ConfigOptionInts* nozzle_temp_opt = it->config.option<ConfigOptionInts>("nozzle_temperature");
const ConfigOptionFloats* speed_opt = it->config.option<ConfigOptionFloats>("filament_max_volumetric_speed");
if (nozzle_temp_opt && speed_opt) {
nozzle_temp = nozzle_temp_opt->get_at(0);
max_volumetric_speed = speed_opt->get_at(0);
}
setting_id = it->setting_id;
name = it->name;
}
}
}
}
// send command
int curr_tray_id = ams_id * 4 + tray_id;
if (tray_id == VIRTUAL_TRAY_ID)
curr_tray_id = tray_id;
obj->command_extrusion_cali_set(curr_tray_id, setting_id, name, k, n, bed_temp, nozzle_temp, max_volumetric_speed);
Close();
}
void ExtrusionCalibration::on_click_last(wxCommandEvent &event)
{
set_step(1);
}
void ExtrusionCalibration::on_click_next(wxCommandEvent& event)
{
set_step(2);
}
bool ExtrusionCalibration::Show(bool show)
{
if (show) {
m_k_val->GetTextCtrl()->SetSize(wxSize(-1, FromDIP(20)));
m_n_val->GetTextCtrl()->SetSize(wxSize(-1, FromDIP(20)));
}
return DPIDialog::Show(show);
}
void ExtrusionCalibration::update_combobox_filaments()
{
m_comboBox_filament->SetValue(wxEmptyString);
user_filaments.clear();
int selection_idx = -1;
int filament_index = -1;
int curr_selection = -1;
wxArrayString filament_items;
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle && obj) {
BOOST_LOG_TRIVIAL(trace) << "system_preset_bundle filament number=" << preset_bundle->filaments.size();
std::string printer_type = obj->printer_type;
std::set<std::string> printer_preset_list;
for (auto printer_it = preset_bundle->printers.begin(); printer_it != preset_bundle->printers.end(); printer_it++) {
// only use system printer preset
if (!printer_it->is_system) continue;
std::string model_id = printer_it->get_current_printer_type(preset_bundle);
ConfigOption* printer_nozzle_opt = printer_it->config.option("nozzle_diameter");
ConfigOptionFloats* printer_nozzle_vals = nullptr;
if (printer_nozzle_opt)
printer_nozzle_vals = dynamic_cast<ConfigOptionFloats*>(printer_nozzle_opt);
double nozzle_value = 0.4;
wxString nozzle_value_str = m_comboBox_nozzle_dia->GetValue();
try {
nozzle_value_str.ToDouble(&nozzle_value);
} catch(...) {
;
}
if (!model_id.empty() && model_id.compare(obj->printer_type) == 0
&& printer_nozzle_vals
&& abs(printer_nozzle_vals->get_at(0) - nozzle_value) < 1e-3) {
printer_preset_list.insert(printer_it->name);
BOOST_LOG_TRIVIAL(trace) << "extrusion_cali: printer_model = " << model_id;
} else {
BOOST_LOG_TRIVIAL(error) << "extrusion_cali: printer_model = " << model_id;
}
}
for (auto filament_it = preset_bundle->filaments.begin(); filament_it != preset_bundle->filaments.end(); filament_it++) {
ConfigOption* printer_opt = filament_it->config.option("compatible_printers");
ConfigOptionStrings* printer_strs = dynamic_cast<ConfigOptionStrings*>(printer_opt);
for (auto printer_str : printer_strs->values) {
if (printer_preset_list.find(printer_str) != printer_preset_list.end()) {
user_filaments.push_back(&(*filament_it));
// set default filament id
filament_index++;
if (filament_it->is_system
&& !ams_filament_id.empty()
&& filament_it->filament_id == ams_filament_id
) {
curr_selection = filament_index;
}
if (filament_it->name == obj->extrusion_cali_filament_name && !obj->extrusion_cali_filament_name.empty())
{
curr_selection = filament_index;
}
wxString filament_name = wxString::FromUTF8(filament_it->name);
filament_items.Add(filament_name);
break;
}
}
}
m_comboBox_filament->Set(filament_items);
m_comboBox_filament->SetSelection(curr_selection);
post_select_event();
}
if (m_comboBox_filament->GetValue().IsEmpty())
m_button_cali->Disable();
else
m_button_cali->Enable();
}
wxString ExtrusionCalibration::get_bed_type_incompatible(bool incompatible)
{
if (incompatible) {
m_button_cali->Enable();
return wxEmptyString;
}
else {
m_button_cali->Disable();
std::string filament_alias = "";
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle) {
for (auto filament_it = preset_bundle->filaments.begin(); filament_it != preset_bundle->filaments.end(); filament_it++) {
wxString filament_name = wxString::FromUTF8(filament_it->name);
if (filament_name.compare(m_comboBox_filament->GetValue()) == 0) {
filament_alias = filament_it->alias;
}
}
}
wxString tips = wxString::Format(_L("%s does not support %s"), m_comboBox_bed_type->GetValue(), filament_alias);
return tips;
}
}
void ExtrusionCalibration::Popup()
{
this->SetSize(EXTRUSION_CALIBRATION_DIALOG_SIZE);
update_combobox_filaments();
set_step(1);
update();
Layout();
Fit();
wxGetApp().UpdateDlgDarkUI(this);
ShowModal();
}
void ExtrusionCalibration::post_select_event() {
wxCommandEvent event(wxEVT_COMBOBOX);
event.SetEventObject(m_comboBox_filament);
wxPostEvent(m_comboBox_filament, event);
}
void ExtrusionCalibration::set_step(int step_index)
{
if (step_index == 2) {
wxString title_text = wxString::Format("%s - %s 2/2", _L("Dynamic flow Calibration"), _L("Step"));
SetTitle(title_text);
m_step_1_panel->Hide();
m_step_2_panel->Show();
} else {
wxString title_text = wxString::Format("%s - %s 1/2", _L("Dynamic flow Calibration"), _L("Step"));
SetTitle(title_text);
m_step_1_panel->Show();
m_step_2_panel->Hide();
}
this->SetMinSize(EXTRUSION_CALIBRATION_DIALOG_SIZE);
Layout();
Fit();
}
void ExtrusionCalibration::on_select_filament(wxCommandEvent &evt)
{
m_filament_type = "";
update_filament_info();
// set a default value for input values
if (m_k_val->GetTextCtrl()->GetValue().IsEmpty()) {
m_k_val->GetTextCtrl()->SetValue("0");
}
if (m_n_val->GetTextCtrl()->GetValue().IsEmpty()) {
m_n_val->GetTextCtrl()->SetValue("0");
}
}
void ExtrusionCalibration::update_filament_info()
{
if (m_comboBox_filament->GetValue().IsEmpty()) {
m_nozzle_temp->GetTextCtrl()->SetValue(wxEmptyString);
m_bed_temp->GetTextCtrl()->SetValue(wxEmptyString);
m_max_flow_ratio->GetTextCtrl()->SetValue(wxEmptyString);
return;
}
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
int bed_temp_int = -1;
if (preset_bundle) {
for (auto filament_it = preset_bundle->filaments.begin(); filament_it != preset_bundle->filaments.end(); filament_it++) {
wxString filament_name = wxString::FromUTF8(filament_it->name);
if (filament_name.compare(m_comboBox_filament->GetValue()) == 0) {
m_filament_type = filament_it->name;
if (obj) {
obj->extrusion_cali_filament_name = filament_it->name;
BOOST_LOG_TRIVIAL(info) << "set extrusion cali filament name = " << obj->extrusion_cali_filament_name;
}
// update nozzle temperature
ConfigOption* opt_nozzle_temp = filament_it->config.option("nozzle_temperature");
if (opt_nozzle_temp) {
ConfigOptionInts* opt_min_ints = dynamic_cast<ConfigOptionInts*>(opt_nozzle_temp);
if (opt_min_ints) {
wxString text_nozzle_temp = wxString::Format("%d", opt_min_ints->get_at(0));
m_nozzle_temp->GetTextCtrl()->SetValue(text_nozzle_temp);
}
}
// update bed temperature
bed_temp_int = get_bed_temp(&filament_it->config);
wxString bed_temp_text = wxString::Format("%d", bed_temp_int);
m_bed_temp->GetTextCtrl()->SetValue(bed_temp_text);
// update max flow speed
ConfigOption* opt_flow_speed = filament_it->config.option("filament_max_volumetric_speed");
if (opt_flow_speed) {
ConfigOptionFloats* opt_flow_floats = dynamic_cast<ConfigOptionFloats*>(opt_flow_speed);
if (opt_flow_floats) {
wxString flow_val_text = wxString::Format("%0.2f", opt_flow_floats->get_at(0));
m_max_flow_ratio->GetTextCtrl()->SetValue(flow_val_text);
}
}
}
}
}
}
int ExtrusionCalibration::get_bed_temp(DynamicPrintConfig* config)
{
BedType curr_bed_type = BedType(m_comboBox_bed_type->GetSelection() + btDefault + 1);
const ConfigOptionInts* opt_bed_temp_ints = config->option<ConfigOptionInts>(get_bed_temp_key(curr_bed_type));
if (opt_bed_temp_ints) {
return opt_bed_temp_ints->get_at(0);
}
return -1;
}
void ExtrusionCalibration::on_select_bed_type(wxCommandEvent &evt)
{
update_filament_info();
}
void ExtrusionCalibration::on_select_nozzle_dia(wxCommandEvent &evt)
{
update_combobox_filaments();
}
void ExtrusionCalibration::on_dpi_changed(const wxRect &suggested_rect) { this->Refresh(); }
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,149 @@
#ifndef slic3r_ExtrusionCalibration_hpp_
#define slic3r_ExtrusionCalibration_hpp_
#include "libslic3r/Preset.hpp"
#include "wxExtensions.hpp"
#include "GUI_Utils.hpp"
#include "DeviceManager.hpp"
#include "wx/clrpicker.h"
#include "Widgets/RadioBox.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/CheckBox.hpp"
#include "Widgets/ComboBox.hpp"
#include "Widgets/TextInput.hpp"
#include "ParamsDialog.hpp"
#include "GUI_App.hpp"
#include "wx/hyperlink.h"
#define EXTRUSION_CALIBRATION_DEF_COLOUR wxColour(255, 255, 255)
#define EXTRUSION_CALIBRATION_GREY900 wxColour(38, 46, 48)
#define EXTRUSION_CALIBRATION_GREY800 wxColour(50, 58, 61)
#define EXTRUSION_CALIBRATION_GREY700 wxColour(107, 107, 107)
#define EXTRUSION_CALIBRATION_GREY300 wxColour(238, 238, 238)
#define EXTRUSION_CALIBRATION_GREY200 wxColour(248, 248, 248)
#define EXTRUSION_CALIBRATION_BODY_WIDTH FromDIP(380)
#define EXTRUSION_CALIBRATION_LABEL_WIDTH FromDIP(80)
#define EXTRUSION_CALIBRATION_WIDGET_GAP FromDIP(18)
#define EXTRUSION_CALIBRATION_DIALOG_SIZE wxSize(FromDIP(400), -1)
//#define EXTRUSION_CALIBRATION_DIALOG_SIZE wxSize(FromDIP(520), -1)
#define EXTRUSION_CALIBRATION_BED_COMBOX wxSize(FromDIP(200), FromDIP(24))
#define EXTRUSION_CALIBRATION_BUTTON_SIZE wxSize(FromDIP(72), FromDIP(24))
#define EXTRUSION_CALIBRATION_INPUT_SIZE wxSize(FromDIP(100), FromDIP(24))
#define EXTRUSION_CALIBRATION_BMP_SIZE wxSize(FromDIP(256), FromDIP(256))
#define EXTRUSION_CALIBRATION_BMP_TIP_BAR wxSize(FromDIP(256), FromDIP(40))
#define EXTRUSION_CALIBRATION_BMP_BTN_SIZE wxSize(FromDIP(16), FromDIP(16))
namespace Slic3r { namespace GUI {
class ExtrusionCalibration : public DPIDialog
{
public:
ExtrusionCalibration(wxWindow *parent, wxWindowID id);
~ExtrusionCalibration();
void create();
void input_value_finish();
void update();
bool Show(bool show) override;
void Popup();
void post_select_event();
void update_machine_obj(MachineObject* obj_) { obj = obj_; };
// input is 1 or 2
void set_step(int step_index);
static bool check_k_n_validation(wxString k_text, wxString n_text);
static bool check_k_validation(wxString k_text);
MachineObject *obj { nullptr };
int ams_id { 0 }; /* 0 ~ 3 */
int tray_id { 0 }; /* 0 ~ 3 | 254 for virtual tray id*/
std::string ams_filament_id;
std::string m_filament_type;
std::vector<Preset*> user_filaments;
protected:
void init_bitmaps();
void on_dpi_changed(const wxRect &suggested_rect) override;
void paint(wxPaintEvent&);
void open_bitmap(wxMouseEvent& event);
void on_select_filament(wxCommandEvent& evt);
void on_select_bed_type(wxCommandEvent& evt);
void on_select_nozzle_dia(wxCommandEvent& evt);
void on_click_cali(wxCommandEvent& evt);
void on_click_cancel(wxCommandEvent& evt);
void on_click_save(wxCommandEvent& evt);
void on_click_last(wxCommandEvent& evt);
void on_click_next(wxCommandEvent& evt);
void update_filament_info();
void update_combobox_filaments();
wxString get_bed_type_incompatible(bool incompatible);
void show_info(bool show, bool is_error, wxString text);
int get_bed_temp(DynamicPrintConfig* config);
protected:
StateColor m_btn_bg_green;
StateColor m_btn_bg_gray;
wxPanel* m_step_1_panel;
wxPanel* m_step_2_panel;
// title of select filament preset
Label* m_filament_preset_title;
// select a filament preset
#ifdef __APPLE__
wxComboBox* m_comboBox_filament;
#else
ComboBox* m_comboBox_filament;
#endif
#ifdef __APPLE__
wxComboBox* m_comboBox_bed_type;
#else
ComboBox* m_comboBox_bed_type;
#endif
#ifdef __APPLE__
wxComboBox* m_comboBox_nozzle_dia;
#else
ComboBox* m_comboBox_nozzle_dia;
#endif
TextInput* m_nozzle_temp;
TextInput* m_bed_temp;
TextInput* m_max_flow_ratio;
Button* m_cali_cancel;
Button* m_button_cali;
Button* m_button_next_step;
Label* m_save_cali_result_title;
wxStaticText* m_fill_cali_params_tips;
wxStaticText* m_info_text;
wxStaticText* m_error_text;
wxBitmap m_calibration_tips_open_btn_bmp;
wxBitmap m_calibration_tips_bmp_zh;
wxBitmap m_calibration_tips_bmp_en;
wxStaticBitmap* m_calibration_tips_static_bmp;
// save n and k result
wxStaticText* m_k_param;
TextInput* m_k_val;
wxStaticText* m_n_param;
TextInput* m_n_val;
Button* m_button_last_step;
Button* m_button_save_result;
bool m_is_zh{ false };
};
}} // namespace Slic3r::GUI
#endif

2146
src/slic3r/GUI/Field.cpp Normal file

File diff suppressed because it is too large Load Diff

536
src/slic3r/GUI/Field.hpp Normal file
View File

@@ -0,0 +1,536 @@
#ifndef SLIC3R_GUI_FIELD_HPP
#define SLIC3R_GUI_FIELD_HPP
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <memory>
#include <cstdint>
#include <functional>
#include <boost/any.hpp>
#include <wx/colourdata.h>
#include <wx/spinctrl.h>
#include <wx/bmpcbox.h>
#include <wx/clrpicker.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Config.hpp"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "wxExtensions.hpp"
#include "Widgets/SpinInput.hpp"
#ifdef __WXMSW__
#define wxMSW true
#else
#define wxMSW false
#endif
namespace Slic3r { namespace GUI {
class Field;
using t_field = std::unique_ptr<Field>;
using t_kill_focus = std::function<void(const std::string&)>;
using t_change = std::function<void(const t_config_option_key&, const boost::any&)>;
using t_back_to_init = std::function<void(const std::string&)>;
wxString double_to_string(double const value, const int max_precision = 4);
wxString get_thumbnail_string(const Vec2d& value);
wxString get_thumbnails_string(const std::vector<Vec2d>& values);
class Field {
protected:
// factory function to defer and enforce creation of derived type.
virtual void PostInitialize();
/// Finish constructing the Field's wxWidget-related properties, including setting its own sizer, etc.
virtual void BUILD() = 0;
/// Call the attached on_kill_focus method.
//! It's important to use wxEvent instead of wxFocusEvent,
//! in another case we can't unfocused control at all
void on_kill_focus();
/// Call the attached on_change method.
void on_change_field();
class EnterPressed {
public:
EnterPressed(Field* field) :
m_parent(field){ m_parent->set_enter_pressed(true); }
~EnterPressed() { m_parent->set_enter_pressed(false); }
private:
Field* m_parent;
};
public:
/// Call the attached m_back_to_initial_value method.
void on_back_to_initial_value();
/// Call the attached m_back_to_sys_value method.
void on_back_to_sys_value();
public:
/// parent wx item, opportunity to refactor (probably not necessary - data duplication)
wxWindow* m_parent {nullptr};
/// Function object to store callback passed in from owning object.
t_kill_focus m_on_kill_focus {nullptr};
/// Function object to store callback passed in from owning object.
t_change m_on_change {nullptr};
/// Function object to store callback passed in from owning object.
t_back_to_init m_back_to_initial_value{ nullptr };
t_back_to_init m_back_to_sys_value{ nullptr };
// This is used to avoid recursive invocation of the field change/update by wxWidgets.
bool m_disable_change_event {false};
bool m_is_modified_value {false};
bool m_is_nonsys_value {true};
/// Copy of ConfigOption for deduction purposes
const ConfigOptionDef m_opt {ConfigOptionDef()};
const t_config_option_key m_opt_id;//! {""};
int m_opt_idx = 0;
double opt_height{ 0.0 };
bool parent_is_custom_ctrl{ false };
/// Sets a value for this control.
/// subclasses should overload with a specific version
/// Postcondition: Method does not fire the on_change event.
virtual void set_value(const boost::any& value, bool change_event) = 0;
virtual void set_last_meaningful_value() {}
virtual void set_na_value() {}
/// Gets a boost::any representing this control.
/// subclasses should overload with a specific version
virtual boost::any& get_value() = 0;
virtual void enable() = 0;
virtual void disable() = 0;
/// Fires the enable or disable function, based on the input.
void toggle(bool en);
virtual wxString get_tooltip_text(const wxString& default_string);
void field_changed() { on_change_field(); }
Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {}
Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {}
virtual ~Field();
/// If you don't know what you are getting back, check both methods for nullptr.
virtual wxSizer* getSizer() { return nullptr; }
virtual wxWindow* getWindow() { return nullptr; }
bool is_matched(const std::string& string, const std::string& pattern);
void get_value_by_opt_type(wxString& str, const bool check_value = true);
/// Factory method for generating new derived classes.
template<class T>
static t_field Create(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id)// interface for creating shared objects
{
auto p = Slic3r::make_unique<T>(parent, opt, id);
p->PostInitialize();
return std::move(p); //!p;
}
bool set_undo_bitmap(const ScalableBitmap *bmp) {
if (m_undo_bitmap != bmp) {
m_undo_bitmap = bmp;
return true;
}
return false;
}
bool set_undo_to_sys_bitmap(const ScalableBitmap *bmp) {
if (m_undo_to_sys_bitmap != bmp) {
m_undo_to_sys_bitmap = bmp;
return true;
}
return false;
}
bool set_label_colour(const wxColour *clr) {
if (m_label_color != clr) {
m_label_color = clr;
}
return false;
}
bool set_undo_tooltip(const wxString *tip) {
if (m_undo_tooltip != tip) {
m_undo_tooltip = tip;
return true;
}
return false;
}
bool set_undo_to_sys_tooltip(const wxString *tip) {
if (m_undo_to_sys_tooltip != tip) {
m_undo_to_sys_tooltip = tip;
return true;
}
return false;
}
bool* get_blink_ptr() {
return &m_blink;
}
virtual void msw_rescale();
virtual void sys_color_changed();
bool get_enter_pressed() const { return bEnterPressed; }
void set_enter_pressed(bool pressed) { bEnterPressed = pressed; }
// Values of width to alignments of fields
static int def_width() ;
static int def_width_wider() ;
static int def_width_thinner() ;
const ScalableBitmap* undo_bitmap() { return m_undo_bitmap; }
const wxString* undo_tooltip() { return m_undo_tooltip; }
const ScalableBitmap* undo_to_sys_bitmap() { return m_undo_to_sys_bitmap; }
const wxString* undo_to_sys_tooltip() { return m_undo_to_sys_tooltip; }
const wxColour* label_color() { return m_label_color; }
const bool blink() { return m_blink; }
const bool combine_side_text() { return m_combine_side_text; } // QDS: new param style
protected:
// Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const ScalableBitmap* m_undo_bitmap = nullptr;
const wxString* m_undo_tooltip = nullptr;
// Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const ScalableBitmap* m_undo_to_sys_bitmap = nullptr;
const wxString* m_undo_to_sys_tooltip = nullptr;
bool m_blink{ false };
// Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_label_color = nullptr;
// current value
boost::any m_value;
// last maeningful value
boost::any m_last_meaningful_value;
int m_em_unit;
bool m_combine_side_text = false;
bool bEnterPressed = false;
friend class OptionsGroup;
};
/// Convenience function, accepts a const reference to t_field and checks to see whether
/// or not both wx pointers are null.
inline bool is_bad_field(const t_field& obj) { return obj->getSizer() == nullptr && obj->getWindow() == nullptr; }
/// Covenience function to determine whether this field is a valid window field.
inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && obj->getWindow() != nullptr && obj->getSizer() == nullptr; }
/// Covenience function to determine whether this field is a valid sizer field.
inline bool is_sizer_field(const t_field& obj) { return !is_bad_field(obj) && obj->getSizer() != nullptr; }
class TextCtrl : public Field {
using Field::Field;
#ifdef __WXGTK__
bool bChangedValueEvent = true;
void change_field_value(wxEvent& event);
#endif //__WXGTK__
public:
TextCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~TextCtrl() {}
void BUILD() override;
bool value_was_changed();
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value();
wxWindow* window {nullptr};
void set_value(const std::string& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxTextCtrl*>(window)->SetValue(wxString(value));
m_disable_change_event = false;
}
void set_value(const boost::any& value, bool change_event = false) override;
void set_last_meaningful_value() override;
void set_na_value() override;
boost::any& get_value() override;
void msw_rescale() override;
void enable() override;
void disable() override;
wxWindow* getWindow() override { return window; }
wxTextCtrl * text_ctrl();
};
class CheckBox : public Field {
using Field::Field;
bool m_is_na_val {false};
public:
CheckBox(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
CheckBox(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~CheckBox() {}
wxWindow* window{ nullptr };
void BUILD() override;
void set_value(const bool value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false) override;
void set_last_meaningful_value() override;
void set_na_value() override;
boost::any& get_value() override;
void msw_rescale() override;
void enable() override { window->Enable(); }
void disable() override { window->Disable(); }
wxWindow* getWindow() override { return window; }
};
class SpinCtrl : public Field {
using Field::Field;
private:
static const int UNDEF_VALUE = INT_MIN;
bool suppress_propagation {false};
public:
SpinCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id), tmp_value(UNDEF_VALUE) {}
SpinCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id), tmp_value(UNDEF_VALUE) {}
~SpinCtrl() {}
int tmp_value;
wxWindow* window{ nullptr };
void BUILD() override;
/// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value() ;
void set_value(const std::string& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<SpinInput*>(window)->SetValue(value);
m_disable_change_event = false;
}
void set_value(const boost::any& value, bool change_event = false) override;
boost::any& get_value() override {
int value = static_cast<SpinInput*>(window)->GetValue();
return m_value = value;
}
void msw_rescale() override;
void enable() override { dynamic_cast<SpinInput*>(window)->Enable(); }
void disable() override { dynamic_cast<SpinInput*>(window)->Disable(); }
wxWindow* getWindow() override { return window; }
};
class Choice;
class DynamicList
{
public:
virtual ~DynamicList() {}
virtual void apply_on(Choice * choice) = 0;
virtual wxString get_value(int index) = 0;
virtual int index_of(wxString value) = 0;
protected:
void update();
std::vector<Choice*> m_choices;
private:
friend class Choice;
void add_choice(Choice *choice);
void remove_choice(Choice *choice);
};
class Choice : public Field {
using Field::Field;
DynamicList * m_list = nullptr;
public:
Choice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
Choice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~Choice();
static void register_dynamic_list(std::string const &optname, DynamicList *list);
wxWindow* window{ nullptr };
void BUILD() override;
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value();
/* Under OSX: wxBitmapComboBox->GetWindowStyle() returns some weard value,
* so let use a flag, which has TRUE value for a control without wxCB_READONLY style
*/
bool m_is_editable { false };
bool m_is_dropped { false };
bool m_suppress_scroll { false };
int m_last_selected { wxNOT_FOUND };
void set_selection();
void set_value(const std::string& value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false) override;
void set_values(const std::vector<std::string> &values);
void set_values(const wxArrayString &values);
boost::any& get_value() override;
void set_last_meaningful_value() override;
void set_na_value() override;
void msw_rescale() override;
void enable() override ;//{ dynamic_cast<wxBitmapComboBox*>(window)->Enable(); };
void disable() override;//{ dynamic_cast<wxBitmapComboBox*>(window)->Disable(); };
wxWindow* getWindow() override { return window; }
void suppress_scroll();
};
class ColourPicker : public Field {
using Field::Field;
void set_undef_value(wxColourPickerCtrl* field);
public:
ColourPicker(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
ColourPicker(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~ColourPicker() {}
wxWindow* window{ nullptr };
void BUILD() override;
void set_value(const std::string& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(value);
m_disable_change_event = false;
}
void set_value(const boost::any& value, bool change_event = false) override;
boost::any& get_value() override;
void msw_rescale() override;
void sys_color_changed() override;
void enable() override { dynamic_cast<wxColourPickerCtrl*>(window)->Enable(); }
void disable() override{ dynamic_cast<wxColourPickerCtrl*>(window)->Disable(); }
wxWindow* getWindow() override { return window; }
private:
void convert_to_picker_widget(wxColourPickerCtrl *widget);
void on_button_click(wxCommandEvent &WXUNUSED(ev));
void save_colors_to_config();
private:
wxColourData* m_clrData{nullptr};
wxColourPickerWidget* m_picker_widget{nullptr};
};
class PointCtrl : public Field {
using Field::Field;
public:
PointCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
PointCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~PointCtrl() {}
wxSizer* sizer{ nullptr };
wxTextCtrl* x_textctrl{ nullptr };
wxTextCtrl* y_textctrl{ nullptr };
void BUILD() override;
bool value_was_changed(wxTextCtrl* win);
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value(wxTextCtrl* win);
void set_value(const Vec2d& value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false) override;
boost::any& get_value() override;
void msw_rescale() override;
void sys_color_changed() override;
void enable() override {
x_textctrl->Enable();
y_textctrl->Enable(); }
void disable() override{
x_textctrl->Disable();
y_textctrl->Disable(); }
wxSizer* getSizer() override { return sizer; }
wxWindow* getWindow() override { return dynamic_cast<wxWindow*>(x_textctrl); }
};
class StaticText : public Field {
using Field::Field;
public:
StaticText(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
StaticText(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~StaticText() {}
wxWindow* window{ nullptr };
void BUILD() override;
void set_value(const std::string& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxStaticText*>(window)->SetLabel(wxString::FromUTF8(value.data()));
m_disable_change_event = false;
}
void set_value(const boost::any& value, bool change_event = false) override {
m_disable_change_event = !change_event;
dynamic_cast<wxStaticText*>(window)->SetLabel(boost::any_cast<wxString>(value));
m_disable_change_event = false;
}
boost::any& get_value()override { return m_value; }
void msw_rescale() override;
void enable() override { dynamic_cast<wxStaticText*>(window)->Enable(); }
void disable() override{ dynamic_cast<wxStaticText*>(window)->Disable(); }
wxWindow* getWindow() override { return window; }
};
class SliderCtrl : public Field {
using Field::Field;
public:
SliderCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
SliderCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~SliderCtrl() {}
wxSizer* m_sizer{ nullptr };
wxTextCtrl* m_textctrl{ nullptr };
wxSlider* m_slider{ nullptr };
int m_scale = 10;
void BUILD() override;
void set_value(const int value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false) override;
boost::any& get_value() override;
void enable() override {
m_slider->Enable();
m_textctrl->Enable();
m_textctrl->SetEditable(true);
}
void disable() override{
m_slider->Disable();
m_textctrl->Disable();
m_textctrl->SetEditable(false);
}
wxSizer* getSizer() override { return m_sizer; }
wxWindow* getWindow() override { return dynamic_cast<wxWindow*>(m_slider); }
};
} // GUI
} // Slic3r
#endif /* SLIC3R_GUI_FIELD_HPP */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,928 @@
#ifndef slic3r_GCodeViewer_hpp_
#define slic3r_GCodeViewer_hpp_
#include "3DScene.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "IMSlider.hpp"
#include "GLModel.hpp"
#include "I18N.hpp"
#include <boost/iostreams/device/mapped_file.hpp>
#include <cstdint>
#include <float.h>
#include <set>
#include <unordered_set>
namespace Slic3r {
class Print;
class TriangleMesh;
class PresetBundle;
namespace GUI {
class PartPlateList;
class OpenGLManager;
class GCodeViewer
{
using IBufferType = unsigned short;
using Color = std::array<float, 4>;
using VertexBuffer = std::vector<float>;
using MultiVertexBuffer = std::vector<VertexBuffer>;
using IndexBuffer = std::vector<IBufferType>;
using MultiIndexBuffer = std::vector<IndexBuffer>;
using InstanceBuffer = std::vector<float>;
using InstanceIdBuffer = std::vector<size_t>;
using InstancesOffsets = std::vector<Vec3f>;
static const std::vector<Color> Extrusion_Role_Colors;
static const std::vector<Color> Options_Colors;
static const std::vector<Color> Travel_Colors;
static const std::vector<Color> Range_Colors;
static const Color Wipe_Color;
static const Color Neutral_Color;
enum class EOptionsColors : unsigned char
{
Retractions,
Unretractions,
Seams,
ToolChanges,
ColorChanges,
PausePrints,
CustomGCodes
};
// vbo buffer containing vertices data used to render a specific toolpath type
struct VBuffer
{
enum class EFormat : unsigned char
{
// vertex format: 3 floats -> position.x|position.y|position.z
Position,
// vertex format: 4 floats -> position.x|position.y|position.z|normal.x
PositionNormal1,
// vertex format: 6 floats -> position.x|position.y|position.z|normal.x|normal.y|normal.z
PositionNormal3
};
EFormat format{ EFormat::Position };
// vbos id
std::vector<unsigned int> vbos;
// sizes of the buffers, in bytes, used in export to obj
std::vector<size_t> sizes;
// count of vertices, updated after data are sent to gpu
size_t count{ 0 };
size_t data_size_bytes() const { return count * vertex_size_bytes(); }
// We set 65536 as max count of vertices inside a vertex buffer to allow
// to use unsigned short in place of unsigned int for indices in the index buffer, to save memory
size_t max_size_bytes() const { return 65536 * vertex_size_bytes(); }
size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); }
size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); }
size_t position_offset_floats() const { return 0; }
size_t position_offset_bytes() const { return position_offset_floats() * sizeof(float); }
size_t position_size_floats() const { return 3; }
size_t position_size_bytes() const { return position_size_floats() * sizeof(float); }
size_t normal_offset_floats() const {
assert(format == EFormat::PositionNormal1 || format == EFormat::PositionNormal3);
return position_size_floats();
}
size_t normal_offset_bytes() const { return normal_offset_floats() * sizeof(float); }
size_t normal_size_floats() const {
switch (format)
{
case EFormat::PositionNormal1: { return 1; }
case EFormat::PositionNormal3: { return 3; }
default: { return 0; }
}
}
size_t normal_size_bytes() const { return normal_size_floats() * sizeof(float); }
void reset();
};
// buffer containing instances data used to render a toolpaths using instanced or batched models
// instance record format:
// instanced models: 5 floats -> position.x|position.y|position.z|width|height (which are sent to the shader as -> vec3 (offset) + vec2 (scales) in GLModel::render_instanced())
// batched models: 3 floats -> position.x|position.y|position.z
struct InstanceVBuffer
{
// ranges used to render only subparts of the intances
struct Ranges
{
struct Range
{
// offset in bytes of the 1st instance to render
unsigned int offset;
// count of instances to render
unsigned int count;
// vbo id
unsigned int vbo{ 0 };
// Color to apply to the instances
Color color;
};
std::vector<Range> ranges;
void reset();
};
enum class EFormat : unsigned char
{
InstancedModel,
BatchedModel
};
EFormat format;
// cpu-side buffer containing all instances data
InstanceBuffer buffer;
// indices of the moves for all instances
std::vector<size_t> s_ids;
// position offsets, used to show the correct value of the tool position
InstancesOffsets offsets;
Ranges render_ranges;
size_t data_size_bytes() const { return s_ids.size() * instance_size_bytes(); }
size_t instance_size_floats() const {
switch (format)
{
case EFormat::InstancedModel: { return 5; }
case EFormat::BatchedModel: { return 3; }
default: { return 0; }
}
}
size_t instance_size_bytes() const { return instance_size_floats() * sizeof(float); }
void reset();
};
// ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type
struct IBuffer
{
// id of the associated vertex buffer
unsigned int vbo{ 0 };
// ibo id
unsigned int ibo{ 0 };
// count of indices, updated after data are sent to gpu
size_t count{ 0 };
void reset();
};
// Used to identify different toolpath sub-types inside a IBuffer
struct Path
{
struct Endpoint
{
// index of the buffer in the multibuffer vector
// the buffer type may change:
// it is the vertex buffer while extracting vertices data,
// the index buffer while extracting indices data
unsigned int b_id{ 0 };
// index into the buffer
size_t i_id{ 0 };
// move id
size_t s_id{ 0 };
Vec3f position{ Vec3f::Zero() };
};
struct Sub_Path
{
Endpoint first;
Endpoint last;
bool contains(size_t s_id) const {
return first.s_id <= s_id && s_id <= last.s_id;
}
};
EMoveType type{ EMoveType::Noop };
ExtrusionRole role{ erNone };
float delta_extruder{ 0.0f };
float height{ 0.0f };
float width{ 0.0f };
float feedrate{ 0.0f };
float fan_speed{ 0.0f };
float temperature{ 0.0f };
float volumetric_rate{ 0.0f };
float layer_time{ 0.0f };
unsigned char extruder_id{ 0 };
unsigned char cp_color_id{ 0 };
std::vector<Sub_Path> sub_paths;
bool matches(const GCodeProcessorResult::MoveVertex& move) const;
size_t vertices_count() const {
return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1;
}
bool contains(size_t s_id) const {
return sub_paths.empty() ? false : sub_paths.front().first.s_id <= s_id && s_id <= sub_paths.back().last.s_id;
}
int get_id_of_sub_path_containing(size_t s_id) const {
if (sub_paths.empty())
return -1;
else {
for (int i = 0; i < static_cast<int>(sub_paths.size()); ++i) {
if (sub_paths[i].contains(s_id))
return i;
}
return -1;
}
}
void add_sub_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) {
Endpoint endpoint = { b_id, i_id, s_id, move.position };
sub_paths.push_back({ endpoint , endpoint });
}
};
// Used to batch the indices needed to render the paths
struct RenderPath
{
// Index of the parent tbuffer
unsigned char tbuffer_id;
// Render path property
Color color;
// Index of the buffer in TBuffer::indices
unsigned int ibuffer_id;
// Render path content
// Index of the path in TBuffer::paths
unsigned int path_id;
std::vector<unsigned int> sizes;
std::vector<size_t> offsets; // use size_t because we need an unsigned integer whose size matches pointer's size (used in the call glMultiDrawElements())
bool contains(size_t offset) const {
for (size_t i = 0; i < offsets.size(); ++i) {
if (offsets[i] <= offset && offset <= offsets[i] + static_cast<size_t>(sizes[i] * sizeof(IBufferType)))
return true;
}
return false;
}
};
struct RenderPathPropertyLower {
bool operator() (const RenderPath &l, const RenderPath &r) const {
if (l.tbuffer_id < r.tbuffer_id)
return true;
for (int i = 0; i < 3; ++i) {
if (l.color[i] < r.color[i])
return true;
else if (l.color[i] > r.color[i])
return false;
}
return l.ibuffer_id < r.ibuffer_id;
}
};
struct RenderPathPropertyEqual {
bool operator() (const RenderPath &l, const RenderPath &r) const {
return l.tbuffer_id == r.tbuffer_id && l.ibuffer_id == r.ibuffer_id && l.color == r.color;
}
};
// buffer containing data for rendering a specific toolpath type
struct TBuffer
{
enum class ERenderPrimitiveType : unsigned char
{
Point,
Line,
Triangle,
InstancedModel,
BatchedModel
};
ERenderPrimitiveType render_primitive_type;
// buffers for point, line and triangle primitive types
VBuffer vertices;
std::vector<IBuffer> indices;
struct Model
{
GLModel model;
Color color;
InstanceVBuffer instances;
GLModel::InitializationData data;
void reset();
};
// contain the buffer for model primitive types
Model model;
std::string shader;
std::vector<Path> paths;
std::vector<RenderPath> render_paths;
bool visible{ false };
void reset();
// b_id index of buffer contained in this->indices
// i_id index of first index contained in this->indices[b_id]
// s_id index of first vertex contained in this->vertices
void add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id);
unsigned int max_vertices_per_segment() const {
switch (render_primitive_type)
{
case ERenderPrimitiveType::Point: { return 1; }
case ERenderPrimitiveType::Line: { return 2; }
case ERenderPrimitiveType::Triangle: { return 8; }
default: { return 0; }
}
}
size_t max_vertices_per_segment_size_floats() const { return vertices.vertex_size_floats() * static_cast<size_t>(max_vertices_per_segment()); }
size_t max_vertices_per_segment_size_bytes() const { return max_vertices_per_segment_size_floats() * sizeof(float); }
unsigned int indices_per_segment() const {
switch (render_primitive_type)
{
case ERenderPrimitiveType::Point: { return 1; }
case ERenderPrimitiveType::Line: { return 2; }
case ERenderPrimitiveType::Triangle: { return 30; } // 3 indices x 10 triangles
default: { return 0; }
}
}
size_t indices_per_segment_size_bytes() const { return static_cast<size_t>(indices_per_segment() * sizeof(IBufferType)); }
unsigned int max_indices_per_segment() const {
switch (render_primitive_type)
{
case ERenderPrimitiveType::Point: { return 1; }
case ERenderPrimitiveType::Line: { return 2; }
case ERenderPrimitiveType::Triangle: { return 36; } // 3 indices x 12 triangles
default: { return 0; }
}
}
size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); }
bool has_data() const {
switch (render_primitive_type)
{
case ERenderPrimitiveType::Point:
case ERenderPrimitiveType::Line:
case ERenderPrimitiveType::Triangle: {
return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0;
}
case ERenderPrimitiveType::InstancedModel: { return model.model.is_initialized() && !model.instances.buffer.empty(); }
case ERenderPrimitiveType::BatchedModel: {
return model.data.vertices_count() > 0 && model.data.indices_count() &&
!vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0;
}
default: { return false; }
}
}
};
// helper to render shells
struct Shells
{
GLVolumeCollection volumes;
bool visible{ false };
//QDS: always load shell when preview
int print_id{ -1 };
int print_modify_count { -1 };
bool previewing{ false };
};
// helper to render extrusion paths
struct Extrusions
{
struct Range
{
enum class EType : unsigned char {
Linear,
Logarithmic
};
float min;
float max;
unsigned int count;
bool log_scale;
Range() { reset(); }
void update_from(const float value) {
if (value != max && value != min)
++count;
min = std::min(min, value);
max = std::max(max, value);
}
void reset(bool log = false) { min = FLT_MAX; max = -FLT_MAX; count = 0; log_scale = log; }
float step_size(EType type = EType::Linear) const;
Color get_color_at(float value, EType type = EType::Linear) const;
float get_value_at_step(int step) const;
};
struct Ranges
{
// Color mapping by layer height.
Range height;
// Color mapping by extrusion width.
Range width;
// Color mapping by feedrate.
Range feedrate;
// Color mapping by fan speed.
Range fan_speed;
// Color mapping by volumetric extrusion rate.
Range volumetric_rate;
// Color mapping by extrusion temperature.
Range temperature;
// Color mapping by layer time.
Range layer_duration;
void reset() {
height.reset();
width.reset();
feedrate.reset();
fan_speed.reset();
volumetric_rate.reset();
temperature.reset();
layer_duration.reset(true);
}
};
unsigned int role_visibility_flags{ 0 };
Ranges ranges;
void reset_role_visibility_flags() {
role_visibility_flags = 0;
for (unsigned int i = 0; i < erCount; ++i) {
role_visibility_flags |= 1 << i;
}
}
void reset_ranges() { ranges.reset(); }
};
class Layers
{
public:
struct Endpoints
{
size_t first{ 0 };
size_t last{ 0 };
bool operator == (const Endpoints& other) const { return first == other.first && last == other.last; }
bool operator != (const Endpoints& other) const { return !operator==(other); }
};
private:
std::vector<double> m_zs;
std::vector<Endpoints> m_endpoints;
public:
void append(double z, Endpoints endpoints) {
m_zs.emplace_back(z);
m_endpoints.emplace_back(endpoints);
}
void reset() {
m_zs = std::vector<double>();
m_endpoints = std::vector<Endpoints>();
}
size_t size() const { return m_zs.size(); }
bool empty() const { return m_zs.empty(); }
const std::vector<double>& get_zs() const { return m_zs; }
const std::vector<Endpoints>& get_endpoints() const { return m_endpoints; }
std::vector<Endpoints>& get_endpoints() { return m_endpoints; }
double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; }
Endpoints get_endpoints_at(unsigned int id) const { return (id < m_endpoints.size()) ? m_endpoints[id] : Endpoints(); }
int get_l_at(float z) const
{
auto iter = std::upper_bound(m_zs.begin(), m_zs.end(), z);
return std::distance(m_zs.begin(), iter);
}
bool operator != (const Layers& other) const {
if (m_zs != other.m_zs)
return true;
if (m_endpoints != other.m_endpoints)
return true;
return false;
}
};
// used to render the toolpath caps of the current sequential range
// (i.e. when sliding on the horizontal slider)
struct SequentialRangeCap
{
TBuffer* buffer{ nullptr };
unsigned int ibo{ 0 };
unsigned int vbo{ 0 };
Color color;
~SequentialRangeCap();
bool is_renderable() const { return buffer != nullptr; }
void reset();
size_t indices_count() const { return 6; }
};
#if ENABLE_GCODE_VIEWER_STATISTICS
struct Statistics
{
// time
int64_t results_time{ 0 };
int64_t load_time{ 0 };
int64_t load_vertices{ 0 };
int64_t smooth_vertices{ 0 };
int64_t load_indices{ 0 };
int64_t refresh_time{ 0 };
int64_t refresh_paths_time{ 0 };
// opengl calls
int64_t gl_multi_points_calls_count{ 0 };
int64_t gl_multi_lines_calls_count{ 0 };
int64_t gl_multi_triangles_calls_count{ 0 };
int64_t gl_triangles_calls_count{ 0 };
int64_t gl_instanced_models_calls_count{ 0 };
int64_t gl_batched_models_calls_count{ 0 };
// memory
int64_t results_size{ 0 };
int64_t total_vertices_gpu_size{ 0 };
int64_t total_indices_gpu_size{ 0 };
int64_t total_instances_gpu_size{ 0 };
int64_t max_vbuffer_gpu_size{ 0 };
int64_t max_ibuffer_gpu_size{ 0 };
int64_t paths_size{ 0 };
int64_t render_paths_size{ 0 };
int64_t models_instances_size{ 0 };
// other
int64_t travel_segments_count{ 0 };
int64_t wipe_segments_count{ 0 };
int64_t extrude_segments_count{ 0 };
int64_t instances_count{ 0 };
int64_t batched_count{ 0 };
int64_t vbuffers_count{ 0 };
int64_t ibuffers_count{ 0 };
void reset_all() {
reset_times();
reset_opengl();
reset_sizes();
reset_others();
}
void reset_times() {
results_time = 0;
load_time = 0;
load_vertices = 0;
smooth_vertices = 0;
load_indices = 0;
refresh_time = 0;
refresh_paths_time = 0;
}
void reset_opengl() {
gl_multi_points_calls_count = 0;
gl_multi_lines_calls_count = 0;
gl_multi_triangles_calls_count = 0;
gl_triangles_calls_count = 0;
gl_instanced_models_calls_count = 0;
gl_batched_models_calls_count = 0;
}
void reset_sizes() {
results_size = 0;
total_vertices_gpu_size = 0;
total_indices_gpu_size = 0;
total_instances_gpu_size = 0;
max_vbuffer_gpu_size = 0;
max_ibuffer_gpu_size = 0;
paths_size = 0;
render_paths_size = 0;
models_instances_size = 0;
}
void reset_others() {
travel_segments_count = 0;
wipe_segments_count = 0;
extrude_segments_count = 0;
instances_count = 0;
batched_count = 0;
vbuffers_count = 0;
ibuffers_count = 0;
}
};
#endif // ENABLE_GCODE_VIEWER_STATISTICS
public:
enum class EViewType : unsigned char;
struct SequentialView
{
class Marker
{
GLModel m_model;
Vec3f m_world_position;
Transform3f m_world_transform;
// for seams, the position of the marker is on the last endpoint of the toolpath containing it
// the offset is used to show the correct value of tool position in the "ToolPosition" window
// see implementation of render() method
Vec3f m_world_offset;
float m_z_offset{ 0.5f };
GCodeProcessorResult::MoveVertex m_curr_move;
bool m_visible{ true };
bool m_is_dark = false;
public:
float m_scale = 1.0f;
void init(std::string filename);
const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); }
void set_world_position(const Vec3f& position);
void set_world_offset(const Vec3f& offset) { m_world_offset = offset; }
bool is_visible() const { return m_visible; }
void set_visible(bool visible) { m_visible = visible; }
//QDS: GUI refactor: add canvas size
void render(int canvas_width, int canvas_height, const EViewType& view_type) const;
void on_change_color_mode(bool is_dark) { m_is_dark = is_dark; }
void update_curr_move(const GCodeProcessorResult::MoveVertex move);
};
class GCodeWindow
{
struct Line
{
std::string command;
std::string parameters;
std::string comment;
};
bool m_is_dark = false;
bool m_visible{ true };
uint64_t m_selected_line_id{ 0 };
size_t m_last_lines_size{ 0 };
std::string m_filename;
boost::iostreams::mapped_file_source m_file;
// map for accessing data in file by line number
std::vector<size_t> m_lines_ends;
// current visible lines
std::vector<Line> m_lines;
public:
GCodeWindow() = default;
~GCodeWindow() { stop_mapping_file(); }
void load_gcode(const std::string& filename, const std::vector<size_t> &lines_ends);
void reset() {
stop_mapping_file();
m_lines_ends.clear();
m_lines_ends.shrink_to_fit();
m_lines.clear();
m_lines.shrink_to_fit();
m_filename.clear();
m_filename.shrink_to_fit();
}
void toggle_visibility() { m_visible = !m_visible; }
//QDS: GUI refactor: add canvas size
//void render(float top, float bottom, uint64_t curr_line_id) const;
void render(float top, float bottom, float right, uint64_t curr_line_id) const;
void on_change_color_mode(bool is_dark) { m_is_dark = is_dark; }
void stop_mapping_file();
};
struct Endpoints
{
size_t first{ 0 };
size_t last{ 0 };
};
bool skip_invisible_moves{ false };
Endpoints endpoints;
Endpoints current;
Endpoints last_current;
Endpoints global;
Vec3f current_position{ Vec3f::Zero() };
Vec3f current_offset{ Vec3f::Zero() };
Marker marker;
GCodeWindow gcode_window;
std::vector<unsigned int> gcode_ids;
float m_scale = 1.0;
//QDS: GUI refactor: add canvas size
void render(float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type) const;
};
struct ETools
{
std::vector<Color> m_tool_colors;
std::vector<bool> m_tool_visibles;
};
enum class EViewType : unsigned char
{
FeatureType = 0,
Height,
Width,
Feedrate,
FanSpeed,
Temperature,
VolumetricRate,
Tool,
ColorPrint,
FilamentId,
LayerTime,
Count
};
//QDS
ConflictResultOpt m_conflict_result;
private:
std::vector<int> m_plater_extruder;
bool m_gl_data_initialized{ false };
unsigned int m_last_result_id{ 0 };
size_t m_moves_count{ 0 };
//QDS: save m_gcode_result as well
const GCodeProcessorResult* m_gcode_result;
//QDS: add only gcode mode
bool m_only_gcode_in_preview {false};
std::vector<size_t> m_ssid_to_moveid_map;
std::vector<TBuffer> m_buffers{ static_cast<size_t>(EMoveType::Extrude) };
// bounding box of toolpaths
BoundingBoxf3 m_paths_bounding_box;
// bounding box of toolpaths + marker tools
BoundingBoxf3 m_max_bounding_box;
//QDS: add shell bounding box
BoundingBoxf3 m_shell_bounding_box;
float m_max_print_height{ 0.0f };
//QDS save m_tools_color and m_tools_visible
ETools m_tools;
ConfigOptionMode m_user_mode;
bool m_fold = {false};
Layers m_layers;
std::array<unsigned int, 2> m_layers_z_range;
std::vector<ExtrusionRole> m_roles;
size_t m_extruders_count;
std::vector<unsigned char> m_extruder_ids;
std::vector<float> m_filament_diameters;
std::vector<float> m_filament_densities;
Extrusions m_extrusions;
SequentialView m_sequential_view;
IMSlider* m_moves_slider;
IMSlider* m_layers_slider;
Shells m_shells;
/*QDS GUI refactor, store displayed items in color scheme combobox */
std::vector<EViewType> view_type_items;
std::vector<std::string> view_type_items_str;
int m_view_type_sel = 0;
EViewType m_view_type{ EViewType::FeatureType };
std::vector<EMoveType> options_items;
bool m_legend_enabled{ true };
float m_legend_height;
PrintEstimatedStatistics m_print_statistics;
PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal };
#if ENABLE_GCODE_VIEWER_STATISTICS
Statistics m_statistics;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
std::array<float, 2> m_detected_point_sizes = { 0.0f, 0.0f };
GCodeProcessorResult::SettingsIds m_settings_ids;
std::array<SequentialRangeCap, 2> m_sequential_range_caps;
std::vector<CustomGCode::Item> m_custom_gcode_per_print_z;
bool m_contained_in_bed{ true };
bool m_is_dark = false;
public:
GCodeViewer();
~GCodeViewer();
void on_change_color_mode(bool is_dark);
float m_scale = 1.0;
void set_scale(float scale = 1.0);
void init(ConfigOptionMode mode, Slic3r::PresetBundle* preset_bundle);
void update_by_mode(ConfigOptionMode mode);
// extract rendering data from the given parameters
//QDS: add only gcode mode
void load(const GCodeProcessorResult& gcode_result, const Print& print, const BuildVolume& build_volume,
const std::vector<BoundingBoxf3>& exclude_bounding_box, bool initialized, ConfigOptionMode mode, bool only_gcode = false);
// recalculate ranges in dependence of what is visible and sets tool/print colors
void refresh(const GCodeProcessorResult& gcode_result, const std::vector<std::string>& str_tool_colors);
void refresh_render_paths();
void update_shells_color_by_extruder(const DynamicPrintConfig* config);
void reset();
//QDS: always load shell at preview
void reset_shell();
void load_shells(const Print& print, bool initialized, bool force_previewing = false);
void set_shells_on_preview(bool is_previewing) { m_shells.previewing = is_previewing; }
//QDS: add all plates filament statistics
void render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show = true) const;
//QDS: GUI refactor: add canvas width and height
void render(int canvas_width, int canvas_height, int right_margin);
//QDS
void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
void render_calibration_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
bool has_data() const { return !m_roles.empty(); }
bool can_export_toolpaths() const;
std::vector<int> get_plater_extruder();
const float get_max_print_height() const { return m_max_print_height; }
const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; }
const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; }
const BoundingBoxf3& get_shell_bounding_box() const { return m_shell_bounding_box; }
const std::vector<double>& get_layers_zs() const { return m_layers.get_zs(); }
const std::array<unsigned int,2> &get_layers_z_range() const { return m_layers_z_range; }
const SequentialView& get_sequential_view() const { return m_sequential_view; }
void update_sequential_view_current(unsigned int first, unsigned int last);
/* QDS IMSlider */
IMSlider *get_moves_slider() { return m_moves_slider; }
IMSlider *get_layers_slider() { return m_layers_slider; }
void enable_moves_slider(bool enable) const;
void update_moves_slider(bool set_to_max = false);
void update_layers_slider_mode();
void update_marker_curr_move();
bool is_contained_in_bed() const { return m_contained_in_bed; }
//QDS: add only gcode mode
bool is_only_gcode_in_preview() const { return m_only_gcode_in_preview; }
EViewType get_view_type() const { return m_view_type; }
void set_view_type(EViewType type, bool reset_feature_type_visible = true) {
if (type == EViewType::Count)
type = EViewType::FeatureType;
m_view_type = (EViewType)type;
if (reset_feature_type_visible && type == EViewType::ColorPrint) {
reset_visible(EViewType::FeatureType);
}
}
void reset_visible(EViewType type) {
if (type == EViewType::FeatureType) {
for (size_t i = 0; i < m_roles.size(); ++i) {
ExtrusionRole role = m_roles[i];
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << role);
}
} else if (type == EViewType::ColorPrint){
for(auto item: m_tools.m_tool_visibles) item = true;
}
}
bool is_toolpath_move_type_visible(EMoveType type) const;
void set_toolpath_move_type_visible(EMoveType type, bool visible);
unsigned int get_toolpath_role_visibility_flags() const { return m_extrusions.role_visibility_flags; }
void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; }
unsigned int get_options_visibility_flags() const;
void set_options_visibility_from_flags(unsigned int flags);
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
bool is_legend_enabled() const { return m_legend_enabled; }
void enable_legend(bool enable) { m_legend_enabled = enable; }
float get_legend_height() { return m_legend_height; }
void export_toolpaths_to_obj(const char* filename) const;
void toggle_gcode_window_visibility() { m_sequential_view.gcode_window.toggle_visibility(); }
std::vector<CustomGCode::Item>& get_custom_gcode_per_print_z() { return m_custom_gcode_per_print_z; }
size_t get_extruders_count() { return m_extruders_count; }
void push_combo_style();
void pop_combo_style();
private:
void load_toolpaths(const GCodeProcessorResult& gcode_result, const BuildVolume& build_volume, const std::vector<BoundingBoxf3>& exclude_bounding_box);
//QDS: always load shell at preview
//void load_shells(const Print& print, bool initialized);
void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const;
void render_toolpaths();
void render_shells();
//QDS: GUI refactor: add canvas size
void render_legend(float &legend_height, int canvas_width, int canvas_height, int right_margin);
void render_slider(int canvas_width, int canvas_height);
#if ENABLE_GCODE_VIEWER_STATISTICS
void render_statistics();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
bool is_visible(ExtrusionRole role) const {
return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0;
}
bool is_visible(const Path& path) const { return is_visible(path.role); }
void log_memory_used(const std::string& label, int64_t additional = 0) const;
Color option_color(EMoveType move_type) const;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GCodeViewer_hpp_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1485
src/slic3r/GUI/GLModel.cpp Normal file

File diff suppressed because it is too large Load Diff

240
src/slic3r/GUI/GLModel.hpp Normal file
View File

@@ -0,0 +1,240 @@
#ifndef slic3r_GLModel_hpp_
#define slic3r_GLModel_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/Color.hpp"
#include "libslic3r/BoundingBox.hpp"
#include <vector>
#include <string>
struct indexed_triangle_set;
namespace Slic3r {
class TriangleMesh;
class Polygon;
using Polygons = std::vector<Polygon>;
namespace GUI {
class GLModel
{
public:
enum class PrimitiveType : unsigned char
{
Triangles,
Lines,
LineStrip,
LineLoop
};
struct Geometry
{
// enum class EPrimitiveType : unsigned char { Points, Triangles, TriangleStrip, TriangleFan, Lines, LineStrip, LineLoop };
enum class EVertexLayout : unsigned char {
P2, // position 2 floats
P2T2, // position 2 floats + texture coords 2 floats
P3, // position 3 floats
P3T2, // position 3 floats + texture coords 2 floats
P3N3, // position 3 floats + normal 3 floats
P3N3T2, // position 3 floats + normal 3 floats + texture coords 2 floats
P4, // position 4 floats
};
enum class EIndexType : unsigned char {
UINT, // unsigned int
USHORT, // unsigned short
UBYTE // unsigned byte
};
struct Format
{
PrimitiveType type{PrimitiveType::Triangles};
EVertexLayout vertex_layout{EVertexLayout::P3N3};
};
Format format;
std::vector<float> vertices;
std::vector<unsigned int> indices;
EIndexType index_type{EIndexType::UINT};
ColorRGBA color{ColorRGBA::BLACK()};
void reserve_vertices(size_t vertices_count) { vertices.reserve(vertices_count * vertex_stride_floats(format)); }
void reserve_indices(size_t indices_count) { indices.reserve(indices_count); }
void add_vertex(const Vec2f &position); // EVertexLayout::P2
void add_vertex(const Vec2f &position, const Vec2f &tex_coord); // EVertexLayout::P2T2
void add_vertex(const Vec3f &position); // EVertexLayout::P3
void add_vertex(const Vec3f &position, const Vec2f &tex_coord); // EVertexLayout::P3T2
void add_vertex(const Vec3f &position, const Vec3f &normal); // EVertexLayout::P3N3
void add_vertex(const Vec3f &position, const Vec3f &normal, const Vec2f &tex_coord); // EVertexLayout::P3N3T2
void add_vertex(const Vec4f &position); // EVertexLayout::P4
void set_vertex(size_t id, const Vec3f &position, const Vec3f &normal); // EVertexLayout::P3N3
void set_index(size_t id, unsigned int index);
void add_index(unsigned int id);
void add_line(unsigned int id1, unsigned int id2);
void add_triangle(unsigned int id1, unsigned int id2, unsigned int id3);
Vec2f extract_position_2(size_t id) const;
Vec3f extract_position_3(size_t id) const;
Vec3f extract_normal_3(size_t id) const;
Vec2f extract_tex_coord_2(size_t id) const;
unsigned int extract_index(size_t id) const;
void remove_vertex(size_t id);
bool is_empty() const { return vertices_count() == 0 || indices_count() == 0; }
size_t vertices_count() const { return vertices.size() / vertex_stride_floats(format); }
size_t indices_count() const { return indices.size(); }
size_t vertices_size_floats() const { return vertices.size(); }
size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); }
size_t indices_size_bytes() const { return indices.size() * index_stride_bytes(*this); }
indexed_triangle_set get_as_indexed_triangle_set() const;
static size_t vertex_stride_floats(const Format &format);
static size_t vertex_stride_bytes(const Format &format) { return vertex_stride_floats(format) * sizeof(float); }
static size_t position_stride_floats(const Format &format);
static size_t position_stride_bytes(const Format &format) { return position_stride_floats(format) * sizeof(float); }
static size_t position_offset_floats(const Format &format);
static size_t position_offset_bytes(const Format &format) { return position_offset_floats(format) * sizeof(float); }
static size_t normal_stride_floats(const Format &format);
static size_t normal_stride_bytes(const Format &format) { return normal_stride_floats(format) * sizeof(float); }
static size_t normal_offset_floats(const Format &format);
static size_t normal_offset_bytes(const Format &format) { return normal_offset_floats(format) * sizeof(float); }
static size_t tex_coord_stride_floats(const Format &format);
static size_t tex_coord_stride_bytes(const Format &format) { return tex_coord_stride_floats(format) * sizeof(float); }
static size_t tex_coord_offset_floats(const Format &format);
static size_t tex_coord_offset_bytes(const Format &format) { return tex_coord_offset_floats(format) * sizeof(float); }
static size_t index_stride_bytes(const Geometry &data);
static bool has_position(const Format &format);
static bool has_normal(const Format &format);
static bool has_tex_coord(const Format &format);
};
struct RenderData
{
Geometry geometry;
PrimitiveType type;
unsigned int vbo_id{0};
unsigned int ibo_id{0};
size_t indices_count{0};
std::array<float, 4> color{1.0f, 1.0f, 1.0f, 1.0f};
};
struct InitializationData
{
struct Entity
{
PrimitiveType type;
std::vector<Vec3f> positions;
std::vector<Vec3f> normals;
std::vector<unsigned int> indices;
std::array<float, 4> color{ 1.0f, 1.0f, 1.0f, 1.0f };
};
std::vector<Entity> entities;
size_t vertices_count() const;
size_t vertices_size_floats() const { return vertices_count() * 6; }
size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); }
size_t indices_count() const;
size_t indices_size_bytes() const { return indices_count() * sizeof(unsigned int); }
};
private:
std::vector<RenderData> m_render_data;
BoundingBoxf3 m_bounding_box;
std::string m_filename;
public:
GLModel() = default;
virtual ~GLModel();
TriangleMesh *mesh{nullptr};
void init_from(Geometry &&data, bool generate_mesh = false);
void init_from(const InitializationData& data);
void init_from(const indexed_triangle_set& its, const BoundingBoxf3& bbox);
void init_from(const indexed_triangle_set& its);
void init_from(const Polygons& polygons, float z);
bool init_from_file(const std::string& filename);
// if entity_id == -1 set the color of all entities
void set_color(int entity_id, const std::array<float, 4>& color);
void reset();
void render() const;
void render_instanced(unsigned int instances_vbo, unsigned int instances_count) const;
bool is_initialized() const { return !m_render_data.empty(); }
const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; }
const std::string& get_filename() const { return m_filename; }
private:
bool send_to_gpu(RenderData &data, const std::vector<float> &vertices, const std::vector<unsigned int> &indices) const;
};
// create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution
// the origin of the arrow is in the center of the stem cap
// the arrow has its axis of symmetry along the Z axis and is pointing upward
// used to render bed axes and sequential marker
GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height);
// create an arrow whose stem is a quarter of circle, with the given dimensions and resolution
// the origin of the arrow is in the center of the circle
// the arrow is contained in the 1st quadrant of the XY plane and is pointing counterclockwise
// used to render sidebar hints for rotations
GLModel::InitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness);
// create an arrow with the given dimensions
// the origin of the arrow is in the center of the stem cap
// the arrow is contained in XY plane and has its main axis along the Y axis
// used to render sidebar hints for position and scale
GLModel::InitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness);
// create a diamond with the given resolution
// the origin of the diamond is in its center
// the diamond is contained into a box with size [1, 1, 1]
GLModel::InitializationData diamond(int resolution);
// create a sphere with smooth normals
// the origin of the sphere is in its center
GLModel::Geometry smooth_sphere(unsigned int resolution, float radius);
// create a cylinder with smooth normals
// the axis of the cylinder is the Z axis
// the origin of the cylinder is the center of its bottom cap face
GLModel::Geometry smooth_cylinder(unsigned int resolution, float radius, float height);
// create a torus with smooth normals
// the axis of the torus is the Z axis
// the origin of the torus is in its center
GLModel::Geometry smooth_torus(unsigned int primary_resolution, unsigned int secondary_resolution, float radius, float thickness);
std::shared_ptr<GLModel> init_plane_data(const indexed_triangle_set &its, const std::vector<int> &triangle_indices,float normal_offset = 0.0f);
std::shared_ptr<GLModel> init_torus_data(unsigned int primary_resolution,
unsigned int secondary_resolution,
const Vec3f & center,
float radius,
float thickness,
const Vec3f & model_axis,
const Transform3f &world_trafo);
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLModel_hpp_

View File

@@ -0,0 +1,129 @@
#include "GLSelectionRectangle.hpp"
#include "Camera.hpp"
#include "3DScene.hpp"
#include "GLCanvas3D.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include <igl/project.h>
#include <GL/glew.h>
namespace Slic3r {
namespace GUI {
void GLSelectionRectangle::start_dragging(const Vec2d& mouse_position, EState state)
{
if (is_dragging() || (state == Off))
return;
m_state = state;
m_start_corner = mouse_position;
m_end_corner = mouse_position;
}
void GLSelectionRectangle::dragging(const Vec2d& mouse_position)
{
if (!is_dragging())
return;
m_end_corner = mouse_position;
}
std::vector<unsigned int> GLSelectionRectangle::stop_dragging(const GLCanvas3D& canvas, const std::vector<Vec3d>& points)
{
std::vector<unsigned int> out;
if (!is_dragging())
return out;
m_state = Off;
const Camera& camera = wxGetApp().plater()->get_camera();
Matrix4d modelview = camera.get_view_matrix().matrix();
Matrix4d projection= camera.get_projection_matrix().matrix();
Vec4i viewport(camera.get_viewport().data());
// Convert our std::vector to Eigen dynamic matrix.
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign> pts(points.size(), 3);
for (size_t i=0; i<points.size(); ++i)
pts.block<1, 3>(i, 0) = points[i];
// Get the projections.
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign> projections;
igl::project(pts, modelview, projection, viewport, projections);
// bounding box created from the rectangle corners - will take care of order of the corners
BoundingBox rectangle(Points{ Point(m_start_corner.cast<coord_t>()), Point(m_end_corner.cast<coord_t>()) });
// Iterate over all points and determine whether they're in the rectangle.
for (int i = 0; i<projections.rows(); ++i)
if (rectangle.contains(Point(projections(i, 0), canvas.get_canvas_size().get_height() - projections(i, 1))))
out.push_back(i);
return out;
}
void GLSelectionRectangle::stop_dragging()
{
if (is_dragging())
m_state = Off;
}
void GLSelectionRectangle::render(const GLCanvas3D& canvas) const
{
if (!is_dragging())
return;
const Camera& camera = wxGetApp().plater()->get_camera();
float inv_zoom = (float)camera.get_inv_zoom();
Size cnv_size = canvas.get_canvas_size();
float cnv_half_width = 0.5f * (float)cnv_size.get_width();
float cnv_half_height = 0.5f * (float)cnv_size.get_height();
if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f))
return;
Vec2d start(m_start_corner(0) - cnv_half_width, cnv_half_height - m_start_corner(1));
Vec2d end(m_end_corner(0) - cnv_half_width, cnv_half_height - m_end_corner(1));
float left = (float)std::min(start(0), end(0)) * inv_zoom;
float top = (float)std::max(start(1), end(1)) * inv_zoom;
float right = (float)std::max(start(0), end(0)) * inv_zoom;
float bottom = (float)std::min(start(1), end(1)) * inv_zoom;
glsafe(::glLineWidth(1.5f));
float color[3];
// y19
color[0] = 0.00f;
color[1] = 0.38f;
color[2] = 1.0f;
glsafe(::glColor3fv(color));
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
// ensure that the rectangle is renderered inside the frustrum
glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5)));
// ensure that the overlay fits the frustrum near z plane
double gui_scale = camera.get_gui_scale();
glsafe(::glScaled(gui_scale, gui_scale, 1.0));
glsafe(::glPushAttrib(GL_ENABLE_BIT));
glsafe(::glLineStipple(4, 0xAAAA));
glsafe(::glEnable(GL_LINE_STIPPLE));
::glBegin(GL_LINE_LOOP);
::glVertex2f((GLfloat)left, (GLfloat)bottom);
::glVertex2f((GLfloat)right, (GLfloat)bottom);
::glVertex2f((GLfloat)right, (GLfloat)top);
::glVertex2f((GLfloat)left, (GLfloat)top);
glsafe(::glEnd());
glsafe(::glPopAttrib());
glsafe(::glPopMatrix());
}
} // namespace GUI
} // namespace Slic3r

View File

@@ -0,0 +1,56 @@
#ifndef slic3r_GLSelectionRectangle_hpp_
#define slic3r_GLSelectionRectangle_hpp_
#include "libslic3r/Point.hpp"
namespace Slic3r {
namespace GUI {
struct Camera;
class GLCanvas3D;
class GLSelectionRectangle {
public:
enum EState {
Off,
Select,
Deselect
};
// Initiates the rectangle.
void start_dragging(const Vec2d& mouse_position, EState state);
// To be called on mouse move.
void dragging(const Vec2d& mouse_position);
// Given a vector of points in world coordinates, the function returns indices of those
// that are in the rectangle. It then disables the rectangle.
std::vector<unsigned int> stop_dragging(const GLCanvas3D& canvas, const std::vector<Vec3d>& points);
// Disables the rectangle.
void stop_dragging();
void render(const GLCanvas3D& canvas) const;
bool is_dragging() const { return m_state != Off; }
EState get_state() const { return m_state; }
float get_width() const { return std::abs(m_start_corner(0) - m_end_corner(0)); }
float get_height() const { return std::abs(m_start_corner(1) - m_end_corner(1)); }
float get_left() const { return std::min(m_start_corner(0), m_end_corner(0)); }
float get_right() const { return std::max(m_start_corner(0), m_end_corner(0)); }
float get_top() const { return std::max(m_start_corner(1), m_end_corner(1)); }
float get_bottom() const { return std::min(m_start_corner(1), m_end_corner(1)); }
private:
EState m_state = Off;
Vec2d m_start_corner;
Vec2d m_end_corner;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoSlaSupports_hpp_

395
src/slic3r/GUI/GLShader.cpp Normal file
View File

@@ -0,0 +1,395 @@
#include "libslic3r/libslic3r.h"
#include "GLShader.hpp"
#include "3DScene.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/format.hpp"
#include <boost/nowide/fstream.hpp>
#include <GL/glew.h>
#include <cassert>
#include <boost/log/trivial.hpp>
namespace Slic3r {
GLShaderProgram::~GLShaderProgram()
{
if (m_id > 0)
glsafe(::glDeleteProgram(m_id));
}
bool GLShaderProgram::init_from_files(const std::string& name, const ShaderFilenames& filenames, const std::initializer_list<std::string_view> &defines)
{
// Load a shader program from file, prepend defs block.
auto load_from_file = [](const std::string& filename, const std::string &defs) {
std::string path = resources_dir() + "/shaders/" + filename;
boost::nowide::ifstream s(path, boost::nowide::ifstream::binary);
if (!s.good()) {
BOOST_LOG_TRIVIAL(error) << "Couldn't open file: '" << path << "'";
return std::string();
}
s.seekg(0, s.end);
int file_length = static_cast<int>(s.tellg());
s.seekg(0, s.beg);
std::string source(defs.size() + file_length, '\0');
memcpy(source.data(), defs.c_str(), defs.size());
s.read(source.data() + defs.size(), file_length);
if (!s.good()) {
BOOST_LOG_TRIVIAL(error) << "Error while loading file: '" << path << "'";
return std::string();
}
s.close();
if (! defs.empty()) {
// Extract the version and flip the order of "defines" and version in the source block.
size_t idx = source.find("\n", defs.size());
if (idx != std::string::npos && strncmp(source.c_str() + defs.size(), "#version", 8) == 0) {
// Swap the version line with the defines.
size_t len = idx - defs.size() + 1;
memmove(source.data(), source.c_str() + defs.size(), len);
memcpy(source.data() + len, defs.c_str(), defs.size());
}
}
return source;
};
// Create a block of C "defines" from list of symbols.
std::string defines_program;
for (std::string_view def : defines)
// Our shaders are stored with "\r\n", thus replicate the same here for consistency. Likely "\n" would suffice,
// but we don't know all the OpenGL shader compilers around.
defines_program += format("#define %s\r\n", def);
ShaderSources sources = {};
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
sources[i] = filenames[i].empty() ? std::string() : load_from_file(filenames[i], defines_program);
}
bool valid = !sources[static_cast<size_t>(EShaderType::Vertex)].empty() && !sources[static_cast<size_t>(EShaderType::Fragment)].empty() && sources[static_cast<size_t>(EShaderType::Compute)].empty();
valid |= !sources[static_cast<size_t>(EShaderType::Compute)].empty() && sources[static_cast<size_t>(EShaderType::Vertex)].empty() && sources[static_cast<size_t>(EShaderType::Fragment)].empty() &&
sources[static_cast<size_t>(EShaderType::Geometry)].empty() && sources[static_cast<size_t>(EShaderType::TessEvaluation)].empty() && sources[static_cast<size_t>(EShaderType::TessControl)].empty();
return valid ? init_from_texts(name, sources) : false;
}
bool GLShaderProgram::init_from_texts(const std::string& name, const ShaderSources& sources)
{
auto shader_type_as_string = [](EShaderType type) {
switch (type)
{
case EShaderType::Vertex: { return "vertex"; }
case EShaderType::Fragment: { return "fragment"; }
case EShaderType::Geometry: { return "geometry"; }
case EShaderType::TessEvaluation: { return "tesselation evaluation"; }
case EShaderType::TessControl: { return "tesselation control"; }
case EShaderType::Compute: { return "compute"; }
default: { return "unknown"; }
}
};
auto create_shader = [](EShaderType type) {
GLuint id = 0;
switch (type)
{
case EShaderType::Vertex: { id = ::glCreateShader(GL_VERTEX_SHADER); glcheck(); break; }
case EShaderType::Fragment: { id = ::glCreateShader(GL_FRAGMENT_SHADER); glcheck(); break; }
case EShaderType::Geometry: { id = ::glCreateShader(GL_GEOMETRY_SHADER); glcheck(); break; }
case EShaderType::TessEvaluation: { id = ::glCreateShader(GL_TESS_EVALUATION_SHADER); glcheck(); break; }
case EShaderType::TessControl: { id = ::glCreateShader(GL_TESS_CONTROL_SHADER); glcheck(); break; }
case EShaderType::Compute: { id = ::glCreateShader(GL_COMPUTE_SHADER); glcheck(); break; }
default: { break; }
}
return (id == 0) ? std::make_pair(false, GLuint(0)) : std::make_pair(true, id);
};
auto release_shaders = [](const std::array<GLuint, static_cast<size_t>(EShaderType::Count)>& shader_ids) {
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
if (shader_ids[i] > 0)
glsafe(::glDeleteShader(shader_ids[i]));
}
};
assert(m_id == 0);
m_name = name;
std::array<GLuint, static_cast<size_t>(EShaderType::Count)> shader_ids = { 0 };
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
const std::string& source = sources[i];
if (!source.empty())
{
EShaderType type = static_cast<EShaderType>(i);
auto [result, id] = create_shader(type);
if (result)
shader_ids[i] = id;
else {
BOOST_LOG_TRIVIAL(error) << "glCreateShader() failed for " << shader_type_as_string(type) << " shader of shader program '" << name << "'";
// release shaders
release_shaders(shader_ids);
return false;
}
const char* source_ptr = source.c_str();
glsafe(::glShaderSource(id, 1, &source_ptr, nullptr));
glsafe(::glCompileShader(id));
GLint params;
glsafe(::glGetShaderiv(id, GL_COMPILE_STATUS, &params));
if (params == GL_FALSE) {
// Compilation failed.
glsafe(::glGetShaderiv(id, GL_INFO_LOG_LENGTH, &params));
std::vector<char> msg(params);
glsafe(::glGetShaderInfoLog(id, params, &params, msg.data()));
BOOST_LOG_TRIVIAL(error) << "Unable to compile " << shader_type_as_string(type) << " shader of shader program '" << name << "':\n" << msg.data();
// release shaders
release_shaders(shader_ids);
return false;
}
}
}
m_id = ::glCreateProgram();
glcheck();
if (m_id == 0) {
BOOST_LOG_TRIVIAL(error) << "glCreateProgram() failed for shader program '" << name << "'";
// release shaders
release_shaders(shader_ids);
return false;
}
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
if (shader_ids[i] > 0)
glsafe(::glAttachShader(m_id, shader_ids[i]));
}
glsafe(::glLinkProgram(m_id));
GLint params;
glsafe(::glGetProgramiv(m_id, GL_LINK_STATUS, &params));
if (params == GL_FALSE) {
// Linking failed.
glsafe(::glGetProgramiv(m_id, GL_INFO_LOG_LENGTH, &params));
std::vector<char> msg(params);
glsafe(::glGetProgramInfoLog(m_id, params, &params, msg.data()));
BOOST_LOG_TRIVIAL(error) << "Unable to link shader program '" << name << "':\n" << msg.data();
// release shaders
release_shaders(shader_ids);
// release shader program
glsafe(::glDeleteProgram(m_id));
m_id = 0;
return false;
}
// release shaders, they are no more needed
release_shaders(shader_ids);
return true;
}
void GLShaderProgram::start_using() const
{
assert(m_id > 0);
glsafe(::glUseProgram(m_id));
}
void GLShaderProgram::stop_using() const
{
glsafe(::glUseProgram(0));
}
bool GLShaderProgram::set_uniform(const char* name, int value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform1i(id, static_cast<GLint>(value)));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, bool value) const
{
return set_uniform(name, value ? 1 : 0);
}
bool GLShaderProgram::set_uniform(const char* name, float value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform1f(id, static_cast<GLfloat>(value)));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, double value) const
{
return set_uniform(name, static_cast<float>(value));
}
bool GLShaderProgram::set_uniform(const char* name, const std::array<int, 2>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform2iv(id, 1, static_cast<const GLint*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const std::array<int, 3>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform3iv(id, 1, static_cast<const GLint*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const std::array<int, 4>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform4iv(id, 1, static_cast<const GLint*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const std::array<float, 2>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform2fv(id, 1, static_cast<const GLfloat*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const std::array<float, 3>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const std::array<float, 4>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform4fv(id, 1, static_cast<const GLfloat*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const float* value, size_t size) const
{
if (size == 1)
return set_uniform(name, value[0]);
else if (size < 5) {
int id = get_uniform_location(name);
if (id >= 0) {
if (size == 2)
glsafe(::glUniform2fv(id, 1, static_cast<const GLfloat*>(value)));
else if (size == 3)
glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value)));
else
glsafe(::glUniform4fv(id, 1, static_cast<const GLfloat*>(value)));
return true;
}
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const Transform3f& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast<const GLfloat*>(value.matrix().data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const Transform3d& value) const
{
return set_uniform(name, value.cast<float>());
}
bool GLShaderProgram::set_uniform(const char* name, const Matrix3f& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniformMatrix3fv(id, 1, GL_FALSE, static_cast<const GLfloat*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const Vec3f& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const Vec3d& value) const
{
return set_uniform(name, static_cast<Vec3f>(value.cast<float>()));
}
int GLShaderProgram::get_attrib_location(const char* name) const
{
assert(m_id > 0);
if (m_id <= 0)
// Shader program not loaded. This should not happen.
return -1;
auto it = std::find_if(m_attrib_location_cache.begin(), m_attrib_location_cache.end(), [name](const auto& p) { return p.first == name; });
if (it != m_attrib_location_cache.end())
// Attrib ID cached.
return it->second;
int id = ::glGetAttribLocation(m_id, name);
const_cast<GLShaderProgram*>(this)->m_attrib_location_cache.push_back({ name, id });
return id;
}
int GLShaderProgram::get_uniform_location(const char* name) const
{
assert(m_id > 0);
if (m_id <= 0)
// Shader program not loaded. This should not happen.
return -1;
auto it = std::find_if(m_uniform_location_cache.begin(), m_uniform_location_cache.end(), [name](const auto &p) { return p.first == name; });
if (it != m_uniform_location_cache.end())
// Uniform ID cached.
return it->second;
int id = ::glGetUniformLocation(m_id, name);
const_cast<GLShaderProgram*>(this)->m_uniform_location_cache.push_back({ name, id });
return id;
}
} // namespace Slic3r

View File

@@ -0,0 +1,72 @@
#ifndef slic3r_GLShader_hpp_
#define slic3r_GLShader_hpp_
#include <array>
#include <string>
#include <string_view>
#include "libslic3r/Point.hpp"
namespace Slic3r {
class GLShaderProgram
{
public:
enum class EShaderType
{
Vertex,
Fragment,
Geometry,
TessEvaluation,
TessControl,
Compute,
Count
};
typedef std::array<std::string, static_cast<size_t>(EShaderType::Count)> ShaderFilenames;
typedef std::array<std::string, static_cast<size_t>(EShaderType::Count)> ShaderSources;
private:
std::string m_name;
unsigned int m_id{ 0 };
std::vector<std::pair<std::string, int>> m_attrib_location_cache;
std::vector<std::pair<std::string, int>> m_uniform_location_cache;
public:
~GLShaderProgram();
bool init_from_files(const std::string& name, const ShaderFilenames& filenames, const std::initializer_list<std::string_view> &defines = {});
bool init_from_texts(const std::string& name, const ShaderSources& sources);
const std::string& get_name() const { return m_name; }
unsigned int get_id() const { return m_id; }
void start_using() const;
void stop_using() const;
bool set_uniform(const char* name, int value) const;
bool set_uniform(const char* name, bool value) const;
bool set_uniform(const char* name, float value) const;
bool set_uniform(const char* name, double value) const;
bool set_uniform(const char* name, const std::array<int, 2>& value) const;
bool set_uniform(const char* name, const std::array<int, 3>& value) const;
bool set_uniform(const char* name, const std::array<int, 4>& value) const;
bool set_uniform(const char* name, const std::array<float, 2>& value) const;
bool set_uniform(const char* name, const std::array<float, 3>& value) const;
bool set_uniform(const char* name, const std::array<float, 4>& value) const;
bool set_uniform(const char* name, const float* value, size_t size) const;
bool set_uniform(const char* name, const Transform3f& value) const;
bool set_uniform(const char* name, const Transform3d& value) const;
bool set_uniform(const char* name, const Matrix3f& value) const;
bool set_uniform(const char* name, const Vec3f& value) const;
bool set_uniform(const char* name, const Vec3d& value) const;
// returns -1 if not found
int get_attrib_location(const char* name) const;
// returns -1 if not found
int get_uniform_location(const char* name) const;
};
} // namespace Slic3r
#endif /* slic3r_GLShader_hpp_ */

View File

@@ -0,0 +1,119 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/Platform.hpp"
#include "GLShadersManager.hpp"
#include "3DScene.hpp"
#include "GUI_App.hpp"
#include <cassert>
#include <algorithm>
#include <string_view>
using namespace std::literals;
#include <GL/glew.h>
namespace Slic3r {
std::pair<bool, std::string> GLShadersManager::init()
{
std::string error;
auto append_shader = [this, &error](const std::string& name, const GLShaderProgram::ShaderFilenames& filenames,
const std::initializer_list<std::string_view> &defines = {}) {
m_shaders.push_back(std::make_unique<GLShaderProgram>());
if (!m_shaders.back()->init_from_files(name, filenames, defines)) {
error += name + "\n";
// if any error happens while initializating the shader, we remove it from the list
m_shaders.pop_back();
return false;
}
return true;
};
assert(m_shaders.empty());
bool valid = true;
// used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview
valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" });
//used to render thumbnail
valid &= append_shader("thumbnail", { "thumbnail.vs", "thumbnail.fs" });
// used to render first layer for calibration
valid &= append_shader("cali", { "cali.vs", "cali.fs"});
valid &= append_shader("flat", {"flat.vs", "flat.fs"});
// used to render printbed
valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" });
// used to render options in gcode preview
if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3))
valid &= append_shader("gouraud_light_instanced", { "gouraud_light_instanced.vs", "gouraud_light_instanced.fs" });
// used to render extrusion and travel paths as lines in gcode preview
valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" });
// used to render objects in 3d editor
//if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 0)) {
if (0) {
valid &= append_shader("gouraud", { "gouraud_130.vs", "gouraud_130.fs" }
#if ENABLE_ENVIRONMENT_MAP
, { "ENABLE_ENVIRONMENT_MAP"sv }
#endif // ENABLE_ENVIRONMENT_MAP
);
}
else {
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }
#if ENABLE_ENVIRONMENT_MAP
, { "ENABLE_ENVIRONMENT_MAP"sv }
#endif // ENABLE_ENVIRONMENT_MAP
);
}
// used to render variable layers heights in 3d editor
valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" });
// used to render highlight contour around selected triangles inside the multi-material gizmo
valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" });
// Used to render painted triangles inside the multi-material gizmo. Triangle normals are computed inside fragment shader.
// For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction.
// Because of this, objects had darker colors inside the multi-material gizmo.
// Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU.
// Since macOS 12 (Monterey), this issue with the opposite direction on Apple's Arm CPU seems to be fixed, and computed
// triangle normals inside fragment shader have the right direction.
if (platform_flavor() == PlatformFlavor::OSXOnArm && wxPlatformInfo::Get().GetOSMajorVersion() < 12) {
//if (GUI::wxGetApp().plater() && GUI::wxGetApp().plater()->is_wireframe_enabled())
// valid &= append_shader("mm_gouraud", {"mm_gouraud_wireframe.vs", "mm_gouraud_wireframe.fs"}, {"FLIP_TRIANGLE_NORMALS"sv});
//else
valid &= append_shader("mm_gouraud", {"mm_gouraud_wireframe.vs", "mm_gouraud_wireframe.fs"}, {"FLIP_TRIANGLE_NORMALS"sv});//{"mm_gouraud.vs", "mm_gouraud.fs"}
}
else {
//if (GUI::wxGetApp().plater() && GUI::wxGetApp().plater()->is_wireframe_enabled())
// valid &= append_shader("mm_gouraud", {"mm_gouraud_wireframe.vs", "mm_gouraud_wireframe.fs"});
//else
valid &= append_shader("mm_gouraud", {"mm_gouraud_wireframe.vs", "mm_gouraud_wireframe.fs"});//{"mm_gouraud.vs", "mm_gouraud.fs"}
}
//QDS: add shader for outline
valid &= append_shader("outline", { "outline.vs", "outline.fs" });
return { valid, error };
}
void GLShadersManager::shutdown()
{
m_shaders.clear();
}
GLShaderProgram* GLShadersManager::get_shader(const std::string& shader_name)
{
auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [&shader_name](std::unique_ptr<GLShaderProgram>& p) { return p->get_name() == shader_name; });
return (it != m_shaders.end()) ? it->get() : nullptr;
}
GLShaderProgram* GLShadersManager::get_current_shader()
{
GLint id = 0;
glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &id));
if (id == 0)
return nullptr;
auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [id](std::unique_ptr<GLShaderProgram>& p) { return static_cast<GLint>(p->get_id()) == id; });
return (it != m_shaders.end()) ? it->get() : nullptr;
}
} // namespace Slic3r

View File

@@ -0,0 +1,30 @@
#ifndef slic3r_GLShadersManager_hpp_
#define slic3r_GLShadersManager_hpp_
#include "GLShader.hpp"
#include <vector>
#include <string>
#include <memory>
namespace Slic3r {
class GLShadersManager
{
std::vector<std::unique_ptr<GLShaderProgram>> m_shaders;
public:
std::pair<bool, std::string> init();
// call this method before to release the OpenGL context
void shutdown();
// returns nullptr if not found
GLShaderProgram* get_shader(const std::string& shader_name);
// returns currently active shader, nullptr if none
GLShaderProgram* get_current_shader();
};
} // namespace Slic3r
#endif // slic3r_GLShadersManager_hpp_

View File

@@ -0,0 +1,959 @@
//QDS:add i18n
#include "I18N.hpp"
//QDS: add fstream for debug output
//#include <fstream>
#include "libslic3r/libslic3r.h"
#include "GLTexture.hpp"
#include "3DScene.hpp"
#include "OpenGLManager.hpp"
#include <GL/glew.h>
#include <wx/image.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <vector>
#include <algorithm>
#include <thread>
#define STB_DXT_IMPLEMENTATION
#include "stb_dxt/stb_dxt.h"
#include "nanosvg/nanosvg.h"
#include "nanosvg/nanosvgrast.h"
#include "libslic3r/Utils.hpp"
#include "GUI_App.hpp"
#include <boost/log/trivial.hpp>
#include <wx/dcgraph.h>
#include "FontUtils.hpp"
namespace Slic3r {
namespace GUI {
void GLTexture::Compressor::reset()
{
if (m_thread.joinable()) {
m_abort_compressing = true;
m_thread.join();
m_levels.clear();
m_num_levels_compressed = 0;
m_abort_compressing = false;
}
assert(m_levels.empty());
assert(m_abort_compressing == false);
assert(m_num_levels_compressed == 0);
}
void GLTexture::Compressor::start_compressing()
{
// The worker thread should be stopped already.
assert(! m_thread.joinable());
assert(! m_levels.empty());
assert(m_abort_compressing == false);
assert(m_num_levels_compressed == 0);
if (! m_levels.empty()) {
std::thread thrd(&GLTexture::Compressor::compress, this);
m_thread = std::move(thrd);
}
}
bool GLTexture::Compressor::unsent_compressed_data_available() const
{
if (m_levels.empty())
return false;
// Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the data of m_levels modified by the worker thread are accessible to the calling thread.
unsigned int num_compressed = m_num_levels_compressed;
for (unsigned int i = 0; i < num_compressed; ++ i)
if (! m_levels[i].sent_to_gpu && ! m_levels[i].compressed_data.empty())
return true;
return false;
}
void GLTexture::Compressor::send_compressed_data_to_gpu()
{
// this method should be called inside the main thread of Slicer or a new OpenGL context (sharing resources) would be needed
if (m_levels.empty())
return;
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.m_id));
// Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the dat of m_levels modified by the worker thread are accessible to the calling thread.
int num_compressed = (int)m_num_levels_compressed;
for (int i = 0; i < num_compressed; ++ i) {
Level& level = m_levels[i];
if (! level.sent_to_gpu && ! level.compressed_data.empty()) {
glsafe(::glCompressedTexSubImage2D(GL_TEXTURE_2D, (GLint)i, 0, 0, (GLsizei)level.w, (GLsizei)level.h, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)level.compressed_data.size(), (const GLvoid*)level.compressed_data.data()));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (i > 0) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR));
level.sent_to_gpu = true;
// we are done with the compressed data, we can discard it
level.compressed_data.clear();
}
}
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
if (num_compressed == (int)m_levels.size())
// Finalize the worker thread, close it.
this->reset();
}
void GLTexture::Compressor::compress()
{
// reference: https://github.com/Cyan4973/RygsDXTc
assert(m_num_levels_compressed == 0);
assert(m_abort_compressing == false);
for (Level& level : m_levels) {
if (m_abort_compressing)
break;
// stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4,
// crashes if doing so, requiring a minimum of 64 bytes and up to a third of the source buffer size, so we set the destination buffer initial size to be half the source buffer size
level.compressed_data = std::vector<unsigned char>(std::max((unsigned int)64, (unsigned int)level.src_data.size() / 2), 0);
int compressed_size = 0;
rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size);
level.compressed_data.resize(compressed_size);
// we are done with the source data, we can discard it
level.src_data.clear();
++ m_num_levels_compressed;
}
}
GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } };
GLTexture::GLTexture()
: m_id(0)
, m_width(0)
, m_height(0)
, m_source("")
, m_compressor(*this)
{
}
GLTexture::~GLTexture()
{
reset();
}
bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy)
{
reset();
if (!boost::filesystem::exists(filename))
return false;
if (boost::algorithm::iends_with(filename, ".png"))
return load_from_png(filename, use_mipmaps, compression_type, apply_anisotropy);
else
return false;
}
bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px)
{
reset();
if (!boost::filesystem::exists(filename))
return false;
if (boost::algorithm::iends_with(filename, ".svg"))
return load_from_svg(filename, use_mipmaps, compress, apply_anisotropy, max_size_px);
else
return false;
}
bool GLTexture::load_from_raw_data(std::vector<unsigned char> data, unsigned int w, unsigned int h, bool apply_anisotropy)
{
m_width = w;
m_height = h;
int n_pixels = m_width * m_height;
if (n_pixels <= 0) {
reset();
return false;
}
// sends data to gpu
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
if (apply_anisotropy) {
GLfloat max_anisotropy = OpenGLManager::get_gl_info().get_max_anisotropy();
if (max_anisotropy > 1.0f)
glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy));
}
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
bool use_mipmaps = true;
if (use_mipmaps) {
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
int lod_w = m_width;
int lod_h = m_height;
GLint level = 0;
while (lod_w > 1 || lod_h > 1) {
++level;
lod_w = std::max(lod_w / 2, 1);
lod_h = std::max(lod_h / 2, 1);
n_pixels = lod_w * lod_h;
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
}
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
}
else {
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
}
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
#if 0
// debug output
static int pass = 0;
++pass;
wxImage output(m_width, m_height);
output.InitAlpha();
for (int h = 0; h < m_height; ++h) {
int px_h = h * m_width;
for (int w = 0; w < m_width; ++w) {
int offset = (px_h + w) * 4;
output.SetRGB(w, h, data.data()[offset + 0], data.data()[offset + 1], data.data()[offset + 2]);
output.SetAlpha(w, h, data.data()[offset + 3]);
}
}
std::string out_filename = resources_dir() + "/images/test_" + std::to_string(pass) + ".png";
output.SaveFile(out_filename, wxBITMAP_TYPE_PNG);
#endif // 0
return true;
}
bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector<std::string>& filenames, const std::vector<std::pair<int, bool>>& states, unsigned int sprite_size_px, bool compress)
{
reset();
if (filenames.empty() || states.empty() || sprite_size_px == 0)
return false;
bool dark_mode = wxGetApp().app_config->get("dark_color_mode") == "1";
// every tile needs to have a 1px border around it to avoid artifacts when linear sampling on its edges
unsigned int sprite_size_px_ex = sprite_size_px + 1;
m_width = 1 + (int)(sprite_size_px_ex * states.size());
m_height = 1 + (int)(sprite_size_px_ex * filenames.size());
int n_pixels = m_width * m_height;
int sprite_n_pixels = sprite_size_px_ex * sprite_size_px_ex;
int sprite_stride = sprite_size_px_ex * 4;
int sprite_bytes = sprite_n_pixels * 4;
if (n_pixels <= 0) {
reset();
return false;
}
std::vector<unsigned char> data(n_pixels * 4, 0);
std::vector<unsigned char> sprite_data(sprite_bytes, 0);
std::vector<unsigned char> sprite_white_only_data(sprite_bytes, 0); // normal
std::vector<unsigned char> sprite_gray_only_data(sprite_bytes, 0); // disable
std::vector<unsigned char> output_data(sprite_bytes, 0);
//QDS
std::vector<unsigned char> pressed_data(sprite_bytes, 0); // (gizmo) pressed
std::vector<unsigned char> disable_data(sprite_bytes, 0);
std::vector<unsigned char> hover_data(sprite_bytes, 0); // hover
const unsigned char pressed_color[3] = {255, 255, 255};
const unsigned char hover_color[3] = {255, 255, 255};
const unsigned char normal_color[3] = {43, 52, 54};
const unsigned char disable_color[3] = {200, 200, 200};
const unsigned char pressed_color_dark[3] = {60, 60, 65};
const unsigned char hover_color_dark[3] = {60, 60, 65};
const unsigned char normal_color_dark[3] = {182, 182, 182};
const unsigned char disable_color_dark[3] = {76, 76, 85};
NSVGrasterizer* rast = nsvgCreateRasterizer();
if (rast == nullptr) {
reset();
return false;
}
int sprite_id = -1;
for (const std::string& filename : filenames) {
++sprite_id;
if (!boost::filesystem::exists(filename))
continue;
if (!boost::algorithm::iends_with(filename, ".svg"))
continue;
NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f);
if (image == nullptr)
continue;
float scale = (float)sprite_size_px / std::max(image->width, image->height);
// offset by 1 to leave the first pixel empty (both in x and y)
nsvgRasterize(rast, image, 1, 1, scale, sprite_data.data(), sprite_size_px, sprite_size_px, sprite_stride);
::memcpy((void*)pressed_data.data(), (const void*)sprite_data.data(), sprite_bytes);
for (int i = 0; i < sprite_n_pixels; ++i) {
int offset = i * 4;
if (pressed_data.data()[offset + 0] == 0 &&
pressed_data.data()[offset + 1] == 0 &&
pressed_data.data()[offset + 2] == 0) {
hover_data.data()[offset + 0] = dark_mode ? pressed_color_dark[0] : pressed_color[0];
hover_data.data()[offset + 0] = dark_mode ? pressed_color_dark[1] : pressed_color[1];
hover_data.data()[offset + 0] = dark_mode ? pressed_color_dark[2] : pressed_color[2];
}
}
::memcpy((void*)disable_data.data(), (const void*)sprite_data.data(), sprite_bytes);
for (int i = 0; i < sprite_n_pixels; ++i) {
int offset = i * 4;
if (disable_data.data()[offset] != 0)
::memset((void*)&disable_data.data()[offset], 200, 3);
}
::memcpy((void*)hover_data.data(), (const void*)sprite_data.data(), sprite_bytes);
for (int i = 0; i < sprite_n_pixels; ++i) {
int offset = i * 4;
if (hover_data.data()[offset + 0] == 0 &&
hover_data.data()[offset + 1] == 0 &&
hover_data.data()[offset + 2] == 0)
{
hover_data.data()[offset + 0] = dark_mode ? hover_color_dark[0] : hover_color[0];
hover_data.data()[offset + 1] = dark_mode ? hover_color_dark[1] : hover_color[1];
hover_data.data()[offset + 2] = dark_mode ? hover_color_dark[2] : hover_color[2];
}
}
::memcpy((void*)sprite_white_only_data.data(), (const void*)sprite_data.data(), sprite_bytes);
for (int i = 0; i < sprite_n_pixels; ++i) {
int offset = i * 4;
if (sprite_white_only_data.data()[offset + 0] != 0 ||
sprite_white_only_data.data()[offset + 1] != 0 ||
sprite_white_only_data.data()[offset + 2] != 0) {
sprite_white_only_data.data()[offset + 0] = dark_mode ? normal_color_dark[0] : normal_color[0];
sprite_white_only_data.data()[offset + 1] = dark_mode ? normal_color_dark[1] : normal_color[1];
sprite_white_only_data.data()[offset + 2] = dark_mode ? normal_color_dark[2] : normal_color[2];
}
}
::memcpy((void*)sprite_gray_only_data.data(), (const void*)sprite_data.data(), sprite_bytes);
for (int i = 0; i < sprite_n_pixels; ++i) {
int offset = i * 4;
if (sprite_gray_only_data.data()[offset + 0] != 0 ||
sprite_gray_only_data.data()[offset + 1] != 0 ||
sprite_gray_only_data.data()[offset + 2] != 0) {
sprite_gray_only_data.data()[offset + 0] = dark_mode ? disable_color_dark[0] : disable_color[0];
sprite_gray_only_data.data()[offset + 1] = dark_mode ? disable_color_dark[1] : disable_color[1];
sprite_gray_only_data.data()[offset + 2] = dark_mode ? disable_color_dark[2] : disable_color[2];
}
}
int sprite_offset_px = sprite_id * (int)sprite_size_px_ex * m_width;
int state_id = -1;
for (const std::pair<int, bool>& state : states) {
++state_id;
// select the sprite variant
std::vector<unsigned char>* src = nullptr;
switch (state.first)
{
case 1: { src = &sprite_white_only_data; break; }
case 2: { src = &sprite_gray_only_data; break; }
default: { src = &hover_data; break; }
}
// applies background, if needed
if (state.second) {
src = &pressed_data;
}
::memcpy((void*)output_data.data(), (const void*)src->data(), sprite_bytes);
//QDS use QDS pressed style
//if (state.second) {
// float inv_255 = 1.0f / 255.0f;
// // offset by 1 to leave the first pixel empty (both in x and y)
// for (unsigned int r = 1; r <= sprite_size_px; ++r) {
// unsigned int offset_r = r * sprite_size_px_ex;
// for (unsigned int c = 1; c <= sprite_size_px; ++c) {
// unsigned int offset = (offset_r + c) * 4;
// float alpha = (float)output_data.data()[offset + 3] * inv_255;
// output_data.data()[offset + 0] = (unsigned char)(output_data.data()[offset + 0] * alpha);
// output_data.data()[offset + 1] = (unsigned char)(output_data.data()[offset + 1] * alpha);
// output_data.data()[offset + 2] = (unsigned char)(output_data.data()[offset + 2] * alpha);
// output_data.data()[offset + 3] = (unsigned char)(128 * (1.0f - alpha) + output_data.data()[offset + 3] * alpha);
// }
// }
//}
int state_offset_px = sprite_offset_px + state_id * sprite_size_px_ex;
for (int j = 0; j < (int)sprite_size_px_ex; ++j) {
::memcpy((void*)&data.data()[(state_offset_px + j * m_width) * 4], (const void*)&output_data.data()[j * sprite_stride], sprite_stride);
}
}
nsvgDelete(image);
}
nsvgDeleteRasterizer(rast);
// sends data to gpu
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
m_source = filenames.front();
#if 0
// debug output
static int pass = 0;
++pass;
wxImage output(m_width, m_height);
output.InitAlpha();
for (int h = 0; h < m_height; ++h) {
int px_h = h * m_width;
for (int w = 0; w < m_width; ++w) {
int offset = (px_h + w) * 4;
output.SetRGB(w, h, data.data()[offset + 0], data.data()[offset + 1], data.data()[offset + 2]);
output.SetAlpha(w, h, data.data()[offset + 3]);
}
}
std::string out_filename = resources_dir() + "/images/test_" + std::to_string(pass) + ".png";
output.SaveFile(out_filename, wxBITMAP_TYPE_PNG);
#endif // 0
return true;
}
void GLTexture::reset()
{
if (m_id != 0)
glsafe(::glDeleteTextures(1, &m_id));
m_id = 0;
m_width = 0;
m_height = 0;
m_source = "";
m_compressor.reset();
//QDS: GUI refactor
m_original_width = m_original_height = 0;
}
bool GLTexture::generate_from_text_string(const std::string& text_str, wxFont &font, wxColor background, wxColor foreground)
{
int w,h,hl;
return generate_from_text(text_str, font, background, foreground);
}
bool GLTexture::generate_from_text(const std::string &text_str, wxFont &font, wxColor background, wxColor foreground)
{
if (text_str.empty())
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":no text string, should not happen\n";
return false;
}
wxString msg = from_u8(text_str);
wxMemoryDC memDC;
memDC.SetFont(font);
// calculates texture size
wxCoord w, h;
memDC.GetMultiLineTextExtent(msg, &w, &h);
m_original_width = (int)w;
m_original_height = (int)h;
m_width = (int)next_highest_power_of_2((uint32_t)w);
m_height = (int)next_highest_power_of_2((uint32_t)h);
// generates bitmap
wxBitmap bitmap(m_width, m_height);
memDC.SelectObject(bitmap);
memDC.SetBackground(wxBrush(background));
memDC.Clear();
// draw message
memDC.SetTextForeground(*wxWHITE);
memDC.DrawLabel(msg, wxRect(0, 0, m_original_width, m_original_height), wxALIGN_CENTER);
memDC.SelectObject(wxNullBitmap);
// Convert the bitmap into a linear data ready to be loaded into the GPU.
wxImage image = bitmap.ConvertToImage();
// prepare buffer
std::vector<unsigned char> data(4 * m_width * m_height, 0);
const unsigned char* src = image.GetData();
if (!src) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":font ConvertToImage error:\n" << text_str << "," << font.GetBaseFont().GetNativeFontInfoDesc().ToStdString();
return false;
}
/* for debug use
std::ofstream fout;
fout.open(text_str+std::to_string(m_width)+"_"+std::to_string(m_height)+".rgb", std::ios::out);
fout.write((const char*)src, 3 * m_width * m_height);
fout.close();*/
for (int h = 0; h < m_height; ++h) {
unsigned char* dst = data.data() + 4 * h * m_width;
for (int w = 0; w < m_width; ++w) {
*dst++ = foreground.Red();
*dst++ = foreground.Green();
*dst++ = foreground.Blue();
*dst++ = (unsigned char)std::min<int>(255, *src);
src += 3;
}
}
// sends buffer to gpu
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id));
if (GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
return true;
}
bool GLTexture::generate_texture_from_text(const std::string& text_str, wxFont& font, int& ww, int& hh, int& hl, wxColor background, wxColor foreground)
{
if (text_str.empty())
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":no text string, should not happen\n";
return false;
}
wxString msg = _(text_str);
wxMemoryDC memDC;
memDC.SetFont(font);
// calculates texture size
wxCoord w, h, ll;
wxClientDC dc(wxGetApp().GetTopWindow());
dc.SetFont(font);
dc.GetMultiLineTextExtent(msg, &w, &h, &ll, &font);
m_original_width = (int)w;
m_original_height = (int)h;
m_width = (int)next_highest_power_of_2((uint32_t)w);
m_height = (int)next_highest_power_of_2((uint32_t)h);
ww = m_width;
hh = m_height;
hl = ll;
// generates bitmap
wxBitmap bitmap(m_width, m_height);
memDC.SelectObject(bitmap);
memDC.SetBackground(wxBrush(background));
memDC.Clear();
// draw message
memDC.SetTextForeground(*wxWHITE);
memDC.DrawLabel(msg, wxRect(0, 0, m_width, m_height), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
memDC.SelectObject(wxNullBitmap);
// Convert the bitmap into a linear data ready to be loaded into the GPU.
wxImage image = bitmap.ConvertToImage();
// prepare buffer
std::vector<unsigned char> data(4 * m_width * m_height, 0);
const unsigned char* src = image.GetData();
if (!src) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":font ConvertToImage error:\n" << text_str << "," << font.GetBaseFont().GetNativeFontInfoDesc().ToStdString();
return false;
}
/* for debug use
std::ofstream fout;
fout.open(text_str+std::to_string(m_width)+"_"+std::to_string(m_height)+".rgb", std::ios::out);
fout.write((const char*)src, 3 * m_width * m_height);
fout.close();*/
bool found = false;
for (int h = 0; h < m_height; ++h) {
unsigned char* dst = data.data() + 4 * h * m_width;
for (int w = 0; w < m_width; ++w) {
*dst++ = foreground.Red();
*dst++ = foreground.Green();
*dst++ = foreground.Blue();
*dst++ = (unsigned char)std::min<int>(255, *src);
if ((*src) != background.Red() && !found) {
found = true;
if (m_height - h < font.GetPointSize())
return false;
}
src += 3;
}
}
if (!found)
return false;
found = false;
src -= 3;
for (int h = m_height; h > 0; --h) {
for (int w = m_width; w > 0; --w) {
if ((*src) != background.Red() && !found) {
found = true;
if (h < font.GetPointSize())
return false;
}
src -= 3;
}
}
if (!found)
return false;
// sends buffer to gpu
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id));
if (GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
return true;
}
void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top)
{
render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs);
}
void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs)
{
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glEnable(GL_TEXTURE_2D));
glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id));
::glBegin(GL_QUADS);
::glTexCoord2f(uvs.left_bottom.u, uvs.left_bottom.v); ::glVertex2f(left, bottom);
::glTexCoord2f(uvs.right_bottom.u, uvs.right_bottom.v); ::glVertex2f(right, bottom);
::glTexCoord2f(uvs.right_top.u, uvs.right_top.v); ::glVertex2f(right, top);
::glTexCoord2f(uvs.left_top.u, uvs.left_top.v); ::glVertex2f(left, top);
glsafe(::glEnd());
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glDisable(GL_TEXTURE_2D));
glsafe(::glDisable(GL_BLEND));
}
bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy)
{
bool compression_enabled = (compression_type != None) && GLEW_EXT_texture_compression_s3tc;
// Load a PNG with an alpha channel.
wxImage image;
if (!image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_PNG)) {
reset();
return false;
}
m_width = image.GetWidth();
m_height = image.GetHeight();
bool requires_rescale = false;
if (compression_enabled && compression_type == MultiThreaded) {
// the stb_dxt compression library seems to like only texture sizes which are a multiple of 4
int width_rem = m_width % 4;
int height_rem = m_height % 4;
if (width_rem != 0) {
m_width += (4 - width_rem);
requires_rescale = true;
}
if (height_rem != 0) {
m_height += (4 - height_rem);
requires_rescale = true;
}
}
if (requires_rescale)
image = image.ResampleBicubic(m_width, m_height);
int n_pixels = m_width * m_height;
if (n_pixels <= 0) {
reset();
return false;
}
// Get RGB & alpha raw data from wxImage, pack them into an array.
unsigned char* img_rgb = image.GetData();
if (img_rgb == nullptr) {
reset();
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":load_from_png error\n";
return false;
}
unsigned char* img_alpha = image.GetAlpha();
std::vector<unsigned char> data(n_pixels * 4, 0);
for (int i = 0; i < n_pixels; ++i) {
int data_id = i * 4;
int img_id = i * 3;
data[data_id + 0] = img_rgb[img_id + 0];
data[data_id + 1] = img_rgb[img_id + 1];
data[data_id + 2] = img_rgb[img_id + 2];
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
}
// sends data to gpu
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
if (apply_anisotropy) {
GLfloat max_anisotropy = OpenGLManager::get_gl_info().get_max_anisotropy();
if (max_anisotropy > 1.0f)
glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy));
}
if (compression_enabled) {
if (compression_type == SingleThreaded)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else {
// initializes the texture on GPU
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
// and send the uncompressed data to the compressor
m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data);
}
}
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (use_mipmaps) {
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
int lod_w = m_width;
int lod_h = m_height;
GLint level = 0;
while (lod_w > 1 || lod_h > 1) {
++level;
lod_w = std::max(lod_w / 2, 1);
lod_h = std::max(lod_h / 2, 1);
n_pixels = lod_w * lod_h;
image = image.ResampleBicubic(lod_w, lod_h);
data.resize(n_pixels * 4);
img_rgb = image.GetData();
img_alpha = image.GetAlpha();
for (int i = 0; i < n_pixels; ++i) {
int data_id = i * 4;
int img_id = i * 3;
data[data_id + 0] = img_rgb[img_id + 0];
data[data_id + 1] = img_rgb[img_id + 1];
data[data_id + 2] = img_rgb[img_id + 2];
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
}
if (compression_enabled) {
if (compression_type == SingleThreaded)
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else {
// initializes the texture on GPU
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
// and send the uncompressed data to the compressor
m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data);
}
}
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
}
if (!compression_enabled) {
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
}
}
else {
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
}
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
m_source = filename;
if (compression_enabled && compression_type == MultiThreaded)
// start asynchronous compression
m_compressor.start_compressing();
return true;
}
bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px)
{
bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc;
NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f);
if (image == nullptr) {
reset();
return false;
}
float scale = (float)max_size_px / std::max(image->width, image->height);
m_width = (int)(scale * image->width);
m_height = (int)(scale * image->height);
if (compression_enabled) {
// the stb_dxt compression library seems to like only texture sizes which are a multiple of 4
int width_rem = m_width % 4;
int height_rem = m_height % 4;
if (width_rem != 0)
m_width += (4 - width_rem);
if (height_rem != 0)
m_height += (4 - height_rem);
}
int n_pixels = m_width * m_height;
if (n_pixels <= 0) {
reset();
nsvgDelete(image);
return false;
}
NSVGrasterizer* rast = nsvgCreateRasterizer();
if (rast == nullptr) {
nsvgDelete(image);
reset();
return false;
}
// creates the temporary buffer only once, with max size, and reuse it for all the levels, if generating mipmaps
std::vector<unsigned char> data(n_pixels * 4, 0);
nsvgRasterize(rast, image, 0, 0, scale, data.data(), m_width, m_height, m_width * 4);
// sends data to gpu
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
if (apply_anisotropy) {
GLfloat max_anisotropy = OpenGLManager::get_gl_info().get_max_anisotropy();
if (max_anisotropy > 1.0f)
glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy));
}
if (compression_enabled) {
// initializes the texture on GPU
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
// and send the uncompressed data to the compressor
m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data);
}
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (use_mipmaps && OpenGLManager::use_manually_generated_mipmaps()) {
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
int lod_w = m_width;
int lod_h = m_height;
GLint level = 0;
while (lod_w >= 4 && lod_h >= 4) {
++level;
lod_w = std::max(lod_w / 2, 1);
lod_h = std::max(lod_h / 2, 1);
scale /= 2.0f;
data.resize(lod_w * lod_h * 4);
nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4);
if (compression_enabled) {
// initializes the texture on GPU
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
// and send the uncompressed data to the compressor
m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data);
}
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
}
if (!compression_enabled) {
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
}
} else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) {
glGenerateMipmap(GL_TEXTURE_2D);
} else {
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
}
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
m_source = filename;
if (compression_enabled)
// start asynchronous compression
m_compressor.start_compressing();
nsvgDeleteRasterizer(rast);
nsvgDelete(image);
return true;
}
} // namespace GUI
} // namespace Slic3r

View File

@@ -0,0 +1,142 @@
#ifndef slic3r_GLTexture_hpp_
#define slic3r_GLTexture_hpp_
#include <atomic>
#include <string>
#include <vector>
#include <thread>
#include <wx/colour.h>
#include <wx/font.h>
class wxImage;
namespace Slic3r {
namespace GUI {
class GLTexture
{
class Compressor
{
struct Level
{
unsigned int w;
unsigned int h;
std::vector<unsigned char> src_data;
std::vector<unsigned char> compressed_data;
bool sent_to_gpu;
Level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data) : w(w), h(h), src_data(data), sent_to_gpu(false) {}
};
GLTexture& m_texture;
std::vector<Level> m_levels;
std::thread m_thread;
// Does the caller want the background thread to stop?
// This atomic also works as a memory barrier for synchronizing the cancel event with the worker thread.
std::atomic<bool> m_abort_compressing;
// How many levels were compressed since the start of the background processing thread?
// This atomic also works as a memory barrier for synchronizing results of the worker thread with the calling thread.
std::atomic<unsigned int> m_num_levels_compressed;
public:
explicit Compressor(GLTexture& texture) : m_texture(texture), m_abort_compressing(false), m_num_levels_compressed(0) {}
~Compressor() { reset(); }
void reset();
void add_level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data) { m_levels.emplace_back(w, h, data); }
void start_compressing();
bool unsent_compressed_data_available() const;
void send_compressed_data_to_gpu();
bool all_compressed_data_sent_to_gpu() const { return m_levels.empty(); }
private:
void compress();
};
public:
enum ECompressionType : unsigned char
{
None,
SingleThreaded,
MultiThreaded
};
struct UV
{
float u;
float v;
};
struct Quad_UVs
{
UV left_bottom;
UV right_bottom;
UV right_top;
UV left_top;
};
static Quad_UVs FullTextureUVs;
protected:
unsigned int m_id;
int m_width;
int m_height;
std::string m_source;
Compressor m_compressor;
public:
GLTexture();
virtual ~GLTexture();
bool load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy);
bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px);
//QDS load GLTexture from raw pixel data
bool load_from_raw_data(std::vector<unsigned char> data, unsigned int w, unsigned int h, bool apply_anisotropy = false);
// meanings of states: (std::pair<int, bool>)
// first field (int):
// 0 -> no changes
// 1 -> use white only color variant
// 2 -> use gray only color variant
// second field (bool):
// false -> no changes
// true -> add background color
bool load_from_svg_files_as_sprites_array(const std::vector<std::string>& filenames, const std::vector<std::pair<int, bool>>& states, unsigned int sprite_size_px, bool compress);
void reset();
//QDS: add generate logic for text strings
int m_original_width;
int m_original_height;
bool generate_texture_from_text(const std::string& text_str, wxFont& font, int& ww, int& hh, int &hl, wxColor background = *wxBLACK, wxColor foreground = *wxWHITE);
bool generate_from_text(const std::string& text_str, wxFont& font, wxColor background = *wxBLACK, wxColor foreground = *wxWHITE);
bool generate_from_text_string(const std::string& text_str, wxFont& font, wxColor background = *wxBLACK, wxColor foreground = *wxWHITE);
unsigned int get_id() const { return m_id; }
int get_original_width() const { return m_original_width; }
int get_width() const { return m_width; }
int get_height() const { return m_height; }
const std::string& get_source() const { return m_source; }
bool unsent_compressed_data_available() const { return m_compressor.unsent_compressed_data_available(); }
void send_compressed_data_to_gpu() { m_compressor.send_compressed_data_to_gpu(); }
bool all_compressed_data_sent_to_gpu() const { return m_compressor.all_compressed_data_sent_to_gpu(); }
static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs);
private:
bool load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy);
bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px);
friend class Compressor;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLTexture_hpp_

Some files were not shown because too many files have changed in this diff Show More